From: Todd C. Miller Date: Mon, 14 Nov 2016 21:21:08 +0000 (-0700) Subject: Add regress for noexec functionality X-Git-Tag: SUDO_1_8_19^2~42 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=66af45eb24b3efff82d6d9ed465eaa3f7d742e6a;p=sudo Add regress for noexec functionality --- diff --git a/MANIFEST b/MANIFEST index 564a1e4c2..fc65b619c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -577,6 +577,7 @@ src/openbsd.c src/parse_args.c src/preload.c src/preserve_fds.c +src/regress/noexec/check_noexec.c src/regress/ttyname/check_ttyname.c src/selinux.c src/sesh.c diff --git a/configure b/configure index 8be0338ad..2a10f9dd0 100755 --- a/configure +++ b/configure @@ -750,6 +750,7 @@ LDAP SELINUX_USAGE BSDAUTH_USAGE DONT_LEAK_PATH_INFO +CHECK_NOEXEC INSTALL_NOEXEC INSTALL_BACKUP sesh_file @@ -2855,6 +2856,7 @@ $as_echo "$as_me: Configuring Sudo version $PACKAGE_VERSION" >&6;} + # @@ -2906,6 +2908,7 @@ PLUGINDIR=/usr/local/libexec/sudo # INSTALL_BACKUP= INSTALL_NOEXEC= +CHECK_NOEXEC= exampledir='$(docdir)/examples' devdir='$(srcdir)' PROGS="sudo" @@ -24406,6 +24409,11 @@ if test X"$with_noexec" != X"no" -o X"$with_selinux" != X"no" -o "$enabled_share PROGS="${PROGS} sudo_noexec.la" INSTALL_NOEXEC="install-noexec" + # Can't use asan with LD_PRELOAD + if test "$enable_asan" != "yes"; then + CHECK_NOEXEC=check_sudo_noexec + fi + noexec_file="$with_noexec" _noexec_file= while test X"$noexec_file" != X"$_noexec_file"; do diff --git a/configure.ac b/configure.ac index 438d25cf8..e3b284be8 100644 --- a/configure.ac +++ b/configure.ac @@ -65,6 +65,7 @@ AC_SUBST([noexec_file]) AC_SUBST([sesh_file]) AC_SUBST([INSTALL_BACKUP]) AC_SUBST([INSTALL_NOEXEC]) +AC_SUBST([CHECK_NOEXEC]) AC_SUBST([DONT_LEAK_PATH_INFO]) AC_SUBST([BSDAUTH_USAGE]) AC_SUBST([SELINUX_USAGE]) @@ -189,6 +190,7 @@ dnl May be overridden by environment variables.. dnl INSTALL_BACKUP= INSTALL_NOEXEC= +CHECK_NOEXEC= exampledir='$(docdir)/examples' devdir='$(srcdir)' PROGS="sudo" @@ -4180,6 +4182,11 @@ if test X"$with_noexec" != X"no" -o X"$with_selinux" != X"no" -o "$enabled_share PROGS="${PROGS} sudo_noexec.la" INSTALL_NOEXEC="install-noexec" + # Can't use asan with LD_PRELOAD + if test "$enable_asan" != "yes"; then + CHECK_NOEXEC=check_sudo_noexec + fi + noexec_file="$with_noexec" _noexec_file= while test X"$noexec_file" != X"$_noexec_file"; do diff --git a/lib/util/sudo_conf.c b/lib/util/sudo_conf.c index b91dbac79..cd08b0e6d 100644 --- a/lib/util/sudo_conf.c +++ b/lib/util/sudo_conf.c @@ -459,13 +459,11 @@ sudo_conf_sesh_path_v1(void) return sudo_conf_paths.sesh; } -#ifdef _PATH_SUDO_NOEXEC const char * sudo_conf_noexec_path_v1(void) { return sudo_conf_paths.noexec; } -#endif #ifdef _PATH_SUDO_PLUGIN_DIR const char * diff --git a/src/Makefile.in b/src/Makefile.in index bcc968ad8..897dbdf89 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -117,6 +117,8 @@ OBJS = conversation.o env_hooks.o exec.o exec_common.o exec_pty.o \ SESH_OBJS = sesh.o exec_common.o +CHECK_NOEXEC_OBJS = check_noexec.o exec_common.o + CHECK_TTYNAME_OBJS = check_ttyname.o ttyname.o LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/ @@ -155,6 +157,9 @@ sudo_noexec.la: libsudo_noexec.la sesh: $(SESH_OBJS) $(LT_LIBS) $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(SESH_OBJS) $(LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) +check_noexec: $(CHECK_NOEXEC_OBJS) $(top_builddir)/lib/util/libsudo_util.la + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_NOEXEC_OBJS) $(TEST_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LIBS) + check_ttyname: $(CHECK_TTYNAME_OBJS) $(top_builddir)/lib/util/libsudo_util.la $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_TTYNAME_OBJS) $(TEST_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LIBS) @@ -217,14 +222,19 @@ splint: cppcheck: cppcheck $(CPPCHECK_OPTS) -I$(incdir) -I$(top_builddir) -I. -I$(srcdir) -I$(top_srcdir) $(srcdir)/*.c -check: $(TEST_PROGS) +check: $(TEST_PROGS) @CHECK_NOEXEC@ @if test X"$(cross_compiling)" != X"yes"; then \ ./check_ttyname; \ fi +check_sudo_noexec: sudo_noexec.la check_noexec + @if test X"$(cross_compiling)" != X"yes"; then \ + ./check_noexec .libs/$(noexecfile); \ + fi + clean: -$(LIBTOOL) $(LTFLAGS) --mode=clean rm -f $(PROGS) $(TEST_PROGS) \ - *.lo *.o *.la *.a stamp-* core *.core core.* + *.lo *.o *.la *.a stamp-* core *.core core.* nohup.out mostlyclean: clean @@ -239,6 +249,11 @@ realclean: distclean cleandir: realclean # Autogenerated dependencies, do not modify +check_noexec.o: $(srcdir)/regress/noexec/check_noexec.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/noexec/check_noexec.c check_ttyname.o: $(srcdir)/regress/ttyname/check_ttyname.c \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ $(incdir)/sudo_debug.h $(incdir)/sudo_fatal.h \ diff --git a/src/exec_common.c b/src/exec_common.c index 78ad7adf7..9dd2b2b8c 100644 --- a/src/exec_common.c +++ b/src/exec_common.c @@ -151,8 +151,8 @@ preload_dso(char *envp[], const char *dso_file) * to run. On systems with privilege sets, we can remove the exec * privilege. On other systems we use LD_PRELOAD and the like. */ -static char ** -disable_execute(char *envp[]) +char ** +disable_execute(char *envp[], const char *dso) { debug_decl(disable_execute, SUDO_DEBUG_UTIL) @@ -167,8 +167,8 @@ disable_execute(char *envp[]) #endif /* HAVE_PRIV_SET */ #ifdef _PATH_SUDO_NOEXEC - if (sudo_conf_noexec_path() != NULL) - envp = preload_dso(envp, sudo_conf_noexec_path()); + if (dso != NULL) + envp = preload_dso(envp, dso); #endif /* _PATH_SUDO_NOEXEC */ debug_return_ptr(envp); @@ -187,7 +187,7 @@ sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noe /* Modify the environment as needed to disable further execve(). */ if (noexec) - envp = disable_execute(envp); + envp = disable_execute(envp, sudo_conf_noexec_path()); #ifdef HAVE_FEXECVE if (fd != -1) diff --git a/src/regress/noexec/check_noexec.c b/src/regress/noexec/check_noexec.c new file mode 100644 index 000000000..c4c22d05b --- /dev/null +++ b/src/regress/noexec/check_noexec.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2016 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. + */ + +#include + +#include +#include +#include +#include +#ifdef HAVE_STDBOOL_H +# include +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_WORDEXP_H +# include +#endif +#include +#include +#include +#include + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" +#include "sudo_exec.h" + +__dso_public int main(int argc, char *argv[], char *envp[]); + +static bool +report_status(int status, const char *what) +{ + bool ret = false; + + /* system() returns -1 for exec failure. */ + if (status == -1) { + printf("%s: OK (%s)\n", getprogname(), what); + return true; + } + + /* check exit value, expecting 127 for failure */ + if (WIFEXITED(status)) { + int exitval = WEXITSTATUS(status); + if (exitval == 127) { + printf("%s: OK (%s)\n", getprogname(), what); + ret = true; + } else { + printf("%s: FAIL (%s) [%d]\n", getprogname(), what, exitval); + } + } else if (WIFSIGNALED(status)) { + printf("%s: FAIL (%s) [signal %d]\n", getprogname(), what, + WTERMSIG(status)); + } else { + /* should not happen */ + printf("%s: FAIL (%s) [status %d]\n", getprogname(), what, status); + } + + return ret; +} + +static int +try_execl(void) +{ + pid_t child, pid; + int status; + + child = fork(); + switch (child) { + case -1: + sudo_fatal_nodebug("fork"); + case 0: + /* child */ + /* Try to exec /bin/true, else exit with value 127. */ + execl("/bin/true", "true", (char *)0); + _exit(127); + default: + /* parent */ + do { + pid = waitpid(child, &status, 0); + } while (pid == -1 && errno == EINTR); + if (pid == -1) + sudo_fatal_nodebug("waitpid"); + + if (report_status(status, "execl")) + return 0; + return 1; + } +} + +static int +try_system(void) +{ + int status; + + /* Try to run /bin/true, system() returns 127 on exec failure. */ + status = system("/bin/true > /dev/null 2>&1"); + + if (report_status(status, "system")) + return 0; + return 1; +} + +#ifdef HAVE_WORDEXP_H +static int +try_wordexp(void) +{ + wordexp_t we; + int rc, ret = 1; + + /* + * sudo_noexec.so prevents command substitution via the WRDE_NOCMD flag + * where possible. + */ + rc = wordexp("$(/bin/echo foo)", &we, 0); + switch (rc) { + case -1: +#ifdef WRDE_ERRNO + case WRDE_ERRNO: + /* + * Solaris returns WRDE_ERRNO for execve() failure and sudo's + * wordexp() wrapper returns -1 if RTLD_NEXT is not supported. + */ + printf("%s: MOSTLY OK (wordexp)\n", getprogname()); + ret = 0; + break; +#endif + case WRDE_CMDSUB: + printf("%s: OK (wordexp)\n", getprogname()); + ret = 0; + break; + case 0: + /* + * On HP-UX 11.00 we don't seem to be able to add WRDE_NOCMD + * but the execve() wrapper prevents the command substitution. + */ + if (we.we_wordc == 0) { + printf("%s: MOSTLY OK (wordexp)\n", getprogname()); + ret = 0; + break; + } + wordfree(&we); + /* FALLTHROUGH */ + default: + printf("%s: FAIL (wordexp) [%d]\n", getprogname(), rc); + break; + } + return ret; +} +#endif + +int +main(int argc, char *argv[], char *envp[]) +{ + int errors = 0; + + initprogname(argc > 0 ? argv[0] : "check_noexec"); + + if (argc != 2) { + fprintf(stderr, "usage: %s regress | /path/to/sudo_noexec.so\n", getprogname()); + exit(1); + } + + /* Disable execution for post-exec and re-exec ourself. */ + if (strcmp(argv[1], "rexec") != 0) { + const char *noexec = argv[1]; + argv[1] = "rexec"; + execve(argv[0], argv, disable_execute(envp, noexec)); + sudo_fatalx_nodebug("execve"); + } + + errors += try_execl(); + errors += try_system(); +#ifdef HAVE_WORDEXP_H + errors += try_wordexp(); +#endif + + return errors; +} diff --git a/src/sudo_exec.h b/src/sudo_exec.h index 0099cb09d..6dc795cbc 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -73,11 +73,14 @@ */ /* exec.c */ -struct sudo_event_base; -int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec); extern volatile pid_t cmnd_pid; +/* exec_common.c */ +int sudo_execve(int fd, const char *path, char *const argv[], char *envp[], bool noexec); +char **disable_execute(char *envp[], const char *dso); + /* exec_pty.c */ +struct sudo_event_base; struct command_details; struct command_status; int fork_pty(struct command_details *details, int sv[], sigset_t *omask);