From f1da8108620a8560436a815f52a82439b16b6ee4 Mon Sep 17 00:00:00 2001
From: Egor Tensin <Egor.Tensin@gmail.com>
Date: Mon, 7 Nov 2022 15:28:03 +0100
Subject: add "Fun with ptrace" posts

---
 _posts/2022-11-07-ptrace-sigtraps.md | 72 ++++++++++++++++++++++++++++++++++++
 _posts/2022-11-07-ptrace-waitpid.md  | 49 ++++++++++++++++++++++++
 2 files changed, 121 insertions(+)
 create mode 100644 _posts/2022-11-07-ptrace-sigtraps.md
 create mode 100644 _posts/2022-11-07-ptrace-waitpid.md

(limited to '_posts')

diff --git a/_posts/2022-11-07-ptrace-sigtraps.md b/_posts/2022-11-07-ptrace-sigtraps.md
new file mode 100644
index 0000000..d3a7e5e
--- /dev/null
+++ b/_posts/2022-11-07-ptrace-sigtraps.md
@@ -0,0 +1,72 @@
+---
+title: 'Fun with ptrace: SIGTRAPs galore'
+date: 2022-11-07 13:00 +0100
+---
+When using `PTRACE_ATTACH` the `ptrace` mechanism reuses SIGTRAP for a number
+of things by default.
+This makes it unnecessarily hard to distinguish regular traps possibly caused
+by breakpoints we might place from other events.
+
+1. After `ptrace(PTRACE_SYSCALL)`, syscall-stops will be reported as SIGTRAPs.
+
+   ```c
+   int status;
+
+   ptrace(PTRACE_SYSCALL, pid, 0, 0);
+   waitpid(pid, &status, 0);
+
+   if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
+       /* We don't know if the tracee has just entered/exited a syscall or
+        * received a regular SIGTRAP (could be caused by a breakpoint we
+        * placed). */
+   }
+   ```
+
+   This is fixed by using the `PTRACE_O_TRACESYSGOOD` option.
+
+   ```c
+   int status;
+
+   ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD);
+   ptrace(PTRACE_SYSCALL, pid, 0, 0);
+   waitpid(pid, &status, 0);
+
+   if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) {
+       /* We know for sure that the tracee has just entered/exited a
+        * syscall. */
+   }
+   ```
+
+2. Every `execve` call will be reported as a SIGTRAP.
+
+   ```c
+   int status;
+
+   ptrace(PTRACE_CONT, pid, 0, 0);
+   waitpid(pid, &status, 0);
+
+   if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
+       /* We don't know if the tracee just called execve() or received a
+        * regular SIGTRAP (could be caused by a breakpoint we placed). */
+   }
+   ```
+
+   This is fixed by using the `PTRACE_O_TRACEEXEC` option.
+
+   ```c
+   int status;
+
+   ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC);
+   ptrace(PTRACE_CONT, pid, 0, 0);
+   waitpid(pid, &status, 0);
+
+   if (WIFSTOPPED(status) && status >> 8 == (SIGTRAP | PTRACE_EVENT_EXEC << 8)) {
+       /* We know for sure that the tracee has just called execve(). */
+   }
+   ```
+
+   This point doesn't apply to tracees attached using `PTRACE_SEIZE`.
+   {: .alert .alert-info }
+
+As you can see, you should always use at least the `PTRACE_O_TRACESYSGOOD` and
+`PTRACE_O_TRACEEXEC` options to be able to distinguish between SIGTRAPs.
diff --git a/_posts/2022-11-07-ptrace-waitpid.md b/_posts/2022-11-07-ptrace-waitpid.md
new file mode 100644
index 0000000..3c331c7
--- /dev/null
+++ b/_posts/2022-11-07-ptrace-waitpid.md
@@ -0,0 +1,49 @@
+---
+title: 'Fun with ptrace: a waitpid pitfall'
+date: 2022-11-07 12:00 +0100
+---
+When tracing a process using `ptrace`, one often uses the `waitpid` system call
+to wait until something happens to the tracee.
+It often goes like this (error handling is omitted for brevity):
+
+```c
+/* We have previously attached to tracee `pid`. */
+
+int status;
+
+waitpid(pid, &status, 0);
+
+if (WIFEXITED(status)) {
+    /* Tracee has exited. */
+}
+if (WIFSIGNALED(status)) {
+    /* Tracee was killed by a signal. */
+}
+/* Tracee was stopped by a signal WSTOPSIG(status). */
+```
+
+What if a single thread is attached to multiple tracees?
+Then we can use `-1` as the first argument to `waitpid`, and it will wait for
+any child to change state.
+
+```c
+int status;
+pid_t pid = waitpid(-1, &status, __WALL);
+```
+
+What's little known, however, is that `waitpid(-1)` will by default consume
+status changes from other thread's children.
+So if you have two tracer threads A and B, and each of them is attached to a
+tracee, then thread A might consume thread B's tracee status change by calling
+`waitpid(-1)`.
+
+To avoid that, use the `__WNOTHREAD` flag.
+That way, thread A will only consume status changes from its own children only.
+
+```c
+int status;
+pid_t pid = waitpid(-1, &status, __WALL | __WNOTHREAD);
+```
+
+In my opinion, `__WNOTHREAD` should often be a default in well-structured
+applications.
-- 
cgit v1.2.3