How to handle SIGSTOP/SIGCONT in a ptracer?

Viewed 328

So I have a nice cooperative PTRACE_TRACEME child program being handled by a PTRACE wrapper program.

The problem is if the admin (or other) decides to SIGSTOP the child program, how can I handle that? And pick up the SIGCONT later, of course.

The answer might be "you can't"? This is PTRACE after all!

I can see that PTRACE_LISTEN (since Linux 3.4) has a role to play, putting the child program in a stopped state, but that is documented to only work when the child is attached with PTRACE_SEIZE.

I have tried the trivial thing of calling PTRACE_LISTEN when in PTRACE_TRACEME mode in the Group-stop, and as documented I get the Old McDonald error - EIO.

I have a thought that I could inject code to do a sigsuspend, but of course that will only pause the 1 thread of the child program. I would have to inject a signal to every thread to then stop them all? And then restart?

Or perhaps the implementation of redelivered SIGSTOP has already stopped them all? In which case would my sigsuspend actually get triggered? I guess I could trigger it directly if another thread reported SIGCONT.

While this sounds like a lot of fun to code, I am wondering have I missed a trick in the PTRACE documentation? Perhaps there is a way to get to PTRACE_SEIZE compatibility mode that I am missing?

[more caviats]

why not just use PTRACE_SEIZE in the first place?

  • PTRACE_TRACEME requires fewer security privileges;
  • we use seccomp to filter down the syscall intercepts, which are the main point;
  • we want to be in from the start of the child exe execution.

So to conclude: Is there a mechanism either to get to PTRACE_SEIZE mode or to simulate SIGSTOP behaviour within the API as it stands?

1 Answers

An example PTRACE_SEIZE Test program that simulates SIGSTOP behavior follows.

#define PTRACE_SEIZE        0x4206
  #define PTRACE_SEIZE_DEVEL    0x80000000

  static const struct timespec ts100ms = { .tv_nsec = 100000000 };
  static const struct timespec ts1s = { .tv_sec = 1 };
  static const struct timespec ts3s = { .tv_sec = 3 };

  int main(int argc, char **argv)
  {
      pid_t tracee;

      tracee = fork();
      if (tracee == 0) {
          nanosleep(&ts100ms, NULL);
          while (1) {
              printf("tracee: alive\n");
              nanosleep(&ts1s, NULL);
          }
      }

      if (argc > 1)
          kill(tracee, SIGSTOP);

      nanosleep(&ts100ms, NULL);

      ptrace(PTRACE_SEIZE, tracee, NULL,
         (void *)(unsigned long)PTRACE_SEIZE_DEVEL);
      waitid(P_PID, tracee, NULL, WSTOPPED);
      ptrace(PTRACE_CONT, tracee, NULL, NULL);
      nanosleep(&ts3s, NULL);
      printf("tracer: exiting\n");
      return 0;
  }

When the above code is called without an argument, tracee is seized from running state and continued. When tracer exits, tracee is returned to running state and keeps printing out.

 # ./test-seize
  tracee: alive
  tracee: alive
  tracee: alive
  tracer: exiting
  # tracee: alive
  tracee: alive
  tracee: alive

When called with an argument, tracee is seized from stopped state and continued, and returns to stopped state when tracer exits.

    # ./test-seize
  tracee: alive
  tracee: alive
  tracee: alive
  tracer: exiting
  # ps -el|grep test-seize
  1 T     0  4720     1  0  80   0 -   941 signal ttyS0    00:00:00 test-seize

You can read more at this thread

Related