OpenBSD's tcpdump(8) is separated into two processes: The packet parser
that will chroot(2) if possible, drop to an unprivileged user, and then
pledge(2) itself tightly as "stdio" before entering its main loop.
And the priviledged "monitor" process, which opens and fdpasses bpf(4)
and any input/output files for the packet parser process. In addition,
DNS requests may be serviced later at runtime.
Both processes communicate over a socketpair(2), with the unpriv process
sending commands to the monitor process, going through several internal
states (from privsep.c):
/*
* tcpdump goes through four states: STATE_INIT is where the
* bpf device and the input file is opened. In STATE_BPF, the
* pcap filter gets set. STATE_FILTER is used for parsing
* /etc/services and /etc/protocols and opening the output
* file. STATE_RUN is the packet processing part.
*/
And now for the diff..
This hoists opening pf.os(5) fingerprints '-o' from the 'RUN' state to
the 'FILTER' state, this allows for a reduced pledge(2) of "stdio bpf"
in the monitor process when using tcpdump '-n' (don't convert addresses
to names), or when writing packet captures to stdout '-w -', removing
the "rpath" promise.
In the normal use case, the pledge(2) now becomes "stdio rpath dns bpf":
'rpath dns' for DNS lookups
"bpf' for bpf(4) stats (when user hits ^C)
I couldn't find any use of the existing pledge(2) promises "inet unix
recvfd". I suspect these were previously required but no longer.
I've also removed the internal privsep "getline" code, which was passing
lines over the socket in favour of explicit descriptor passing, this was
the only instance found so I doubt the file lookup table will be needed
again in the future.
Finally.. we can drop root privileges in the monitor process as well
before we service any network requests, and if we know beforehand that
we never will, chroot(2).
This works for me.. but my networks packets are dull.
-Bryan.
Index: pfctl_osfp.c
===================================================================
RCS file: /cvs/src/usr.sbin/tcpdump/pfctl_osfp.c,v
retrieving revision 1.13
diff -u -p -u -r1.13 pfctl_osfp.c
--- usr.sbin/tcpdump/pfctl_osfp.c 28 May 2017 10:06:12 -0000 1.13
+++ usr.sbin/tcpdump/pfctl_osfp.c 14 Sep 2017 00:28:36 -0000
@@ -81,17 +81,14 @@ void print_name_list(int, struct
name
void sort_name_list(int, struct name_list *);
struct name_entry *lookup_name_list(struct name_list *, const char *);
-/* XXX arbitrary */
-#define MAX_FP_LINE 1024
-
/* Load fingerprints from a file */
int
pfctl_file_fingerprints(int dev, int opts, const char *fp_filename)
{
- u_char buf[MAX_FP_LINE];
+ FILE *in;
u_char *line;
size_t len;
- int i, lineno = 0;
+ int i, fd, lineno = 0;
int window, w_mod, ttl, df, psize, p_mod, mss, mss_mod, wscale,
wscale_mod, optcnt, ts0;
pf_tcpopts_t packed_tcpopts;
@@ -99,15 +96,22 @@ pfctl_file_fingerprints(int dev, int opt
struct pf_osfp_ioctl fp;
pfctl_flush_my_fingerprints(&classes);
+
+ fd = priv_open_pfosfp();
+ if (fd < 0)
+ return (1);
+
+ if ((in = fdopen(fd, "r")) == NULL) {
+ warn("%s", fp_filename);
+ return (1);
+ }
+
class = version = subtype = desc = tcpopts = NULL;
if ((opts & PF_OPT_NOACTION) == 0)
pfctl_clear_fingerprints(dev, opts);
- priv_getlines(FTAB_PFOSFP);
- while ((len = priv_getline(buf, sizeof(buf))) > 0) {
- buf[len -1] = '\n';
- line = buf;
+ while ((line = fgetln(in, &len)) != NULL) {
lineno++;
free(class);
free(version);
Index: privsep.c
===================================================================
RCS file: /cvs/src/usr.sbin/tcpdump/privsep.c,v
retrieving revision 1.47
diff -u -p -u -r1.47 privsep.c
--- usr.sbin/tcpdump/privsep.c 8 Sep 2017 19:30:13 -0000 1.47
+++ usr.sbin/tcpdump/privsep.c 14 Sep 2017 00:28:36 -0000
@@ -73,12 +73,13 @@ static const int allowed_max[] = {
/* INIT */ ALLOW(PRIV_OPEN_BPF) | ALLOW(PRIV_OPEN_DUMP) |
ALLOW(PRIV_SETFILTER),
/* BPF */ ALLOW(PRIV_SETFILTER),
- /* FILTER */ ALLOW(PRIV_OPEN_OUTPUT) | ALLOW(PRIV_GETSERVENTRIES) |
+ /* FILTER */ ALLOW(PRIV_OPEN_PFOSFP) | ALLOW(PRIV_OPEN_OUTPUT) |
+ ALLOW(PRIV_GETSERVENTRIES) |
ALLOW(PRIV_GETPROTOENTRIES) |
ALLOW(PRIV_ETHER_NTOHOST) | ALLOW(PRIV_INIT_DONE),
/* RUN */ ALLOW(PRIV_GETHOSTBYADDR) | ALLOW(PRIV_ETHER_NTOHOST) |
- ALLOW(PRIV_GETRPCBYNUMBER) | ALLOW(PRIV_GETLINES) |
- ALLOW(PRIV_LOCALTIME) | ALLOW(PRIV_PCAP_STATS),
+ ALLOW(PRIV_GETRPCBYNUMBER) | ALLOW(PRIV_LOCALTIME) |
+ ALLOW(PRIV_PCAP_STATS),
/* EXIT */ 0
};
@@ -90,21 +91,10 @@ static int allowed_ext[] = {
/* INIT */ ALLOW(PRIV_SETFILTER),
/* BPF */ ALLOW(PRIV_SETFILTER),
/* FILTER */ ALLOW(PRIV_GETSERVENTRIES),
- /* RUN */ ALLOW(PRIV_GETLINES) | ALLOW(PRIV_LOCALTIME) |
- ALLOW(PRIV_PCAP_STATS),
+ /* RUN */ ALLOW(PRIV_LOCALTIME) | ALLOW(PRIV_PCAP_STATS),
/* EXIT */ 0
};
-struct ftab {
- char *name;
- int max;
- int count;
-};
-
-static struct ftab file_table[] = {{PF_OSFP_FILE, 1, 0}};
-
-#define NUM_FILETAB (sizeof(file_table) / sizeof(struct ftab))
-
int debug_level = LOG_INFO;
int priv_fd = -1;
volatile pid_t child_pid = -1;
@@ -112,8 +102,11 @@ static volatile sig_atomic_t cur_state =
extern void set_slave_signals(void);
+static void drop_privs(int);
+
static void impl_open_bpf(int, int *);
static void impl_open_dump(int, const char *);
+static void impl_open_pfosfp(int);
static void impl_open_output(int, const char *);
static void impl_setfilter(int, char *, int *);
static void impl_init_done(int, int *);
@@ -123,17 +116,47 @@ static void impl_getrpcbynumber(int);
static void impl_getserventries(int);
static void impl_getprotoentries(int);
static void impl_localtime(int fd);
-static void impl_getlines(int);
static void impl_pcap_stats(int, int *);
static void test_state(int, int);
static void logmsg(int, const char *, ...);
+static void
+drop_privs(int nochroot)
+{
+ struct passwd *pw;
+
+ /*
+ * If run as regular user, then tcpdump will rely on
+ * pledge(2). If we are root, we want to chroot also..
+ */
+ if (getuid() != 0)
+ return;
+
+ pw = getpwnam("_tcpdump");
+ if (pw == NULL)
+ errx(1, "unknown user _tcpdump");
+
+ if (!nochroot) {
+ if (chroot(pw->pw_dir) == -1)
+ err(1, "unable to chroot");
+ if (chdir("/") == -1)
+ err(1, "unable to chdir");
+ }
+
+ /* drop to _tcpdump */
+ if (setgroups(1, &pw->pw_gid) == -1)
+ err(1, "setgroups() failed");
+ if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
+ err(1, "setresgid() failed");
+ if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
+ err(1, "setresuid() failed");
+}
+
int
priv_init(int argc, char **argv)
{
int i, nargc, socks[2];
- struct passwd *pw;
sigset_t allsigs, oset;
char **privargv;
@@ -159,29 +182,7 @@ priv_init(int argc, char **argv)
set_slave_signals();
sigprocmask(SIG_SETMASK, &oset, NULL);
- /*
- * If run as regular user, packet parser will rely on
- * pledge(2). If we are root, we want to chroot also..
- */
- if (getuid() != 0)
- return (0);
-
- pw = getpwnam("_tcpdump");
- if (pw == NULL)
- errx(1, "unknown user _tcpdump");
-
- if (chroot(pw->pw_dir) == -1)
- err(1, "unable to chroot");
- if (chdir("/") == -1)
- err(1, "unable to chdir");
-
- /* drop to _tcpdump */
- if (setgroups(1, &pw->pw_gid) == -1)
- err(1, "setgroups() failed");
- if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
- err(1, "setresgid() failed");
- if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
- err(1, "setresuid() failed");
+ drop_privs(0);
return (0);
}
@@ -207,7 +208,7 @@ __dead void
priv_exec(int argc, char *argv[])
{
int bpfd = -1;
- int i, sock, cmd, nflag = 0, Pflag = 0;
+ int i, sock, cmd, nflag = 0, oflag = 0, Pflag = 0;
char *cmdbuf, *infile = NULL;
char *RFileName = NULL;
char *WFileName = NULL;
@@ -229,6 +230,10 @@ priv_exec(int argc, char *argv[])
nflag++;
break;
+ case 'o':
+ oflag = 1;
+ break;
+
case 'r':
RFileName = optarg;
break;
@@ -273,6 +278,8 @@ priv_exec(int argc, char *argv[])
allowed_ext[STATE_RUN] |= ALLOW(PRIV_GETRPCBYNUMBER);
allowed_ext[STATE_FILTER] |= ALLOW(PRIV_GETPROTOENTRIES);
}
+ if (oflag)
+ allowed_ext[STATE_FILTER] |= ALLOW(PRIV_OPEN_PFOSFP);
if (infile)
cmdbuf = read_infile(infile);
@@ -293,6 +300,10 @@ priv_exec(int argc, char *argv[])
test_state(cmd, STATE_BPF);
impl_open_dump(sock, RFileName);
break;
+ case PRIV_OPEN_PFOSFP:
+ test_state(cmd, STATE_FILTER);
+ impl_open_pfosfp(sock);
+ break;
case PRIV_OPEN_OUTPUT:
test_state(cmd, STATE_RUN);
impl_open_output(sock, WFileName);
@@ -305,8 +316,15 @@ priv_exec(int argc, char *argv[])
test_state(cmd, STATE_RUN);
impl_init_done(sock, &bpfd);
- if (pledge("stdio rpath inet unix dns recvfd bpf",
NULL) == -1)
- err(1, "pledge");
+ if (!nflag && WFileName == NULL) {
+ drop_privs(1);
+ if (pledge("stdio rpath dns bpf", NULL) == -1)
+ err(1, "pledge");
+ } else {
+ drop_privs(0);
+ if (pledge("stdio bpf", NULL) == -1)
+ err(1, "pledge");
+ }
break;
case PRIV_GETHOSTBYADDR:
@@ -333,10 +351,6 @@ priv_exec(int argc, char *argv[])
test_state(cmd, STATE_RUN);
impl_localtime(sock);
break;
- case PRIV_GETLINES:
- test_state(cmd, STATE_RUN);
- impl_getlines(sock);
- break;
case PRIV_PCAP_STATS:
test_state(cmd, STATE_RUN);
impl_pcap_stats(sock, &bpfd);
@@ -404,6 +418,24 @@ impl_open_dump(int fd, const char *RFile
}
static void
+impl_open_pfosfp(int fd)
+{
+ int file, err = 0;
+
+ logmsg(LOG_DEBUG, "[priv]: msg PRIV_OPEN_PFOSFP received");
+
+ file = open(PF_OSFP_FILE, O_RDONLY, 0);
+ err = errno;
+ if (file < 0)
+ logmsg(LOG_DEBUG, "[priv]: failed to open %s: %s",
+ PF_OSFP_FILE, strerror(errno));
+ send_fd(fd, file);
+ must_write(fd, &err, sizeof(int));
+ if (file >= 0)
+ close(file);
+}
+
+static void
impl_open_output(int fd, const char *WFileName)
{
int file, err;
@@ -565,55 +597,6 @@ impl_localtime(int fd)
}
static void
-impl_getlines(int fd)
-{
- FILE *fp;
- char *buf, *lbuf, *file;
- size_t len, fid;
-
- logmsg(LOG_DEBUG, "[priv]: msg PRIV_GETLINES received");
-
- must_read(fd, &fid, sizeof(size_t));
- if (fid >= NUM_FILETAB)
- errx(1, "invalid file id");
-
- file = file_table[fid].name;
-
- if (file == NULL)
- errx(1, "invalid file referenced");
-
- if (file_table[fid].count >= file_table[fid].max)
- errx(1, "maximum open count exceeded for %s", file);
-
- file_table[fid].count++;
-
- if ((fp = fopen(file, "r")) == NULL) {
- write_zero(fd);
- return;
- }
-
- lbuf = NULL;
- while ((buf = fgetln(fp, &len))) {
- if (buf[len - 1] == '\n')
- buf[len - 1] = '\0';
- else {
- if ((lbuf = malloc(len + 1)) == NULL)
- err(1, NULL);
- memcpy(lbuf, buf, len);
- lbuf[len] = '\0';
- buf = lbuf;
- }
-
- write_string(fd, buf);
-
- free(lbuf);
- lbuf = NULL;
- }
- write_zero(fd);
- fclose(fp);
-}
-
-static void
impl_pcap_stats(int fd, int *bpfd)
{
struct pcap_stat stats;
@@ -774,17 +757,6 @@ priv_localtime(const time_t *t)
return <
}
-/* start getting lines from a file */
-void
-priv_getlines(size_t sz)
-{
- if (priv_fd < 0)
- errx(1, "%s called from privileged portion", __func__);
-
- write_command(priv_fd, PRIV_GETLINES);
- must_write(priv_fd, &sz, sizeof(size_t));
-}
-
int
priv_pcap_stats(struct pcap_stat *ps)
{
@@ -796,16 +768,20 @@ priv_pcap_stats(struct pcap_stat *ps)
return (0);
}
-/* retrieve a line from a file, should be called repeatedly after calling
- priv_getlines(), until it returns zero. */
-size_t
-priv_getline(char *line, size_t line_len)
+int
+priv_open_pfosfp(void)
{
- if (priv_fd < 0)
- errx(1, "%s called from privileged portion", __func__);
+ int fd, err = 0;
+ write_command(priv_fd, PRIV_OPEN_PFOSFP);
+
+ fd = receive_fd(priv_fd);
+ must_read(priv_fd, &err, sizeof(int));
+ if (fd < 0) {
+ warnc(err, "%s", PF_OSFP_FILE);
+ return (-1);
+ }
- /* read the line */
- return (read_string(priv_fd, line, line_len, __func__));
+ return (fd);
}
/* Read all data or return 1 for error. */
Index: privsep.h
===================================================================
RCS file: /cvs/src/usr.sbin/tcpdump/privsep.h,v
retrieving revision 1.10
diff -u -p -u -r1.10 privsep.h
--- usr.sbin/tcpdump/privsep.h 8 Sep 2017 19:10:57 -0000 1.10
+++ usr.sbin/tcpdump/privsep.h 14 Sep 2017 00:28:36 -0000
@@ -21,12 +21,10 @@
#define TCPDUMP_MAGIC 0xa1b2c3d4
-/* file ids used by priv_getlines */
-#define FTAB_PFOSFP 0
-
enum cmd_types {
PRIV_OPEN_BPF, /* open a bpf descriptor */
PRIV_OPEN_DUMP, /* open dump file for reading */
+ PRIV_OPEN_PFOSFP, /* open pf.os(5) fingerprint db for reading */
PRIV_OPEN_OUTPUT, /* open output file */
PRIV_SETFILTER, /* set a bpf read filter */
PRIV_GETHOSTBYADDR, /* resolve numeric address into hostname */
@@ -35,7 +33,6 @@ enum cmd_types {
PRIV_GETSERVENTRIES, /* get the service entries table */
PRIV_GETPROTOENTRIES, /* get the ip protocol entries table */
PRIV_LOCALTIME, /* return localtime */
- PRIV_GETLINES, /* get lines from a file */
PRIV_INIT_DONE, /* signal that the initialization is done */
PRIV_PCAP_STATS /* get pcap_stats() results */
};
@@ -75,12 +72,8 @@ void priv_getprotoentries(void);
calling priv_getprotoentries() until it returns zero */
size_t priv_getprotoentry(char *, size_t, int *);
-/* Start getting lines from a file */
-void priv_getlines(size_t);
-
-/* Retrieve a single line from a file, should be called repeatedly after
- calling priv_getlines() until it returns zero */
-size_t priv_getline(char *, size_t);
+/* Retrieve pf.os(5) fingerprints file descriptor */
+int priv_open_pfosfp();
/* Return the pcap statistics upon completion */
int priv_pcap_stats(struct pcap_stat *);
Index: tcpdump.c
===================================================================
RCS file: /cvs/src/usr.sbin/tcpdump/tcpdump.c,v
retrieving revision 1.80
diff -u -p -u -r1.80 tcpdump.c
--- usr.sbin/tcpdump/tcpdump.c 8 Sep 2017 19:10:57 -0000 1.80
+++ usr.sbin/tcpdump/tcpdump.c 14 Sep 2017 00:28:36 -0000
@@ -461,6 +461,8 @@ main(int argc, char **argv)
bpf_dump(fcode, dflag);
exit(0);
}
+ if (oflag)
+ oflag = init_pfosfp();
init_addrtoname(localnet, netmask);
if (WFileName) {
@@ -490,8 +492,6 @@ main(int argc, char **argv)
(void)fflush(stderr);
}
- if (oflag)
- oflag = init_pfosfp();
if (tflag > 0)
thiszone = gmt2local(0);