[radvd-devel-l] [RFC] Add privilege separation capability on Linux hosts.

Jim Paris jim at jtan.com
Wed Jan 23 21:30:50 EST 2008


Pekka Savola wrote:
> On Mon, 21 Jan 2008, Jim Paris wrote:
>> A similar idea to your "master/worker", but much easier to code, would
>> be to create a setuid helper binary.
>
> That's indeed a bit easier, but also a bit tricky if anyone is using the 
> chroot version of radvd.

I changed my mind.  Setting up a pipe and forking isn't so bad.
Here's a first pass at it, what do you think?

Before switching user, the code now calls privsep_init() which creates
a pipe and forks.  The forked copy stays running as root, and listens
to fixed-size messages on the pipe.  These messages tell it to write
a value to one of the four /proc files that we need to adjust.

It seems to work in my testing here, but there are some things to
think about:

- it probably should be made a runtime option whether to enable
  privsep, for those who really don't like to see an extra "root" 
  process lying around

- I hardcoded limits in the priviliged section (privsep_linux.c) so
  the passed values wouldn't be inappropriate.  For some of them,
  limits were already defined, but for others I had to make them up.
  They should be checked for sanity, especially
  MIN/MAX_AdvRetransTimer, as I have no idea about that one.

- The communication with the pipe is one-way to keep things simple.
  There's therefore no error indication if the writes fail.  Maybe
  another pipe could be set up in the reverse direction (deadlocks
  could be an issue), or the unprivileged code could wait a second and
  read-back the /proc file to see if it's changed.  Or the priviliged
  code could log it, but I wanted to keep that loop as straightforward
  as possible.

- The privsep happens after the chroot, so users would still need
  /proc mounted in there.

-jim


>From 13cfe7e91e73837c0f2a7761e7fef36fd00070e7 Mon Sep 17 00:00:00 2001
From: Jim Paris <jim at jtan.com>
Date: Wed, 23 Jan 2008 20:22:20 -0500
Subject: [PATCH] Add privilege separation capability on Linux hosts.

Under Linux, radvd needs to write to certain files in
/proc/sys/net/ipv6 depending on radvd.conf.  If the "-u username"
option is used, the unprivileged user does not have access to these
files.  Before dropping root privileges, create a separate process
connected by a pipe that can carry out these writes on our behalf.
---
 Makefile.am     |    4 +-
 configure.in    |    1 +
 defaults.h      |    6 ++
 device-linux.c  |   14 ++++
 privsep-bsd44.c |   34 ++++++++
 privsep-linux.c |  233 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 radvd.c         |    7 +-
 radvd.h         |   10 +++
 util.c          |   42 ++++++++++
 9 files changed, 346 insertions(+), 5 deletions(-)
 create mode 100644 privsep-bsd44.c
 create mode 100644 privsep-linux.c

diff --git a/Makefile.am b/Makefile.am
index 506e32f..655c80e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -28,9 +28,9 @@ COMMON_SRC = log.c socket.c recv.c util.c radvd.h defaults.h pathnames.h \
 sbin_PROGRAMS = radvd radvdump
 
 radvd_SOURCES = $(COMMON_SRC) radvd.c timer.c send.c process.c interface.c \
-	device.c device-common.c gram.y gram.h scanner.l
+	device.c device-common.c privsep.c gram.y gram.h scanner.l
 radvd_LDADD = -lfl
-EXTRA_radvd_SOURCES = device-linux.c device-bsd44.c
+EXTRA_radvd_SOURCES = device-linux.c device-bsd44.c privsep-linux.c privsep-bsd44.c
 YFLAGS = -d
 CFLAGS = @CFLAGS@ -Wall -Wpointer-arith  -Wcast-qual -Wcast-align -Wconversion \
 	-Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations
diff --git a/configure.in b/configure.in
index 95eba1e..34afd60 100644
--- a/configure.in
+++ b/configure.in
@@ -146,6 +146,7 @@ dnl Checks for library functions.
 AC_CHECK_FUNCS(getopt_long)
 
 AC_LINK_FILES(device-${arch}.c, device.c)
+AC_LINK_FILES(privsep-${arch}.c, privsep.c)
 
 AC_SUBST(VERSION)
 AC_SUBST(PATH_RADVD_CONF)
diff --git a/defaults.h b/defaults.h
index 4c90dc1..e65c920 100644
--- a/defaults.h
+++ b/defaults.h
@@ -108,9 +108,15 @@
 #define MAX_AdvDefaultLifetime		9000
 
 #define	MIN_AdvLinkMTU			1280
+#define	MAX_AdvLinkMTU			9000
 
+#define MIN_AdvReachableTime		1000
 #define MAX_AdvReachableTime		3600000 /* 1 hour in milliseconds */
 
+#define MIN_AdvRetransTimer		100
+#define MAX_AdvRetransTimer		3600000
+
+#define MIN_AdvCurHopLimit		2
 #define MAX_AdvCurHopLimit		255
 
 #define MAX_PrefixLen			128
diff --git a/device-linux.c b/device-linux.c
index 1c5520e..b007187 100644
--- a/device-linux.c
+++ b/device-linux.c
@@ -251,6 +251,9 @@ set_interface_var(const char *iface,
 int
 set_interface_linkmtu(const char *iface, uint32_t mtu)
 {
+	if (privsep_enabled())
+		return privsep_interface_linkmtu(iface, mtu);
+
 	return set_interface_var(iface,
 				 PROC_SYS_IP6_LINKMTU, "LinkMTU",
 				 mtu);
@@ -259,6 +262,9 @@ set_interface_linkmtu(const char *iface, uint32_t mtu)
 int
 set_interface_curhlim(const char *iface, uint8_t hlim)
 {
+	if (privsep_enabled())
+		return privsep_interface_curhlim(iface, hlim);
+
 	return set_interface_var(iface,
 				 PROC_SYS_IP6_CURHLIM, "CurHopLimit",
 				 hlim);
@@ -268,6 +274,10 @@ int
 set_interface_reachtime(const char *iface, uint32_t rtime)
 {
 	int ret;
+
+	if (privsep_enabled())
+		return privsep_interface_reachtime(iface, rtime);
+
 	ret = set_interface_var(iface,
 				PROC_SYS_IP6_BASEREACHTIME_MS,
 				NULL,
@@ -284,6 +294,10 @@ int
 set_interface_retranstimer(const char *iface, uint32_t rettimer)
 {
 	int ret;
+
+	if (privsep_enabled())
+		return privsep_interface_retranstimer(iface, rettimer);
+
 	ret = set_interface_var(iface,
 				PROC_SYS_IP6_RETRANSTIMER_MS,
 				NULL,
diff --git a/privsep-bsd44.c b/privsep-bsd44.c
new file mode 100644
index 0000000..c342468
--- /dev/null
+++ b/privsep-bsd44.c
@@ -0,0 +1,34 @@
+/*
+ *   $Id$
+ *
+ *   Authors:
+ *    Jim Paris			<jim at jtan.com>
+ *    Pedro Roque		<roque at di.fc.ul.pt>
+ *    Lars Fenneberg		<lf at elemental.net>	 
+ *
+ *   This software is Copyright 1996,1997,2008 by the above mentioned author(s), 
+ *   All Rights Reserved.
+ *
+ *   The license which is distributed with this software in the file COPYRIGHT
+ *   applies to this software. If your distribution is missing this file, you
+ *   may request it from <pekkas at netcore.fi>.
+ *
+ */
+
+#include <config.h>
+#include <includes.h>
+#include <radvd.h>
+#include <pathnames.h>
+
+/* Not available */
+int
+privsep_init(void)
+{
+	return 0;
+}
+
+int
+privsep_enabled(void)
+{
+	return 0;
+}
diff --git a/privsep-linux.c b/privsep-linux.c
new file mode 100644
index 0000000..3007993
--- /dev/null
+++ b/privsep-linux.c
@@ -0,0 +1,233 @@
+/*
+ *   $Id$
+ *
+ *   Authors:
+ *    Jim Paris			<jim at jtan.com>
+ *    Pedro Roque		<roque at di.fc.ul.pt>
+ *    Lars Fenneberg		<lf at elemental.net>	 
+ *
+ *   This software is Copyright 1996,1997,2008 by the above mentioned author(s), 
+ *   All Rights Reserved.
+ *
+ *   The license which is distributed with this software in the file COPYRIGHT
+ *   applies to this software. If your distribution is missing this file, you
+ *   may request it from <pekkas at netcore.fi>.
+ *
+ */
+
+#include <config.h>
+#include <includes.h>
+#include <radvd.h>
+#include <pathnames.h>
+
+int privsep_set(const char *iface, const char *var, uint32_t val);
+void privsep_read_loop(void);
+
+/* For reading or writing, depending on process */
+static int pfd = -1;
+
+/* Command types */
+enum privsep_type {
+	SET_INTERFACE_LINKMTU,
+	SET_INTERFACE_CURHLIM,
+	SET_INTERFACE_REACHTIME,
+	SET_INTERFACE_RETRANSTIMER,
+};
+
+/* Command sent over pipe is a fixed size binary structure. */
+struct privsep_command {
+	int type;
+	char iface[IFNAMSIZ];
+	uint32_t val;
+};
+
+/* Privileged read loop */
+void
+privsep_read_loop(void)
+{
+	struct privsep_command cmd;
+	int ret;
+
+	while (1) {
+		ret = readn(pfd, &cmd, sizeof(cmd));
+		if (ret <= 0) {
+			/* Error or EOF, give up */
+			close(pfd);
+			_exit(0);
+		}
+		if (ret != sizeof(cmd)) {
+			/* Short read, ignore */
+			continue;
+		}
+
+		cmd.iface[IFNAMSIZ-1] = '\0';
+
+		switch(cmd.type) {
+
+		case SET_INTERFACE_LINKMTU:
+			if (cmd.val < MIN_AdvLinkMTU || cmd.val > MAX_AdvLinkMTU)
+				break;
+			ret = privsep_set(cmd.iface, PROC_SYS_IP6_LINKMTU, cmd.val);
+			break;
+
+		case SET_INTERFACE_CURHLIM:
+			if (cmd.val < MIN_AdvCurHopLimit || cmd.val > MAX_AdvCurHopLimit)
+				break;
+			ret = privsep_set(cmd.iface, PROC_SYS_IP6_CURHLIM, cmd.val);
+			break;
+
+		case SET_INTERFACE_REACHTIME:
+			if (cmd.val < MIN_AdvReachableTime || cmd.val > MAX_AdvReachableTime) 
+				break;
+			ret = privsep_set(cmd.iface, PROC_SYS_IP6_BASEREACHTIME_MS, cmd.val);
+			if (ret == 0)
+				break;
+			privsep_set(cmd.iface, PROC_SYS_IP6_BASEREACHTIME, cmd.val / 1000);
+			break;
+
+		case SET_INTERFACE_RETRANSTIMER:
+			if (cmd.val < MIN_AdvRetransTimer || cmd.val > MAX_AdvRetransTimer)
+				break;
+			ret = privsep_set(cmd.iface, PROC_SYS_IP6_RETRANSTIMER_MS, cmd.val);
+			if (ret == 0)
+				break;
+			privsep_set(cmd.iface, PROC_SYS_IP6_RETRANSTIMER, cmd.val / 1000);
+			break;
+
+		default:
+			/* Bad command */
+			break;
+		}
+	}
+}
+
+int
+privsep_set(const char *iface, const char *var, uint32_t val)
+{
+	FILE *fp;
+	char spath[64+IFNAMSIZ];	/* XXX: magic constant */
+
+	if (snprintf(spath, sizeof(spath), var, iface) >= sizeof(spath))
+		return (-1);
+
+	if (access(spath, F_OK) != 0)
+		return -1;
+
+	fp = fopen(spath, "w");
+	if (!fp)
+		return -1;
+
+	fprintf(fp, "%u", val);
+	fclose(fp);
+	return 0;
+}
+
+/* Return 1 if privsep is currently enabled */
+int
+privsep_enabled(void)
+{
+	if (pfd < 0)
+		return 0;
+	return 1;
+}
+
+/* Fork to create privileged process connected by a pipe */
+int
+privsep_init(void)
+{
+	int pipefds[2];
+	int pid;
+
+	if (privsep_enabled())
+		return 0;
+
+	if (pipe(pipefds) != 0) {
+		flog(LOG_ERR, "Couldn't create privsep pipe\n");
+		return (-1);
+	}
+
+	pid = fork();
+	if (pid == -1) {
+		flog(LOG_ERR, "Couldn't fork for privsep\n");
+		return (-1);
+	}
+
+	if (pid == 0) {
+		int nullfd;
+
+		/* This will be the privileged child */
+		close(pipefds[1]);
+		pfd = pipefds[0];
+
+		/* Detach from stdio */
+		nullfd = open("/dev/null", O_RDONLY);
+		if (nullfd < 0) {
+			perror("/dev/null");
+			close(pfd);
+			_exit(1);
+		}
+		dup2(nullfd, 0);
+		dup2(nullfd, 1);
+		dup2(nullfd, 2);
+
+		privsep_read_loop();
+		close(pfd);
+		_exit(0);
+	}
+
+	/* Continue execution (will drop privileges soon) */
+	close(pipefds[0]);
+	pfd = pipefds[1];
+
+	return 0;
+}
+
+/* Interface calls for the unprivileged process */
+int
+privsep_interface_linkmtu(const char *iface, uint32_t mtu)
+{
+	struct privsep_command cmd;
+	cmd.type = SET_INTERFACE_LINKMTU;
+	strncpy(cmd.iface, iface, sizeof(cmd.iface));
+	cmd.val = mtu;
+
+	if (writen(pfd, &cmd, sizeof(cmd)) != sizeof(cmd))
+		return (-1);
+	return 0;
+}
+
+int
+privsep_interface_curhlim(const char *iface, uint32_t hlim)
+{
+	struct privsep_command cmd;
+	cmd.type = SET_INTERFACE_CURHLIM;
+	strncpy(cmd.iface, iface, sizeof(cmd.iface));
+	cmd.val = hlim;
+	if (writen(pfd, &cmd, sizeof(cmd)) != sizeof(cmd))
+		return (-1);
+	return 0;
+}
+
+int
+privsep_interface_reachtime(const char *iface, uint32_t rtime)
+{
+	struct privsep_command cmd;
+	cmd.type = SET_INTERFACE_REACHTIME;
+	strncpy(cmd.iface, iface, sizeof(cmd.iface));
+	cmd.val = rtime;
+	if (writen(pfd, &cmd, sizeof(cmd)) != sizeof(cmd))
+		return (-1);
+	return 0;
+}
+
+int
+privsep_interface_retranstimer(const char *iface, uint32_t rettimer)
+{
+	struct privsep_command cmd;
+	cmd.type = SET_INTERFACE_RETRANSTIMER;
+	strncpy(cmd.iface, iface, sizeof(cmd.iface));
+	cmd.val = rettimer;
+	if (writen(pfd, &cmd, sizeof(cmd)) != sizeof(cmd))
+		return (-1);
+	return 0;
+}
diff --git a/radvd.c b/radvd.c
index 2e02725..bc86d09 100644
--- a/radvd.c
+++ b/radvd.c
@@ -213,11 +213,11 @@ main(int argc, char *argv[])
 	if (readin_config(conf_file) < 0)
 		exit(1);
 
-	/* XXX: config_interface() only works with non-root user at startup */
-	config_interface();
-
 	/* drop root privileges if requested. */
 	if (username) {
+		if (privsep_init() < 0)
+			flog(LOG_WARNING, "Failed to initialize privsep.");
+
 		if (drop_root_privileges(username) < 0)
 			exit(1);
 	}
@@ -271,6 +271,7 @@ main(int argc, char *argv[])
 	
 	close(fd);
 
+	config_interface();
 	kickoff_adverts();
 
 	/* enter loop */
diff --git a/radvd.h b/radvd.h
index 46f7a3b..b8596cc 100644
--- a/radvd.h
+++ b/radvd.h
@@ -222,5 +222,15 @@ void mdelay(double);
 double rand_between(double, double);
 void print_addr(struct in6_addr *, char *);
 int check_rdnss_presence(struct AdvRDNSS *, struct in6_addr *);
+ssize_t readn(int fd, void *buf, size_t count);
+ssize_t writen(int fd, const void *buf, size_t count);
+
+/* privsep.c */
+int privsep_init(void);
+int privsep_enabled(void);
+int privsep_interface_linkmtu(const char *iface, uint32_t mtu);
+int privsep_interface_curhlim(const char *iface, uint32_t hlim);
+int privsep_interface_reachtime(const char *iface, uint32_t rtime);
+int privsep_interface_retranstimer(const char *iface, uint32_t rettimer);
 
 #endif
diff --git a/util.c b/util.c
index 0ea15e7..e3ac12f 100644
--- a/util.c
+++ b/util.c
@@ -63,3 +63,45 @@ check_rdnss_presence(struct AdvRDNSS *rdnss, struct in6_addr *addr)
 	}
 	return (rdnss != NULL);
 }
+
+/* Like read(), but retries in case of partial read */
+ssize_t
+readn(int fd, void *buf, size_t count)
+{
+	size_t n = 0;
+	while (count > 0) {
+		int r = read(fd, buf, count);
+		if (r < 0) {
+			if (errno == EINTR)
+				continue;
+			return r;
+		}
+		if (r == 0)
+			return n;
+		buf = (char *)buf + r;
+		count -= r;
+		n += r;
+	}
+	return n;
+}
+
+/* Like write(), but retries in case of partial write */
+ssize_t
+writen(int fd, const void *buf, size_t count)
+{
+	size_t n = 0;
+	while (count > 0) {
+		int r = write(fd, buf, count);
+		if (r < 0) {
+			if (errno == EINTR)
+				continue;
+			return r;
+		}
+		if (r == 0)
+			return n;
+		buf = (char *)buf + r;
+		count -= r;
+		n += r;
+	}
+	return n;
+}
-- 
1.5.3.4




More information about the radvd-devel-l mailing list