sudo/plugins/sudoers/lookup.c

566 lines
17 KiB
C

/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2004-2005, 2007-2024 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) == ALLOW)
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 unsigned int
sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct sudoers_context *ctx,
time_t now, sudoers_lookup_callback_fn_t callback, void *cb_data,
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 nopass, match = UNSPEC;
unsigned 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 (ctx->runas.list_pw != NULL) {
root_pw = sudo_getpwuid(ROOT_UID);
if (root_pw == NULL)
sudo_warnx(U_("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(ctx, nss, ctx->user.pw) == -1) {
/* The query function should have printed an error message. */
SET(validated, VALIDATE_ERROR);
break;
}
/*
* We have to traverse the policy forwards, not in reverse,
* to support the "pwcheck == all" case.
*/
TAILQ_FOREACH(us, &nss->parse_tree->userspecs, entries) {
const int user_match = userlist_matches(nss->parse_tree,
ctx->user.pw, &us->users);
if (user_match != ALLOW) {
if (callback != NULL && user_match == DENY) {
callback(nss->parse_tree, us, user_match, NULL, UNSPEC,
NULL, UNSPEC, UNSPEC, UNSPEC, cb_data);
}
continue;
}
TAILQ_FOREACH(priv, &us->privileges, entries) {
int priv_nopass = UNSPEC;
const int host_match = hostlist_matches(nss->parse_tree,
ctx->user.pw, &priv->hostlist);
if (host_match != ALLOW) {
if (callback != NULL) {
callback(nss->parse_tree, us, user_match, priv,
host_match, NULL, UNSPEC, UNSPEC, UNSPEC, cb_data);
}
continue;
}
TAILQ_FOREACH(def, &priv->defaults, entries) {
if (strcmp(def->var, "authenticate") == 0) {
priv_nopass = !def->op;
break;
}
}
TAILQ_FOREACH(cs, &priv->cmndlist, entries) {
int cmnd_match = UNSPEC;
int date_match = UNSPEC;
int runas_match = UNSPEC;
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 (cs->notbefore != UNSPEC) {
date_match = now < cs->notbefore ? DENY : ALLOW;
}
if (cs->notafter != UNSPEC) {
date_match = now > cs->notafter ? DENY : ALLOW;
}
/*
* Root can list any user's privileges.
* A user may always list their own privileges.
*/
if (ctx->user.uid == 0 || ctx->runas.list_pw == NULL ||
ctx->user.uid == ctx->runas.list_pw->pw_uid) {
cmnd_match = ALLOW;
runas_match = ALLOW;
} else if (date_match != DENY) {
/*
* To list another user's privileges, the runas
* user must match the list user or root.
*/
runas_match = runas_matches_pw(nss->parse_tree, cs,
ctx->runas.list_pw);
switch (runas_match) {
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);
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) {
runas_match = ALLOW;
cmnd_match = cmnd_matches_all(nss->parse_tree,
cs->cmnd, cs->runchroot, NULL);
}
break;
}
}
if (callback != NULL) {
callback(nss->parse_tree, us, user_match, priv,
host_match, cs, date_match, runas_match,
cmnd_match, cb_data);
}
if (SPECIFIED(cmnd_match)) {
/*
* We take the last match but must process
* the entire policy for pwcheck == all.
*/
match = cmnd_match;
}
}
}
}
if (!sudo_nss_can_continue(nss, match))
break;
}
if (root_pw != NULL)
sudo_pw_delref(root_pw);
if (match == ALLOW || ctx->user.uid == 0) {
/* User has an entry for this host. */
SET(validated, VALIDATE_SUCCESS);
} else {
/* No entry or user is not allowed to list other users. */
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_uint(validated);
}
static int
sudoers_lookup_check(struct sudo_nss *nss, struct sudoers_context *ctx,
unsigned int *validated, struct cmnd_info *info, time_t now,
sudoers_lookup_callback_fn_t callback, void *cb_data,
struct cmndspec **matching_cs, struct defaults_list **defs)
{
struct cmndspec *cs;
struct privilege *priv;
struct userspec *us;
struct member *matching_user;
debug_decl(sudoers_lookup_check, SUDOERS_DEBUG_PARSER);
memset(info, 0, sizeof(*info));
TAILQ_FOREACH_REVERSE(us, &nss->parse_tree->userspecs, userspec_list, entries) {
const int user_match = userlist_matches(nss->parse_tree, ctx->user.pw,
&us->users);
if (user_match != ALLOW) {
if (callback != NULL && user_match == DENY) {
callback(nss->parse_tree, us, user_match, NULL, UNSPEC, NULL,
UNSPEC, UNSPEC, UNSPEC, cb_data);
}
continue;
}
CLR(*validated, FLAG_NO_USER);
TAILQ_FOREACH_REVERSE(priv, &us->privileges, privilege_list, entries) {
const int host_match = hostlist_matches(nss->parse_tree,
ctx->user.pw, &priv->hostlist);
if (host_match == ALLOW) {
CLR(*validated, FLAG_NO_HOST);
} else {
if (callback != NULL) {
callback(nss->parse_tree, us, user_match, priv, host_match,
NULL, UNSPEC, UNSPEC, UNSPEC, cb_data);
}
continue;
}
TAILQ_FOREACH_REVERSE(cs, &priv->cmndlist, cmndspec_list, entries) {
int cmnd_match = UNSPEC;
int date_match = UNSPEC;
int runas_match = UNSPEC;
if (cs->notbefore != UNSPEC) {
date_match = now < cs->notbefore ? DENY : ALLOW;
}
if (cs->notafter != UNSPEC) {
date_match = now > cs->notafter ? DENY : ALLOW;
}
if (date_match != DENY) {
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 (callback != NULL) {
callback(nss->parse_tree, us, user_match, priv, host_match,
cs, date_match, runas_match, cmnd_match, cb_data);
}
if (SPECIFIED(cmnd_match)) {
/*
* If user is running command as themselves,
* set ctx->runas.pw = ctx->user.pw.
* XXX - hack, want more general solution
*/
if (matching_user && matching_user->type == MYSELF) {
sudo_pw_delref(ctx->runas.pw);
sudo_pw_addref(ctx->user.pw);
ctx->runas.pw = ctx->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);
memset(info, 0, sizeof(*info));
}
}
}
debug_return_int(UNSPEC);
}
/*
* Apply cmndspec-specific settings including SELinux role/type,
* AppArmor profile, Solaris privs, and command tags.
*/
static bool
apply_cmndspec(struct sudoers_context *ctx, struct cmndspec *cs)
{
debug_decl(apply_cmndspec, SUDOERS_DEBUG_PARSER);
if (cs != NULL) {
/* Set role and type if not specified on command line. */
if (ctx->runas.role == NULL) {
if (cs->role != NULL) {
ctx->runas.role = strdup(cs->role);
if (ctx->runas.role == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
debug_return_bool(false);
}
} else {
ctx->runas.role = def_role;
def_role = NULL;
}
if (ctx->runas.role != NULL) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"ctx->runas.role -> %s", ctx->runas.role);
}
}
if (ctx->runas.type == NULL) {
if (cs->type != NULL) {
ctx->runas.type = strdup(cs->type);
if (ctx->runas.type == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
debug_return_bool(false);
}
} else {
ctx->runas.type = def_type;
def_type = NULL;
}
if (ctx->runas.type != NULL) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"ctx->runas.type -> %s", ctx->runas.type);
}
}
/* Set AppArmor profile, if specified */
if (cs->apparmor_profile != NULL) {
free(ctx->runas.apparmor_profile);
ctx->runas.apparmor_profile = strdup(cs->apparmor_profile);
if (ctx->runas.apparmor_profile == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
debug_return_bool(false);
}
} else {
free(ctx->runas.apparmor_profile);
ctx->runas.apparmor_profile = def_apparmor_profile;
def_apparmor_profile = NULL;
}
if (ctx->runas.apparmor_profile != NULL) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"ctx->runas.apparmor_profile -> %s", ctx->runas.apparmor_profile);
}
/* Set Solaris privilege sets */
if (cs->privs != NULL) {
free(ctx->runas.privs);
ctx->runas.privs = strdup(cs->privs);
if (ctx->runas.privs == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
debug_return_bool(false);
}
} else {
free(ctx->runas.privs);
ctx->runas.privs = def_privs;
def_privs = NULL;
}
if (ctx->runas.privs != NULL) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"ctx->runas.privs -> %s", ctx->runas.privs);
}
if (cs->limitprivs != NULL) {
free(ctx->runas.limitprivs);
ctx->runas.limitprivs = strdup(cs->limitprivs);
if (ctx->runas.limitprivs == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
debug_return_bool(false);
}
} else {
free(ctx->runas.limitprivs);
ctx->runas.limitprivs = def_limitprivs;
def_limitprivs = NULL;
}
if (ctx->runas.limitprivs != NULL) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"ctx->runas.limitprivs -> %s", ctx->runas.limitprivs);
}
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(ctx, 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(ctx, 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.
*/
unsigned int
sudoers_lookup(struct sudo_nss_list *snl, struct sudoers_context *ctx,
time_t now, sudoers_lookup_callback_fn_t callback, void *cb_data,
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;
unsigned int validated = FLAG_NO_USER | FLAG_NO_HOST;
int m, match = UNSPEC;
debug_decl(sudoers_lookup, SUDOERS_DEBUG_PARSER);
/*
* Special case checking the "validate", "list" and "kill" pseudo-commands.
*/
if (pwflag) {
debug_return_uint(sudoers_lookup_pseudo(snl, ctx, now, callback,
cb_data, pwflag));
}
/* Need to be runas user while stat'ing things. */
if (!set_perms(ctx, PERM_RUNAS))
debug_return_uint(validated);
/* Query each sudoers source and check the user. */
TAILQ_FOREACH(nss, snl, entries) {
if (nss->query(ctx, nss, ctx->user.pw) == -1) {
/* The query function should have printed an error message. */
SET(validated, VALIDATE_ERROR);
break;
}
m = sudoers_lookup_check(nss, ctx, &validated, &info, now, callback,
cb_data, &cs, &defs);
if (SPECIFIED(m)) {
match = m;
parse_tree = nss->parse_tree;
}
if (!sudo_nss_can_continue(nss, m))
break;
}
if (SPECIFIED(match)) {
if (info.cmnd_path != NULL) {
/* Update cmnd, cmnd_stat, cmnd_status from matching entry. */
free(ctx->user.cmnd);
ctx->user.cmnd = info.cmnd_path;
if (ctx->user.cmnd_stat != NULL)
*ctx->user.cmnd_stat = info.cmnd_stat;
*cmnd_status = info.status;
}
if (defs != NULL)
(void)update_defaults(ctx, parse_tree, defs, SETDEF_GENERIC, false);
if (!apply_cmndspec(ctx, 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_uint(validated);
}