PSARC 2009/686 Improving the use and debugging of the basic privilege set.
PSARC/2009/685 Basic Network Privilege
6434380 Expanding the basic privilege set in order to restrict network access and IPC
6912229 Multiple applications mishandle privilege operations, particular they ignore the basic set
6915243 dladm mishandles basic privileges
6915244 in.tftpd mishandles privileges operations
6915250 NDMP mishandles basic privileges
6915257 smbd mishandles basic privileges
6915277 login audit mishandles basic privileges
6915284 su audit mishandles basic privileges
6915778 lpd-port mishandles basic privileges
6915782 zlogin mishandles basic privileges
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
*/
/* $Id: lpd-port.c 155 2006-04-26 02:34:54Z ktou $ */
#include <config-site.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <values.h>
#include <stropts.h> /* for sendfd */
#include <sys/uio.h> /* for sendmsg stuff */
#include <pwd.h>
#include <sys/sendfile.h>
#include <ctype.h>
#ifdef HAVE_PRIV_H
#include <priv.h>
#endif
#ifndef JOB_ID_FILE
#define JOB_ID_FILE "/var/run/rfc-1179.seq"
#endif /* JOB_ID_FILE */
static int
sendfd(int sockfd, int fd)
{
syslog(LOG_DEBUG, "sendfd(%d, %d)", sockfd, fd);
#if defined(sun) && defined(unix) && defined(I_SENDFD)
return (ioctl(sockfd, I_SENDFD, fd));
#else
struct iovec iov[1];
struct msghdr msg;
#ifdef CMSG_DATA
struct cmsghdr cmp[1];
char buf[2]; /* send/recv 2 byte protocol */
iov[0].iov_base = buf;
iov[0].iov_len = 2;
cmp[0].cmsg_level = SOL_SOCKET;
cmp[0].cmsg_type = SCM_RIGHTS;
cmp[0].cmsg_len = sizeof (struct cmsghdr) + sizeof (int);
* (int *)CMSG_DATA(cmp) = fd;
buf[1] = 0;
buf[0] = 0;
msg.msg_control = cmp;
msg.msg_controllen = sizeof (struct cmsghdr) + sizeof (int);
#else
iov[0].iov_base = NULL;
iov[0].iov_len = 0;
msg.msg_accrights = (caddr_t)&fd;
msg.msg_accrights = sizeof (fd);
#endif
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
return (sendmsg(sockfd, &msg, 0));
#endif
}
static void
null(int i)
{
}
static int
sock_connect(int sock, char *host, int timeout)
{
struct hostent *hp;
struct servent *sp;
#if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF)
struct sockaddr_in6 sin;
#else
struct sockaddr_in sin;
#endif
static void (*old_handler)();
int err, error_num;
unsigned timo = 1;
/*
* Get the host address and port number to connect to.
*/
if (host == NULL) {
return (-1);
}
/* linux style NULL usage */
(void) memset((char *)&sin, (int)NULL, sizeof (sin));
#if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF)
if ((hp = getipnodebyname(host, AF_INET6, AI_DEFAULT,
&error_num)) == NULL) {
errno = ENOENT;
return (-1);
}
(void) memcpy((caddr_t)&sin.sin6_addr, hp->h_addr, hp->h_length);
sin.sin6_family = hp->h_addrtype;
#else
if ((hp = gethostbyname(host)) == NULL) {
errno = ENOENT;
return (-1);
}
(void) memcpy((caddr_t)&sin.sin_addr, hp->h_addr, hp->h_length);
sin.sin_family = hp->h_addrtype;
#endif
if ((sp = getservbyname("printer", "tcp")) == NULL) {
errno = ENOENT;
return (-1);
}
#if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF)
sin.sin6_port = sp->s_port;
#else
sin.sin_port = sp->s_port;
#endif
retry:
old_handler = signal(SIGALRM, null);
(void) alarm(timeout);
if (connect(sock, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
(void) alarm(0);
(void) signal(SIGALRM, old_handler);
if (errno == ECONNREFUSED && timo <= 16) {
(void) sleep(timo);
timo *= 2;
goto retry;
}
return (-1);
}
(void) alarm(0);
(void) signal(SIGALRM, old_handler);
return (sock);
}
static int
next_job_id()
{
int fd, result = getpid() % 1000;
/* gain back enough privilege to open the id file */
#ifdef PRIV_ALLSETS
if ((priv_set(PRIV_ON, PRIV_EFFECTIVE,
PRIV_FILE_DAC_READ, PRIV_FILE_DAC_WRITE, NULL)) < 0) {
syslog(LOG_ERR, "lpd_port:next_job_id:priv_set fails: : %m");
return (-1);
}
#else
seteuid(0);
#endif
/* open the sequence file */
if (((fd = open(JOB_ID_FILE, O_RDWR)) < 0) && (errno == ENOENT))
fd = open(JOB_ID_FILE, O_CREAT|O_EXCL|O_RDWR, 0644);
syslog(LOG_DEBUG, "sequence file fd: %d", fd);
/* drop our privilege again */
#ifdef PRIV_ALLSETS
/* drop file access privilege */
priv_set(PRIV_OFF, PRIV_PERMITTED,
PRIV_FILE_DAC_READ, PRIV_FILE_DAC_WRITE, NULL);
#else
seteuid(getuid());
#endif
if (fd >= 0) {
/* wait for a lock on the file */
if (lockf(fd, F_LOCK, 0) == 0) {
char buf[8];
int next;
/* get the current id */
(void) memset(buf, 0, sizeof (buf));
if (read(fd, buf, sizeof (buf)) > 0)
result = atoi(buf);
next = ((result < 999) ? (result + 1) : 0);
/* store the next id in the file */
snprintf(buf, sizeof (buf), "%.3d", next);
if ((lseek(fd, 0, SEEK_SET) == 0) &&
(ftruncate(fd, 0) == 0))
write(fd, buf, strlen(buf));
}
close(fd);
}
syslog(LOG_DEBUG, "next_job_id() is %d", result);
return (result);
}
static int
reserved_port()
{
int result = -1;
int port;
/* gain back enough privilege to open a reserved port */
#ifdef PRIV_ALLSETS
if ((priv_set(
PRIV_ON, PRIV_EFFECTIVE, PRIV_NET_PRIVADDR, NULL)) != 0) {
syslog(LOG_ERR, "priv_set fails for net_privaddr %m");
return (-1);
}
#else
seteuid(0);
#endif
#if defined(HAVE_GETIPNODEBYNAME) && defined(HAVE_RRESVPORT_AF)
port = 0; /* set to 0, rresvport_af() will find us one. */
result = rresvport_af(&port, AF_INET6);
#else
port = IPPORT_RESERVED - 1;
while (((result = rresvport(&port)) < 0) && (port >= 0))
port--;
#endif
/* drop our privilege again */
#ifdef PRIV_ALLSETS
priv_set(PRIV_OFF, PRIV_PERMITTED, PRIV_NET_PRIVADDR, NULL);
#else
seteuid(getuid());
#endif
return (result);
}
static char *
get_user_name()
{
static struct passwd *p = NULL;
if ((p = getpwuid(getuid())) != NULL)
return (p->pw_name);
else
return ("unknown");
}
static void
add_args(int ac, char **av, char *buf, size_t len)
{
while (ac--) {
strlcat(buf, " ", len);
strlcat(buf, *(av++), len);
}
}
static int
massage_control_data(char *data, int id)
{
char *line, *iter = NULL;
char *ptr, *mod_ptr, *datacpy;
char host[BUFSIZ];
int host_present = 0;
if (gethostname(host, sizeof (host)) != 0)
return (-1);
if ((datacpy = strdup(data)) == NULL) {
return (-1);
}
for (ptr = strtok_r(datacpy, "\n", &iter); ptr != NULL;
ptr = strtok_r(NULL, "\n", &iter)) {
if (ptr[0] == 'H') {
if (strncmp(++ptr, host, strlen(host)) != 0) {
free(datacpy);
return (-1);
}
host_present = 1;
} else if ((ptr[0] == 'P') || (ptr[0] == 'L')) {
/* check the user name */
uid_t uid = getuid();
struct passwd *pw;
int len;
if (uid == 0) { /* let root do what they want */
continue;
}
if ((pw = getpwuid(uid)) == NULL) {
free(datacpy);
return (-1); /* failed */
}
len = strlen(pw->pw_name);
if ((strncmp(++ptr, pw->pw_name, len) != 0)) {
free(datacpy);
return (-1); /* failed */
}
} else if ((islower(ptr[0]) != 0) || (ptr[0] == 'U')) {
/* check/fix df?XXXhostname */
ptr++;
if (strlen(ptr) < 6) {
free(datacpy);
return (-1);
}
/*
* As ptr is a copy of the string (df?XXX...) the code
* needs to work on the original, hence the need for
* mod_ptr. No need to check for a NULL mod_ptr
* because the required string must already exist as
* ptr is a copy of the original data.
*/
mod_ptr = strstr(data, ptr);
if ((mod_ptr[0] == 'd') && (mod_ptr[1] == 'f') &&
(mod_ptr[3] == 'X') && (mod_ptr[4] == 'X') &&
(mod_ptr[5] == 'X')) {
mod_ptr[3] = '0' + (id / 100) % 10;
mod_ptr[4] = '0' + (id / 10) % 10;
mod_ptr[5] = '0' + id % 10;
if (strncmp(&mod_ptr[6], host, strlen(host))
!= 0) {
free(datacpy);
return (-1);
}
} else {
free(datacpy);
return (-1);
}
}
}
free(datacpy);
if (!host_present) {
return (-1);
}
return (1);
}
static int
send_lpd_message(int fd, char *fmt, ...)
{
char buf[BUFSIZ];
size_t size;
va_list ap;
va_start(ap, fmt);
size = vsnprintf(buf, sizeof (buf), fmt, ap);
va_end(ap);
if (size == 0)
size = 1;
syslog(LOG_DEBUG, "lpd_messsage(%d, %s)", fd, buf);
if (write(fd, buf, size) != size) {
errno = EIO;
return (-1);
}
if ((read(fd, buf, 1) != 1) || (buf[0] != 0))
return (-1);
return (0);
}
static int
send_data_file(int sock, char *dfname, char *name)
{
size_t len;
off_t off = 0;
struct stat st;
char buf[32];
int fd = -1;
if (strcmp(name, "standard input") != 0) {
if ((fd = open(name, O_RDONLY)) < 0)
return (-1);
if (fstat(fd, &st) < 0)
return (-1);
} else
st.st_size = MAXINT; /* should be 0 */
/* request data file transfer, read ack/nack */
errno = ENOSPC;
if (send_lpd_message(sock, "\003%d %s\n", st.st_size, dfname) < 0)
return (-1);
if (fd != -1) {
/* write the data */
if (sendfile(sock, fd, &off, st.st_size) != st.st_size)
return (-1);
close(fd);
/* request ack/nack after the data transfer */
errno = EIO;
if (send_lpd_message(sock, "") < 0)
return (-1);
}
return (0);
}
static int
send_control_file(int sock, char *data, int id)
{
int len;
char buf[BUFSIZ];
char *ptr, *iter = NULL;
char *datacpy = NULL;
char *host = NULL;
len = strlen(data);
if ((datacpy = strdup(data)) == NULL)
return (-1);
syslog(LOG_DEBUG, "cfA: %s\n", datacpy);
for (ptr = strtok_r(datacpy, "\n", &iter); ptr != NULL;
ptr = strtok_r(NULL, "\n", &iter)) {
if (ptr[0] != 'H')
continue;
ptr++;
host = ptr;
syslog(LOG_DEBUG, "hostname: %s\n", host);
}
free(datacpy);
/* request data file transfer, read ack/nack */
errno = ENOSPC;
if (send_lpd_message(sock, "\002%d cfA%.3d%s\n", len, id, host) < 0)
return (-1);
/* write the data */
if (write(sock, data, len) != len)
return (-1);
/* request ack/nack after the data transfer */
errno = EIO;
if (send_lpd_message(sock, "") < 0)
return (-1);
return (0);
}
static int
submit_job(int sock, char *printer, int job_id, char *path)
{
struct stat st;
int current = 0;
off_t off = 0;
char *metadata = NULL;
char *ptr, *iter = NULL;
int fd, err;
int sent_files = 0;
char buf[BUFSIZ];
size_t len;
/* open the control file */
if ((fd = open(path, O_RDONLY)) < 0) {
syslog(LOG_ERR, "submit_job(%d, %s, %d, %s): open(): %m",
sock, printer, job_id, path);
return (-1);
}
/* get the size of the control file */
if (fstat(fd, &st) < 0) {
syslog(LOG_ERR, "submit_job(%d, %s, %d, %s): fstat(): %m",
sock, printer, job_id, path);
close(fd);
return (-1);
}
/* allocate memory for the control file */
if ((metadata = calloc(1, st.st_size + 1)) == NULL) {
syslog(LOG_ERR, "submit_job(%d, %s, %d, %s): calloc(): %m",
sock, printer, job_id, path);
close(fd);
return (-1);
}
/* read in the control file */
if (read(fd, metadata, st.st_size) != st.st_size) {
syslog(LOG_ERR, "submit_job(%d, %s, %d, %s): read(): %m",
sock, printer, job_id, path);
free(metadata);
close(fd);
return (-1);
}
/* massage the control file */
if (massage_control_data(metadata, job_id) < 0) {
/* bad control data, dump the job */
syslog(LOG_ALERT,
"bad control file, possible subversion attempt");
free(metadata);
errno = EINVAL;
close(fd);
return (-1);
}
/* request to transfer the job */
if (send_lpd_message(sock, "\002%s\n", printer) < 0) {
/* no such (or disabled) queue, got to love rfc-1179 */
errno = ENOENT;
return (-1);
}
/* send the control data */
if (send_control_file(sock, metadata, job_id) < 0) {
err = errno;
write(sock, "\001\n", 2); /* abort */
errno = err;
return (-1);
}
/* walk the control file sending the data files */
for (ptr = strtok_r(metadata, "\n", &iter); ptr != NULL;
ptr = strtok_r(NULL, "\n", &iter)) {
char *name = NULL;
if (ptr[0] != 'U')
continue;
name = strtok_r(NULL, "\n", &iter);
if (name[0] != 'N')
continue;
ptr++;
name++;
if (send_data_file(sock, ptr, name) < 0) {
err = errno;
write(sock, "\001\n", 2); /* abort */
errno = err;
return (-1);
}
if (strcmp(name, "standard input") != 0)
sent_files++;
}
/* write back the job-id */
err = errno;
if ((fd = open(path, O_WRONLY)) >= 0) {
ftruncate(fd, 0);
write(fd, &job_id, sizeof (job_id));
close(fd);
}
errno = err;
if (sent_files != 0) {
err = errno;
close(sock);
errno = err;
}
return (0);
}
static int
query(int fd, char *printer, int ac, char **av)
{
char buf[BUFSIZ];
int rc, len;
/* build the request */
snprintf(buf, sizeof (buf), "\04%s", printer);
add_args(ac, av, buf, sizeof (buf));
strlcat(buf, "\n", sizeof (buf));
len = strlen(buf);
if (((rc = write(fd, buf, len)) >= 0) && (rc != len)) {
errno = EMSGSIZE;
rc = -1;
} else
rc = 0;
return (rc);
}
static int
cancel(int fd, char *printer, int ac, char **av)
{
char buf[BUFSIZ];
int rc, len;
/* build the request */
snprintf(buf, sizeof (buf), "\05%s %s", printer, get_user_name());
add_args(ac, av, buf, sizeof (buf));
strlcat(buf, "\n", sizeof (buf));
len = strlen(buf);
if (((rc = write(fd, buf, len)) >= 0) && (rc != len)) {
errno = EMSGSIZE;
rc = -1;
} else
rc = 0;
return (rc);
}
static void
usage(char *program)
{
char *name;
setreuid(getuid(), getuid());
if ((name = strrchr(program, '/')) == NULL)
name = program;
else
name++;
fprintf(stderr, "usage:\t%s -H host [-t timeout] -s queue control ]\n",
name);
fprintf(stderr, "\t%s -H host [-t timeout] -c queue [user|job ...]\n",
name);
fprintf(stderr, "\t%s -H host [-t timeout] -q queue [user|job ...]\n",
name);
exit(EINVAL);
}
/*
* The main program temporarily loses privilege while searching the command
* line arguments. It then allocates any resources it need privilege for
* job-id, reserved port. Once it has the resources it needs, it perminently
* drops all elevated privilege. It ghen connects to the remote print service
* based on destination hostname. Doing it this way reduces the potenential
* opportunity for a breakout with elevated privilege, breakout with an
* unconnected reserved port, and exploitation of the remote print service
* by a calling program.
*/
int
main(int ac, char *av[])
{
enum { OP_NONE, OP_SUBMIT, OP_QUERY, OP_CANCEL } operation = OP_NONE;
int fd, c, timeout = 0, exit_code = 0;
char *host = NULL, *queue = NULL;
uid_t uid = getuid();
#ifdef PRIV_ALLSETS
priv_set_t *saveset;
#endif
openlog("lpd-port", LOG_PID, LOG_LPR);
#ifdef PRIV_ALLSETS
/* lose as much as we can perminently and temporarily drop the rest. */
if ((saveset = priv_allocset()) == NULL) {
syslog(LOG_ERR, "lpd_port: priv_allocset saveset failed: %m\n");
return (-1);
}
priv_basicset(saveset);
(void) priv_addset(saveset, PRIV_NET_PRIVADDR);
(void) priv_addset(saveset, PRIV_FILE_DAC_READ);
(void) priv_addset(saveset, PRIV_FILE_DAC_WRITE);
if ((setppriv(PRIV_SET, PRIV_PERMITTED, saveset)) < 0) {
syslog(LOG_ERR, "lpd_port:setppriv:priv_set failed: %m");
return (-1);
}
priv_freeset(saveset);
/*
* These privileges permanently dropped in next_job_id() and
* reserved_port()
*/
if (priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_NET_PRIVADDR,
PRIV_FILE_DAC_READ, PRIV_FILE_DAC_WRITE, (char *)NULL) < 0) {
syslog(LOG_ERR, "lpd_port:priv_set:priv_off failed: %m");
return (-1);
}
syslog(LOG_DEBUG, "using privs");
#else
syslog(LOG_DEBUG, "no privs");
seteuid(uid);
#endif
while ((c = getopt(ac, av, "H:t:c:q:s:")) != EOF) {
switch (c) {
case 'H':
host = optarg;
break;
case 't':
timeout = atoi(optarg);
break;
case 'c':
if (operation != OP_NONE)
usage(av[0]);
operation = OP_CANCEL;
queue = optarg;
break;
case 'q':
if (operation != OP_NONE)
usage(av[0]);
operation = OP_QUERY;
queue = optarg;
break;
case 's':
if (operation != OP_NONE)
usage(av[0]);
operation = OP_SUBMIT;
queue = optarg;
break;
default:
usage(av[0]);
/* does not return */
}
}
if ((host == NULL) || (queue == NULL) || (timeout < 0) ||
(operation == OP_NONE))
usage(av[0]);
if (operation == OP_SUBMIT) /* get a job-id if we need it */
if ((c = next_job_id()) < 0) {
syslog(LOG_ERR, "lpd_port:main:next_job_id fails");
return (-1);
}
if ((fd = reserved_port()) < 0) {
syslog(LOG_ERR, "reserved_port() failed %m");
return (errno);
}
/*
* we no longer want or need any elevated privilege, lose it all
* permanently.
*/
setreuid(uid, uid);
/* connect to the print service */
if ((fd = sock_connect(fd, host, timeout)) < 0)
return (errno);
/* perform the requested operation */
switch (operation) {
case OP_SUBMIT: /* transfer the job, close the fd */
if (submit_job(fd, queue, c, av[optind]) < 0)
exit_code = errno;
break;
case OP_QUERY: /* send the query string, return the fd */
if (query(fd, queue, ac - optind, &av[optind]) < 0)
exit_code = errno;
break;
case OP_CANCEL: /* send the cancel string, return the fd */
if (cancel(fd, queue, ac - optind, &av[optind]) < 0)
exit_code = errno;
break;
default: /* This should never happen */
exit_code = EINVAL;
}
/* if the operation succeeded, send the fd to our parent */
if ((exit_code == 0) && (sendfd(1, fd) < 0)) {
char buf[BUFSIZ];
exit_code = errno;
/* sendfd() failed, dump the socket data for the heck of it */
while ((c = read(fd, buf, sizeof (buf))) > 0)
write(1, buf, c);
}
syslog(LOG_DEBUG, "exit code: %d", exit_code);
return (exit_code);
}