diff -up ./config.h.in.CVE-2019-19234 ./config.h.in --- ./config.h.in.CVE-2019-19234 2019-10-28 13:28:52.000000000 +0100 +++ ./config.h.in 2020-01-14 15:53:40.506988064 +0100 @@ -334,6 +334,9 @@ /* Define to 1 if you have the `getuserattr' function. */ #undef HAVE_GETUSERATTR +/* Define to 1 if you have the `getusershell' function. */ +#undef HAVE_GETUSERSHELL + /* Define to 1 if you have the `getutid' function. */ #undef HAVE_GETUTID diff -up ./configure.ac.CVE-2019-19234 ./configure.ac --- ./configure.ac.CVE-2019-19234 2020-01-14 15:53:40.496987995 +0100 +++ ./configure.ac 2020-01-14 15:53:40.509988084 +0100 @@ -2562,6 +2562,10 @@ AC_CHECK_FUNCS([getdelim], [], [ SUDO_APPEND_COMPAT_EXP(sudo_getdelim) COMPAT_TEST_PROGS="${COMPAT_TEST_PROGS}${COMPAT_TEST_PROGS+ }getdelim_test" ]) +AC_CHECK_FUNCS([getusershell], [], [ + AC_LIBOBJ(getusershell) + SUDO_APPEND_COMPAT_EXP(sudo_getusershell) +]) AC_CHECK_FUNCS([reallocarray], [], [ AC_LIBOBJ(reallocarray) SUDO_APPEND_COMPAT_EXP(sudo_reallocarray) diff -up ./configure.CVE-2019-19234 ./configure --- ./configure.CVE-2019-19234 2019-10-28 13:29:14.000000000 +0100 +++ ./configure 2020-01-14 15:53:40.509988084 +0100 @@ -19395,6 +19395,32 @@ esac fi done +for ac_func in getusershell +do : + ac_fn_c_check_func "$LINENO" "getusershell" "ac_cv_func_getusershell" +if test "x$ac_cv_func_getusershell" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_GETUSERSHELL 1 +_ACEOF + +else + + case " $LIBOBJS " in + *" getusershell.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS getusershell.$ac_objext" + ;; +esac + + + for _sym in sudo_getusershell; do + COMPAT_EXP="${COMPAT_EXP}${_sym} +" + done + + +fi +done + for ac_func in reallocarray do : ac_fn_c_check_func "$LINENO" "reallocarray" "ac_cv_func_reallocarray" diff -up ./doc/sudoers.man.in.CVE-2019-19234 ./doc/sudoers.man.in --- ./doc/sudoers.man.in.CVE-2019-19234 2020-01-14 15:53:40.503988043 +0100 +++ ./doc/sudoers.man.in 2020-01-14 15:53:40.510988091 +0100 @@ -2959,6 +2959,28 @@ Older versions of \fBsudo\fR always allowed matching of unknown user and group IDs. .TP 18n +runas_check_shell +.br +If enabled, +\fBsudo\fR +will only run commands as a user whose shell appears in the +\fI/etc/shells\fR +file, even if the invoking user's +\fRRunas_List\fR +would otherwise permit it. +If no +\fI/etc/shells\fR +file is present, a system-dependent list of built-in default shells is used. +On many operating systems, system users such as +\(lqbin\(rq, +do not have a valid shell and this flag can be used to prevent +commands from being run as those users. +This flag is +\fIoff\fR +by default. +.sp +This setting is only supported by version 1.8.29 or higher. +.TP 18n runaspw If set, \fBsudo\fR diff -up ./doc/sudoers.mdoc.in.CVE-2019-19234 ./doc/sudoers.mdoc.in --- ./doc/sudoers.mdoc.in.CVE-2019-19234 2020-01-14 15:53:40.504988050 +0100 +++ ./doc/sudoers.mdoc.in 2020-01-14 15:53:40.510988091 +0100 @@ -2784,6 +2784,26 @@ This setting is only supported by versio Older versions of .Nm sudo always allowed matching of unknown user and group IDs. +.It runas_check_shell +If enabled, +.Nm sudo +will only run commands as a user whose shell appears in the +.Pa /etc/shells +file, even if the invoking user's +.Li Runas_List +would otherwise permit it. +If no +.Pa /etc/shells +file is present, a system-dependent list of built-in default shells is used. +On many operating systems, system users such as +.Dq bin , +do not have a valid shell and this flag can be used to prevent +commands from being run as those users. +This flag is +.Em off +by default. +.Pp +This setting is only supported by version 1.8.29 or higher. .It runaspw If set, .Nm sudo diff -up ./include/sudo_compat.h.CVE-2019-19234 ./include/sudo_compat.h --- ./include/sudo_compat.h.CVE-2019-19234 2019-10-28 13:28:52.000000000 +0100 +++ ./include/sudo_compat.h 2020-01-14 15:53:40.511988098 +0100 @@ -407,6 +407,17 @@ __dso_public ssize_t sudo_getdelim(char # undef getdelim # define getdelim(_a, _b, _c, _d) sudo_getdelim((_a), (_b), (_c), (_d)) #endif /* HAVE_GETDELIM */ +#ifndef HAVE_GETUSERSHELL +__dso_public char *sudo_getusershell(void); +# undef getusershell +# define getusershell() sudo_getusershell() +__dso_public void sudo_setusershell(void); +# undef setusershell +# define setusershell() sudo_setusershell() +__dso_public void sudo_endusershell(void); +# undef endusershell +# define endusershell() sudo_endusershell() +#endif /* HAVE_GETUSERSHELL */ #ifndef HAVE_UTIMENSAT __dso_public int sudo_utimensat(int fd, const char *file, const struct timespec *times, int flag); # undef utimensat diff -up ./lib/util/getusershell.c.CVE-2019-19234 ./lib/util/getusershell.c --- ./lib/util/getusershell.c.CVE-2019-19234 2020-01-14 15:53:40.511988098 +0100 +++ ./lib/util/getusershell.c 2020-01-14 15:53:40.511988098 +0100 @@ -0,0 +1,138 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2019 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#define DEFAULT_TEXT_DOMAIN "sudo" +#include "sudo_gettext.h" /* must be included before sudo_compat.h */ + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +static char **allowed_shells, **current_shell; +static char *default_shells[] = { + "/bin/sh", + "/bin/ksh", + "/bin/ksh93", + "/bin/bash", + "/bin/dash", + "/bin/zsh", + "/bin/csh", + "/bin/tcsh", + NULL +}; + +static char ** +read_shells(void) +{ + size_t maxshells = 16, nshells = 0; + size_t linesize = 0; + char *line = NULL; + FILE *fp; + debug_decl(read_shells, SUDO_DEBUG_UTIL) + + if ((fp = fopen("/etc/shells", "r")) == NULL) + goto bad; + + free(allowed_shells); + allowed_shells = reallocarray(NULL, maxshells, sizeof(char *)); + if (allowed_shells == NULL) + goto bad; + + while (sudo_parseln(&line, &linesize, NULL, fp, PARSELN_CONT_IGN) != -1) { + if (nshells + 1 >= maxshells) { + char **new_shells; + + new_shells = reallocarray(NULL, maxshells + 16, sizeof(char *)); + if (new_shells == NULL) + goto bad; + allowed_shells = new_shells; + maxshells += 16; + } + if ((allowed_shells[nshells] = strdup(line)) == NULL) + goto bad; + nshells++; + } + allowed_shells[nshells] = NULL; + + free(line); + fclose(fp); + debug_return_ptr(allowed_shells); +bad: + free(line); + if (fp != NULL) + fclose(fp); + while (nshells != 0) + free(allowed_shells[--nshells]); + free(allowed_shells); + allowed_shells = NULL; + debug_return_ptr(default_shells); +} + +void +sudo_setusershell(void) +{ + debug_decl(setusershell, SUDO_DEBUG_UTIL) + + current_shell = read_shells(); + + debug_return; +} + +void +sudo_endusershell(void) +{ + debug_decl(endusershell, SUDO_DEBUG_UTIL) + + if (allowed_shells != NULL) { + char **shell; + + for (shell = allowed_shells; *shell != NULL; shell++) + free(*shell); + free(allowed_shells); + allowed_shells = NULL; + } + current_shell = NULL; + + debug_return; +} + +char * +sudo_getusershell(void) +{ + debug_decl(getusershell, SUDO_DEBUG_UTIL) + + if (current_shell == NULL) + current_shell = read_shells(); + + debug_return_str(*current_shell++); +} diff -up ./lib/util/Makefile.in.CVE-2019-19234 ./lib/util/Makefile.in --- ./lib/util/Makefile.in.CVE-2019-19234 2019-10-28 13:28:53.000000000 +0100 +++ ./lib/util/Makefile.in 2020-01-14 15:53:40.511988098 +0100 @@ -678,6 +678,18 @@ gettime.i: $(srcdir)/gettime.c $(incdir) $(CC) -E -o $@ $(CPPFLAGS) $< gettime.plog: gettime.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/gettime.c --i-file $< --output-file $@ +getusershell.lo: $(srcdir)/getusershell.c $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/getusershell.c +getusershell.i: $(srcdir)/getusershell.c $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_gettext.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +getusershell.plog: getusershell.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/getusershell.c --i-file $< --output-file $@ gidlist.lo: $(srcdir)/gidlist.c $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ diff -up ./MANIFEST.CVE-2019-19234 ./MANIFEST --- ./MANIFEST.CVE-2019-19234 2019-10-28 13:28:52.000000000 +0100 +++ ./MANIFEST 2020-01-14 15:53:40.506988064 +0100 @@ -103,6 +103,7 @@ lib/util/getgrouplist.c lib/util/gethostname.c lib/util/getopt_long.c lib/util/gettime.c +lib/util/getusershell.c lib/util/gidlist.c lib/util/glob.c lib/util/inet_ntop.c diff -up ./mkdep.pl.CVE-2019-19234 ./mkdep.pl --- ./mkdep.pl.CVE-2019-19234 2019-10-28 13:28:52.000000000 +0100 +++ ./mkdep.pl 2020-01-14 15:53:40.511988098 +0100 @@ -116,7 +116,7 @@ sub mkdep { # XXX - fill in AUTH_OBJS from contents of the auth dir instead $makefile =~ s:\@AUTH_OBJS\@:afs.lo aix_auth.lo bsdauth.lo dce.lo fwtk.lo getspwuid.lo kerb5.lo pam.lo passwd.lo rfc1938.lo secureware.lo securid5.lo sia.lo:; $makefile =~ s:\@DIGEST\@:digest.lo digest_openssl.lo digest_gcrypt.lo:; - $makefile =~ s:\@LTLIBOBJS\@:arc4random.lo arc4random_uniform.lo closefrom.lo fnmatch.lo getaddrinfo.lo getcwd.lo getentropy.lo getgrouplist.lo getdelim.lo getopt_long.lo glob.lo inet_ntop_lo inet_pton.lo isblank.lo memrchr.lo memset_s.lo mksiglist.lo mksigname.lo mktemp.lo nanosleep.lo pw_dup.lo reallocarray.lo sha2.lo sig2str.lo siglist.lo signame.lo snprintf.lo str2sig.lo strlcat.lo strlcpy.lo strndup.lo strnlen.lo strsignal.lo utimens.lo vsyslog.lo pipe2.lo:; + $makefile =~ s:\@LTLIBOBJS\@:arc4random.lo arc4random_uniform.lo closefrom.lo fnmatch.lo getaddrinfo.lo getcwd.lo getentropy.lo getgrouplist.lo getdelim.lo getopt_long.lo getusershell.lo glob.lo inet_ntop_lo inet_pton.lo isblank.lo memrchr.lo memset_s.lo mksiglist.lo mksigname.lo mktemp.lo nanosleep.lo pw_dup.lo reallocarray.lo sha2.lo sig2str.lo siglist.lo signame.lo snprintf.lo str2sig.lo strlcat.lo strlcpy.lo strndup.lo strnlen.lo strsignal.lo utimens.lo vsyslog.lo pipe2.lo:; # Parse OBJS lines my %objs; diff -up ./plugins/sudoers/check.c.CVE-2019-19234 ./plugins/sudoers/check.c --- ./plugins/sudoers/check.c.CVE-2019-19234 2019-10-28 13:27:45.000000000 +0100 +++ ./plugins/sudoers/check.c 2020-01-14 15:53:40.511988098 +0100 @@ -333,3 +333,28 @@ get_authpw(int mode) debug_return_ptr(pw); } + +/* + * Returns true if the specified shell is allowed by /etc/shells, else false. + */ +bool +check_user_shell(const struct passwd *pw) +{ + const char *shell; + debug_decl(check_user_shell, SUDOERS_DEBUG_AUTH) + + if (!def_runas_check_shell) + debug_return_bool(true); + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: checking /etc/shells for %s", __func__, pw->pw_shell); + + setusershell(); + while ((shell = getusershell()) != NULL) { + if (strcmp(shell, pw->pw_shell) == 0) + debug_return_bool(true); + } + endusershell(); + + debug_return_bool(false); +} diff -up ./plugins/sudoers/def_data.c.CVE-2019-19234 ./plugins/sudoers/def_data.c --- ./plugins/sudoers/def_data.c.CVE-2019-19234 2020-01-14 15:53:40.504988050 +0100 +++ ./plugins/sudoers/def_data.c 2020-01-14 15:53:40.511988098 +0100 @@ -518,6 +518,10 @@ struct sudo_defs_types sudo_defs_table[] N_("Allow the use of unknown runas user and/or group ID"), NULL, }, { + "runas_check_shell", T_FLAG, + N_("Only permit running commands as a user with a valid shell"), + NULL, + }, { NULL, 0, NULL } }; diff -up ./plugins/sudoers/def_data.h.CVE-2019-19234 ./plugins/sudoers/def_data.h --- ./plugins/sudoers/def_data.h.CVE-2019-19234 2020-01-14 15:53:40.512988105 +0100 +++ ./plugins/sudoers/def_data.h 2020-01-14 15:58:06.927808982 +0100 @@ -238,6 +238,8 @@ #define def_cmnd_no_wait (sudo_defs_table[I_CMND_NO_WAIT].sd_un.flag) #define I_RUNAS_ALLOW_UNKNOWN_ID 119 #define def_runas_allow_unknown_id (sudo_defs_table[I_RUNAS_ALLOW_UNKNOWN_ID].sd_un.flag) +#define I_RUNAS_CHECK_SHELL 120 +#define def_runas_check_shell (sudo_defs_table[I_RUNAS_CHECK_SHELL].sd_un.flag) enum def_tuple { never, diff -up ./plugins/sudoers/def_data.in.CVE-2019-19234 ./plugins/sudoers/def_data.in --- ./plugins/sudoers/def_data.in.CVE-2019-19234 2020-01-14 15:53:40.505988057 +0100 +++ ./plugins/sudoers/def_data.in 2020-01-14 15:53:40.512988105 +0100 @@ -375,3 +375,7 @@ cmnd_no_wait runas_allow_unknown_id T_FLAG "Allow the use of unknown runas user and/or group ID" +runas_check_shell + T_FLAG + "Only permit running commands as a user with a valid shell" + diff -up ./plugins/sudoers/sudoers.c.CVE-2019-19234 ./plugins/sudoers/sudoers.c --- ./plugins/sudoers/sudoers.c.CVE-2019-19234 2020-01-14 15:53:40.505988057 +0100 +++ ./plugins/sudoers/sudoers.c 2020-01-14 15:53:40.512988105 +0100 @@ -273,7 +273,7 @@ sudoers_policy_main(int argc, char * con /* Not an audit event. */ sudo_warnx(U_("sudoers specifies that root is not allowed to sudo")); goto bad; - } + } if (!set_perms(PERM_INITIAL)) goto bad; @@ -412,6 +412,13 @@ sudoers_policy_main(int argc, char * con goto bad; } + /* Check runas user's shell. */ + if (!check_user_shell(runas_pw)) { + log_warningx(SLOG_RAW_MSG, N_("invalid shell for user %s: %s"), + runas_pw->pw_name, runas_pw->pw_shell); + goto bad; + } + /* * We don't reset the environment for sudoedit or if the user * specified the -E command line flag and they have setenv privs. diff -up ./plugins/sudoers/sudoers.h.CVE-2019-19234 ./plugins/sudoers/sudoers.h --- ./plugins/sudoers/sudoers.h.CVE-2019-19234 2020-01-14 15:53:40.502988036 +0100 +++ ./plugins/sudoers/sudoers.h 2020-01-14 15:53:40.512988105 +0100 @@ -264,6 +264,7 @@ int find_path(const char *infile, char * /* check.c */ int check_user(int validate, int mode); +bool check_user_shell(const struct passwd *pw); bool user_is_exempt(void); /* prompt.c */