2010-07-18 10 views
5

il mio codice è il seguente: preload.c, con il seguente contenuto:LD_PRELOAD colpisce nuovo figlio anche dopo unsetenv ("LD_PRELOAD")

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

poi nel guscio (fare il 2 ° comando con cura !!):

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

!!! stai attento con l'ultimo comando che si otterrà con un ciclo infinito di biforcazioni "sh -c ls". Fermalo dopo 2 secondi con^C, (o meglio^Z e poi vedi ps).

Maggiori informazioni

  1. Questo problema si riferiscono per colpire in qualche modo; o come comando che l'utente esegue, o come bash che il popen esegue.
  2. fattori chiave aggiuntivi: 1) eseguire il popen dalla libreria precaricata, 2) probabilmente è necessario eseguire il popen nella sezione di inizializzazione della libreria.
  3. se si utilizza:

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

    al posto dell'ultimo comando, si ottengono molti file LD-debug, denominata /tmp/ld-debug.*. Uno per ogni processo biforcato. IN TUTTI QUESTI FILES vedrai che i simboli vengono prima cercati su mylib.so anche se LD_PRELOAD è stato rimosso dall'ambiente.

+0

di quale lingua stiamo parlando? – mvds

+0

stiamo parlando del linguaggio C – avner

+0

@ user395074, quindi, forse, dovresti aver regolato i tuoi tag per riflettere la lingua (fai clic sul link "modifica"). Inoltre, il tag [preloader] non sembra riflettere il componente del sistema operativo che stiamo discutendo. –

risposta

8

edit: così il problema/domanda in realtà era: howcome non può disinserire LD_PRELOAD in modo affidabile utilizzando main_init() precaricato da bash.

La ragione è che execve, che si chiama dopo aver popen, prende l'ambiente da (probabilmente)

extern char **environ; 

che è un po 'variabile di stato globale che punta al proprio ambiente. unsetenv() normalmente modifica l'ambiente e pertanto avrà un effetto sul contenuto di **environ.

Se bash prova a fare qualcosa di speciale con l'ambiente (beh ... sarebbe una shell?) Allora potresti essere nei guai.

Appare in anticipo, bash sovraccarichi unsetenv() anche prima di main_init(). Cambiando il codice di esempio in:

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

mostra il problema. Nel piste normali (in esecuzione cat, ls, ecc) ho questa versione di unsetenv:

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

Tuttavia, l'esecuzione bash o sh:

unsetenv: 46d170 

Quindi, il gioco è fatto. bash ha avuto modo si ingannare ;-)

Quindi, solo modificare l'ambiente in posizione utilizzando il proprio unsetenv, agendo su **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'; 
    } 
} 

che può essere visto per lavorare nel strace:

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

QED

+0

LD_PRELOAD non è nell'ambiente del processo figlio (né nel genitore dopo unsetenv). La risposta di Pavel suggerisce il motivo: LD_PRELOAD è usato dal loader e non dal mio programma. mylib.so è già caricato e il loader probabilmente mantiene il suo comportamento senza rileggere LD_PRELOAD. fork & execle è un'opzione; tuttavia, il popen è molto preferito dal momento che mi permette di leggere l'output del processo figlio in un modo semplice. strace gace un sacco di informazioni che non mi hanno aiutato. Ho usato: export LD_DEBUG = symnols e ho visto chiaramente che tutti i simboli sono cercati in mylib.so prima di ogni altra lib. – avner

+0

questo sta diventando sempre più interessante, ho appena aggiornato la mia risposta con qualche altro suggerimento – mvds

+0

@avner: ancora più suggerimenti nella risposta, costruito il mio '.so' e ancora non riesco a vedere quello che stai vedendo ... – mvds

2

(La risposta è una pura speculazione, e possono essere non è corretto).

Forse, quando si esegue il fork del processo, il contesto delle librerie caricate persiste. Quindi, mylib.soè stato caricato quando è stato richiamato il programma principale tramite LD_PRELOAD. Quando si disinserisce la variabile e si biforca, non è stata caricata di nuovo; tuttavia già è stato caricato dal processo padre. Forse, dovresti scaricarlo esplicitamente dopo la forking.

Si può anche provare a "abbassare" i simboli in mylib.so. Per fare questo, riaprirlo tramite dlopen con le bandiere che pongono alla fine della coda risoluzione simbolo:

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

+0

Grazie per il commento di Pavel. Questo suona ragionevole; tuttavia, nel mio caso questa soluzione alternativa è impossibile. Non riesco a cambiare nulla dopo la forchetta, dal momento che sto usando il popen che fa forchetta/exec per me. (Ancora, fork & exec sono opzioni di fallback se non riesco a farlo con i popen). Il processo padre deve rimanere con l'ambiente LD_PRELOAD (si tratta di un processo cliente con più thread). – avner

+0

stai dicendo che dopo 'exec' le librerie caricate persistono? Vedo in un batter d'occhio che anche libc viene riaperto di nuovo dopo 'exec', non riesco a immaginare che altre librerie prevalgano. – mvds

+0

@mvds, essere riaperti e scaricati/caricati sono cose diverse. È possibile riaprire una libreria già caricata. –

0

la risposta di mvds non è corretta!

popen() genera un processo figlio che eredita il file .so precaricato nel processo padre. a questo processo figlio non interessa l'ambiente LD_PRELOAD.

Problemi correlati