LD_PRELOAD affects new child even after unsetenv("LD_PRELOAD")
my code is as follows: preload.c, with the following content:
#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!!):
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).
More info
- This problem relate to bash in some way; either as the command that the user run, or as the bash the popen execute.
- 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.
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'l开发者_Go百科l see that symbols are first searched in mylib.so even though LD_PRELOAD was removed from the environment.
edit: so the problem/question actually was: howcome can't you unset LD_PRELOAD
reliably using a preloaded main_init()
from within bash
.
The reason is that execve
, which is called after you popen
, takes the environment from (probably)
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
.
If bash
tries to do something special with the environment (well... would it? being a shell?) then you may be in trouble.
Appearantly, bash
overloads unsetenv()
even before main_init()
. Changing the example code to:
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:
unsetenv: 7f4c78fd5290
unsetenv: 7f1127317290
unsetenv: 7f1ab63a2290
however, running bash
or sh
:
unsetenv: 46d170
So, there you have it. bash
has got you fooled ;-)
So just modify the environment in place using your own unsetenv
, acting on **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
:
execve("/bin/sh", ["sh", "-c", "ls"], [... "DD_PRELOAD=mylib.so" ...]) = 0
Q.E.D.
(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.
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:
dlopen("mylib.so", RTLD_NOLOAD | RTLD_LOCAL);
the answer from mvds is incorrect!
popen() will spawn child process which inherit the preloaded .so lied in parent process. this child process don't care LD_PRELOAD environment.
精彩评论