Come risultato della pipa in x | y
, viene creata una subshell per contenere la pipeline come parte del gruppo di processi in primo piano. Questo continua a creare subshell (tramite fork()
) all'infinito, creando così una fork bomb.
$ for (( i=0; i<3; i++ )); do
> echo "$BASHPID"
> done
16907
16907
16907
$ for (( i=0; i<3; i++ )); do
> echo "$BASHPID" | cat
> done
17195
17197
17199
Tuttavia, il fork non si verifica fino a quando il codice non viene eseguito, che è l'invocazione finale di :
nel tuo codice.
Per smontare come funziona la fork bomb:
:()
- definire una nuova funzione chiamata:
{ :|: & }
- una definizione di funzione che convoglia ricorsivamente la funzione chiamante in un'altra istanza della funzione chiamante in background:
- chiama la funzione fork bomb
Questo tende a non essere troppo intensivo per la memoria, ma assorbirà i PID e consumerà i cicli della CPU.
L'ultimo bit del codice, ;:
sta eseguendo la funzione :(){ ... }
. È qui che si verifica il fork.
Il punto e virgola termina il primo comando e ne iniziamo un altro, ovvero invocando la funzione :
. La definizione di questa funzione include una chiamata a se stessa (:
) e l'output di questa chiamata viene reindirizzato a una versione in background :
. Questo sostiene il processo a tempo indeterminato.
Ogni volta che chiami la funzione :()
stai chiamando la funzione C fork()
. Alla fine questo esaurirà tutti gli ID di processo (PID) sul sistema.
Esempio
Puoi sostituire il |:&
con qualcos'altro in modo da farti un'idea di cosa sta succedendo.
Imposta un osservatore
In una finestra di terminale, fai questo:
$ watch "ps -eaf|grep \"[s]leep 61\""
Imposta la fork bomb "miccia ritardata"
In un'altra finestra eseguiremo una versione leggermente modificata della fork bomb. Questa versione tenterà di rallentare se stessa in modo da poter studiare cosa sta facendo. La nostra versione dormirà per 61 secondi prima di chiamare la funzione :()
.
Inoltre, eseguiremo il background anche della chiamata iniziale, dopo che è stata richiamata. Ctrl + z , quindi digita bg
.
$ :(){ sleep 61; : | : & };:
# control + z
[1]+ Stopped sleep 61
[2] 5845
$ bg
[1]+ sleep 61 &
Ora, se eseguiamo il file jobs
comando nella finestra iniziale vedremo questo:
$ jobs
[1]- Running sleep 61 &
[2]+ Running : | : &
Dopo un paio di minuti:
$ jobs
[1]- Done sleep 61
[2]+ Done : | :
Controlla con l'osservatore
Nel frattempo nell'altra finestra in cui stiamo eseguendo watch
:
Every 2.0s: ps -eaf|grep "[s]leep 61" Sat Aug 31 12:48:14 2013
saml 6112 6108 0 12:47 pts/2 00:00:00 sleep 61
saml 6115 6110 0 12:47 pts/2 00:00:00 sleep 61
saml 6116 6111 0 12:47 pts/2 00:00:00 sleep 61
saml 6117 6109 0 12:47 pts/2 00:00:00 sleep 61
saml 6119 6114 0 12:47 pts/2 00:00:00 sleep 61
saml 6120 6113 0 12:47 pts/2 00:00:00 sleep 61
saml 6122 6118 0 12:47 pts/2 00:00:00 sleep 61
saml 6123 6121 0 12:47 pts/2 00:00:00 sleep 61
Gerarchia dei processi
E un ps -auxf
mostra questa gerarchia di processi:
$ ps -auxf
saml 6245 0.0 0.0 115184 5316 pts/2 S 12:48 0:00 bash
saml 6247 0.0 0.0 100988 468 pts/2 S 12:48 0:00 \_ sleep 61
....
....
saml 6250 0.0 0.0 115184 5328 pts/2 S 12:48 0:00 bash
saml 6268 0.0 0.0 100988 468 pts/2 S 12:48 0:00 \_ sleep 61
saml 6251 0.0 0.0 115184 5320 pts/2 S 12:48 0:00 bash
saml 6272 0.0 0.0 100988 468 pts/2 S 12:48 0:00 \_ sleep 61
saml 6252 0.0 0.0 115184 5324 pts/2 S 12:48 0:00 bash
saml 6269 0.0 0.0 100988 464 pts/2 S 12:48 0:00 \_ sleep 61
...
...
Tempo di pulizia
Un killall bash
fermerà le cose prima che sfuggano di mano. Pulire in questo modo potrebbe essere un po' pesante, un modo più gentile e gentile che potenzialmente non strapperà ogni bash
shell down, sarebbe quello di fare quanto segue:
-
Determina in quale pseudo terminale verrà eseguito il fork bomb
$ tty /dev/pts/4
-
Uccidi lo pseudo terminale
$ pkill -t pts/4
Allora cosa sta succedendo?
Bene ogni invocazione di bash
e sleep
è una chiamata alla funzione C fork()
dal bash
shell da cui è stato eseguito il comando.