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: