From 10d3d69aa1eab983c5abbcbe148d34fc95d2ed62 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Wed, 9 Sep 2020 15:26:45 -0600 Subject: [PATCH] Pass a struct to the match functions to track the resolved command. This makes it possible to update user_cmnd and cmnd_status modified by per-rule CHROOT settings. --- plugins/sudoers/defaults.c | 2 +- plugins/sudoers/match.c | 11 ++++++----- plugins/sudoers/match_command.c | 24 +++++++++++++++++++----- plugins/sudoers/parse.c | 27 +++++++++++++++++++++------ plugins/sudoers/parse.h | 18 ++++++++++++++---- plugins/sudoers/sudoers.c | 3 +-- plugins/sudoers/testsudoers.c | 2 +- 7 files changed, 63 insertions(+), 24 deletions(-) diff --git a/plugins/sudoers/defaults.c b/plugins/sudoers/defaults.c index 637bf9947..ba6228d47 100644 --- a/plugins/sudoers/defaults.c +++ b/plugins/sudoers/defaults.c @@ -688,7 +688,7 @@ default_binding_matches(struct sudoers_parse_tree *parse_tree, debug_return_bool(true); break; case DEFAULTS_CMND: - if (cmndlist_matches(parse_tree, d->binding, NULL) == ALLOW) + if (cmndlist_matches(parse_tree, d->binding, NULL, NULL) == ALLOW) debug_return_bool(true); break; } diff --git a/plugins/sudoers/match.c b/plugins/sudoers/match.c index 66bfd2911..eb77cf2c3 100644 --- a/plugins/sudoers/match.c +++ b/plugins/sudoers/match.c @@ -366,14 +366,15 @@ host_matches(struct sudoers_parse_tree *parse_tree, const struct passwd *pw, */ int cmndlist_matches(struct sudoers_parse_tree *parse_tree, - const struct member_list *list, const char *runchroot) + const struct member_list *list, const char *runchroot, + struct cmnd_info *info) { struct member *m; int matched = UNSPEC; debug_decl(cmndlist_matches, SUDOERS_DEBUG_MATCH); TAILQ_FOREACH_REVERSE(m, list, member_list, entries) { - matched = cmnd_matches(parse_tree, m, runchroot); + matched = cmnd_matches(parse_tree, m, runchroot, info); if (matched != UNSPEC) break; } @@ -386,7 +387,7 @@ cmndlist_matches(struct sudoers_parse_tree *parse_tree, */ int cmnd_matches(struct sudoers_parse_tree *parse_tree, const struct member *m, - const char *runchroot) + const char *runchroot, struct cmnd_info *info) { struct alias *a; struct sudo_command *c; @@ -402,13 +403,13 @@ cmnd_matches(struct sudoers_parse_tree *parse_tree, const struct member *m, FALLTHROUGH; case COMMAND: c = (struct sudo_command *)m->name; - if (command_matches(c->cmnd, c->args, runchroot, &c->digests)) + if (command_matches(c->cmnd, c->args, runchroot, info, &c->digests)) matched = !m->negated; break; case ALIAS: a = alias_get(parse_tree, m->name, CMNDALIAS); if (a != NULL) { - rc = cmndlist_matches(parse_tree, &a->members, runchroot); + rc = cmndlist_matches(parse_tree, &a->members, runchroot, info); if (rc != UNSPEC) matched = m->negated ? !rc : rc; alias_put(a); diff --git a/plugins/sudoers/match_command.c b/plugins/sudoers/match_command.c index 07769320f..0be9e850e 100644 --- a/plugins/sudoers/match_command.c +++ b/plugins/sudoers/match_command.c @@ -528,8 +528,10 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, /* If it ends in '/' it is a directory spec. */ dlen = strlen(sudoers_cmnd); - if (sudoers_cmnd[dlen - 1] == '/') - debug_return_bool(command_matches_dir(sudoers_cmnd, dlen, runchroot, digests)); + if (sudoers_cmnd[dlen - 1] == '/') { + debug_return_bool(command_matches_dir(sudoers_cmnd, dlen, runchroot, + digests)); + } /* Only proceed if user_base and basename(sudoers_cmnd) match */ if ((base = strrchr(sudoers_cmnd, '/')) == NULL) @@ -584,7 +586,8 @@ bad: */ bool command_matches(const char *sudoers_cmnd, const char *sudoers_args, - const char *runchroot, const struct command_digest_list *digests) + const char *runchroot, struct cmnd_info *info, + const struct command_digest_list *digests) { char *saved_user_cmnd = NULL; struct stat saved_user_stat; @@ -605,11 +608,16 @@ command_matches(const char *sudoers_cmnd, const char *sudoers_args, runchroot = def_runchroot; } else { /* Rule-specific runchroot, reset user_cmnd and user_stat. */ + int status; + saved_user_cmnd = user_cmnd; if (user_stat != NULL) saved_user_stat = *user_stat; - if (set_cmnd_path(runchroot) != FOUND) + status = set_cmnd_path(runchroot); + if (status != FOUND) saved_user_cmnd = NULL; + if (info != NULL) + info->status = status; } if (sudoers_cmnd == NULL) { @@ -648,7 +656,13 @@ command_matches(const char *sudoers_cmnd, const char *sudoers_args, } done: if (saved_user_cmnd != NULL) { - free(user_cmnd); + if (info != NULL) { + info->cmnd_path = user_cmnd; + if (user_stat != NULL) + info->cmnd_stat = *user_stat; + } else { + free(user_cmnd); + } user_cmnd = saved_user_cmnd; if (user_stat != NULL) *user_stat = saved_user_stat; diff --git a/plugins/sudoers/parse.c b/plugins/sudoers/parse.c index b469b959d..dcb0be4a6 100644 --- a/plugins/sudoers/parse.c +++ b/plugins/sudoers/parse.c @@ -92,7 +92,8 @@ sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw, /* Only check the command when listing another user. */ if (user_uid == 0 || list_pw == NULL || user_uid == list_pw->pw_uid || - cmnd_matches(nss->parse_tree, cs->cmnd, cs->runchroot) == ALLOW) + cmnd_matches(nss->parse_tree, cs->cmnd, cs->runchroot, + NULL) == ALLOW) match = ALLOW; } } @@ -112,7 +113,7 @@ sudoers_lookup_pseudo(struct sudo_nss_list *snl, struct passwd *pw, static int sudoers_lookup_check(struct sudo_nss *nss, struct passwd *pw, - int *validated, struct cmndspec **matching_cs, + int *validated, struct cmnd_info *info, struct cmndspec **matching_cs, struct defaults_list **defs, time_t now) { int host_match, runas_match, cmnd_match; @@ -122,6 +123,8 @@ sudoers_lookup_check(struct sudo_nss *nss, struct passwd *pw, 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) { if (userlist_matches(nss->parse_tree, pw, &us->users) != ALLOW) continue; @@ -147,7 +150,7 @@ sudoers_lookup_check(struct sudo_nss *nss, struct passwd *pw, NULL); if (runas_match == ALLOW) { cmnd_match = cmnd_matches(nss->parse_tree, cs->cmnd, - cs->runchroot); + cs->runchroot, info); if (cmnd_match != UNSPEC) { /* * If user is running command as himself, @@ -167,6 +170,8 @@ sudoers_lookup_check(struct sudo_nss *nss, struct passwd *pw, cmnd_match ? "allowed" : "denied"); debug_return_int(cmnd_match); } + free(info->cmnd_path); + memset(info, 0, sizeof(*info)); } } } @@ -327,13 +332,15 @@ apply_cmndspec(struct cmndspec *cs) * 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 validated, +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); @@ -357,7 +364,7 @@ sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int validated, break; } - m = sudoers_lookup_check(nss, pw, &validated, &cs, &defs, now); + m = sudoers_lookup_check(nss, pw, &validated, &info, &cs, &defs, now); if (m != UNSPEC) { match = m; parse_tree = nss->parse_tree; @@ -367,6 +374,14 @@ sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int validated, 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) update_defaults(parse_tree, defs, SETDEF_GENERIC, false); if (!apply_cmndspec(cs)) @@ -876,7 +891,7 @@ display_cmnd_check(struct sudoers_parse_tree *parse_tree, struct passwd *pw, cs->runasgrouplist, NULL, NULL); if (runas_match == ALLOW) { cmnd_match = cmnd_matches(parse_tree, cs->cmnd, - cs->runchroot); + cs->runchroot, NULL); if (cmnd_match != UNSPEC) debug_return_int(cmnd_match); } diff --git a/plugins/sudoers/parse.h b/plugins/sudoers/parse.h index 6fc23d7c4..f8e820f18 100644 --- a/plugins/sudoers/parse.h +++ b/plugins/sudoers/parse.h @@ -20,6 +20,7 @@ #ifndef SUDOERS_PARSE_H #define SUDOERS_PARSE_H +#include #include "sudo_queue.h" /* Characters that must be quoted in sudoers. */ @@ -281,6 +282,15 @@ struct sudoers_parse_tree { const char *shost, *lhost; }; +/* + * Info about the command being resolved. + */ +struct cmnd_info { + struct stat cmnd_stat; + char *cmnd_path; + int status; +}; + /* alias.c */ struct rbtree *alloc_aliases(void); void free_aliases(struct rbtree *aliases); @@ -312,7 +322,7 @@ void reparent_parse_tree(struct sudoers_parse_tree *new_tree); bool addr_matches(char *n); /* match_command.c */ -bool command_matches(const char *sudoers_cmnd, const char *sudoers_args, const char *runchroot, const struct command_digest_list *digests); +bool command_matches(const char *sudoers_cmnd, const char *sudoers_args, const char *runchroot, struct cmnd_info *info, const struct command_digest_list *digests); /* match_digest.c */ bool digest_matches(int fd, const char *file, const struct command_digest_list *digests); @@ -325,8 +335,8 @@ bool hostname_matches(const char *shost, const char *lhost, const char *pattern) bool netgr_matches(const char *netgr, const char *lhost, const char *shost, const char *user); bool usergr_matches(const char *group, const char *user, const struct passwd *pw); bool userpw_matches(const char *sudoers_user, const char *user, const struct passwd *pw); -int cmnd_matches(struct sudoers_parse_tree *parse_tree, const struct member *m, const char *runchroot); -int cmndlist_matches(struct sudoers_parse_tree *parse_tree, const struct member_list *list, const char *runchroot); +int cmnd_matches(struct sudoers_parse_tree *parse_tree, const struct member *m, const char *runchroot, struct cmnd_info *info); +int cmndlist_matches(struct sudoers_parse_tree *parse_tree, const struct member_list *list, const char *runchroot, struct cmnd_info *info); int host_matches(struct sudoers_parse_tree *parse_tree, const struct passwd *pw, const char *host, const char *shost, const struct member *m); int hostlist_matches(struct sudoers_parse_tree *parse_tree, const struct passwd *pw, const struct member_list *list); int runaslist_matches(struct sudoers_parse_tree *parse_tree, const struct member_list *user_list, const struct member_list *group_list, struct member **matching_user, struct member **matching_group); @@ -362,7 +372,7 @@ const char *digest_type_to_name(int digest_type); /* parse.c */ struct sudo_nss_list; -int sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int validated, int pwflag); +int sudoers_lookup(struct sudo_nss_list *snl, struct passwd *pw, int *cmnd_status, int pwflag); int display_privs(struct sudo_nss_list *snl, struct passwd *pw, bool verbose); int display_cmnd(struct sudo_nss_list *snl, struct passwd *pw); diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index 933279b09..df2da0730 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -418,8 +418,7 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], * Check sudoers sources, using the locale specified in sudoers. */ sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale); - validated = sudoers_lookup(snl, sudo_user.pw, FLAG_NO_USER | FLAG_NO_HOST, - pwflag); + validated = sudoers_lookup(snl, sudo_user.pw, &cmnd_status, pwflag); if (ISSET(validated, VALIDATE_ERROR)) { /* The lookup function should have printed an error. */ goto done; diff --git a/plugins/sudoers/testsudoers.c b/plugins/sudoers/testsudoers.c index e88e5f1a0..8bce2682a 100644 --- a/plugins/sudoers/testsudoers.c +++ b/plugins/sudoers/testsudoers.c @@ -337,7 +337,7 @@ main(int argc, char *argv[]) if (runas_match == ALLOW) { puts("\trunas matched"); cmnd_match = cmnd_matches(&parsed_policy, cs->cmnd, - cs->runchroot); + cs->runchroot, NULL); if (cmnd_match != UNSPEC) match = cmnd_match; printf("\tcmnd %s\n", match == ALLOW ? "allowed" :