sudo/Fix-CVE-2019-19234-add-runas_check_shell-flag.patch
2020-01-20 17:29:30 +08:00

486 lines
15 KiB
Diff

From 1078336fb8769eb9e022d088f5b2db443bb775d6 Mon Sep 17 00:00:00 2001
From: Todd C. Miller <Todd.Miller@sudo.ws>
Date: Mon, 09 Dec 2019 19:29:45 -0700
Subject: [PATCH] Fix CVE-2019-19234
Add runas_check_shell flag to require a runas user to have a valid shell.
Not enabled by default.
---
MANIFEST | 1 +
config.h.in | 3 +
configure | 26 +++++++++
configure.ac | 6 +-
doc/sudoers.man.in | 22 +++++++
doc/sudoers.mdoc.in | 20 +++++++
include/sudo_compat.h | 11 ++++
lib/util/Makefile.in | 12 ++++
lib/util/getusershell.c | 138 ++++++++++++++++++++++++++++++++++++++++++++
mkdep.pl | 2 +-
plugins/sudoers/check.c | 25 ++++++++
plugins/sudoers/def_data.c | 4 ++
plugins/sudoers/def_data.h | 2 +
plugins/sudoers/def_data.in | 3 +
plugins/sudoers/sudoers.c | 7 +++
plugins/sudoers/sudoers.h | 1 +
16 files changed, 281 insertions(+), 2 deletions(-)
create mode 100644 lib/util/getusershell.c
diff --git a/MANIFEST b/MANIFEST
index 08d7f25..d883174 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -111,6 +111,7 @@ lib/util/gethostname.c
lib/util/getline.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 --git a/config.h.in b/config.h.in
index 9b83b12..c65a6f7 100644
--- a/config.h.in
+++ b/config.h.in
@@ -337,6 +337,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 --git a/configure b/configure
index e1b5dbb..658768e 100755
--- a/configure
+++ b/configure
@@ -19418,6 +19418,32 @@ done
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 --git a/configure.ac b/configure.ac
index 962a032..c6d6c55 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
dnl
dnl Use the top-level autogen.sh script to generate configure and config.h.in
dnl
-dnl Copyright (c) 1994-1996,1998-2018 Todd C. Miller <Todd.Miller@sudo.ws>
+dnl Copyright (c) 1994-1996,1998-2019 Todd C. Miller <Todd.Miller@sudo.ws>
dnl
AC_PREREQ([2.59])
AC_INIT([sudo], [1.8.27], [https://bugzilla.sudo.ws/], [sudo])
@@ -2839,6 +2839,10 @@ AC_CHECK_FUNCS([vsyslog], [], [
SUDO_APPEND_COMPAT_EXP(sudo_vsyslog)
COMPAT_TEST_PROGS="${COMPAT_TEST_PROGS}${COMPAT_TEST_PROGS+ }vsyslog_test"
])
+AC_CHECK_FUNCS([getusershell], [], [
+ AC_LIBOBJ(getusershell)
+ SUDO_APPEND_COMPAT_EXP(sudo_getusershell)
+])
dnl
dnl 4.4BSD-based systems can force the password or group file to be held open
dnl
diff --git a/doc/sudoers.man.in b/doc/sudoers.man.in
index 45a600f..8a97a55 100644
--- a/doc/sudoers.man.in
+++ b/doc/sudoers.man.in
@@ -2886,6 +2886,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.30 or higher.
+.TP 18n
runaspw
If set,
\fBsudo\fR
diff --git a/doc/sudoers.mdoc.in b/doc/sudoers.mdoc.in
index 6a511d9..ec7ed4f 100644
--- a/doc/sudoers.mdoc.in
+++ b/doc/sudoers.mdoc.in
@@ -2714,6 +2714,26 @@ This setting is only supported by version 1.8.30 or higher.
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.30 or higher.
.It runaspw
If set,
.Nm sudo
diff --git a/include/sudo_compat.h b/include/sudo_compat.h
index 5c32840..3faa167 100644
--- a/include/sudo_compat.h
+++ b/include/sudo_compat.h
@@ -409,6 +409,17 @@ __dso_public ssize_t sudo_getline(char **bufp, size_t *bufsizep, FILE *fp);
# undef getline
# define getline(_a, _b, _c) sudo_getline((_a), (_b), (_c))
#endif /* HAVE_GETLINE */
+#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 --git a/lib/util/Makefile.in b/lib/util/Makefile.in
index cadfcf2..aa21ac9 100644
--- a/lib/util/Makefile.in
+++ b/lib/util/Makefile.in
@@ -630,6 +630,18 @@ gettime.i: $(srcdir)/gettime.c $(incdir)/compat/stdbool.h \
$(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 --git a/lib/util/getusershell.c b/lib/util/getusershell.c
new file mode 100644
index 0000000..6aeae3b
--- /dev/null
+++ b/lib/util/getusershell.c
@@ -0,0 +1,138 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2019 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 <config.h>
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#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 --git a/mkdep.pl b/mkdep.pl
index 2040320..02b5f41 100755
--- a/mkdep.pl
+++ b/mkdep.pl
@@ -112,7 +112,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 getline.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 strlcat.lo strlcpy.lo strndup.lo strnlen.lo strsignal.lo strtonum.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 getline.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 strlcat.lo strlcpy.lo strndup.lo strnlen.lo strsignal.lo strtonum.lo utimens.lo vsyslog.lo pipe2.lo:;
# Parse OBJS lines
my %objs;
diff --git a/plugins/sudoers/check.c b/plugins/sudoers/check.c
index 92f859c..31d2615 100644
--- a/plugins/sudoers/check.c
+++ b/plugins/sudoers/check.c
@@ -331,3 +331,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 --git a/plugins/sudoers/def_data.c b/plugins/sudoers/def_data.c
index 97eaf79..7b111f8 100644
--- a/plugins/sudoers/def_data.c
+++ b/plugins/sudoers/def_data.c
@@ -498,6 +498,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 --git a/plugins/sudoers/def_data.h b/plugins/sudoers/def_data.h
index 55878c9..50725e6 100644
--- a/plugins/sudoers/def_data.h
+++ b/plugins/sudoers/def_data.h
@@ -228,6 +228,8 @@
#define def_case_insensitive_group (sudo_defs_table[I_CASE_INSENSITIVE_GROUP].sd_un.flag)
#define I_RUNAS_ALLOW_UNKNOWN_ID 114
#define def_runas_allow_unknown_id (sudo_defs_table[I_RUNAS_ALLOW_UNKNOWN_ID].sd_un.flag)
+#define I_RUNAS_CHECK_SHELL 115
+#define def_runas_check_shell (sudo_defs_table[I_RUNAS_CHECK_SHELL].sd_un.flag)
enum def_tuple {
never,
diff --git a/plugins/sudoers/def_data.in b/plugins/sudoers/def_data.in
index cf0eead..bb81d8b 100644
--- a/plugins/sudoers/def_data.in
+++ b/plugins/sudoers/def_data.in
@@ -360,3 +360,6 @@ case_insensitive_group
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 --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c
index b393d70..a791942 100644
--- a/plugins/sudoers/sudoers.c
+++ b/plugins/sudoers/sudoers.c
@@ -390,6 +390,13 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
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 --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h
index 28dbbb3..d33c4ef 100644
--- a/plugins/sudoers/sudoers.h
+++ b/plugins/sudoers/sudoers.h
@@ -250,6 +250,7 @@ int find_path(const char *infile, char **outfile, struct stat *sbp,
/* check.c */
int check_user(int validate, int mode);
+bool check_user_shell(const struct passwd *pw);
bool user_is_exempt(void);
/* prompt.c */
--
1.8.3.1