diff options
Diffstat (limited to 'usr/src/cmd/whodo/whodo.c')
| -rw-r--r-- | usr/src/cmd/whodo/whodo.c | 354 |
1 files changed, 244 insertions, 110 deletions
diff --git a/usr/src/cmd/whodo/whodo.c b/usr/src/cmd/whodo/whodo.c index fe097ace23..98abc0792d 100644 --- a/usr/src/cmd/whodo/whodo.c +++ b/usr/src/cmd/whodo/whodo.c @@ -23,6 +23,8 @@ * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * + * Copyright 2020 Joyent, Inc. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ @@ -55,9 +57,11 @@ #include <ctype.h> #include <fcntl.h> #include <time.h> +#include <err.h> #include <errno.h> #include <sys/types.h> #include <utmpx.h> +#include <sys/sysmacros.h> #include <sys/utsname.h> #include <sys/stat.h> #include <sys/mkdev.h> @@ -95,8 +99,8 @@ #define ZOMBIE 'z' /* zombie process */ #define VISITED 'v' /* marked node as visited */ -static int ndevs; /* number of configured devices */ -static int maxdev; /* slots for configured devices */ +static uint_t ndevs; /* number of configured devices */ +static uint_t maxdev; /* slots for configured devices */ #define DNINCR 100 static struct devl { /* device list */ char dname[DEVNAMELEN]; /* device name */ @@ -136,6 +140,10 @@ static char *getty(dev_t); static void prttime(time_t, int); static void prtat(time_t *); +static int priv_proc_open(const char *, int); +static int priv_proc_openat(int, const char *, int); +static boolean_t do_proc_read(int, void *, size_t); + static char *prog; static int header = 1; /* true if -h flag: don't print heading */ static int lflag = 0; /* true if -l flag: w command format */ @@ -150,6 +158,18 @@ static time_t proctime; /* cpu time of process in doing */ static int empty; static pid_t curpid; +/* + * Basic privs we never need and can drop. This is likely not exhaustive, + * but should significantly reduce any potential attack surfaces. + */ +static const char *drop_privs[] = { + PRIV_FILE_WRITE, + PRIV_NET_ACCESS, + PRIV_PROC_EXEC, + PRIV_PROC_FORK, + PRIV_FILE_LINK_ANY +}; + #if SIGQUIT > SIGINT #define ACTSIZE SIGQUIT #else @@ -168,23 +188,62 @@ main(int argc, char *argv[]) struct psinfo info; struct sigaction actinfo[ACTSIZE]; struct pstatus statinfo; - size_t size; struct stat sbuf; struct utsname uts; DIR *dirp; struct dirent *dp; - char pname[64]; - char *fname; - int procfd; + char pname[PATH_MAX]; + int procfd, dirfd; int i; int days, hrs, mins; int entries; + priv_set_t *pset; + + if (__init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER, NULL) != 0) { + err(EXIT_FAILURE, "failed to enable privilege bracketing"); + } + + /* + * After setting up privilege bracketing, we can further reduce the + * privileges in use. The effective set is set to the basic set minus + * the privs in drop_privs. The permitted set is the effective set + * plus PRIV_PROC_OWNER (i.e. the privilege being bracketed). + */ + pset = priv_allocset(); + if (pset == NULL) + err(EXIT_FAILURE, "priv_allocset failed"); + + priv_basicset(pset); + for (i = 0; i < ARRAY_SIZE(drop_privs); i++) { + if (priv_delset(pset, drop_privs[i]) != 0) { + err(EXIT_FAILURE, + "failed to remove %s privilege from privilege set", + drop_privs[i]); + } + } + + if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pset) < 0) + err(EXIT_FAILURE, "failed setting effective privilege set"); + + if (priv_addset(pset, PRIV_PROC_OWNER) != 0) { + err(EXIT_FAILURE, + "failed to add PRIV_PROC_OWNER privilege to privilege set"); + } + + if (setppriv(PRIV_SET, PRIV_PERMITTED, pset) < 0) + err(EXIT_FAILURE, "failed to set permitted privilege set"); /* - * This program needs the proc_owner privilege + * Unfortunately, when run as root, privilege bracketing is a no-op, + * so we have to add PRIV_PROC_OWNER into our effective set for things + * to work. */ - (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER, - (char *)NULL); + if (getuid() == 0 && setppriv(PRIV_SET, PRIV_EFFECTIVE, pset) < 0) { + err(EXIT_FAILURE, "failed to set effective privilege set"); + } + + priv_freeset(pset); + pset = NULL; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) @@ -229,25 +288,18 @@ main(int argc, char *argv[]) * read the UTMPX_FILE (contains information about * each logged in user) */ - if (stat(UTMPX_FILE, &sbuf) == ERR) { - (void) fprintf(stderr, gettext("%s: stat error of %s: %s\n"), - prog, UTMPX_FILE, strerror(errno)); - exit(1); - } + if (stat(UTMPX_FILE, &sbuf) < 0) + err(EXIT_FAILURE, gettext("stat error of %s"), UTMPX_FILE); + entries = sbuf.st_size / sizeof (struct futmpx); - size = sizeof (struct utmpx) * entries; - if ((ut = malloc(size)) == NULL) { - (void) fprintf(stderr, gettext("%s: malloc error of %s: %s\n"), - prog, UTMPX_FILE, strerror(errno)); - exit(1); - } + if ((ut = calloc(entries, sizeof (struct utmpx))) == NULL) + err(EXIT_FAILURE, gettext("calloc error of %s"), UTMPX_FILE); (void) utmpxname(UTMPX_FILE); utmpbegin = ut; - /* LINTED pointer cast may result in improper alignment */ - utmpend = (struct utmpx *)((char *)utmpbegin + size); + utmpend = utmpbegin + entries; setutxent(); while ((ut < utmpend) && ((utp = getutxent()) != NULL)) @@ -306,31 +358,36 @@ main(int argc, char *argv[]) * loop through /proc, reading info about each process * and build the parent/child tree */ - if (!(dirp = opendir(PROCDIR))) { - (void) fprintf(stderr, gettext("%s: could not open %s: %s\n"), - prog, PROCDIR, strerror(errno)); - exit(1); - } + if ((dirp = opendir(PROCDIR)) == NULL) + err(EXIT_FAILURE, gettext("could not open %s"), PROCDIR); while ((dp = readdir(dirp)) != NULL) { if (dp->d_name[0] == '.') continue; -retry: - (void) snprintf(pname, sizeof (pname), - "%s/%s/", PROCDIR, dp->d_name); - fname = pname + strlen(pname); - (void) strcpy(fname, "psinfo"); - if ((procfd = open(pname, O_RDONLY)) < 0) + + if (snprintf(pname, sizeof (pname), "%s/%s", PROCDIR, + dp->d_name) > sizeof (pname)) continue; - if (read(procfd, &info, sizeof (info)) != sizeof (info)) { - int err = errno; - (void) close(procfd); - if (err == EAGAIN) - goto retry; - if (err != ENOENT) - (void) fprintf(stderr, gettext( - "%s: read() failed on %s: %s\n"), - prog, pname, strerror(err)); + + dirfd = priv_proc_open(pname, O_RDONLY | O_DIRECTORY); + + if (dirfd < 0) { + if (errno == ENOENT) + continue; + warn(gettext("failed to open %s"), pname); + continue; + } + + + procfd = priv_proc_openat(dirfd, "psinfo", O_RDONLY); + if (procfd < 0) { + (void) close(dirfd); + continue; + } + + if (!do_proc_read(procfd, &info, sizeof (info))) { + warn(gettext("read() failed on %s"), pname); + (void) close(dirfd); continue; } (void) close(procfd); @@ -341,34 +398,24 @@ retry: up->p_time = 0; up->p_ctime = 0; up->p_igintr = 0; - (void) strncpy(up->p_comm, info.pr_fname, - sizeof (info.pr_fname)); + (void) strlcpy(up->p_comm, info.pr_fname, + sizeof (up->p_comm)); up->p_args[0] = 0; if (up->p_state != NONE && up->p_state != ZOMBIE) { - (void) strcpy(fname, "status"); - - /* now we need the proc_owner privilege */ - (void) __priv_bracket(PRIV_ON); - - procfd = open(pname, O_RDONLY); - - /* drop proc_owner privilege after open */ - (void) __priv_bracket(PRIV_OFF); - - if (procfd < 0) + procfd = priv_proc_openat(dirfd, "status", O_RDONLY); + if (procfd < 0) { + (void) close(dirfd); continue; + } + + if (!do_proc_read(procfd, &statinfo, + sizeof (statinfo))) { + warn(gettext("read() failed on %s/status"), + pname); - if (read(procfd, &statinfo, sizeof (statinfo)) - != sizeof (statinfo)) { - int err = errno; (void) close(procfd); - if (err == EAGAIN) - goto retry; - if (err != ENOENT) - (void) fprintf(stderr, gettext( - "%s: read() failed on %s: %s \n"), - prog, pname, strerror(err)); + (void) close(dirfd); continue; } (void) close(procfd); @@ -378,31 +425,22 @@ retry: up->p_ctime = statinfo.pr_cutime.tv_sec + statinfo.pr_cstime.tv_sec; - (void) strcpy(fname, "sigact"); - - /* now we need the proc_owner privilege */ - (void) __priv_bracket(PRIV_ON); - - procfd = open(pname, O_RDONLY); + procfd = priv_proc_openat(dirfd, "sigact", O_RDONLY); + if (procfd < 0) { + (void) close(dirfd); + continue; + } - /* drop proc_owner privilege after open */ - (void) __priv_bracket(PRIV_OFF); + if (!do_proc_read(procfd, actinfo, sizeof (actinfo))) { + warn(gettext("read() failed on %s/sigact"), + pname); - if (procfd < 0) - continue; - if (read(procfd, actinfo, sizeof (actinfo)) - != sizeof (actinfo)) { - int err = errno; (void) close(procfd); - if (err == EAGAIN) - goto retry; - if (err != ENOENT) - (void) fprintf(stderr, gettext( - "%s: read() failed on %s: %s \n"), - prog, pname, strerror(err)); + (void) close(dirfd); continue; } (void) close(procfd); + (void) close(dirfd); up->p_igintr = actinfo[SIGINT-1].sa_handler == SIG_IGN && @@ -415,14 +453,18 @@ retry: */ if (lflag) { /* w command needs args */ clnarglist(info.pr_psargs); - (void) strcpy(up->p_args, info.pr_psargs); + (void) strlcpy(up->p_args, info.pr_psargs, + sizeof (up->p_args)); if (up->p_args[0] == 0 || up->p_args[0] == '-' && up->p_args[1] <= ' ' || up->p_args[0] == '?') { - (void) strcat(up->p_args, " ("); - (void) strcat(up->p_args, up->p_comm); - (void) strcat(up->p_args, ")"); + (void) strlcat(up->p_args, " (", + sizeof (up->p_args)); + (void) strlcat(up->p_args, up->p_comm, + sizeof (up->p_args)); + (void) strlcat(up->p_args, ")", + sizeof (up->p_args)); } } @@ -452,7 +494,34 @@ retry: } /* revert to non-privileged user */ - (void) __priv_relinquish(); + __priv_relinquish(); + if (getuid() == 0) { + /* + * Since the privilege bracketing functions are effectively + * no-ops when running as root, we must explicitly + * relinquish PRIV_PROC_OWNER ourselves. + */ + pset = priv_allocset(); + if (pset == NULL) { + err(EXIT_FAILURE, + gettext("failed to allocate privilege set")); + } + + priv_emptyset(pset); + + if (priv_addset(pset, PRIV_PROC_OWNER) != 0) { + err(EXIT_FAILURE, gettext("failed to add " + "PRIV_PROC_OWNER to privilege set")); + } + + if (setppriv(PRIV_OFF, PRIV_PERMITTED, pset) != 0) { + err(EXIT_FAILURE, + gettext("failed to set permitted privilege set")); + } + + priv_freeset(pset); + pset = NULL; + } (void) closedir(dirp); (void) time(&now); /* get current time */ @@ -553,7 +622,9 @@ showtotals(struct uproc *up) proctime = 0; empty = 1; curpid = -1; - (void) strcpy(doing, "-"); /* default act: normally never prints */ + + /* default act: normally never prints */ + (void) strlcpy(doing, "-", sizeof (doing)); calctotals(up); /* print CPU time for all processes & children */ @@ -598,7 +669,7 @@ calctotals(struct uproc *up) if (up->p_upid > curpid && (!up->p_igintr || empty)) { curpid = up->p_upid; - (void) strcpy(doing, up->p_args); + (void) strlcpy(doing, up->p_args, sizeof (doing)); } /* descend for its children */ @@ -610,34 +681,36 @@ calctotals(struct uproc *up) } static char * -devadd(char *name, dev_t ddev) +devadd(const char *name, dev_t ddev) { struct devl *dp; int leng, start, i; if (ndevs == maxdev) { - maxdev += DNINCR; - dp = realloc(devl, maxdev * sizeof (struct devl)); - if (!dp) { - (void) fprintf(stderr, - gettext("%s: out of memory!: %s\n"), - prog, strerror(errno)); - exit(1); - } + uint_t newdev; + + newdev = maxdev + DNINCR; + if (newdev < DNINCR) + errx(EXIT_FAILURE, gettext("devadd overflow")); + + dp = recallocarray(devl, maxdev, newdev, sizeof (struct devl)); + if (dp == NULL) + err(EXIT_FAILURE, gettext("out of memory!")); + maxdev = newdev; devl = dp; } dp = &devl[ndevs++]; dp->ddev = ddev; if (name == NULL) { - (void) strcpy(dp->dname, " ? "); + (void) strlcpy(dp->dname, " ? ", sizeof (dp->dname)); return (dp->dname); } leng = strlen(name); if (leng < DEVNAMELEN + 4) { /* strip off "/dev/" */ - (void) strcpy(dp->dname, &name[5]); + (void) strlcpy(dp->dname, &name[5], sizeof (dp->dname)); } else { /* strip enough off the front to fit */ start = leng - DEVNAMELEN - 1; @@ -645,9 +718,9 @@ devadd(char *name, dev_t ddev) for (i = start; i < leng && name[i] != '/'; i++) ; if (i == leng) - (void) strncpy(dp->dname, &name[start], DEVNAMELEN); + (void) strlcpy(dp->dname, &name[start], DEVNAMELEN); else - (void) strncpy(dp->dname, &name[i+1], DEVNAMELEN); + (void) strlcpy(dp->dname, &name[i+1], DEVNAMELEN); } return (dp->dname); } @@ -715,11 +788,9 @@ findhash(pid_t pid) } } tp = malloc(sizeof (*tp)); /* add new node */ - if (!tp) { - (void) fprintf(stderr, gettext("%s: out of memory!: %s\n"), - prog, strerror(errno)); - exit(1); - } + if (tp == NULL) + err(EXIT_FAILURE, gettext("out of memory!")); + (void) memset((char *)tp, 0, sizeof (*tp)); tp->p_upid = pid; tp->p_state = NONE; @@ -802,8 +873,8 @@ findidle(char *devname) time_t lastaction, diff; char ttyname[64]; - (void) strcpy(ttyname, "/dev/"); - (void) strcat(ttyname, devname); + (void) strlcpy(ttyname, "/dev/", sizeof (ttyname)); + (void) strlcat(ttyname, devname, sizeof (ttyname)); if (stat(ttyname, &stbuf) != -1) { lastaction = stbuf.st_atime; diff = now - lastaction; @@ -835,3 +906,66 @@ clnarglist(char *arglist) } } } + +static int +priv_proc_open(const char *path, int oflag) +{ + int fd, errsave = 0; + + if (__priv_bracket(PRIV_ON) != 0) + err(EXIT_FAILURE, gettext("privilege bracketing failed")); + + do { + fd = open(path, oflag); + if (fd < 0) + errsave = errno; + } while (fd < 0 && errno == EAGAIN); + + if (__priv_bracket(PRIV_OFF) != 0) + err(EXIT_FAILURE, gettext("privilege bracketing failed")); + + if (fd < 0) + errno = errsave; + + return (fd); +} + +static int +priv_proc_openat(int dfd, const char *path, int mode) +{ + int fd, errsave = 0; + + if (__priv_bracket(PRIV_ON) != 0) + err(EXIT_FAILURE, gettext("privilege bracketing failed")); + + do { + fd = openat(dfd, path, mode); + if (fd < 0) + errsave = errno; + } while (fd < 0 && errno == EAGAIN); + + if (__priv_bracket(PRIV_OFF) != 0) + err(EXIT_FAILURE, gettext("privilege bracketing failed")); + + if (fd < 0) + errno = errsave; + + return (fd); +} + +static boolean_t +do_proc_read(int fd, void *buf, size_t bufsize) +{ + ssize_t n; + + do { + n = pread(fd, buf, bufsize, 0); + if (n == bufsize) + return (B_TRUE); + /* + * Retry on a partial read or EAGAIN, otherwise fail + */ + } while (n >= 0 || errno == EAGAIN); + + return (B_FALSE); +} |
