diff --git a/doc/sudoers.man.in b/doc/sudoers.man.in index d32613156..25904fb57 100644 --- a/doc/sudoers.man.in +++ b/doc/sudoers.man.in @@ -25,7 +25,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.TH "SUDOERS" "@mansectform@" "August 16, 2021" "Sudo @PACKAGE_VERSION@" "File Formats Manual" +.TH "SUDOERS" "@mansectform@" "August 18, 2021" "Sudo @PACKAGE_VERSION@" "File Formats Manual" .nh .if n .ad l .SH "NAME" @@ -3056,6 +3056,28 @@ by default. .sp This setting is only supported by version 1.9.8 or higher. .TP 18n +intercept_allow_setid +On most systems, the dynamic loader will ignore +\fRLD_PRELOAD\fR +(or the equivalent) when running set user-ID and set group-ID +programs, effectively disabling intercept mode. +To prevent this from happening, +\fBsudoers\fR +will not permit a set user-ID or set group-ID program to be run in +intercept mode unless +\fIintercept_allow_setid\fR +is set. +This flag has no effect unless the +\fIintercept\fR +flag is enabled or the +\fIINTERCEPT\fR +tag has been set for the command. +This flag is +\fIon\fR +by default. +.sp +This setting is only supported by version 1.9.8 or higher. +.TP 18n intercept_authenticate If set, commands run by an intercepted process must be authenticated when the user's time stamp is not current. @@ -6300,6 +6322,13 @@ functionality only works for programs that use the system call to run the new command. This may be expanded in a future release of \fBsudo\fR. +Because most dynamic loaders ignore +\fRLD_PRELOAD\fR +(or the equivalent) when running set user-ID and set group-ID programs, +\fBsudoers\fR +will not permit such programs to be run in +\fIintercept\fR +mode. .sp The \fIintercept\fR diff --git a/doc/sudoers.mdoc.in b/doc/sudoers.mdoc.in index 8be2f2e84..f4444302e 100644 --- a/doc/sudoers.mdoc.in +++ b/doc/sudoers.mdoc.in @@ -24,7 +24,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.Dd August 16, 2021 +.Dd August 18, 2021 .Dt SUDOERS @mansectform@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -2877,6 +2877,27 @@ This flag is by default. .Pp This setting is only supported by version 1.9.8 or higher. +.It intercept_allow_setid +On most systems, the dynamic loader will ignore +.Ev LD_PRELOAD +(or the equivalent) when running set user-ID and set group-ID +programs, effectively disabling intercept mode. +To prevent this from happening, +.Nm +will not permit a set user-ID or set group-ID program to be run in +intercept mode unless +.Em intercept_allow_setid +is set. +This flag has no effect unless the +.Em intercept +flag is enabled or the +.Em INTERCEPT +tag has been set for the command. +This flag is +.Em on +by default. +.Pp +This setting is only supported by version 1.9.8 or higher. .It intercept_authenticate If set, commands run by an intercepted process must be authenticated when the user's time stamp is not current. @@ -5822,6 +5843,13 @@ functionality only works for programs that use the system call to run the new command. This may be expanded in a future release of .Nm sudo . +Because most dynamic loaders ignore +.Ev LD_PRELOAD +(or the equivalent) when running set user-ID and set group-ID programs, +.Nm +will not permit such programs to be run in +.Em intercept +mode. .Pp The .Em intercept diff --git a/plugins/sudoers/def_data.c b/plugins/sudoers/def_data.c index 06d3db6a6..50b1d55a1 100644 --- a/plugins/sudoers/def_data.c +++ b/plugins/sudoers/def_data.c @@ -593,6 +593,10 @@ struct sudo_defs_types sudo_defs_table[] = { "intercept_authenticate", T_FLAG, N_("Subsequent commands in an intercepted session must be authenticated"), NULL, + }, { + "intercept_allow_setid", T_FLAG, + N_("Allow an intercepted command to run set setuid or setgid programs"), + NULL, }, { NULL, 0, NULL } diff --git a/plugins/sudoers/def_data.h b/plugins/sudoers/def_data.h index e1cbb9c7e..dfdce1e93 100644 --- a/plugins/sudoers/def_data.h +++ b/plugins/sudoers/def_data.h @@ -274,6 +274,8 @@ #define def_log_exit_status (sudo_defs_table[I_LOG_EXIT_STATUS].sd_un.flag) #define I_INTERCEPT_AUTHENTICATE 136 #define def_intercept_authenticate (sudo_defs_table[I_INTERCEPT_AUTHENTICATE].sd_un.flag) +#define I_INTERCEPT_ALLOW_SETID 137 +#define def_intercept_allow_setid (sudo_defs_table[I_INTERCEPT_ALLOW_SETID].sd_un.flag) enum def_tuple { never, diff --git a/plugins/sudoers/def_data.in b/plugins/sudoers/def_data.in index 6309fb6ae..8872714cb 100644 --- a/plugins/sudoers/def_data.in +++ b/plugins/sudoers/def_data.in @@ -427,3 +427,6 @@ log_exit_status intercept_authenticate T_FLAG "Subsequent commands in an intercepted session must be authenticated" +intercept_allow_setid + T_FLAG + "Allow an intercepted command to run set setuid or setgid programs" diff --git a/plugins/sudoers/match_command.c b/plugins/sudoers/match_command.c index a6f847226..27b9ca946 100644 --- a/plugins/sudoers/match_command.c +++ b/plugins/sudoers/match_command.c @@ -87,29 +87,40 @@ command_args_match(const char *sudoers_cmnd, const char *sudoers_args) * Returns true on success, else false. */ static bool -do_stat(int fd, const char *path, const char *runchroot, struct stat *sb) +do_stat(int fd, const char *path, const char *runchroot, bool intercepted, + struct stat *sb) { struct stat sbuf; char pathbuf[PATH_MAX]; + bool ret; debug_decl(do_stat, SUDOERS_DEBUG_MATCH); if (sb == NULL) sb = &sbuf; - if (fd != -1) - debug_return_bool(fstat(fd, sb) == 0); - - /* Make path relative to the new root, if any. */ - if (runchroot != NULL) { - const int len = - snprintf(pathbuf, sizeof(pathbuf), "%s%s", runchroot, path); - if (len >= ssizeof(pathbuf)) { - errno = ENAMETOOLONG; - debug_return_bool(false); + if (fd != -1) { + ret = fstat(fd, sb) == 0; + } else { + /* Make path relative to the new root, if any. */ + if (runchroot != NULL) { + const int len = + snprintf(pathbuf, sizeof(pathbuf), "%s%s", runchroot, path); + if (len >= ssizeof(pathbuf)) { + errno = ENAMETOOLONG; + debug_return_bool(false); + } + path = pathbuf; } - path = pathbuf; + ret = stat(path, sb) == 0; } - debug_return_bool(stat(path, sb) == 0); + if (ret && intercepted) { + if (!def_intercept_allow_setid && ISSET(sb->st_mode, S_ISUID|S_ISGID)) { + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "rejecting setid command %s", path); + ret = false; + } + } + debug_return_int(ret); } #endif /* SUDOERS_NAME_MATCH */ @@ -220,7 +231,7 @@ set_cmnd_fd(int fd) */ static bool command_matches_dir(const char *sudoers_dir, size_t dlen, const char *runchroot, - const struct command_digest_list *digests) + bool intercepted, const struct command_digest_list *digests) { char buf[PATH_MAX], sdbuf[PATH_MAX]; struct stat sudoers_stat; @@ -271,7 +282,7 @@ command_matches_dir(const char *sudoers_dir, size_t dlen, const char *runchroot, /* Open the file for fdexec or for digest matching. */ if (!open_cmnd(buf, NULL, digests, &fd)) continue; - if (!do_stat(fd, buf, NULL, &sudoers_stat)) + if (!do_stat(fd, buf, NULL, intercepted, &sudoers_stat)) continue; if (user_stat == NULL || @@ -305,7 +316,7 @@ command_matches_dir(const char *sudoers_dir, size_t dlen, const char *runchroot, */ static bool command_matches_dir(const char *sudoers_dir, size_t dlen, const char *runchroot, - const struct command_digest_list *digests) + bool intercepted, const struct command_digest_list *digests) { int fd = -1; debug_decl(command_matches_dir, SUDOERS_DEBUG_MATCH); @@ -335,7 +346,7 @@ bad: static bool command_matches_all(const char *runchroot, - const struct command_digest_list *digests) + bool intercepted, const struct command_digest_list *digests) { int fd = -1; debug_decl(command_matches_all, SUDOERS_DEBUG_MATCH); @@ -345,7 +356,7 @@ command_matches_all(const char *runchroot, if (!open_cmnd(user_cmnd, runchroot, digests, &fd)) goto bad; #ifndef SUDOERS_NAME_MATCH - if (!do_stat(fd, user_cmnd, runchroot, NULL)) + if (!do_stat(fd, user_cmnd, runchroot, intercepted, NULL)) goto bad; #endif } @@ -365,7 +376,8 @@ bad: static bool command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args, - const char *runchroot, const struct command_digest_list *digests) + const char *runchroot, bool intercepted, + const struct command_digest_list *digests) { int fd = -1; debug_decl(command_matches_fnmatch, SUDOERS_DEBUG_MATCH); @@ -386,7 +398,7 @@ command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args, if (!open_cmnd(user_cmnd, runchroot, digests, &fd)) goto bad; #ifndef SUDOERS_NAME_MATCH - if (!do_stat(fd, user_cmnd, runchroot, NULL)) + if (!do_stat(fd, user_cmnd, runchroot, intercepted, NULL)) goto bad; #endif /* Check digest of user_cmnd since sudoers_cmnd is a pattern. */ @@ -407,7 +419,8 @@ bad: #ifndef SUDOERS_NAME_MATCH static bool command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args, - const char *runchroot, const struct command_digest_list *digests) + const char *runchroot, bool intercepted, + const struct command_digest_list *digests) { struct stat sudoers_stat; bool bad_digest = false; @@ -468,7 +481,7 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args, /* Open the file for fdexec or for digest matching. */ if (!open_cmnd(cp, runchroot, digests, &fd)) continue; - if (!do_stat(fd, cp, runchroot, &sudoers_stat)) + if (!do_stat(fd, cp, runchroot, intercepted, &sudoers_stat)) continue; if (user_stat == NULL || (user_stat->st_dev == sudoers_stat.st_dev && @@ -505,7 +518,7 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args, /* If it ends in '/' it is a directory spec. */ dlen = strlen(cp); if (cp[dlen - 1] == '/') { - if (command_matches_dir(cp, dlen, runchroot, digests)) { + if (command_matches_dir(cp, dlen, runchroot, intercepted, digests)) { globfree(&gl); debug_return_bool(true); } @@ -520,7 +533,7 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args, /* Open the file for fdexec or for digest matching. */ if (!open_cmnd(cp, runchroot, digests, &fd)) continue; - if (!do_stat(fd, cp, runchroot, &sudoers_stat)) + if (!do_stat(fd, cp, runchroot, intercepted, &sudoers_stat)) continue; if (user_stat == NULL || (user_stat->st_dev == sudoers_stat.st_dev && @@ -553,7 +566,8 @@ done: static bool command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, - const char *runchroot, const struct command_digest_list *digests) + const char *runchroot, bool intercepted, + const struct command_digest_list *digests) { struct stat sudoers_stat; const char *base; @@ -565,7 +579,7 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, dlen = strlen(sudoers_cmnd); if (sudoers_cmnd[dlen - 1] == '/') { debug_return_bool(command_matches_dir(sudoers_cmnd, dlen, runchroot, - digests)); + intercepted, digests)); } /* Only proceed if user_base and basename(sudoers_cmnd) match */ @@ -584,7 +598,7 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, * c) there are args in sudoers and on command line and they match * d) there is a digest and it matches */ - if (user_stat != NULL && do_stat(fd, sudoers_cmnd, runchroot, &sudoers_stat)) { + if (user_stat != NULL && do_stat(fd, sudoers_cmnd, runchroot, intercepted, &sudoers_stat)) { if (user_stat->st_dev != sudoers_stat.st_dev || user_stat->st_ino != sudoers_stat.st_ino) goto bad; @@ -614,15 +628,17 @@ bad: #else /* SUDOERS_NAME_MATCH */ static bool command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args, - const char *runchroot, const struct command_digest_list *digests) + const char *runchroot, bool intercepted, + const struct command_digest_list *digests) { return command_matches_fnmatch(sudoers_cmnd, sudoers_args, runchroot, - digests); + intercepted, digests); } static bool command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, - const char *runchroot, const struct command_digest_list *digests) + const char *runchroot, bool intercepted, + const struct command_digest_list *digests) { size_t dlen; int fd = -1; @@ -632,7 +648,7 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, dlen = strlen(sudoers_cmnd); if (sudoers_cmnd[dlen - 1] == '/') { debug_return_bool(command_matches_dir(sudoers_cmnd, dlen, runchroot, - digests)); + intercepted, digests)); } if (strcmp(user_cmnd, sudoers_cmnd) == 0) { @@ -670,6 +686,7 @@ command_matches(const char *sudoers_cmnd, const char *sudoers_args, const char *runchroot, struct cmnd_info *info, const struct command_digest_list *digests) { + const bool intercepted = info ? info->intercepted : false; char *saved_user_cmnd = NULL; struct stat saved_user_stat; bool rc = false; @@ -702,7 +719,7 @@ command_matches(const char *sudoers_cmnd, const char *sudoers_args, } if (sudoers_cmnd == NULL) { - rc = command_matches_all(runchroot, digests); + rc = command_matches_all(runchroot, intercepted, digests); goto done; } @@ -728,12 +745,16 @@ command_matches(const char *sudoers_cmnd, const char *sudoers_args, * If sudoers_cmnd has meta characters in it, we need to * use glob(3) and/or fnmatch(3) to do the matching. */ - if (def_fast_glob) - rc = command_matches_fnmatch(sudoers_cmnd, sudoers_args, runchroot, digests); - else - rc = command_matches_glob(sudoers_cmnd, sudoers_args, runchroot, digests); + if (def_fast_glob) { + rc = command_matches_fnmatch(sudoers_cmnd, sudoers_args, runchroot, + intercepted, digests); + } else { + rc = command_matches_glob(sudoers_cmnd, sudoers_args, runchroot, + intercepted, digests); + } } else { - rc = command_matches_normal(sudoers_cmnd, sudoers_args, runchroot, digests); + rc = command_matches_normal(sudoers_cmnd, sudoers_args, runchroot, + intercepted, digests); } done: if (saved_user_cmnd != NULL) { diff --git a/plugins/sudoers/parse.c b/plugins/sudoers/parse.c index da51487d8..6c21daca6 100644 --- a/plugins/sudoers/parse.c +++ b/plugins/sudoers/parse.c @@ -124,6 +124,8 @@ sudoers_lookup_check(struct sudo_nss *nss, struct passwd *pw, debug_decl(sudoers_lookup_check, SUDOERS_DEBUG_PARSER); memset(info, 0, sizeof(*info)); + if (def_intercept || ISSET(sudo_mode, MODE_POLICY_INTERCEPTED)) + info->intercepted = true; TAILQ_FOREACH_REVERSE(us, &nss->parse_tree->userspecs, userspec_list, entries) { if (userlist_matches(nss->parse_tree, pw, &us->users) != ALLOW) diff --git a/plugins/sudoers/parse.h b/plugins/sudoers/parse.h index 0aedfd95b..ea2a179d1 100644 --- a/plugins/sudoers/parse.h +++ b/plugins/sudoers/parse.h @@ -302,6 +302,7 @@ struct cmnd_info { struct stat cmnd_stat; char *cmnd_path; int status; + bool intercepted; }; /* diff --git a/plugins/sudoers/regress/fuzz/fuzz_sudoers.c b/plugins/sudoers/regress/fuzz/fuzz_sudoers.c index 19eb4ed61..25e43de80 100644 --- a/plugins/sudoers/regress/fuzz/fuzz_sudoers.c +++ b/plugins/sudoers/regress/fuzz/fuzz_sudoers.c @@ -48,6 +48,7 @@ struct sudo_user sudo_user; struct passwd *list_pw; sudo_conv_t sudo_conv = fuzz_conversation; bool sudoers_recovery = true; +int sudo_mode; FILE * open_sudoers(const char *file, bool doedit, bool *keepopen)