我有一个项目旨在运行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()路径。
但我认为这不是最简单的方法:
我确实想减少对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()调用。
我看到的其他方式是如何做到的:
如果可能的话,我将非常感谢您学习如何仍然可以挂接到openlog_internal发出的__connect()的调用,建议或与syscall挂接和重定向有关的内核文档的链接。
我的Google搜索与“hook syscalls”相关,找到了很多对LSM的引用,但似乎只允许ACL回答"is"或“否”,但没有重定向open()路径。
谢谢阅读。
最佳答案
如果不构建自己的经过大量修改的libc,使用LD_PRELOAD
绝对是不可能的,在这种情况下,您最好将重定向hacks直接放入内部。不一定需要调用open
,connect
等。取而代之的是,可能会在库创建时绑定(bind)一个类似的隐藏函数(不可动态重新绑定(bind)),甚至是内联syscall,这当然会随着版本的变化而变化。
您可以选择一个内核模块,或者在“chroot”内部的所有内容上使用ptrace
,并在跟踪过程遇到需要修补的过程时修改syscall的参数。听起来都不容易...
或者您可以接受,您需要在chroot中存在最少的关键设备节点和文件集,这样它才能正常工作。如果可能的话,使用其他libc代替glibc将有助于您最大程度地减少所需的其他文件数量。
关于c - 使用LD_PRELOAD覆盖从另一个libc函数调用的libc函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7612724/