Blob Blame History Raw
Add, and document, a -u argument to change to a specified unprivileged user
after establishing sockets.

This patch rebases and combines arpwatch-drop.patch, which provided -u;
arpwatch-drop-man.patch, which documented it; and
arpwatch-2.1a15-dropgroup.patch, which fixed CVE-2012-2653 (RHBZ #825328) in
the original arpwatch-drop.patch, into a single combined patch. It also removes
an unnecessary and unchecked strdup() in the original patch that could have
theoretically led to a null pointer dereference.

diff -Naur arpwatch-3.2-original/arpwatch.8.in arpwatch-3.2/arpwatch.8.in
--- arpwatch-3.2-original/arpwatch.8.in	2021-12-14 19:47:54.000000000 -0500
+++ arpwatch-3.2/arpwatch.8.in	2021-12-16 08:18:21.803266980 -0500
@@ -43,6 +43,7 @@
 .Op Fl n Ar net[/width]
 .Op Fl x Ar net[/width]
 .Op Fl r Ar file
+.Op Fl u Ar username
 .Sh DESCRIPTION
 .Nm
 keeps track of ethernet/ip address pairings. It syslogs activity
@@ -137,13 +138,30 @@
 Note that an empty
 .Ar arp.dat
 file must be created before the first time you run
-.Fl arpwatch .
+.Nm .
+Also, the default directory (where
+.Ar arp.dat
+is stored) must be owned by
+.Ar username
+if the
+.Fl u
+flag is used.
 .Pp
 The
 .Fl s
 flag suppresses reports sent by email.
 .Pp
 The
+.Fl u
+flag causes
+.Nm
+to drop root privileges and change user ID to
+.Ar username
+and group ID to that of the primary group of
+.Ar username .
+This is recommended for security reasons.
+.Pp
+The
 .Fl v
 flag disables the reporting of VRRP/CARP ethernet prefixes as
 described in RFC5798 (@MACZERO@0:@MACZERO@0:5e:@MACZERO@0:@MACZERO@1:xx).
diff -Naur arpwatch-3.2-original/arpwatch.c arpwatch-3.2/arpwatch.c
--- arpwatch-3.2-original/arpwatch.c	2019-11-30 13:35:23.000000000 -0500
+++ arpwatch-3.2/arpwatch.c	2021-12-16 08:18:21.812267045 -0500
@@ -72,6 +72,8 @@
 #include <syslog.h>
 #include <unistd.h>
 
+#include <grp.h>
+#include <pwd.h>
 #include <pcap.h>
 
 #include "gnuc.h"
@@ -170,6 +172,24 @@
 int	toskip(u_int32_t);
 void	usage(void) __attribute__((noreturn));
 
+void dropprivileges(const char* user)
+{
+	struct passwd* const pw = getpwnam(user);
+	if (pw) {
+		if (setgid(pw->pw_gid) != 0 || setgroups(0, NULL) != 0 ||
+				setuid(pw->pw_uid) != 0) {
+			lg(LOG_ERR, "Couldn't change to '%.32s' uid=%d gid=%d",
+				       user, pw->pw_uid, pw->pw_gid);
+			exit(1);
+		}
+	} else {
+		lg(LOG_ERR, "Couldn't find user '%.32s' in /etc/passwd",
+			       user);
+		exit(1);
+	}
+	lg(LOG_DEBUG, "Running as uid=%d gid=%d", getuid(), getgid());
+}
+
 int
 main(int argc, char **argv)
 {
@@ -181,6 +201,7 @@
 	char *interface, *rfilename;
 	struct bpf_program code;
 	char errbuf[PCAP_ERRBUF_SIZE];
+	char* serveruser = NULL;
 
 	if (argv[0] == NULL)
 		prog = "arpwatch";
@@ -198,7 +219,7 @@
 	interface = NULL;
 	rfilename = NULL;
 	pd = NULL;
-	while ((op = getopt(argc, argv, "CdD:Ff:i:n:NpP:qr:svw:W:x:zZ")) != EOF)
+	while ((op = getopt(argc, argv, "CdD:Ff:i:n:NpP:qr:svw:W:x:zZu:")) != EOF)
 		switch (op) {
 
 		case 'C':
@@ -283,6 +304,17 @@
 			zeropad = 1;
 			break;
 
+		case 'u':
+			if (optarg) {
+				/* no need to strdup() a pointer into the
+				 * original arguments vector */
+				serveruser = optarg;
+			} else {
+				fprintf(stderr, "%s: Need username after -u\n", prog);
+				usage();
+			}
+			break;
+
 		default:
 			usage();
 		}
@@ -379,6 +411,11 @@
 		}
 	}
 
+	/* Explicit user change (privilege drop) with -u? */
+	if (serveruser) {
+		dropprivileges(serveruser);
+	}
+
 	/*
 	 * Revert to non-privileged user after opening sockets
 	 * (not needed on most systems).
@@ -927,6 +964,7 @@
 	    "usage: %s [-CdFNpqsvzZ] [-D arpdir] [-f datafile]"
 	    " [-i interface]\n\t"
 	    " [-P pidfile] [-w watcher@email] [-W watchee@email]\n\t"
-	    " [-n net[/width]] [-x net[/width]] [-r file]\n", prog);
+	    " [-n net[/width]] [-x net[/width]] [-r file] [-u username]\n",
+	    prog);
 	exit(1);
 }