Support adminconfdir for relative include paths in sudoers.

This commit is contained in:
Todd C. Miller
2023-05-02 10:47:11 -06:00
parent 7a6ac5d26e
commit 5446b009e3
9 changed files with 430 additions and 245 deletions

View File

@@ -3965,7 +3965,7 @@ free_parse_tree(struct sudoers_parse_tree *parse_tree)
* the current sudoers file to path. * the current sudoers file to path.
*/ */
bool bool
init_parser_ext(const char *path, bool strict, int verbose) init_parser_ext(const char *file, const char *path, bool strict, int verbose)
{ {
bool ret = true; bool ret = true;
debug_decl(init_parser, SUDOERS_DEBUG_PARSER); debug_decl(init_parser, SUDOERS_DEBUG_PARSER);
@@ -3975,8 +3975,8 @@ init_parser_ext(const char *path, bool strict, int verbose)
init_lexer(); init_lexer();
sudo_rcstr_delref(sudoers); sudo_rcstr_delref(sudoers);
if (path != NULL) { if (file != NULL) {
if ((sudoers = sudo_rcstr_dup(path)) == NULL) { if ((sudoers = sudo_rcstr_dup(file)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
ret = false; ret = false;
} }
@@ -3984,6 +3984,16 @@ init_parser_ext(const char *path, bool strict, int verbose)
sudoers = NULL; sudoers = NULL;
} }
sudo_rcstr_delref(sudoers_search_path);
if (path != NULL) {
if ((sudoers_search_path = sudo_rcstr_dup(path)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
ret = false;
}
} else {
sudoers_search_path = NULL;
}
parse_error = false; parse_error = false;
sudoers_strict = strict; sudoers_strict = strict;
sudoers_verbose = verbose; sudoers_verbose = verbose;
@@ -3992,9 +4002,9 @@ init_parser_ext(const char *path, bool strict, int verbose)
} }
bool bool
init_parser(const char *path) init_parser(const char *file)
{ {
return init_parser_ext(path, false, 1); return init_parser_ext(file, NULL, false, 1);
} }
/* /*

View File

@@ -1782,7 +1782,7 @@ free_parse_tree(struct sudoers_parse_tree *parse_tree)
* the current sudoers file to path. * the current sudoers file to path.
*/ */
bool bool
init_parser_ext(const char *path, bool strict, int verbose) init_parser_ext(const char *file, const char *path, bool strict, int verbose)
{ {
bool ret = true; bool ret = true;
debug_decl(init_parser, SUDOERS_DEBUG_PARSER); debug_decl(init_parser, SUDOERS_DEBUG_PARSER);
@@ -1792,8 +1792,8 @@ init_parser_ext(const char *path, bool strict, int verbose)
init_lexer(); init_lexer();
sudo_rcstr_delref(sudoers); sudo_rcstr_delref(sudoers);
if (path != NULL) { if (file != NULL) {
if ((sudoers = sudo_rcstr_dup(path)) == NULL) { if ((sudoers = sudo_rcstr_dup(file)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
ret = false; ret = false;
} }
@@ -1801,6 +1801,16 @@ init_parser_ext(const char *path, bool strict, int verbose)
sudoers = NULL; sudoers = NULL;
} }
sudo_rcstr_delref(sudoers_search_path);
if (path != NULL) {
if ((sudoers_search_path = sudo_rcstr_dup(path)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
ret = false;
}
} else {
sudoers_search_path = NULL;
}
parse_error = false; parse_error = false;
sudoers_strict = strict; sudoers_strict = strict;
sudoers_verbose = verbose; sudoers_verbose = verbose;
@@ -1809,9 +1819,9 @@ init_parser_ext(const char *path, bool strict, int verbose)
} }
bool bool
init_parser(const char *path) init_parser(const char *file)
{ {
return init_parser_ext(path, false, 1); return init_parser_ext(file, NULL, false, 1);
} }
/* /*

View File

@@ -372,8 +372,8 @@ int check_aliases(struct sudoers_parse_tree *parse_tree, bool strict, bool quiet
/* gram.y */ /* gram.y */
extern struct sudoers_parse_tree parsed_policy; extern struct sudoers_parse_tree parsed_policy;
extern bool (*sudoers_error_hook)(const char *file, int line, int column, const char *fmt, va_list args); extern bool (*sudoers_error_hook)(const char *file, int line, int column, const char *fmt, va_list args);
bool init_parser(const char *path); bool init_parser(const char *file);
bool init_parser_ext(const char *path, bool strict, int verbose); bool init_parser_ext(const char *file, const char *path, bool strict, int verbose);
void free_member(struct member *m); void free_member(struct member *m);
void free_members(struct member_list *members); void free_members(struct member_list *members);
void free_cmndspec(struct cmndspec *cs, struct cmndspec_list *csl); void free_cmndspec(struct cmndspec *cs, struct cmndspec_list *csl);

View File

@@ -312,7 +312,7 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
/* Initialize defaults and parse sudoers. */ /* Initialize defaults and parse sudoers. */
init_defaults(); init_defaults();
init_parser_ext("sudoers", true, 1); init_parser_ext("sudoers", NULL, true, 1);
sudoersrestart(fp); sudoersrestart(fp);
sudoersparse(); sudoersparse();
reparent_parse_tree(&parse_tree); reparent_parse_tree(&parse_tree);

View File

@@ -335,6 +335,7 @@ void sudoersrestart(FILE *);
extern FILE *sudoersin; extern FILE *sudoersin;
extern const char *sudoers_file; extern const char *sudoers_file;
extern char *sudoers; extern char *sudoers;
extern char *sudoers_search_path;
extern mode_t sudoers_mode; extern mode_t sudoers_mode;
extern uid_t sudoers_uid; extern uid_t sudoers_uid;
extern gid_t sudoers_gid; extern gid_t sudoers_gid;

View File

@@ -274,7 +274,7 @@ main(int argc, char *argv[])
} }
/* Initialize the parser and set sudoers filename to "sudoers". */ /* Initialize the parser and set sudoers filename to "sudoers". */
init_parser_ext("sudoers", true, 2); init_parser_ext("sudoers", NULL, true, 2);
/* /*
* Set runas passwd/group entries based on command line or sudoers. * Set runas passwd/group entries based on command line or sudoers.

File diff suppressed because it is too large Load Diff

View File

@@ -55,6 +55,7 @@
int sudolineno; /* current sudoers line number. */ int sudolineno; /* current sudoers line number. */
char *sudoers; /* sudoers file being parsed. */ char *sudoers; /* sudoers file being parsed. */
char *sudoers_search_path; /* colon-separated path of sudoers files. */
const char *sudoers_errstr; /* description of last error from lexer. */ const char *sudoers_errstr; /* description of last error from lexer. */
struct sudolinebuf sudolinebuf; /* sudoers line being parsed. */ struct sudolinebuf sudolinebuf; /* sudoers line being parsed. */
@@ -922,7 +923,8 @@ SLIST_HEAD(path_list_head, path_list);
struct include_stack { struct include_stack {
struct sudolinebuf line; struct sudolinebuf line;
YY_BUFFER_STATE bs; YY_BUFFER_STATE bs;
char *path; char *path; /* search path */
char *file;
struct path_list_head more; /* more files in case of includedir */ struct path_list_head more; /* more files in case of includedir */
int lineno; int lineno;
bool keepopen; bool keepopen;
@@ -957,6 +959,7 @@ read_dir_files(const char *dirpath, struct path_list ***pathsp, int verbose)
const size_t dirlen = strlen(dirpath); const size_t dirlen = strlen(dirpath);
debug_decl(read_dir_files, SUDOERS_DEBUG_PARSER); debug_decl(read_dir_files, SUDOERS_DEBUG_PARSER);
/* XXX - fdopendir */
dir = opendir(dirpath); dir = opendir(dirpath);
if (dir == NULL) { if (dir == NULL) {
if (errno == ENOENT) if (errno == ENOENT)
@@ -1117,89 +1120,150 @@ init_lexer(void)
debug_return; debug_return;
} }
/*
* Like strlcpy() but expand %h escapes to user_shost.
*/
static size_t
strlcpy_expand_host(char *dst, const char *src, size_t size)
{
size_t len = 0;
char ch;
debug_decl(strlcpy_expand_host, SUDOERS_DEBUG_PARSER);
while ((ch = *src++) != '\0') {
if (ch == '%' && *src == 'h') {
size_t n = strlcpy(dst, user_shost, size);
len += n;
if (n >= size) {
/* truncated */
n = size ? size - 1 : 0;
}
dst += n;
size -= n;
src++;
continue;
}
if (size > 1) {
*dst++ = ch;
size--;
len++;
}
}
if (size > 0)
*dst = '\0';
debug_return_size_t(len);
}
/* /*
* Expand any embedded %h (host) escapes in the given path and makes * Expand any embedded %h (host) escapes in the given path and makes
* a relative path fully-qualified based on the current sudoers file. * a relative path fully-qualified based on the current sudoers file.
* Returns a reference-counted string. * Returns a reference-counted string on success or NULL on failure.
*/ */
static char * static char *
expand_include(const char *opath) expand_include(const char *src)
{ {
const char *path = sudoers_search_path ? sudoers_search_path : sudoers;
const char *path_end = path + strlen(path);
const char *cp, *ep; const char *cp, *ep;
char *path, *pp; char *dst0, *dst;
size_t len, olen, dirlen = 0; size_t dst_size, src_len;
bool subst = false; unsigned int nhost = 0;
debug_decl(expand_include, SUDOERS_DEBUG_PARSER); debug_decl(expand_include, SUDOERS_DEBUG_PARSER);
/* Strip double quotes if present. */ /* Strip double quotes if present. */
olen = strlen(opath); src_len = strlen(src);
if (olen > 1 && opath[0] == '"' && opath[olen - 1] == '"') { if (src_len > 1 && src[0] == '"' && src[src_len - 1] == '"') {
opath++; src++;
olen -= 2; src_len -= 2;
} }
if (olen == 0) if (src_len == 0)
debug_return_ptr(NULL); debug_return_ptr(NULL);
/* Relative paths are located in the same dir as the sudoers file. */ /* Check for %h escapes in src. */
if (*opath != '/') { cp = src;
char *dirend = strrchr(sudoers, '/'); ep = src + src_len;
if (dirend != NULL)
dirlen = (size_t)(dirend - sudoers) + 1;
}
cp = opath;
ep = opath + olen;
len = olen;
while (cp < ep) { while (cp < ep) {
if (cp[0] == '%' && cp[1] == 'h') { if (cp[0] == '%' && cp[1] == 'h') {
subst = true; nhost++;
len += strlen(user_shost);
cp += 2; cp += 2;
continue; continue;
} }
cp++; cp++;
} }
/* Make a copy of the fully-qualified path and return it. */ if (*src == '/') {
path = pp = sudo_rcstr_alloc(dirlen + len); /* Fully-qualified path, make a copy and expand %h escapes. */
if (path == NULL) { dst_size = src_len + (nhost * strlen(user_shost)) - (nhost * 2) + 1;
dst0 = sudo_rcstr_alloc(dst_size - 1);
if (dst0 == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
sudoerserror(NULL); sudoerserror(NULL);
debug_return_str(NULL); debug_return_str(NULL);
} }
if (dirlen) { if (strlcpy_expand_host(dst0, src, dst_size) >= dst_size)
memcpy(path, sudoers, dirlen);
pp += dirlen;
}
if (subst) {
/* substitute for %h */
cp = opath;
while (cp < ep) {
if (cp[0] == '%' && cp[1] == 'h') {
size_t n = strlcpy(pp, user_shost, len + 1);
if (n >= len + 1)
goto oflow; goto oflow;
cp += 2; debug_return_str(dst0);
pp += n;
len -= n;
continue;
}
if (len < 1)
goto oflow;
*pp++ = *cp++;
len--;
}
*pp = '\0';
} else {
memcpy(pp, opath, len);
pp[len] = '\0';
} }
debug_return_str(path); /*
* Relative paths are located in the same dir as the sudoers file.
* If the current sudoers file was opened via a colon-separated path,
* use the same path when opening src.
*/
dst_size = 0;
for (cp = sudo_strsplit(path, path_end, ":", &ep); cp != NULL;
cp = sudo_strsplit(NULL, path_end, ":", &ep)) {
char *dirend = memrchr(cp, '/', ep - cp);
if (dirend != NULL) {
dst_size += (size_t)(dirend - cp) + 1;
}
/* Includes space for ':' separator and NUL terminator. */
dst_size += src_len + (nhost * strlen(user_shost)) - (nhost * 2) + 1;
}
/* Make a copy of the fully-qualified path and return it. */
dst = dst0 = sudo_rcstr_alloc(dst_size - 1);
if (dst0 == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
sudoerserror(NULL);
debug_return_str(NULL);
}
for (cp = sudo_strsplit(path, path_end, ":", &ep); cp != NULL;
cp = sudo_strsplit(NULL, path_end, ":", &ep)) {
size_t len;
char *dirend;
if (cp != path) {
if (dst_size < 2)
goto oflow;
*dst++ = ':';
dst_size--;
}
dirend = memrchr(cp, '/', ep - cp);
if (dirend != NULL) {
len = (size_t)(dirend - cp) + 1;
if (len >= dst_size)
goto oflow;
memcpy(dst, cp, len);
dst += len;
dst_size -= len;
}
len = strlcpy_expand_host(dst, src, dst_size);
if (len >= dst_size)
goto oflow;
dst += len;
dst_size -= len;
}
*dst = '\0';
debug_return_str(dst0);
oflow: oflow:
sudo_warnx(U_("internal error, %s overflow"), __func__); sudo_warnx(U_("internal error, %s overflow"), __func__);
sudoerserror(NULL); sudoerserror(NULL);
sudo_rcstr_delref(path); free(dst0);
debug_return_str(NULL); debug_return_str(NULL);
} }
@@ -1213,7 +1277,7 @@ static bool
push_include_int(const char *opath, bool isdir, int verbose) push_include_int(const char *opath, bool isdir, int verbose)
{ {
struct path_list *pl; struct path_list *pl;
char *path; char *file = NULL, *path;
FILE *fp; FILE *fp;
debug_decl(push_include, SUDOERS_DEBUG_PARSER); debug_decl(push_include, SUDOERS_DEBUG_PARSER);
@@ -1226,7 +1290,8 @@ push_include_int(const char *opath, bool isdir, int verbose)
if (idepth > MAX_SUDOERS_DEPTH) { if (idepth > MAX_SUDOERS_DEPTH) {
if (verbose > 0) { if (verbose > 0) {
fprintf(stderr, U_("%s: %s"), path, U_("too many levels of includes")); fprintf(stderr, U_("%s: %s"), path,
U_("too many levels of includes"));
fputc('\n', stderr); fputc('\n', stderr);
} }
sudoerserror(NULL); sudoerserror(NULL);
@@ -1246,9 +1311,12 @@ push_include_int(const char *opath, bool isdir, int verbose)
SLIST_INIT(&istack[idepth].more); SLIST_INIT(&istack[idepth].more);
if (isdir) { if (isdir) {
struct stat sb; struct stat sb;
int count, status; char dname[PATH_MAX];
int count, fd, status;
status = sudo_secure_dir(path, sudoers_uid, sudoers_gid, &sb); fd = sudo_open_conf_path(path, dname, sizeof(dname), NULL);
status = sudo_secure_fd(fd, S_IFDIR, sudoers_uid, sudoers_gid, &sb);
close(fd); /* XXX use in read_dir_files? */
if (status != SUDO_PATH_SECURE) { if (status != SUDO_PATH_SECURE) {
if (verbose > 0) { if (verbose > 0) {
switch (status) { switch (status) {
@@ -1277,7 +1345,7 @@ push_include_int(const char *opath, bool isdir, int verbose)
sudo_rcstr_delref(path); sudo_rcstr_delref(path);
debug_return_bool(true); debug_return_bool(true);
} }
count = switch_dir(&istack[idepth], path, verbose); count = switch_dir(&istack[idepth], dname, verbose);
if (count <= 0) { if (count <= 0) {
/* switch_dir() called sudoerserror() for us */ /* switch_dir() called sudoerserror() for us */
sudo_rcstr_delref(path); sudo_rcstr_delref(path);
@@ -1286,6 +1354,7 @@ push_include_int(const char *opath, bool isdir, int verbose)
/* Parse the first dir entry we can open, leave the rest for later. */ /* Parse the first dir entry we can open, leave the rest for later. */
do { do {
sudo_rcstr_delref(file);
sudo_rcstr_delref(path); sudo_rcstr_delref(path);
if ((pl = SLIST_FIRST(&istack[idepth].more)) == NULL) { if ((pl = SLIST_FIRST(&istack[idepth].more)) == NULL) {
/* Unable to open any files in include dir, not an error. */ /* Unable to open any files in include dir, not an error. */
@@ -1294,24 +1363,32 @@ push_include_int(const char *opath, bool isdir, int verbose)
SLIST_REMOVE_HEAD(&istack[idepth].more, entries); SLIST_REMOVE_HEAD(&istack[idepth].more, entries);
path = pl->path; path = pl->path;
free(pl); free(pl);
} while ((fp = open_sudoers(path, NULL, false, &keepopen)) == NULL); /* The file and path and the same for sudoers.d files. */
file = path;
sudo_rcstr_addref(file);
} while ((fp = open_sudoers(file, NULL, false, &keepopen)) == NULL);
} else { } else {
if ((fp = open_sudoers(path, NULL, true, &keepopen)) == NULL) { if ((fp = open_sudoers(path, &file, true, &keepopen)) == NULL) {
/* The error was already printed by open_sudoers() */ /* The error was already printed by open_sudoers() */
sudoerserror(NULL); sudoerserror(NULL);
sudo_rcstr_delref(path); sudo_rcstr_delref(path);
debug_return_bool(false); debug_return_bool(false);
} }
} }
/* Push the old (current) file and open the new one. */ /*
istack[idepth].path = sudoers; /* push old path (and its ref) */ * Push the old (current) file and open the new one.
* We use the existing refs of sudoers and sudoers_search_path.
*/
istack[idepth].file = sudoers;
istack[idepth].path = sudoers_search_path;
istack[idepth].line = sudolinebuf; istack[idepth].line = sudolinebuf;
istack[idepth].bs = YY_CURRENT_BUFFER; istack[idepth].bs = YY_CURRENT_BUFFER;
istack[idepth].lineno = sudolineno; istack[idepth].lineno = sudolineno;
istack[idepth].keepopen = keepopen; istack[idepth].keepopen = keepopen;
idepth++; idepth++;
sudolineno = 1; sudolineno = 1;
sudoers = path; sudoers = file;
sudoers_search_path = path;
sudoers_switch_to_buffer(sudoers_create_buffer(fp, YY_BUF_SIZE)); sudoers_switch_to_buffer(sudoers_create_buffer(fp, YY_BUF_SIZE));
memset(&sudolinebuf, 0, sizeof(sudolinebuf)); memset(&sudolinebuf, 0, sizeof(sudolinebuf));
@@ -1356,7 +1433,10 @@ pop_include(void)
sudolinebuf.len = sudolinebuf.off = 0; sudolinebuf.len = sudolinebuf.off = 0;
sudolinebuf.toke_start = sudolinebuf.toke_end = 0; sudolinebuf.toke_start = sudolinebuf.toke_end = 0;
sudo_rcstr_delref(sudoers); sudo_rcstr_delref(sudoers);
sudoers = pl->path; sudo_rcstr_delref(sudoers_search_path);
sudoers_search_path = pl->path;
sudoers = sudoers_search_path;
sudo_rcstr_addref(sudoers);
sudolineno = 1; sudolineno = 1;
sudoers_switch_to_buffer(sudoers_create_buffer(fp, YY_BUF_SIZE)); sudoers_switch_to_buffer(sudoers_create_buffer(fp, YY_BUF_SIZE));
free(pl); free(pl);
@@ -1373,7 +1453,9 @@ pop_include(void)
free(sudolinebuf.buf); free(sudolinebuf.buf);
sudolinebuf = istack[idepth].line; sudolinebuf = istack[idepth].line;
sudo_rcstr_delref(sudoers); sudo_rcstr_delref(sudoers);
sudoers = istack[idepth].path; sudoers = istack[idepth].file;
sudo_rcstr_delref(sudoers_search_path);
sudoers_search_path = istack[idepth].path;
sudolineno = istack[idepth].lineno; sudolineno = istack[idepth].lineno;
keepopen = istack[idepth].keepopen; keepopen = istack[idepth].keepopen;
} }

View File

@@ -287,7 +287,7 @@ main(int argc, char *argv[])
* Parse the existing sudoers file(s) to highlight any existing * Parse the existing sudoers file(s) to highlight any existing
* errors and to pull in editor and env_editor conf values. * errors and to pull in editor and env_editor conf values.
*/ */
init_parser_ext(NULL, true, quiet ? 0 : 2); init_parser_ext(NULL, sudoers_file, true, quiet ? 0 : 2);
if ((sudoersin = open_sudoers(sudoers_file, &sudoers, true, NULL)) == NULL) if ((sudoersin = open_sudoers(sudoers_file, &sudoers, true, NULL)) == NULL)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale); sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
@@ -650,7 +650,7 @@ reparse_sudoers(char *editor, int editor_argc, char **editor_argv,
/* Clean slate for each parse */ /* Clean slate for each parse */
if (!init_defaults()) if (!init_defaults())
sudo_fatalx("%s", U_("unable to initialize sudoers default values")); sudo_fatalx("%s", U_("unable to initialize sudoers default values"));
init_parser_ext(sp->opath, true, quiet ? 0 : 2); init_parser_ext(sp->opath, sudoers_file, true, quiet ? 0 : 2);
sp->errorline = -1; sp->errorline = -1;
/* Parse the sudoers temp file(s) */ /* Parse the sudoers temp file(s) */
@@ -1067,7 +1067,7 @@ check_syntax(const char *path, bool quiet, bool strict, bool check_owner,
goto done; goto done;
} }
} }
init_parser_ext(fname, true, quiet ? 0 : 2); init_parser_ext(fname, path, true, quiet ? 0 : 2);
sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale); sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
if (sudoersparse() && !parse_error) { if (sudoersparse() && !parse_error) {
if (!quiet) if (!quiet)
@@ -1228,7 +1228,7 @@ open_sudoers(const char *path, char **outfile, bool doedit, bool *keepopen)
/* Check for existing entry using the first file in path. */ /* Check for existing entry using the first file in path. */
len = strcspn(path, ":"); len = strcspn(path, ":");
TAILQ_FOREACH(entry, &sudoerslist, entries) { TAILQ_FOREACH(entry, &sudoerslist, entries) {
if (strncmp(path, entry->opath, len) == 0 && entry->opath[len] == '\0') if (strncmp(path, entry->dpath, len) == 0 && entry->dpath[len] == '\0')
break; break;
} }
if (entry == NULL) { if (entry == NULL) {