Wo ist die Gabel () auf der Gabelbombe: () {: |: &};:?


25

Warnung: Das Ausführen dieses Befehls in den meisten Shells führt zu einem Systemausfall, für dessen Behebung ein erzwungenes Herunterfahren erforderlich ist

Ich verstehe die rekursive Funktion :(){ :|: & };:und was sie tut. Aber ich weiß nicht, wo der Anruf beim Gabelsystem ist. Ich bin mir nicht sicher, aber ich vermute in der Pfeife |.


Verwandte (und lesenswert): Wie funktioniert eine Gabelbombe?
Terdon

Antworten:


30

Als Ergebnis der x | yeingehenden Pipe wird eine Subshell erstellt, die die Pipeline als Teil der Vordergrundprozessgruppe enthält. Dies setzt fort, Unterschalen (über fork()) auf unbestimmte Zeit zu erzeugen , wodurch eine Gabelbombe erzeugt wird.

$ 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

Die Verzweigung tritt jedoch erst auf, wenn der Code ausgeführt wird. Dies ist der letzte Aufruf :in Ihrem Code.

So zerlegen Sie die Funktionsweise der Gabelbombe:

  • :() - eine neue Funktion definieren, die aufgerufen wird :
  • { :|: & } - Eine Funktionsdefinition, die die aufrufende Funktion rekursiv in eine andere Instanz der aufrufenden Funktion im Hintergrund leitet
  • : - Gabelbombenfunktion aufrufen

Dies ist in der Regel nicht zu speicherintensiv, saugt jedoch PIDs auf und verbraucht CPU-Zyklen.


In x | y, warum gibt es eine Unterschale geschaffen? Wenn bash a sieht pipe, führt es meines Erachtens einen pipe()Systemaufruf aus, der zwei zurückgibt fds. Jetzt wird command_left editiert execund die Ausgabe als Eingabe an command_right übergeben. Jetzt ist command_right editiert exec. Also, warum ist BASHPIDjedes Mal anders?
Abhijeet Rastogi

2
@shadyabhi Es ist einfach - xund es ygibt 2 separate Befehle, die in 2 separaten Prozessen ausgeführt werden, sodass Sie 2 separate Subshells haben. Wenn xim selben Prozess wie die Shell ausgeführt wird, xmuss dies eine integrierte sein.
Jw013

24

Das letzte Bit des Codes ;:führt die Funktion aus :(){ ... }. Hier tritt die Gabel auf.

Das Semikolon beendet den ersten Befehl und wir starten einen anderen, dh wir rufen die Funktion auf :. Die Definition dieser Funktion beinhaltet einen Aufruf an sich selbst ( :) und die Ausgabe dieses Aufrufs wird an eine Hintergrundversion weitergeleitet :. Dies beschleunigt den Prozess auf unbestimmte Zeit.

Jedes Mal, wenn Sie die Funktion :()aufrufen, rufen Sie die C-Funktion auf fork(). Letztendlich werden dadurch alle Prozess-IDs (PIDs) im System erschöpft.

Beispiel

Sie können das |:&mit etwas anderem austauschen, um eine Vorstellung davon zu bekommen, was los ist.

Richten Sie einen Beobachter ein

Führen Sie in einem Terminalfenster Folgendes aus:

$ watch "ps -eaf|grep \"[s]leep 61\""

Richten Sie die Gabelbombe "Sicherung verzögert" ein

In einem anderen Fenster führen wir eine leicht modifizierte Version der Gabelbombe aus. Diese Version wird versuchen, sich selbst zu drosseln, damit wir untersuchen können, was es tut. Unsere Version wartet 61 Sekunden, bevor sie die Funktion aufruft :().

Außerdem wird der erste Aufruf nach dem Aufrufen ebenfalls im Hintergrund ausgeführt. Ctrl+ z, dann tippe bg.

$ :(){ sleep 61; : | : & };:

# control + z
[1]+  Stopped                 sleep 61
[2] 5845
$ bg
[1]+ sleep 61 &

Wenn wir nun den jobsBefehl im Anfangsfenster ausführen, sehen wir Folgendes:

$ jobs
[1]-  Running                 sleep 61 &
[2]+  Running                 : | : &

Nach ein paar Minuten:

$ jobs
[1]-  Done                    sleep 61
[2]+  Done                    : | :

Checken Sie mit dem Beobachter ein

In der Zwischenzeit in dem anderen Fenster, in dem wir laufen 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

Prozesshierarchie

Und a ps -auxfzeigt diese Prozesshierarchie:

$ 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
...
...

Putzzeit

A killall bashstoppt die Dinge, bevor sie außer Kontrolle geraten. Wenn Sie auf diese Weise bereinigen, ist dies möglicherweise etwas umständlich. Dies ist eine schonendere Methode, die möglicherweise nicht alle bashMuscheln abreißt.

  1. Bestimmen Sie, in welchem ​​Pseudoterminal die Gabelbombe laufen wird

    $ tty
    /dev/pts/4
  2. Töte das Pseudoterminal

    $ pkill -t pts/4

So was ist los?

Nun, jeder Aufruf von bashund sleepist ein Aufruf der C-Funktion fork()von der bashShell aus, von der aus der Befehl ausgeführt wurde.


7
bashwird möglicherweise auf separaten Terminals ausgeführt. Besser wäre es zu benutzen pkill -t pts/2.
Maciej Piechotka

@MaciejPiechotka - danke für den Tipp. Nie zuvor gesehen, habe ich es der Antwort hinzugefügt!
SLM
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.