566 lines
13 KiB
C
566 lines
13 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <spawn.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <sched.h>
|
|
|
|
#ifdef __FreeBSD__
|
|
#include <sys/jail.h>
|
|
#include <sys/syscall.h>
|
|
#endif
|
|
|
|
extern char **environ;
|
|
|
|
static int failures = 0;
|
|
|
|
static void report_value(const char *key, const char *value)
|
|
{
|
|
printf("%s=%s\n", key, value);
|
|
}
|
|
|
|
static void report_ok(const char *key)
|
|
{
|
|
printf("%s=ok\n", key);
|
|
}
|
|
|
|
static void report_fail(const char *key, const char *detail)
|
|
{
|
|
failures++;
|
|
printf("%s=fail:%s\n", key, detail);
|
|
}
|
|
|
|
static void report_errno_detail(const char *key, const char *context)
|
|
{
|
|
char buffer[256];
|
|
snprintf(buffer, sizeof(buffer), "%s:%s", context, strerror(errno));
|
|
report_fail(key, buffer);
|
|
}
|
|
|
|
static void report_feature_macros(void)
|
|
{
|
|
#ifdef SYS_clone
|
|
report_value("feature.SYS_clone", "yes");
|
|
#else
|
|
report_value("feature.SYS_clone", "no");
|
|
#endif
|
|
|
|
#ifdef SYS_unshare
|
|
report_value("feature.SYS_unshare", "yes");
|
|
#else
|
|
report_value("feature.SYS_unshare", "no");
|
|
#endif
|
|
|
|
#ifdef SYS_setns
|
|
report_value("feature.SYS_setns", "yes");
|
|
#else
|
|
report_value("feature.SYS_setns", "no");
|
|
#endif
|
|
|
|
#ifdef SYS_pivot_root
|
|
report_value("feature.SYS_pivot_root", "yes");
|
|
#else
|
|
report_value("feature.SYS_pivot_root", "no");
|
|
#endif
|
|
|
|
#ifdef __FreeBSD__
|
|
report_value("feature.FreeBSD_jail", "yes");
|
|
#else
|
|
report_value("feature.FreeBSD_jail", "no");
|
|
#endif
|
|
|
|
#if defined(__has_include)
|
|
# if __has_include(<sys/capsicum.h>)
|
|
report_value("feature.Capsicum_headers", "yes");
|
|
# else
|
|
report_value("feature.Capsicum_headers", "no");
|
|
# endif
|
|
# if __has_include(<sys/prctl.h>)
|
|
report_value("feature.sys_prctl_h", "yes");
|
|
# else
|
|
report_value("feature.sys_prctl_h", "no");
|
|
# endif
|
|
# if __has_include(<linux/close_range.h>)
|
|
report_value("feature.linux_close_range_h", "yes");
|
|
# else
|
|
report_value("feature.linux_close_range_h", "no");
|
|
# endif
|
|
#else
|
|
report_value("feature.Capsicum_headers", "unknown");
|
|
report_value("feature.sys_prctl_h", "unknown");
|
|
report_value("feature.linux_close_range_h", "unknown");
|
|
#endif
|
|
}
|
|
|
|
static void test_fork_waitpid(void)
|
|
{
|
|
int fds[2];
|
|
pid_t pid;
|
|
char buf[16] = {0};
|
|
int status = 0;
|
|
|
|
if (pipe(fds) != 0) {
|
|
report_errno_detail("runtime.fork_waitpid", "pipe");
|
|
return;
|
|
}
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
report_errno_detail("runtime.fork_waitpid", "fork");
|
|
return;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
close(fds[0]);
|
|
(void)write(fds[1], "child-ok", 8);
|
|
close(fds[1]);
|
|
_exit(0);
|
|
}
|
|
|
|
close(fds[1]);
|
|
if (read(fds[0], buf, sizeof(buf)) < 0) {
|
|
close(fds[0]);
|
|
report_errno_detail("runtime.fork_waitpid", "read");
|
|
return;
|
|
}
|
|
close(fds[0]);
|
|
|
|
if (waitpid(pid, &status, 0) < 0) {
|
|
report_errno_detail("runtime.fork_waitpid", "waitpid");
|
|
return;
|
|
}
|
|
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0 || strcmp(buf, "child-ok") != 0) {
|
|
report_fail("runtime.fork_waitpid", "unexpected-child-result");
|
|
return;
|
|
}
|
|
|
|
report_ok("runtime.fork_waitpid");
|
|
}
|
|
|
|
static void test_posix_spawn_closefrom(void)
|
|
{
|
|
posix_spawn_file_actions_t actions;
|
|
pid_t pid = -1;
|
|
int status = 0;
|
|
|
|
if (posix_spawn_file_actions_init(&actions) != 0) {
|
|
report_fail("runtime.posix_spawn_addclosefrom_np", "file-actions-init");
|
|
return;
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
if (posix_spawn_file_actions_addclosefrom_np(&actions, 3) != 0) {
|
|
posix_spawn_file_actions_destroy(&actions);
|
|
report_fail("runtime.posix_spawn_addclosefrom_np", "addclosefrom_np");
|
|
return;
|
|
}
|
|
#else
|
|
posix_spawn_file_actions_destroy(&actions);
|
|
report_value("runtime.posix_spawn_addclosefrom_np", "skipped");
|
|
return;
|
|
#endif
|
|
|
|
if (posix_spawn(&pid, "/usr/bin/true", &actions, NULL,
|
|
(char *const[]){(char *)"/usr/bin/true", NULL}, environ) != 0) {
|
|
posix_spawn_file_actions_destroy(&actions);
|
|
report_errno_detail("runtime.posix_spawn_addclosefrom_np", "posix_spawn");
|
|
return;
|
|
}
|
|
|
|
posix_spawn_file_actions_destroy(&actions);
|
|
|
|
if (waitpid(pid, &status, 0) < 0) {
|
|
report_errno_detail("runtime.posix_spawn_addclosefrom_np", "waitpid");
|
|
return;
|
|
}
|
|
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
|
report_fail("runtime.posix_spawn_addclosefrom_np", "child-nonzero");
|
|
return;
|
|
}
|
|
|
|
report_ok("runtime.posix_spawn_addclosefrom_np");
|
|
}
|
|
|
|
static void test_close_range(void)
|
|
{
|
|
int fds[2];
|
|
int fd3;
|
|
|
|
if (pipe(fds) != 0) {
|
|
report_errno_detail("runtime.close_range", "pipe");
|
|
return;
|
|
}
|
|
|
|
fd3 = dup(fds[0]);
|
|
if (fd3 < 0) {
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
report_errno_detail("runtime.close_range", "dup");
|
|
return;
|
|
}
|
|
|
|
if (close_range((unsigned int)fds[0], (unsigned int)fd3, 0) != 0) {
|
|
report_errno_detail("runtime.close_range", "close_range");
|
|
return;
|
|
}
|
|
|
|
if (fcntl(fds[0], F_GETFD) != -1 || errno != EBADF) {
|
|
report_fail("runtime.close_range", "fd-not-closed");
|
|
return;
|
|
}
|
|
|
|
report_ok("runtime.close_range");
|
|
}
|
|
|
|
static void test_posix_fallocate(void)
|
|
{
|
|
char path[] = "/tmp/fruix-posix-fallocate.XXXXXX";
|
|
int fd;
|
|
struct stat st;
|
|
|
|
fd = mkstemp(path);
|
|
if (fd < 0) {
|
|
report_errno_detail("runtime.posix_fallocate", "mkstemp");
|
|
return;
|
|
}
|
|
|
|
{
|
|
int rc = posix_fallocate(fd, 0, 4096);
|
|
if (rc != 0) {
|
|
close(fd);
|
|
unlink(path);
|
|
if (rc == EOPNOTSUPP || rc == ENOTSUP) {
|
|
report_value("runtime.posix_fallocate", "unsupported-on-tested-filesystem");
|
|
return;
|
|
}
|
|
report_fail("runtime.posix_fallocate", strerror(rc));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (fstat(fd, &st) != 0) {
|
|
close(fd);
|
|
unlink(path);
|
|
report_errno_detail("runtime.posix_fallocate", "fstat");
|
|
return;
|
|
}
|
|
|
|
close(fd);
|
|
unlink(path);
|
|
|
|
if (st.st_size < 4096) {
|
|
report_fail("runtime.posix_fallocate", "short-size");
|
|
return;
|
|
}
|
|
|
|
report_ok("runtime.posix_fallocate");
|
|
}
|
|
|
|
static void test_lutimes(void)
|
|
{
|
|
char dir[] = "/tmp/fruix-lutimes.XXXXXX";
|
|
char target[PATH_MAX];
|
|
char linkpath[PATH_MAX];
|
|
struct timeval times[2];
|
|
|
|
if (mkdtemp(dir) == NULL) {
|
|
report_errno_detail("runtime.lutimes", "mkdtemp");
|
|
return;
|
|
}
|
|
|
|
snprintf(target, sizeof(target), "%s/target", dir);
|
|
snprintf(linkpath, sizeof(linkpath), "%s/link", dir);
|
|
|
|
{
|
|
int fd = open(target, O_CREAT | O_WRONLY, 0600);
|
|
if (fd < 0) {
|
|
report_errno_detail("runtime.lutimes", "open-target");
|
|
return;
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
if (symlink(target, linkpath) != 0) {
|
|
report_errno_detail("runtime.lutimes", "symlink");
|
|
unlink(target);
|
|
rmdir(dir);
|
|
return;
|
|
}
|
|
|
|
times[0].tv_sec = 1;
|
|
times[0].tv_usec = 0;
|
|
times[1].tv_sec = 2;
|
|
times[1].tv_usec = 0;
|
|
|
|
if (lutimes(linkpath, times) != 0) {
|
|
report_errno_detail("runtime.lutimes", "lutimes");
|
|
unlink(linkpath);
|
|
unlink(target);
|
|
rmdir(dir);
|
|
return;
|
|
}
|
|
|
|
unlink(linkpath);
|
|
unlink(target);
|
|
rmdir(dir);
|
|
report_ok("runtime.lutimes");
|
|
}
|
|
|
|
static void test_statvfs(void)
|
|
{
|
|
struct statvfs st;
|
|
|
|
if (statvfs("/tmp", &st) != 0) {
|
|
report_errno_detail("runtime.statvfs", "statvfs");
|
|
return;
|
|
}
|
|
|
|
if (st.f_bsize == 0) {
|
|
report_fail("runtime.statvfs", "zero-block-size");
|
|
return;
|
|
}
|
|
|
|
report_ok("runtime.statvfs");
|
|
}
|
|
|
|
static void test_chroot_smoke(void)
|
|
{
|
|
char top[] = "/tmp/fruix-chroot.XXXXXX";
|
|
char rootdir[PATH_MAX];
|
|
char etcdir[PATH_MAX];
|
|
char passwdpath[PATH_MAX];
|
|
char outside[PATH_MAX];
|
|
pid_t pid;
|
|
int status = 0;
|
|
|
|
if (mkdtemp(top) == NULL) {
|
|
report_errno_detail("root.chroot", "mkdtemp");
|
|
return;
|
|
}
|
|
|
|
snprintf(rootdir, sizeof(rootdir), "%s/root", top);
|
|
snprintf(etcdir, sizeof(etcdir), "%s/etc", rootdir);
|
|
snprintf(passwdpath, sizeof(passwdpath), "%s/passwd", etcdir);
|
|
snprintf(outside, sizeof(outside), "%s/outside-sentinel", top);
|
|
|
|
if (mkdir(rootdir, 0755) != 0 || mkdir(etcdir, 0755) != 0) {
|
|
report_errno_detail("root.chroot", "mkdir");
|
|
return;
|
|
}
|
|
|
|
{
|
|
int fd = open(passwdpath, O_CREAT | O_WRONLY, 0644);
|
|
if (fd < 0) {
|
|
report_errno_detail("root.chroot", "open-passwd");
|
|
return;
|
|
}
|
|
(void)write(fd, "root:x:0:0::/:/bin/sh\n", 22);
|
|
close(fd);
|
|
}
|
|
|
|
{
|
|
int fd = open(outside, O_CREAT | O_WRONLY, 0644);
|
|
if (fd < 0) {
|
|
report_errno_detail("root.chroot", "open-outside");
|
|
return;
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
report_errno_detail("root.chroot", "fork");
|
|
return;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
if (chdir(rootdir) != 0)
|
|
_exit(10);
|
|
if (chroot(".") != 0)
|
|
_exit(11);
|
|
if (access("/etc/passwd", F_OK) != 0)
|
|
_exit(12);
|
|
if (access("/outside-sentinel", F_OK) == 0)
|
|
_exit(13);
|
|
_exit(0);
|
|
}
|
|
|
|
if (waitpid(pid, &status, 0) < 0) {
|
|
report_errno_detail("root.chroot", "waitpid");
|
|
return;
|
|
}
|
|
|
|
unlink(outside);
|
|
unlink(passwdpath);
|
|
rmdir(etcdir);
|
|
rmdir(rootdir);
|
|
rmdir(top);
|
|
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
|
report_fail("root.chroot", "child-failed");
|
|
return;
|
|
}
|
|
|
|
report_ok("root.chroot");
|
|
}
|
|
|
|
#ifdef __FreeBSD__
|
|
static void test_jail_smoke(void)
|
|
{
|
|
char top[] = "/tmp/fruix-jail.XXXXXX";
|
|
struct jail j;
|
|
pid_t pid;
|
|
int status = 0;
|
|
|
|
if (mkdtemp(top) == NULL) {
|
|
report_errno_detail("root.jail", "mkdtemp");
|
|
return;
|
|
}
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
report_errno_detail("root.jail", "fork");
|
|
rmdir(top);
|
|
return;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
char hostname[] = "fruix-jail";
|
|
char jailname[] = "fruix-jail";
|
|
char observed[256] = {0};
|
|
|
|
memset(&j, 0, sizeof(j));
|
|
j.version = JAIL_API_VERSION;
|
|
j.path = top;
|
|
j.hostname = hostname;
|
|
j.jailname = jailname;
|
|
j.ip4s = 0;
|
|
j.ip6s = 0;
|
|
j.ip4 = NULL;
|
|
j.ip6 = NULL;
|
|
|
|
if (jail(&j) < 0)
|
|
_exit(20);
|
|
if (gethostname(observed, sizeof(observed)) != 0)
|
|
_exit(21);
|
|
if (strcmp(observed, hostname) != 0)
|
|
_exit(22);
|
|
_exit(0);
|
|
}
|
|
|
|
if (waitpid(pid, &status, 0) < 0) {
|
|
report_errno_detail("root.jail", "waitpid");
|
|
rmdir(top);
|
|
return;
|
|
}
|
|
|
|
rmdir(top);
|
|
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
|
report_fail("root.jail", "child-failed");
|
|
return;
|
|
}
|
|
|
|
report_ok("root.jail");
|
|
}
|
|
#else
|
|
static void test_jail_smoke(void)
|
|
{
|
|
report_value("root.jail", "skipped");
|
|
}
|
|
#endif
|
|
|
|
static void test_lchown_root(void)
|
|
{
|
|
char dir[] = "/tmp/fruix-lchown.XXXXXX";
|
|
char target[PATH_MAX];
|
|
char linkpath[PATH_MAX];
|
|
|
|
if (mkdtemp(dir) == NULL) {
|
|
report_errno_detail("root.lchown", "mkdtemp");
|
|
return;
|
|
}
|
|
|
|
snprintf(target, sizeof(target), "%s/target", dir);
|
|
snprintf(linkpath, sizeof(linkpath), "%s/link", dir);
|
|
|
|
{
|
|
int fd = open(target, O_CREAT | O_WRONLY, 0600);
|
|
if (fd < 0) {
|
|
report_errno_detail("root.lchown", "open-target");
|
|
return;
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
if (symlink(target, linkpath) != 0) {
|
|
report_errno_detail("root.lchown", "symlink");
|
|
unlink(target);
|
|
rmdir(dir);
|
|
return;
|
|
}
|
|
|
|
if (lchown(linkpath, getuid(), getgid()) != 0) {
|
|
report_errno_detail("root.lchown", "lchown");
|
|
unlink(linkpath);
|
|
unlink(target);
|
|
rmdir(dir);
|
|
return;
|
|
}
|
|
|
|
unlink(linkpath);
|
|
unlink(target);
|
|
rmdir(dir);
|
|
report_ok("root.lchown");
|
|
}
|
|
|
|
static int run_regular_tests(void)
|
|
{
|
|
report_feature_macros();
|
|
test_fork_waitpid();
|
|
test_posix_spawn_closefrom();
|
|
test_close_range();
|
|
test_posix_fallocate();
|
|
test_lutimes();
|
|
test_statvfs();
|
|
return failures == 0 ? 0 : 1;
|
|
}
|
|
|
|
static int run_root_tests(void)
|
|
{
|
|
if (geteuid() != 0) {
|
|
fprintf(stderr, "root tests require euid 0\n");
|
|
return 2;
|
|
}
|
|
|
|
test_chroot_smoke();
|
|
test_jail_smoke();
|
|
test_lchown_root();
|
|
return failures == 0 ? 0 : 1;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
if (argc > 1 && strcmp(argv[1], "--root-tests") == 0)
|
|
return run_root_tests();
|
|
|
|
return run_regular_tests();
|
|
}
|