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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3275015/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-17 22:20:48  来源:igfitidea点击:

LD_PRELOAD affects new child even after unsetenv("LD_PRELOAD")

clinuxbashshared-librariespopen

提问by avner

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”。2 秒后用 ^C 停止它,(或者更好的 ^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. 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.
  3. 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.

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

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

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

采纳答案by mvds

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

编辑:所以问题/问题实际上是:你为什么不能LD_PRELOAD使用main_init()从内部预加载的bash.

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

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

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 bashtries to do something special with the environment (well... would it? being a shell?) then you may be in trouble.

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

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

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

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

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

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

however, running bashor sh:

但是,运行bashsh

unsetenv: 46d170

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

所以你有它。bash让你上当了;-)

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

因此,只需使用您自己的环境修改就地环境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';
    }
}

which can be seen to work in the strace:

可以看出在以下情况下工作strace

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

Q.E.D.

QED

回答by P Shved

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

(答案纯属猜测,可能有误)。

Perhaps, when you fork your process, the context of the loaded libraries persists. So, mylib.sowas loadedwhen 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 loadedby the parent process. Maybe, you should explicitly unload it after forking.

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

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

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

您也可以尝试“降级” mylib.so. 为此,请通过dlopen将其置于符号解析队列末尾的标志重新打开它:

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

回答by scz

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 环境。