Systemd/src/test/test-seccomp.c
Topi Miettinen 3c14dc61f7 tests: various small fixes for strict systems
Don't assume that 4MB can be allocated from stack since there could be smaller
DefaultLimitSTACK= in force, so let's use malloc(). NUL terminate the huge
strings by hand, also ensure termination in test_lz4_decompress_partial() and
optimize the memset() for the string.

Some items in /proc and /etc may not be accessible to poor unprivileged users
due to e.g. SELinux, BOFH or both, so check for EACCES and EPERM.

/var/tmp may be a symlink to /tmp and then path_compare() will always fail, so
let's stick to /tmp like elsewhere.

/tmp may be mounted with noexec option and then trying to execute scripts from
there would fail.

Detect and warn if seccomp is already in use, which could make seccomp test
fail if the syscalls are already blocked.

Unset $TMPDIR so it will not break specifier tests where %T is assumed to be
/tmp and %V /var/tmp.
2020-04-26 20:18:48 +02:00

1047 lines
39 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fcntl.h>
#include <poll.h>
#include <stdlib.h>
#include <sys/eventfd.h>
#include <sys/mman.h>
#include <sys/personality.h>
#include <sys/shm.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include "alloc-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "macro.h"
#include "memory-util.h"
#include "missing_sched.h"
#include "nsflags.h"
#include "nulstr-util.h"
#include "process-util.h"
#include "raw-clone.h"
#include "rm-rf.h"
#include "seccomp-util.h"
#include "set.h"
#include "string-util.h"
#include "tests.h"
#include "tmpfile-util.h"
#include "virt.h"
/* __NR_socket may be invalid due to libseccomp */
#if !defined(__NR_socket) || __NR_socket < 0 || defined(__i386__) || defined(__s390x__) || defined(__s390__)
/* On these archs, socket() is implemented via the socketcall() syscall multiplexer,
* and we can't restrict it hence via seccomp. */
# define SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN 1
#else
# define SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN 0
#endif
static void test_seccomp_arch_to_string(void) {
uint32_t a, b;
const char *name;
log_info("/* %s */", __func__);
a = seccomp_arch_native();
assert_se(a > 0);
name = seccomp_arch_to_string(a);
assert_se(name);
assert_se(seccomp_arch_from_string(name, &b) >= 0);
assert_se(a == b);
}
static void test_architecture_table(void) {
const char *n, *n2;
log_info("/* %s */", __func__);
NULSTR_FOREACH(n,
"native\0"
"x86\0"
"x86-64\0"
"x32\0"
"arm\0"
"arm64\0"
"mips\0"
"mips64\0"
"mips64-n32\0"
"mips-le\0"
"mips64-le\0"
"mips64-le-n32\0"
"ppc\0"
"ppc64\0"
"ppc64-le\0"
"s390\0"
"s390x\0") {
uint32_t c;
assert_se(seccomp_arch_from_string(n, &c) >= 0);
n2 = seccomp_arch_to_string(c);
log_info("seccomp-arch: %s → 0x%"PRIx32" → %s", n, c, n2);
assert_se(streq_ptr(n, n2));
}
}
static void test_syscall_filter_set_find(void) {
log_info("/* %s */", __func__);
assert_se(!syscall_filter_set_find(NULL));
assert_se(!syscall_filter_set_find(""));
assert_se(!syscall_filter_set_find("quux"));
assert_se(!syscall_filter_set_find("@quux"));
assert_se(syscall_filter_set_find("@clock") == syscall_filter_sets + SYSCALL_FILTER_SET_CLOCK);
assert_se(syscall_filter_set_find("@default") == syscall_filter_sets + SYSCALL_FILTER_SET_DEFAULT);
assert_se(syscall_filter_set_find("@raw-io") == syscall_filter_sets + SYSCALL_FILTER_SET_RAW_IO);
}
static void test_filter_sets(void) {
unsigned i;
int r;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
for (i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) {
pid_t pid;
log_info("Testing %s", syscall_filter_sets[i].name);
pid = fork();
assert_se(pid >= 0);
if (pid == 0) { /* Child? */
int fd;
/* If we look at the default set (or one that includes it), whitelist instead of blacklist */
if (IN_SET(i, SYSCALL_FILTER_SET_DEFAULT, SYSCALL_FILTER_SET_SYSTEM_SERVICE))
r = seccomp_load_syscall_filter_set(SCMP_ACT_ERRNO(EUCLEAN), syscall_filter_sets + i, SCMP_ACT_ALLOW, true);
else
r = seccomp_load_syscall_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + i, SCMP_ACT_ERRNO(EUCLEAN), true);
if (r < 0)
_exit(EXIT_FAILURE);
/* Test the sycall filter with one random system call */
fd = eventfd(0, EFD_NONBLOCK|EFD_CLOEXEC);
if (IN_SET(i, SYSCALL_FILTER_SET_IO_EVENT, SYSCALL_FILTER_SET_DEFAULT))
assert_se(fd < 0 && errno == EUCLEAN);
else {
assert_se(fd >= 0);
safe_close(fd);
}
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check(syscall_filter_sets[i].name, pid, WAIT_LOG) == EXIT_SUCCESS);
}
}
static void test_filter_sets_ordered(void) {
size_t i;
log_info("/* %s */", __func__);
/* Ensure "@default" always remains at the beginning of the list */
assert_se(SYSCALL_FILTER_SET_DEFAULT == 0);
assert_se(streq(syscall_filter_sets[0].name, "@default"));
for (i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) {
const char *k, *p = NULL;
/* Make sure each group has a description */
assert_se(!isempty(syscall_filter_sets[0].help));
/* Make sure the groups are ordered alphabetically, except for the first entry */
assert_se(i < 2 || strcmp(syscall_filter_sets[i-1].name, syscall_filter_sets[i].name) < 0);
NULSTR_FOREACH(k, syscall_filter_sets[i].value) {
/* Ensure each syscall list is in itself ordered, but groups before names */
assert_se(!p ||
(*p == '@' && *k != '@') ||
(((*p == '@' && *k == '@') ||
(*p != '@' && *k != '@')) &&
strcmp(p, k) < 0));
p = k;
}
}
}
static void test_restrict_namespace(void) {
char *s = NULL;
unsigned long ul;
pid_t pid;
if (!have_namespaces()) {
log_notice("Testing without namespaces, skipping %s", __func__);
return;
}
log_info("/* %s */", __func__);
assert_se(namespace_flags_to_string(0, &s) == 0 && isempty(s));
s = mfree(s);
assert_se(namespace_flags_to_string(CLONE_NEWNS, &s) == 0 && streq(s, "mnt"));
s = mfree(s);
assert_se(namespace_flags_to_string(CLONE_NEWNS|CLONE_NEWIPC, &s) == 0 && streq(s, "ipc mnt"));
s = mfree(s);
assert_se(namespace_flags_to_string(CLONE_NEWCGROUP, &s) == 0 && streq(s, "cgroup"));
s = mfree(s);
assert_se(namespace_flags_from_string("mnt", &ul) == 0 && ul == CLONE_NEWNS);
assert_se(namespace_flags_from_string(NULL, &ul) == 0 && ul == 0);
assert_se(namespace_flags_from_string("", &ul) == 0 && ul == 0);
assert_se(namespace_flags_from_string("uts", &ul) == 0 && ul == CLONE_NEWUTS);
assert_se(namespace_flags_from_string("mnt uts ipc", &ul) == 0 && ul == (CLONE_NEWNS|CLONE_NEWUTS|CLONE_NEWIPC));
assert_se(namespace_flags_to_string(CLONE_NEWUTS, &s) == 0 && streq(s, "uts"));
assert_se(namespace_flags_from_string(s, &ul) == 0 && ul == CLONE_NEWUTS);
s = mfree(s);
assert_se(namespace_flags_from_string("ipc", &ul) == 0 && ul == CLONE_NEWIPC);
assert_se(namespace_flags_to_string(ul, &s) == 0 && streq(s, "ipc"));
s = mfree(s);
assert_se(namespace_flags_to_string(NAMESPACE_FLAGS_ALL, &s) == 0);
assert_se(streq(s, "cgroup ipc net mnt pid user uts"));
assert_se(namespace_flags_from_string(s, &ul) == 0 && ul == NAMESPACE_FLAGS_ALL);
s = mfree(s);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping remaining tests in %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping remaining tests in %s", __func__);
return;
}
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
assert_se(seccomp_restrict_namespaces(CLONE_NEWNS|CLONE_NEWNET) >= 0);
assert_se(unshare(CLONE_NEWNS) == 0);
assert_se(unshare(CLONE_NEWNET) == 0);
assert_se(unshare(CLONE_NEWUTS) == -1);
assert_se(errno == EPERM);
assert_se(unshare(CLONE_NEWIPC) == -1);
assert_se(errno == EPERM);
assert_se(unshare(CLONE_NEWNET|CLONE_NEWUTS) == -1);
assert_se(errno == EPERM);
/* We use fd 0 (stdin) here, which of course will fail with EINVAL on setns(). Except of course our
* seccomp filter worked, and hits first and makes it return EPERM */
assert_se(setns(0, CLONE_NEWNS) == -1);
assert_se(errno == EINVAL);
assert_se(setns(0, CLONE_NEWNET) == -1);
assert_se(errno == EINVAL);
assert_se(setns(0, CLONE_NEWUTS) == -1);
assert_se(errno == EPERM);
assert_se(setns(0, CLONE_NEWIPC) == -1);
assert_se(errno == EPERM);
assert_se(setns(0, CLONE_NEWNET|CLONE_NEWUTS) == -1);
assert_se(errno == EPERM);
assert_se(setns(0, 0) == -1);
assert_se(errno == EPERM);
pid = raw_clone(CLONE_NEWNS);
assert_se(pid >= 0);
if (pid == 0)
_exit(EXIT_SUCCESS);
pid = raw_clone(CLONE_NEWNET);
assert_se(pid >= 0);
if (pid == 0)
_exit(EXIT_SUCCESS);
pid = raw_clone(CLONE_NEWUTS);
assert_se(pid < 0);
assert_se(errno == EPERM);
pid = raw_clone(CLONE_NEWIPC);
assert_se(pid < 0);
assert_se(errno == EPERM);
pid = raw_clone(CLONE_NEWNET|CLONE_NEWUTS);
assert_se(pid < 0);
assert_se(errno == EPERM);
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("nsseccomp", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static void test_protect_sysctl(void) {
pid_t pid;
_cleanup_free_ char *seccomp = NULL;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
/* in containers _sysctl() is likely missing anyway */
if (detect_container() > 0) {
log_notice("Testing in container, skipping %s", __func__);
return;
}
assert_se(get_proc_field("/proc/self/status", "Seccomp", WHITESPACE, &seccomp) == 0);
if (!streq(seccomp, "0"))
log_warning("Warning: seccomp filter detected, results may be unreliable for %s", __func__);
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
#if defined __NR__sysctl && __NR__sysctl >= 0
assert_se(syscall(__NR__sysctl, NULL) < 0);
assert_se(errno == EFAULT);
#endif
assert_se(seccomp_protect_sysctl() >= 0);
#if defined __NR__sysctl && __NR__sysctl >= 0
assert_se(syscall(__NR__sysctl, 0, 0, 0) < 0);
assert_se(errno == EPERM);
#endif
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("sysctlseccomp", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static void test_protect_syslog(void) {
pid_t pid;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
/* in containers syslog() is likely missing anyway */
if (detect_container() > 0) {
log_notice("Testing in container, skipping %s", __func__);
return;
}
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
#if defined __NR_syslog && __NR_syslog >= 0
assert_se(syscall(__NR_syslog, -1, NULL, 0) < 0);
assert_se(errno == EINVAL);
#endif
assert_se(seccomp_protect_syslog() >= 0);
#if defined __NR_syslog && __NR_syslog >= 0
assert_se(syscall(__NR_syslog, 0, 0, 0) < 0);
assert_se(errno == EPERM);
#endif
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("syslogseccomp", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static void test_restrict_address_families(void) {
pid_t pid;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
int fd;
Set *s;
fd = socket(AF_INET, SOCK_DGRAM, 0);
assert_se(fd >= 0);
safe_close(fd);
fd = socket(AF_UNIX, SOCK_DGRAM, 0);
assert_se(fd >= 0);
safe_close(fd);
fd = socket(AF_NETLINK, SOCK_DGRAM, 0);
assert_se(fd >= 0);
safe_close(fd);
assert_se(s = set_new(NULL));
assert_se(set_put(s, INT_TO_PTR(AF_UNIX)) >= 0);
assert_se(seccomp_restrict_address_families(s, false) >= 0);
fd = socket(AF_INET, SOCK_DGRAM, 0);
assert_se(fd >= 0);
safe_close(fd);
fd = socket(AF_UNIX, SOCK_DGRAM, 0);
#if SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN
assert_se(fd >= 0);
safe_close(fd);
#else
assert_se(fd < 0);
assert_se(errno == EAFNOSUPPORT);
#endif
fd = socket(AF_NETLINK, SOCK_DGRAM, 0);
assert_se(fd >= 0);
safe_close(fd);
set_clear(s);
assert_se(set_put(s, INT_TO_PTR(AF_INET)) >= 0);
assert_se(seccomp_restrict_address_families(s, true) >= 0);
fd = socket(AF_INET, SOCK_DGRAM, 0);
assert_se(fd >= 0);
safe_close(fd);
fd = socket(AF_UNIX, SOCK_DGRAM, 0);
#if SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN
assert_se(fd >= 0);
safe_close(fd);
#else
assert_se(fd < 0);
assert_se(errno == EAFNOSUPPORT);
#endif
fd = socket(AF_NETLINK, SOCK_DGRAM, 0);
#if SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN
assert_se(fd >= 0);
safe_close(fd);
#else
assert_se(fd < 0);
assert_se(errno == EAFNOSUPPORT);
#endif
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("socketseccomp", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static void test_restrict_realtime(void) {
pid_t pid;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
/* in containers RT privs are likely missing anyway */
if (detect_container() > 0) {
log_notice("Testing in container, skipping %s", __func__);
return;
}
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
assert_se(sched_setscheduler(0, SCHED_FIFO, &(struct sched_param) { .sched_priority = 1 }) >= 0);
assert_se(sched_setscheduler(0, SCHED_RR, &(struct sched_param) { .sched_priority = 1 }) >= 0);
assert_se(sched_setscheduler(0, SCHED_IDLE, &(struct sched_param) { .sched_priority = 0 }) >= 0);
assert_se(sched_setscheduler(0, SCHED_BATCH, &(struct sched_param) { .sched_priority = 0 }) >= 0);
assert_se(sched_setscheduler(0, SCHED_OTHER, &(struct sched_param) {}) >= 0);
assert_se(seccomp_restrict_realtime() >= 0);
assert_se(sched_setscheduler(0, SCHED_IDLE, &(struct sched_param) { .sched_priority = 0 }) >= 0);
assert_se(sched_setscheduler(0, SCHED_BATCH, &(struct sched_param) { .sched_priority = 0 }) >= 0);
assert_se(sched_setscheduler(0, SCHED_OTHER, &(struct sched_param) {}) >= 0);
assert_se(sched_setscheduler(0, SCHED_FIFO, &(struct sched_param) { .sched_priority = 1 }) < 0);
assert_se(errno == EPERM);
assert_se(sched_setscheduler(0, SCHED_RR, &(struct sched_param) { .sched_priority = 1 }) < 0);
assert_se(errno == EPERM);
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("realtimeseccomp", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static void test_memory_deny_write_execute_mmap(void) {
pid_t pid;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
void *p;
p = mmap(NULL, page_size(), PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1,0);
assert_se(p != MAP_FAILED);
assert_se(munmap(p, page_size()) >= 0);
p = mmap(NULL, page_size(), PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1,0);
assert_se(p != MAP_FAILED);
assert_se(munmap(p, page_size()) >= 0);
assert_se(seccomp_memory_deny_write_execute() >= 0);
p = mmap(NULL, page_size(), PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1,0);
#if defined(__x86_64__) || defined(__i386__) || defined(__powerpc64__) || defined(__arm__) || defined(__aarch64__)
assert_se(p == MAP_FAILED);
assert_se(errno == EPERM);
#endif
/* Depending on kernel, libseccomp, and glibc versions, other architectures
* might fail or not. Let's not assert success. */
if (p != MAP_FAILED)
assert_se(munmap(p, page_size()) == 0);
p = mmap(NULL, page_size(), PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1,0);
assert_se(p != MAP_FAILED);
assert_se(munmap(p, page_size()) >= 0);
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("memoryseccomp-mmap", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static void test_memory_deny_write_execute_shmat(void) {
int shmid;
pid_t pid;
uint32_t arch;
log_info("/* %s */", __func__);
SECCOMP_FOREACH_LOCAL_ARCH(arch) {
log_debug("arch %s: SCMP_SYS(mmap) = %d", seccomp_arch_to_string(arch), SCMP_SYS(mmap));
log_debug("arch %s: SCMP_SYS(mmap2) = %d", seccomp_arch_to_string(arch), SCMP_SYS(mmap2));
log_debug("arch %s: SCMP_SYS(shmget) = %d", seccomp_arch_to_string(arch), SCMP_SYS(shmget));
log_debug("arch %s: SCMP_SYS(shmat) = %d", seccomp_arch_to_string(arch), SCMP_SYS(shmat));
log_debug("arch %s: SCMP_SYS(shmdt) = %d", seccomp_arch_to_string(arch), SCMP_SYS(shmdt));
}
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
shmid = shmget(IPC_PRIVATE, page_size(), 0);
assert_se(shmid >= 0);
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
void *p;
p = shmat(shmid, NULL, 0);
assert_se(p != MAP_FAILED);
assert_se(shmdt(p) == 0);
p = shmat(shmid, NULL, SHM_EXEC);
assert_se(p != MAP_FAILED);
assert_se(shmdt(p) == 0);
assert_se(seccomp_memory_deny_write_execute() >= 0);
p = shmat(shmid, NULL, SHM_EXEC);
log_debug_errno(p == MAP_FAILED ? errno : 0, "shmat(SHM_EXEC): %m");
#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
assert_se(p == MAP_FAILED);
assert_se(errno == EPERM);
#endif
/* Depending on kernel, libseccomp, and glibc versions, other architectures
* might fail or not. Let's not assert success. */
if (p != MAP_FAILED)
assert_se(shmdt(p) == 0);
p = shmat(shmid, NULL, 0);
log_debug_errno(p == MAP_FAILED ? errno : 0, "shmat(0): %m");
assert_se(p != MAP_FAILED);
assert_se(shmdt(p) == 0);
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("memoryseccomp-shmat", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static void test_restrict_archs(void) {
pid_t pid;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
_cleanup_set_free_ Set *s = NULL;
assert_se(access("/", F_OK) >= 0);
assert_se(s = set_new(NULL));
#ifdef __x86_64__
assert_se(set_put(s, UINT32_TO_PTR(SCMP_ARCH_X86+1)) >= 0);
#endif
assert_se(seccomp_restrict_archs(s) >= 0);
assert_se(access("/", F_OK) >= 0);
assert_se(seccomp_restrict_archs(NULL) >= 0);
assert_se(access("/", F_OK) >= 0);
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("archseccomp", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static void test_load_syscall_filter_set_raw(void) {
pid_t pid;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
_cleanup_hashmap_free_ Hashmap *s = NULL;
assert_se(access("/", F_OK) >= 0);
assert_se(poll(NULL, 0, 0) == 0);
assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, NULL, scmp_act_kill_process(), true) >= 0);
assert_se(access("/", F_OK) >= 0);
assert_se(poll(NULL, 0, 0) == 0);
assert_se(s = hashmap_new(NULL));
#if defined __NR_access && __NR_access >= 0
assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_access + 1), INT_TO_PTR(-1)) >= 0);
#else
assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_faccessat + 1), INT_TO_PTR(-1)) >= 0);
#endif
assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EUCLEAN), true) >= 0);
assert_se(access("/", F_OK) < 0);
assert_se(errno == EUCLEAN);
assert_se(poll(NULL, 0, 0) == 0);
s = hashmap_free(s);
assert_se(s = hashmap_new(NULL));
#if defined __NR_access && __NR_access >= 0
assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_access + 1), INT_TO_PTR(EILSEQ)) >= 0);
#else
assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_faccessat + 1), INT_TO_PTR(EILSEQ)) >= 0);
#endif
assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EUCLEAN), true) >= 0);
assert_se(access("/", F_OK) < 0);
assert_se(errno == EILSEQ);
assert_se(poll(NULL, 0, 0) == 0);
s = hashmap_free(s);
assert_se(s = hashmap_new(NULL));
#if defined __NR_poll && __NR_poll >= 0
assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_poll + 1), INT_TO_PTR(-1)) >= 0);
#else
assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_ppoll + 1), INT_TO_PTR(-1)) >= 0);
#endif
assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EUNATCH), true) >= 0);
assert_se(access("/", F_OK) < 0);
assert_se(errno == EILSEQ);
assert_se(poll(NULL, 0, 0) < 0);
assert_se(errno == EUNATCH);
s = hashmap_free(s);
assert_se(s = hashmap_new(NULL));
#if defined __NR_poll && __NR_poll >= 0
assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_poll + 1), INT_TO_PTR(EILSEQ)) >= 0);
#else
assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_ppoll + 1), INT_TO_PTR(EILSEQ)) >= 0);
#endif
assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EUNATCH), true) >= 0);
assert_se(access("/", F_OK) < 0);
assert_se(errno == EILSEQ);
assert_se(poll(NULL, 0, 0) < 0);
assert_se(errno == EILSEQ);
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("syscallrawseccomp", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static void test_lock_personality(void) {
unsigned long current;
pid_t pid;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
assert_se(opinionated_personality(&current) >= 0);
log_info("current personality=%lu", current);
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
assert_se(seccomp_lock_personality(current) >= 0);
assert_se((unsigned long) safe_personality(current) == current);
/* Note, we also test that safe_personality() works correctly, by checkig whether errno is properly
* set, in addition to the return value */
errno = 0;
assert_se(safe_personality(PER_LINUX | ADDR_NO_RANDOMIZE) == -EPERM);
assert_se(errno == EPERM);
assert_se(safe_personality(PER_LINUX | MMAP_PAGE_ZERO) == -EPERM);
assert_se(safe_personality(PER_LINUX | ADDR_COMPAT_LAYOUT) == -EPERM);
assert_se(safe_personality(PER_LINUX | READ_IMPLIES_EXEC) == -EPERM);
assert_se(safe_personality(PER_LINUX_32BIT) == -EPERM);
assert_se(safe_personality(PER_SVR4) == -EPERM);
assert_se(safe_personality(PER_BSD) == -EPERM);
assert_se(safe_personality(current == PER_LINUX ? PER_LINUX32 : PER_LINUX) == -EPERM);
assert_se(safe_personality(PER_LINUX32_3GB) == -EPERM);
assert_se(safe_personality(PER_UW7) == -EPERM);
assert_se(safe_personality(0x42) == -EPERM);
assert_se(safe_personality(PERSONALITY_INVALID) == -EPERM); /* maybe remove this later */
assert_se((unsigned long) personality(current) == current);
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("lockpersonalityseccomp", pid, WAIT_LOG) == EXIT_SUCCESS);
}
static int real_open(const char *path, int flags, mode_t mode) {
/* glibc internally calls openat() when open() is requested. Let's hence define our own wrapper for
* testing purposes that calls the real syscall, on architectures where SYS_open is defined. On
* other architectures, let's just fall back to the glibc call. */
#if defined __NR_open && __NR_open >= 0
return (int) syscall(__NR_open, path, flags, mode);
#else
return open(path, flags, mode);
#endif
}
static void test_restrict_suid_sgid(void) {
pid_t pid;
log_info("/* %s */", __func__);
if (!is_seccomp_available()) {
log_notice("Seccomp not available, skipping %s", __func__);
return;
}
if (geteuid() != 0) {
log_notice("Not root, skipping %s", __func__);
return;
}
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
char path[] = "/tmp/suidsgidXXXXXX", dir[] = "/tmp/suidsgiddirXXXXXX";
int fd = -1, k = -1;
const char *z;
fd = mkostemp_safe(path);
assert_se(fd >= 0);
assert_se(mkdtemp(dir));
z = strjoina(dir, "/test");
assert_se(chmod(path, 0755 | S_ISUID) >= 0);
assert_se(chmod(path, 0755 | S_ISGID) >= 0);
assert_se(chmod(path, 0755 | S_ISGID | S_ISUID) >= 0);
assert_se(chmod(path, 0755) >= 0);
assert_se(fchmod(fd, 0755 | S_ISUID) >= 0);
assert_se(fchmod(fd, 0755 | S_ISGID) >= 0);
assert_se(fchmod(fd, 0755 | S_ISGID | S_ISUID) >= 0);
assert_se(fchmod(fd, 0755) >= 0);
assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISUID, 0) >= 0);
assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISGID, 0) >= 0);
assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISGID | S_ISUID, 0) >= 0);
assert_se(fchmodat(AT_FDCWD, path, 0755, 0) >= 0);
k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISGID);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID | S_ISGID);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = creat(z, 0644 | S_ISUID);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = creat(z, 0644 | S_ISGID);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = creat(z, 0644 | S_ISUID | S_ISGID);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = creat(z, 0644);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISGID);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID | S_ISGID);
k = safe_close(k);
assert_se(unlink(z) >= 0);
k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644);
k = safe_close(k);
assert_se(unlink(z) >= 0);
assert_se(mkdir(z, 0755 | S_ISUID) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mkdir(z, 0755 | S_ISGID) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mkdir(z, 0755 | S_ISUID | S_ISGID) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mkdir(z, 0755) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISUID) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISGID) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISUID | S_ISGID) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mkdirat(AT_FDCWD, z, 0755) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mknod(z, S_IFREG | 0755 | S_ISUID, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(mknod(z, S_IFREG | 0755 | S_ISGID, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(mknod(z, S_IFREG | 0755 | S_ISUID | S_ISGID, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(mknod(z, S_IFREG | 0755, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISUID, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISGID, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISUID | S_ISGID, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(seccomp_restrict_suid_sgid() >= 0);
assert_se(chmod(path, 0775 | S_ISUID) < 0 && errno == EPERM);
assert_se(chmod(path, 0775 | S_ISGID) < 0 && errno == EPERM);
assert_se(chmod(path, 0775 | S_ISGID | S_ISUID) < 0 && errno == EPERM);
assert_se(chmod(path, 0775) >= 0);
assert_se(fchmod(fd, 0775 | S_ISUID) < 0 && errno == EPERM);
assert_se(fchmod(fd, 0775 | S_ISGID) < 0 && errno == EPERM);
assert_se(fchmod(fd, 0775 | S_ISGID | S_ISUID) < 0 && errno == EPERM);
assert_se(fchmod(fd, 0775) >= 0);
assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISUID, 0) < 0 && errno == EPERM);
assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISGID, 0) < 0 && errno == EPERM);
assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISGID | S_ISUID, 0) < 0 && errno == EPERM);
assert_se(fchmodat(AT_FDCWD, path, 0755, 0) >= 0);
assert_se(real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID) < 0 && errno == EPERM);
assert_se(real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISGID) < 0 && errno == EPERM);
assert_se(real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID | S_ISGID) < 0 && errno == EPERM);
k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644);
k = safe_close(k);
assert_se(unlink(z) >= 0);
assert_se(creat(z, 0644 | S_ISUID) < 0 && errno == EPERM);
assert_se(creat(z, 0644 | S_ISGID) < 0 && errno == EPERM);
assert_se(creat(z, 0644 | S_ISUID | S_ISGID) < 0 && errno == EPERM);
k = creat(z, 0644);
k = safe_close(k);
assert_se(unlink(z) >= 0);
assert_se(openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID) < 0 && errno == EPERM);
assert_se(openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISGID) < 0 && errno == EPERM);
assert_se(openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID | S_ISGID) < 0 && errno == EPERM);
k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644);
k = safe_close(k);
assert_se(unlink(z) >= 0);
assert_se(mkdir(z, 0755 | S_ISUID) < 0 && errno == EPERM);
assert_se(mkdir(z, 0755 | S_ISGID) < 0 && errno == EPERM);
assert_se(mkdir(z, 0755 | S_ISUID | S_ISGID) < 0 && errno == EPERM);
assert_se(mkdir(z, 0755) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISUID) < 0 && errno == EPERM);
assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISGID) < 0 && errno == EPERM);
assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISUID | S_ISGID) < 0 && errno == EPERM);
assert_se(mkdirat(AT_FDCWD, z, 0755) >= 0);
assert_se(rmdir(z) >= 0);
assert_se(mknod(z, S_IFREG | 0755 | S_ISUID, 0) < 0 && errno == EPERM);
assert_se(mknod(z, S_IFREG | 0755 | S_ISGID, 0) < 0 && errno == EPERM);
assert_se(mknod(z, S_IFREG | 0755 | S_ISUID | S_ISGID, 0) < 0 && errno == EPERM);
assert_se(mknod(z, S_IFREG | 0755, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISUID, 0) < 0 && errno == EPERM);
assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISGID, 0) < 0 && errno == EPERM);
assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISUID | S_ISGID, 0) < 0 && errno == EPERM);
assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755, 0) >= 0);
assert_se(unlink(z) >= 0);
assert_se(unlink(path) >= 0);
assert_se(rm_rf(dir, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
_exit(EXIT_SUCCESS);
}
assert_se(wait_for_terminate_and_check("suidsgidseccomp", pid, WAIT_LOG) == EXIT_SUCCESS);
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
test_seccomp_arch_to_string();
test_architecture_table();
test_syscall_filter_set_find();
test_filter_sets();
test_filter_sets_ordered();
test_restrict_namespace();
test_protect_sysctl();
test_protect_syslog();
test_restrict_address_families();
test_restrict_realtime();
test_memory_deny_write_execute_mmap();
test_memory_deny_write_execute_shmat();
test_restrict_archs();
test_load_syscall_filter_set_raw();
test_lock_personality();
test_restrict_suid_sgid();
return 0;
}