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]