selftests/ptrace: Add PTRACE_SYSEMU_SINGLESTEP syscall-exit-stop test
PATCH 1/1 selftests/ptrace: Add PTRACE_SYSEMU_SINGLESTEP syscall-exit-stop test
ptrace(2) states that no syscall-exit-stop occurs when a tracee is continued with PTRACE_SYSEMU or PTRACE_SYSEMU_SINGLESTEP. arm64 gated its syscall-exit report on
flags & (_TIF_SYSCALL_TRACE | _TIF_SINGLESTEP)
and ptrace_resume() clears SYSCALL_TRACE for both SYSEMU requests while PTRACE_SYSEMU_SINGLESTEP additionally sets TIF_SINGLESTEP, so the single-step bit alone produced a stop that must not exist. Nothing in tools/testing/selftests covered this.
Detecting the extra stop is less direct than it looks. The report is emitted with step=1, so ptrace_report_syscall_exit() dispatches to user_single_step_report() rather than ptrace_report_syscall(), and it arrives as a plain SIGTRAP indistinguishable by signal from the legitimate single-step trap: neither PTRACE_O_TRACESYSGOOD nor PTRACE_GET_SYSCALL_INFO separates the two. What does separate them is the PC. The redundant report fires before the tracee has moved past the syscall instruction, so it lands on the PC of the syscall-entry stop:
[0] sig=133 SYSCALL-STOP pc=0x41ec28 [1] sig=5 trap pc=0x41ec28 <-- must not exist [2] sig=5 trap pc=0x41ec2c
A fixed kernel reports pc=0x41ec2c already at stop [1]. The test therefore asserts that the first stop after the syscall-entry stop is at a different PC, which tests the consequence rather than the mechanism and does not depend on how many pseudo-step traps follow.
PTRACE_SYSEMU is used to reach the syscall-entry stop, as single- stepping there costs one stop per instruction and takes roughly 200000 stops. Architectures without a PC accessor here, or without SYSEMU support, skip rather than fail.
Build tested ARCH=arm64 with GCC aarch64-linux-gnu 16.1.0, ARCH=arm with GCC arm-linux-gnueabihf 16.1.0, and ARCH=x86_64 with GCC 16.2.0.
Tests passing on ARCH=arm64 under qemu-system-aarch64, and the same test fails as expected on v7.3-rc2 without the fix[1]:
# entry stop pc=0x41ec28, next stop sig=5 pc=0x41ec28 # Expected next_pc (4320296) != entry_pc (4320296) not ok 1 sysemu.no_syscall_exit_stop
An AArch32 build of the same test on an arm64 kernel reproduces the failure identically, covering the is_compat_task() path. It also passes on x86_64, which already uses generic entry, so the test does not report a false positive against a correct implementation.
--- tools/testing/selftests/ptrace/Makefile | 3 +- .../selftests/ptrace/sysemu_singlestep.c | 176 ++++++++++++++++++ tools/testing/selftests/ptrace/.gitignore | 1 + 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/ptrace/sysemu_singlestep.c
tools/testing/selftests/ptrace/Makefile +2 -1
@@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only CFLAGS += -std=c99 -pthread -Wall $(KHDR_INCLUDES) -TEST_GEN_PROGS := get_syscall_info set_syscall_info peeksiginfo vmaccess get_set_sud +TEST_GEN_PROGS := get_syscall_info set_syscall_info peeksiginfo vmaccess get_set_sud \ + sysemu_singlestep include ../lib.mk
tools/testing/selftests/ptrace/sysemu_singlestep.c new file +176 -0
176 lines not on this page · raw message
tools/testing/selftests/ptrace/.gitignore +1 -0
@@ -4,3 +4,4 @@ get_set_sud peeksiginfo vmaccess set_syscall_info +sysemu_singlestep
Signature · 1 line
2.34.1
Quoted 11 lines · Kees Cook
ptrace(2) states that no syscall-exit-stop occurs when a tracee is continued with
PTRACE_SYSEMUorPTRACE_SYSEMU_SINGLESTEP. arm64 gated its syscall-exit report onflags & (
_TIF_SYSCALL_TRACE|_TIF_SINGLESTEP)and
ptrace_resume()clearsSYSCALL_TRACEfor both SYSEMU requests whilePTRACE_SYSEMU_SINGLESTEPadditionally setsTIF_SINGLESTEP, so the single-step bit alone produced a stop that must not exist. Nothing intools/testing/selftestscovered this.
IOW, this test-case veirifies that in the sequence like the generic code
step = report_single_step(work); if (step || work & SYSCALL_WORK_SYSCALL_TRACE) arch_ptrace_report_syscall_exit(regs, step);
report_single_step() returns false, right? SYSCALL_TRACE is cleared by the arch neutral code...
Quoted 9 lines
Assisted-by LLMSigned-off-by Kees Cook <[email protected]>--- tools/testing/selftests/ptrace/Makefile | 3 +- .../selftests/ptrace/sysemu_singlestep.c | 176 ++++++++++++++++++ tools/testing/selftests/ptrace/.gitignore | 1 + 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/ptrace/sysemu_singlestep.c
Quoted 5 lines · Kees Cook
ptrace(2) states that no syscall-exit-stop occurs when a tracee is continued with
PTRACE_SYSEMUorPTRACE_SYSEMU_SINGLESTEP. arm64 gated its syscall-exit report onflags & (
_TIF_SYSCALL_TRACE|_TIF_SINGLESTEP)
Hi Kees,
The original fix description is a bit off: on arm64 there is no extra syscall-exit-stop; instead, there's an additional pseudo single-step injection.
As sashiko pointed out, as commit ac2081cdc4d9 ("arm64: ptrace:
Consistently use pseudo-singlestep exceptions") described, for PTRACE_SYSEMU_SINGLESTEP, arm64 still needs the pseudo single-step because its hardware single-step state machine is fast-forwarded over the SVC instruction.
static void report_syscall_exit(struct pt_regs *regs) {
...
if (test_thread_flag(TIF_SINGLESTEP)) {
/*
* Signal a pseudo-step exception since we are stepping but
* tracer modifications to the registers may have rewound the
* state machine.
*/
ptrace_report_syscall_exit(regs, 1);
-> user_single_step_report(regs);
}
}https://sashiko.dev/#/patchset/20260922035510.1090299-1-ruanjinjie%40huawei.com
Quoted 53 lines
and
ptrace_resume()clearsSYSCALL_TRACEfor both SYSEMU requests whilePTRACE_SYSEMU_SINGLESTEPadditionally setsTIF_SINGLESTEP, so the single-step bit alone produced a stop that must not exist. Nothing intools/testing/selftestscovered this.Detecting the extra stop is less direct than it looks. The report is emitted with step=1, so
ptrace_report_syscall_exit()dispatches touser_single_step_report()rather thanptrace_report_syscall(), and it arrives as a plain SIGTRAP indistinguishable by signal from the legitimate single-step trap: neitherPTRACE_O_TRACESYSGOODnorPTRACE_GET_SYSCALL_INFOseparates the two. What does separate them is the PC. The redundant report fires before the tracee has moved past the syscall instruction, so it lands on the PC of the syscall-entry stop:[0] sig=133 SYSCALL-STOP pc=0x41ec28 [1] sig=5 trap pc=0x41ec28 <-- must not exist [2] sig=5 trap pc=0x41ec2cA fixed kernel reports pc=0x41ec2c already at stop [1]. The test therefore asserts that the first stop after the syscall-entry stop is at a different PC, which tests the consequence rather than the mechanism and does not depend on how many pseudo-step traps follow.
PTRACE_SYSEMUis used to reach the syscall-entry stop, as single- stepping there costs one stop per instruction and takes roughly 200000 stops. Architectures without a PC accessor here, or without SYSEMU support, skip rather than fail.Build tested ARCH=arm64 with GCC aarch64-linux-gnu 16.1.0, ARCH=arm with GCC arm-linux-gnueabihf 16.1.0, and ARCH=x86_64 with GCC 16.2.0.
Tests passing on ARCH=arm64 under qemu-system-aarch64, and the same test fails as expected on v7.3-rc2 without the fix[1]:
# entry stop pc=0x41ec28, next stop sig=5 pc=0x41ec28 # Expected next_pc (4320296) != entry_pc (4320296) not ok 1 sysemu.no_syscall_exit_stopAn AArch32 build of the same test on an arm64 kernel reproduces the failure identically, covering the
is_compat_task()path. It also passes onx86_64, which already uses generic entry, so the test does not report a false positive against a correct implementation.Assisted-by LLMSigned-off-by Kees Cook <[email protected]>--- tools/testing/selftests/ptrace/Makefile | 3 +- .../selftests/ptrace/sysemu_singlestep.c | 176 ++++++++++++++++++ tools/testing/selftests/ptrace/.gitignore | 1 + 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/ptrace/sysemu_singlestep.c
[...]
Quoted 10 lines
tools/testing/selftests/ptrace/sysemu_singlestep.c new file +1
-0+TEST_HARNESS_MAIN tools/testing/selftests/ptrace/.gitignore +1
-0@@ -4,3 +4,4 @@ get_set_sud peeksiginfo vmaccess set_syscall_info +sysemu_singlestep
Signature · 2 lines
Best regards, Jinjie
Quoted 15 lines · Jinjie Ruan
Quoted 5 lines · Kees Cook
ptrace(2) states that no syscall-exit-stop occurs when a tracee is continued with
PTRACE_SYSEMUorPTRACE_SYSEMU_SINGLESTEP. arm64 gated its syscall-exit report onflags & (
_TIF_SYSCALL_TRACE|_TIF_SINGLESTEP)Hi Kees,
The original fix description is a bit off: on arm64 there is no extra syscall-exit-stop; instead, there's an additional pseudo single-step injection.
Quoted 5 lines
As sashiko pointed out, as commit
ac2081cdc4d9("arm64: ptrace:
Consistently use pseudo-singlestep exceptions") described, forPTRACE_SYSEMU_SINGLESTEP, arm64 still needs the pseudo single-step because its hardware single-step state machine is fast-forwarded over the SVC instruction.
Ah! Okay. So this test should be tweaked to deal with that? Feel free to snag/update this test for the series. We didn't have anything testing syscall exit stops, so it seemed reasonable to get it written.
-Kees
Signature · 1 line
Kees Cook
Quoted 16 lines · Kees Cook
Quoted 15 lines · Jinjie Ruan
Quoted 5 lines · Kees Cook
ptrace(2) states that no syscall-exit-stop occurs when a tracee is continued with
PTRACE_SYSEMUorPTRACE_SYSEMU_SINGLESTEP. arm64 gated its syscall-exit report onflags & (
_TIF_SYSCALL_TRACE|_TIF_SINGLESTEP)Hi Kees,
The original fix description is a bit off: on arm64 there is no extra syscall-exit-stop; instead, there's an additional pseudo single-step injection.
Quoted 7 lines
Quoted 5 lines
As sashiko pointed out, as commit
ac2081cdc4d9("arm64: ptrace:
Consistently use pseudo-singlestep exceptions") described, forPTRACE_SYSEMU_SINGLESTEP, arm64 still needs the pseudo single-step because its hardware single-step state machine is fast-forwarded over the SVC instruction.Ah! Okay. So this test should be tweaked to deal with that? Feel free to
Yes, I understand that these two cases should be distinguished.
Quoted 2 lines
snag/update this test for the series. We didn't have anything testing syscall exit stops, so it seemed reasonable to get it written.
I agree with adding a syscall exit stop test case to check whether it violates what the man page describes.
At the same time, I think we could also add a test case to verify the problems that may arise from arm64's own missing pseudo single-step.
Quoted 3 lines
-Kees
Signature · 2 lines
Best regards, Jinjie