Где вилка () на бомбе: () {: |: &};:?

25

Предупреждение: выполнение этой команды в большинстве оболочек приведет к поломке системы, для исправления которой потребуется принудительное отключение

Я понимаю рекурсивную функцию :(){ :|: & };:и что она делает. Но я не знаю, где находится системный вызов fork. Я не уверен, но я подозреваю в трубе |.

mavillan
источник
Связанный (и стоит прочитать): Как работает вилочная бомба?
Тердон

Ответы:

30

В результате конвейера x | yсоздается подоболочка, которая содержит конвейер как часть группы процессов переднего плана. Это продолжает создавать подоболочки (через fork()) бесконечно, создавая таким образом вилочную бомбу.

$ 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

Однако на самом деле ответвление не происходит до тех пор, пока код не будет запущен, что является последним вызовом :вашего кода.

Чтобы разобрать, как работает вилочная бомба:

  • :() - определить новую функцию под названием :
  • { :|: & } - определение функции, которое рекурсивно передает вызывающую функцию в другой экземпляр вызывающей функции в фоновом режиме
  • : - вызвать функцию вилочной бомбы

Это, как правило, не слишком интенсивно использует память, но будет поглощать PID и потреблять циклы процессора.

Крис Даун
источник
В x | y, почему есть суб-оболочка создана? Насколько я понимаю, когда bash видит a pipe, он выполняет pipe()системный вызов, который возвращает два fds. Теперь command_left редактируется, execи вывод подается в command_right в качестве ввода. Теперь command_right редактируется exec. Итак, почему BASHPIDкаждый раз разные?
Абхиджит Растоги
2
@shadyabhi Это просто - xи yэто две отдельные команды, работающие в двух отдельных процессах, так что у вас есть две отдельные вложенные оболочки . Если xвыполняется в том же процессе, что и оболочка, это означает, что он xдолжен быть встроенным.
jw013
24

Последний бит кода, ;:это функция :(){ ... }. Это где вилка происходит.

Точка с запятой завершает первую команду, а мы запускаем другую, то есть вызываем функцию :. Определение этой функции включает в себя вызов самой себя ( :), и результат этого вызова передается в фоновую версию :. Это поддерживает процесс на неопределенный срок.

Каждый раз , когда вы вызываете функцию :()вы вызываете функцию C fork(). В конечном итоге это приведет к исчерпанию всех идентификаторов процессов (PID) в системе.

пример

Вы можете поменяться |:&с чем-то другим, чтобы вы могли понять, что происходит.

Настройте наблюдателя

В одном окне терминала сделайте это:

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

Настройте "взрыватель с задержкой" вилка бомба

В другом окне мы запустим слегка модифицированную версию вилочной бомбы. Эта версия попытается задушить себя, чтобы мы могли изучить, что она делает. Наша версия будет спать в течение 61 секунды перед вызовом функции :().

Кроме того, мы также отправим фоновый вызов после его вызова. Ctrl+ z, затем введите bg.

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

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

Теперь, если мы запустим jobsкоманду в начальном окне, мы увидим это:

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

Через пару минут:

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

Проверьте с наблюдателем

Тем временем в другом окне, где мы бежим 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

Иерархия процессов

И ps -auxfпоказывает эту иерархию процесса:

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

Время уборки

А killall bashостановит вещи прежде, чем они выйдут из-под контроля. Выполнение очистки таким образом может быть немного тяжелым, более мягким способом, который потенциально не разрушит каждую bashраковину, было бы сделать следующее:

  1. Определите, в каком псевдо-терминале будет работать вилочная бомба

    $ tty
    /dev/pts/4
  2. Убить псевдо-терминал

    $ pkill -t pts/4

Итак, что происходит?

Хорошо, каждый вызов bashи sleepявляется вызовом функции C fork()из bashоболочки, из которой была выполнена команда.

SLM
источник
7
bashможет работать на отдельных терминалах. Лучше было бы использовать pkill -t pts/2.
Мацей Печотка
@MaciejPiechotka - спасибо за совет. Никогда раньше такого не видел, я добавил это в ответ!
SLM