diff --git a/INSTALL b/INSTALL index e53b25c3d..7eb516ca3 100644 --- a/INSTALL +++ b/INSTALL @@ -257,8 +257,8 @@ Compilation options: policy and I/O logging plugins. If the --disable-shared option is specified, this support is disabled and the default sudoers policy and I/O plugins are embedded in the sudo - binary itself. This will also disable the noexec option - as it too relies on dynamic shared object support. + binary itself. This will also disable the intercept and noexec + options as they also rely on dynamic shared object support. --disable-shared-libutil Disable the use of the dynamic libsudo_util library. By @@ -274,8 +274,8 @@ Compilation options: dynamic shared object. When the --enable-static-sudoers option is specified, the sudoers plugin is compiled directly into the sudo binary. Unlike --disable-shared, this does - not prevent other plugins from being used and the noexec - option will continue to function. + not prevent other plugins from being used and the intercept + and noexec options will continue to function. --enable-tmpfiles.d=DIR Set the directory to be used when installing the sudo @@ -381,16 +381,30 @@ Optional features: not work, which may be the case on some SysV-based OS's using STREAMS. + --enable-intercept[=PATH] + Enable support for the "intercept" functionality which allows + sudo to perform a policy check when a dynamically-linked + program run by sudo attempts to execute another program. + For example, this means that for a shell run through sudo, + the individual commands run by the shell are also subject + to rules in the sudoers file. Please see the "Preventing + Shell Escapes" section in the sudoers man page for details. + If specified, PATH should be a fully qualified path name, + e.g. /usr/local/libexec/sudo/sudo_noexec.so. If PATH is + "no", intercept support will not be compiled in. The default + is to compile intercept support if libtool supports building + shared objects on your system. + --with-noexec[=PATH] Enable support for the "noexec" functionality which prevents a dynamically-linked program being run by sudo from executing another program (think shell escapes). Please see the - "PREVENTING SHELL ESCAPES" section in the sudoers man page + "Preventing Shell Escapes" section in the sudoers man page for details. If specified, PATH should be a fully qualified path name, e.g. /usr/local/libexec/sudo/sudo_noexec.so. If PATH is "no", noexec support will not be compiled in. The default is to compile noexec support if libtool supports building - shared objects on your OS. + shared objects on your system. --with-selinux Enable support for role based access control (RBAC) on diff --git a/configure b/configure index 1f2dfec13..ecc08eb8a 100755 --- a/configure +++ b/configure @@ -808,14 +808,19 @@ LDAP SELINUX_USAGE BSDAUTH_USAGE DONT_LEAK_PATH_INFO -NOEXEC_MODULE +PRELOAD_MODULE CHECK_NOEXEC +CHECK_INTERCEPT INSTALL_NOEXEC +INSTALL_INTERCEPT INSTALL_BACKUP sesh_file noexec_file NOEXECDIR NOEXECFILE +intercept_file +INTERCEPTDIR +INTERCEPTFILE mansectform mansectsu devdir @@ -1041,6 +1046,7 @@ with_gnu_ld with_sysroot enable_libtool_lock with_libtool +enable_intercept with_noexec with_netsvc enable_sia @@ -1751,6 +1757,7 @@ Optional Features: --enable-fast-install[=PKGS] optimize for fast installation [default=yes] --disable-libtool-lock avoid locking (might break parallel builds) + --enable-intercept fully qualified pathname of sudo_intercept.so --disable-sia Disable SIA on Digital UNIX --disable-largefile omit support for large files --disable-pam-session Disable PAM session support @@ -3534,6 +3541,11 @@ ac_config_headers="$ac_config_headers config.h pathnames.h" + + + + + @@ -3580,6 +3592,7 @@ path_info=on ldap_conf=/etc/ldap.conf ldap_secret=/etc/ldap.secret netsvc_conf=/etc/netsvc.conf +intercept_file="$libexecdir/sudo/sudo_intercept.so" noexec_file="$libexecdir/sudo/sudo_noexec.so" sesh_file="$libexecdir/sudo/sesh" nsswitch_conf=/etc/nsswitch.conf @@ -3593,9 +3606,11 @@ devsearch="/dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev" # End initial values for man page substitution # INSTALL_BACKUP= +INSTALL_INTERCEPT= INSTALL_NOEXEC= +CHECK_INTERCEPT= CHECK_NOEXEC= -NOEXEC_MODULE=-module +PRELOAD_MODULE=-module exampledir='$(docdir)/examples' devdir='$(srcdir)' PROGS="sudo" @@ -15534,6 +15549,7 @@ fi if test "$enable_shared" = "no"; then + enable_intercept=no with_noexec=no enable_dlopen=no lt_cv_dlopen=none @@ -15544,6 +15560,23 @@ fi LIBDL="$lt_cv_dlopen_libs" SHLIB_ENABLE="$enable_dlopen" +# Check whether --enable-intercept was given. +if test ${enable_intercept+y} +then : + enableval=$enable_intercept; case "$enableval" in + yes) ;; + no) ;; + *) intercept_file="$enableval" + ;; + esac + +else $as_nop + enable_intercept="$intercept_file" +fi + +INTERCEPTFILE="sudo_intercept.so" +INTERCEPTDIR="`echo $intercept_file|sed -e 's:^${\([^}]*\)}:$(\1):' -e 's:^\(.*\)/[^/]*:\1:'`" + # Check whether --with-noexec was given. if test ${with_noexec+y} @@ -16609,7 +16642,7 @@ done # Build sudo_noexec.so as a shared library, not a module. # On Darwin, modules and shared libraries are incompatible. - NOEXEC_MODULE= + PRELOAD_MODULE= # Mach monotonic timer that runs while sleeping ac_fn_c_check_func "$LINENO" "mach_continuous_time" "ac_cv_func_mach_continuous_time" @@ -16787,7 +16820,13 @@ if test X"$enable_pvs_studio" = X"yes"; then EOF fi -if test X"$with_noexec" != X"no"; then +if test X"$enable_intercept" = X"no"; then + intercept_file=disabled +fi +if test X"$with_noexec" = X"no"; then + noexec_file=disabled +fi +if test X"${intercept_file} ${noexec_file}" != X"disabled disabled"; then cat >>confdefs.h <>confdefs.h <>confdefs.h < + * Copyright (c) 2011-2017, 2019-2021 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 @@ -65,6 +65,7 @@ sudo_dso_public int sudo_conf_read_v1(const char *conf_file, int conf_types); /* Accessor functions. */ sudo_dso_public const char *sudo_conf_askpass_path_v1(void); sudo_dso_public const char *sudo_conf_sesh_path_v1(void); +sudo_dso_public const char *sudo_conf_intercept_path_v1(void); sudo_dso_public const char *sudo_conf_noexec_path_v1(void); sudo_dso_public const char *sudo_conf_plugin_dir_path_v1(void); sudo_dso_public const char *sudo_conf_devsearch_path_v1(void); @@ -79,6 +80,7 @@ sudo_dso_public int sudo_conf_max_groups_v1(void); sudo_dso_public void sudo_conf_clear_paths_v1(void); #define sudo_conf_askpass_path() sudo_conf_askpass_path_v1() #define sudo_conf_sesh_path() sudo_conf_sesh_path_v1() +#define sudo_conf_intercept_path() sudo_conf_intercept_path_v1() #define sudo_conf_noexec_path() sudo_conf_noexec_path_v1() #define sudo_conf_plugin_dir_path() sudo_conf_plugin_dir_path_v1() #define sudo_conf_devsearch_path() sudo_conf_devsearch_path_v1() diff --git a/lib/util/sudo_conf.c b/lib/util/sudo_conf.c index a459f0346..c35a45c59 100644 --- a/lib/util/sudo_conf.c +++ b/lib/util/sudo_conf.c @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2009-2017 Todd C. Miller + * Copyright (c) 2009-2021 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 @@ -104,13 +104,15 @@ static struct sudo_conf_table sudo_conf_var_table[] = { /* Indexes into path_table[] below (order is important). */ #define SUDO_CONF_PATH_ASKPASS 0 #define SUDO_CONF_PATH_SESH 1 -#define SUDO_CONF_PATH_NOEXEC 2 -#define SUDO_CONF_PATH_PLUGIN_DIR 3 -#define SUDO_CONF_PATH_DEVSEARCH 4 +#define SUDO_CONF_PATH_INTERCEPT 2 +#define SUDO_CONF_PATH_NOEXEC 3 +#define SUDO_CONF_PATH_PLUGIN_DIR 4 +#define SUDO_CONF_PATH_DEVSEARCH 5 #define SUDO_CONF_PATH_INITIALIZER { \ { "askpass", sizeof("askpass") - 1, false, _PATH_SUDO_ASKPASS }, \ { "sesh", sizeof("sesh") - 1, false, _PATH_SUDO_SESH }, \ + { "intercept", sizeof("intercept") - 1, false, _PATH_SUDO_INTERCEPT }, \ { "noexec", sizeof("noexec") - 1, false, _PATH_SUDO_NOEXEC }, \ { "plugin_dir", sizeof("plugin_dir") - 1, false, _PATH_SUDO_PLUGIN_DIR }, \ { "devsearch", sizeof("devsearch") - 1, false, _PATH_SUDO_DEVSEARCH }, \ @@ -141,7 +143,7 @@ static struct sudo_conf_data { struct sudo_conf_settings settings; struct sudo_conf_debug_list debugging; struct plugin_info_list plugins; - struct sudo_conf_path_table path_table[6]; + struct sudo_conf_path_table path_table[7]; } sudo_conf_data = { SUDO_CONF_SETTINGS_INITIALIZER, TAILQ_HEAD_INITIALIZER(sudo_conf_data.debugging), @@ -480,6 +482,12 @@ sudo_conf_sesh_path_v1(void) return sudo_conf_data.path_table[SUDO_CONF_PATH_SESH].pval; } +const char * +sudo_conf_intercept_path_v1(void) +{ + return sudo_conf_data.path_table[SUDO_CONF_PATH_INTERCEPT].pval; +} + const char * sudo_conf_noexec_path_v1(void) { diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in index 61db6495f..96f037f98 100644 --- a/lib/util/util.exp.in +++ b/lib/util/util.exp.in @@ -9,6 +9,7 @@ sudo_conf_developer_mode_v1 sudo_conf_devsearch_path_v1 sudo_conf_disable_coredump_v1 sudo_conf_group_source_v1 +sudo_conf_intercept_path_v1 sudo_conf_max_groups_v1 sudo_conf_noexec_path_v1 sudo_conf_plugin_dir_path_v1 diff --git a/pathnames.h.in b/pathnames.h.in index 0115961a5..5d86a73c4 100644 --- a/pathnames.h.in +++ b/pathnames.h.in @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 1996, 1998, 1999, 2001, 2004, 2005, 2007-2014 + * Copyright (c) 1996, 1998, 1999, 2001, 2004, 2005, 2007-2021 * Todd C. Miller . * * Permission to use, copy, modify, and distribute this software for any @@ -162,6 +162,10 @@ # undef _PATH_SUDO_SENDMAIL #endif /* _PATH_SUDO_SENDMAIL */ +#ifndef _PATH_SUDO_INTERCEPT +# undef _PATH_SUDO_INTERCEPT +#endif /* _PATH_SUDO_INTERCEPT */ + #ifndef _PATH_SUDO_NOEXEC # undef _PATH_SUDO_NOEXEC #endif /* _PATH_SUDO_NOEXEC */ diff --git a/src/Makefile.in b/src/Makefile.in index 2415c786a..72a156bf2 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,7 +1,7 @@ # # SPDX-License-Identifier: ISC # -# Copyright (c) 2010-2021 +# Copyright (c) 2010-2021 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 @@ -64,8 +64,9 @@ LT_LDFLAGS = @LT_LDFLAGS@ # Flags to pass to libtool LTFLAGS = --tag=disable-static -# Build sudo_noexec as a module instead of a shared lib (except on macOS) -NOEXEC_MODULE = @NOEXEC_MODULE@ +# Flag to build sudo_module.so and sudo_noexec.so as modules instead of +# shared libs (except on macOS) +PRELOAD_MODULE = @PRELOAD_MODULE@ # Address sanitizer flags ASAN_CFLAGS = @ASAN_CFLAGS@ @@ -179,7 +180,7 @@ sudo: $(OBJS) $(LT_LIBS) @STATIC_SUDOERS@ $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(OBJS) $(SUDO_LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) @STATIC_SUDOERS@ sudo_noexec.la: sudo_noexec.lo - $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LDFLAGS) $(LT_LDFLAGS) $(SSP_LDFLAGS) @LIBDL@ -o $@ sudo_noexec.lo $(NOEXEC_MODULE) -avoid-version -rpath $(noexecdir) -shrext .so + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) $(LDFLAGS) $(LT_LDFLAGS) $(SSP_LDFLAGS) @LIBDL@ -o $@ sudo_noexec.lo $(PRELOAD_MODULE) -avoid-version -rpath $(noexecdir) -shrext .so sesh: $(SESH_OBJS) $(LT_LIBS) $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(SESH_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) @@ -243,7 +244,8 @@ install-plugin: install-fuzzer: uninstall: - -$(LIBTOOL) $(LTFLAGS) --mode=uninstall rm -f $(DESTDIR)$(noexecdir)/sudo_noexec.la + -$(LIBTOOL) $(LTFLAGS) --mode=uninstall \ + rm -f $(DESTDIR)$(noexecdir)/sudo_noexec.la -rm -f $(DESTDIR)$(bindir)/sudo \ $(DESTDIR)$(bindir)/sudoedit \ $(DESTDIR)$(libexecdir)/sudo/sesh \ diff --git a/src/exec.c b/src/exec.c index ffee016aa..3acb53132 100644 --- a/src/exec.c +++ b/src/exec.c @@ -248,7 +248,7 @@ exec_cmnd(struct command_details *details, int errfd) #endif { sudo_execve(details->execfd, details->command, details->argv, - details->envp, ISSET(details->flags, CD_NOEXEC)); + details->envp, details->flags); } } sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to exec %s: %s", @@ -317,7 +317,7 @@ sudo_needs_pty(struct command_details *details) { struct plugin_container *plugin; - if (ISSET(details->flags, CD_USE_PTY)) + if (ISSET(details->flags, CD_USE_PTY|CD_INTERCEPT|CD_LOG_CHILDREN)) return true; TAILQ_FOREACH(plugin, &io_plugins, entries) { diff --git a/src/exec_common.c b/src/exec_common.c index 32834de08..bbae00b59 100644 --- a/src/exec_common.c +++ b/src/exec_common.c @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2009-2016 Todd C. Miller + * Copyright (c) 2009-2021 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 @@ -173,20 +173,39 @@ disable_execute(char *envp[], const char *dso) debug_return_ptr(envp); } +/* + * Trap execution of child processes in the command we are about to run. + * Uses LD_PRELOAD and the like to perform a policy check on child commands. + */ +char ** +enable_intercept(char *envp[], const char *dso) +{ + debug_decl(enable_intercept, SUDO_DEBUG_UTIL); + +#ifdef RTLD_PRELOAD_VAR + if (dso != NULL) + envp = preload_dso(envp, dso); +#endif /* RTLD_PRELOAD_VAR */ + + debug_return_ptr(envp); +} + /* * Like execve(2) but falls back to running through /bin/sh * ala execvp(3) if we get ENOEXEC. */ int -sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec) +sudo_execve(int fd, const char *path, char *const argv[], char *envp[], int flags) { debug_decl(sudo_execve, SUDO_DEBUG_UTIL); sudo_debug_execve(SUDO_DEBUG_INFO, path, argv, envp); - /* Modify the environment as needed to disable further execve(). */ - if (noexec) + /* Modify the environment as needed to trap execve(). */ + if (ISSET(flags, CD_NOEXEC)) envp = disable_execute(envp, sudo_conf_noexec_path()); + else if (ISSET(flags, CD_INTERCEPT|CD_LOG_CHILDREN)) + envp = enable_intercept(envp, sudo_conf_intercept_path()); #ifdef HAVE_FEXECVE if (fd != -1) diff --git a/src/selinux.c b/src/selinux.c index c2f50aafb..d7f7ffd0d 100644 --- a/src/selinux.c +++ b/src/selinux.c @@ -502,7 +502,7 @@ selinux_execve(int fd, const char *path, char *const argv[], char *envp[], memcpy(&nargv[nargc], &argv[1], argc * sizeof(char *)); /* copies NULL */ /* sesh will handle noexec for us. */ - sudo_execve(-1, sesh, nargv, envp, false); + sudo_execve(-1, sesh, nargv, envp, 0); serrno = errno; free(nargv); errno = serrno; diff --git a/src/sesh.c b/src/sesh.c index 69756196a..706a3b7ec 100644 --- a/src/sesh.c +++ b/src/sesh.c @@ -80,16 +80,19 @@ main(int argc, char *argv[], char *envp[]) if (strcmp(argv[1], "-e") == 0) { ret = sesh_sudoedit(argc, argv); } else { - bool login_shell, noexec = false; + bool login_shell; char *cp, *cmnd; + int flags = 0; int fd = -1; /* If the first char of argv[0] is '-', we are running a login shell. */ login_shell = argv[0][0] == '-'; /* If argv[0] ends in -noexec, pass the flag to sudo_execve() */ - if ((cp = strrchr(argv[0], '-')) != NULL && cp != argv[0]) - noexec = strcmp(cp, "-noexec") == 0; + if ((cp = strrchr(argv[0], '-')) != NULL && cp != argv[0]) { + if (strcmp(cp, "-noexec") == 0) + SET(flags, CD_NOEXEC); + } /* If argv[1] is --execfd=%d, extract the fd to exec with. */ if (strncmp(argv[1], "--execfd=", 9) == 0) { @@ -116,7 +119,7 @@ main(int argc, char *argv[], char *envp[]) *cp = '-'; argv[0] = cp; } - sudo_execve(fd, cmnd, argv, envp, noexec); + sudo_execve(fd, cmnd, argv, envp, flags); sudo_warn(U_("unable to execute %s"), cmnd); ret = SESH_ERR_FAILURE; } diff --git a/src/sudo.c b/src/sudo.c index 9a1fe0c21..806039e7f 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -724,8 +724,12 @@ command_info_to_details(char * const info[], struct command_details *details) break; } break; + case 'i': + SET_FLAG("intercept=", CD_INTERCEPT) + break; case 'l': SET_STRING("login_class=", login_class) + SET_FLAG("log_children=", CD_LOG_CHILDREN) break; case 'n': if (strncmp("nice=", info[i], sizeof("nice=") - 1) == 0) { diff --git a/src/sudo.h b/src/sudo.h index 7f48ece79..e3c853599 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -123,21 +123,23 @@ struct user_details { #define CD_SET_GID 0x000004 #define CD_SET_EGID 0x000008 #define CD_PRESERVE_GROUPS 0x000010 -#define CD_NOEXEC 0x000020 -#define CD_SET_PRIORITY 0x000040 -#define CD_SET_UMASK 0x000080 -#define CD_SET_TIMEOUT 0x000100 -#define CD_SUDOEDIT 0x000200 -#define CD_BACKGROUND 0x000400 -#define CD_RBAC_ENABLED 0x000800 -#define CD_USE_PTY 0x001000 -#define CD_SET_UTMP 0x002000 -#define CD_EXEC_BG 0x004000 -#define CD_SUDOEDIT_FOLLOW 0x008000 -#define CD_SUDOEDIT_CHECKDIR 0x010000 -#define CD_SET_GROUPS 0x020000 -#define CD_LOGIN_SHELL 0x040000 -#define CD_OVERRIDE_UMASK 0x080000 +#define CD_INTERCEPT 0x000020 +#define CD_NOEXEC 0x000040 +#define CD_SET_PRIORITY 0x000080 +#define CD_SET_UMASK 0x000100 +#define CD_SET_TIMEOUT 0x000200 +#define CD_SUDOEDIT 0x000400 +#define CD_BACKGROUND 0x000800 +#define CD_RBAC_ENABLED 0x001000 +#define CD_USE_PTY 0x002000 +#define CD_SET_UTMP 0x004000 +#define CD_EXEC_BG 0x008000 +#define CD_SUDOEDIT_FOLLOW 0x010000 +#define CD_SUDOEDIT_CHECKDIR 0x020000 +#define CD_SET_GROUPS 0x040000 +#define CD_LOGIN_SHELL 0x080000 +#define CD_OVERRIDE_UMASK 0x100000 +#define CD_LOG_CHILDREN 0x200000 struct preserved_fd { TAILQ_ENTRY(preserved_fd) entries; diff --git a/src/sudo_exec.h b/src/sudo_exec.h index 1e086d2cc..cc0fb3700 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -92,8 +92,9 @@ void terminate_command(pid_t pid, bool use_pgrp); bool sudo_terminated(struct command_status *cstat); /* exec_common.c */ -int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec); +int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], int flags); char **disable_execute(char *envp[], const char *dso); +char **enable_monitor(char *envp[], const char *dso); /* exec_nopty.c */ void exec_nopty(struct command_details *details, struct command_status *cstat);