512 lines
15 KiB
C
512 lines
15 KiB
C
/*
|
|
* SPDX-License-Identifier: ISC
|
|
*
|
|
* Copyright (c) 2004-2005, 2007-2023 Todd C. Miller <Todd.Miller@sudo.ws>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
|
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <pwd.h>
|
|
|
|
#include "sudoers.h"
|
|
#include <gram.h>
|
|
|
|
static int
|
|
runas_matches_pw(struct sudoers_parse_tree *parse_tree,
|
|
const struct cmndspec *cs, const struct passwd *pw)
|
|
{
|
|
debug_decl(runas_matches_pw, SUDOERS_DEBUG_PARSER);
|
|
|
|
if (cs->runasuserlist != NULL)
|
|
debug_return_int(userlist_matches(parse_tree, pw, cs->runasuserlist));
|
|
|
|
if (cs->runasgrouplist == NULL) {
|
|
/* No explicit runas user or group, use default. */
|
|
if (userpw_matches(def_runas_default, pw->pw_name, pw))
|
|
debug_return_int(ALLOW);
|
|
}
|
|
debug_return_int(UNSPEC);
|
|
}
|
|
|
|
/*
|
|
* Look up the user in the sudoers parse tree for pseudo-commands like
|
|
* list, verify and kill.
|
|
*/
|
|
static int
|
|
sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw, int pwflag)
|
|
{
|
|
char *saved_runchroot;
|
|
struct passwd *root_pw = NULL;
|
|
struct sudo_nss *nss;
|
|
struct cmndspec *cs;
|
|
struct privilege *priv;
|
|
struct userspec *us;
|
|
struct defaults *def;
|
|
int cmnd_match, nopass, match = DENY;
|
|
int validated = 0;
|
|
enum def_tuple pwcheck;
|
|
debug_decl(sudoers_lookup_pseudo, SUDOERS_DEBUG_PARSER);
|
|
|
|
pwcheck = (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple;
|
|
nopass = (pwcheck == never || pwcheck == all) ? true : false;
|
|
|
|
if (list_pw != NULL) {
|
|
root_pw = sudo_getpwuid(ROOT_UID);
|
|
if (root_pw == NULL)
|
|
log_warningx(SLOG_SEND_MAIL, N_("unknown uid %u"), ROOT_UID);
|
|
} 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. */
|
|
SET(validated, VALIDATE_ERROR);
|
|
break;
|
|
}
|
|
TAILQ_FOREACH(us, &nss->parse_tree->userspecs, entries) {
|
|
if (userlist_matches(nss->parse_tree, pw, &us->users) != ALLOW)
|
|
continue;
|
|
TAILQ_FOREACH(priv, &us->privileges, entries) {
|
|
int priv_nopass = UNSPEC;
|
|
|
|
if (hostlist_matches(nss->parse_tree, pw, &priv->hostlist) != ALLOW)
|
|
continue;
|
|
TAILQ_FOREACH(def, &priv->defaults, entries) {
|
|
if (strcmp(def->var, "authenticate") == 0)
|
|
priv_nopass = !def->op;
|
|
}
|
|
TAILQ_FOREACH(cs, &priv->cmndlist, entries) {
|
|
if (pwcheck == any) {
|
|
if (cs->tags.nopasswd == true || priv_nopass == true)
|
|
nopass = true;
|
|
} else if (pwcheck == all) {
|
|
if (cs->tags.nopasswd != true && priv_nopass != true)
|
|
nopass = false;
|
|
}
|
|
if (match == ALLOW)
|
|
continue;
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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:
|
|
break;
|
|
case ALLOW:
|
|
/*
|
|
* RunAs user matches list user.
|
|
* Match on command "list" or ALL.
|
|
*/
|
|
cmnd_match = cmnd_matches(nss->parse_tree,
|
|
cs->cmnd, cs->runchroot, NULL);
|
|
if (cmnd_match != UNSPEC) {
|
|
match = cmnd_match;
|
|
goto done;
|
|
}
|
|
break;
|
|
default:
|
|
/*
|
|
* RunAs user doesn't match list user. Only allow
|
|
* listing if the user has "sudo ALL" for root.
|
|
*/
|
|
if (root_pw != NULL && runas_matches_pw(nss->parse_tree,
|
|
cs, root_pw) == ALLOW) {
|
|
cmnd_match = cmnd_matches_all(nss->parse_tree,
|
|
cs->cmnd, cs->runchroot, NULL);
|
|
if (cmnd_match != UNSPEC) {
|
|
match = cmnd_match;
|
|
goto done;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
done:
|
|
if (root_pw != NULL)
|
|
sudo_pw_delref(root_pw);
|
|
if (match == ALLOW || user_uid == 0) {
|
|
/* User has an entry for this host. */
|
|
SET(validated, VALIDATE_SUCCESS);
|
|
} else if (match == DENY)
|
|
SET(validated, VALIDATE_FAILURE);
|
|
if (pwcheck == always && def_authenticate)
|
|
SET(validated, FLAG_CHECK_USER);
|
|
else if (nopass == true)
|
|
def_authenticate = false;
|
|
|
|
/* Restore original def_runchroot. */
|
|
def_runchroot = saved_runchroot;
|
|
|
|
debug_return_int(validated);
|
|
}
|
|
|
|
static void
|
|
init_cmnd_info(struct cmnd_info *info)
|
|
{
|
|
memset(info, 0, sizeof(*info));
|
|
if (def_intercept || ISSET(sudo_mode, MODE_POLICY_INTERCEPTED))
|
|
info->intercepted = true;
|
|
}
|
|
|
|
static int
|
|
sudoers_lookup_check(struct sudo_nss *nss, struct passwd *pw,
|
|
int *validated, struct cmnd_info *info, struct cmndspec **matching_cs,
|
|
struct defaults_list **defs, time_t now)
|
|
{
|
|
int host_match, runas_match, cmnd_match;
|
|
struct cmndspec *cs;
|
|
struct privilege *priv;
|
|
struct userspec *us;
|
|
struct member *matching_user;
|
|
debug_decl(sudoers_lookup_check, SUDOERS_DEBUG_PARSER);
|
|
|
|
init_cmnd_info(info);
|
|
|
|
TAILQ_FOREACH_REVERSE(us, &nss->parse_tree->userspecs, userspec_list, entries) {
|
|
if (userlist_matches(nss->parse_tree, pw, &us->users) != ALLOW)
|
|
continue;
|
|
CLR(*validated, FLAG_NO_USER);
|
|
TAILQ_FOREACH_REVERSE(priv, &us->privileges, privilege_list, entries) {
|
|
host_match = hostlist_matches(nss->parse_tree, pw, &priv->hostlist);
|
|
if (host_match == ALLOW)
|
|
CLR(*validated, FLAG_NO_HOST);
|
|
else
|
|
continue;
|
|
TAILQ_FOREACH_REVERSE(cs, &priv->cmndlist, cmndspec_list, entries) {
|
|
if (cs->notbefore != UNSPEC) {
|
|
if (now < cs->notbefore)
|
|
continue;
|
|
}
|
|
if (cs->notafter != UNSPEC) {
|
|
if (now > cs->notafter)
|
|
continue;
|
|
}
|
|
matching_user = NULL;
|
|
runas_match = runaslist_matches(nss->parse_tree,
|
|
cs->runasuserlist, cs->runasgrouplist, &matching_user,
|
|
NULL);
|
|
if (runas_match == ALLOW) {
|
|
cmnd_match = cmnd_matches(nss->parse_tree, cs->cmnd,
|
|
cs->runchroot, info);
|
|
if (cmnd_match != UNSPEC) {
|
|
/*
|
|
* If user is running command as himself,
|
|
* set runas_pw = sudo_user.pw.
|
|
* XXX - hack, want more general solution
|
|
*/
|
|
if (matching_user && matching_user->type == MYSELF) {
|
|
sudo_pw_delref(runas_pw);
|
|
sudo_pw_addref(sudo_user.pw);
|
|
runas_pw = sudo_user.pw;
|
|
}
|
|
*matching_cs = cs;
|
|
*defs = &priv->defaults;
|
|
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
|
|
"userspec matched @ %s:%d:%d: %s",
|
|
us->file ? us->file : "???", us->line, us->column,
|
|
cmnd_match ? "allowed" : "denied");
|
|
debug_return_int(cmnd_match);
|
|
}
|
|
free(info->cmnd_path);
|
|
init_cmnd_info(info);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
debug_return_int(UNSPEC);
|
|
}
|
|
|
|
/*
|
|
* Apply cmndspec-specific settings including SELinux role/type,
|
|
* Solaris privs, and command tags.
|
|
*/
|
|
static bool
|
|
apply_cmndspec(struct cmndspec *cs)
|
|
{
|
|
debug_decl(apply_cmndspec, SUDOERS_DEBUG_PARSER);
|
|
|
|
if (cs != NULL) {
|
|
#ifdef HAVE_SELINUX
|
|
/* Set role and type if not specified on command line. */
|
|
if (user_role == NULL) {
|
|
if (cs->role != NULL) {
|
|
user_role = strdup(cs->role);
|
|
if (user_role == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
user_role = def_role;
|
|
def_role = NULL;
|
|
}
|
|
if (user_role != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"user_role -> %s", user_role);
|
|
}
|
|
}
|
|
if (user_type == NULL) {
|
|
if (cs->type != NULL) {
|
|
user_type = strdup(cs->type);
|
|
if (user_type == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
user_type = def_type;
|
|
def_type = NULL;
|
|
}
|
|
if (user_type != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"user_type -> %s", user_type);
|
|
}
|
|
}
|
|
#endif /* HAVE_SELINUX */
|
|
#ifdef HAVE_APPARMOR
|
|
/* Set AppArmor profile, if specified */
|
|
if (cs->apparmor_profile != NULL) {
|
|
user_apparmor_profile = strdup(cs->apparmor_profile);
|
|
if (user_apparmor_profile == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
user_apparmor_profile = def_apparmor_profile;
|
|
def_apparmor_profile = NULL;
|
|
}
|
|
if (user_apparmor_profile != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"user_apparmor_profile -> %s", user_apparmor_profile);
|
|
}
|
|
#endif
|
|
#ifdef HAVE_PRIV_SET
|
|
/* Set Solaris privilege sets */
|
|
if (runas_privs == NULL) {
|
|
if (cs->privs != NULL) {
|
|
runas_privs = strdup(cs->privs);
|
|
if (runas_privs == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
runas_privs = def_privs;
|
|
def_privs = NULL;
|
|
}
|
|
if (runas_privs != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"runas_privs -> %s", runas_privs);
|
|
}
|
|
}
|
|
if (runas_limitprivs == NULL) {
|
|
if (cs->limitprivs != NULL) {
|
|
runas_limitprivs = strdup(cs->limitprivs);
|
|
if (runas_limitprivs == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
} else {
|
|
runas_limitprivs = def_limitprivs;
|
|
def_limitprivs = NULL;
|
|
}
|
|
if (runas_limitprivs != NULL) {
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"runas_limitprivs -> %s", runas_limitprivs);
|
|
}
|
|
}
|
|
#endif /* HAVE_PRIV_SET */
|
|
if (cs->timeout > 0) {
|
|
def_command_timeout = cs->timeout;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_command_timeout -> %d", def_command_timeout);
|
|
}
|
|
if (cs->runcwd != NULL) {
|
|
free(def_runcwd);
|
|
def_runcwd = strdup(cs->runcwd);
|
|
if (def_runcwd == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_runcwd -> %s", def_runcwd);
|
|
}
|
|
if (cs->runchroot != NULL) {
|
|
free(def_runchroot);
|
|
def_runchroot = strdup(cs->runchroot);
|
|
if (def_runchroot == NULL) {
|
|
sudo_warnx(U_("%s: %s"), __func__,
|
|
U_("unable to allocate memory"));
|
|
debug_return_bool(false);
|
|
}
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_runchroot -> %s", def_runchroot);
|
|
}
|
|
if (cs->tags.nopasswd != UNSPEC) {
|
|
def_authenticate = !cs->tags.nopasswd;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_authenticate -> %s", def_authenticate ? "true" : "false");
|
|
}
|
|
if (cs->tags.noexec != UNSPEC) {
|
|
def_noexec = cs->tags.noexec;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_noexec -> %s", def_noexec ? "true" : "false");
|
|
}
|
|
if (cs->tags.intercept != UNSPEC) {
|
|
def_intercept = cs->tags.intercept;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_intercept -> %s", def_intercept ? "true" : "false");
|
|
}
|
|
if (cs->tags.setenv != UNSPEC) {
|
|
def_setenv = cs->tags.setenv;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_setenv -> %s", def_setenv ? "true" : "false");
|
|
}
|
|
if (cs->tags.log_input != UNSPEC) {
|
|
def_log_input = cs->tags.log_input;
|
|
cb_log_input(NULL, 0, 0, NULL, cs->tags.log_input);
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_log_input -> %s", def_log_input ? "true" : "false");
|
|
}
|
|
if (cs->tags.log_output != UNSPEC) {
|
|
def_log_output = cs->tags.log_output;
|
|
cb_log_output(NULL, 0, 0, NULL, cs->tags.log_output);
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_log_output -> %s", def_log_output ? "true" : "false");
|
|
}
|
|
if (cs->tags.send_mail != UNSPEC) {
|
|
if (cs->tags.send_mail) {
|
|
def_mail_all_cmnds = true;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_mail_all_cmnds -> true");
|
|
} else {
|
|
def_mail_all_cmnds = false;
|
|
def_mail_always = false;
|
|
def_mail_no_perms = false;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_mail_all_cmnds -> false, def_mail_always -> false, "
|
|
"def_mail_no_perms -> false");
|
|
}
|
|
}
|
|
if (cs->tags.follow != UNSPEC) {
|
|
def_sudoedit_follow = cs->tags.follow;
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"def_sudoedit_follow -> %s", def_sudoedit_follow ? "true" : "false");
|
|
}
|
|
}
|
|
|
|
debug_return_bool(true);
|
|
}
|
|
|
|
/*
|
|
* Look up the user in the sudoers parse tree and check to see if they are
|
|
* allowed to run the specified command on this host as the target user.
|
|
*/
|
|
int
|
|
sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int *cmnd_status,
|
|
int pwflag)
|
|
{
|
|
struct defaults_list *defs = NULL;
|
|
struct sudoers_parse_tree *parse_tree = NULL;
|
|
struct cmndspec *cs = NULL;
|
|
struct sudo_nss *nss;
|
|
struct cmnd_info info;
|
|
int validated = FLAG_NO_USER | FLAG_NO_HOST;
|
|
int m, match = UNSPEC;
|
|
time_t now;
|
|
debug_decl(sudoers_lookup, SUDOERS_DEBUG_PARSER);
|
|
|
|
/*
|
|
* Special case checking the "validate", "list" and "kill" pseudo-commands.
|
|
*/
|
|
if (pwflag)
|
|
debug_return_int(sudoers_lookup_pseudo(snl, pw, pwflag));
|
|
|
|
/* Need to be runas user while stat'ing things. */
|
|
if (!set_perms(PERM_RUNAS))
|
|
debug_return_int(validated);
|
|
|
|
/* Query each sudoers source and check the user. */
|
|
time(&now);
|
|
TAILQ_FOREACH(nss, snl, entries) {
|
|
if (nss->query(nss, pw) == -1) {
|
|
/* The query function should have printed an error message. */
|
|
SET(validated, VALIDATE_ERROR);
|
|
break;
|
|
}
|
|
|
|
m = sudoers_lookup_check(nss, pw, &validated, &info, &cs, &defs, now);
|
|
if (m != UNSPEC) {
|
|
match = m;
|
|
parse_tree = nss->parse_tree;
|
|
}
|
|
|
|
if (!sudo_nss_can_continue(nss, m))
|
|
break;
|
|
}
|
|
if (match != UNSPEC) {
|
|
if (info.cmnd_path != NULL) {
|
|
/* Update user_cmnd, user_stat, cmnd_status from matching entry. */
|
|
free(user_cmnd);
|
|
user_cmnd = info.cmnd_path;
|
|
if (user_stat != NULL)
|
|
*user_stat = info.cmnd_stat;
|
|
*cmnd_status = info.status;
|
|
}
|
|
if (defs != NULL)
|
|
(void)update_defaults(parse_tree, defs, SETDEF_GENERIC, false);
|
|
if (!apply_cmndspec(cs))
|
|
SET(validated, VALIDATE_ERROR);
|
|
else if (match == ALLOW)
|
|
SET(validated, VALIDATE_SUCCESS);
|
|
else
|
|
SET(validated, VALIDATE_FAILURE);
|
|
}
|
|
if (!restore_perms())
|
|
SET(validated, VALIDATE_ERROR);
|
|
debug_return_int(validated);
|
|
}
|