Как получить код возврата системного вызова с помощью SECCOMP_RET_DATA и PTRACE_GETEVENTMSG

Я немного сбит с толку, пытаясь получить возвращаемое значение системного вызова с помощью ptrace + seccomp.

man 4 bpf говорит :

 FILTER MACHINE
A filter program is an array of instructions, with  all branches forwardly 
directed, terminated by a return instruction

человек 2 ptrace говорит:

 PTRACE_O_TRACESECCOMP  
While this triggers a PTRACE_EVENT stop, it is
similar to a syscall-enter-stop, in that the tracee has not yet
entered the syscall that seccomp triggered on. The seccomp event
message data (from the SECCOMP_RET_DATA portion of the seccomp filter
rule) can be retrieved with PTRACE_GETEVENTMSG.

 PTRACE_GETEVENTMSG 
For PTRACE_EVENT_SECCOMP, this is the seccomp(2)
filter's SECCOMP_RET_DATA associated with the triggered rule.

человек 2 seccomp говорит:

 SECCOMP_RET_TRACE
The tracer will be notified of a 
PTRACE_EVENT_SECCOMP  and  the  SECCOMP_RET_DATA
portion of the filter's return value will be available to 
the tracer via PTRACE_GETEVENTMSG
 [...]
The seccomp check will not be run again after the tracer is notified.

Оказывается, программа BPF не может выполнять что-либо дальше после оператора BPF_RET. Итак, когда tracee прерывается на SECCOMP_RET_TRACE, он находится в состоянии syscall-enter-stop, а системный вызов еще не был произведен, поэтому код возврата точно некуда брать. Я ожидаю, что после последующего вызова PTRACE_SYSCALL, трассировщик будет в состоянии syscall-exit-stop и трассировщик сможет получить результат системного вызова, используя PTRACE_GETEVENTMSG. Но в моем образце это не работает.

#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>

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

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <prog> <arg1> ... <argN>\n", argv[0]);
        return 1;
    }

    if ((pid = fork()) == 0) {
        ptrace(PTRACE_TRACEME, 0, 0, 0);

        struct sock_filter filter[] = {
            BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
            BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 1, 2),
            BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 0, 1),
            BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE | SECCOMP_RET_DATA),
            BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        };
        struct sock_fprog prog = {
            .filter = filter,
            .len = (unsigned short) (sizeof(filter)/sizeof(filter[0])),
        };

        if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
            return 2;
        if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1)
            return 3;

        kill(getpid(), SIGSTOP);
        return execvp(argv[1], argv + 1);
    } else {
        waitpid(pid, &status, 0);
        ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESECCOMP);
        ptrace(PTRACE_CONT, pid, 0, 0);

        int status = 0;
        unsigned long ret_data = 0;
        while(1) {
            while (1) {
                waitpid(pid, &status, 0);
                fprintf(stderr, "status = %08x\n", status);

                if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8)))
                    break;

                if (WIFEXITED(status))
                    return 0;
                ptrace(PTRACE_CONT, pid, 0, 0);
            }
            // restart stopped tracee
            ptrace(PTRACE_SYSCALL, pid, 0, 0);
            // wait for SIGTRAP, when tracee will be in the syscall-exit-stop state
            waitpid(pid, &status, 0);

            ptrace(PTRACE_GETEVENTMSG, pid, 0, &ret_data);
            fprintf(stderr, "retdat = %lu\n", ret_data);

            ptrace(PTRACE_CONT, pid, 0, 0);
        }
        return 0;
    }
}

Я могу получить код возврата системного вызова, проверяющий регистры

    // ptrace(PTRACE_GETEVENTMSG, pid, 0, &ret_data);
    struct user_regs_struct regs;
    ptrace(PTRACE_GETREGS, pid, 0, &regs);
    fprintf(stderr, "retdat = %lu\n", regs.rax);

но мне интересно, как это сделать так, как указано в документации.


person Sam Toliman    schedule 05.03.2019    source источник


Ответы (1)


Как получить код возврата системного вызова с помощью SECCOMP_RET_DATA и PTRACE_GETEVENTMSG?

Простой ответ: вы не можете. Событие seccomp отправляется даже до входа в системный вызов. Вы не видите никаких результатов, так как системного вызова еще не было. Чтобы получить его, вы должны дважды запустить процесс с PTRACE_SYSCALL после получения события seccomp:

bool WaitForSyscallExit(const pid_t pid)
{
  bool entered = false;
  int  status  = 0;

  while (true)
  {
    ptrace(PTRACE_SYSCALL, pid, 0, 0);
    waitpid(pid, &status, 0);

    if (WSTOPSIG(status) == SIGTRAP)
    {
      if (entered)
      {
        // If we had already entered before, then current SIGTRAP signal means exiting
        break;
      }
      entered = true;
    }
    else if (WIFEXITED(status) || WIFSIGNALED(status) || WCOREDUMP(status))
    {
      std::cerr << "The child has unexpectedly exited." << std::endl;

      return false;
    }
  }

  return true;
}

Поскольку используется PTRACE_SYSCALL, процесс будет остановлен дважды (первый раз после входа в системный вызов, следующий и последний раз после выхода из него). Вы можете получить результат только после того, как системный вызов действительно завершится, поэтому после остановки второго процесса. И да, вы можете сделать это только путем чтения регистров вручную, поскольку структура seccomp может использоваться только в обработчике трассировки seccomp для этого события. Даже сама структура не содержит ничего, связанного с результатом системного вызова, и на страницах руководства также не упоминается получение значения результата.

person Victor Polevoy    schedule 22.05.2019