/*
* scamper_privsep.c: code that does root-required tasks
*
* $Id: scamper_privsep.c,v 1.47 2007/05/10 02:25:30 mjl Exp $
*
* Matthew Luckie
*
* Copyright (C) 2004-2007 The University of Waikato
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef WITHOUT_PRIVSEP
#if defined(__APPLE__)
#include <stdint.h>
#endif
#if defined(__sun__)
#define HAVE_ACCRIGHTS
#endif
#if defined(__FreeBSD__)
#define HAVE_SETPROCTITLE
#endif
#if defined(__NetBSD__)
#define HAVE_SETPROCTITLE
#endif
#if defined(__OpenBSD__)
#define HAVE_SETPROCTITLE
#endif
#if defined(__DragonFly__)
#define HAVE_SETPROCTITLE
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <pwd.h>
#include <time.h>
#include <grp.h>
#include <netdb.h>
#include <assert.h>
#if defined(DMALLOC)
#include <dmalloc.h>
#endif
#include "scamper_privsep.h"
#include "scamper_debug.h"
#include "utils.h"
#include "scamper_dl.h"
#include "scamper_addr.h"
#include "scamper_list.h"
#include "scamper_tlv.h"
#include "scamper_trace.h"
#include "scamper_rtsock.h"
#ifndef PRIVSEP_DIR
#define PRIVSEP_DIR "/var/empty"
#endif
#ifndef PRIVSEP_DIR_USER
#define PRIVSEP_DIR_USER "root"
#endif
#ifndef PRIVSEP_DIR_GROUP
#define PRIVSEP_DIR_GROUP "wheel"
#endif
#ifndef PRIVSEP_USER
#define PRIVSEP_USER "nobody"
#endif
typedef struct privsep_msg
{
uint16_t plen;
uint16_t type;
} privsep_msg_t;
struct privsep_handler
{
uint8_t type;
int (*handler)(const uint16_t len, const uint8_t *param);
};
static pid_t root_pid = -1; /* the process id of the root code */
static int root_fd = -1; /* the fd the root code send/recv on */
static int lame_fd = -1; /* the fd that the lame code uses */
/*
* the privilege separation code works by allowing the lame process to send
* request messages to the root process. these define the messages that
* the root process understands.
*/
#define SCAMPER_PRIVSEP_EXIT 0x00
#define SCAMPER_PRIVSEP_OPEN_DATALINK 0x01
#define SCAMPER_PRIVSEP_OPEN_FILE 0x02
#define SCAMPER_PRIVSEP_OPEN_RTSOCK 0x03
#define SCAMPER_PRIVSEP_OPEN_RAWSOCK 0x04
#define SCAMPER_PRIVSEP_OPEN_DIVERT 0x05
/*
* the privilege separation code permits both the privileged opening of file
* descriptors and the execution of privileged operations. the first half
* of the operations are for opening file descriptors; the second half are
* for doing a task. At the moment, there are no tasks defined so the next
* two #defines resolve to the same value.
*/
#define SCAMPER_PRIVSEP_MAXOPEN (SCAMPER_PRIVSEP_OPEN_DIVERT)
#define SCAMPER_PRIVSEP_MAXTYPE (SCAMPER_PRIVSEP_OPEN_DIVERT)
/*
* privsep_open_rawsock
*
* open a raw socket. two integer parameters corresponding to the 'type'
* and 'protocol' fields are supplied in param.
*
* this function restricts the range of valid values for the type parameter
* to 'AF_INET' and 'AF_INET6'. it also restricts the 'protocol' parameter
* to 'IPPROTO_ICMP', 'IPPROTO_ICMPV6', and 'IPPROTO_UDP'.
*/
static int privsep_open_rawsock(const uint16_t plen, const uint8_t *param)
{
int type, protocol;
if(plen != (2 * sizeof(int)))
{
scamper_debug(__func__, "plen %d != %d", plen, 2 * sizeof(int));
return -1;
}
memcpy(&type, param, sizeof(type));
memcpy(&protocol, param+sizeof(type), sizeof(protocol));
if(type == AF_INET)
{
/* check if the protocol is permitted for this socket type */
if(protocol != IPPROTO_ICMP && protocol != IPPROTO_UDP)
{
scamper_debug(__func__,
"protocol %d not permitted for socket type %d",
protocol, type);
return -1;
}
}
else if(type == AF_INET6)
{
/* check if the protocol is permitted for this socket type */
if(protocol != IPPROTO_ICMPV6 && protocol != IPPROTO_UDP)
{
scamper_debug(__func__,
"protocol %d not permitted for socket type %d",
protocol, type);
return -1;
}
}
else
{
scamper_debug(__func__, "type %d != AF_INET || AF_INET6", type);
return -1;
}
return socket(type, SOCK_RAW, protocol);
}
/*
* privsep_open_rtsock
*
* open a routing socket. there are no parameters permitted to this
* method call.
*/
static int privsep_open_rtsock(const uint16_t plen, const uint8_t *param)
{
if(plen != 0)
{
scamper_debug(__func__, "plen %d != 0", plen, 0);
return -1;
}
return scamper_rtsock_open_fd();
}
/*
* privsep_open_datalink
*
* open a BPF or PF_PACKET socket to the datalink. the param has a single
* field: the ifindex of the device to monitor.
*/
static int privsep_open_datalink(const uint16_t plen, const uint8_t *param)
{
int ifindex;
/* the payload should have an integer field - no more, no less. */
if(plen != sizeof(ifindex))
{
scamper_debug(__func__, "plen %d != %d", plen, sizeof(ifindex));
return -1;
}
memcpy(&ifindex, param, sizeof(ifindex));
return scamper_dl_open_fd(ifindex);
}
/*
* privsep_open_divert
*
* open a divert socket. bind it to the port supplied.
*/
static int privsep_open_divert(const uint16_t plen, const uint8_t *param)
{
int fd = -1;
#if defined(IPPROTO_DIVERT)
struct sockaddr_in sin;
int port;
if(plen != sizeof(port))
{
scamper_debug(__func__, "plen %d != %d", plen, sizeof(port));
return -1;
}
memcpy(&port, param, sizeof(port));
if((fd = socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT)) == -1)
{
printerror(errno, strerror, __func__, "could not open socket");
return -1;
}
memset(&sin, 0, sizeof(sin));
sin.sin_len = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
{
printerror(errno, strerror, __func__, "could not bind socket");
return -1;
}
#endif
return fd;
}
/*
* privsep_open_file
*
* switch to the user running the process and open the file specified.
* the param has two fields in it: the mode of open, and the file to open.
*/
static int privsep_open_file(const uint16_t plen, const uint8_t *param)
{
const char *file;
uid_t uid, euid;
int flags;
mode_t mode;
uint16_t off;
int fd;
/*
* if the payload of param is not large enough to hold the flags and a
* filename, then don't go any further
*/
if(plen < sizeof(int) + 2)
{
return -1;
}
memcpy(&flags, param, sizeof(int));
off = sizeof(int);
/* if the O_CREAT flag is set, we need to fetch the mode parameter too */
if(flags & O_CREAT)
{
/*
* the payload length of the parameter must be large enough to hold
* the flags, mode, and a filename
*/
if(plen < off + sizeof(mode_t) + 2)
{
return -1;
}
memcpy(&mode, param+off, sizeof(mode));
off += sizeof(mode_t);
}
file = (const char *)(param + off);
/*
* make sure the length of the file to open checks out.
* the last byte of the string must be a null character.
*/
if(file[plen-off-1] != '\0')
{
scamper_debug(__func__, "filename not terminated with a null");
return -1;
}
uid = getuid();
euid = geteuid();
/* set our effective uid to be the user who started scamper */
if(seteuid(uid) == -1)
{
return -1;
}
if(flags & O_CREAT)
{
fd = open(file, flags, mode);
}
else
{
fd = open(file, flags);
}
/*
* ask for our root permissions back. if we can't get them back, then
* this process is crippled and it might as well exit now.
*/
if(seteuid(euid) == -1)
{
if(fd != -1) close(fd);
exit(-errno);
}
return fd;
}
/*
* privsep_send_fd
*
* send the fd created using the priviledged code. if the fd was not
* successfully created, we send the errno back in the payload of the
* message.
*/
static int privsep_send_fd(const int fd,const int error,const uint8_t msg_type)
{
uint8_t buf[sizeof(int)];
struct msghdr msg;
struct iovec vec;
#if !defined(HAVE_ACCRIGHTS)
struct cmsghdr *cmsg;
uint8_t tmp[CMSG_LEN(sizeof(int))];
#endif
scamper_debug(__func__, "fd: %d error: %d msg_type: 0x%02x",
fd, error, msg_type);
memset(&vec, 0, sizeof(vec));
memset(&msg, 0, sizeof(msg));
memcpy(buf, &error, sizeof(int));
vec.iov_base = buf;
vec.iov_len = sizeof(buf);
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
if(fd != -1)
{
#if defined(HAVE_ACCRIGHTS)
msg.msg_accrights = (caddr_t)&fd;
msg.msg_accrightslen = sizeof(fd);
#else
msg.msg_control = (caddr_t)tmp;
msg.msg_controllen = CMSG_LEN(sizeof(int));
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
*(int *)CMSG_DATA(cmsg) = fd;
#endif
}
if(sendmsg(root_fd, &msg, 0) == -1)
{
return -1;
}
return 0;
}
static int privsep_recv_fd(void)
{
struct msghdr msg;
struct iovec vec;
ssize_t rc;
int fd = -1, error;
#if !defined(HAVE_ACCRIGHTS)
struct cmsghdr *cmsg;
uint8_t tmp[CMSG_LEN(sizeof(int))];
#endif
memset(&vec, 0, sizeof(vec));
memset(&msg, 0, sizeof(msg));
vec.iov_base = (char *)&error;
vec.iov_len = sizeof(error);
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
#if defined(HAVE_ACCRIGHTS)
msg.msg_accrights = (caddr_t)&fd;
msg.msg_accrightslen = sizeof(fd);
#else
msg.msg_control = tmp;
msg.msg_controllen = sizeof(tmp);
#endif
if((rc = recvmsg(lame_fd, &msg, 0)) == -1)
{
printerror(errno, strerror, __func__, "recvmsg failed");
return -1;
}
else if(rc != sizeof(error))
{
return -1;
}
if(error == 0)
{
#if defined(HAVE_ACCRIGHTS)
if(msg.msg_accrightslen != sizeof(fd))
{
fd = -1;
}
#else
cmsg = CMSG_FIRSTHDR(&msg);
if(cmsg != NULL && cmsg->cmsg_type == SCM_RIGHTS)
{
fd = (*(int *)CMSG_DATA(cmsg));
}
#endif
}
else
{
errno = error;
}
return fd;
}
/*
* privsep_do
*
* this is the only piece of code with root priviledges. we use it to
* create raw sockets, routing/netlink sockets, BPF/PF_PACKET sockets, and
* ordinary files that scamper itself cannot do by itself.
*/
static int privsep_do(void)
{
static int (* const func[])(const uint16_t plen, const uint8_t *param) = {
NULL, /* SCAMPER_PRIVSEP_EXIT */
privsep_open_datalink, /* SCAMPER_PRIVSEP_OPEN_DATALINK */
privsep_open_file, /* SCAMPER_PRIVSEP_OPEN_FILE */
privsep_open_rtsock, /* SCAMPER_PRIVSEP_OPEN_RTSOCK */
privsep_open_rawsock, /* SCAMPER_PRIVSEP_OPEN_RAWSOCK */
privsep_open_divert, /* SCAMPER_PRIVSEP_OPEN_DIVERT */
};
privsep_msg_t msg;
void *data = NULL;
int ret = 0, error;
int fd;
#if defined(HAVE_SETPROCTITLE)
setproctitle("%s", "[priv]");
#endif
/* might as well set our copy of the root_pid to something useful */
root_pid = getpid();
/*
* the priviledged process does not need the lame file descriptor for
* anything, so get rid of it
*/
close(lame_fd);
lame_fd = -1;
for(;;)
{
/* read the msg header */
if((ret = read_wrap(root_fd, (uint8_t *)&msg, NULL, sizeof(msg))) != 0)
{
if(ret == -1)
{
printerror(errno, strerror, __func__, "could not read msg hdr");
}
break;
}
/* if we've been told to exit, then do so now */
if(msg.type == SCAMPER_PRIVSEP_EXIT)
{
break;
}
if(msg.type > SCAMPER_PRIVSEP_MAXTYPE)
{
scamper_debug(__func__, "msg %d > maxtype", msg.type);
ret = -EINVAL;
break;
}
/* if there is more data to read, read it now */
if(msg.plen != 0)
{
if((data = malloc(msg.plen)) == NULL)
{
printerror(errno, strerror, __func__, "couldnt malloc data");
ret = (-errno);
break;
}
if((ret = read_wrap(root_fd, data, NULL, msg.plen)) != 0)
{
printerror(errno, strerror, __func__, "couldnt read data");
free(data);
break;
}
}
else data = NULL;
/* decode the message now */
fd = func[msg.type](msg.plen, data);
/* if the task was to open a file descriptor, handle that case */
if(msg.type <= SCAMPER_PRIVSEP_MAXOPEN)
{
/* if we have a file descriptor, we don't pass an error condition */
if(fd == -1) error = errno;
else error = 0;
}
else
{
/* the task was to do something. report the outcome of the task */
error = fd;
fd = -1;
}
/* we don't need the data we read anymore */
if(data != NULL) free(data);
/* send the file descriptor back to the lame process */
if(privsep_send_fd(fd, error, msg.type) == -1)
{
printerror(errno, strerror, __func__, "couldnt send fd");
break;
}
/*
* if we have a file descriptor that has been passed to the parent
* process, we don't need our copy any longer
*/
if(fd != -1)
{
close(fd);
}
}
close(root_fd);
return ret;
}
/*
* privsep_lame_send
*
* compose and send the messages necessary to communicate with the root
* process.
*/
static int privsep_lame_send(const uint16_t type, const uint16_t len,
const uint8_t *param)
{
privsep_msg_t msg;
int i;
assert(type != SCAMPER_PRIVSEP_EXIT);
assert(type <= SCAMPER_PRIVSEP_MAXTYPE);
/* send the header first */
msg.type = type;
msg.plen = len;
if((i == write_wrap(lame_fd, &msg, NULL, sizeof(msg))) == -1)
{
printerror(errno, strerror, __func__, "could not send msg header");
return -1;
}
/* if there is a parameter data to send, send it now */
if(len > 0 && (i = write_wrap(lame_fd, param, NULL, len)) == -1)
{
printerror(errno, strerror, __func__, "could not send msg param");
return -1;
}
return 0;
}
/*
* privsep_getfd
*
* send a request to the piece of code running as root to do open a file
* descriptor that requires priviledge to do. return the file descriptor.
*/
static int privsep_getfd(const uint16_t type, const uint16_t len,
const uint8_t *param)
{
if(privsep_lame_send(type, len, param) == -1)
{
return -1;
}
return privsep_recv_fd();
}
/*
* scamper_privsep_dotask
*
* send a request to the piece of code running as root to do a task that
* requires priviledge to do. return any error code.
*
* XXX: currently not used for anything.
*/
#if 0
static int privsep_dotask(const uint16_t type, const uint16_t len,
const uint8_t *param)
{
int error;
if(privsep_lame_send(type, len, param) == -1)
{
return -1;
}
if(read_wrap(lame_fd, (uint8_t *)&error, NULL, sizeof(error)) == -1)
{
return -1;
}
return error;
}
#endif
int scamper_privsep_open_datalink(const int ifindex)
{
uint8_t param[sizeof(ifindex)];
memcpy(param, &ifindex, sizeof(ifindex));
return privsep_getfd(SCAMPER_PRIVSEP_OPEN_DATALINK, sizeof(ifindex), param);
}
int scamper_privsep_open_file(const char *file,
const int flags, const mode_t mode)
{
uint8_t *param;
int off, len, fd;
/*
* decide how big the message is going to be. don't pass it if the message
* length parameter constrains us
*/
len = sizeof(flags) + strlen(file) + 1;
if(flags & O_CREAT)
{
len += sizeof(mode);
}
/*
* the len is fixed because the length parameter used in the privsep
* header is a 16-bit unsigned integer.
*/
if(len > 65535)
{
return -1;
}
/* allocate the parameter */
if((param = malloc(len)) == NULL)
{
return -1;
}
/* copy in the flags parameter, and the mode parameter if necessary */
memcpy(param, &flags, sizeof(flags)); off = sizeof(flags);
if(flags & O_CREAT)
{
memcpy(param+off, &mode, sizeof(mode));
off += sizeof(mode);
}
/* finally copy in the name of the file to open */
memcpy(param+off, file, len-off);
/* get the file descriptor and return it */
fd = privsep_getfd(SCAMPER_PRIVSEP_OPEN_FILE, len, param);
free(param);
return fd;
}
int scamper_privsep_open_rtsock(void)
{
return privsep_getfd(SCAMPER_PRIVSEP_OPEN_RTSOCK, 0, NULL);
}
int scamper_privsep_open_rawsock(const int domain, const int protocol)
{
uint8_t param[sizeof(domain) + sizeof(protocol)];
/* copy the parameters into the buffer */
memcpy(param, &domain, sizeof(domain));
memcpy(param+sizeof(domain), &protocol, sizeof(protocol));
return privsep_getfd(SCAMPER_PRIVSEP_OPEN_RAWSOCK,
sizeof(domain) + sizeof(protocol), param);
}
int scamper_privsep_open_divert(const int port)
{
uint8_t param[sizeof(port)];
memcpy(param, &port, sizeof(port));
return privsep_getfd(SCAMPER_PRIVSEP_OPEN_DIVERT, sizeof(port), param);
}
/*
* scamper_privsep
*
* start a child process that has the root priviledges that scamper starts
* with. then, revoke scamper's priviledges to the minimum scamper can
* obtain
*/
int scamper_privsep_init()
{
struct addrinfo hints, *res0;
struct timeval tv;
struct passwd *pw;
struct stat sb;
mode_t mode;
uid_t uid;
gid_t gid;
int sockets[2];
pid_t pid;
int ret;
time_t t;
/* check to see if the PRIVSEP_DIR exists */
if(stat(PRIVSEP_DIR, &sb) == -1)
{
/* if the directory does not exist, try and create it now */
if(errno == ENOENT)
{
/*
* get the uid of the user who will get ownership of the directory.
* by default, this will be root.
*/
if((pw = getpwnam(PRIVSEP_DIR_USER)) == NULL)
{
printerror(errno, strerror, __func__,
"could not getpwnam " PRIVSEP_DIR_USER);
endpwent();
return -1;
}
uid = pw->pw_uid;
endpwent();
gid = 0;
/* create the directory as 555 : no one can write to it */
mode = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
if(mkdir(PRIVSEP_DIR, mode) == -1)
{
printerror(errno, strerror, __func__,
"could not mkdir " PRIVSEP_DIR);
return -1;
}
/* assign ownership appropriately */
if(chown(PRIVSEP_DIR, uid, gid) == -1)
{
printerror(errno, strerror, __func__,
"could not chown " PRIVSEP_DIR);
rmdir(PRIVSEP_DIR);
return -1;
}
}
else
{
printerror(errno, strerror, __func__, "could not stat " PRIVSEP_DIR);
return -1;
}
}
/*
* open up the unix domain sockets that will allow the prober to talk
* with the priviledged process
*/
if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1)
{
printerror(errno, strerror, __func__, "could not socketpair");
return -1;
}
lame_fd = sockets[0];
root_fd = sockets[1];
if((pid = fork()) == -1)
{
printerror(errno, strerror, __func__, "could not fork");
return -1;
}
else if(pid == 0) /* child */
{
/*
* this is the process that will do the root tasks.
* when this function exits, we call exit() on the forked process.
*/
ret = privsep_do();
exit(ret);
}
/* set our copy of the root_pid to the relevant process id */
root_pid = pid;
/*
* we don't need our copy of the file descriptor passed to the priviledged
* process any longer
*/
close(root_fd);
root_fd = -1;
/*
* get the details for the PRIVSEP_USER login, which the rest of scamper
* will use to get things done
*/
if((pw = getpwnam(PRIVSEP_USER)) == NULL)
{
printerror(errno, strerror, __func__,
"could not getpwnam " PRIVSEP_USER);
return -1;
}
uid = pw->pw_uid;
gid = pw->pw_gid;
memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
endpwent();
/*
* call localtime now, as then the unpriviledged process will have the
* local time zone information cached in the process, so localtime will
* actually mean something
*/
gettimeofday_wrap(&tv);
t = tv.tv_sec;
localtime(&t);
/*
* call getaddrinfo now, as then the unpriviledged process will load
* whatever files it needs to to help resolve IP addresses; the need for
* this was first noticed in SunOS
*/
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_family = AF_INET;
getaddrinfo("127.0.0.1", NULL, &hints, &res0);
freeaddrinfo(res0);
/* change the root directory of the unpriviledged directory */
#ifndef NDEBUG
if(chroot(PRIVSEP_DIR) == -1)
{
printerror(errno, strerror, __func__,
"could not chroot to " PRIVSEP_DIR);
return -1;
}
/* go into the chroot environment */
if(chdir("/") == -1)
{
printerror(errno, strerror, __func__, "could not chdir /");
return -1;
}
#endif
/* change the operating group */
if(setgroups(1, &gid) == -1)
{
printerror(errno, strerror, __func__, "could not setgroups");
return -1;
}
if(setgid(gid) == -1)
{
printerror(errno, strerror, __func__, "could not setgid");
return -1;
}
/* change the operating user */
if(setuid(uid) == -1)
{
printerror(errno, strerror, __func__, "could not setuid");
return -1;
}
return lame_fd;
}
void scamper_privsep_cleanup()
{
privsep_msg_t msg;
if(root_pid != -1)
{
msg.plen = 0;
msg.type = SCAMPER_PRIVSEP_EXIT;
write_wrap(lame_fd, (uint8_t *)&msg, NULL, sizeof(msg));
root_pid = -1;
}
if(lame_fd != -1)
{
close(lame_fd);
lame_fd = -1;
}
return;
}
#endif /* ifndef WITHOUT_PRIVSEP */
syntax highlighted by Code2HTML, v. 0.9.1