dropen在chroot中失败

时间:2016-10-20 14:50:21

标签: c linux dlopen 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库时结果是错误。 最后一行:

read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0P#\0\000"..., 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 =&gt; (0xb7f16000)libpthread.so.0 =&gt; /lib/libpthread.so.0(0xb78c6000)libstdc ++。so.6 =&gt;     /usr/lib/libstdc++.so.6(0xb77d1000)libm.so.6 =&gt; /lib/libm.so.6     (0xb77aa000)libc.so.6 =&gt; /lib/libc.so.6(0xb7665000)
    libgcc_s.so.1 =&gt; /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中),我仍然在执行时得到相同的消息:

  

打开库/lib/libc.so.6时出错:找不到版本“GLIBC_2.4”   (/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

1 个答案:

答案 0 :(得分:0)

GLIBC_2.4未找到相关的错误很可能是因为dlopen的调用发生在已经映射到libc副本但尚未公开的过程中版本

因此,我们需要有点创意,并使用辅助例程dlmopen而不是简单的旧dlopen

dlmopen接受一个参数lm_id,它是一个命名空间,它加载所有相关的库 - 如果使用默认值(LM_ID_BASE),它将从程序运行的命名空间开始;例如,它将包含在启动应用程序时已加载的libc

因此,我们不是仅仅调用handle = dlopen(argv[2], RTLD_NOW);,而是通过调用:

来替换它
handle = dlmopen(LM_ID_NEWLM, argv[2], RTLD_NOW | RTLD_LOCAL);

这会将argv[2]所需的所有库的新副本加载到该句柄使用的链接地址空间中。

然后,您可以使用以下方法引用库中的符号:

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时重新执行,因此会在那里搜索。

还有一些其他可能的解决方案 - 如果路径设置不适合re-exec

您可以确保libOne.so在二进制文件的rpath中有$ORIGIN。您可以在编译/链接时使用以下命令执行此操作:

-Wl,-rpath,'$ORIGIN'

引用是为了防止$ORIGIN被shell转义。如果这是makefile,那么您需要使用$$语法进行目标操作。

您可以使用patchelf之类的工具对库进行临时补丁(在我的系统上我做了一个简单的sudo apt-get install patchelf):

patchelf --set-rpath '$ORIGIN' libOne.so

这当然依赖于$ORIGIN中对ld.so的支持(我不记得它何时被添加到ld.so;但它已经过了几年);如果您的ld.so不支持$ORIGIN,那么您可以随时将其替换为/libs/,如果您找不到patchelf发行版,那么您可以随时build it yourself