diff -up ./plugins/sudoers/editor.c.other ./plugins/sudoers/editor.c --- ./plugins/sudoers/editor.c.other 2023-01-16 17:37:04.659967300 +0100 +++ ./plugins/sudoers/editor.c 2023-01-16 17:40:35.944400376 +0100 @@ -39,6 +39,82 @@ #include "sudoers.h" /* + * Non-destructive word-split that handles single and double quotes and + * escaped white space. Quotes are only recognized at the start of a word. + * They are treated as normal characters inside a word. + */ +static const char * +wordsplit(const char *str, const char *endstr, const char **last) +{ + const char *cp; + debug_decl(wordsplit, SUDO_DEBUG_UTIL); + + /* If no str specified, use last ptr (if any). */ + if (str == NULL) { + str = *last; + /* Consume end quote if present. */ + if (*str == '"' || *str == '\'') + str++; + } + + /* Skip leading white space characters. */ + while (str < endstr && (*str == ' ' || *str == '\t')) + str++; + + /* Empty string? */ + if (str >= endstr) { + *last = endstr; + debug_return_ptr(NULL); + } + + /* If word is quoted, skip to end quote and return. */ + if (*str == '"' || *str == '\'') { + const char *endquote = memchr(str + 1, *str, endstr - str); + if (endquote != NULL) { + *last = endquote; + debug_return_const_ptr(str + 1); + } + } + + /* Scan str until we encounter white space. */ + for (cp = str; cp < endstr; cp++) { + if (*cp == '\\') { + /* quoted char, do not interpret */ + cp++; + continue; + } + if (*cp == ' ' || *cp == '\t') { + /* end of word */ + break; + } + } + *last = cp; + debug_return_const_ptr(str); +} + +/* Copy len chars from string, collapsing chars escaped with a backslash. */ +static char * +copy_arg(const char *src, size_t len) +{ + const char *src_end = src + len; + char *copy, *dst; + debug_decl(copy_arg, SUDOERS_DEBUG_UTIL); + + if ((copy = malloc(len + 1)) != NULL) { + for (dst = copy; src < src_end; ) { + if (*src == '\\') { + src++; + continue; + } + *dst++ = *src++; + } + *dst = '\0'; + } + + debug_return_ptr(copy); +} + +/* * Search for the specified editor in the user's PATH, checking * the result against allowlist if non-NULL. An argument vector * suitable for execve() is allocated and stored in argv_out. @@ -52,7 +128,7 @@ static char * resolve_editor(const char *ed, size_t edlen, int nfiles, char **files, int *argc_out, char ***argv_out, char * const *allowlist) { - char **nargv, *editor, *editor_path = NULL; + char **nargv = NULL, *editor = NULL, *editor_path = NULL; const char *cp, *ep, *tmp; const char *edend = ed + edlen; struct stat user_editor_sb; @@ -64,14 +140,12 @@ resolve_editor(const char *ed, size_t ed * The EDITOR and VISUAL environment variables may contain command * line args so look for those and alloc space for them too. */ - cp = sudo_strsplit(ed, edend, " \t", &ep); + cp = wordsplit(ed, edend, &ep); if (cp == NULL) debug_return_str(NULL); - editor = strndup(cp, (size_t)(ep - cp)); - if (editor == NULL) { - sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - debug_return_str(NULL); - } + editor = copy_arg(cp, ep - cp); + if (editor == NULL) + goto oom; /* If we can't find the editor in the user's PATH, give up. */ if (find_path(editor, &editor_path, &user_editor_sb, getenv("PATH"), 0, allowlist) != FOUND) { @@ -81,30 +155,22 @@ resolve_editor(const char *ed, size_t ed } /* Count rest of arguments and allocate editor argv. */ - for (nargc = 1, tmp = ep; sudo_strsplit(NULL, edend, " \t", &tmp) != NULL; ) + for (nargc = 1, tmp = ep; wordsplit(NULL, edend, &tmp) != NULL; ) nargc++; if (nfiles != 0) nargc += nfiles + 1; nargv = reallocarray(NULL, nargc + 1, sizeof(char *)); - if (nargv == NULL) { - sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - free(editor); - free(editor_path); - debug_return_str(NULL); - } + if (nargv == NULL) + goto oom; /* Fill in editor argv (assumes files[] is NULL-terminated). */ nargv[0] = editor; - for (nargc = 1; (cp = sudo_strsplit(NULL, edend, " \t", &ep)) != NULL; nargc++) { - nargv[nargc] = strndup(cp, (size_t)(ep - cp)); - if (nargv[nargc] == NULL) { - sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - free(editor_path); - while (nargc--) - free(nargv[nargc]); - free(nargv); - debug_return_str(NULL); - } + editor = NULL; + for (nargc = 1; (cp = wordsplit(NULL, edend, &ep)) != NULL; nargc++) { + /* Copy string, collapsing chars escaped with a backslash. */ + nargv[nargc] = copy_arg(cp, ep - cp); + if (nargv[nargc] == NULL) + goto oom; } if (nfiles != 0) { nargv[nargc++] = "--"; @@ -116,6 +182,16 @@ resolve_editor(const char *ed, size_t ed *argc_out = nargc; *argv_out = nargv; debug_return_str(editor_path); +oom: + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + free(editor); + free(editor_path); + if (nargv != NULL) { + while (nargc--) + free(nargv[nargc]); + free(nargv); + } + debug_return_str(NULL); } /*