Killing with ptrace

I found myself wanting to kill a process under Linux, but have it exit with a zero return code, so that its parent thought that it had no error. Impossible using kill directly, but possible with ptrace. The use case was stopping a component of the Debian/Ubuntu install system such that it would not be rerun as a failure.

The idea is to use ptrace to attach to the process, to stop the process, alter the next instruction pointed to by the instruction pointer to syscall, and to set the registers to indicate a function of 60 (exit), with a return code, held in %rdi, of zero. The traced process is then resumed.

The code will run as root, or will run against one's own processes if /proc/sys/kernel/yama/ptrace_scope is zero, which it is not on most modern Linux distributions. It assumes x86_64, and therefore that the op code for syscall is 0x0f05, and that storage is little-endian, so that a word of any length set to 0x050f will be written to memory as 0x0f, followed by 0x05, probably followed by several bytes of zeroes.

The code can be considered a very basic demonstration of what ptrace is capable of.


#include<stdio.h>
#include<stdlib.h>
#include<sys/ptrace.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/user.h>
#include<errno.h>

int main(int argc, char * argv[]){
  pid_t pid;
  long err;
  int status;
  struct user_regs_struct regs;
  
  pid=atoi(argv[1]);
  err=ptrace(PTRACE_ATTACH,pid,NULL,NULL);
  if (err) {perror(NULL); exit(1);}
  fprintf(stderr,"Successfully attached\n");

  waitpid(pid,&status,WUNTRACED);
  fprintf(stderr,"Wait over\n");

  ptrace(PTRACE_GETREGS, pid, NULL, ®s);
  if (err) {perror(NULL); exit(1);}
  fprintf(stderr,"Registers fetched\n");

  regs.rax=60;
  regs.rdi=0;
  ptrace(PTRACE_SETREGS, pid, NULL, ®s);
  if (err) {perror(NULL); exit(1);}

  fprintf(stderr,"Registers set\n");
  ptrace(PTRACE_POKETEXT, pid, (void*)regs.rip, (void*)0x050f);
  
  ptrace(PTRACE_DETACH, pid, NULL, NULL);
  fprintf(stderr,"Target resumes\n");
  exit(0);
}

To demonstrate its use, try typing
sleep 100; echo $?
in one window, and in another find the PID of the sleep command, and kill it. If killed with simply kill, the response in the first window will be

Terminated
143

whereas if killed with this trick, the response will be simply

0

showing that the sleep exited with no error. (Note that the error code on exiting due to a signal is 128 + signal number. Here the signal number is 15, being SIGTERM, the default signal sent by kill, so the return code is 143.)