diff options
Diffstat (limited to '_posts')
-rw-r--r-- | _posts/2022-11-07-ptrace-sigtraps.md | 72 | ||||
-rw-r--r-- | _posts/2022-11-07-ptrace-waitpid.md | 49 |
2 files changed, 121 insertions, 0 deletions
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. |