All of lore.kernel.org
 help / color / mirror / Atom feed
* [LTP] [PATCH v2 0/3] Separate ptrace tests for CONFIG_PROC_MEM_FORCE_PTRACE
@ 2026-07-16  9:50 Jan Polensky
  2026-07-16  9:50 ` [LTP] [PATCH v2 1/3] thp04: Simplify to focus on CVE-2017-1000405 race test only Jan Polensky
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Jan Polensky @ 2026-07-16  9:50 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.

Validated on s390x with CONFIG_PROC_MEM_FORCE_PTRACE=y:

- 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 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

v1: https://lore.kernel.org/all/20260714150631.250972-1-japo@linux.ibm.com/

Jan Polensky (3):
  thp04: Simplify to focus on CVE-2017-1000405 race test only
  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            | 166 +++++++++-----
 testcases/kernel/syscalls/ptrace/.gitignore |   2 +
 testcases/kernel/syscalls/ptrace/ptrace12.c |  96 ++++++++
 testcases/kernel/syscalls/ptrace/ptrace13.c | 235 ++++++++++++++++++++
 5 files changed, 449 insertions(+), 52 deletions(-)
 create mode 100644 testcases/kernel/syscalls/ptrace/ptrace12.c
 create mode 100644 testcases/kernel/syscalls/ptrace/ptrace13.c


base-commit: d139524b04d360c4dabaee61a6066a9a52b0e454
prerequisite-patch-id: fb884fb8843ffadb56383142ca39c8f3f2db898d
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 v2 1/3] thp04: Simplify to focus on CVE-2017-1000405 race test only
  2026-07-16  9:50 [LTP] [PATCH v2 0/3] Separate ptrace tests for CONFIG_PROC_MEM_FORCE_PTRACE Jan Polensky
@ 2026-07-16  9:50 ` Jan Polensky
  2026-07-16 13:17   ` [LTP] " linuxtestproject.agent
  2026-07-16  9:50 ` [LTP] [PATCH v2 2/3] ptrace: add test for /proc/self/mem write rejection Jan Polensky
  2026-07-16  9:50 ` [LTP] [PATCH v2 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-07-16  9:50 UTC (permalink / raw)
  To: ltp

Remove ptrace mode support from thp04.c to keep the test focused on
its original purpose: testing the CVE-2017-1000405 race condition.

The ptrace-based /proc/pid/mem write functionality is now covered by
dedicated tests in testcases/kernel/syscalls/ptrace/:
- ptrace12: Tests that CONFIG_PROC_MEM_FORCE_PTRACE blocks self-writes
- ptrace13: Tests that ptrace allows parent-to-child memory writes

This separation follows the principle of one test per feature and makes
the test suite more maintainable.

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
---
 testcases/kernel/mem/thp/thp04.c | 166 +++++++++++++++++++++----------
 1 file changed, 114 insertions(+), 52 deletions(-)

diff --git a/testcases/kernel/mem/thp/thp04.c b/testcases/kernel/mem/thp/thp04.c
index 16d766c349b7..c8d71289e005 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. If direct writes are not available (e.g., when
+ * CONFIG_PROC_MEM_FORCE_PTRACE=y is set), 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,70 @@ 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);
+	/* write_ptr points to last int in write_thp page */
+	state->write_ptr = (int *)(state->write_thp + state->thp_size - sizeof(int));
+	/* read_ptr points to last int in read_thp page */
+	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 +155,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 +177,45 @@ static void run(void)
 	tst_res(TPASS, "Huge zero page is still clean");
 }
 
+static void setup(void)
+{
+	int test_val = 0;
+
+	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();
+
+	/* Verify that direct writes to /proc/self/mem work */
+	TEST(lseek(state->writefd, (off_t)state->write_ptr, SEEK_SET));
+	if (TST_RET == -1)
+		tst_brk(TBROK | TTERRNO, "lseek on /proc/self/mem failed");
+
+	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). "
+			"See testcases/kernel/syscalls/ptrace/ptrace12 and ptrace13 for ptrace coverage.");
+	}
+
+	if (TST_RET == -1)
+		tst_brk(TBROK | TTERRNO, "test write to /proc/self/mem failed");
+
+	if (TST_RET != sizeof(test_val))
+		tst_brk(TBROK, "short write to /proc/self/mem: %ld bytes", TST_RET);
+}
+
 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 = {
@@ -162,6 +223,7 @@ static struct tst_test test = {
 	.setup = setup,
 	.cleanup = cleanup,
 	.runtime = 150,
+
 	.tags = (const struct tst_tag[]) {
 		{"linux-git", "a8f97366452e"},
 		{"linux-git", "8310d48b125d"},
-- 
2.55.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [LTP] [PATCH v2 2/3] ptrace: add test for /proc/self/mem write rejection
  2026-07-16  9:50 [LTP] [PATCH v2 0/3] Separate ptrace tests for CONFIG_PROC_MEM_FORCE_PTRACE Jan Polensky
  2026-07-16  9:50 ` [LTP] [PATCH v2 1/3] thp04: Simplify to focus on CVE-2017-1000405 race test only Jan Polensky
@ 2026-07-16  9:50 ` Jan Polensky
  2026-07-16  9:50 ` [LTP] [PATCH v2 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-07-16  9:50 UTC (permalink / raw)
  To: ltp

Add ptrace12 to verify that direct writes to /proc/self/mem are
correctly rejected when CONFIG_PROC_MEM_FORCE_PTRACE=y is active.

The test allocates a read-only memory page and attempts to write to it
via /proc/self/mem. With CONFIG_PROC_MEM_FORCE_PTRACE=y, this write
should fail with EIO because:
- FOLL_FORCE flag is needed to write to read-only pages
- CONFIG_PROC_MEM_FORCE_PTRACE blocks FOLL_FORCE unless actively ptracing
- A process cannot ptrace itself

Test validates kernel commit 41e8149c8892 ("proc: add config & param to
block forcing mem writes").

Signed-off-by: Jan Polensky <japo@linux.ibm.com>
---
 runtest/syscalls                            |  1 +
 testcases/kernel/syscalls/ptrace/.gitignore |  1 +
 testcases/kernel/syscalls/ptrace/ptrace12.c | 96 +++++++++++++++++++++
 3 files changed, 98 insertions(+)
 create mode 100644 testcases/kernel/syscalls/ptrace/ptrace12.c

diff --git a/runtest/syscalls b/runtest/syscalls
index 2be7012a6cd4..9df4684cca41 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1176,6 +1176,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..0a528f27961d
--- /dev/null
+++ b/testcases/kernel/syscalls/ptrace/ptrace12.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC <japo@suse.cz>
+ */
+
+/*\
+ * 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 succeeds: TCONF (feature not active)
+ * - If write fails with EIO: TPASS (feature working correctly)
+ * - 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)
+{
+	/* Allocate a page, initialize it, then make it read-only */
+	test_ptr = SAFE_MMAP(NULL, sizeof(int), PROT_READ | PROT_WRITE,
+			     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	*test_ptr = 0;
+
+	/* Make the page read-only to test permission override blocking */
+	SAFE_MPROTECT((void *)test_ptr, sizeof(int), PROT_READ);
+
+	/* Open /proc/self/mem for writing */
+	memfd = SAFE_OPEN("/proc/self/mem", O_RDWR);
+}
+
+static void run(void)
+{
+	int test_val = 0xdeadbeef;
+
+	/* Seek to our test memory location */
+	SAFE_LSEEK(memfd, (off_t)test_ptr, SEEK_SET);
+
+	/* Attempt to write to our own memory via /proc/self/mem */
+	TEST(write(memfd, &test_val, sizeof(test_val)));
+
+	if (TST_RET == sizeof(test_val)) {
+		tst_res(TCONF,
+			"Direct writes to /proc/self/mem succeeded - "
+			"CONFIG_PROC_MEM_FORCE_PTRACE not active or overridden");
+		return;
+	}
+
+	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;
+	}
+
+	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,
+	.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 v2 3/3] ptrace: add test for /proc/pid/mem writes under ptrace
  2026-07-16  9:50 [LTP] [PATCH v2 0/3] Separate ptrace tests for CONFIG_PROC_MEM_FORCE_PTRACE Jan Polensky
  2026-07-16  9:50 ` [LTP] [PATCH v2 1/3] thp04: Simplify to focus on CVE-2017-1000405 race test only Jan Polensky
  2026-07-16  9:50 ` [LTP] [PATCH v2 2/3] ptrace: add test for /proc/self/mem write rejection Jan Polensky
@ 2026-07-16  9:50 ` Jan Polensky
  2 siblings, 0 replies; 5+ messages in thread
From: Jan Polensky @ 2026-07-16  9:50 UTC (permalink / raw)
  To: ltp

Add ptrace13 to verify that a tracer can write to tracee memory via
/proc/pid/mem when CONFIG_PROC_MEM_FORCE_PTRACE=y is active.

The test uses PTRACE_SEIZE to attach to a child process and writes to
its read-only memory pages via /proc/pid/mem. This validates that:
- FOLL_FORCE is allowed when actively ptracing
- Parent can modify child's read-only memory
- Write-stop-continue cycle works correctly (100 iterations)

Test validates kernel commit 41e8149c8892 ("proc: add config & param to
block forcing mem 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 | 235 ++++++++++++++++++++
 3 files changed, 237 insertions(+)
 create mode 100644 testcases/kernel/syscalls/ptrace/ptrace13.c

diff --git a/runtest/syscalls b/runtest/syscalls
index 9df4684cca41..476c69d4c5f0 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1177,6 +1177,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..96510392be51
--- /dev/null
+++ b/testcases/kernel/syscalls/ptrace/ptrace13.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC <japo@suse.cz>
+ */
+
+/*\
+ * 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. Child self-stops with raise(SIGSTOP)
+ * 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. Repeat for multiple iterations
+ *
+ * The test uses checkpoint synchronization to prevent race conditions
+ * during ptrace attachment.
+ */
+
+#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;
+	int result;
+};
+
+static struct shared_state *shared;
+static pid_t tracee_pid;
+
+static void tracee_main(void)
+{
+	int i;
+
+	/* Allocate test memory in tracee's address space */
+	shared->test_ptr = SAFE_MMAP(NULL, sizeof(int), PROT_READ | PROT_WRITE,
+				     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	*shared->test_ptr = 0;
+
+	/* Make the page read-only - parent will need FOLL_FORCE to write */
+	SAFE_MPROTECT((void *)shared->test_ptr, sizeof(int), PROT_READ);
+
+	/* Signal parent that we're ready */
+	TST_CHECKPOINT_WAKE(0);
+
+	/* Wait for parent to complete PTRACE_SEIZE */
+	TST_CHECKPOINT_WAIT(1);
+
+	/* Initial stop - parent will write first value */
+	raise(SIGSTOP);
+
+	/*
+	 * Main test loop: verify parent's write, then stop for next write.
+	 * Parent controls termination via PTRACE_DETACH + SIGKILL.
+	 */
+	for (i = 0; i < TEST_ITERATIONS; i++) {
+		/* Verify parent's write from previous iteration */
+		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);
+			shared->result = 1;
+			exit(1);
+		}
+
+		/* Stop self to let parent write next value */
+		raise(SIGSTOP);
+	}
+
+	/* All iterations passed - wait for parent to terminate us */
+	shared->result = 0;
+	while (1)
+		pause();
+}
+
+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);
+	memset(shared, 0, sizeof(*shared));
+}
+
+static void run(void)
+{
+	char path[64];
+	int memfd;
+	int status;
+	int i;
+
+	/* Fork tracee */
+	tracee_pid = SAFE_FORK();
+	if (!tracee_pid) {
+		tracee_main();
+		exit(0);
+	}
+
+	/* Wait for tracee to be ready */
+	TST_CHECKPOINT_WAIT(0);
+
+	/* Attach to tracee with PTRACE_SEIZE */
+	SAFE_PTRACE(PTRACE_SEIZE, tracee_pid, NULL, NULL);
+
+	/* Signal tracee that attachment is complete */
+	TST_CHECKPOINT_WAKE(1);
+
+	/* Wait for tracee's first self-stop */
+	SAFE_WAITPID(tracee_pid, &status, 0);
+
+	if (WIFEXITED(status)) {
+		tst_brk(TBROK,
+			"Tracee exited unexpectedly before first SIGSTOP: %s",
+			tst_strstatus(status));
+	}
+
+	if (WIFSIGNALED(status)) {
+		tst_brk(TBROK,
+			"Tracee was killed before first SIGSTOP: %s",
+			tst_strstatus(status));
+	}
+
+	if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) {
+		tst_brk(TBROK,
+			"Tracee did not self-stop with SIGSTOP: %s",
+			tst_strstatus(status));
+	}
+
+	/* Open /proc/pid/mem for writing */
+	snprintf(path, sizeof(path), "/proc/%d/mem", tracee_pid);
+	memfd = SAFE_OPEN(path, O_RDWR);
+
+	/*
+	 * Write-stop-continue cycle: tracee is already stopped from initial raise(SIGSTOP).
+	 * Pattern: write → continue → tracee verifies → tracee stops → repeat
+	 */
+	for (i = 0; i < TEST_ITERATIONS; i++) {
+		int write_val = 0xdead0000 | i;
+
+		/* Update expected value for tracee to verify */
+		shared->expected_val = write_val;
+
+		/* Write to tracee's memory while it's stopped */
+		SAFE_LSEEK(memfd, (off_t)shared->test_ptr, SEEK_SET);
+		SAFE_WRITE(SAFE_WRITE_ALL, memfd, &write_val, sizeof(write_val));
+
+		/* Continue tracee to verify the write and stop again */
+		SAFE_PTRACE(PTRACE_CONT, tracee_pid, NULL, NULL);
+
+		/* Wait for tracee to stop itself after verification */
+		SAFE_WAITPID(tracee_pid, &status, 0);
+
+		if (WIFEXITED(status)) {
+			if (WEXITSTATUS(status) == 0) {
+				tst_brk(TBROK,
+					"Tracee exited unexpectedly at iteration %d: %s",
+					i, tst_strstatus(status));
+			}
+			/* Tracee reported failure and exited */
+			SAFE_CLOSE(memfd);
+			tracee_pid = 0;
+			return;
+		}
+
+		if (WIFSIGNALED(status)) {
+			tst_brk(TBROK,
+				"Tracee was killed at iteration %d: %s",
+				i, tst_strstatus(status));
+		}
+
+		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);
+
+	/* Test passed - all writes were successful and tracee verified them */
+	tst_res(TPASS,
+		"Successfully wrote to tracee memory via /proc/pid/mem "
+		"for %d iterations", TEST_ITERATIONS);
+
+	/* Detach and terminate tracee */
+	SAFE_PTRACE(PTRACE_DETACH, tracee_pid, NULL, NULL);
+	SAFE_KILL(tracee_pid, SIGKILL);
+	SAFE_WAITPID(tracee_pid, &status, 0);
+	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,
+	.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: Simplify to focus on CVE-2017-1000405 race test only
  2026-07-16  9:50 ` [LTP] [PATCH v2 1/3] thp04: Simplify to focus on CVE-2017-1000405 race test only Jan Polensky
@ 2026-07-16 13:17   ` linuxtestproject.agent
  0 siblings, 0 replies; 5+ messages in thread
From: linuxtestproject.agent @ 2026-07-16 13:17 UTC (permalink / raw)
  To: Jan Polensky; +Cc: ltp

Hi Jan,

On Thu, 16 Jul 2026, Jan Polensky wrote:
> thp04: Simplify to focus on CVE-2017-1000405 race test only

--- [PATCH 1/3] ---

> +	/* write_ptr points to last int in write_thp page */
> +	state->write_ptr = (int *)(state->write_thp + state->thp_size - sizeof(int));
> +	/* read_ptr points to last int in read_thp page */
> +	state->read_ptr = (int *)(state->read_thp + state->thp_size - sizeof(int));

These two comments restate what the pointer arithmetic already
expresses literally. The LTP coding style prohibits comments that
are plain English translations of the code. Should both be dropped?

--- [PATCH 2/3] ---

> +/*\
> + * ...
> + * Test behavior:
> + * - If write succeeds: TCONF (feature not active)

RST requires a blank line before a bulleted list. Without it the
list items render as continuation of the preceding paragraph. Should
a blank "* " line be inserted between "Test behavior:" and the list?

> +static void setup(void)
> +{
> +	/* Allocate a page, initialize it, then make it read-only */
> +	test_ptr = SAFE_MMAP(NULL, sizeof(int), PROT_READ | PROT_WRITE,
> +			     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +	*test_ptr = 0;
> +
> +	/* Make the page read-only to test permission override blocking */
> +	SAFE_MPROTECT((void *)test_ptr, sizeof(int), PROT_READ);
> +
> +	/* Open /proc/self/mem for writing */
> +	memfd = SAFE_OPEN("/proc/self/mem", O_RDWR);

The first comment is immediately superseded by the more specific one
that follows it. The third ("Open /proc/self/mem for writing") and the
inline comments in run() ("Seek to our test memory location",
"Attempt to write to our own memory via /proc/self/mem") all describe
what the API call already says. Are these needed?

--- [PATCH 3/3] ---

> +/*\
> + * ...
> + * 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

Same RST issue as in patch 2: no blank line between "Test flow:" and
the ordered list. Should a blank "* " line be inserted there?

> +	for (i = 0; i < TEST_ITERATIONS; i++) {
> +		/* Verify parent's write from previous iteration */
> +		if (*shared->test_ptr != shared->expected_val) {

The comment says "previous iteration" but the parent writes
expected_val and *test_ptr, then resumes the tracee. When the
tracee executes iteration i it is verifying what the parent wrote
in iteration i -- not i-1. Is the comment wrong?

> +struct shared_state {
> +	int *test_ptr;
> +	int expected_val;
> +	int result;

The result field is assigned in tracee_main() (0 on success, 1 on
failure) but is never read by the parent. The parent uses
WEXITSTATUS(status) to detect tracee failure. Is the field intended
to be used somewhere, or is it dead code that can be removed?

> +static void tracee_main(void)
> +{
> +	int i;
> +
> +	/* Allocate test memory in tracee's address space */
> +	shared->test_ptr = SAFE_MMAP(NULL, sizeof(int), ...);
> +	*shared->test_ptr = 0;
> +
> +	/* Make the page read-only - parent will need FOLL_FORCE to write */
> +	SAFE_MPROTECT((void *)shared->test_ptr, sizeof(int), PROT_READ);
> +
> +	/* Signal parent that we're ready */
> +	TST_CHECKPOINT_WAKE(0);
> +
> +	/* Wait for parent to complete PTRACE_SEIZE */
> +	TST_CHECKPOINT_WAIT(1);
> +
> +	/* Initial stop - parent will write first value */
> +	raise(SIGSTOP);

And inside run():

> +	/* Fork tracee */
> +	tracee_pid = SAFE_FORK();
> +	...
> +	/* Wait for tracee to be ready */
> +	TST_CHECKPOINT_WAIT(0);
> +	/* Attach to tracee with PTRACE_SEIZE */
> +	SAFE_PTRACE(PTRACE_SEIZE, tracee_pid, NULL, NULL);
> +	/* Signal tracee that attachment is complete */
> +	TST_CHECKPOINT_WAKE(1);
> +	/* Wait for tracee's first self-stop */
> +	SAFE_WAITPID(tracee_pid, &status, 0);

And in the loop:

> +		/* Update expected value for tracee to verify */
> +		shared->expected_val = write_val;
> +		/* Write to tracee's memory while it's stopped */
> +		SAFE_LSEEK(memfd, (off_t)shared->test_ptr, SEEK_SET);
> +		SAFE_WRITE(SAFE_WRITE_ALL, memfd, &write_val, sizeof(write_val));
> +		/* Continue tracee to verify the write and stop again */
> +		SAFE_PTRACE(PTRACE_CONT, tracee_pid, NULL, NULL);
> +		/* Wait for tracee to stop itself after verification */
> +		SAFE_WAITPID(tracee_pid, &status, 0);

These comments ("Allocate test memory in tracee's address space",
"Signal parent that we're ready", "Wait for parent to complete
PTRACE_SEIZE", "Initial stop - parent will write first value",
"Fork tracee", "Wait for tracee to be ready", "Attach to tracee with
PTRACE_SEIZE", "Signal tracee that attachment is complete", "Wait for
tracee's first self-stop", "Update expected value for tracee to verify",
"Write to tracee's memory while it's stopped", "Continue tracee to
verify the write and stop again", "Wait for tracee to stop itself after
verification") each restate what the API call or macro already
expresses. The coding style rule says to explain why, not how. Should
these be removed, retaining only the comments that explain intent
(such as "parent will need FOLL_FORCE to write")?

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-07-16 13:17 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-16  9:50 [LTP] [PATCH v2 0/3] Separate ptrace tests for CONFIG_PROC_MEM_FORCE_PTRACE Jan Polensky
2026-07-16  9:50 ` [LTP] [PATCH v2 1/3] thp04: Simplify to focus on CVE-2017-1000405 race test only Jan Polensky
2026-07-16 13:17   ` [LTP] " linuxtestproject.agent
2026-07-16  9:50 ` [LTP] [PATCH v2 2/3] ptrace: add test for /proc/self/mem write rejection Jan Polensky
2026-07-16  9:50 ` [LTP] [PATCH v2 3/3] ptrace: add test for /proc/pid/mem writes under ptrace Jan Polensky

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.