From 898765e0a8b01c4a092acc604c1e452ab6ed5a6b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Kain" Date: Tue, 9 Aug 2022 13:40:10 -0400 Subject: [PATCH] scriptd: Use posix_spawn rather than fork/exec. Also don't bother providing an artificial environment. --- nk/exec.c | 193 --------------------------------------------------- nk/exec.h | 19 ----- nk/nstrcpy.h | 52 ++++++++++++++ nk/pspawn.c | 90 ++++++++++++++++++++++++ nk/pspawn.h | 15 ++++ scriptd.c | 23 ++---- 6 files changed, 162 insertions(+), 230 deletions(-) delete mode 100644 nk/exec.c delete mode 100644 nk/exec.h create mode 100644 nk/nstrcpy.h create mode 100644 nk/pspawn.c create mode 100644 nk/pspawn.h diff --git a/nk/exec.c b/nk/exec.c deleted file mode 100644 index 1d62574..0000000 --- a/nk/exec.c +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2003-2022 Nicholas J. Kain -// SPDX-License-Identifier: MIT -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "nk/stb_sprintf.h" -#include "nk/exec.h" -#include "nk/io.h" - -/* - * Note that nk_generate_env is not async signal safe if chroot_path is not - * NULL, so it should only be called after fork() in a non-multithreaded - * process if chroot_path is ever non-NULL. - * - * I don't consider this to be a problem in general, since in a multithreaded process - * it would be far better to fork off a subprocess early on before threads are - * created and use a socketpair() to request subprocess creation from the - * single-threaded subprocess from the multithreaded main program. - */ - -#define DEFAULT_ROOT_PATH "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" -#define DEFAULT_PATH "/bin:/usr/bin:/usr/local/bin" -#define MAX_ARGS 256 -#define MAX_ARGBUF 16384 -#define MAX_PWBUF 16384 - -#define NK_GEN_ENV(GEN_STR, ...) do { \ - if (env_offset >= envlen) return -3; \ - ssize_t snlen = stbsp_snprintf(envbuf, envbuflen, GEN_STR, __VA_ARGS__); \ - if (snlen < 0 || (size_t)snlen > envbuflen) return -2; \ - xe->env[env_offset++] = envbuf; envbuf += snlen; envbuflen -= (size_t)snlen; \ - } while (0) - -/* - * xe: contains generated env and backing buffer - * uid: userid of the user account that the environment will constructed for - * chroot_path: path where the environment will be chrooted or NULL if no chroot - * path_var: value of the PATH variable in the environment or defaults if NULL - * - * returns: - * 0 on success - * -1 if an account for uid does not exist - * -2 if there is not enough space in envbuf for the generated environment - * -3 if there is not enough space in env for the generated environment - * -4 if chdir to homedir or rootdir failed - * -5 if oom or i/o failed - * -6 if MAX_PWBUF is too small - */ -int nk_generate_env(struct nk_exec_env *xe, uid_t uid, const char *chroot_path, const char *path_var) -{ - char pwbuf[MAX_PWBUF]; - struct passwd pw_s, *pw; - - for (;;) { - int r = getpwuid_r(uid, &pw_s, pwbuf, sizeof pwbuf, &pw); - if (!r) { - if (pw == NULL) return -1; - break; - } else { - if (r == EINTR) continue; - if (r == ERANGE) return -6; - return -5; - } - } - - size_t env_offset = 0; - size_t envlen = sizeof xe->env / sizeof xe->env[0]; - char *envbuf = xe->envbuf; - size_t envbuflen = sizeof xe->envbuf; - if (envlen-- < 1) return -3; // So we don't have to account for the terminal NULL - - NK_GEN_ENV("UID=%i", uid); - NK_GEN_ENV("USER=%s", pw->pw_name); - NK_GEN_ENV("USERNAME=%s", pw->pw_name); - NK_GEN_ENV("LOGNAME=%s", pw->pw_name); - NK_GEN_ENV("HOME=%s", pw->pw_dir); - NK_GEN_ENV("SHELL=%s", pw->pw_shell); - NK_GEN_ENV("PATH=%s", path_var ? path_var : (uid > 0 ? DEFAULT_PATH : DEFAULT_ROOT_PATH)); - NK_GEN_ENV("PWD=%s", !chroot_path ? pw->pw_dir : "/"); - if (chroot_path && chroot(chroot_path)) return -4; - if (chdir(chroot_path ? chroot_path : "/")) return -4; - - xe->env[env_offset] = 0; - return 0; -} - -#define ERRSTR0 "exec: failed to generate environment - (?) unknown error\n" -#define ERRSTR1 "exec: failed to generate environment - (-1) account for uid does not exist\n" -#define ERRSTR2 "exec: failed to generate environment - (-2) not enough space in envbuf\n" -#define ERRSTR3 "exec: failed to generate environment - (-3) not enough space in env\n" -#define ERRSTR4 "exec: failed to generate environment - (-4) chdir to homedir or rootdir failed\n" -#define ERRSTR5 "exec: failed to generate environment - (-5) oom or i/o error\n" -#define ERRSTR6 "exec: failed to generate environment - (-6) MAX_PWBUF is too small\n" -void nk_generate_env_print_error(int err) -{ - switch (err) { - default: safe_write(STDERR_FILENO, ERRSTR0, sizeof ERRSTR0); break; - case -1: safe_write(STDERR_FILENO, ERRSTR1, sizeof ERRSTR1); break; - case -2: safe_write(STDERR_FILENO, ERRSTR2, sizeof ERRSTR2); break; - case -3: safe_write(STDERR_FILENO, ERRSTR3, sizeof ERRSTR3); break; - case -4: safe_write(STDERR_FILENO, ERRSTR4, sizeof ERRSTR4); break; - case -5: safe_write(STDERR_FILENO, ERRSTR5, sizeof ERRSTR5); break; - case -6: safe_write(STDERR_FILENO, ERRSTR6, sizeof ERRSTR6); break; - } -} -#undef ERRSTR0 -#undef ERRSTR1 -#undef ERRSTR2 -#undef ERRSTR3 -#undef ERRSTR4 -#undef ERRSTR5 -#undef ERRSTR6 - -#define NK_GEN_ARG(GEN_STR, ...) do { \ - ssize_t snlen = stbsp_snprintf(argbuf, argbuflen, GEN_STR, __VA_ARGS__); \ - if (snlen < 0 || (size_t)snlen > argbuflen) { \ - static const char errstr[] = "nk_execute: constructing argument list failed\n"; \ - safe_write(STDERR_FILENO, errstr, sizeof errstr); \ - _Exit(EXIT_FAILURE); \ - } \ - argv[curv] = argbuf; argv[++curv] = NULL; \ - argbuf += snlen; argbuflen -= (size_t)snlen; \ - } while (0) - -void __attribute__((noreturn)) -nk_execute(const char *command, const char *args, char * const envp[]) -{ - char *argv[MAX_ARGS]; - char argbuf_s[MAX_ARGBUF]; - char *argbuf = argbuf_s; - size_t curv = 0; - size_t argbuflen = sizeof argbuf_s; - - if (!command) - _Exit(EXIT_SUCCESS); - - // strip the path from the command name and set argv[0] - const char *p = strrchr(command, '/'); - NK_GEN_ARG("%s", p ? p + 1 : command); - - if (args) { - p = args; - const char *q = args; - bool squote = false, dquote = false, atend = false; - for (;; ++p) { - switch (*p) { - default: continue; - case '\0': - atend = true; - goto endarg; - case ' ': - if (!squote && !dquote) - goto endarg; - continue; - case '\'': - if (!dquote) - squote = !squote; - continue; - case '"': - if (!squote) - dquote = !dquote; - continue; - } -endarg: - { - if (p == q) break; - // Push an argument. - if (q > p) { - static const char errstr[] = "nk_execute: argument length too long\n"; - safe_write(STDERR_FILENO, errstr, sizeof errstr); - _Exit(EXIT_FAILURE); - } - NK_GEN_ARG("%.*s", (int)(p - q), q); - q = p + 1; - if (atend || curv >= (MAX_ARGS - 1)) - break; - } - } - } - execve(command, argv, envp); - { - static const char errstr[] = "nk_execute: execve failed\n"; - safe_write(STDERR_FILENO, errstr, sizeof errstr); - _Exit(EXIT_FAILURE); - } -} - diff --git a/nk/exec.h b/nk/exec.h deleted file mode 100644 index ecbbff7..0000000 --- a/nk/exec.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2003-2022 Nicholas J. Kain -// SPDX-License-Identifier: MIT -#ifndef NCM_EXEC_H_ -#define NCM_EXEC_H_ - -struct nk_exec_env -{ - char *env[32]; - char envbuf[4096]; -}; - -int nk_generate_env(struct nk_exec_env *xe, uid_t uid, const char *chroot_path, const char *path_var); -void nk_generate_env_print_error(int err); -void __attribute__((noreturn)) - nk_execute(const char *command, const char *args, char * const envp[]) ; - -#endif - - diff --git a/nk/nstrcpy.h b/nk/nstrcpy.h new file mode 100644 index 0000000..6d5f5ce --- /dev/null +++ b/nk/nstrcpy.h @@ -0,0 +1,52 @@ +#ifndef NKLIB_NSTRCPY_H_ +#define NKLIB_NSTRCPY_H_ + +#include +#include + +// Returns pointer to end of dest string (NULL terminator) if +// src is not truncated when copying to dest. +// Otherwise, returns NULL if src is truncated or size == 0. +static inline char *nstrcpy(char *dest, size_t size, const char *src) +{ + if (!size) return NULL; + char c; + for (size_t i = 0; i < size; ++i, ++dest) { + c = *src++; + *dest = c; + if (!c) return dest; + } + *(dest - 1) = 0; + return NULL; +} + +// Same semantics as above, except we append to dest. +static inline char *nstrcat(char *dest, size_t size, const char *src) +{ + size_t len = strlen(dest); + return nstrcpy(dest + len, size - len, src); +} + +// Acts as nstrcpy, but does not require src to be NULL terminated. +// That said, it will stop early if src contains a NULL terminator. +static inline char *nstrcpyl(char *dest, size_t dsize, const char *src, size_t ssize) +{ + if (!dsize) return NULL; + char c; + size_t i = 0, j = 0; + for (; i < dsize && j < ssize; ++i, ++j, ++dest) { + c = *src++; + *dest = c; + if (!c) return dest; + } + if (i == dsize) { + *(dest - 1) = 0; + return NULL; + } + // j == ssize here + *dest = 0; + return dest; +} + +#endif + diff --git a/nk/pspawn.c b/nk/pspawn.c new file mode 100644 index 0000000..c49271b --- /dev/null +++ b/nk/pspawn.c @@ -0,0 +1,90 @@ +// Copyright 2022 Nicholas J. Kain +// SPDX-License-Identifier: MIT +#include +#include +#include +#include +#include +#include "nk/nstrcpy.h" +#include "nk/pspawn.h" +#include "nk/io.h" + +#define MAX_ARGS 256 +#define MAX_ARGBUF 16384 + +#define NK_GEN_ARG(STRVAL, STRLEN) do { \ + char *snp = nstrcpyl(argbuf, argbuflen, STRVAL, STRLEN); \ + if (!snp) { \ + static const char errstr[] = "nk_pspawn: constructing argument list failed\n"; \ + safe_write(STDERR_FILENO, errstr, sizeof errstr); \ + _Exit(EXIT_FAILURE); \ + } \ + argv[curv] = argbuf; argv[++curv] = NULL; \ + size_t l = (size_t)(snp - argbuf); \ + argbuf += l; argbuflen -= l; \ + } while (0) + +int nk_pspawn(pid_t *pid, const char *command, + const posix_spawn_file_actions_t *restrict file_actions, + const posix_spawnattr_t *restrict attrp, + const char *args, char * const envp[]) +{ + char *argv[MAX_ARGS]; + char argbuf_s[MAX_ARGBUF]; + char *argbuf = argbuf_s; + size_t curv = 0; + size_t argbuflen = sizeof argbuf_s; + + if (!command) + _Exit(EXIT_SUCCESS); + + // strip the path from the command name and set argv[0] + const char *p = strrchr(command, '/'); + { + const char *q = p ? p + 1 : command; + size_t ql = strlen(q); + NK_GEN_ARG(q, ql); + } + + if (args) { + p = args; + const char *q = args; + bool squote = false, dquote = false, atend = false; + for (;; ++p) { + switch (*p) { + default: continue; + case '\0': + atend = true; + goto endarg; + case ' ': + if (!squote && !dquote) + goto endarg; + continue; + case '\'': + if (!dquote) + squote = !squote; + continue; + case '"': + if (!squote) + dquote = !dquote; + continue; + } +endarg: + { + if (p == q) break; + // Push an argument. + if (q > p) { + static const char errstr[] = "nk_execute: argument length too long\n"; + safe_write(STDERR_FILENO, errstr, sizeof errstr); + _Exit(EXIT_FAILURE); + } + NK_GEN_ARG(q, (size_t)(p - q)); + q = p + 1; + if (atend || curv >= (MAX_ARGS - 1)) + break; + } + } + } + return posix_spawn(pid, command, file_actions, attrp, argv, envp); +} + diff --git a/nk/pspawn.h b/nk/pspawn.h new file mode 100644 index 0000000..8c84f1a --- /dev/null +++ b/nk/pspawn.h @@ -0,0 +1,15 @@ +// Copyright 2022 Nicholas J. Kain +// SPDX-License-Identifier: MIT +#ifndef NCM_PSPAWN_H_ +#define NCM_PSPAWN_H_ + +#include +#include + +int nk_pspawn(pid_t *pid, const char *command, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + const char *args, char * const envp[]); + +#endif + diff --git a/scriptd.c b/scriptd.c index 6d60057..a973098 100644 --- a/scriptd.c +++ b/scriptd.c @@ -12,11 +12,12 @@ #include #include "nk/log.h" #include "nk/io.h" -#include "nk/exec.h" +#include "nk/pspawn.h" #include "scriptd.h" #include "ndhc.h" #include "sys.h" +extern char **environ; bool valid_script_file = false; // Runs the 'script_file'-specified script. Called from ndhc process. @@ -33,23 +34,9 @@ void request_scriptd_run(void) static void run_script(void) { - struct nk_exec_env xe; - switch ((int)fork()) { - case 0: { - int r = nk_generate_env(&xe, 0, NULL, NULL); - if (r < 0) { - nk_generate_env_print_error(r); - exit(EXIT_FAILURE); - } - nk_execute(script_file, NULL, xe.env); - } - case -1: { - static const char errstr[] = "exec: fork failed\n"; - safe_write(STDERR_FILENO, errstr, sizeof errstr); - exit(EXIT_FAILURE); - } - default: break; - } + pid_t pid; + int ret = nk_pspawn(&pid, script_file, NULL, NULL, NULL, environ); + if (ret) log_line("posix_spawn failed for '%s': %s\n", script_file, strerror(ret)); } static void process_client_socket(void)