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:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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
@@ -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 ");
|
||||
|
Reference in New Issue
Block a user