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!
From 59fb63fd4e3fcc8d49f493d5900831350a1aa924 Mon Sep 17 00:00:00 2001 From: Alex Wilson <alex.wilson@xxxxxxxxxx> 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)
_______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev