Add some basic tests for nested listeners.
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: Kees Cook <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Cc: Will Drewry <[email protected]>
Cc: Jonathan Corbet <[email protected]>
Cc: Shuah Khan <[email protected]>
Cc: Aleksa Sarai <[email protected]>
Cc: Tycho Andersen <[email protected]>
Cc: Andrei Vagin <[email protected]>
Cc: Christian Brauner <[email protected]>
Cc: Stéphane Graber <[email protected]>
Signed-off-by: Alexander Mikhalitsyn <[email protected]>
---
tools/testing/selftests/seccomp/seccomp_bpf.c | 303 ++++++++++++++++++
1 file changed, 303 insertions(+)
diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c
b/tools/testing/selftests/seccomp/seccomp_bpf.c
index 874f17763536..bbf3ef58ad07 100644
--- a/tools/testing/selftests/seccomp/seccomp_bpf.c
+++ b/tools/testing/selftests/seccomp/seccomp_bpf.c
@@ -301,6 +301,10 @@ struct seccomp_notif_addfd_big {
#define SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV (1UL << 5)
#endif
+#ifndef SECCOMP_FILTER_FLAG_ALLOW_NESTED_LISTENERS
+#define SECCOMP_FILTER_FLAG_ALLOW_NESTED_LISTENERS (1UL << 6)
+#endif
+
#ifndef seccomp
int seccomp(unsigned int op, unsigned int flags, void *args)
{
@@ -4416,6 +4420,305 @@ TEST(user_notification_sync)
ASSERT_EQ(status, 0);
}
+/*
+ * This test is here to ensure that seccomp() behavior before
+ * introducing nested listeners is preserved.
+ */
+TEST(user_notification_many_ret_notif_old_behavior)
+{
+ pid_t pid, ppid;
+ long ret;
+ int status, listener;
+ struct seccomp_notif req = {};
+ struct seccomp_notif_resp resp = {};
+
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
+ };
+ struct sock_fprog prog = {
+ .len = (unsigned short)ARRAY_SIZE(filter),
+ .filter = filter,
+ };
+
+ ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
+ }
+
+ /* Add some no-op filters for grins. */
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+
+ /* Install a filter that returns SECCOMP_RET_USER_NOTIF, but has no
listener. */
+ ASSERT_GE(user_notif_syscall(__NR_getppid, 0), 0);
+
+ /* Install a filter that returns SECCOMP_RET_USER_NOTIF, and then close
listener. */
+ listener = user_notif_syscall(__NR_getppid,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER);
+ ASSERT_GE(listener, 0);
+ close(listener);
+
+ /*
+ * Note, that we can install another listener now (without nesting
enabled!),
+ * because notify fd of the previous filter has been closed.
+ */
+ listener = user_notif_syscall(__NR_getppid,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER);
+ ASSERT_GE(listener, 0);
+
+ /* Add some no-op filters for grins. */
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+
+ ppid = getpid();
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = syscall(__NR_getppid);
+ exit(ret != ppid);
+ }
+
+ memset(&req, 0, sizeof(req));
+ EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
+ EXPECT_EQ(req.pid, pid);
+ EXPECT_EQ(req.data.nr, __NR_getppid);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.id = req.id;
+
+ /* tell kernel to continue syscall and expect that upper-level filters
are ignored */
+ resp.flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
+
+ EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ close(listener);
+}
+
+TEST(user_notification_many_ret_notif_closed_listener_nested)
+{
+ pid_t pid;
+ long ret;
+ int status, listener, closed_listener;
+ struct seccomp_notif req = {};
+ struct seccomp_notif_resp resp = {};
+
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
+ };
+ struct sock_fprog prog = {
+ .len = (unsigned short)ARRAY_SIZE(filter),
+ .filter = filter,
+ };
+
+ ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
+ }
+
+ /* Add some no-op filters for grins. */
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+
+ closed_listener = user_notif_syscall(__NR_getppid,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER |
+
SECCOMP_FILTER_FLAG_ALLOW_NESTED_LISTENERS);
+ ASSERT_GE(closed_listener, 0);
+
+ /*
+ * Note, that we can install another listener now (without nesting
enabled!),
+ * because notify fd of the previous filter has been closed.
+ */
+ listener = user_notif_syscall(__NR_getppid,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER);
+ ASSERT_GE(listener, 0);
+
+ /* Now, once we installed a nested listener, close the previous one. */
+ close(closed_listener);
+
+ /* Add some no-op filters for grins. */
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = syscall(__NR_getppid);
+ exit(ret >= 0 || errno != ENOSYS);
+ }
+
+ memset(&req, 0, sizeof(req));
+ EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
+ EXPECT_EQ(req.pid, pid);
+ EXPECT_EQ(req.data.nr, __NR_getppid);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.id = req.id;
+
+ /*
+ * Tell kernel to continue syscall and expect ENOSYS,
+ * because upper filter's notify fd has been closed.
+ */
+ resp.flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
+
+ EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ close(listener);
+}
+
+/*
+ * Ensure that EBUSY is returned on attempt to
+ * install a nested listener without nesting being allowed.
+ */
+TEST(user_notification_nested_limits)
+{
+ pid_t pid;
+ long ret;
+ int i, status, listeners[8];
+
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
+ };
+ struct sock_fprog prog = {
+ .len = (unsigned short)ARRAY_SIZE(filter),
+ .filter = filter,
+ };
+
+ ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
+ }
+
+ /* Install 6 levels of listeners and allow nesting. */
+ for (i = 0; i < 6; i++) {
+ listeners[i] = user_notif_syscall(__NR_getppid,
+
SECCOMP_FILTER_FLAG_NEW_LISTENER |
+
SECCOMP_FILTER_FLAG_ALLOW_NESTED_LISTENERS);
+ ASSERT_GE(listeners[i], 0);
+
+ /* Add some no-op filters for grins. */
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+ }
+
+ /* Check behavior when nesting is not allowed. */
+ pid = fork();
+ ASSERT_GE(pid, 0);
+ if (pid == 0) {
+ /* Install a next listener in the chain without nesting
allowed. */
+ listeners[6] = user_notif_syscall(__NR_getppid,
+
SECCOMP_FILTER_FLAG_NEW_LISTENER);
+ if (listeners[6] < 0)
+ exit(1);
+
+ /* Add some no-op filters for grins. */
+ ret = seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog);
+ if (ret != 0)
+ exit(2);
+
+ ret = user_notif_syscall(__NR_getppid,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER);
+ /* Installing a next listener in the chain should result in
EBUSY. */
+ exit((ret >= 0 || errno != EBUSY) ? 3 : 0);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+TEST(user_notification_nested)
+{
+ pid_t pid;
+ long ret;
+ int i, status, listeners[6];
+ struct seccomp_notif req = {};
+ struct seccomp_notif_resp resp = {};
+
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
+ };
+ struct sock_fprog prog = {
+ .len = (unsigned short)ARRAY_SIZE(filter),
+ .filter = filter,
+ };
+
+ ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
+ }
+
+ /* Install 6 levels of listeners and allow nesting. */
+ for (i = 0; i < 6; i++) {
+ /*
+ * Install a filter that returns SECCOMP_RET_USER_NOTIF, but
has no listener.
+ * We expect that these filters are not affecting the end
result.
+ */
+ ASSERT_GE(user_notif_syscall(__NR_getppid, 0), 0);
+
+ listeners[i] = user_notif_syscall(__NR_getppid,
+
SECCOMP_FILTER_FLAG_NEW_LISTENER |
+
SECCOMP_FILTER_FLAG_ALLOW_NESTED_LISTENERS);
+ ASSERT_GE(listeners[i], 0);
+
+ /* Add some no-op filters for grins. */
+ EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
+ }
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ ret = syscall(__NR_getppid);
+ exit(ret != (USER_NOTIF_MAGIC-3));
+ }
+
+ /*
+ * We want to have the following picture:
+ *
+ * | Listener level (i) | Listener decision |
+ * |--------------------|-------------------|
+ * | 0 | WHATEVER |
+ * | 1 | WHATEVER |
+ * | 2 | WHATEVER |
+ * | 3 | RETURN | <-- stop here
+ * | 4 | CONTINUE SYSCALL |
+ * | 5 | CONTINUE SYSCALL | <- start here
(current->seccomp.filter)
+ *
+ * First listener who receives a notification is level 5, then 4,
+ * then we expect to stop on level 3 and return from syscall with
+ * (USER_NOTIF_MAGIC - 3) return value.
+ */
+ for (i = 6 - 1; i >= 3; i--) {
+ memset(&req, 0, sizeof(req));
+ EXPECT_EQ(ioctl(listeners[i], SECCOMP_IOCTL_NOTIF_RECV, &req),
0);
+ EXPECT_EQ(req.pid, pid);
+ EXPECT_EQ(req.data.nr, __NR_getppid);
+
+ memset(&resp, 0, sizeof(resp));
+ resp.id = req.id;
+
+ if (i == 5 || i == 4) {
+ resp.flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
+ } else {
+ resp.error = 0;
+ resp.val = USER_NOTIF_MAGIC - i;
+ }
+
+ EXPECT_EQ(ioctl(listeners[i], SECCOMP_IOCTL_NOTIF_SEND, &resp),
0);
+ }
+
+ EXPECT_EQ(waitpid(pid, &status, 0), pid);
+ EXPECT_EQ(true, WIFEXITED(status));
+ EXPECT_EQ(0, WEXITSTATUS(status));
+
+ for (i = 0; i < 6; i++)
+ close(listeners[i]);
+}
/* Make sure PTRACE_O_SUSPEND_SECCOMP requires CAP_SYS_ADMIN. */
FIXTURE(O_SUSPEND_SECCOMP) {
--
2.43.0