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:[EMAIL PROTECTED]

Reply via email to