LD_PRELOAD和clone()

3

我正在使用一个脚本来运行一个程序,并且通过一个我创建的库,使用LD_PRELOAD来拦截一些调用。它工作得很好,但是在某个时刻,进程调用了clone()函数,之后我失去了拦截接下来步骤的能力(程序再次运行,但没有我的库)。有没有办法解决这个问题?

clone(child_stack, 
  CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | 
  CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID, 
  parent_tidptr, tls, child_tidptr)

查看克隆参数时,我发现可以追踪子进程,但没有任何与预加载相关的内容。

需要说明的是,我试图截取特定文件描述符上的所有调用,而进程克隆了文件描述符,因此我甚至不确定是否可能在没有克隆标志的情况下实现我想要的目标(问题在于我不理解所有标志)。

更新: 我正在尝试记录由qemu-dm执行的所有活动(由xen运行)。

#define _LARGEFILE64_SOURCE
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdarg.h>

#define dprintf(...) if(__debug__) { char tmp[256]; int cnt = sprintf(tmp, __VA_ARGS__); _write_f_(2, tmp, cnt); _write_f_(__outfile__, tmp, cnt); }

typedef int (*_open_f_t_)(const char *path, int flags, ...);
typedef int (*_open64_f_t_)(const char *path, int flags, ...);
typedef FILE *(*_fopen_f_t_)(const char *path, const char *mode);
typedef int (*_close_f_t_)(int fd);
typedef ssize_t (*_read_f_t_)(int fd, void *buf, size_t count);
typedef ssize_t (*_write_f_t_)(int fd, const void *buf, size_t count);
typedef off_t (*_lseek_f_t_)(int fd, off_t offset, int whence);

static _open_f_t_ _open_f_ = NULL;
static _open64_f_t_ _open64_f_ = NULL;
static _fopen_f_t_ _fopen_f_ = NULL;
static _close_f_t_ _close_f_ = NULL;
static _read_f_t_ _read_f_ = NULL;
static _write_f_t_ _write_f_ = NULL;
static _lseek_f_t_ _lseek_f_ = NULL;
static int __outfile__ = NULL;
static int __debug__ = 0;

void __init__ ()
{
    _open_f_ = (_open_f_t_)dlsym(RTLD_NEXT, "open");
    _open64_f_ = (_open64_f_t_)dlsym(RTLD_NEXT, "open64");
    _fopen_f_ = (_fopen_f_t_)dlsym(RTLD_NEXT, "fopen");
    _close_f_ = (_close_f_t_)dlsym(RTLD_NEXT, "close");
    _read_f_ = (_read_f_t_)dlsym(RTLD_NEXT, "read");
    _write_f_ = (_write_f_t_)dlsym(RTLD_NEXT, "write");
    _lseek_f_ = (_lseek_f_t_)dlsym(RTLD_NEXT, "lseek");
    unlink("/tmp/qemu-dm-preload.log");
    __outfile__ = _open_f_("/tmp/out-0", O_WRONLY | O_CREAT | O_APPEND);
    __debug__ = 1;
}

void __fini__ ()
{
    __debug__ = 0;
    fsync(__outfile__);
    _close_f_(__outfile__);
}

int open(const char *path, int flags, ...)
{
    //replace this
    int result;
    if (flags & O_CREAT)
    {
        va_list arg;
        int mode = 0;
        va_start (arg, flags);
        mode = va_arg (arg, int);
        va_end (arg);
        result = _open_f_(path, flags, mode);
        dprintf("open(%s, %d, %d) => %d\n", path, flags, mode, result);
    } else {
        result = _open_f_(path, flags);
        dprintf("open(%s, %d) => %d\n", path, flags, result);
    }
    return result;
}

int open64(const char *path, int flags, ...)
{
    //replace this
    int result;
    if (flags & O_CREAT)
    {
        va_list arg;
        int mode = 0;
        va_start (arg, flags);
        mode = va_arg (arg, int);
        va_end (arg);
        result = _open64_f_(path, flags, mode);
        dprintf("open(%s, %d, %d) => %d\n", path, flags, mode, result);
    } else {
        result = _open64_f_(path, flags);
        dprintf("open(%s, %d) => %d\n", path, flags, result);
    }

    return result;
}

FILE * fopen(const char *path, const char *mode)
{
    FILE *result = _fopen_f_(path, mode);
    dprintf("fopen(%s, %s) => %p\n", path, mode, result);
    return result;
}

int close(int fd)
{
    //replace this
    int result = _close_f_(fd);
    dprintf("close(%d) => %d\n", fd, result);
    return result;
}

ssize_t read(int fd, void *buf, size_t count)
{
    // replace this
    ssize_t result = _read_f_(fd, buf, count);
    dprintf("read(%d, %p, %lu) => %ld\n", fd, buf, count, result);
    return result;
}

ssize_t write(int fd, const void *buf, size_t count)
{
    // replace this
    ssize_t result = _write_f_(fd, buf, count);
    dprintf("write(%d, %p, %lu) => %ld\n", fd, buf, count, result);
    return result;
}

off_t lseek(int fd, off_t offset, int whence)
{
    // replace this
    off_t result = _lseek_f_(fd, offset, whence);
    dprintf("lseek(%d, %ld, %d) => %ld\n", fd, offset, whence, result);
    return result;
}

使用以下命令编译:gcc -ggdb -shared -fPIC -Wl,-init,__init__ -Wl,-fini,__fini__ -o fileaccesshooks.so -ldl fileaccesshooks.c

包装脚本内容:

#!/bin/bash
export LD_PRELOAD=/home/xception/work/fileaccesshooks.so
exec /usr/lib/xen/bin/qemu-dm-orig "$@"

如下评论所述,任务和进程的环境实际上是相同的(LD_PRELOAD对/proc/8408/task/8526/environ和/proc/8408/environ都是相同的),但在调用clone之后不再记录任何数据。 grep -e "testfile" -e "(11" /tmp/out-0

open(/root/testfile.raw, 2) => 11
read(11, 0x7fffb7259d00, 512) => 512
read(11, 0x7fba6e341200, 512) => 512
read(11, 0x7fba6e341200, 512) => 512
read(11, 0x7fba6e341200, 512) => 512
read(11, 0x7fba6e341200, 512) => 512
read(11, 0x7fba6e341200, 512) => 512
read(11, 0x7fba6e341200, 512) => 512

我得到的是这样的结果,但是相比之下,在同一个可执行文件上运行 strace -f 的输出包含了大量的读取和查找操作。


1
调用clone后会发生什么?克隆进程应该具有相同的LD_PRELOAD环境变量设置;环境不会被修改。 - Joni
1
你应该能够通过/proc/{PID}/environ查看环境变量,它是否有LD_PRELOAD? - Paul Rubel
没有创建新进程,只是创建了一个新任务,并且确实保留了环境/proc/8408/task/8526/environ。嗯,我想我必须在库中进行一些额外的检查,以获取任务ID并检查它是否与进程ID相同。 - xception
@Joni 我认为如果一个子进程没有被导出,它不一定会有相同的 LD_PRELOAD(例如 LD_PRELOAD=blah.so ./a.outLD_PRELOAD=blah.sh; export LD_PRELOAD; ./a.out)。 - twalberg
@twalberg已更新问题,并附上了包装脚本。 - xception
2个回答

5
clone 的参数和类似的 CLONE_VM 中看来,这个调用 clone 只是创建了一个新线程而不是新进程。我不认为产生的线程会重新加载任何库,因此我不认为您预加载的库需要在新线程中再次运行 - 您现有的函数实现应该“正常工作”;所有跳转到您库中的指令在新线程中与旧线程一样有效。
因此,我怀疑这不是您的问题,clone 是一个红色的掉包。
我唯一的理论是:
  • 还有一个 exec
  • 每个新线程都会调用库中的 __init__ 代码,尽管这似乎非常不可能。
关于qemu的最后一点 - 现代 qemu 对许多IO操作使用协程。它使用各种后端,具体取决于主机系统上可用的内容 - 如果你很不幸,它会为每个内容创建一个线程,这可能导致非常大量的线程。在这里阅读 - http://lists.gnu.org/archive/html/qemu-devel/2011-07/msg02894.html - 有一些方法可以让 qemu 的 configure 显示它正在使用哪个协程后端。但是,我怀疑 Xen 的 qemu-dm 可能太旧了,没有这个协程东西?我不知道。

我的最初想法是它应该像一个线程一样运行,但是strace告诉我,在文件描述符上发生的事情比我能拦截的要多得多...直接运行qemu使用pread/preadv/pwrite/pwritev调用,而且strace -f -e trace=process,fork,clone ...的输出只包含初始的execvearch_prctl,几个clone调用,几个_exit调用,一个exit_group以及一些SIGALRM和SIGUSR2。我担心它可能直接使用系统调用,这就是为什么我无法拦截它的原因?!? - xception
还有一件非常奇怪的事情,让我怀疑克隆进程是否作为另一个进程运行的是,为了使 strace 拦截更多的读取和我不需要的 seek 调用,我必须使用 -f 参数运行它。 - xception

4
经过一番深入调查,我得出了以下结论:
  • #include <unistd.h> 是个“大忌”,因为它将文件访问调用重定向到其64位等效项,从而严重限制了我实际能够捕获的内容(我只能捕获较低的读取操作,因为较高的读取操作使用了read64或pread64)。
  • 需要同时实现32位和64位版本的所有函数。
  • 尽管strace报告了很多lseek和read调用,但xen的qemu-dm实际上使用的是pread和pread64(而同样的strace在使用qemu时会正确地报告这一点)。
  • 定义_GNU_SOURCE(RTLD_NEXT所需)会将off_t定义为off64_t,因此请确保在应用程序中使用适当的偏移类型作为参数。
删除unistd.h头文件并实现open、open64、fopen、fopen64、read、read64、write、write64、pread、pread64、preadv、preadv64、pwrite、pwrite64、pwritev、pwritev64、close函数后,我现在终于得到比以前更多的输出,并且实现也真正起作用了(还有一些缺失的文件访问函数需要完善,但我打开此问题的原因已解决)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接