dlopen 在 chroot 中失败
dlopen fails in chroot
我正在尝试:我在 /chroot/debian6.0/
中定义了一个环境
我在其中绑定了一些目录并创建了其他目录。一个是 libs/
,其中包含库 libOne.so
及其依赖项
所以:
/chroot/debian6.0/
\--- libs/
\--- libOne.so
\--- other dependencies (*.so)
这个库是在chroot环境下编译的,我想用包含环境的进程运行打开它。
这是代码:
remote.c
#include <unistd.h>
#include <dlfcn.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int res = 0;
void* handle;
/*chdir to first argument(path1)*/
res = chdir(argv[1]);
if (res == 0) {
printf("\nchdir: %s", argv[1]);
} else {
printf("\nError chdir %s\n", argv[1]);
return 1;
}
/*chroot to path in first argument*/
res = chroot(argv[1]);
if (res == 0) {
printf("\nchroot: %s", argv[1]);
} else {
printf("\nError chroot %s\n", argv[1]);
return 1;
}
/*Define path for dependencies*/
putenv("LD_LIBRARY_PATH=/libs/");
/*Opens library*/
handle = dlopen(argv[2], RTLD_NOW);
if (handle == NULL) {
printf("\nError opening %s\n", argv[2]);
return 1;
} else {
printf("\ndlopen: library %s opened\n", argv[2]);
}
return 0;
}
然后我执行以下命令:
./remote "/chroot/debian6.0/Debian-6.0-chroot/" "/libs/libOne.so"
结果是尝试dlopen库时出错。
strace 的最后几行:
read(3, "7ELF[=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=]P#[=12=][=12=]0"..., 1024) = 1024
fstat64(3, {st_mode=S_IFREG|0644, st_size=116600, ...}) = 0
old_mmap(NULL, 119656, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0xb7644000
mprotect(0xb7661000, 872, PROT_NONE) = 0
old_mmap(0xb7661000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x1c000) = 0xb7661000
close(3) = 0
munmap(0xb7f01000, 6291) = 0
munmap(0xb7770000, 6496996) = 0
munmap(0xb7757000, 98784) = 0
munmap(0xb7662000, 1000332) = 0
munmap(0xb7644000, 119656) = 0
write(1, "chroot: /chroot/debian6.0/Deb"..., 48chroot: /chroot/debian6.0/Debian-6.0-chroot/
) = 48
write(1, "Error opening /libs/libD"..., 50Error opening /libs/libOne.so
) = 50
munmap(0xb7f03000, 4096) = 0
_exit(1) = ?
该库似乎具有所有依赖项 - 在 chroot 中我可以 ldd libOne.so
并且我得到
linux-gate.so.1 => (0xb7f16000) libpthread.so.0 => /lib/libpthread.so.0 (0xb78c6000) libstdc++.so.6 =>
/usr/lib/libstdc++.so.6 (0xb77d1000) libm.so.6 => /lib/libm.so.6
(0xb77aa000) libc.so.6 => /lib/libc.so.6 (0xb7665000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7647000) /lib/ld-linux.so.2
(0xb7f17000)
知道为什么 dlopen
失败了吗?或者如何让它发挥作用?
添加了 perror 和 dlerror,我得到:
Error opening library /lib/libc.so.6: version `GLIBC_2.4' not found (required by /libs/libOne.so)) = 117
更新:
虽然我已经将我用来编译程序的 libc 版本复制到我的包含环境和 chroot' 环境(都在 /lib 中),但我在执行时仍然得到相同的消息:
Error opening library /lib/libc.so.6: version `GLIBC_2.4' not found
(required by /libs/libOne.so)) = 117
所以我去了每个 lib 文件夹,这就是我所看到的:
包含环境:
ll /lib
libc-2.2.4.so
libc.so.6 -> libc-2.2.4.so
ldd libc.so.6
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0xb7dcc000)
Chroot 的环境:
ll /lib
libc.so.6 -> libc-2.11.3.so
libc-2.11.3.so
ldd libc.so.6
/lib/ld-linux.so.2 (0xb7fb4000)
linux-gate.so.1 => (0xb7fb3000)
在我的编译环境中:
ll /lib
libc.so.6 -> libc-2.11.3.so
libc-2.11.3.so
ldd libc.so.6
/lib/ld-linux.so.2 (0xf770f000)
linux-gate.so.1 => (0xf770c000)
看来我的libc.so.6的编译和执行版本是一样的,并且链接到第二个一样的,即libc-2.11.3.so
所以,我不明白为什么我会收到 GLIBC_2.4 消息
最后更新:
现在,在 Petesh 的指导和使用 dlmopen
之后,我的 strace 抛出了下一条消息(最后):
writev(2, [{"./remote", 5}, {": ", 2}, {"/lib/libdl.so.2", 15}, {": ", 2}, {"version \`GLIBC_2.3.4\' not found "..., 51}, {"\n", 1}], 6./remote: /lib/libdl.so.2: version `GLIBC_2.3.4' not found (required by ./remote) ) = 76
与 GLIBC_2.4
not being found 有关的错误很可能是因为 dlopen
的调用发生在一个已经映射到 libc 副本中的进程中,该副本不公开版本。
所以,我们需要有点创意,使用辅助套路dlmopen
而不是简单的老套路dlopen
。
dlmopen 采用一个参数 lm_id
,它是一个名称space,它加载所有相关的库 - 如果您使用默认值 (LM_ID_BASE
),它以名称 space 开头=78=] 程序 运行 在里面;例如,其中包含应用程序启动时已经加载的 libc
。
因此,我们不只是调用 handle = dlopen(argv[2], RTLD_NOW);
,而是调用:
handle = dlmopen(LM_ID_NEWLM, argv[2], RTLD_NOW | RTLD_LOCAL);
这会将 argv[2]
需要的所有库的新副本加载到该句柄使用的 link 地址 space 中。
然后您可以使用以下方式在库中引用符号:
symbol = dlsym(handle, "symname");
它应该可以工作。
我稍微更改了您的示例以利用它:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int res = 0;
void* handle;
/*chdir to first argument(path1)*/
res = chdir(argv[1]);
if (res == 0) {
printf("\nchdir: %s", argv[1]);
} else {
printf("\nError chdir %s\n", argv[1]);
return 1;
}
/*chroot to path in first argument*/
res = chroot(argv[1]);
if (res == 0) {
printf("\nchroot: %s", argv[1]);
} else {
printf("\nError chroot %s\n", argv[1]);
return 1;
}
/*Define path for dependencies*/
putenv("LD_LIBRARY_PATH=/libs/");
/*Opens library*/
handle = dlmopen(LM_ID_NEWLM, argv[2], RTLD_NOW); // needs _GNU_SOURCE
if (handle == NULL) {
printf("\nError opening %s: %s\n", argv[2], dlerror());
return 1;
} else {
printf("\ndlopen: library %s opened\n", argv[2]);
}
int (*foo)(void);
foo = (int (*)(void))dlsym(handle, argv[3]);
if (foo == NULL) {
printf("\nError referencing %s: %s\n", argv[3], dlerror());
return 1;
}
printf("%d\n", foo());
return 0;
}
现在,putenv("LD_LIBRARY_PATH=/libs/");
没有做必要的事情 - 你不能在程序的 运行 时改变 LD_LIBRARY_PATH
;根本不会检测到更改。因为您的程序非常简单,我们可以在主要程序的开头做一些事情,例如:
if (0 == getenv("RE_EXEC")) {
putenv("RE_EXEC=1");
putenv("LD_LIBRARY_PATH=/libs/");
execvp(argv[0], argv);
perror("execvp failed");
exit(1);
}
重新执行 LD_LIBRARY_PATH
设置为 /libs
所以它将在那里搜索。
还有一些其他可能的解决方案 - 如果路径设置不适合重新执行
您可以确保 libOne.so
在二进制文件的 rpath 中有 $ORIGIN
。您可以在 compile/link 时间执行此操作:
-Wl,-rpath,'$ORIGIN'
引用是为了防止 $ORIGIN
被 shell 转义。如果这是在 makefile
中,那么您需要对目标操作使用 $$
语法。
您可以 post-hoc 使用类似 patchelf
的工具修补库(在我的系统上我做了一个简单的 sudo apt-get install patchelf
):
patchelf --set-rpath '$ORIGIN' libOne.so
这当然依赖于您 ld.so
中对 $ORIGIN
的支持(我不记得它是什么时候添加到 ld.so
的;但已经有几年了);如果你的 ld.so
不支持 $ORIGIN
那么你总是可以用 /libs/
替换它,如果你找不到适合你的发行版的 patchelf
,那么你总是可以 build it yourself
我正在尝试:我在 /chroot/debian6.0/
中定义了一个环境
我在其中绑定了一些目录并创建了其他目录。一个是 libs/
,其中包含库 libOne.so
及其依赖项
所以:
/chroot/debian6.0/
\--- libs/
\--- libOne.so
\--- other dependencies (*.so)
这个库是在chroot环境下编译的,我想用包含环境的进程运行打开它。
这是代码:
remote.c
#include <unistd.h>
#include <dlfcn.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int res = 0;
void* handle;
/*chdir to first argument(path1)*/
res = chdir(argv[1]);
if (res == 0) {
printf("\nchdir: %s", argv[1]);
} else {
printf("\nError chdir %s\n", argv[1]);
return 1;
}
/*chroot to path in first argument*/
res = chroot(argv[1]);
if (res == 0) {
printf("\nchroot: %s", argv[1]);
} else {
printf("\nError chroot %s\n", argv[1]);
return 1;
}
/*Define path for dependencies*/
putenv("LD_LIBRARY_PATH=/libs/");
/*Opens library*/
handle = dlopen(argv[2], RTLD_NOW);
if (handle == NULL) {
printf("\nError opening %s\n", argv[2]);
return 1;
} else {
printf("\ndlopen: library %s opened\n", argv[2]);
}
return 0;
}
然后我执行以下命令:
./remote "/chroot/debian6.0/Debian-6.0-chroot/" "/libs/libOne.so"
结果是尝试dlopen库时出错。 strace 的最后几行:
read(3, "7ELF[=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=]P#[=12=][=12=]0"..., 1024) = 1024
fstat64(3, {st_mode=S_IFREG|0644, st_size=116600, ...}) = 0
old_mmap(NULL, 119656, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0xb7644000
mprotect(0xb7661000, 872, PROT_NONE) = 0
old_mmap(0xb7661000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x1c000) = 0xb7661000
close(3) = 0
munmap(0xb7f01000, 6291) = 0
munmap(0xb7770000, 6496996) = 0
munmap(0xb7757000, 98784) = 0
munmap(0xb7662000, 1000332) = 0
munmap(0xb7644000, 119656) = 0
write(1, "chroot: /chroot/debian6.0/Deb"..., 48chroot: /chroot/debian6.0/Debian-6.0-chroot/
) = 48
write(1, "Error opening /libs/libD"..., 50Error opening /libs/libOne.so
) = 50
munmap(0xb7f03000, 4096) = 0
_exit(1) = ?
该库似乎具有所有依赖项 - 在 chroot 中我可以 ldd libOne.so
并且我得到
linux-gate.so.1 => (0xb7f16000) libpthread.so.0 => /lib/libpthread.so.0 (0xb78c6000) libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb77d1000) libm.so.6 => /lib/libm.so.6 (0xb77aa000) libc.so.6 => /lib/libc.so.6 (0xb7665000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7647000) /lib/ld-linux.so.2 (0xb7f17000)
知道为什么 dlopen
失败了吗?或者如何让它发挥作用?
添加了 perror 和 dlerror,我得到:
Error opening library /lib/libc.so.6: version `GLIBC_2.4' not found (required by /libs/libOne.so)) = 117
更新:
虽然我已经将我用来编译程序的 libc 版本复制到我的包含环境和 chroot' 环境(都在 /lib 中),但我在执行时仍然得到相同的消息:
Error opening library /lib/libc.so.6: version `GLIBC_2.4' not found (required by /libs/libOne.so)) = 117
所以我去了每个 lib 文件夹,这就是我所看到的:
包含环境:
ll /lib
libc-2.2.4.so
libc.so.6 -> libc-2.2.4.so
ldd libc.so.6
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0xb7dcc000)
Chroot 的环境:
ll /lib
libc.so.6 -> libc-2.11.3.so
libc-2.11.3.so
ldd libc.so.6
/lib/ld-linux.so.2 (0xb7fb4000)
linux-gate.so.1 => (0xb7fb3000)
在我的编译环境中:
ll /lib
libc.so.6 -> libc-2.11.3.so
libc-2.11.3.so
ldd libc.so.6
/lib/ld-linux.so.2 (0xf770f000)
linux-gate.so.1 => (0xf770c000)
看来我的libc.so.6的编译和执行版本是一样的,并且链接到第二个一样的,即libc-2.11.3.so
所以,我不明白为什么我会收到 GLIBC_2.4 消息
最后更新:
现在,在 Petesh 的指导和使用 dlmopen
之后,我的 strace 抛出了下一条消息(最后):
writev(2, [{"./remote", 5}, {": ", 2}, {"/lib/libdl.so.2", 15}, {": ", 2}, {"version \`GLIBC_2.3.4\' not found "..., 51}, {"\n", 1}], 6./remote: /lib/libdl.so.2: version `GLIBC_2.3.4' not found (required by ./remote) ) = 76
与 GLIBC_2.4
not being found 有关的错误很可能是因为 dlopen
的调用发生在一个已经映射到 libc 副本中的进程中,该副本不公开版本。
所以,我们需要有点创意,使用辅助套路dlmopen
而不是简单的老套路dlopen
。
dlmopen 采用一个参数 lm_id
,它是一个名称space,它加载所有相关的库 - 如果您使用默认值 (LM_ID_BASE
),它以名称 space 开头=78=] 程序 运行 在里面;例如,其中包含应用程序启动时已经加载的 libc
。
因此,我们不只是调用 handle = dlopen(argv[2], RTLD_NOW);
,而是调用:
handle = dlmopen(LM_ID_NEWLM, argv[2], RTLD_NOW | RTLD_LOCAL);
这会将 argv[2]
需要的所有库的新副本加载到该句柄使用的 link 地址 space 中。
然后您可以使用以下方式在库中引用符号:
symbol = dlsym(handle, "symname");
它应该可以工作。
我稍微更改了您的示例以利用它:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int res = 0;
void* handle;
/*chdir to first argument(path1)*/
res = chdir(argv[1]);
if (res == 0) {
printf("\nchdir: %s", argv[1]);
} else {
printf("\nError chdir %s\n", argv[1]);
return 1;
}
/*chroot to path in first argument*/
res = chroot(argv[1]);
if (res == 0) {
printf("\nchroot: %s", argv[1]);
} else {
printf("\nError chroot %s\n", argv[1]);
return 1;
}
/*Define path for dependencies*/
putenv("LD_LIBRARY_PATH=/libs/");
/*Opens library*/
handle = dlmopen(LM_ID_NEWLM, argv[2], RTLD_NOW); // needs _GNU_SOURCE
if (handle == NULL) {
printf("\nError opening %s: %s\n", argv[2], dlerror());
return 1;
} else {
printf("\ndlopen: library %s opened\n", argv[2]);
}
int (*foo)(void);
foo = (int (*)(void))dlsym(handle, argv[3]);
if (foo == NULL) {
printf("\nError referencing %s: %s\n", argv[3], dlerror());
return 1;
}
printf("%d\n", foo());
return 0;
}
现在,putenv("LD_LIBRARY_PATH=/libs/");
没有做必要的事情 - 你不能在程序的 运行 时改变 LD_LIBRARY_PATH
;根本不会检测到更改。因为您的程序非常简单,我们可以在主要程序的开头做一些事情,例如:
if (0 == getenv("RE_EXEC")) {
putenv("RE_EXEC=1");
putenv("LD_LIBRARY_PATH=/libs/");
execvp(argv[0], argv);
perror("execvp failed");
exit(1);
}
重新执行 LD_LIBRARY_PATH
设置为 /libs
所以它将在那里搜索。
还有一些其他可能的解决方案 - 如果路径设置不适合重新执行
您可以确保 libOne.so
在二进制文件的 rpath 中有 $ORIGIN
。您可以在 compile/link 时间执行此操作:
-Wl,-rpath,'$ORIGIN'
引用是为了防止 $ORIGIN
被 shell 转义。如果这是在 makefile
中,那么您需要对目标操作使用 $$
语法。
您可以 post-hoc 使用类似 patchelf
的工具修补库(在我的系统上我做了一个简单的 sudo apt-get install patchelf
):
patchelf --set-rpath '$ORIGIN' libOne.so
这当然依赖于您 ld.so
中对 $ORIGIN
的支持(我不记得它是什么时候添加到 ld.so
的;但已经有几年了);如果你的 ld.so
不支持 $ORIGIN
那么你总是可以用 /libs/
替换它,如果你找不到适合你的发行版的 patchelf
,那么你总是可以 build it yourself