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