ba8f674e15
The current code pads with an extra character that is then rewritten into a null character. This isn't necessary with post-C99 implementations of standardized snprintf, so get rid of it. Also add a note warning that nk_generate_env() and nk_execute() are not async signal safe and are thus unsuitable for use in multithreaded processes. nk_execute() could be rewritten to be async signal safe without much trouble, as the only problem point is snprintf() which is not guaranteed to be async signal safe by POSIX. However, nk_generate_env() performs chroot() if a chroot_path is specified, and chroot() is not async signal safe in POSIX. Additionally, malloc() can be called in rare cases where user information fields are very long, and malloc() is obviously not async signal safe. Finally, snprintf() is used here, too, but it could be replaced. Converting to posix_spawn() is a no-go because posix_spawn() has no facility for changing rlimits or chroot on the spawned process. In summary, I don't think the gains are worth it. Multithreaded processes should just not fork().
194 lines
6.6 KiB
C
194 lines
6.6 KiB
C
// Copyright 2003-2018 Nicholas J. Kain <njkain at gmail dot com>
|
|
// SPDX-License-Identifier: MIT
|
|
#include <sys/types.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <pwd.h>
|
|
#include "nk/exec.h"
|
|
#include "nk/io.h"
|
|
|
|
/*
|
|
* Note that neither nk_generate_env or nk_execute are async signal safe, so
|
|
* these functions should only be called after fork() in a non-multithreaded
|
|
* process.
|
|
*
|
|
* 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 4096
|
|
|
|
#define NK_GEN_ENV(GEN_STR, ...) do { \
|
|
if (env_offset >= envlen) return -3; \
|
|
ssize_t snlen = snprintf(envbuf, envbuflen, GEN_STR, __VA_ARGS__); \
|
|
if (snlen < 0 || (size_t)snlen >= envbuflen) return -2; \
|
|
env[env_offset++] = envbuf; envbuf += snlen; envbuflen -= (size_t)snlen; \
|
|
} while (0)
|
|
|
|
/*
|
|
* 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
|
|
* env: array of character pointers that will be filled in with the new environment
|
|
* envlen: number of character pointers available in env; a terminal '0' ptr must be available
|
|
* envbuf: character buffer that will be used for storing state associated with env
|
|
* envbuflen: number of available characters in envbuf for use
|
|
*
|
|
* 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
|
|
*/
|
|
int nk_generate_env(uid_t uid, const char *chroot_path, const char *path_var,
|
|
char *env[], size_t envlen, char *envbuf, size_t envbuflen)
|
|
{
|
|
char pw_strs[4096];
|
|
struct passwd pw_s;
|
|
struct passwd *pw;
|
|
char *pw_buf = NULL;
|
|
int ret = 0, pwr;
|
|
|
|
getpwagain0:
|
|
pwr = getpwuid_r(uid, &pw_s, pw_strs, sizeof pw_strs, &pw);
|
|
if (pw == NULL) {
|
|
if (pwr == 0) { ret = -1; goto out; }
|
|
if (pwr == EINTR) goto getpwagain0;
|
|
if (pwr == ERANGE) {
|
|
size_t pwlen = (sizeof pw_strs >> 1) * 3;
|
|
for (;;) {
|
|
if (pw_buf) free(pw_buf);
|
|
pw_buf = malloc(pwlen);
|
|
if (!pw_buf) { ret = -5; goto out; }
|
|
getpwagain:
|
|
pwr = getpwuid_r(uid, &pw_s, pw_buf, pwlen, &pw);
|
|
if (pw == NULL) {
|
|
if (pwr == 0) { ret = -1; goto out; }
|
|
if (pwr == EINTR) goto getpwagain;
|
|
if (pwr == ERANGE) {
|
|
size_t oldpwlen = pwlen;
|
|
pwlen = (pwlen >> 1) * 3;
|
|
if (pwlen > oldpwlen) continue;
|
|
else { // overflowed
|
|
ret = -5; goto out;
|
|
}
|
|
}
|
|
ret = -5; goto out;
|
|
}
|
|
break; // the pwr != 0 check below applies here
|
|
}
|
|
}
|
|
ret = -5; goto out;
|
|
}
|
|
if (pwr != 0) { ret = -5; goto out; }
|
|
|
|
size_t env_offset = 0;
|
|
if (envlen-- < 1) { // So we don't have to account for the terminal NULL
|
|
ret = -3;
|
|
goto out;
|
|
}
|
|
|
|
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)) { ret = -4; goto out; }
|
|
if (chdir(chroot_path ? chroot_path : "/")) { ret = -4; goto out; }
|
|
|
|
env[env_offset] = 0;
|
|
out:
|
|
free(pw_buf);
|
|
return ret;
|
|
}
|
|
|
|
#define NK_GEN_ARG(GEN_STR, ...) do { \
|
|
ssize_t snlen = 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);
|
|
}
|
|
}
|
|
|