From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from canpmsgout08.his.huawei.com (canpmsgout08.his.huawei.com [113.46.200.223]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4AC103EEAF7 for ; Tue, 14 Jul 2026 07:36:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=113.46.200.223 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784014618; cv=none; b=hxFNSKPzFd6TIGEFDne68HzeQ9N3nJNK0Hr+CvoYKw55dkThkAqhBuc2BDrLhaeJ6cV7fikgjfzfIjKTYEO57jDdYKbC3oYFibt+pq9isoNBQ7Xx1JHmkuj+oQYwlrLfmHfC8YqdVPOfmH3bLYqy23niGLyOGtoXOjDPcedfu38= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784014618; c=relaxed/simple; bh=37D8xcWvbwP56Pp0sr2l2r5ReJkYUv6amSmxA4gtwJE=; h=Message-ID:Date:MIME-Version:Subject:To:References:From: In-Reply-To:Content-Type; b=monpV3qt6XtN+qY/1TxdK+XMuRg0yUytaLeNOcrP13Fk9sQxY5I7XvoEAZp35M+gaeiESaREi64DJdKSKVkyc2rBRME3gzFkNdk3rtWWlf1a5Vm9W/YrIwZTP/dXBoSszSwD/zuhPSOcI+o6BNdIJzp9RsBimxqe0NW5l5loY/Q= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b=6X05MYxr; arc=none smtp.client-ip=113.46.200.223 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b="6X05MYxr" dkim-signature: v=1; a=rsa-sha256; d=huawei.com; s=dkim; c=relaxed/relaxed; q=dns/txt; h=From; bh=lWXZ7PZf3xr2I9rrvAa5zdlnSHSAZNHYy7CL70zYFF8=; b=6X05MYxr1zMpg2plBaslo5yk2klDUB7D/Mqh/ii7u1wIMD61cr+2R4KscGff/2UClKwxhCq9y r2PvXgtqnuBnxIV68EKuCVbm+QRHJWbrNlFz9Q6sLs5l0zcVfiuoU1DgDUXyKkeExGYJATJDdFD ZOVNz5k7aSAWyWbcoO8nLSk= Received: from mail.maildlp.com (unknown [172.19.163.214]) by canpmsgout08.his.huawei.com (SkyGuard) with ESMTPS id 4gzrVH6hK2zmVXc; Tue, 14 Jul 2026 15:27:19 +0800 (CST) Received: from dggpemf500011.china.huawei.com (unknown [7.185.36.131]) by mail.maildlp.com (Postfix) with ESMTPS id CB57C4057C; Tue, 14 Jul 2026 15:36:37 +0800 (CST) Received: from [10.67.109.254] (10.67.109.254) by dggpemf500011.china.huawei.com (7.185.36.131) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1544.11; Tue, 14 Jul 2026 15:36:37 +0800 Message-ID: <157aa098-a263-4f4f-b0fc-108701480303@huawei.com> Date: Tue, 14 Jul 2026 15:36:36 +0800 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v2] entry: Fix seccomp bypass after ptrace with TSYNC To: , , , , References: <20260713025712.416366-1-ruanjinjie@huawei.com> From: Jinjie Ruan In-Reply-To: <20260713025712.416366-1-ruanjinjie@huawei.com> Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8bit X-ClientProxiedBy: kwepems200002.china.huawei.com (7.221.188.68) To dggpemf500011.china.huawei.com (7.185.36.131) On 7/13/2026 10:57 AM, Jinjie Ruan wrote: > Sashiko review pointed out the following issue. > > If a thread is stopped in syscall_trace_enter() for ptrace, another > thread can install a seccomp filter with SECCOMP_FILTER_FLAG_TSYNC > (e.g., via seccomp_attach_filter()). This will successfully set > SYSCALL_WORK_SECCOMP on the stopped thread, but syscall_trace_enter() > evaluates a cached 'work' variable sampled on entry. Consequently, > the subsequent check for SYSCALL_WORK_SECCOMP misses the newly > assigned flag, and the filter is silently bypassed. > > This race condition could allow an unprivileged process to execute > a prohibited system call (e.g., execve) that the newly installed filter > was intended to block, especially since the tracer might have modified > the system call number during the ptrace stop. > > Fix this by re-reading the syscall_work flags after ptrace handling, > so that any new SYSCALL_WORK_SECCOMP flag set by another thread via > TSYNC during the ptrace stop is observed before the subsequent > seccomp check. > > Cc: Kees Cook > Cc: Thomas Gleixner > Cc: Andy Lutomirski > Cc: Will Drewry > Cc: stable@vger.kernel.org > Link: https://lore.kernel.org/all/20260629132914.1135C1F000E9@smtp.kernel.org/ > Fixes: 142781e108b1 ("entry: Provide generic syscall entry functionality") > Signed-off-by: Jinjie Ruan > --- > v2: > - Adopt lighter-weight approach suggested by Thomas: re-read syscall_work > after ptrace instead of calling seccomp_permit_syscall() unconditionally. > - Update the commit message to reflect the actual implementation. > --- > include/linux/entry-common.h | 3 +++ > 1 file changed, 3 insertions(+) > > diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h > index 07d97def7dcb..299f13c78a6f 100644 > --- a/include/linux/entry-common.h > +++ b/include/linux/entry-common.h > @@ -97,6 +97,9 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l > if (!arch_ptrace_report_syscall_permit_entry(regs) || > (work & SYSCALL_WORK_SYSCALL_EMU)) > return -1L; > + > + /* ptrace might have changed work flags */ > + work = READ_ONCE(current_thread_info()->syscall_work); Hi, Thomas, This issue can be reproduced itself on x86 on v7.2-rc1 and this patch can fix the issue. I used DeepSeek to help write the following test method and script, which can successfully reproduce the issue. # Test Approach ## How the test works: A child process is created and traced with ptrace(PTRACE_SYSCALL). The child raises SIGSTOP to synchronise with the tracer, then calls getpid. When the child stops at the syscall‑enter tracepoint, the tracer signals a helper thread (via a pipe) to install a seccomp filter using SECCOMP_FILTER_FLAG_TSYNC. The filter kills any getpid invocation, while the child’s getpid is still paused inside syscall_trace_enter(). After a short sleep to guarantee installation, the tracer resumes the child. ## Expected results: Without the fix: The cached syscall_work flag does not see the newly added SYSCALL_WORK_SECCOMP; the getpid call executes normally, the child exits cleanly, and the test prints [tracer] child exited normally -> BYPASS. With the fix: The re‑read of syscall_work after the ptrace stop picks up the SYSCALL_WORK_SECCOMP flag, __secure_computing() is invoked, the filter kills the process, and the test prints [tracer] child killed by SIGSYS -> bug fixed. # kernel debug patch --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -96,6 +96,10 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l ret = arch_ptrace_report_syscall_entry(regs); if (ret || (work & SYSCALL_WORK_SYSCALL_EMU)) return -1L; + + pr_info("[DEBUG] After ptrace, scno: %d, SECCOMP: 0x%lx\n", syscall_get_nr(current, regs), work & SYSCALL_WORK_SECCOMP); + work = READ_ONCE(current_thread_info()->syscall_work); + pr_info("[DEBUG] After update, scno: %d, SECCOMP: 0x%lx\n", syscall_get_nr(current, regs), work & SYSCALL_WORK_SECCOMP); } # Test results ## baseline (Comment out "work refresh") # ./tsync_seccomp_bypass_test [tracer] signaling filter thread [filter thread] seccomp filter installed [ 10.497627] [DEBUG] After ptrace, scno: 39, SECCOMP: 0x0 [ 10.498086] [DEBUG] After update, scno: 39, SECCOMP: 0x0 [ 10.498593] [DEBUG] After ptrace, scno: 231, SECCOMP: 0x1 [ 10.499060] [DEBUG] After update, scno: 231, SECCOMP: 0x1 [tracer] child exited normally -> BYPASS ## After refresh work with the patch # ./tsync_seccomp_bypass_test [tracer] signaling filter thread [filter thread] seccomp filter installed [ 7.590580] [DEBUG] After ptrace, scno: 39, SECCOMP: 0x0 [ 7.591063] [DEBUG] After update, scno: 39, SECCOMP: 0x1 [ 7.591734] audit: type=1326 audit(1784013262.411:2): auid=4294967295 uid=0 gid=0 ses=4294967295 subj=kernel pid=589 comm="tsync_seccomp_b" exe="/root/tsync0 [tracer] child killed by SIGSYS -> bug fixed # Test program cat tsync_seccomp_bypass_test.c ``` /* * TSYNC seccomp bypass test for x86-64, using only getpid (syscall 39) * * Reproduces the race fixed by: * "entry: Fix seccomp bypass after ptrace with TSYNC" * * Expected behavior: * Without fix: getpid executes and child exits normally (bypass). * With fix: child is killed by SIGSYS because filter blocks getpid. * * Build: gcc -o tsync_seccomp_bypass_test tsync_seccomp_bypass_test.c * Run: ./tsync_seccomp_bypass_test */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include static int pipefd[2]; /* Thread 2: install TSYNC filter that kills any getpid() call */ static void *install_filter(void *arg) { char ch; if (read(pipefd[0], &ch, 1) != 1) { perror("pipe read"); pthread_exit(NULL); } struct sock_filter filter[] = { /* Load syscall number */ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 0), /* If it is getpid (39 on x86-64), kill */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_getpid, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), /* All other syscalls allowed */ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), }; struct sock_fprog prog = { .len = sizeof(filter) / sizeof(filter[0]), .filter = filter, }; if (syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &prog) == -1) { perror("seccomp TSYNC"); } else { fprintf(stderr, "[filter thread] seccomp filter installed\n"); } pthread_exit(NULL); } int main(void) { if (pipe(pipefd) == -1) { perror("pipe"); exit(1); } pid_t child = fork(); if (child < 0) { perror("fork"); exit(1); } if (child == 0) { /* ========== Child process ========== */ pthread_t tid; close(pipefd[1]); if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { perror("prctl NO_NEW_PRIVS"); exit(1); } if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) { perror("ptrace TRACEME"); exit(1); } if (pthread_create(&tid, NULL, install_filter, NULL) != 0) { perror("pthread_create"); exit(1); } raise(SIGSTOP); /* initial sync with tracer */ /* * Only call getpid. It will be stopped by ptrace at entry, * the filter thread will install TSYNC during that stop. */ syscall(__NR_getpid); _exit(0); } /* ========== Parent process (tracer) ========== */ close(pipefd[0]); int status; if (waitpid(child, &status, 0) == -1) { perror("waitpid"); return 1; } if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { fprintf(stderr, "expected initial SIGSTOP\n"); return 1; } ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD); ptrace(PTRACE_SYSCALL, child, 0, 0); int bypass = 0; int triggered = 0; while (1) { if (waitpid(child, &status, 0) == -1) { perror("waitpid"); break; } if (WIFEXITED(status)) { fprintf(stderr, "[tracer] child exited normally -> BYPASS\n"); bypass = 1; break; } if (WIFSIGNALED(status)) { if (WTERMSIG(status) == SIGSYS) fprintf(stderr, "[tracer] child killed by SIGSYS -> bug fixed\n"); else fprintf(stderr, "[tracer] child killed by signal %d\n", WTERMSIG(status)); break; } if (WIFSTOPPED(status)) { int sig = WSTOPSIG(status); if (sig == (SIGTRAP | 0x80)) { /* syscall stop */ if (!triggered) { fprintf(stderr, "[tracer] signaling filter thread\n"); char ch = 'x'; if (write(pipefd[1], &ch, 1) != 1) perror("pipe write"); usleep(100000); /* wait for TSYNC to complete */ triggered = 1; } ptrace(PTRACE_SYSCALL, child, 0, 0); } else { ptrace(PTRACE_SYSCALL, child, 0, sig); } } } close(pipefd[1]); return bypass ? 1 : 0; } ``` > } > > /* Do seccomp after ptrace, to catch any tracer changes. */