Files
fruix/tests/system/freebsd-syscall-mapping.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();
}