我的项目旨在为大规模虚拟主机(超过10k虚拟主机)运行php-cgi chrooted ,每个虚拟主机都有自己的chroot,在Ubuntu Lucid x86_64下。
我想避免在每个chroot中创建必要的环境,例如/ dev / null,/ dev / zero,locales,icons ......以及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()函数,但没有成功。 我还尝试在我的preload库的_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),它似乎调用了openlog_internal,而后者又在misc / syslog.c行调用__connect() 386:
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(),建议或与系统调用挂钩和重定向相关的内核文档的链接。
与“钩子系统调用”相关的我的谷歌搜索发现了很多对LSM的引用,但它似乎只允许ACL回答“是”或“否”,但没有重定向open()路径。
感谢阅读。
答案 0 :(得分:2)
如果没有构建自己经过大量修改的libc,LD_PRELOAD
肯定是不可能的,在这种情况下,您可能只是直接将重定向黑客放入其中。无论如何都不一定要拨打open
,connect
等。相反,可能会在库创建时调用类似的隐藏函数(不是动态可重新绑定),甚至是内联系统调用,这当然可以随着版本的变化而无法预测。
您的选项可以是内核模块,也可以在“chroot”内的所有内容上使用ptrace
,并在跟踪进程遇到需要修补的进程时修改syscalls的参数。听起来都不容易......
或者您可以接受在chroot中需要一组最小的关键设备节点和文件才能工作。如果可能,使用不同的libc代替glibc将有助于您最小化所需的其他文件的数量。