blob: fa0821c01c88438c39d09567dceacfcf09b67612 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <array>
#include <condition_variable>
#include <fstream>
#include <mutex>
#include <thread>
#include <gtest/gtest.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#ifndef SECCOMP_FILTER_FLAG_TSYNC_ESRCH
#define SECCOMP_FILTER_FLAG_TSYNC_ESRCH (1UL << 4)
#endif // SECCOMP_FILTER_FLAG_TSYNC_ESRCH
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
namespace {
// A syscall not implemented by Linux that we don't expect to be called.
#if defined(__x86_64__)
constexpr uint32_t kFilteredSyscall = SYS_vserver;
#elif defined(__aarch64__) || defined(__riscv) || defined(__arm__)
// Use the first of arch_specific_syscalls. It is not implemented on ARM64 or RISC-V.
// TODO(b/383629319): Newer revisions of bionic seem to cause a build failure as
// we can't find a reference to __NR_arch_specific_syscall. Use the underlying
// number here until we can figure this out. This value is currently sourced
// from: bionic/libc/kernel/uapi/asm-generic/unistd.h.
constexpr uint32_t kFilteredSyscall = 244;
#else
#error Unsupported Architecture
#endif
#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x[0])))
void exit_with_failure_code() {
if (testing::Test::HasFatalFailure() || testing::Test::HasNonfatalFailure()) {
exit(1);
} else {
exit(0);
}
}
// Installs a filter that blocks the given syscall.
void install_filter_block(uint32_t syscall_nr, uint32_t action) {
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
sock_filter filter[] = {
// A = seccomp_data.nr
BPF_STMT(BPF_LD | BPF_ABS | BPF_W, offsetof(struct seccomp_data, nr)),
// if (A == sysno) goto kill
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, syscall_nr, 1, 0),
// allow: return SECCOMP_RET_ALLOW
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
// kill: return the provided action
BPF_STMT(BPF_RET | BPF_K, action),
};
sock_fprog prog = {.len = ARRAY_SIZE(filter), .filter = filter};
EXPECT_EQ(0, syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog))
<< "syscall failed with errno " << errno;
}
TEST(SeccompTest, RetTrapBypassesIgn) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.ExpectSignal(SIGSYS);
helper.RunInForkedProcess([] {
std::thread t([]() {
install_filter_block(kFilteredSyscall, SECCOMP_RET_TRAP);
signal(SIGSYS, SIG_IGN);
syscall(kFilteredSyscall);
exit_with_failure_code();
});
// Should never join - process should be killed.
t.join();
});
}
// Test to ensure strict mode works.
TEST(SeccompTest, Strict) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.ExpectSignal(SIGKILL);
helper.RunInForkedProcess([] {
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
sock_filter filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};
sock_fprog prog = {
.len = ARRAY_SIZE(filter),
.filter = filter,
};
// prog argument should be ignored with prctl(SECCOMP_MODE_STRICT)
EXPECT_EQ(0, prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT, &prog, 0, 0));
syscall(kFilteredSyscall);
});
}
// Cannot change from Filtered to Strict
TEST(SeccompTest, FilterToStrictErrors) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
ASSERT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
sock_filter filter[] = {
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};
sock_fprog prog = {.len = sizeof(filter) / sizeof(sock_filter), .filter = filter};
EXPECT_EQ(0, syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog));
EXPECT_EQ(-1, prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT, 0, 0, 0))
<< "Was able to set a seccomp filter to strict when there was a filter " << errno;
EXPECT_EQ(EINVAL, errno);
exit_with_failure_code();
});
}
// Checks for attempt to install null filter with FILTER, or non-null filter
// with seccomp(SET_MODE_STRICT)
TEST(SeccompTest, BadArgs) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
EXPECT_EQ(-1, prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, nullptr, nullptr, nullptr));
EXPECT_EQ(EFAULT, errno);
sock_filter filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};
sock_fprog prog = {
.len = ARRAY_SIZE(filter),
.filter = filter,
};
EXPECT_EQ(-1, syscall(__NR_seccomp, SECCOMP_SET_MODE_STRICT, 0, &prog));
EXPECT_EQ(EINVAL, errno);
});
}
// Test to attempt to install filter without the right caps.
TEST(SeccompTest, BadNoNewPrivs) {
// Skip this test if we are root.
if (test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Skipped privs test because running as root";
}
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
// Running in locked down mode with no new privs already set - skip.
if (prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1) {
GTEST_SKIP() << "Skipped privs test because privs already set";
}
sock_filter filter[] = {
// A = seccomp_data.nr
BPF_STMT(BPF_LD | BPF_ABS | BPF_W, offsetof(struct seccomp_data, nr)),
// if (A == sysno) goto kill
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_getpid, 1, 0),
// allow: return SECCOMP_RET_ALLOW
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
// kill: return SECCOMP_RET_KILL_PROCESS
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
};
sock_fprog prog = {.len = ARRAY_SIZE(filter), .filter = filter};
EXPECT_EQ(-1, syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog))
<< "syscall failed with errno " << errno;
EXPECT_EQ(EACCES, errno);
});
}
TEST(SeccompTest, FilterMax4KMinOne) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
constexpr uint64_t too_big = BPF_MAXINSNS + 1;
auto filter = std::make_unique<sock_filter[]>(too_big);
sock_filter val = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
std::fill(filter.get(), filter.get() + too_big, val);
struct sock_fprog fprog = {.len = too_big, .filter = filter.get()};
EXPECT_NE(0, prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &fprog, 0, 0))
<< "Too large a filter was allowed";
sock_filter zero_filter[] = {};
sock_fprog zero_prog = {
.len = 0,
.filter = zero_filter,
};
EXPECT_NE(0, prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &zero_prog, 0, 0))
<< "Zero size filter was allowed";
});
}
#define MAX_INSNS_PER_PATH 32768
// Ensure that the total number of instructions in filters does not exceed a
// maximum. In practice, the "32768 instructions" limitation on Linux doesn't
// seem to mean 32768 instructions consistently, so it's acceptable just to use
// that as an upper bound.
TEST(SeccompTest, FilterMaxTotalInsns) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
const int kFilterSize = BPF_MAXINSNS;
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
auto filter = std::make_unique<sock_filter[]>(kFilterSize);
sock_filter val = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
std::fill(filter.get(), filter.get() + kFilterSize, val);
struct sock_fprog fprog = {.len = kFilterSize, .filter = filter.get()};
for (int i = 0; i < (MAX_INSNS_PER_PATH / kFilterSize) - 1; i++) {
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &fprog, 0, 0);
}
// At this point, we've attempted to add enough filters that there should be
// a failure.
EXPECT_EQ(-1, prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &fprog, 0, 0));
EXPECT_EQ(errno, ENOMEM);
});
}
// If a BPF operation contains BPF_ABS, then the specified offset has to be
// aligned to a 32-bit boundary and not exceed sizeof(seccomp_data)
TEST(SeccompTest, FilterAccessInBounds) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
sock_filter filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, sizeof(seccomp_data) + 4),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};
sock_fprog prog = {
.len = ARRAY_SIZE(filter),
.filter = filter,
};
EXPECT_EQ(-1, prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0))
<< "unaligned abs was allowed in filter";
EXPECT_EQ(EINVAL, errno) << "unaligned abs did not set set errno correctly";
});
}
TEST(SeccompTest, RetKillProcess) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.ExpectSignal(SIGSYS);
helper.RunInForkedProcess([] {
std::thread t([]() {
install_filter_block(kFilteredSyscall, SECCOMP_RET_KILL_PROCESS);
syscall(kFilteredSyscall);
exit_with_failure_code();
});
// Should never join - process should be killed.
t.join();
});
}
TEST(SeccompTest, KillProcessOnBadRetAction) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.ExpectSignal(SIGSYS);
helper.RunInForkedProcess([] {
std::thread t([]() {
install_filter_block(kFilteredSyscall, 0x00020000U);
syscall(kFilteredSyscall);
exit_with_failure_code();
});
// Should never join - process should be killed.
t.join();
});
}
TEST(SeccompTest, PrctlGetSeccomp) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.ExpectSignal(SIGSYS);
helper.RunInForkedProcess([] {
std::thread t([]() {
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
// prctl(PR_GET_SECCOMP) with no filter returns 0
EXPECT_EQ(0, prctl(PR_GET_SECCOMP, 0, 0, 0, 0));
install_filter_block(kFilteredSyscall, SECCOMP_RET_KILL_PROCESS);
// prctl(PR_GET_SECCOMP) with a filter that does not block prctl returns 2
EXPECT_EQ(2, prctl(PR_GET_SECCOMP, 0, 0, 0, 0));
install_filter_block(__NR_prctl, SECCOMP_RET_KILL_PROCESS);
// We're about to kill the process, so if there weer any failures before this,
// report them and exit.
if (testing::Test::HasFatalFailure() || testing::Test::HasNonfatalFailure()) {
exit(1);
}
// prctl(PR_GET_SECCOMP) with a filter that blocks prctl kills the process
prctl(PR_GET_SECCOMP, 0, 0, 0, 0);
exit_with_failure_code();
});
// Should never join - process should be killed.
t.join();
});
}
TEST(SeccompTest, GetActionAvail) {
uint32_t actions[] = {SECCOMP_RET_ALLOW, SECCOMP_RET_ERRNO, SECCOMP_RET_KILL_PROCESS,
SECCOMP_RET_KILL_THREAD, SECCOMP_RET_LOG, SECCOMP_RET_TRACE,
SECCOMP_RET_TRAP};
for (unsigned long i = 0; i < ARRAY_SIZE(actions); i++) {
EXPECT_EQ(0, syscall(__NR_seccomp, SECCOMP_GET_ACTION_AVAIL, 0, actions + i));
}
uint32_t illegal = 0xcafebeef;
EXPECT_EQ(-1, syscall(__NR_seccomp, SECCOMP_GET_ACTION_AVAIL, 0, &illegal));
EXPECT_EQ(EOPNOTSUPP, errno);
}
TEST(SeccompTest, GetActionsAvailProc) {
std::ifstream t("/proc/sys/kernel/seccomp/actions_avail");
std::stringstream buffer;
buffer << t.rdbuf();
EXPECT_EQ("kill_process kill_thread trap errno user_notif trace log allow\n", buffer.str());
}
// Read-only test. There is a test in starnix/task/seccomp.rs that exercises the ability to
// write to this file; it isn't here, because we don't want to change a global config.
TEST(SeccompTest, ActionsLoggedProc) {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Skipped ActionsLoggedProc because not running as root";
}
int fd = open("/proc/sys/kernel/seccomp/actions_logged", O_RDWR);
EXPECT_LT(0, fd);
// Make sure you can't write nonsense to it.
const char nonsense[] = "How doth the little crocodile improve its shining tail";
EXPECT_GT(0, write(fd, nonsense, strlen(nonsense)));
EXPECT_EQ(EINVAL, errno);
close(fd);
}
TEST(SeccompTest, ErrnoIsMaxFFF) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
install_filter_block(kFilteredSyscall, SECCOMP_RET_ERRNO | 0x1000);
syscall(kFilteredSyscall);
EXPECT_EQ(0xfff, errno);
exit_with_failure_code();
});
}
// Test that TSYNC from thread A won't work if you add a filter in thread B,
// spawned from thread A.
TEST(SeccompTest, TsyncEsrch) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
std::mutex m;
std::condition_variable cv;
int ready = 0;
std::thread t([&m, &cv, &ready]() {
// Step 1: Install filter that will prevent other thread from doing TSYNC
install_filter_block(kFilteredSyscall, SECCOMP_RET_KILL);
{
std::unique_lock l(m);
ready++;
}
// Wake up other thread so it performs TSYNC
cv.notify_all();
{
// Wait around until other thread tries to do TSYNC|TSYNC_ESRCH
std::unique_lock l(m);
cv.wait(l, [&ready] { return ready == 2; });
}
});
{
std::unique_lock l(m);
if (ready != 1) {
cv.wait(l, [&ready] { return ready == 1; });
}
}
// Step 2: When t has installed its filter, try to do TSYNC|TSYNC_ESRCH
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
sock_filter filter[] = {
// A = seccomp_data.nr
BPF_STMT(BPF_LD | BPF_ABS | BPF_W, offsetof(struct seccomp_data, nr)),
// if (A == sysno) goto kill
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kFilteredSyscall, 1, 0),
// allow: return SECCOMP_RET_ALLOW
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
// kill: return SECCOMP_RET_KILL
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
};
sock_fprog prog = {.len = ARRAY_SIZE(filter), .filter = filter};
EXPECT_EQ(-1, syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
SECCOMP_FILTER_FLAG_TSYNC | SECCOMP_FILTER_FLAG_TSYNC_ESRCH, &prog))
<< "expected seccomp fail, but succeeded";
EXPECT_EQ(ESRCH, errno);
{
std::unique_lock l(m);
ready++;
}
cv.notify_all();
t.join();
});
}
TEST(SeccompTest, UserNotifMissingListener) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
install_filter_block(kFilteredSyscall, SECCOMP_RET_USER_NOTIF);
EXPECT_EQ(-1, syscall(kFilteredSyscall));
EXPECT_EQ(ENOSYS, errno);
});
}
// Installs a filter that blocks the given syscall.
int install_filter_user_notif(uint32_t syscall_nr) {
EXPECT_GE(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
sock_filter filter[] = {
// A = seccomp_data.nr
BPF_STMT(BPF_LD | BPF_ABS | BPF_W, offsetof(struct seccomp_data, nr)),
// if (A == sysno) goto kill
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, syscall_nr, 1, 0),
// allow: return SECCOMP_RET_ALLOW
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
// notify: return SECCOMP_RET_USER_NOTIF
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF),
};
sock_fprog prog = {.len = ARRAY_SIZE(filter), .filter = filter};
return static_cast<int>(
syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog));
}
TEST(SeccompTest, UserNotifBlocking) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
const int kFilterReturnValue = 0xbadcafe;
int fd = install_filter_user_notif(kFilteredSyscall);
EXPECT_GT(fd, 0);
{
int fail = install_filter_user_notif(kFilteredSyscall);
EXPECT_EQ(fail, -1);
EXPECT_EQ(errno, EBUSY);
}
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
pid_t pid = helper.RunInForkedProcess(
[kFilterReturnValue] { EXPECT_EQ(kFilterReturnValue, syscall(kFilteredSyscall)); });
struct seccomp_notif req;
// When req has a non-zero field, ioctl returns EINVAL
req.id = 1;
EXPECT_EQ(-1, ioctl(fd, SECCOMP_IOCTL_NOTIF_RECV, &req))
<< "ioctl did not return correct error code";
memset(&req, 0, sizeof(req));
EXPECT_NE(-1, ioctl(fd, SECCOMP_IOCTL_NOTIF_RECV, &req))
<< "ioctl failed unexpectedly with errno " << errno;
EXPECT_EQ(pid, static_cast<pid_t>(req.pid));
EXPECT_EQ(kFilteredSyscall, static_cast<unsigned int>(req.data.nr));
struct seccomp_notif_resp resp;
resp.id = req.id;
resp.val = kFilterReturnValue;
resp.flags = resp.error = 0;
// Test that setting both legal flags value + legal val value is an error.
resp.flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
EXPECT_EQ(-1, ioctl(fd, SECCOMP_IOCTL_NOTIF_SEND, &resp));
EXPECT_EQ(errno, EINVAL);
// Test illegal flags value
resp.flags = ~resp.flags;
EXPECT_EQ(-1, ioctl(fd, SECCOMP_IOCTL_NOTIF_SEND, &resp));
EXPECT_EQ(errno, EINVAL);
// cookie value should still be valid.
EXPECT_EQ(0, ioctl(fd, SECCOMP_IOCTL_NOTIF_ID_VALID, &resp.id));
// Okay, let's do this for real.
resp.flags = 0;
EXPECT_NE(-1, ioctl(fd, SECCOMP_IOCTL_NOTIF_SEND, &resp))
<< "ioctl failed with errno " << errno;
EXPECT_TRUE(helper.WaitForChildren());
// cookie value should no longer be valid
EXPECT_EQ(-1, ioctl(fd, SECCOMP_IOCTL_NOTIF_ID_VALID, &resp.id));
EXPECT_EQ(errno, ENOENT);
});
}
TEST(SeccompTest, UserNotifNonBlocking) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
int fd;
std::thread t([&fd] {
fd = install_filter_user_notif(kFilteredSyscall);
EXPECT_GT(fd, 0);
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
pid_t pid = helper.RunInForkedProcess([] {
// Call 1: resp.error = -enotnam, get -1 result and errno set.
EXPECT_EQ(-1, syscall(kFilteredSyscall));
EXPECT_EQ(ENOTNAM, errno);
errno = 0;
// Call 2: resp.error = enotnam, get enotnam result and no errno.
EXPECT_EQ(ENOTNAM, syscall(kFilteredSyscall));
EXPECT_EQ(0, errno);
// Call 3: Zeros all around.
EXPECT_EQ(0, syscall(kFilteredSyscall));
EXPECT_EQ(0, errno);
});
struct seccomp_notif_resp resps[3];
resps[0] = {.error = -ENOTNAM, .flags = 0};
resps[1] = {.error = ENOTNAM, .flags = 0};
resps[2] = {.error = 0, .flags = 0};
for (int i = 0; i < 3; i++) {
// Do receive and send in separate threads. This increases the chance that we'll actually
// exercise the blocking case for poll() for the sender, which, if it followed the receiver,
// would probably not block.
pollfd pfd[] = {{.fd = fd, .events = POLLIN, .revents = 0}};
EXPECT_NE(-1, poll(pfd, ARRAY_SIZE(pfd), -1)) << "poll failed with errno " << errno;
EXPECT_EQ(POLLIN, pfd[0].revents);
struct seccomp_notif req;
memset(&req, 0, sizeof(req));
EXPECT_NE(-1, ioctl(fd, SECCOMP_IOCTL_NOTIF_RECV, &req))
<< "ioctl failed with errno " << errno;
EXPECT_EQ(pid, static_cast<pid_t>(req.pid));
EXPECT_EQ(kFilteredSyscall, static_cast<unsigned int>(req.data.nr));
resps[i].id = req.id;
pfd[0].events = POLLOUT;
pfd[0].revents = 0;
EXPECT_NE(-1, poll(pfd, ARRAY_SIZE(pfd), -1)) << "poll failed with errno " << errno;
EXPECT_EQ(POLLOUT, pfd[0].revents);
EXPECT_NE(-1, ioctl(fd, SECCOMP_IOCTL_NOTIF_SEND, &resps[i]))
<< "ioctl failed with errno " << errno;
}
EXPECT_TRUE(helper.WaitForChildren());
});
t.join();
// Every thread using the filter has terminated, so fd should be POLLHUP.
pollfd pfd[] = {{.fd = fd, .events = POLLHUP, .revents = 0}};
EXPECT_NE(-1, poll(pfd, ARRAY_SIZE(pfd), -1)) << "poll failed with errno " << errno;
});
}
TEST(SeccompTest, UserNotifClose) {
test_helper::ForkHelper helper;
helper.OnlyWaitForForkedChildren();
helper.RunInForkedProcess([] {
int fd = install_filter_user_notif(kFilteredSyscall);
EXPECT_GT(fd, 0);
close(fd);
EXPECT_EQ(-1, syscall(kFilteredSyscall));
EXPECT_EQ(ENOSYS, errno);
});
}
} // anonymous namespace