【问题标题】:Linux syscalls: PTRACE_O_TRACECLONE causes indefinite hangingLinux 系统调用:PTRACE_O_TRACECLONE 导致无限期挂起
【发布时间】:2020-09-15 20:20:12
【问题描述】:

我有一个二进制文件,我需要从中截获某个系统调用——在本例中为unlinkat——并让它什么都不做。我有以下代码适用于单个进程;但是,将PTRACE_O_TRACECLONE 添加到 ptrace opts 后,在 tracee 调用 clone 后,waitpid 调用将永远挂起。几天来,我一直在互联网的不同部分上拉我的头发,直到我正在通过 strace 的来源,并且实际上已经 strace 以查看我所跟踪的 strace 是什么。

这是源代码——为了便于阅读,我删除了一些内容。

#define _POSIX_C_SOURCE 200112L

// std (i think)
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// linux
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>

#define OPTS PTRACE_O_TRACESYSGOOD // | PTRACE_O_TRACECLONE | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEFORK
#define WOPTS 0

/* The TRACEE. Executes the process we want to target with PTRACE_TRACEME */
int do_child(int argc, char **argv) {
    char *args[argc + 1];
    memcpy(args, argv, argc * sizeof(char *));
    args[argc] = NULL;

    ptrace(PTRACE_TRACEME);
    kill(getpid(), SIGSTOP);
    return execvp(args[0], args);
}

/* Waits for the next syscall and checks to see if the process has been exited */
int wait_for_syscall(pid_t child) {
    int status;
    while (1) {
        ptrace(PTRACE_SYSCALL, child, 0, 0);
        waitpid(child, &status, WOPTS); // <--- THIS CALL HANGS FOREVER AFTER CLONE
        if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80)
            return 0;
        if (WIFEXITED(status))
            return 1;
    }
    return -1; // unreachable
}

/* The TRACER. Takes the pid of the child process that we just started and actually does the 
    PTRACE stuff by passing signals back and forth to that process. */
int do_trace(pid_t child) {
    int status, syscall;
    waitpid(child, &status, WOPTS);
    ptrace(PTRACE_SETOPTIONS, child, 0, (unsigned long)OPTS);
    while (1) {
        // ptrace(PTRACE_SYSCALL) really needs to be called twice, first is before entry second is after exit, but idgaf
        if (wait_for_syscall(child) != 0) {
            break;
        }
        syscall = ptrace(PTRACE_PEEKUSER, child, sizeof(long) * ORIG_RAX);
        switch (syscall) {
            case SYS_clone:
                fprintf(stderr, "DEBUG: clone detected\n");
                break;
            case SYS_unlinkat:
                fprintf(stderr, "DEBUG: unlinkat detected\n");
                ptrace(PTRACE_POKEUSER, child, sizeof(long) * RAX, 0); 
                break;
        }
    }

    return 0;
}

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s prog args\n", argv[0]);
        exit(1);
    }

    pid_t child = fork();
    if (child == 0) {
        return do_child(argc - 1, argv + 1);
    } else {
        return do_trace(child);
    }
    return 0;
}

作为免责声明,我不是 C 开发人员,这些天我主要编写 Python,所以很多内容只是从我找到的不同教程中复制和粘贴的,我基本上添加/删除了随机垃圾,直到 gcc 没有给我那么多警告。

根据我所读到的内容,我怀疑问题在于向所涉及的进程发出信号并等待 SIGTRAP,我只是没有真正的直觉知道在那个级别做什么。

【问题讨论】:

  • “实际上已经跟踪了 strace 以查看我跟踪的 strace 是什么” - 哦,伙计 :')
  • 您关于拦截unlinkat 的第一个声明看起来就像您只想跳过该系统调用并且可能不关心使用PTRACE。如果正确,您始终可以使用最少的 unlinkat() 例程创建一个共享库,并使用 LD_PRELOAD=/full/path/your/lib.so 运行您的 bin。
  • 这是一个静态绑定的二进制文件,我可能应该更清楚
  • 如果您只想过滤特定的系统调用(甚至可能检查参数来决定是否过滤)我建议您使用适当的seccomp 过滤器,这更容易设置。
  • 谢谢——我完全不知道seccomp,我一定会试一试的。无论哪种方式,这都是一次宝贵的学习经历!

标签: c linux-kernel system-calls ptrace


【解决方案1】:

解决方案是改用libseccomp

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <seccomp.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>

int do_child(int argc, char **argv)
{
    char *args[argc + 1];
    memcpy(args, argv, argc * sizeof(char *));
    args[argc] = NULL;

    return execvp(args[0], args);
}

int main(int argc, char **argv)
{
    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s prog args\n", argv[0]);
        exit(1);
    }

    // Init the filter
    scmp_filter_ctx ctx;
    ctx = seccomp_init(SCMP_ACT_ALLOW); // default allow

    // setup basic whitelist
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(0), SCMP_SYS(unlinkat), 0);

    // build and load the filter
    seccomp_load(ctx);

    pid_t child = fork();
    if (child == 0)
    {
        return do_child(argc - 1, argv + 1);
    }

    return 0;
}

【讨论】:

    猜你喜欢
    • 2013-08-18
    • 2014-02-26
    • 1970-01-01
    • 1970-01-01
    • 2011-02-11
    • 1970-01-01
    • 2014-09-21
    • 1970-01-01
    • 2016-06-23
    相关资源
    最近更新 更多