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

时间:2020-12-23 20:00:12

my code is as follows: preload.c, with the following content:

我的代码如下: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);
}

then in the shell (do the 2nd command with care!!):

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

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

!!! be carefull with the last command it will result with endless loop of forking "sh -c ls". Stop it after 2 seconds with ^C, (or better ^Z and then see ps).

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

More info

  1. This problem relate to bash in some way; either as the command that the user run, or as the bash the popen execute.
  2. 这个问题在某种程度上与bash有关;或者作为用户运行的命令,或者作为popen执行的bash。

  3. additional Key factors: 1) perform the popen from the pre-loaded library, 2) probably need to do the popen in the initialization section of the library.
  4. 其他关键因素:1)从预加载的库中执行popen,2)可能需要在库的初始化部分中执行popen。

  5. if you use:

    如果您使用:

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

    instead of the last command, you will get many ld-debug files, named /tmp/ld-debug.*. One for each forked process. IN ALL THESE FILES you'll see that symbols are first searched in mylib.so even though LD_PRELOAD was removed from the environment.

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

3 个解决方案

#1


8  

edit: so the problem/question actually was: howcome can't you unset LD_PRELOAD reliably using a preloaded main_init() from within bash.

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

The reason is that execve, which is called after you popen, takes the environment from (probably)

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

extern char **environ;

which is some global state variable that points to your environment. unsetenv() normally modifies your environment and will therefore have an effect on the contents of **environ.

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

If bash tries to do something special with the environment (well... would it? being a shell?) then you may be in trouble.

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

Appearantly, bash overloads unsetenv() even before main_init(). Changing the example code to:

显然,bash甚至在main_init()之前就会重载unsetenv()。将示例代码更改为:

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);
}

shows the problem. In normal runs (running cat, ls, etc) I get this version of unsetenv:

显示问题。在正常运行(运行cat,ls等)我得到这个版本的unsetenv:

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

however, running bash or sh:

但是,运行bash或sh:

unsetenv: 46d170

So, there you have it. bash has got you fooled ;-)

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

So just modify the environment in place using your own unsetenv, acting on **environ:

因此,只需使用您自己的unsetenv修改环境,就像**环境一样:

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';
    }
}

which can be seen to work in the strace:

可以看出在strace中工作:

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

Q.E.D.

#2


2  

(The answer is a pure speculation, and may be is incorrect).

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

Perhaps, when you fork your process, the context of the loaded libraries persists. So, mylib.so was loaded when you invoked the main program via LD_PRELOAD. When you unset the variable and forked, it wasn't loaded again; however it already has been loaded by the parent process. Maybe, you should explicitly unload it after forking.

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

You may also try to "demote" symbols in mylib.so. To do this, reopen it via dlopen with flags that place it to the end of the symbol resolution queue:

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

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

#3


0  

the answer from mvds is incorrect!

来自mvds的答案是不正确的!

popen() will spawn child process which inherit the preloaded .so lied in parent process. this child process don't care LD_PRELOAD environment.

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

#1


8  

edit: so the problem/question actually was: howcome can't you unset LD_PRELOAD reliably using a preloaded main_init() from within bash.

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

The reason is that execve, which is called after you popen, takes the environment from (probably)

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

extern char **environ;

which is some global state variable that points to your environment. unsetenv() normally modifies your environment and will therefore have an effect on the contents of **environ.

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

If bash tries to do something special with the environment (well... would it? being a shell?) then you may be in trouble.

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

Appearantly, bash overloads unsetenv() even before main_init(). Changing the example code to:

显然,bash甚至在main_init()之前就会重载unsetenv()。将示例代码更改为:

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);
}

shows the problem. In normal runs (running cat, ls, etc) I get this version of unsetenv:

显示问题。在正常运行(运行cat,ls等)我得到这个版本的unsetenv:

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

however, running bash or sh:

但是,运行bash或sh:

unsetenv: 46d170

So, there you have it. bash has got you fooled ;-)

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

So just modify the environment in place using your own unsetenv, acting on **environ:

因此,只需使用您自己的unsetenv修改环境,就像**环境一样:

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';
    }
}

which can be seen to work in the strace:

可以看出在strace中工作:

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

Q.E.D.

#2


2  

(The answer is a pure speculation, and may be is incorrect).

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

Perhaps, when you fork your process, the context of the loaded libraries persists. So, mylib.so was loaded when you invoked the main program via LD_PRELOAD. When you unset the variable and forked, it wasn't loaded again; however it already has been loaded by the parent process. Maybe, you should explicitly unload it after forking.

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

You may also try to "demote" symbols in mylib.so. To do this, reopen it via dlopen with flags that place it to the end of the symbol resolution queue:

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

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

#3


0  

the answer from mvds is incorrect!

来自mvds的答案是不正确的!

popen() will spawn child process which inherit the preloaded .so lied in parent process. this child process don't care LD_PRELOAD environment.

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