* [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; 5+ 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] 5+ 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 15:01 ` [LTP] " linuxtestproject.agent
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, 1 reply; 5+ 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] 5+ 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; 5+ 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] 5+ 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; 5+ 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] 5+ messages in thread
* Re: [LTP] thp04: group runtime state and skip when /proc/self/mem writes are blocked
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 15:01 ` linuxtestproject.agent
0 siblings, 0 replies; 5+ messages in thread
From: linuxtestproject.agent @ 2026-08-12 15:01 UTC (permalink / raw)
To: Jan Polensky; +Cc: ltp
Hi Jan,
On Aug 12, 2026, Jan Polensky wrote:
> thp04: group runtime state and skip when /proc/self/mem writes are blocked
--- [PATCH 1/3] ---
> + SAFE_MPROTECT((void *)state->write_thp, state->thp_size, PROT_READ);
> [...]
> + *state->write_ptr = 0;
Could the mapping be made writable again before this direct store, or could
the value be reset through /proc/self/mem? At this point write_ptr points
inside a PROT_READ mapping, so a successful probe is followed by SIGSEGV in
setup() and the test never runs.
--- [PATCH 2/3] ---
> + if (TST_RET == sizeof(test_val)) {
> + tst_res(TFAIL,
> + "Write to /proc/self/mem succeeded under CONFIG_PROC_MEM_FORCE_PTRACE=y");
Could this successful write report TCONF, as described in the commit
message? The proc_mem.force_override=always boot parameter overrides the
Kconfig default, so .needs_kconfigs does not prove that the ptrace policy is
active at runtime. A successful write is therefore valid on that boot and
must not produce TFAIL.
--- [PATCH 3/3] ---
> + TST_CHECKPOINT_WAKE(1);
> + SAFE_PTRACE(PTRACE_INTERRUPT, tracee_pid, NULL, NULL);
> + SAFE_WAITPID(tracee_pid, &status, 0);
Could checkpoint 1 remain blocked until after PTRACE_INTERRUPT has produced
the initial stop? The child can currently check the initial zero value and
raise(SIGSTOP) before the interrupt takes effect. If the wait observes that
stop, every write is checked one iteration late; after the final write the
child reaches pause() without checking or stopping, and the parent hangs in
SAFE_WAITPID().
> + SAFE_WRITE(SAFE_WRITE_ALL, memfd, &write_val, sizeof(write_val));
Could this write be probed explicitly and EIO handled as TCONF? The released
kernel permits proc_mem.force_override=never to override
CONFIG_PROC_MEM_FORCE_PTRACE. On that valid boot, FOLL_FORCE remains disabled
even for an active tracer and SAFE_WRITE() aborts the test as TBROK.
> + tracee_pid = 0;
Could the trailing whitespace on the following blank line be removed? It
causes git diff --check to fail.
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-12 15:01 UTC | newest]
Thread overview: 5+ 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 15:01 ` [LTP] " linuxtestproject.agent
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