#include <stdlib.h>
#include <err.h>
#include <stdbool.h>
#include <sys/sysmacros.h>
#include <unistd.h>
#include <sys/fork.h>
#include <wait.h>
#include <pwd.h>
#include <limits.h>
#include <libgen.h>
#include <sys/debug.h>
#include <priv.h>
static const char *getenv_checker = "checker";
static char getenv_path[PATH_MAX];
static struct passwd *getenv_nobody;
typedef struct {
const char *gf_desc;
bool gf_secure;
void (*gf_forker)(const char *);
} getenv_fork_t;
static void
getenv_fork_nobody(const char *desc)
{
if (setgid(getenv_nobody->pw_gid) != 0) {
errx(EXIT_FAILURE, "TEST FAILED: %s: failed to setgid to "
"nobody (%u)", desc, getenv_nobody->pw_gid);
}
if (setuid(getenv_nobody->pw_uid) != 0) {
errx(EXIT_FAILURE, "TEST FAILED: %s: failed to setuid to "
"nobody (%u)", desc, getenv_nobody->pw_uid);
}
}
static void
getenv_fork_seteuid(const char *desc)
{
if (seteuid(getenv_nobody->pw_uid) != 0) {
errx(EXIT_FAILURE, "TEST FAILED: %s: failed to seteuid to "
"nobody (%u)", desc, getenv_nobody->pw_uid);
}
}
static void
getenv_fork_setegid(const char *desc)
{
if (setegid(getenv_nobody->pw_gid) != 0) {
errx(EXIT_FAILURE, "TEST FAILED: %s: failed to setegid to "
"nobody (%u)", desc, getenv_nobody->pw_gid);
}
}
static void
getenv_fork_seteugid(const char *desc)
{
getenv_fork_setegid(desc);
getenv_fork_seteuid(desc);
}
static void
getenv_fork_privs(const char *desc)
{
priv_set_t *priv = priv_allocset();
if (priv == NULL) {
err(EXIT_FAILURE, "TEST FAILED: %s: failed to allocate a "
"priv_set_t", desc);
}
VERIFY0(priv_addset(priv, PRIV_PROC_CLOCK_HIGHRES));
if (setppriv(PRIV_ON, PRIV_INHERITABLE, priv) != 0) {
err(EXIT_FAILURE, "TEST FAILED: %s: failed to add privs to "
"the inheritable set", desc);
}
priv_basicset(priv);
if (setppriv(PRIV_SET, PRIV_PERMITTED, priv) != 0) {
err(EXIT_FAILURE, "TEST FAILED: %s: failed to set permitted "
"set to the basic set", desc);
}
priv_freeset(priv);
}
static const getenv_fork_t getenv_tests[] = { {
.gf_desc = "change all to nobody",
.gf_secure = false,
.gf_forker = getenv_fork_nobody
}, {
.gf_desc = "seteuid to nobody",
.gf_secure = true,
.gf_forker = getenv_fork_seteuid
}, {
.gf_desc = "setegid to nobody",
.gf_secure = true,
.gf_forker = getenv_fork_setegid
}, {
.gf_desc = "sete[ug]id to nobody",
.gf_secure = true,
.gf_forker = getenv_fork_seteugid
}, {
.gf_desc = "privilege increase",
.gf_secure = true,
.gf_forker = getenv_fork_privs
} };
static bool
getenv_fork(const getenv_fork_t *test)
{
pid_t child;
siginfo_t cret;
child = forkx(FORK_NOSIGCHLD | FORK_WAITPID);
if (child == 0) {
char *argv[4] = { (char *)getenv_checker, (char *)test->gf_desc,
NULL, NULL };
if (test->gf_secure) {
argv[2] = "secure";
}
test->gf_forker(test->gf_desc);
(void) execv(getenv_path, argv);
warn("TEST FAILED: %s: failed to exec verifier %s",
test->gf_desc, getenv_path);
_exit(EXIT_FAILURE);
}
if (waitid(P_PID, child, &cret, WEXITED) < 0) {
err(EXIT_FAILURE, "TEST FAILED: internal test failure waiting "
"for forked child to report");
}
if (cret.si_code != CLD_EXITED) {
warnx("TEST FAILED: %s: child process did not successfully "
"exit: found si_code: %d", test->gf_desc, cret.si_code);
return (false);
} else if (cret.si_status != 0) {
warnx("TEST FAILED: %s: child process did not exit with code "
"0: found %d", test->gf_desc, cret.si_status);
return (false);
}
return (true);
}
static void
getenv_getpath(void)
{
ssize_t ret;
char dir[PATH_MAX];
ret = readlink("/proc/self/path/a.out", dir, sizeof (dir));
if (ret < 0) {
err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to read our "
"a.out path from /proc");
} else if (ret == 0) {
errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: reading "
"/proc/self/path/a.out returned 0 bytes");
} else if (ret == sizeof (dir)) {
errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: Using "
"/proc/self/path/a.out requires truncation");
}
dir[ret] = '\0';
if (snprintf(getenv_path, sizeof (getenv_path), "%s/%s", dirname(dir),
getenv_checker) >= sizeof (getenv_path)) {
errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: constructing path "
"for child process would overflow internal buffer");
}
}
int
main(void)
{
int ret = EXIT_SUCCESS;
(void) clearenv();
if (putenv("SECRET=keep it safe") != 0) {
errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to put "
"environment variable");
}
VERIFY3P(getenv("SECRET"), !=, NULL);
getenv_getpath();
if ((getenv_nobody = getpwnam("nobody")) == NULL) {
err(EXIT_FAILURE, "failed to get passwd entry for nobody");
}
for (size_t i = 0; i < ARRAY_SIZE(getenv_tests); i++) {
if (!getenv_fork(&getenv_tests[i]))
ret = EXIT_FAILURE;
}
if (ret == EXIT_SUCCESS) {
(void) printf("All tests passed successfully\n");
}
return (ret);
}