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 BA @BAMAN@
.nr LC @LCMAN@ .nr LC @LCMAN@
.nr PS @PSMAN@ .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 .nh
.if n .ad l .if n .ad l
.SH "NAME" .SH "NAME"
@@ -1025,9 +1025,12 @@ Edit_Spec ::= "sudoedit" file name+ |
"sudoedit" regex | "sudoedit" regex |
"sudoedit" "sudoedit"
List_Spec ::= "list"
Cmnd ::= Digest_List? '!'* command | Cmnd ::= Digest_List? '!'* command |
'!'* directory | '!'* directory |
'!'* Edit_Spec | '!'* Edit_Spec |
'!'* List_Spec |
'!'* Cmnd_Alias '!'* Cmnd_Alias
.RE .RE
.fi .fi
@@ -1096,9 +1099,43 @@ character from being interpreted as a regular expression, the
must be escaped with a must be escaped with a
\(oq\e\(cq. \(oq\e\(cq.
.PP .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 \(lqsudoedit\(rq
is used to permit a user to run built-in is used to permit a user to run
\fBsudo\fR \fBsudo\fR
with the with the
\fB\-e\fR \fB\-e\fR

View File

@@ -25,7 +25,7 @@
.nr BA @BAMAN@ .nr BA @BAMAN@
.nr LC @LCMAN@ .nr LC @LCMAN@
.nr PS @PSMAN@ .nr PS @PSMAN@
.Dd October 20, 2022 .Dd December 9, 2022
.Dt SUDOERS @mansectform@ .Dt SUDOERS @mansectform@
.Os Sudo @PACKAGE_VERSION@ .Os Sudo @PACKAGE_VERSION@
.Sh NAME .Sh NAME
@@ -982,9 +982,12 @@ Edit_Spec ::= "sudoedit" file name+ |
"sudoedit" regex | "sudoedit" regex |
"sudoedit" "sudoedit"
List_Spec ::= "list"
Cmnd ::= Digest_List? '!'* command | Cmnd ::= Digest_List? '!'* command |
'!'* directory | '!'* directory |
'!'* Edit_Spec | '!'* Edit_Spec |
'!'* List_Spec |
'!'* Cmnd_Alias '!'* Cmnd_Alias
.Ed .Ed
.Pp .Pp
@@ -1052,9 +1055,43 @@ character from being interpreted as a regular expression, the
must be escaped with a must be escaped with a
.Ql \e . .Ql \e .
.Pp .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 .Dq sudoedit
is used to permit a user to run built-in is used to permit a user to run
.Nm sudo .Nm sudo
with the with the
.Fl e .Fl e

View File

@@ -348,12 +348,16 @@ log_failure(int status, int flags)
debug_decl(log_failure, SUDOERS_DEBUG_LOGGING); debug_decl(log_failure, SUDOERS_DEBUG_LOGGING);
/* The user doesn't always get to see the log message (path info). */ /* 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 && if (!ISSET(status, FLAG_NO_USER | FLAG_NO_HOST) && list_pw == NULL &&
(flags == NOT_FOUND_DOT || flags == NOT_FOUND)) def_path_info && (flags == NOT_FOUND_DOT || flags == NOT_FOUND))
inform_user = false; inform_user = false;
ret = log_denial(status, inform_user); ret = log_denial(status, inform_user);
if (!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 * 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 * *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. * their path to just contain a single dir.
*/ */
if (flags == NOT_FOUND) 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) 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); debug_return_bool(ret);

View File

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

View File

@@ -60,6 +60,7 @@ static int
sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw, sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw,
int validated, int pwflag) int validated, int pwflag)
{ {
char *saved_runchroot;
struct passwd *root_pw = NULL; struct passwd *root_pw = NULL;
struct sudo_nss *nss; struct sudo_nss *nss;
struct cmndspec *cs; struct cmndspec *cs;
@@ -82,6 +83,11 @@ sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw,
} else { } else {
SET(validated, FLAG_NO_CHECK); 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) { TAILQ_FOREACH(nss, snl, entries) {
if (nss->query(nss, pw) == -1) { if (nss->query(nss, pw) == -1) {
/* The query function should have printed an error message. */ /* 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) if (match == ALLOW)
continue; 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 || if (user_uid == 0 || list_pw == NULL ||
user_uid == list_pw->pw_uid) { user_uid == list_pw->pw_uid) {
match = ALLOW; match = ALLOW;
continue; 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; continue;
if (root_pw == NULL || runas_matches_pw(nss->parse_tree, case ALLOW:
cs, root_pw) != ALLOW) { break;
default:
if (root_pw != NULL && runas_matches_pw(nss->parse_tree,
cs, root_pw) == ALLOW) {
break;
}
continue; continue;
} }
/* Match command: "list" or ALL. */
if (cmnd_matches(nss->parse_tree, cs->cmnd, cs->runchroot, if (cmnd_matches(nss->parse_tree, cs->cmnd, cs->runchroot,
NULL) == ALLOW) { NULL) == ALLOW) {
match = ALLOW; match = ALLOW;
@@ -143,6 +164,10 @@ sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw,
SET(validated, FLAG_CHECK_USER); SET(validated, FLAG_CHECK_USER);
else if (nopass == true) else if (nopass == true)
def_authenticate = false; def_authenticate = false;
/* Restore original def_runchroot. */
def_runchroot = saved_runchroot;
debug_return_int(validated); debug_return_int(validated);
} }
@@ -963,12 +988,22 @@ static int
display_cmnd_check(struct sudoers_parse_tree *parse_tree, struct passwd *pw, display_cmnd_check(struct sudoers_parse_tree *parse_tree, struct passwd *pw,
time_t now) 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 cmndspec *cs;
struct privilege *priv; struct privilege *priv;
struct userspec *us; struct userspec *us;
debug_decl(display_cmnd_check, SUDOERS_DEBUG_PARSER); 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) { TAILQ_FOREACH_REVERSE(us, &parse_tree->userspecs, userspec_list, entries) {
if (userlist_matches(parse_tree, pw, &us->users) != ALLOW) if (userlist_matches(parse_tree, pw, &us->users) != ALLOW)
continue; 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, cmnd_match = cmnd_matches(parse_tree, cs->cmnd,
cs->runchroot, NULL); cs->runchroot, NULL);
if (cmnd_match != UNSPEC) 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; break;
} }
if (match == ALLOW) { if (match == ALLOW) {
const int len = sudo_printf(SUDO_CONV_INFO_MSG, "%s%s%s\n", /* For "sudo -l cmd" user_args includes the command being checked. */
safe_cmnd, user_args ? " " : "", user_args ? user_args : ""); const int len = sudo_printf(SUDO_CONV_INFO_MSG, "%s\n", user_args);
ret = len < 0 ? -1 : true; ret = len < 0 ? -1 : true;
} }
debug_return_int(ret); 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); sudoers_gc_remove(GC_PTR, NewArgv);
free(NewArgv); free(NewArgv);
} }
NewArgc = argc; NewArgv = reallocarray(NULL, argc + 2, sizeof(char *));
NewArgv = reallocarray(NULL, NewArgc + 2, sizeof(char *));
if (NewArgv == NULL) { if (NewArgv == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto done; goto done;
} }
sudoers_gc_add(GC_PTR, NewArgv); 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; NewArgv[NewArgc] = NULL;
if (ISSET(sudo_mode, MODE_LOGIN_SHELL) && runas_pw != NULL) { if (ISSET(sudo_mode, MODE_LOGIN_SHELL) && runas_pw != NULL) {
NewArgv[0] = strdup(runas_pw->pw_shell); 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) { } else if (cmnd_status == NOT_FOUND) {
if (ISSET(sudo_mode, MODE_CHECK)) { if (ISSET(sudo_mode, MODE_CHECK)) {
audit_failure(NewArgv, N_("%s: command not found"), audit_failure(NewArgv, N_("%s: command not found"),
NewArgv[0]); NewArgv[1]);
sudo_warnx(U_("%s: command not found"), NewArgv[0]); sudo_warnx(U_("%s: command not found"), NewArgv[1]);
} else { } else {
audit_failure(NewArgv, N_("%s: command not found"), audit_failure(NewArgv, N_("%s: command not found"),
user_cmnd); user_cmnd);
@@ -970,17 +977,21 @@ init_vars(char * const envp[])
int int
set_cmnd_path(const char *runchroot) set_cmnd_path(const char *runchroot)
{ {
const char *cmnd_in;
char *cmnd_out = NULL;
char *path = user_path; char *path = user_path;
int ret; int ret;
debug_decl(set_cmnd_path, SUDOERS_DEBUG_PLUGIN); debug_decl(set_cmnd_path, SUDOERS_DEBUG_PLUGIN);
cmnd_in = ISSET(sudo_mode, MODE_CHECK) ? NewArgv[1] : NewArgv[0];
free(user_cmnd); free(user_cmnd);
user_cmnd = NULL; user_cmnd = NULL;
if (def_secure_path && !user_is_exempt()) if (def_secure_path && !user_is_exempt())
path = def_secure_path; path = def_secure_path;
if (!set_perms(PERM_RUNAS)) if (!set_perms(PERM_RUNAS))
debug_return_int(NOT_FOUND_ERROR); 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); runchroot, def_ignore_dot, NULL);
if (!restore_perms()) if (!restore_perms())
debug_return_int(NOT_FOUND_ERROR); debug_return_int(NOT_FOUND_ERROR);
@@ -988,12 +999,17 @@ set_cmnd_path(const char *runchroot)
/* Failed as root, try as invoking user. */ /* Failed as root, try as invoking user. */
if (!set_perms(PERM_USER)) if (!set_perms(PERM_USER))
debug_return_int(false); 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); runchroot, def_ignore_dot, NULL);
if (!restore_perms()) if (!restore_perms())
debug_return_int(NOT_FOUND_ERROR); 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); debug_return_int(ret);
} }
@@ -1858,6 +1874,7 @@ sudo_user_free(void)
free(user_runhost); free(user_runhost);
free(user_cmnd); free(user_cmnd);
free(user_args); free(user_args);
free(list_cmnd);
free(safe_cmnd); free(safe_cmnd);
free(saved_cmnd); free(saved_cmnd);
free(user_stat); free(user_stat);

View File

@@ -102,6 +102,7 @@ struct sudo_user {
char *cmnd; char *cmnd;
char *cmnd_args; char *cmnd_args;
char *cmnd_base; char *cmnd_base;
char *cmnd_list;
char *cmnd_safe; char *cmnd_safe;
char *cmnd_saved; char *cmnd_saved;
char *class_name; char *class_name;
@@ -248,6 +249,7 @@ struct sudo_user {
#define user_runhost (sudo_user.runhost) #define user_runhost (sudo_user.runhost)
#define user_srunhost (sudo_user.srunhost) #define user_srunhost (sudo_user.srunhost)
#define user_ccname (sudo_user.krb5_ccname) #define user_ccname (sudo_user.krb5_ccname)
#define list_cmnd (sudo_user.cmnd_list)
#define safe_cmnd (sudo_user.cmnd_safe) #define safe_cmnd (sudo_user.cmnd_safe)
#define saved_cmnd (sudo_user.cmnd_saved) #define saved_cmnd (sudo_user.cmnd_saved)
#define cmnd_fd (sudo_user.execfd) #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; return SHA512_TOK;
} }
list {
/* No command line args. */
LEXTRACE("COMMAND ");
if (!fill_cmnd(sudoerstext, sudoersleng))
yyterminate();
return COMMAND;
} /* sudo -l -U otheruser */
sudoedit { sudoedit {
BEGIN GOTCMND; BEGIN GOTCMND;
LEXTRACE("COMMAND "); LEXTRACE("COMMAND ");