How to strace monitor itself

kernelmonitoringrecursivestrace

I have a hypothetical situation:

  1. Let us say we have two strace processes S1 & S2, which are simply monitoring each other.
    How can this be possible?
    Well, in the command-line options for strace, -p PID is the way to pass the required PID, which (in our case) is not yet known when we issue the strace command. We could change the strace source code, such that -P 0 means, ask user for PID. E.g., read() from STDIN. When we can run two strace processes in two shell sessions and find the PIDs in a third shell, we can provide that input to S1 & S2 and let them monitor each other.
    Would S1 & S2 get stuck? Or, go into infinite loops, or crash immediately or…?

  2. Again, let us say we have another strace process S3, with -p -1, which, by modifying the source code, we use to tell S3 to monitor itself. E.g., use getpid() without using STDIN.
    Would S3 crash? Or, would it hang with no further processing possible? Would it wait for some event to happen, but, because it is waiting, no event would happen?

In the strace man-page, it says that we can not monitor an init process. Is there any other limitation enforced by strace, or by the kernel, to avoid a circular dependency or loop?

Some Special Cases :
S4 monitors S5, S5 monitors S6, S6 monitors S4.
S7 & S8 monitoring each other where S7 is the Parent of S8.
More special cases are possible.

EDIT (after comments by @Ralph Rönnquist & @pfnuesel) :
https://github.com/bnoordhuis/strace/blob/master/strace.c#L941

if (pid <= 0) {
    error_msg_and_die("Invalid process id: '%s'", opt);
}
if (pid == strace_tracer_pid) {
    error_msg_and_die("I'm sorry, I can't let you do that, Dave.");
}

Specifically, what will happen if strace.c does not check for pid == strace_tracer_pid or any other special cases? Is there any technical limitation (in kernel) over one process monitoring itself? How about a group of 2 (or 3 or more) processes monitoring themselves? Will the system crash or hang?

Best Answer

I will answer for Linux only.

Surprisingly, in newer kernels, the ptrace system call, which is used by strace in order to actually perform the tracing, is allowed to trace the init process. The manual page says:

   EPERM  The specified process cannot be traced.  This could  be  because
          the  tracer has insufficient privileges (the required capability
          is CAP_SYS_PTRACE); unprivileged  processes  cannot  trace  pro‐
          cesses  that  they  cannot send signals to or those running set-
          user-ID/set-group-ID programs, for  obvious  reasons.   Alterna‐
          tively,  the process may already be being traced, or (on kernels
          before 2.6.26) be init(8) (PID 1).

implying that starting in version 2.6.26, you can trace init, although of course you must still be root in order to do so. The strace binary on my system allows me to trace init, and in fact I can even use gdb to attach to init and kill it. (When I did this, the system immediately came to a halt.)

ptrace cannot be used by a process to trace itself, so if strace did not check, it would nevertheless fail at tracing itself. The following program:

#include <sys/ptrace.h>
#include <stdio.h>
#include <unistd.h>
int main() {
    if (ptrace(PTRACE_ATTACH, getpid(), 0, 0) == -1) {
        perror(NULL);
    }
}

prints Operation not permitted (i.e., the result is EPERM). The kernel performs this check in ptrace.c:

 retval = -EPERM;
 if (unlikely(task->flags & PF_KTHREAD))
         goto out;
 if (same_thread_group(task, current)) // <-- this is the one
         goto out;

Now, it is possible for two strace processes can trace each other; the kernel will not prevent this, and you can observe the result yourself. For me, the last thing that the first strace process (PID = 5882) prints is:

ptrace(PTRACE_SEIZE, 5882, 0, 0x11

whereas the second strace process (PID = 5890) prints nothing at all. ps shows both processes in the state t, which, according to the proc(5) manual page, means trace-stopped.

This occurs because a tracee stops whenever it enters or exits a system call and whenever a signal is about to be delivered to it (other than SIGKILL).

Assume process 5882 is already tracing process 5890. Then, we can deduce the following sequence of events:

  1. Process 5890 enters the ptrace system call, attempting to trace process 5882. Process 5890 enters trace-stop.
  2. Process 5882 receives SIGCHLD to inform it that its tracee, process 5890 has stopped. (A trace-stopped process appears as though it received the `SIGTRAP signal.)
  3. Process 5882, seeing that its tracee has made a system call, dutifully prints out the information about the syscall that process 5890 is about to make, and the arguments. This is the last output you see.
  4. Process 5882 calls ptrace(PTRACE_SYSCALL, 5890, ...) to allow process 5890 to continue.
  5. Process 5890 leaves trace-stop and performs its ptrace(PTRACE_SEIZE, 5882, ...). When the latter returns, process 5890 enters trace-stop.
  6. Process 5882 is sent SIGCHLD since its tracee has just stopped again. Since it is being traced, the receipt of the signal causes it to enter trace-stop.

Now both processes are stopped. The end.

As you can see from this example, the situation of two process tracing each other does not create any inherent logical difficulties for the kernel, which is probably why the kernel code does not contain a check to prevent this situation from happening. It just happens to not be very useful for two processes to trace each other.

Related Question