summaryrefslogtreecommitdiff
path: root/usr/src/cmd/whodo/whodo.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/whodo/whodo.c')
-rw-r--r--usr/src/cmd/whodo/whodo.c354
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);
+}