即使在unsetenv(“LD_PRELOAD”)之后,LD_PRELOAD也会影响新的孩子

时间:2010-07-18 09:35:51

标签: c linux bash shared-libraries popen

我的代码如下:preload.c,包含以下内容:

#include <stdio.h>
#include <stdlib.h>

int  __attribute__((constructor))  main_init(void)
{
    printf("Unsetting LD_PRELOAD: %x\n",unsetenv("LD_PRELOAD"));
    FILE *fp = popen("ls", "r");
    pclose(fp);
}

然后在shell中(小心做第二个命令!!):

    gcc preload.c -shared -Wl,-soname,mylib -o mylib.so -fPIC
    LD_PRELOAD=./mylib.so bash

!!!小心最后一个命令,它将导致无限循环的分叉“sh -c ls”。用^ C在2秒后停止它,(或者更好^ Z然后看ps)。

更多信息

  1. 这个问题在某种程度上与bash有关;或者作为用户运行的命令,或者作为popen执行的bash。
  2. 其他关键因素:1)从预加载的库中执行popen,2)可能需要在库的初始化部分执行popen。
  3. 如果你使用:

    LD_DEBUG=all LD_DEBUG_OUTPUT=/tmp/ld-debug LD_PRELOAD=./mylib.so bash
    

    而不是最后一个命令,您将获得许多名为/tmp/ld-debug.*的ld-debug文件。每个分叉过程一个。在所有这些文件中,您将看到首先在mylib中搜索符号。即使LD_PRELOAD已从环境中删除。

3 个答案:

答案 0 :(得分:8)

编辑,所以问题/问题实际上是:如果使用LD_PRELOAD中的预加载main_init(),您无法可靠地取消bash

原因是在execve之后调用的popen从(可能)获取环境

extern char **environ;

这是一个指向您的环境的全局状态变量。 unsetenv()通常会修改您的环境,因此会对**environ的内容产生影响。

如果bash尝试对环境做一些特别的事情(好吧......它会成为一个shell吗?)那么你可能遇到麻烦。

即使在bash之前,unsetenv()重载main_init()也是如此。将示例代码更改为:

extern char**environ;

int  __attribute__((constructor))  main_init(void)
{
int i;
printf("Unsetting LD_PRELOAD: %x\n",unsetenv("LD_PRELOAD"));
printf("LD_PRELOAD: \"%s\"\n",getenv("LD_PRELOAD"));
printf("Environ: %lx\n",environ);
printf("unsetenv: %lx\n",unsetenv);
for (i=0;environ[i];i++ ) printf("env: %s\n",environ[i]);
fflush(stdout);
FILE *fp = popen("ls", "r");
pclose(fp);
}

显示问题。在正常运行中(运行catls等),我得到此版本的unsetenv:

unsetenv: 7f4c78fd5290
unsetenv: 7f1127317290
unsetenv: 7f1ab63a2290

但是,正在运行bashsh

unsetenv: 46d170

所以,你有它。 bash让你被骗了! - )

所以,只需使用您自己的unsetenv修改环境,就**environ采取行动:

for (i=0;environ[i];i++ )
{
    if ( strstr(environ[i],"LD_PRELOAD=") )
    {
         printf("hacking out LD_PRELOAD from environ[%d]\n",i);
         environ[i][0] = 'D';
    }
}

可以看作在strace

中工作
execve("/bin/sh", ["sh", "-c", "ls"], [... "DD_PRELOAD=mylib.so" ...]) = 0

Q.E.D。

答案 1 :(得分:2)

(答案是纯粹的推测,而可能是不正确的。)

也许,当你分叉你的进程时,加载的库的上下文仍然存在。因此,当您通过mylib.so调用主程序时,LD_PRELOAD 已加载。当您取消设置变量并分叉时,它没有再次加载;但它已经由父进程加载。也许,你应该在分叉后明确地卸载它。

您也可以尝试在mylib.so中“降级”符号。要执行此操作,请通过dlopen重新打开它,并使用标记将其置于符号解析队列的末尾:

dlopen("mylib.so", RTLD_NOLOAD | RTLD_LOCAL);

答案 2 :(得分:0)

mvds的回答不正确!

popen()将生成子进程,该进程继承父进程中预先加载的.so。这个子进程并不关心LD_PRELOAD环境。