Add intercept_allow_setid sudoers option, disabled by default.

With this change, a shell in intercept mode cannot run a setuid or
setgid binary by default.  On most systems, the dynamic loader will
ignore LD_PRELOAD for setuid/setgid binaries such as sudo which
would effectively disable intercept mode.
This commit is contained in:
Todd C. Miller
2021-08-18 15:43:26 -06:00
parent 53a95e3a50
commit f9d3f46fa7
9 changed files with 130 additions and 39 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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"

View File

@@ -87,18 +87,20 @@ 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);
if (fd != -1) {
ret = fstat(fd, sb) == 0;
} else {
/* Make path relative to the new root, if any. */
if (runchroot != NULL) {
const int len =
@@ -109,7 +111,16 @@ do_stat(int fd, const char *path, const char *runchroot, struct stat *sb)
}
path = pathbuf;
}
debug_return_bool(stat(path, sb) == 0);
ret = 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_normal(sudoers_cmnd, sudoers_args, runchroot, digests);
rc = command_matches_glob(sudoers_cmnd, sudoers_args, runchroot,
intercepted, digests);
}
} else {
rc = command_matches_normal(sudoers_cmnd, sudoers_args, runchroot,
intercepted, digests);
}
done:
if (saved_user_cmnd != NULL) {

View File

@@ -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)

View File

@@ -302,6 +302,7 @@ struct cmnd_info {
struct stat cmnd_stat;
char *cmnd_path;
int status;
bool intercepted;
};
/*

View File

@@ -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)