From: Jan Polensky <japo@linux.ibm.com>
To: ltp@lists.linux.it
Subject: [LTP] [PATCH v1 3/3] ptrace: add test for /proc/pid/mem writes under ptrace
Date: Tue, 14 Jul 2026 17:06:30 +0200 [thread overview]
Message-ID: <20260714150631.250972-4-japo@linux.ibm.com> (raw)
In-Reply-To: <20260714150631.250972-1-japo@linux.ibm.com>
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>
---
testcases/kernel/syscalls/ptrace/.gitignore | 1 +
testcases/kernel/syscalls/ptrace/ptrace13.c | 236 ++++++++++++++++++++
2 files changed, 237 insertions(+)
create mode 100644 testcases/kernel/syscalls/ptrace/ptrace13.c
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..9b8c93832eda
--- /dev/null
+++ b/testcases/kernel/syscalls/ptrace/ptrace13.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 SUSE LLC <japo@suse.cz>
+ */
+
+/*\
+ * [Description]
+ *
+ * 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 <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
prev parent reply other threads:[~2026-07-14 15:07 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-14 15:06 [LTP] [PATCH v1 0/3] Separate ptrace tests for CONFIG_PROC_MEM_FORCE_PTRACE Jan Polensky
2026-07-14 15:06 ` [LTP] [PATCH v1 1/3] thp04: Simplify to focus on CVE-2017-1000405 race test only Jan Polensky
2026-07-14 16:26 ` [LTP] " linuxtestproject.agent
2026-07-14 15:06 ` [LTP] [PATCH v1 2/3] ptrace: add test for /proc/self/mem write rejection Jan Polensky
2026-07-14 15:06 ` Jan Polensky [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260714150631.250972-4-japo@linux.ibm.com \
--to=japo@linux.ibm.com \
--cc=ltp@lists.linux.it \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.