tcpdump mailing list archives
Re: why doesn't tcpdump drop privileges?
From: Andrew Pimlott <andrew () pimlott net>
Date: Fri, 23 Jan 2004 21:14:54 -0500
There is a new attempt at the end of this message. I referred to Wietse Venema's chrootuid as suggested. One question with doing a chroot is what directory to chroot into. sshd with privilege separation uses /var/run/sshd (on my Debian system). I considered assuming a /var/run/tcpdump directory or even a generic /var/run/empty, but instead decided to create a new temporary directory, since this requires less setup. If others would prefer a pre-determined directory, that's fine. I observed that on Linux 2.4, I could mkdir, chdir, rmdir, chroot to end up rooted in a non-existent directory. This saves leaking temporary directories (since we have no way of cleaning them up after we chroot and setuid). I suspect this is fairly portable. There doesn't seem to be a good, portable C function for creating temporary directories. I try to honor TMPDIR then use /tmp, but perhaps there are other conventions I should follow. I used mkdtemp, but I'm not sure how portable that is; it can be replaced if needed. I threw in a bunch of error checking. I don't think the calls I checked should ever fail on a normal unix system, so it's sort of a judgement call whether to fail on that odd system where they do. I decided to err on the side of reporting errors for now. I also took into account some of the comments in Jefferson's message: On Wed, Jan 21, 2004 at 10:45:31PM -0500, Jefferson Ogata wrote:
Andrew Pimlott wrote:I agree that a dedicated user is better. However, I still think that defaulting to nobody will protect people (to some degree) on most systems, and I think the risk of nobody being a bad choice is low (certainly it can't be worse than remaining root). If nobody doesn't exist, oh well.You could also just pick an arbitrary numeric uid if nobody fails. So maybe try getpwnam("pcap") first, then getpwnam("nobody"), then find a uid > 1024 that is unused for your last-ditch default.
This is going too far IMO. If nobody doesn't exist, then the system is sufficiently unusual that I don't think we should make any guesses. I also didn't try pcap first, because it seems to be Redhat-specific. If others think a pcap user should be generally encouraged, I'm fine with adding it.
There's a big problem domain you're not fully treating, which is what happens when one process captures and writes to a pcap file, and someone else comes along and runs a protocol dissector on the saved file later. First, your patch is dropping privileges before opening the pcap file, which looks out of order to me.This is important so that a setuid tcpdump (I can't imagine why anyone would do that, but it seems to be supported in the code) can't open root trace files, as mentioned in the existing comments. I didn't change this from the old behavior.But then how can root read his own trace file when it's mode 0600? I think if ruid == euid you want to open the trace file before dropping privileges.
Duh, obviously right. I separated drop_euid for opening the trace file, from drop_privileges for dropping "all" privileges (for some value of "all"). Andrew --- tcpdump.c.orig 2003-12-17 20:22:57.000000000 -0500 +++ tcpdump.c 2004-01-23 20:04:01.000000000 -0500 @@ -57,6 +57,7 @@ #endif /* WIN32 */ #include <pcap.h> +#include <pwd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> @@ -104,7 +105,12 @@ int32_t thiszone; /* seconds offset from gmt to local time */ +static uid_t euid; /* for drop_euid, restore_euid */ + /* Forwards */ +static void drop_euid(); +static void restore_euid(); +static void drop_privileges(); static RETSIGTYPE cleanup(int); static void usage(void) __attribute__((noreturn)); static void show_dlts_and_exit(pcap_t *pd) __attribute__((noreturn)); @@ -617,20 +623,22 @@ if (RFileName != NULL) { int dlt; const char *dlt_name; + uid_t euid; -#ifndef WIN32 /* - * We don't need network access, so relinquish any set-UID - * or set-GID privileges we have (if any). - * * We do *not* want set-UID privileges when opening a * trace file, as that might let the user read other * people's trace files (especially if we're set-UID * root). + * + * Restore euid afterwards so that drop_privileges works. */ - setuid(getuid()); -#endif /* WIN32 */ + drop_euid(); pd = pcap_open_offline(RFileName, ebuf); + seteuid(euid); + + drop_privileges(); + if (pd == NULL) error("%s", ebuf); dlt = pcap_datalink(pd); @@ -712,12 +720,8 @@ netmask = 0; warning("%s", ebuf); } - /* - * Let user own process after socket has been opened. - */ -#ifndef WIN32 - setuid(getuid()); -#endif /* WIN32 */ + + drop_privileges(); } if (infile) cmdbuf = read_infile(infile); @@ -834,6 +838,78 @@ exit(status == -1 ? 1 : 0); } +static void +drop_euid() +{ +#ifndef WIN32 + euid = geteuid(); + if (seteuid(getuid()) != 0) + error("seteuid to ruid %d failed", getuid()); +#endif /* WIN32 */ +} + +static void +restore_euid() +{ +#ifndef WIN32 + if (seteuid(euid) != 0) + error("seteuid back to %d failed", euid); +#endif /* WIN32 */ +} + +/* + * Since we are processing untrusted data, drop privileges to mitigate the + * risk due to bugs in analysis code. + * + * Parts cribbed from Wietse Venema's chrootuid. + */ +static void +drop_privileges() +{ +#ifndef WIN32 + struct passwd *nobody = NULL; + uid_t new_uid; + + /* If run as root, switch to nobody. Otherwise, back to ruid. */ + if (getuid() == 0 && (nobody = getpwnam("nobody"))) { + new_uid = nobody->pw_uid; + if (setgid(nobody->pw_gid) != 0) + error("setgid to %d failed", nobody->pw_gid); + if (initgroups("nobody", nobody->pw_gid) != 0) + error("initgroups for nobody failed"); + endpwent(); /* in case passwd file is still open */ + endgrent(); /* in case group file is still open */ + } else + new_uid = getuid(); + + /* Try to chroot to an empty dir. */ + if (geteuid() == 0) { + char *tmpdir; + char chrootdir[1024]; + int rc; + + tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL) + tmpdir = "/tmp"; + + rc = snprintf(chrootdir, sizeof(chrootdir), + "%s/tcpdump.XXXXXX", tmpdir); + + if (mkdtemp(chrootdir) == NULL) + error("couldn't create %s", chrootdir); + if (chdir(chrootdir) != 0) + error("couldn't chdir to %s", chrootdir); + if (rmdir(chrootdir) != 0) + error("couldn't remove %s", chrootdir); + if (chroot(".") != 0) + error("couldn't chroot"); + } + + if (setuid(new_uid) != 0) + error("setuid to %d failed", new_uid); +#endif /* WIN32 */ +} + /* make a clean exit on interrupts */ static RETSIGTYPE cleanup(int signo _U_) - This is the TCPDUMP workers list. It is archived at http://www.tcpdump.org/lists/workers/index.html To unsubscribe use mailto:tcpdump-workers-request () tcpdump org?body=unsubscribe
Current thread:
- why doesn't tcpdump drop privileges? Andrew Pimlott (Jan 20)
- Re: why doesn't tcpdump drop privileges? Jefferson Ogata (Jan 20)
- Re: why doesn't tcpdump drop privileges? Andrew Pimlott (Jan 20)
- Re: why doesn't tcpdump drop privileges? Jefferson Ogata (Jan 20)
- Re: why doesn't tcpdump drop privileges? Andrew Pimlott (Jan 21)
- Re: why doesn't tcpdump drop privileges? Jefferson Ogata (Jan 21)
- Re: why doesn't tcpdump drop privileges? Andrew Pimlott (Jan 23)
- Re: why doesn't tcpdump drop privileges? Andrew Pimlott (Jan 20)
- Re: why doesn't tcpdump drop privileges? Ryan Mooney (Jan 21)
- Re: why doesn't tcpdump drop privileges? Jefferson Ogata (Jan 21)
- Re: why doesn't tcpdump drop privileges? Jefferson Ogata (Jan 20)
- Re: why doesn't tcpdump drop privileges? Andrew Pimlott (Jan 20)
- Re: why doesn't tcpdump drop privileges? Andrew Pimlott (Jan 20)
- Re: why doesn't tcpdump drop privileges? Pekka Savola (Jan 20)
- Re: why doesn't tcpdump drop privileges? Andrew Pimlott (Jan 21)
- Re: why doesn't tcpdump drop privileges? Pekka Savola (Jan 21)
- Re: why doesn't tcpdump drop privileges? Hannes Gredler (Jan 24)