[PATCH] Drop fine-grained privileges on Illumos/Solaris

Alex Wilson alex at cooperi.net
Fri Nov 13 12:00:48 AEDT 2015


Hi,

I'm not sure how interested anybody here is in this, but I've been
working lately on getting rid of the horror that is SunSSH for some
distros of Illumos (mostly SmartOS). One of the patches we're carrying
around at the moment is one that simply drops fine-grained privileges in
sshd, ssh-agent and sftp-server. Since the privilege dropping here is
roughly equivalent to a more verbose, coarser version of a tame() call,
I was wondering if there might be any interest in taking it into
openssh-portable in future.

Patch is attached. I've made sure all the code is behind
#ifdef USE_SOLARIS_PRIVS and added some code in configure.ac to turn
this macro on and off.

It also has a related fix which turns off the UID restoration test when
building --with-solaris-privs (since the fine-grained privs model lets
you create an ordinary user who can setuid to root, and sshd should
still let such a user log in if they're allowed to by the system).

Any feedback or comments would be appreciated, of course, even if this
isn't suitable for integration into -portable.

Thanks!
-------------- next part --------------
From 59fb63fd4e3fcc8d49f493d5900831350a1aa924 Mon Sep 17 00:00:00 2001
From: Alex Wilson <alex.wilson at joyent.com>
Date: Tue, 4 Aug 2015 15:50:09 -0700
Subject: [PATCH] Support for fine-grained Illumos/Solaris privileges

---
 configure.ac  | 14 ++++++++++++++
 sftp-server.c | 33 +++++++++++++++++++++++++++++++++
 ssh-agent.c   | 37 +++++++++++++++++++++++++++++++++++++
 sshd.c        | 33 +++++++++++++++++++++++++++++++++
 uidswap.c     | 18 ++++++++++++------
 5 files changed, 129 insertions(+), 6 deletions(-)

diff --git a/configure.ac b/configure.ac
index 1527a13..66493dc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -575,6 +575,8 @@ case "$host" in
 	LIBS="$LIBS /usr/lib/textreadmode.o"
 	AC_DEFINE([HAVE_CYGWIN], [1], [Define if you are on Cygwin])
 	AC_DEFINE([USE_PIPES], [1], [Use PIPES instead of a socketpair()])
+	AC_DEFINE([NO_UID_RESTORATION_TEST], [1],
+		[Define to disable UID restoration test])
 	AC_DEFINE([DISABLE_SHADOW], [1],
 		[Define if you want to disable shadow passwords])
 	AC_DEFINE([NO_X11_UNIX_SOCKETS], [1],
@@ -910,6 +912,18 @@ mips-sony-bsd|mips-sony-newsos4)
 			SP_MSG="yes" ], )
 		],
 	)
+	AC_ARG_WITH([solaris-privs],
+		[  --with-solaris-privs    Enable Solaris/Illumos privileges (experimental)],
+		[
+		AC_CHECK_FUNC([setppriv],
+			[ AC_CHECK_HEADERS([priv.h])
+			  AC_DEFINE([NO_UID_RESTORATION_TEST], [1],
+				[Define to disable UID restoration test])
+			  AC_DEFINE([USE_SOLARIS_PRIVS], [1],
+				[Define if you have Solaris privileges])
+			SP_MSG="yes" ], )
+		],
+	)
 	TEST_SHELL=$SHELL	# let configure find us a capable shell
 	;;
 *-*-sunos4*)
diff --git a/sftp-server.c b/sftp-server.c
index eac11d7..db950c3 100644
--- a/sftp-server.c
+++ b/sftp-server.c
@@ -32,6 +32,9 @@
 #ifdef HAVE_SYS_PRCTL_H
 #include <sys/prctl.h>
 #endif
+#ifdef HAVE_PRIV_H
+# include <priv.h>
+#endif
 
 #include <dirent.h>
 #include <errno.h>
@@ -1509,6 +1512,9 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
 	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
 	char *cp, *homedir = NULL, buf[4*4096];
 	long mask;
+#ifdef USE_SOLARIS_PRIVS
+	priv_set_t *pset = NULL;
+#endif
 
 	extern char *optarg;
 	extern char *__progname;
@@ -1598,6 +1604,33 @@ sftp_server_main(int argc, char **argv, struct passwd *user_pw)
 		fatal("unable to make the process undumpable");
 #endif /* defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) */
 
+#ifdef USE_SOLARIS_PRIVS
+	/*
+	 * Drop Solaris/Illumos privileges. We don't need the ability to open
+	 * new network sockets, the ability to fork or exec, or the ability to
+	 * list/access other processes past this point.
+	 */
+	if ((pset = priv_allocset()) == NULL)
+		fatal("priv_allocset failed");
+	/* Start with "basic" and drop everything we don't need. */
+	priv_basicset(pset);
+	priv_delset(pset, PRIV_FILE_LINK_ANY);
+	priv_delset(pset, PRIV_PROC_INFO);
+	priv_delset(pset, PRIV_PROC_SESSION);
+	priv_delset(pset, PRIV_PROC_FORK);
+	priv_delset(pset, PRIV_NET_ACCESS);
+	priv_delset(pset, PRIV_PROC_EXEC);
+
+	if (setppriv(PRIV_SET, PRIV_PERMITTED, pset))
+		fatal("setppriv failed: %s", strerror(errno));
+	if (setppriv(PRIV_SET, PRIV_LIMIT, pset))
+		fatal("setppriv failed: %s", strerror(errno));
+	if (setppriv(PRIV_SET, PRIV_INHERITABLE, pset))
+		fatal("setppriv failed: %s", strerror(errno));
+
+	priv_freeset(pset);
+#endif
+
 	if ((cp = getenv("SSH_CONNECTION")) != NULL) {
 		client_addr = xstrdup(cp);
 		if ((cp = strchr(client_addr, ' ')) == NULL) {
diff --git a/ssh-agent.c b/ssh-agent.c
index a335ea3..23c6822 100644
--- a/ssh-agent.c
+++ b/ssh-agent.c
@@ -67,6 +67,9 @@
 #include <stdlib.h>
 #include <time.h>
 #include <string.h>
+#ifdef HAVE_PRIV_H
+# include <priv.h>
+#endif
 #include <unistd.h>
 #ifdef HAVE_UTIL_H
 # include <util.h>
@@ -1187,6 +1190,9 @@ main(int ac, char **av)
 	struct timeval *tvp = NULL;
 	size_t len;
 	mode_t prev_mask;
+#ifdef USE_SOLARIS_PRIVS
+	priv_set_t *pset = NULL;
+#endif
 
 	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
 	sanitise_stdfd();
@@ -1361,6 +1367,37 @@ main(int ac, char **av)
 	/* child */
 	log_init(__progname, SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 0);
 
+#ifdef USE_SOLARIS_PRIVS
+	/*
+	 * Drop unneeded privs, including basic ones like fork/exec.
+	 */
+	if ((pset = priv_allocset()) == NULL) {
+		error("priv_allocset: %s", strerror(errno));
+		cleanup_exit(1);
+	}
+	/* Start with "basic" and drop everything we don't need. */
+	priv_basicset(pset);
+	priv_delset(pset, PRIV_PROC_EXEC);
+	priv_delset(pset, PRIV_PROC_FORK);
+	priv_delset(pset, PRIV_FILE_LINK_ANY);
+	priv_delset(pset, PRIV_PROC_INFO);
+	priv_delset(pset, PRIV_PROC_SESSION);
+
+	if (setppriv(PRIV_SET, PRIV_PERMITTED, pset)) {
+		error("setppriv: %s", strerror(errno));
+		cleanup_exit(1);
+	}
+	if (setppriv(PRIV_SET, PRIV_LIMIT, pset)) {
+		error("setppriv: %s", strerror(errno));
+		cleanup_exit(1);
+	}
+	if (setppriv(PRIV_SET, PRIV_INHERITABLE, pset)) {
+		error("setppriv: %s", strerror(errno));
+		cleanup_exit(1);
+	}
+	priv_freeset(pset);
+#endif
+
 	if (setsid() == -1) {
 		error("setsid: %s", strerror(errno));
 		cleanup_exit(1);
diff --git a/sshd.c b/sshd.c
index d868089..9fb45a3 100644
--- a/sshd.c
+++ b/sshd.c
@@ -72,6 +72,9 @@
 #include <string.h>
 #include <unistd.h>
 #include <limits.h>
+#ifdef HAVE_PRIV_H
+# include <priv.h>
+#endif
 
 #ifdef WITH_OPENSSL
 #include <openssl/dh.h>
@@ -610,6 +613,9 @@ privsep_preauth_child(void)
 {
 	u_int32_t rnd[256];
 	gid_t gidset[1];
+#ifdef USE_SOLARIS_PRIVS
+	priv_set_t *pset = NULL;
+#endif
 
 	/* Enable challenge-response authentication for privilege separation */
 	privsep_challenge_enable();
@@ -649,6 +655,33 @@ privsep_preauth_child(void)
 		fatal("setgroups: %.100s", strerror(errno));
 	permanently_set_uid(privsep_pw);
 #endif
+#ifdef USE_SOLARIS_PRIVS
+	/* Drop Solaris/Illumos privileges. */
+	if ((pset = priv_allocset()) == NULL)
+		fatal("priv_allocset failed");
+	/* Start with "basic" and drop everything we don't need. */
+	priv_basicset(pset);
+	priv_delset(pset, PRIV_FILE_LINK_ANY);
+	priv_delset(pset, PRIV_PROC_INFO);
+	priv_delset(pset, PRIV_PROC_SESSION);
+	priv_delset(pset, PRIV_PROC_FORK);
+	priv_delset(pset, PRIV_NET_ACCESS);
+	priv_delset(pset, PRIV_PROC_EXEC);
+	/* These may not be available on older Solaris-es */
+# if defined(PRIV_FILE_READ) && defined(PRIV_FILE_WRITE)
+	priv_delset(pset, PRIV_FILE_READ);
+	priv_delset(pset, PRIV_FILE_WRITE);
+# endif
+
+	if (setppriv(PRIV_SET, PRIV_PERMITTED, pset))
+		fatal("setppriv failed: %s", strerror(errno));
+	if (setppriv(PRIV_SET, PRIV_LIMIT, pset))
+		fatal("setppriv failed: %s", strerror(errno));
+	if (setppriv(PRIV_SET, PRIV_INHERITABLE, pset))
+		fatal("setppriv failed: %s", strerror(errno));
+
+	priv_freeset(pset);
+#endif
 }
 
 static int
diff --git a/uidswap.c b/uidswap.c
index 0702e1d..8bf6b24 100644
--- a/uidswap.c
+++ b/uidswap.c
@@ -134,7 +134,7 @@ temporarily_use_uid(struct passwd *pw)
 void
 permanently_drop_suid(uid_t uid)
 {
-#ifndef HAVE_CYGWIN
+#ifndef NO_UID_RESTORATION_TEST
 	uid_t old_uid = getuid();
 #endif
 
@@ -142,8 +142,14 @@ permanently_drop_suid(uid_t uid)
 	if (setresuid(uid, uid, uid) < 0)
 		fatal("setresuid %u: %.100s", (u_int)uid, strerror(errno));
 
-#ifndef HAVE_CYGWIN
-	/* Try restoration of UID if changed (test clearing of saved uid) */
+#ifndef NO_UID_RESTORATION_TEST
+	/*
+	 * Try restoration of UID if changed (test clearing of saved uid).
+	 *
+	 * Note that we don't do this on Cygwin, or on Solaris-based platforms
+	 * where fine-grained privileges are available (the user might be
+	 * deliberately allowed the right to setuid back to root).
+	 */
 	if (old_uid != uid &&
 	    (setuid(old_uid) != -1 || seteuid(old_uid) != -1))
 		fatal("%s: was able to restore old [e]uid", __func__);
@@ -199,7 +205,7 @@ restore_uid(void)
 void
 permanently_set_uid(struct passwd *pw)
 {
-#ifndef HAVE_CYGWIN
+#ifndef NO_UID_RESTORATION_TEST
 	uid_t old_uid = getuid();
 	gid_t old_gid = getgid();
 #endif
@@ -227,7 +233,7 @@ permanently_set_uid(struct passwd *pw)
 	if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) < 0)
 		fatal("setresuid %u: %.100s", (u_int)pw->pw_uid, strerror(errno));
 
-#ifndef HAVE_CYGWIN
+#ifndef NO_UID_RESTORATION_TEST
 	/* Try restoration of GID if changed (test clearing of saved gid) */
 	if (old_gid != pw->pw_gid && pw->pw_uid != 0 &&
 	    (setgid(old_gid) != -1 || setegid(old_gid) != -1))
@@ -241,7 +247,7 @@ permanently_set_uid(struct passwd *pw)
 		    (u_int)pw->pw_gid);
 	}
 
-#ifndef HAVE_CYGWIN
+#ifndef NO_UID_RESTORATION_TEST
 	/* Try restoration of UID if changed (test clearing of saved uid) */
 	if (old_uid != pw->pw_uid &&
 	    (setuid(old_uid) != -1 || seteuid(old_uid) != -1))
-- 
2.4.9 (Apple Git-60)



More information about the openssh-unix-dev mailing list