selftests/seccomp: Test a TSYNC filter arriving during a ptrace stop
Subsystems (2)
Kees Cook
maintainerPATCH 1/1 selftests/seccomp: Test a TSYNC filter arriving during a ptrace stop
Seccomp runs after ptrace at syscall entry so that it checks what the tracer left. A thread can get its first filter by TSYNC from a sibling while it is stopped at a syscall entry; the generic entry code used the syscall work flags read before the stop, missed SYSCALL_WORK_SECCOMP, and ran that syscall unfiltered until commit 4a3591287fb7f ("entry: Fix seccomp bypass after ptrace with TSYNC").
Add a test based on the reproducer from that patch's thread, using pipes instead of a sleep so the filter is known to be installed before the tracer resumes the syscall.
Tests run under qemu on ARCH=x86_64 defconfig plus the seccomp selftest config, with GCC 16.2.0: running the test 20 times all 20 runs pass at v7.3-rc2 and fails in all 20 with the fix reverted.
--- tools/testing/selftests/seccomp/seccomp_bpf.c | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+)
tools/testing/selftests/seccomp/seccomp_bpf.c +112 -0
@@ -5041,6 +5041,118 @@ TEST(tsync_vs_dead_thread_leader)EXPECT_EQ (0 , status); } +struct tsync_vs_ptrace_args { +int go[2 ]; +int done[2 ]; +}; + +static void *tsync_vs_ptrace_syscall_stop_sibling (void *_args) +{ +struct sock_filter filter[] = { +BPF_STMT (BPF_LD |BPF_W |BPF_ABS , + offsetof(struct seccomp_data , nr)), +BPF_JUMP (BPF_JMP |BPF_JEQ |BPF_K , __NR_getppid,0 ,1 ), +BPF_STMT (BPF_RET |BPF_K ,SECCOMP_RET_ERRNO |ESRCH ), +BPF_STMT (BPF_RET |BPF_K ,SECCOMP_RET_ALLOW ), + }; +struct sock_fprog prog = { + .len = (unsigned short )ARRAY_SIZE (filter), + .filter = filter, + }; +struct tsync_vs_ptrace_args *args = _args; +char result ='N' ; +char go; + +/* Wait for the leader to stop at the entry of getppid(). */ +if (read (args->go[0 ], &go,1 ) ==1 && +seccomp (SECCOMP_SET_MODE_FILTER ,SECCOMP_FILTER_FLAG_TSYNC , + &prog) ==0 ) + result ='Y' ; +if (write (args->done[1 ], &result,1 ) !=1 ) +exit (3 ); + +return NULL ; +} + +/* +* Seccomp runs after ptrace at syscall entry, so that it sees what the +* tracer left. A thread's first filter can arrive by TSYNC from a sibling +* while the thread is stopped at a syscall entry, and it must still apply +* to that syscall once the tracer resumes it. +*/ +TEST (tsync_vs_ptrace_syscall_stop) +{ +struct tsync_vs_ptrace_args args; +unsigned long msg; +char result; +int status; +pid_t pid; +long ret; + + 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!" ); + } +ASSERT_EQ (0 ,pipe (args.go)); +ASSERT_EQ (0 ,pipe (args.done)); + + pid =fork (); +ASSERT_GE (pid,0 ); + +if (pid ==0 ) { +pthread_t sibling; + +if (pthread_create (&sibling,NULL , + tsync_vs_ptrace_syscall_stop_sibling, &args)) +exit (1 ); +/* Only this thread is traced; the sibling runs freely. */ +if (ptrace (PTRACE_TRACEME ,0 ,NULL ,NULL )) +exit (2 ); +raise (SIGSTOP ); + + ret =syscall (__NR_getppid); +/* The filter installed during the entry stop must apply. */ +if (ret !=-1 || errno !=ESRCH ) +exit (4 ); +exit (0 ); + } + +ASSERT_EQ (pid,waitpid (pid, &status,0 )); +ASSERT_TRUE (WIFSTOPPED (status)); +ASSERT_EQ (SIGSTOP ,WSTOPSIG (status)); +ASSERT_EQ (0 ,ptrace (PTRACE_SETOPTIONS , pid,NULL , +PTRACE_O_TRACESYSGOOD |PTRACE_O_EXITKILL )); + +/* Run the child to its syscall entry stop for getppid(). */ +do { +ASSERT_EQ (0 ,ptrace (PTRACE_SYSCALL , pid,NULL ,0 )); +ASSERT_EQ (pid,waitpid (pid, &status,0 )); +ASSERT_TRUE (WIFSTOPPED (status )) { +TH_LOG ("Child did not stop: status 0x%x" , status); + } +ASSERT_EQ (SIGTRAP |0x80 ,WSTOPSIG (status)); +ASSERT_EQ (0 ,ptrace (PTRACE_GETEVENTMSG , pid,NULL , &msg)); + }while (msg !=PTRACE_EVENTMSG_SYSCALL_ENTRY || +get_syscall (_metadata, pid) != __NR_getppid); + +/* Have the sibling install the first filter with TSYNC, and wait. */ +ASSERT_EQ (1 ,write (args.go[1 ],"G" ,1 )); +ASSERT_EQ (1 ,read (args.done[0 ], &result,1 )); + ASSERT_EQ('Y', result) { +TH_LOG ("Sibling could not install the filter with TSYNC" ); + } + +ASSERT_EQ (0 ,ptrace (PTRACE_CONT , pid,NULL ,0 )); +ASSERT_EQ (pid,waitpid (pid, &status,0 )); +ASSERT_TRUE (WIFEXITED (status )) { +TH_LOG ("Child did not exit: status 0x%x" , status); + } + EXPECT_EQ(0, WEXITSTATUS(status)) { +if (WEXITSTATUS (status) ==4 ) +TH_LOG ("getppid() ran without the TSYNC filter" ); + } +} +#ifdef __x86_64__/*
Signature · 1 line
2.55.0