Add "list" pseudo-command to allow a user to list another user's

privs.  Previously, only root or a user with the ability to run any
command as either root or the target user on the current host could
use the -U option.  For "sudo -l [-U otheruser] command", NewArgv[0]
is now set to "list" (just like "sudo -l") and the actual command
to be checked starts with NewArgv[1].
This commit is contained in:
Todd C. Miller
2022-12-11 13:46:00 -07:00
parent 8c16c8faf6
commit a514a6eed5
9 changed files with 2577 additions and 2418 deletions

View File

@@ -25,7 +25,7 @@
.nr BA @BAMAN@
.nr LC @LCMAN@
.nr PS @PSMAN@
.TH "SUDOERS" "@mansectform@" "October 20, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.TH "SUDOERS" "@mansectform@" "December 9, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual"
.nh
.if n .ad l
.SH "NAME"
@@ -1025,9 +1025,12 @@ Edit_Spec ::= "sudoedit" file name+ |
"sudoedit" regex |
"sudoedit"
List_Spec ::= "list"
Cmnd ::= Digest_List? '!'* command |
'!'* directory |
'!'* Edit_Spec |
'!'* List_Spec |
'!'* Cmnd_Alias
.RE
.fi
@@ -1096,9 +1099,43 @@ character from being interpreted as a regular expression, the
must be escaped with a
\(oq\e\(cq.
.PP
The built-in command
There are two commands built into
\fBsudo\fR
itself:
\(lqlist\(rq
and
\(lqsudoedit\(rq.
Unlike other commands, these two must be specified in the
\fIsudoers\fR
file
\fIwithout\fR
a leading path.
.PP
The
\(lqlist\(rq
built-in can be used to permit a user to list another user's privileges with
\fBsudo\fR's
\fB\-U\fR
option.
For example,
\(lqsudo -l -U otheruser\(rq.
A user with the
\(lqlist\(rq
privilege is able to list another user's privileges even if they
don't have permission to run commands as that user.
By default, only root or a user with the ability to run any command as
either root or the specified
\fIuser\fR
on the current host may use the
\fB\-U\fR
option.
No command line arguments may be specified with the
\(lqlist\(rq
built-in.
.PP
The
\(lqsudoedit\(rq
is used to permit a user to run
built-in is used to permit a user to run
\fBsudo\fR
with the
\fB\-e\fR

View File

@@ -25,7 +25,7 @@
.nr BA @BAMAN@
.nr LC @LCMAN@
.nr PS @PSMAN@
.Dd October 20, 2022
.Dd December 9, 2022
.Dt SUDOERS @mansectform@
.Os Sudo @PACKAGE_VERSION@
.Sh NAME
@@ -982,9 +982,12 @@ Edit_Spec ::= "sudoedit" file name+ |
"sudoedit" regex |
"sudoedit"
List_Spec ::= "list"
Cmnd ::= Digest_List? '!'* command |
'!'* directory |
'!'* Edit_Spec |
'!'* List_Spec |
'!'* Cmnd_Alias
.Ed
.Pp
@@ -1052,9 +1055,43 @@ character from being interpreted as a regular expression, the
must be escaped with a
.Ql \e .
.Pp
The built-in command
There are two commands built into
.Nm sudo
itself:
.Dq list
and
.Dq sudoedit .
Unlike other commands, these two must be specified in the
.Em sudoers
file
.Em without
a leading path.
.Pp
The
.Dq list
built-in can be used to permit a user to list another user's privileges with
.Nm sudo Ns 's
.Fl U
option.
For example,
.Dq sudo -l -U otheruser .
A user with the
.Dq list
privilege is able to list another user's privileges even if they
don't have permission to run commands as that user.
By default, only root or a user with the ability to run any command as
either root or the specified
.Ar user
on the current host may use the
.Fl U
option.
No command line arguments may be specified with the
.Dq list
built-in.
.Pp
The
.Dq sudoedit
is used to permit a user to run
built-in is used to permit a user to run
.Nm sudo
with the
.Fl e

View File

@@ -348,12 +348,16 @@ log_failure(int status, int flags)
debug_decl(log_failure, SUDOERS_DEBUG_LOGGING);
/* The user doesn't always get to see the log message (path info). */
if (!ISSET(status, FLAG_NO_USER | FLAG_NO_HOST) && def_path_info &&
(flags == NOT_FOUND_DOT || flags == NOT_FOUND))
if (!ISSET(status, FLAG_NO_USER | FLAG_NO_HOST) && list_pw == NULL &&
def_path_info && (flags == NOT_FOUND_DOT || flags == NOT_FOUND))
inform_user = false;
ret = log_denial(status, inform_user);
if (!inform_user) {
const char *cmnd = user_cmnd;
if (ISSET(sudo_mode, MODE_CHECK))
cmnd = list_cmnd ? list_cmnd : NewArgv[1];
/*
* We'd like to not leak path info at all here, but that can
* *really* confuse the users. To really close the leak we'd
@@ -362,9 +366,9 @@ log_failure(int status, int flags)
* their path to just contain a single dir.
*/
if (flags == NOT_FOUND)
sudo_warnx(U_("%s: command not found"), user_cmnd);
sudo_warnx(U_("%s: command not found"), cmnd);
else if (flags == NOT_FOUND_DOT)
sudo_warnx(U_("ignoring \"%s\" found in '.'\nUse \"sudo ./%s\" if this is the \"%s\" you wish to run."), user_cmnd, user_cmnd, user_cmnd);
sudo_warnx(U_("ignoring \"%s\" found in '.'\nUse \"sudo ./%s\" if this is the \"%s\" you wish to run."), cmnd, cmnd, cmnd);
}
debug_return_bool(ret);

View File

@@ -844,16 +844,18 @@ command_matches(const char *sudoers_cmnd, const char *sudoers_args,
/* Check for pseudo-commands */
if (sudoers_cmnd[0] != '/') {
/*
* Return true if both sudoers_cmnd and user_cmnd are "sudoedit" AND
* Return true if sudoers_cmnd and user_cmnd match a pseudo-command AND
* a) there are no args in sudoers OR
* b) there are no args on command line and none req by sudoers OR
* c) there are args in sudoers and on command line and they match
*/
if (strcmp(sudoers_cmnd, "sudoedit") == 0 &&
strcmp(user_cmnd, "sudoedit") == 0 &&
command_args_match(sudoers_cmnd, sudoers_args)) {
/* No need to set safe_cmnd since user_cmnd matches sudoers_cmnd */
rc = true;
if (strcmp(sudoers_cmnd, "list") == 0 ||
strcmp(sudoers_cmnd, "sudoedit") == 0) {
if (strcmp(user_cmnd, sudoers_cmnd) == 0 &&
command_args_match(sudoers_cmnd, sudoers_args)) {
/* No need to set safe_cmnd since user_cmnd == sudoers_cmnd */
rc = true;
}
}
goto done;
}

View File

@@ -60,6 +60,7 @@ static int
sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw,
int validated, int pwflag)
{
char *saved_runchroot;
struct passwd *root_pw = NULL;
struct sudo_nss *nss;
struct cmndspec *cs;
@@ -82,6 +83,11 @@ sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw,
} else {
SET(validated, FLAG_NO_CHECK);
}
/* Don't use chroot setting for pseudo-commands. */
saved_runchroot = def_runchroot;
def_runchroot = NULL;
TAILQ_FOREACH(nss, snl, entries) {
if (nss->query(nss, pw) == -1) {
/* The query function should have printed an error message. */
@@ -111,19 +117,34 @@ sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw,
if (match == ALLOW)
continue;
/* Only check runas/command when listing another user. */
/*
* Root can list any user's privileges.
* A user may always list their own privileges.
*/
if (user_uid == 0 || list_pw == NULL ||
user_uid == list_pw->pw_uid) {
match = ALLOW;
continue;
}
/* Runas user must match list user or root. */
if (runas_matches_pw(nss->parse_tree, cs, list_pw) == DENY)
/*
* To list another user's prilileges, the runas
* user must match the list user or root.
*/
switch (runas_matches_pw(nss->parse_tree, cs, list_pw)) {
case DENY:
continue;
if (root_pw == NULL || runas_matches_pw(nss->parse_tree,
cs, root_pw) != ALLOW) {
case ALLOW:
break;
default:
if (root_pw != NULL && runas_matches_pw(nss->parse_tree,
cs, root_pw) == ALLOW) {
break;
}
continue;
}
/* Match command: "list" or ALL. */
if (cmnd_matches(nss->parse_tree, cs->cmnd, cs->runchroot,
NULL) == ALLOW) {
match = ALLOW;
@@ -143,6 +164,10 @@ sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw,
SET(validated, FLAG_CHECK_USER);
else if (nopass == true)
def_authenticate = false;
/* Restore original def_runchroot. */
def_runchroot = saved_runchroot;
debug_return_int(validated);
}
@@ -963,12 +988,22 @@ static int
display_cmnd_check(struct sudoers_parse_tree *parse_tree, struct passwd *pw,
time_t now)
{
int host_match, runas_match, cmnd_match;
int host_match, runas_match, cmnd_match = UNSPEC;
char *saved_user_cmnd, *saved_user_base;
struct cmndspec *cs;
struct privilege *priv;
struct userspec *us;
debug_decl(display_cmnd_check, SUDOERS_DEBUG_PARSER);
/*
* For "sudo -l command", user_cmnd is "list" and the actual
* command we are checking is in list_cmnd.
*/
saved_user_cmnd = user_cmnd;
saved_user_base = user_base;
user_cmnd = list_cmnd;
user_base = sudo_basename(user_cmnd);
TAILQ_FOREACH_REVERSE(us, &parse_tree->userspecs, userspec_list, entries) {
if (userlist_matches(parse_tree, pw, &us->users) != ALLOW)
continue;
@@ -991,12 +1026,15 @@ display_cmnd_check(struct sudoers_parse_tree *parse_tree, struct passwd *pw,
cmnd_match = cmnd_matches(parse_tree, cs->cmnd,
cs->runchroot, NULL);
if (cmnd_match != UNSPEC)
debug_return_int(cmnd_match);
goto done;
}
}
}
}
debug_return_int(UNSPEC);
done:
user_cmnd = saved_user_cmnd;
user_base = saved_user_base;
debug_return_int(cmnd_match);
}
/*
@@ -1029,8 +1067,8 @@ display_cmnd(struct sudo_nss_list *snl, struct passwd *pw)
break;
}
if (match == ALLOW) {
const int len = sudo_printf(SUDO_CONV_INFO_MSG, "%s%s%s\n",
safe_cmnd, user_args ? " " : "", user_args ? user_args : "");
/* For "sudo -l cmd" user_args includes the command being checked. */
const int len = sudo_printf(SUDO_CONV_INFO_MSG, "%s\n", user_args);
ret = len < 0 ? -1 : true;
}
debug_return_int(ret);

View File

@@ -442,14 +442,21 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
sudoers_gc_remove(GC_PTR, NewArgv);
free(NewArgv);
}
NewArgc = argc;
NewArgv = reallocarray(NULL, NewArgc + 2, sizeof(char *));
NewArgv = reallocarray(NULL, argc + 2, sizeof(char *));
if (NewArgv == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto done;
}
sudoers_gc_add(GC_PTR, NewArgv);
memcpy(NewArgv, argv, argc * sizeof(char *));
if (ISSET(sudo_mode, MODE_CHECK)) {
/* For "sudo -l [-U otheruser] command" */
NewArgv[0] = (char *)"list";
memcpy(NewArgv + 1, argv, argc * sizeof(char *));
NewArgc = argc + 1;
} else {
memcpy(NewArgv, argv, argc * sizeof(char *));
NewArgc = argc;
}
NewArgv[NewArgc] = NULL;
if (ISSET(sudo_mode, MODE_LOGIN_SHELL) && runas_pw != NULL) {
NewArgv[0] = strdup(runas_pw->pw_shell);
@@ -646,8 +653,8 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
} else if (cmnd_status == NOT_FOUND) {
if (ISSET(sudo_mode, MODE_CHECK)) {
audit_failure(NewArgv, N_("%s: command not found"),
NewArgv[0]);
sudo_warnx(U_("%s: command not found"), NewArgv[0]);
NewArgv[1]);
sudo_warnx(U_("%s: command not found"), NewArgv[1]);
} else {
audit_failure(NewArgv, N_("%s: command not found"),
user_cmnd);
@@ -970,17 +977,21 @@ init_vars(char * const envp[])
int
set_cmnd_path(const char *runchroot)
{
const char *cmnd_in;
char *cmnd_out = NULL;
char *path = user_path;
int ret;
debug_decl(set_cmnd_path, SUDOERS_DEBUG_PLUGIN);
cmnd_in = ISSET(sudo_mode, MODE_CHECK) ? NewArgv[1] : NewArgv[0];
free(user_cmnd);
user_cmnd = NULL;
if (def_secure_path && !user_is_exempt())
path = def_secure_path;
if (!set_perms(PERM_RUNAS))
debug_return_int(NOT_FOUND_ERROR);
ret = find_path(NewArgv[0], &user_cmnd, user_stat, path,
ret = find_path(cmnd_in, &cmnd_out, user_stat, path,
runchroot, def_ignore_dot, NULL);
if (!restore_perms())
debug_return_int(NOT_FOUND_ERROR);
@@ -988,12 +999,17 @@ set_cmnd_path(const char *runchroot)
/* Failed as root, try as invoking user. */
if (!set_perms(PERM_USER))
debug_return_int(false);
ret = find_path(NewArgv[0], &user_cmnd, user_stat, path,
ret = find_path(cmnd_in, &cmnd_out, user_stat, path,
runchroot, def_ignore_dot, NULL);
if (!restore_perms())
debug_return_int(NOT_FOUND_ERROR);
}
if (ISSET(sudo_mode, MODE_CHECK))
list_cmnd = cmnd_out;
else
user_cmnd = cmnd_out;
debug_return_int(ret);
}
@@ -1858,6 +1874,7 @@ sudo_user_free(void)
free(user_runhost);
free(user_cmnd);
free(user_args);
free(list_cmnd);
free(safe_cmnd);
free(saved_cmnd);
free(user_stat);

View File

@@ -102,6 +102,7 @@ struct sudo_user {
char *cmnd;
char *cmnd_args;
char *cmnd_base;
char *cmnd_list;
char *cmnd_safe;
char *cmnd_saved;
char *class_name;
@@ -248,6 +249,7 @@ struct sudo_user {
#define user_runhost (sudo_user.runhost)
#define user_srunhost (sudo_user.srunhost)
#define user_ccname (sudo_user.krb5_ccname)
#define list_cmnd (sudo_user.cmnd_list)
#define safe_cmnd (sudo_user.cmnd_safe)
#define saved_cmnd (sudo_user.cmnd_saved)
#define cmnd_fd (sudo_user.execfd)

File diff suppressed because it is too large Load Diff

View File

@@ -750,6 +750,14 @@ sha512 {
return SHA512_TOK;
}
list {
/* No command line args. */
LEXTRACE("COMMAND ");
if (!fill_cmnd(sudoerstext, sudoersleng))
yyterminate();
return COMMAND;
} /* sudo -l -U otheruser */
sudoedit {
BEGIN GOTCMND;
LEXTRACE("COMMAND ");