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: 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 | 162 ++++++++++++++++++
 1 file changed, 162 insertions(+)

diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c 
b/tools/testing/selftests/seccomp/seccomp_bpf.c
index fc4910d35342..0bf02d04fe15 100644
--- a/tools/testing/selftests/seccomp/seccomp_bpf.c
+++ b/tools/testing/selftests/seccomp/seccomp_bpf.c
@@ -293,6 +293,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)
 {
@@ -4408,6 +4412,164 @@ TEST(user_notification_sync)
        ASSERT_EQ(status, 0);
 }
 
+/* from kernel/seccomp.c */
+#define MAX_LISTENERS_PER_PATH 8
+
+TEST(user_notification_nested_limits)
+{
+       pid_t pid;
+       long ret;
+       int i, status, listeners[MAX_LISTENERS_PER_PATH];
+
+       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));
+
+       /* Install more filters with listeners to reach nesting levels limit. */
+       for (; i < MAX_LISTENERS_PER_PATH; 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);
+       }
+
+       /* Installing a next listener in the chain should result in ELOOP. */
+       EXPECT_EQ(user_notif_syscall(__NR_getppid,
+                                    SECCOMP_FILTER_FLAG_NEW_LISTENER),
+                 -1);
+       EXPECT_EQ(errno, ELOOP);
+}
+
+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++) {
+               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


Reply via email to