c - 使用LD_PRELOAD覆盖从另一个libc函数调用的libc函数

标签 c preload chroot

我有一个项目旨在运行php-cgi chrooted 进行大规模虚拟主机(超过10k个虚拟主机),并且每个虚拟主机在Ubuntu Lucid x86_64下都具有自己的chroot。

我想避免在每个chroot内为/dev/null,/dev/zero,语言环境,图标...以及php模块认为它们在chroot之外运行所需的任何内容创建必要的环境。

的目标是使php-cgi在chroot内运行,但允许他访问chroot 以外的文件,只要这些文件(对于大多数文件)以只读模式打开并在允许的列表中(/dev/log,/dev/zero,/dev/null,语言环境路径...)

一种明显的方法似乎是创建(或使用)内核模块,该模块可以在chroot之外钩住并重定向受信任的open()路径。
但我认为这不是最简单的方法:

  • 我从未做过内核模块,所以我没有正确估计难度。
  • 似乎有多个syscall来挂接文件“打开”(打开,连接,mmap ...),但是我想对于与文件打开有关的所有事情都有一个通用的内核功能。

  • 我确实想减少对php或其模块的补丁数量,以减少每次将平台更新到最新的稳定PHP版本(并因此更频繁,更快速地从上游PHP版本更新)时所需的工作量。我发现从外部修补PHP的行为更好(因为我们有特定的设置,因此修补PHP并向上游建议修补是无关紧要的)。

    相反,我当前正在尝试一个用户级解决方案:使用LD_PRELOAD钩接libc函数,该函数在大多数情况下都可以正常工作,并且实现起来很快,但是遇到了一个我无法单独解决的问题。
    (其想法是与在chroot外部运行的守护进程进行对话,并使用ioctl SENDFD和RECVFD从该守护进程获取文件描述符)。

    当我调用syslog()(首先不带openlog())时,syslog()调用connect()打开文件

    例子:
    folays@phenix:~/ldpreload$ strace logger test 2>&1 | grep connect
    connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
    connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
    connect(1, {sa_family=AF_FILE, path="/dev/log"}, 110) = 0
    

    到目前为止,到目前为止,我尝试钩住libc的connect()函数,但未成功。
    我还尝试在预加载库的_init()函数内的dlopen()中放置一些标志,以测试其中一些标志是否可以使这项工作成功,但没有成功

    这是我的预载库的相关代码:
    void __attribute__((constructor)) my_init(void)
    {
      printf("INIT preloadz %s\n", __progname);
      dlopen(getenv("LD_PRELOAD"), RTLD_NOLOAD | RTLD_DEEPBIND | RTLD_GLOBAL |
                                   RTLD_NOW);
    }
    
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
    {
      printf("HOOKED connect\n");
      int (*f)() = dlsym(RTLD_NEXT, "connect");
      int ret = f(sockfd, addr, addrlen);
      return ret;
    }
    
    int __connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
    {
      printf("HOOKED __connect\n");
      int (*f)() = dlsym(RTLD_NEXT, "connect");
      int ret = f(sockfd, addr, addrlen);
      return ret;
    }
    

    但是libc的connect()函数仍然优先于我的:
    folays@phenix:~/ldpreload$ LD_PRELOAD=./lib-preload.so logger test
    INIT preloadz logger
    [...] no lines with "HOOKED connect..." [...]
    folays@phenix:~/ldpreload$
    

    查看syslog()的代码(apt-get source libc6,glibc-2.13/misc/syslog.c),似乎在misc/syslog.c第386行调用了openlog_internal,而后者又调用了__connect():
                if (LogFile != -1 && !connected)
                {
                        int old_errno = errno;
                        if (__connect(LogFile, &SyslogAddr, sizeof(SyslogAddr))
                            == -1)
                        {
    

    好吧,objdump在libc的动态符号表中向我展示了connect和__connect:
    folays@phenix:~/ldpreload$ objdump -T /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
    00000000000e6d00  w   DF .text  000000000000005e  GLIBC_2.2.5 connect
    00000000000e6d00  w   DF .text  000000000000005e  GLIBC_2.2.5 __connect
    

    但是动态重定位条目中没有连接符号,因此我猜想它解释了为什么我无法成功覆盖openlog_internal()使用的connect(),它可能不使用动态符号重定位,并且可能具有__connect()的地址很难发挥作用(相对于-fPIC偏移量?)。
    folays@phenix:~/ldpreload$ objdump -R /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
    folays@phenix:~/ldpreload$ 
    

    connect是__connect的弱别名:
     eglibc-2.13/socket/connect.c:weak_alias (__connect, connect)
    

    gdb仍然能够在libc的libc连接符号上断点:
    folays@phenix:~/ldpreload$ gdb logger
    (gdb) b connect
    Breakpoint 1 at 0x400dc8
    (gdb) r test
    Starting program: /usr/bin/logger 
    
    Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
    82      ../sysdeps/unix/syscall-template.S: No such file or directory.
            in ../sysdeps/unix/syscall-template.S
    (gdb) c 2
    Will ignore next crossing of breakpoint 1.  Continuing.
    
    Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
    82      in ../sysdeps/unix/syscall-template.S
    (gdb) bt
    #0  connect () at ../sysdeps/unix/syscall-template.S:82
    #1  0x00007ffff7b28974 in openlog_internal (ident=<value optimized out>, logstat=<value optimized out>, logfac=<value optimized out>) at ../misc/syslog.c:386
    #2  0x00007ffff7b29187 in __vsyslog_chk (pri=<value optimized out>, flag=1, fmt=0x40198e "%s", ap=0x7fffffffdd40) at ../misc/syslog.c:274
    #3  0x00007ffff7b293af in __syslog_chk (pri=<value optimized out>, flag=<value optimized out>, fmt=<value optimized out>) at ../misc/syslog.c:131
    

    当然,我自己可以通过执行openlog()来完全跳过此特定问题,但是我猜想其他一些函数将遇到相同类型的问题。

    我不太了解为什么openlog_internal不使用动态符号重定位来调用__connect(),以及是否甚至有可能通过使用简单的LD_PRELOAD机制来挂接此__connect()调用。

    我看到的其他方式是如何做到的:
  • 使用dlopen从LD_PRELOAD加载libc.so,使用dlsym()获取libc的__connect的地址,然后修补函数(明智地使用ASM)以使钩子(Hook)正常工作。看来确实是过度杀伤和容易出错。
  • 使用针对PHP的经过修改的自定义libc在源头直接解决这些问题(打开/连接/mmap函数...)
  • 对LKM进行编码,以将文件访问重定向到我想要的位置。优点:不需要ioctl(SENDFD),也不需要chroot外的守护进程。

  • 如果可能的话,我将非常感谢您学习如何仍然可以挂接到openlog_internal发出的__connect()的调用,建议或与syscall挂接和重定向有关的内核文档的链接。

    我的Google搜索与“hook syscalls”相关,找到了很多对LSM的引用,但似乎只允许ACL回答"is"或“否”,但没有重定向open()路径。

    谢谢阅读。

    最佳答案

    如果不构建自己的经过大量修改的libc,使用LD_PRELOAD绝对是不可能的,在这种情况下,您最好将重定向hacks直接放入内部。不一定需要调用openconnect等。取而代之的是,可能会在库创建时绑定(bind)一个类似的隐藏函数(不可动态重新绑定(bind)),甚至是内联syscall,这当然会随着版本的变化而变化。

    您可以选择一个内核模块,或者在“chroot”内部的所有内容上使用ptrace,并在跟踪过程遇到需要修补的过程时修改syscall的参数。听起来都不容易...

    或者您可以接受,您需要在chroot中存在最少的关键设备节点和文件集,这样它才能正常工作。如果可能的话,使用其他libc代替glibc将有助于您最大程度地减少所需的其他文件数量。

    关于c - 使用LD_PRELOAD覆盖从另一个libc函数调用的libc函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7612724/

    相关文章:

    php - Apache2 mod_ruid2 和 Document ChRoot — 无法连接到 MySQL 数据库

    导致 Gtk 在断言时中止

    c - 如何纠正我的 C 程序中的段错误

    c - 为一个简单的shell程序解析多个命令

    c - 当我点击我的子窗口时,为什么我得到 WM_MOUSEACTIVATE?我让它把焦点转移到 parent 身上,这会搞砸 child 杀死焦点逻辑

    css - 如何预加载CSS :hover background-images

    node.js - 在 Jailkit 中设置 nodejs 和 npm

    css - 预加载的媒体查询 CSS 不适用于 Chrome 中的调整大小

    javascript - addClass 更改 bg-image 但在第一次加载时没有闪烁

    linux - Makefile 中的 Chroot 语法