Не удается отследить системные вызовы подпроцесса, которые вызывают execve, используя ptrace и seccomp - PullRequest
3 голосов
/ 07 мая 2019

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

Когда я запускаю свою программу так (она называется tracer ):

encer

трассировки

Все работает хорошо, и я вижу логи в файле после. Тем не менее, если я пытаюсь отследить программу, которая вызывает execve внутри, она завершится неудачно:

часы трассировки -n1 env

или

трассировщик strace -o / tmp / log env

терпит неудачу с stdout

env: ошибка при загрузке общих библиотек: невозможно создать кеш для пути поиска: невозможно выделить память

и журнал:

$ cat /tmp/log
execve("/usr/bin/env", ["env"], [/* 19 vars */]) = 0
brk(NULL)                               = 0x415000
mmap(0xffffffffffffffda, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2
writev(103, [{iov_base="env", iov_len=3}, {iov_base=": ", iov_len=2}, {iov_base="error while loading shared libraries", iov_len=36}, {iov_base=": ", iov_len=2}, {iov_base="", iov_len=0}, {iov_base="", iov_len=0}, {iov_base="cannot create cache for search path", iov_len=35}, {iov_base=": ", iov_len=2}, {iov_base="Cannot allocate memory", iov_len=22}, {iov_base="\n", iov_len=1}], 10) = 127
+++ exited with 127 +++

Обратите внимание на странный адрес mmap и его возвращаемое значение. Я не понимаю, что не так и почему это происходит. Любая другая программа работает нормально, поэтому, я думаю, проблема в том, что копируем фильтры seccomp в разветвленный процесс, который вызывает execve.

Вот мои seccomp правила:

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_openat, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_write, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_mmap, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_mprotect, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_close, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
};

Я не перечисляю весь код, поскольку он очевиден и может быть написан только одним способом, также он написан в статье, на которую я ссылался выше. Эта проблема также известна в Интернете , но я не смог найти никакого решения. Если вы все еще настаиваете на целом коде (я сомневаюсь в этом) или MCVE, я могу предоставить его.

Кроме того, когда я добавляю трассировку execve, у меня другое поведение:

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_openat, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_write, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_mmap, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_mprotect, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_close, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_execve, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
};

Журнал становится:

$ cat /tmp/log
execve(0xffffffffffffffda, ["env"], [/* 19 vars */]) = -1 ENOSYS (Function not implemented)
getpid()                                = 15535
exit_group(1)                           = ?
+++ exited with 1 +++

Linux 4.4 aarch64, Linux 4.15 x86-64

Чем больше времени я трачу на эту проблему, тем больше понимаю, что проблема на самом деле в исходном коде ядра. Он копирует фильтры из одного процесса в другой , дочерний, но они не копируют реализацию, и поэтому все правила SECCOMP_RET_TRACE копируются, и в дочернем элементе нет трассировщика, поэтому каждая система call в подчиненном возвращает -ENOSYS, поскольку там нет трассировщика, однако правила копируются.

1 Ответ

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

Я нашел способ решить эту проблему. Чтобы также настроить трассировщик для дочерних процессов или, по крайней мере, избежать проблемы ENOSYS для дочерних процессов, мы можем указать флаг PTRACE_O_TRACEFORK и PTRACE_O_TRACECLONE при установке параметров ptrace , например:

ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESECCOMP | PTRACE_O_TRACEFORK | PTRACE_O_TRACECLONE);

Причину, по которой нам нужно добавить оба, нелегко объяснить кратко. Во-первых, это архитектура и libc -зависимая, какие системные вызовы присутствуют в системе и которые используются программами (обычно через реализацию libc ). Возможно, даже этот список не является полным: нам, возможно, придется отслеживать VFORK и другие способы, связанные с клонированием (или порождением) потока или процесса (помните, поток - это легкий процесс в Linux). Итак, что эти опции делают, указано в man:

PTRACE_O_TRACECLONE (начиная с Linux 2.5.46) Остановите трассировку у следующего клона (2) и автоматически начать отслеживание недавно клонированного процесса, который будет начать с SIGSTOP или PTRACE_EVENT_STOP, если PTRACE_SEIZE был использован. Ожидание (2) трассировщиком вернуть значение состояния так, чтобы

status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8))

PID нового процесса можно получить с помощью PTRACE_GETEVENTMSG. Эта опция может не перехватывать вызовы clone (2) во всех случаях. Если трассировка вызывает clone (2) с флагом CLONE_VFORK, вместо нее будет доставлено PTRACE_EVENT_VFORK, если установлено PTRACE_O_TRACEVFORK; в противном случае, если трассировка вызывает clone (2) с сигналом выхода, установленным на SIGCHLD, PTRACE_EVENT_FORK будет доставлено, если установлено PTRACE_O_TRACE‐FORK.

Причина, по которой это работает в моем случае, заключается в том, что после простого клонирования правила seccomp были скопированы в клонированный процесс, а трассировщик - нет. При указании этих флагов родительский процесс автоматически становится трассировщиком для каждого дочернего процесса, поэтому, когда правила копируются и указывается трассировщик, все работает как чудо.

Примечание При использовании этого способа родительский процесс становится трассировщиком, вам также нужно будет дождаться всех дочерних и дочерних процессов, а не только процесса, который вы на самом деле породили. Для этого используйте -1 в качестве аргумента pid в waitpid или аналогичных системных вызовах:

const pid_t childWaited = waitpid(-1, &status, 0);
// but not const pid_t result = waitpid(myChildPid, &status, 0);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...