* [LTP] [PATCH v4 0/3] Handle FORCE_PTRACE in thp04 and add ptrace coverage
@ 2026-08-12 13:28 Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 1/3] thp04: group runtime state and skip when /proc/self/mem writes are blocked Jan Polensky
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Jan Polensky @ 2026-08-12 13:28 UTC (permalink / raw)
To: ltp
thp04 is a CVE-2017-1000405 regression test. If direct
/proc/self/mem writes are blocked by CONFIG_PROC_MEM_FORCE_PTRACE=y,
the CVE cannot be triggered and thp04 should report TCONF.
Add separate ptrace tests for the CONFIG_PROC_MEM_FORCE_PTRACE behavior
instead:
- ptrace12 checks that writing to /proc/self/mem is rejected with EIO.
- ptrace13 checks that a parent can write to a traced child's memory via
/proc/pid/mem.
Both ptrace tests use read-only mappings, so the writes require
FOLL_FORCE and exercise the CONFIG_PROC_MEM_FORCE_PTRACE enforcement.
Tested on s390x with CONFIG_PROC_MEM_FORCE_PTRACE=y:
- thp04: TCONF (direct /proc/self/mem writes blocked)
- ptrace12: TPASS
- ptrace13: TPASS, 100 iterations
Follow-up to the previous thp04 ptrace mode discussion:
https://lore.kernel.org/all/20260709175927.268677-1-japo@linux.ibm.com/
Changes in v4:
- thp04: prove the /proc/self/mem probe changes memory before running the race
- thp04: tighten direct-write result handling and document blocked-write behavior
- ptrace12: gate the test on CONFIG_PROC_MEM_FORCE_PTRACE=y
- ptrace12: treat successful /proc/self/mem writes as TFAIL under the required config
- ptrace13: use a tracer-controlled PTRACE_INTERRUPT initial stop
- ptrace13: fix the iteration diagnostic and clean up wait-status handling
- ptrace13: use PTRACE_KILL for normal termination
Changes in v3:
- All patches: Fixed RST doc formatting (added blank lines before lists)
- All patches: Removed redundant comments per LTP coding style
- thp04: Removed comments restating pointer arithmetic
- ptrace12: Kept only intent-focused FOLL_FORCE comment
- ptrace13: Removed unused shared_state.result field
- ptrace13: Removed misleading "previous iteration" comment
- ptrace13: Clarified iteration counter in TFAIL message to show which write is being verified
- ptrace13: Kept only intent-focused FOLL_FORCE comment and synchronization pattern comment
Changes in v2:
- thp04: Removed .forks_child flag (not needed without actual fork)
- thp04: Changed MAP_SHARED to MAP_PRIVATE for child state mapping
- thp04: Added missing #include <string.h> for memset()
- thp04: Renamed struct child_state → struct thp_state (no longer uses fork)
- thp04: Renamed child → state, child_setup() → thp_setup(), child_cleanup() → thp_cleanup()
- ptrace12: Removed deprecated [Description] line from doc block
- ptrace12: Replaced bare write() with TEST() macro, using TST_RET/TST_ERR
- ptrace12: Adjusted setup comment to clarify page initialization sequence
- ptrace13: Removed deprecated [Description] line from doc block
- ptrace13: Added missing #include <string.h> for memset()
- Both ptrace tests: Added to runtest/syscalls
v3: https://lore.kernel.org/all/20260721202452.315581-1-japo@linux.ibm.com/
v2: https://lore.kernel.org/all/20260716095004.92793-1-japo@linux.ibm.com/
v1: https://lore.kernel.org/all/20260714150631.250972-1-japo@linux.ibm.com/
Jan Polensky (3):
thp04: group runtime state and skip when /proc/self/mem writes are
blocked
ptrace: add test for /proc/self/mem write rejection
ptrace: add test for /proc/pid/mem writes under ptrace
runtest/syscalls | 2 +
testcases/kernel/mem/thp/thp04.c | 179 ++++++++++++-----
testcases/kernel/syscalls/ptrace/.gitignore | 2 +
testcases/kernel/syscalls/ptrace/ptrace12.c | 95 +++++++++
testcases/kernel/syscalls/ptrace/ptrace13.c | 202 ++++++++++++++++++++
5 files changed, 428 insertions(+), 52 deletions(-)
create mode 100644 testcases/kernel/syscalls/ptrace/ptrace12.c
create mode 100644 testcases/kernel/syscalls/ptrace/ptrace13.c
base-commit: 0b69550e055b5385822f001e2a27fedfbef31816
prerequisite-patch-id: 478e6be646f4717e084dd1eec187665b854ea028
prerequisite-patch-id: 16fc00d302aefbf6fb17e972a4390341bfbb0e50
prerequisite-patch-id: 6e77ef09b2259daae915f4a4895bf0879b7b7211
prerequisite-patch-id: 1d13f676c1c1b9b37e97b1e18ae75611a3516f96
prerequisite-patch-id: 11b8f2b102029b41838899f2a78132b50cac12a0
prerequisite-patch-id: 811e85d9a4158f72559de5f551ad93f109362c96
--
2.55.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 4+ messages in thread
* [LTP] [PATCH v4 1/3] thp04: group runtime state and skip when /proc/self/mem writes are blocked
2026-08-12 13:28 [LTP] [PATCH v4 0/3] Handle FORCE_PTRACE in thp04 and add ptrace coverage Jan Polensky
@ 2026-08-12 13:28 ` Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 2/3] ptrace: add test for /proc/self/mem write rejection Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 3/3] ptrace: add test for /proc/pid/mem writes under ptrace Jan Polensky
2 siblings, 0 replies; 4+ messages in thread
From: Jan Polensky @ 2026-08-12 13:28 UTC (permalink / raw)
To: ltp
Move thp04 runtime state from static globals into a heap-allocated
struct thp_state. Group write_thp, read_thp, write_ptr, read_ptr,
thp_size, writefd, readfd and fzsync_pair in one object instead of
spreading them across file-scope variables.
Also add a real /proc/self/mem write probe in setup(). If the write
fails with EIO, report TCONF and skip the test cleanly on kernels where
CONFIG_PROC_MEM_FORCE_PTRACE or CONFIG_PROC_MEM_NO_FORCE blocks
unforced /proc/self/mem self-writes.
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
---
testcases/kernel/mem/thp/thp04.c | 179 ++++++++++++++++++++++---------
1 file changed, 127 insertions(+), 52 deletions(-)
diff --git a/testcases/kernel/mem/thp/thp04.c b/testcases/kernel/mem/thp/thp04.c
index a4b2fa7bc629..1fe75c3608f0 100644
--- a/testcases/kernel/mem/thp/thp04.c
+++ b/testcases/kernel/mem/thp/thp04.c
@@ -3,7 +3,7 @@
* Copyright (c) 2019 SUSE LLC <mdoucha@suse.cz>
*/
-/*
+/*\
* CVE-2017-1000405
*
* Check for the Huge Dirty Cow vulnerability which allows a userspace process
@@ -21,27 +21,40 @@
* On old kernel such as 4.9, it has fixed the Dirty Cow bug but a similar check
* in huge_memory.c was forgotten. As a result, remote memory writes to ro regions
* of memory backed by transparent huge pages cause an infinite loop in the kernel.
- * While in this state the process is stil SIGKILLable, but little else works.
+ * While in this state the process is still SIGKILLable, but little else works.
* It is also a regression test about kernel
* commit 8310d48b125d("huge_memory.c: respect FOLL_FORCE/FOLL_COW for thp").
+ *
+ * This test uses direct writes to /proc/self/mem with fuzzy-sync to trigger
+ * the race condition. The test verifies that forced writes work before proceeding.
+ * If forced writes are blocked by kernel configuration, the test reports TCONF.
+ * For ptrace-based /proc/pid/mem testing, see testcases/kernel/syscalls/ptrace/.
*/
-#include "tst_test.h"
+#include <string.h>
+
#include "lapi/mmap.h"
#include "tst_fuzzy_sync.h"
+#include "tst_test.h"
-static char *write_thp, *read_thp;
-static int *write_ptr, *read_ptr;
-static size_t thp_size;
-static int writefd = -1, readfd = -1;
-static struct tst_fzsync_pair fzsync_pair;
+struct thp_state {
+ char *write_thp;
+ char *read_thp;
+ int *write_ptr;
+ int *read_ptr;
+ size_t thp_size;
+ int writefd;
+ int readfd;
+ struct tst_fzsync_pair fzsync_pair;
+};
-static void *alloc_zero_page(void *baseaddr)
+static struct thp_state *state;
+
+static void *alloc_zero_page(void *baseaddr, size_t thp_size)
{
int i;
void *ret;
- /* Find aligned chunk of address space. MAP_HUGETLB doesn't work. */
for (i = 0; i < 16; i++, baseaddr += thp_size) {
ret = mmap(baseaddr, thp_size, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
@@ -67,47 +80,68 @@ static void *alloc_zero_page(void *baseaddr)
}
tst_brk(TBROK, "Cannot map huge zero page near the specified address");
- return NULL; /* Silence compiler warning */
+ return NULL;
}
-static void setup(void)
+static void thp_cleanup(void)
+{
+ if (!state)
+ return;
+
+ tst_fzsync_pair_cleanup(&state->fzsync_pair);
+
+ if (state->readfd >= 0)
+ SAFE_CLOSE(state->readfd);
+
+ if (state->writefd >= 0)
+ SAFE_CLOSE(state->writefd);
+
+ if (state->read_thp)
+ SAFE_MUNMAP(state->read_thp, state->thp_size);
+
+ if (state->write_thp)
+ SAFE_MUNMAP(state->write_thp, state->thp_size);
+}
+
+static void thp_setup(void)
{
size_t i;
- thp_size = tst_get_hugepage_size();
+ state->thp_size = tst_get_hugepage_size();
- if (!thp_size)
+ if (!state->thp_size)
tst_brk(TCONF, "Kernel does not support huge pages");
- write_thp = alloc_zero_page((void *)thp_size);
+ state->write_thp = alloc_zero_page((void *)state->thp_size,
+ state->thp_size);
- for (i = 0; i < thp_size; i++) {
- if (write_thp[i])
+ for (i = 0; i < state->thp_size; i++) {
+ if (state->write_thp[i])
tst_brk(TCONF, "Huge zero page is pre-polluted");
}
- /* leave a hole between read and write THP to prevent merge */
- read_thp = alloc_zero_page(write_thp + 2 * thp_size);
- write_ptr = (int *)(write_thp + thp_size - sizeof(int));
- read_ptr = (int *)(read_thp + thp_size - sizeof(int));
- writefd = SAFE_OPEN("/proc/self/mem", O_RDWR);
- readfd = SAFE_OPEN("/proc/self/mem", O_RDWR);
+ state->read_thp = alloc_zero_page(state->write_thp + 2 * state->thp_size,
+ state->thp_size);
+ state->write_ptr = (int *)(state->write_thp + state->thp_size - sizeof(int));
+ state->read_ptr = (int *)(state->read_thp + state->thp_size - sizeof(int));
- fzsync_pair.exec_loops = 100000;
- tst_fzsync_pair_init(&fzsync_pair);
+ state->writefd = SAFE_OPEN("/proc/self/mem", O_RDWR);
+ state->readfd = SAFE_OPEN("/proc/self/mem", O_RDWR);
+ state->fzsync_pair.exec_loops = 100000;
+ tst_fzsync_pair_init(&state->fzsync_pair);
}
static void *thread_run(void *arg)
{
int c;
- while (tst_fzsync_run_b(&fzsync_pair)) {
- tst_fzsync_start_race_b(&fzsync_pair);
- madvise(write_thp, thp_size, MADV_DONTNEED);
- memcpy(&c, write_ptr, sizeof(c));
- SAFE_LSEEK(readfd, (off_t)write_ptr, SEEK_SET);
- SAFE_READ(1, readfd, &c, sizeof(int));
- tst_fzsync_end_race_b(&fzsync_pair);
+ while (tst_fzsync_run_b(&state->fzsync_pair)) {
+ tst_fzsync_start_race_b(&state->fzsync_pair);
+ madvise(state->write_thp, state->thp_size, MADV_DONTNEED);
+ memcpy(&c, state->write_ptr, sizeof(c));
+ SAFE_LSEEK(state->readfd, (off_t)state->write_ptr, SEEK_SET);
+ SAFE_READ(1, state->readfd, &c, sizeof(int));
+ tst_fzsync_end_race_b(&state->fzsync_pair);
/* Wait for dirty page handling before next madvise() */
usleep(10);
}
@@ -119,20 +153,20 @@ static void run(void)
{
int c = 0xdeadbeef;
- tst_fzsync_pair_reset(&fzsync_pair, thread_run);
+ tst_fzsync_pair_reset(&state->fzsync_pair, thread_run);
- while (tst_fzsync_run_a(&fzsync_pair)) {
+ while (tst_fzsync_run_a(&state->fzsync_pair)) {
/* Write into the main huge page */
- tst_fzsync_start_race_a(&fzsync_pair);
- SAFE_LSEEK(writefd, (off_t)write_ptr, SEEK_SET);
- madvise(write_thp, thp_size, MADV_DONTNEED);
- SAFE_WRITE(SAFE_WRITE_ALL, writefd, &c, sizeof(int));
- tst_fzsync_end_race_a(&fzsync_pair);
+ tst_fzsync_start_race_a(&state->fzsync_pair);
+ SAFE_LSEEK(state->writefd, (off_t)state->write_ptr, SEEK_SET);
+ madvise(state->write_thp, state->thp_size, MADV_DONTNEED);
+ SAFE_WRITE(SAFE_WRITE_ALL, state->writefd, &c, sizeof(int));
+ tst_fzsync_end_race_a(&state->fzsync_pair);
/* Check the other huge zero page for pollution */
- madvise(read_thp, thp_size, MADV_DONTNEED);
+ madvise(state->read_thp, state->thp_size, MADV_DONTNEED);
- if (*read_ptr != 0) {
+ if (*state->read_ptr != 0) {
tst_res(TFAIL, "Huge zero page was polluted");
return;
}
@@ -141,20 +175,61 @@ static void run(void)
tst_res(TPASS, "Huge zero page is still clean");
}
+static void setup(void)
+{
+ int test_val = 0xdeadbeef;
+
+ state = SAFE_MMAP(NULL, sizeof(*state), PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ memset(state, 0, sizeof(*state));
+ state->writefd = -1;
+ state->readfd = -1;
+
+ thp_setup();
+
+ if (*state->write_ptr != 0) {
+ tst_brk(TBROK, "write_ptr not zero initially: 0x%x",
+ *state->write_ptr);
+ }
+
+ SAFE_MPROTECT((void *)state->write_thp, state->thp_size,
+ PROT_READ | PROT_WRITE);
+ *state->write_ptr = 0x12345678;
+ SAFE_MPROTECT((void *)state->write_thp, state->thp_size, PROT_READ);
+
+ SAFE_LSEEK(state->writefd, (off_t)state->write_ptr, SEEK_SET);
+
+ TEST(write(state->writefd, &test_val, sizeof(test_val)));
+
+ if (TST_RET == -1 && TST_ERR == EIO) {
+ tst_brk(TCONF,
+ "Direct writes to /proc/self/mem disabled "
+ "(CONFIG_PROC_MEM_FORCE_PTRACE=y)");
+ }
+
+ if (TST_RET == -1)
+ tst_brk(TBROK | TTERRNO, "probe write to /proc/self/mem failed");
+
+ if (TST_RET != sizeof(test_val)) {
+ tst_brk(TBROK, "short write to /proc/self/mem: %ld bytes (expected %zu)",
+ TST_RET, sizeof(test_val));
+ }
+
+ if (*state->write_ptr != test_val) {
+ tst_brk(TBROK,
+ "write to /proc/self/mem did not modify memory: "
+ "expected 0x%x, got 0x%x", test_val, *state->write_ptr);
+ }
+
+ *state->write_ptr = 0;
+}
+
static void cleanup(void)
{
- tst_fzsync_pair_cleanup(&fzsync_pair);
+ thp_cleanup();
- if (readfd >= 0)
- SAFE_CLOSE(readfd);
-
- if (writefd >= 0)
- SAFE_CLOSE(writefd);
-
- if (read_thp)
- SAFE_MUNMAP(read_thp, thp_size);
- if (write_thp)
- SAFE_MUNMAP(write_thp, thp_size);
+ if (state)
+ SAFE_MUNMAP(state, sizeof(*state));
}
static struct tst_test test = {
--
2.55.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [LTP] [PATCH v4 2/3] ptrace: add test for /proc/self/mem write rejection
2026-08-12 13:28 [LTP] [PATCH v4 0/3] Handle FORCE_PTRACE in thp04 and add ptrace coverage Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 1/3] thp04: group runtime state and skip when /proc/self/mem writes are blocked Jan Polensky
@ 2026-08-12 13:28 ` Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 3/3] ptrace: add test for /proc/pid/mem writes under ptrace Jan Polensky
2 siblings, 0 replies; 4+ messages in thread
From: Jan Polensky @ 2026-08-12 13:28 UTC (permalink / raw)
To: ltp
Add ptrace12 to verify that /proc/self/mem writes are rejected when
CONFIG_PROC_MEM_FORCE_PTRACE requires ptrace access checks for
/proc/pid/mem writes.
The test maps a page, makes it read-only so the write path needs
FOLL_FORCE, then attempts to write to it through /proc/self/mem. Since
a task cannot ptrace itself, the write is expected to fail with EIO.
If the write succeeds, the test reports TCONF because the required
kernel behavior is not active.
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/ptrace/.gitignore | 1 +
testcases/kernel/syscalls/ptrace/ptrace12.c | 95 +++++++++++++++++++++
3 files changed, 97 insertions(+)
create mode 100644 testcases/kernel/syscalls/ptrace/ptrace12.c
diff --git a/runtest/syscalls b/runtest/syscalls
index b024d4c43a2c..7fc443247361 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1182,6 +1182,7 @@ ptrace09 ptrace09
ptrace10 ptrace10
ptrace11 ptrace11
+ptrace12 ptrace12
pwrite01 pwrite01
pwrite02 pwrite02
pwrite03 pwrite03
diff --git a/testcases/kernel/syscalls/ptrace/.gitignore b/testcases/kernel/syscalls/ptrace/.gitignore
index 1ee6117e9d5b..8631219312d5 100644
--- a/testcases/kernel/syscalls/ptrace/.gitignore
+++ b/testcases/kernel/syscalls/ptrace/.gitignore
@@ -9,3 +9,4 @@
/ptrace09
/ptrace10
/ptrace11
+/ptrace12
diff --git a/testcases/kernel/syscalls/ptrace/ptrace12.c b/testcases/kernel/syscalls/ptrace/ptrace12.c
new file mode 100644
index 000000000000..d72987a69a92
--- /dev/null
+++ b/testcases/kernel/syscalls/ptrace/ptrace12.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 IBM Corporation
+ */
+
+/*\
+ * Verify that direct writes to /proc/self/mem are correctly rejected
+ * when CONFIG_PROC_MEM_FORCE_PTRACE=y is active.
+ *
+ * When CONFIG_PROC_MEM_FORCE_PTRACE=y is set, the kernel requires
+ * PTRACE_MODE_ATTACH for /proc/pid/mem writes. This means a process
+ * cannot write to its own memory via /proc/self/mem - such writes
+ * should fail with EIO.
+ *
+ * Test behavior:
+ *
+ * - If write fails with EIO: TPASS (correct rejection)
+ * - If write succeeds: TFAIL (policy violation under required config)
+ * - If write fails with other error: TFAIL (unexpected behavior)
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "tst_test.h"
+
+static int *test_ptr;
+static int memfd = -1;
+
+static void setup(void)
+{
+ test_ptr = SAFE_MMAP(NULL, sizeof(int), PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ *test_ptr = 0;
+
+ /* Force /proc/self/mem to require FOLL_FORCE by targeting a read-only page */
+ SAFE_MPROTECT((void *)test_ptr, sizeof(int), PROT_READ);
+
+ memfd = SAFE_OPEN("/proc/self/mem", O_RDWR);
+}
+
+static void run(void)
+{
+ int test_val = 0xdeadbeef;
+
+ SAFE_LSEEK(memfd, (off_t)test_ptr, SEEK_SET);
+ TEST(write(memfd, &test_val, sizeof(test_val)));
+
+ if (TST_RET == -1 && TST_ERR == EIO) {
+ tst_res(TPASS,
+ "Write to /proc/self/mem correctly rejected with EIO");
+ return;
+ }
+
+ if (TST_RET == -1) {
+ tst_res(TFAIL | TERRNO,
+ "Write to /proc/self/mem failed with unexpected error");
+ return;
+ }
+
+ if (TST_RET == sizeof(test_val)) {
+ tst_res(TFAIL,
+ "Write to /proc/self/mem succeeded under CONFIG_PROC_MEM_FORCE_PTRACE=y");
+ return;
+ }
+
+ tst_res(TFAIL,
+ "Short write to /proc/self/mem: %zd bytes (expected %zu or -1)",
+ TST_RET, sizeof(test_val));
+}
+
+static void cleanup(void)
+{
+ if (memfd >= 0)
+ SAFE_CLOSE(memfd);
+
+ if (test_ptr)
+ SAFE_MUNMAP(test_ptr, sizeof(int));
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .setup = setup,
+ .cleanup = cleanup,
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_PROC_MEM_FORCE_PTRACE=y",
+ NULL
+ },
+ .tags = (const struct tst_tag[]) {
+ {"linux-git", "41e8149c8892"},
+ {}
+ }
+};
--
2.55.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [LTP] [PATCH v4 3/3] ptrace: add test for /proc/pid/mem writes under ptrace
2026-08-12 13:28 [LTP] [PATCH v4 0/3] Handle FORCE_PTRACE in thp04 and add ptrace coverage Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 1/3] thp04: group runtime state and skip when /proc/self/mem writes are blocked Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 2/3] ptrace: add test for /proc/self/mem write rejection Jan Polensky
@ 2026-08-12 13:28 ` Jan Polensky
2 siblings, 0 replies; 4+ messages in thread
From: Jan Polensky @ 2026-08-12 13:28 UTC (permalink / raw)
To: ltp
Add ptrace13 to verify that a tracer can write to a tracee through
/proc/pid/mem when CONFIG_PROC_MEM_FORCE_PTRACE requires ptrace access
checks.
The test forks a child, attaches to it with PTRACE_SEIZE, waits for the
child to stop, then repeatedly writes a value to the child's read-only
mapping via /proc/pid/mem and resumes it. The child verifies each write
after it is continued, covering the write-stop-continue cycle needed
for ptrace-mediated memory writes.
Signed-off-by: Jan Polensky <japo@linux.ibm.com>
---
runtest/syscalls | 1 +
testcases/kernel/syscalls/ptrace/.gitignore | 1 +
testcases/kernel/syscalls/ptrace/ptrace13.c | 202 ++++++++++++++++++++
3 files changed, 204 insertions(+)
create mode 100644 testcases/kernel/syscalls/ptrace/ptrace13.c
diff --git a/runtest/syscalls b/runtest/syscalls
index 7fc443247361..20d442ed4b99 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1183,6 +1183,7 @@ ptrace10 ptrace10
ptrace11 ptrace11
ptrace12 ptrace12
+ptrace13 ptrace13
pwrite01 pwrite01
pwrite02 pwrite02
pwrite03 pwrite03
diff --git a/testcases/kernel/syscalls/ptrace/.gitignore b/testcases/kernel/syscalls/ptrace/.gitignore
index 8631219312d5..72f9cef98e22 100644
--- a/testcases/kernel/syscalls/ptrace/.gitignore
+++ b/testcases/kernel/syscalls/ptrace/.gitignore
@@ -10,3 +10,4 @@
/ptrace10
/ptrace11
/ptrace12
+/ptrace13
diff --git a/testcases/kernel/syscalls/ptrace/ptrace13.c b/testcases/kernel/syscalls/ptrace/ptrace13.c
new file mode 100644
index 000000000000..d88968273d21
--- /dev/null
+++ b/testcases/kernel/syscalls/ptrace/ptrace13.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 IBM Corporation
+ */
+
+/*\
+ * Verify that a parent process can write to a traced child's memory
+ * via /proc/pid/mem when the child is in a stopped state.
+ *
+ * This test validates the ptrace-based memory write mechanism that
+ * becomes mandatory when CONFIG_PROC_MEM_FORCE_PTRACE=y is active.
+ *
+ * Test flow:
+ *
+ * 1. Parent forks a child process
+ * 2. Child signals readiness via checkpoint
+ * 3. Parent attaches with PTRACE_SEIZE
+ * 4. Parent issues PTRACE_INTERRUPT to create initial stop
+ * 5. Parent writes to child's memory via /proc/pid/mem
+ * 6. Parent continues child with PTRACE_CONT
+ * 7. Child verifies the write took effect
+ * 8. Child self-stops with raise(SIGSTOP) for next iteration
+ * 9. Repeat for multiple iterations
+ *
+ * The test uses checkpoint synchronization to prevent race conditions
+ * during ptrace attachment, and PTRACE_INTERRUPT for deterministic
+ * initial stop control.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "tst_test.h"
+#include "tst_checkpoint.h"
+
+#define TEST_ITERATIONS 100
+
+struct shared_state {
+ int *test_ptr;
+ int expected_val;
+};
+
+static struct shared_state *shared;
+static pid_t tracee_pid;
+
+static void tracee_main(void)
+{
+ int i;
+
+ shared->test_ptr = SAFE_MMAP(NULL, sizeof(int), PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ *shared->test_ptr = 0;
+
+ /* Force parent write through /proc/pid/mem to require FOLL_FORCE */
+ SAFE_MPROTECT((void *)shared->test_ptr, sizeof(int), PROT_READ);
+
+ TST_CHECKPOINT_WAKE(0);
+ TST_CHECKPOINT_WAIT(1);
+
+ for (i = 0; i < TEST_ITERATIONS; i++) {
+ if (*shared->test_ptr != shared->expected_val) {
+ tst_res(TFAIL,
+ "Iteration %d: expected 0x%x, got 0x%x",
+ i, shared->expected_val, *shared->test_ptr);
+ exit(1);
+ }
+
+ raise(SIGSTOP);
+ }
+
+ pause();
+ exit(0);
+}
+
+static void setup(void)
+{
+ /* Allocate shared memory for parent-child communication */
+ shared = SAFE_MMAP(NULL, sizeof(*shared), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+}
+
+static void run(void)
+{
+ char path[64];
+ int memfd;
+ int status;
+ int i;
+
+ tracee_pid = SAFE_FORK();
+ if (!tracee_pid) {
+ tracee_main();
+ exit(0);
+ }
+
+ TST_CHECKPOINT_WAIT(0);
+ SAFE_PTRACE(PTRACE_SEIZE, tracee_pid, NULL, NULL);
+ TST_CHECKPOINT_WAKE(1);
+ SAFE_PTRACE(PTRACE_INTERRUPT, tracee_pid, NULL, NULL);
+ SAFE_WAITPID(tracee_pid, &status, 0);
+
+ if (WIFEXITED(status)) {
+ tst_brk(TBROK,
+ "Tracee exited unexpectedly at initial stop: %s",
+ tst_strstatus(status));
+ } else if (WIFSIGNALED(status)) {
+ tst_brk(TBROK,
+ "Tracee was killed at initial stop: %s",
+ tst_strstatus(status));
+ } else if (!WIFSTOPPED(status)) {
+ tst_brk(TBROK,
+ "Tracee not stopped at initial stop: %s",
+ tst_strstatus(status));
+ }
+
+ snprintf(path, sizeof(path), "/proc/%d/mem", tracee_pid);
+ memfd = SAFE_OPEN(path, O_RDWR);
+
+ for (i = 0; i < TEST_ITERATIONS; i++) {
+ int write_val = 0xdead0000 | i;
+
+ shared->expected_val = write_val;
+ SAFE_LSEEK(memfd, (off_t)shared->test_ptr, SEEK_SET);
+ SAFE_WRITE(SAFE_WRITE_ALL, memfd, &write_val, sizeof(write_val));
+ SAFE_PTRACE(PTRACE_CONT, tracee_pid, NULL, NULL);
+ SAFE_WAITPID(tracee_pid, &status, 0);
+
+ if (WIFEXITED(status)) {
+ SAFE_CLOSE(memfd);
+ tracee_pid = 0;
+
+ if (WEXITSTATUS(status) == 0) {
+ tst_brk(TBROK,
+ "Tracee exited unexpectedly at iteration %d: %s",
+ i, tst_strstatus(status));
+ } else {
+ return;
+ }
+ } else if (WIFSIGNALED(status)) {
+ tst_brk(TBROK,
+ "Tracee was killed at iteration %d: %s",
+ i, tst_strstatus(status));
+ } else if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) {
+ tst_brk(TBROK,
+ "Tracee did not stop correctly at iteration %d: %s",
+ i, tst_strstatus(status));
+ }
+ }
+
+ SAFE_CLOSE(memfd);
+
+ tst_res(TPASS,
+ "Successfully wrote to tracee memory via /proc/pid/mem "
+ "for %d iterations", TEST_ITERATIONS);
+
+ SAFE_PTRACE(PTRACE_DETACH, tracee_pid, NULL, NULL);
+ SAFE_KILL(tracee_pid, SIGTERM);
+ SAFE_WAITPID(tracee_pid, &status, 0);
+
+ if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGTERM) {
+ tst_res(TWARN, "Unexpected final wait status: %s",
+ tst_strstatus(status));
+ }
+
+ tracee_pid = 0;
+}
+
+static void cleanup(void)
+{
+ if (tracee_pid > 0) {
+ /* Kill tracee if still alive (e.g., test aborted) */
+ if (kill(tracee_pid, 0) == 0) {
+ SAFE_KILL(tracee_pid, SIGKILL);
+ tst_reap_children();
+ }
+ tracee_pid = 0;
+ }
+
+ if (shared)
+ SAFE_MUNMAP(shared, sizeof(*shared));
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .setup = setup,
+ .cleanup = cleanup,
+ .forks_child = 1,
+ .needs_checkpoints = 1,
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_PROC_MEM_FORCE_PTRACE=y",
+ NULL
+ },
+ .tags = (const struct tst_tag[]) {
+ {"linux-git", "41e8149c8892"},
+ {}
+ }
+};
--
2.55.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-12 13:30 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-12 13:28 [LTP] [PATCH v4 0/3] Handle FORCE_PTRACE in thp04 and add ptrace coverage Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 1/3] thp04: group runtime state and skip when /proc/self/mem writes are blocked Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 2/3] ptrace: add test for /proc/self/mem write rejection Jan Polensky
2026-08-12 13:28 ` [LTP] [PATCH v4 3/3] ptrace: add test for /proc/pid/mem writes under ptrace Jan Polensky
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox