Как получить код возврата системного вызова, используя SECCOMP_RET_DATA и PTRACE_GETEVENTMSG - PullRequest
3 голосов
/ 05 марта 2019

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

человек 4 бпф говорит:

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

мужчина 2 говорит:

 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.

man 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. Поэтому, когда трассировка прерывается на SECCOMP_RET_TRACE, она находится в состоянии syscall-enter-stop, а системный вызов еще не выполнен, поэтому код возврата определенно некуда взять. Я ожидаю, что после последующего вызова PTRACE_SYSCALL tracee будет в состоянии 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;
    }
}

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

    // 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);

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

1 Ответ

0 голосов
/ 22 мая 2019

Как получить код возврата системного вызова, используя 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 для этого события.Даже сама структура не содержит ничего, связанного с результатом системного вызова, и на страницах руководства также не упоминается получение значения результата.

...