#include <errno.h>
#include <signal.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/spawn_impl.h>
#include <sys/types.h>
#include <sys/wait.h>
static uint_t failures = 0;
#define TFAIL(name, fmt, ...) do { \
(void) fprintf(stderr, "TEST FAILED: %s: " fmt "\n", \
(name), ##__VA_ARGS__); \
failures++; \
} while (0)
#define TPASS(name) (void) printf("TEST PASSED: %s\n", (name))
#define SA_DATA 16
#define SP_DATA 64
static pid_t
raw_spawn(const char *path, const void *sp, uint32_t spsize, const void *sa,
uint32_t sasize)
{
return (syscall(SYS_spawn, path, sp, spsize, sa, sasize));
}
static uint32_t
valid_args(spawn_args_t *sa)
{
uint32_t sasize = sizeof (*sa) + 2;
(void) memset(sa, 0, sasize);
sa->sa_size = sasize;
sa->sa_datalen = 2;
sa->sa_arg_cnt = 1;
sa->sa_env_off = 2;
sa->sa_data[0] = 'x';
sa->sa_data[1] = '\0';
return (sasize);
}
static const char *
errname(int err)
{
const char *name = strerrorname_np(err);
return (name != NULL ? name : "?");
}
static void
expect_err(const char *name, int wanted, const char *path, const void *sp,
uint32_t spsize, const void *sa, uint32_t sasize)
{
pid_t pid = raw_spawn(path, sp, spsize, sa, sasize);
if (pid != -1) {
TFAIL(name, "spawn unexpectedly succeeded (pid %d)",
(int)pid);
(void) waitpid(pid, NULL, 0);
return;
}
if (errno != wanted) {
TFAIL(name, "got %s (%d), wanted %s (%d)", errname(errno),
errno, errname(wanted), wanted);
return;
}
TPASS(name);
}
static void
t_valid(void)
{
union {
spawn_args_t sa;
char pad[sizeof (spawn_args_t) + SA_DATA];
} au;
uint32_t sasize = valid_args(&au.sa);
int status;
pid_t pid;
pid = raw_spawn("/usr/bin/true", NULL, 0, &au.sa, sasize);
if (pid == -1) {
TFAIL("valid", "spawn failed: %s", strerror(errno));
return;
}
if (waitpid(pid, &status, 0) != pid) {
TFAIL("valid", "waitpid: %s", strerror(errno));
return;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
TFAIL("valid", "status %#x", status);
return;
}
TPASS("valid, well-formed, minimal spawn request");
}
static void
t_args_fuzz(void)
{
union {
spawn_args_t sa;
char pad[sizeof (spawn_args_t) + SA_DATA];
} au;
spawn_args_t *sa = &au.sa;
uint32_t sasize;
expect_err("null-path", EINVAL, NULL, NULL, 0, &au.sa,
valid_args(&au.sa));
expect_err("null-args", EINVAL, "/usr/bin/true", NULL, 0, NULL, 0);
sasize = valid_args(sa);
sa->sa_size = sasize + 8;
expect_err("args-size-mismatch", EINVAL, "/usr/bin/true", NULL, 0,
sa, sasize);
sasize = valid_args(sa);
sa->sa_datalen = 1;
expect_err("args-datalen-mismatch", EINVAL, "/usr/bin/true", NULL, 0,
sa, sasize);
sasize = valid_args(sa);
sa->sa_arg_cnt = 100;
expect_err("args-cnt-overrun", EINVAL, "/usr/bin/true", NULL, 0,
sa, sasize);
sasize = valid_args(sa);
sa->sa_data[1] = 'y';
expect_err("args-unterminated", EINVAL, "/usr/bin/true", NULL, 0,
sa, sasize);
sasize = valid_args(sa);
sa->sa_env_off = 100;
expect_err("args-env-off", EINVAL, "/usr/bin/true", NULL, 0,
sa, sasize);
sasize = valid_args(sa);
sa->sa_env_cnt = 7;
expect_err("args-env-cnt", EINVAL, "/usr/bin/true", NULL, 0,
sa, sasize);
sasize = valid_args(sa);
expect_err("args-e2big", E2BIG, "/usr/bin/true", NULL, 0,
sa, 0x400000);
sasize = valid_args(sa);
expect_err("args-short", EINVAL, "/usr/bin/true", NULL, 0,
sa, sizeof (*sa) - 4);
}
static void
t_param_fuzz(void)
{
union {
spawn_args_t sa;
char pad[sizeof (spawn_args_t) + SA_DATA];
} au;
union {
spawn_param_t sp;
char pad[sizeof (spawn_param_t) + SP_DATA];
} pu;
spawn_param_t *sp = &pu.sp;
uint32_t sasize = valid_args(&au.sa);
uint32_t spsize = sizeof (*sp) + SP_DATA;
kfile_attr_t *kfa;
(void) memset(&pu, 0, sizeof (pu));
sp->sp_size = spsize + 4;
sp->sp_datalen = SP_DATA;
expect_err("param-size-mismatch", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) memset(&pu, 0, sizeof (pu));
sp->sp_size = spsize;
sp->sp_datalen = SP_DATA;
sp->sp_attr_off = 60;
sp->sp_attr_len = sizeof (spawn_attr_t);
expect_err("param-attr-off", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) memset(&pu, 0, sizeof (pu));
sp->sp_size = spsize;
sp->sp_datalen = SP_DATA;
sp->sp_attr_len = 8;
expect_err("param-attr-len", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) memset(&pu, 0, sizeof (pu));
sp->sp_size = spsize;
sp->sp_datalen = SP_DATA;
sp->sp_attr_off = 0;
sp->sp_attr_len = sizeof (spawn_attr_t);
((spawn_attr_t *)&sp->sp_data[0])->sa_psflags = ~0;
expect_err("param-attr-flags", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) memset(&pu, 0, sizeof (pu));
sp->sp_size = spsize;
sp->sp_datalen = SP_DATA;
sp->sp_fattr_cnt = 2;
expect_err("param-fattr-len", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) memset(&pu, 0, sizeof (pu));
sp->sp_size = spsize;
sp->sp_datalen = SP_DATA;
sp->sp_fattr_cnt = 1;
kfa = (kfile_attr_t *)&sp->sp_data[0];
kfa->kfa_len = sizeof (*kfa);
kfa->kfa_type = 666;
expect_err("param-fattr-type", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) memset(&pu, 0, sizeof (pu));
sp->sp_size = spsize;
sp->sp_datalen = SP_DATA;
sp->sp_fattr_cnt = 1;
kfa = (kfile_attr_t *)&sp->sp_data[0];
kfa->kfa_type = FA_CHDIR;
kfa->kfa_pathsize = 4;
kfa->kfa_len = sizeof (*kfa) + 4;
(void) memcpy(kfa->kfa_path, "/tmp", 4);
expect_err("param-chdir-nul", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) memset(&pu, 0, sizeof (pu));
sp->sp_size = spsize;
sp->sp_datalen = SP_DATA;
sp->sp_shell_off = 0;
sp->sp_shell_len = 4;
(void) memcpy(sp->sp_data, "/bin", 4);
expect_err("param-shell-nul", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) memset(&pu, 0, sizeof (pu));
sp->sp_size = spsize;
sp->sp_datalen = SP_DATA;
sp->sp_path_off = 62;
sp->sp_path_len = 8;
expect_err("param-path-off", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
expect_err("param-e2big", E2BIG, "/usr/bin/true",
sp, 0x400000, &au.sa, sasize);
expect_err("param-short", EINVAL, "/usr/bin/true",
sp, sizeof (*sp) - 4, &au.sa, sasize);
}
static kspawn_sched_t *
valid_sched(spawn_param_t *sp, uint32_t spsize, int op)
{
spawn_attr_t *spa;
kspawn_sched_t *ks;
(void) memset(sp, 0, spsize);
sp->sp_size = spsize;
sp->sp_datalen = spsize - sizeof (*sp);
sp->sp_attr_off = 0;
sp->sp_attr_len = sizeof (spawn_attr_t);
spa = (spawn_attr_t *)&sp->sp_data[0];
spa->sa_psflags = POSIX_SPAWN_SETSCHEDPARAM;
sp->sp_sched_off = sizeof (spawn_attr_t);
sp->sp_sched_len = sizeof (kspawn_sched_t);
ks = (kspawn_sched_t *)&sp->sp_data[sp->sp_sched_off];
ks->ksched_op = op;
return (ks);
}
static void
t_sched_fuzz(void)
{
union {
spawn_args_t sa;
char pad[sizeof (spawn_args_t) + SA_DATA];
} au;
union {
spawn_param_t sp;
char pad[sizeof (spawn_param_t) + 128];
} pu;
spawn_param_t *sp = &pu.sp;
uint32_t sasize = valid_args(&au.sa);
uint32_t spsize = sizeof (*sp) + 128;
kspawn_sched_t *ks;
(void) valid_sched(sp, spsize, KSCHED_PARMS);
sp->sp_sched_off = sp->sp_sched_len = 0;
expect_err("sched-missing", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) valid_sched(sp, spsize, KSCHED_PARMS);
((spawn_attr_t *)&sp->sp_data[0])->sa_psflags = 0;
expect_err("sched-no-flags", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) valid_sched(sp, spsize, KSCHED_PARMS);
sp->sp_sched_len = 8;
expect_err("sched-len", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) valid_sched(sp, spsize, KSCHED_PARMS);
sp->sp_sched_off = sp->sp_datalen - 4;
expect_err("sched-off", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
(void) valid_sched(sp, spsize, 0);
expect_err("sched-op", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
ks = valid_sched(sp, spsize, KSCHED_PRIO);
ks->ksched_prio.pc_op = PC_GETPRIO;
ks->ksched_prio.pc_cid = 1;
expect_err("sched-prio-op", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
ks = valid_sched(sp, spsize, KSCHED_PRIO);
ks->ksched_prio.pc_op = PC_SETPRIO;
ks->ksched_prio.pc_cid = 999;
expect_err("sched-prio-cid", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
ks = valid_sched(sp, spsize, KSCHED_PARMS);
ks->ksched_parms.pc_cid = 999;
expect_err("sched-parms-cid", EINVAL, "/usr/bin/true",
sp, spsize, &au.sa, sasize);
}
static void
t_kill_race(void)
{
union {
spawn_args_t sa;
char pad[sizeof (spawn_args_t) + SA_DATA];
} au;
uint32_t sasize = valid_args(&au.sa);
for (int i = 0; i < 100; i++) {
int status;
pid_t pid;
pid = raw_spawn("/usr/bin/sleep", NULL, 0, &au.sa, sasize);
if (pid == -1) {
TFAIL("kill-race", "spawn failed: %s",
strerror(errno));
return;
}
(void) kill(pid, SIGKILL);
if (waitpid(pid, &status, 0) != pid) {
TFAIL("kill-race", "waitpid: %s", strerror(errno));
return;
}
if (!WIFSIGNALED(status) &&
!(WIFEXITED(status) && WEXITSTATUS(status) != 0)) {
TFAIL("kill-race", "iteration %d: status %#x",
i, status);
return;
}
}
TPASS("kill-race");
}
int
main(void)
{
t_valid();
t_args_fuzz();
t_param_fuzz();
t_sched_fuzz();
t_kill_race();
if (failures == 0) {
(void) printf("All tests passed\n");
return (EXIT_SUCCESS);
}
(void) fprintf(stderr, "%u test(s) failed\n", failures);
return (EXIT_FAILURE);
}