diff --git a/config.h.in b/config.h.in index 106af3a..30c3928 100644 --- a/config.h.in +++ b/config.h.in @@ -87,6 +87,10 @@ don't. */ #undef HAVE_DECL_H_ERRNO +/* Define to 1 if you have the declaration of `SECCOMP_SET_MODE_FILTER', and + to 0 if you don't. */ +#undef HAVE_DECL_SECCOMP_SET_MODE_FILTER + /* Define to 1 if you have the declaration of `sys_sigabbrev', and to 0 if you don't. */ #undef HAVE_DECL_SYS_SIGABBREV @@ -134,9 +138,21 @@ /* Define to 1 if the compiler supports the __visibility__ attribute. */ #undef HAVE_DSO_VISIBILITY +/* Define to 1 if you have the `exect' function. */ +#undef HAVE_EXECT + +/* Define to 1 if you have the `execvp' function. */ +#undef HAVE_EXECVP + +/* Define to 1 if you have the `execvpe' function. */ +#undef HAVE_EXECVPE + /* Define to 1 if your system has the F_CLOSEM fcntl. */ #undef HAVE_FCNTL_CLOSEM +/* Define to 1 if you have the `fexecve' function. */ +#undef HAVE_FEXECVE + /* Define to 1 if you have the `fgetln' function. */ #undef HAVE_FGETLN @@ -421,6 +437,12 @@ /* Define to 1 if you have the `posix_openpt' function. */ #undef HAVE_POSIX_OPENPT +/* Define to 1 if you have the `posix_spawn' function. */ +#undef HAVE_POSIX_SPAWN + +/* Define to 1 if you have the `posix_spawnp' function. */ +#undef HAVE_POSIX_SPAWNP + /* Define to 1 if you have the `priv_set' function. */ #undef HAVE_PRIV_SET @@ -703,6 +725,12 @@ /* Define to 1 if you have the `vsnprintf' function. */ #undef HAVE_VSNPRINTF +/* Define to 1 if you have the `wordexp' function. */ +#undef HAVE_WORDEXP + +/* Define to 1 if you have the header file. */ +#undef HAVE_WORDEXP_H + /* Define to 1 if you have the header file. */ #undef HAVE_ZLIB_H diff --git a/configure.in b/configure.in index 06b4722..945284e 100644 --- a/configure.in +++ b/configure.in @@ -1824,6 +1824,14 @@ case "$host" in shadow_funcs="getspnam" shadow_libs_optional="-lshadow" test -z "$with_pam" && AUTH_EXCL_DEF="PAM" + # Check for SECCOMP_SET_MODE_FILTER in linux/seccomp.h + AC_CHECK_DECLS([SECCOMP_SET_MODE_FILTER], [], [], [ +#include +#include +#include +#include +#include + ]) ;; *-convex-bsd*) OSDEFS="${OSDEFS} -D_CONVEX_SOURCE" @@ -2091,7 +2099,7 @@ AC_HEADER_DIRENT AC_HEADER_TIME AC_HEADER_STDBOOL AC_HEADER_MAJOR -AC_CHECK_HEADERS(malloc.h netgroup.h paths.h spawn.h utime.h utmpx.h sys/sockio.h sys/bsdtypes.h sys/select.h sys/stropts.h sys/sysmacros.h) +AC_CHECK_HEADERS(malloc.h netgroup.h paths.h spawn.h utime.h utmpx.h wordexp.h sys/sockio.h sys/bsdtypes.h sys/select.h sys/stropts.h sys/sysmacros.h) AC_CHECK_HEADERS([procfs.h] [sys/procfs.h], [AC_CHECK_MEMBERS(struct psinfo.pr_ttydev, [AC_CHECK_FUNCS(_ttyname_dev)], [], [AC_INCLUDES_DEFAULT #ifdef HAVE_PROCFS_H #include @@ -2226,7 +2234,8 @@ dnl AC_FUNC_GETGROUPS AC_CHECK_FUNCS(glob strrchr sysconf tzset strftime setenv \ regcomp setlocale nl_langinfo mbr_check_membership \ - setrlimit64) + setrlimit64 \ + wordexp exect execvp execvpe fexecve posix_spawn posix_spawnp) AC_REPLACE_FUNCS(getgrouplist) AC_CHECK_FUNCS(getline, [], [ AC_LIBOBJ(getline) diff --git a/include/missing.h b/include/missing.h index fda151b..cac8088 100644 --- a/include/missing.h +++ b/include/missing.h @@ -198,6 +198,13 @@ typedef struct sigaction sigaction_t; #endif /* + * The nitems macro may be defined in sys/param.h + */ +#ifndef nitems +# define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +/* * If dirfd() does not exists, hopefully dd_fd does. */ #if !defined(HAVE_DIRFD) && defined(HAVE_DD_FD) diff --git a/src/Makefile.in b/src/Makefile.in index 918cf04..079aa3e 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -109,7 +109,7 @@ sudo: $(OBJS) $(LT_LIBS) $(LIBTOOL) --mode=link $(CC) -o $@ $(OBJS) $(LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) libsudo_noexec.la: sudo_noexec.lo - $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) $(LT_LDFLAGS) -o $@ sudo_noexec.lo -avoid-version -rpath $(noexecdir) + $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) $(LT_LDFLAGS) @LIBDL@ -o $@ sudo_noexec.lo -avoid-version -rpath $(noexecdir) sesh: sesh.o error.o exec_common.o @LIBINTL@ $(LT_LIBS) $(LIBTOOL) --mode=link $(CC) -o $@ sesh.o error.o exec_common.o $(LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) @LIBINTL@ $(LIBS) diff --git a/src/sudo_noexec.c b/src/sudo_noexec.c index 2872501..f04b277 100644 --- a/src/sudo_noexec.c +++ b/src/sudo_noexec.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2005, 2010-2011 Todd C. Miller + * Copyright (c) 2004-2005, 2010-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 @@ -18,20 +18,64 @@ #include +#if defined(HAVE_DECL_SECCOMP_SET_MODE_FILTER) && HAVE_DECL_SECCOMP_SET_MODE_FILTER +# include +# include +# include +# include +#endif + #include #include +#include +#include +#include #ifdef HAVE_SPAWN_H #include #endif +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_WORDEXP_H +#include +#endif +#if defined(HAVE_SHL_LOAD) +# include +#elif defined(HAVE_DLOPEN) +# include +#endif #include "missing.h" +#include "pathnames.h" +#ifdef HAVE___INTERPOSE /* - * Dummy versions of the execve() family of syscalls. We don't need - * to stub out all of them, just the ones that correspond to actual - * system calls (which varies by OS). Note that it is still possible - * to access the real syscalls via the syscall() interface but very - * few programs actually do that. + * Mac OS X 10.4 and above has support for library symbol interposition. + * There is a good explanation of this in the Mac OS X Internals book. + */ +typedef struct interpose_s { + void *new_func; + void *orig_func; +} interpose_t; + +# define FN_NAME(fn) dummy_ ## fn +# define INTERPOSE(fn) \ + __attribute__((__used__)) static const interpose_t interpose_ ## fn \ + __attribute__((__section__("__DATA,__interpose"))) = \ + { (void *)dummy_ ## fn, (void *)fn }; +#else +# define FN_NAME(fn) fn +# define INTERPOSE(fn) +#endif + +/* + * Dummy versions of the exec(3) family of syscalls. It is not enough to + * just dummy out execve(2) since many C libraries do not call the public + * execve(2) interface. Note that it is still possible to access the real + * syscalls via the syscall(2) interface, but that is rarely done. */ #define DUMMY_BODY \ @@ -40,61 +84,172 @@ return -1; \ } +#define DUMMY1(fn, t1) \ +__dso_public int \ +FN_NAME(fn)(t1 a1) \ +DUMMY_BODY \ +INTERPOSE(fn) + #define DUMMY2(fn, t1, t2) \ __dso_public int \ -fn(t1 a1, t2 a2) \ -DUMMY_BODY +FN_NAME(fn)(t1 a1, t2 a2) \ +DUMMY_BODY \ +INTERPOSE(fn) #define DUMMY3(fn, t1, t2, t3) \ __dso_public int \ -fn(t1 a1, t2 a2, t3 a3) \ -DUMMY_BODY +FN_NAME(fn)(t1 a1, t2 a2, t3 a3) \ +DUMMY_BODY \ +INTERPOSE(fn) #define DUMMY6(fn, t1, t2, t3, t4, t5, t6) \ __dso_public int \ -fn(t1 a1, t2 a2, t3 a3, t4 a4, t5 a5, t6 a6) \ -DUMMY_BODY +FN_NAME(fn)(t1 a1, t2 a2, t3 a3, t4 a4, t5 a5, t6 a6) \ +DUMMY_BODY \ +INTERPOSE(fn) #define DUMMY_VA(fn, t1, t2) \ __dso_public int \ -fn(t1 a1, t2 a2, ...) \ -DUMMY_BODY +FN_NAME(fn)(t1 a1, t2 a2, ...) \ +DUMMY_BODY \ +INTERPOSE(fn) +/* + * Standard exec(3) family of functions. + */ DUMMY_VA(execl, const char *, const char *) -DUMMY_VA(_execl, const char *, const char *) -DUMMY_VA(__execl, const char *, const char *) DUMMY_VA(execle, const char *, const char *) -DUMMY_VA(_execle, const char *, const char *) -DUMMY_VA(__execle, const char *, const char *) DUMMY_VA(execlp, const char *, const char *) -DUMMY_VA(_execlp, const char *, const char *) -DUMMY_VA(__execlp, const char *, const char *) -DUMMY3(exect, const char *, char * const *, char * const *) -DUMMY3(_exect, const char *, char * const *, char * const *) -DUMMY3(__exect, const char *, char * const *, char * const *) DUMMY2(execv, const char *, char * const *) -DUMMY2(_execv, const char *, char * const *) -DUMMY2(__execv, const char *, char * const *) DUMMY2(execvp, const char *, char * const *) -DUMMY2(_execvp, const char *, char * const *) -DUMMY2(__execvp, const char *, char * const *) -DUMMY3(execvP, const char *, const char *, char * const *) -DUMMY3(_execvP, const char *, const char *, char * const *) -DUMMY3(__execvP, const char *, const char *, char * const *) DUMMY3(execve, const char *, char * const *, char * const *) -DUMMY3(_execve, const char *, char * const *, char * const *) -DUMMY3(__execve, const char *, char * const *, char * const *) + +/* + * Non-standard exec(3) functions and corresponding private versions. + */ +#ifdef HAVE_EXECVP +DUMMY3(execvP, const char *, const char *, char * const *) +#endif +#ifdef HAVE_EXECVPE DUMMY3(execvpe, const char *, char * const *, char * const *) -DUMMY3(_execvpe, const char *, char * const *, char * const *) -DUMMY3(__execvpe, const char *, char * const *, char * const *) +#endif +#ifdef HAVE_EXECT +DUMMY3(exect, const char *, char * const *, char * const *) +#endif + +/* + * Not all systems support fexecve(2), posix_spawn(2) and posix_spawnp(2). + */ +#ifdef HAVE_FEXECVE DUMMY3(fexecve, int , char * const *, char * const *) -DUMMY3(_fexecve, int , char * const *, char * const *) -DUMMY3(__fexecve, int , char * const *, char * const *) -#ifdef HAVE_SPAWN_H +#endif +#ifdef HAVE_POSIX_SPAWN DUMMY6(posix_spawn, pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *, char * const *, char * const *) -DUMMY6(_posix_spawn, pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *, char * const *, char * const *) -DUMMY6(__posix_spawn, pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *, char * const *, char * const *) +#endif +#ifdef HAVE_POSIX_SPAWNP DUMMY6(posix_spawnp, pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *, char * const *, char * const *) -DUMMY6(_posix_spawnp, pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *, char * const *, char * const *) -DUMMY6(__posix_spawnp, pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *, char * const *, char * const *) -#endif /* HAVE_SPAWN_H */ +#endif + +/* + * system(3) and popen(3). + * We can't use a wrapper for popen since it returns FILE *, not int. + */ +DUMMY1(system, const char *) + +__dso_public FILE * +FN_NAME(popen)(const char *c, const char *t) +{ + errno = EACCES; + return NULL; +} +INTERPOSE(popen) + +#if defined(HAVE_WORDEXP) && (defined(RTLD_NEXT) || defined(HAVE_SHL_LOAD) || defined(HAVE___INTERPOSE)) +/* + * We can't use a wrapper for wordexp(3) since we still want to call + * the real wordexp(3) but with WRDE_NOCMD added to the flags argument. + */ +typedef int (*sudo_fn_wordexp_t)(const char *, wordexp_t *, int); + +__dso_public int +FN_NAME(wordexp)(const char *words, wordexp_t *we, int flags) +{ +#if defined(HAVE___INTERPOSE) + return wordexp(words, we, flags | WRDE_NOCMD); +#else +# if defined(HAVE_DLOPEN) + void *fn = dlsym(RTLD_NEXT, "wordexp"); +# elif defined(HAVE_SHL_LOAD) + const char *name, *myname = _PATH_SUDO_NOEXEC; + struct shl_descriptor *desc; + void *fn = NULL; + int idx = 0; + + name = strrchr(myname, '/'); + if (name != NULL) + myname = name + 1; + + /* Search for wordexp() but skip this shared object. */ + while (shl_get(idx++, &desc) == 0) { + name = strrchr(desc->filename, '/'); + if (name == NULL) + name = desc->filename; + else + name++; + if (strcmp(name, myname) == 0) + continue; + if (shl_findsym(&desc->handle, "wordexp", TYPE_PROCEDURE, &fn) == 0) + break; + } +# else + void *fn = NULL; +# endif + if (fn == NULL) { + errno = EACCES; + return -1; + } + return ((sudo_fn_wordexp_t)fn)(words, we, flags | WRDE_NOCMD); +#endif /* HAVE___INTERPOSE */ +} +INTERPOSE(wordexp) +#endif /* HAVE_WORDEXP && (RTLD_NEXT || HAVE_SHL_LOAD || HAVE___INTERPOSE) */ + +/* + * On Linux we can use a seccomp() filter to disable exec. + */ +#if defined(HAVE_DECL_SECCOMP_SET_MODE_FILTER) && HAVE_DECL_SECCOMP_SET_MODE_FILTER + +/* Older systems may not support execveat(2). */ +#ifndef __NR_execveat +# define __NR_execveat -1 +#endif + +static void noexec_ctor(void) __attribute__((constructor)); + +static void +noexec_ctor(void) +{ + struct sock_filter exec_filter[] = { + /* Load syscall number into the accumulator */ + BPF_STMT(BPF_LD | BPF_ABS, offsetof(struct seccomp_data, nr)), + /* Jump to deny for execve/execveat */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 2, 0), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execveat, 1, 0), + /* Allow non-matching syscalls */ + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + /* Deny execve/execveat syscall */ + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EACCES & SECCOMP_RET_DATA)) + }; + const struct sock_fprog exec_fprog = { + nitems(exec_filter), + exec_filter + }; + + /* + * SECCOMP_MODE_FILTER will fail unless the process has + * CAP_SYS_ADMIN or the no_new_privs bit is set. + */ + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0) + (void)prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &exec_fprog); +} +#endif /* HAVE_DECL_SECCOMP_SET_MODE_FILTER */