Pourquoi ces bombes bash fork fonctionnent-elles différemment et quelle est la signification de & dedans?

16

Je comprends comment fonctionne une bombe fork normale, mais je ne comprends pas vraiment pourquoi le & à la fin de la bombe bash fork commune est requis et pourquoi ces scripts se comportent différemment:

:(){ (:) | (:) }; :

et

:(){ : | :& }; :

Le premier provoque une pointe d'utilisation du processeur avant de me renvoyer à l'écran de connexion. À la place, ce dernier provoque simplement le gel de mon système, ce qui m'oblige à un redémarrage dur. Pourquoi donc? Les deux créent continuellement de nouveaux processus, alors pourquoi le système se comporte-t-il différemment?

Les deux scripts se comportent également différemment de

:(){ : | : }; :

ce qui ne pose aucun problème, même si je m'attendais à ce qu'ils se ressemblent. La page de manuel bash indique que les commandes dans un pipeline sont déjà exécutées dans un sous-shell, donc je suis amené à croire que: | : devrait déjà suffire. Je crois que je devrais simplement exécuter le pipeline dans un nouveau sous-shell, mais pourquoi cela change-t-il tant?

Edit: En utilisant htop et en limitant la quantité de processus, j'ai pu voir que la première variante crée un arbre de processus réel, la deuxième variante crée tous les processus au même niveau et la dernière variante ne semble pas créer de processus du tout. Cela m'embrouille encore plus, mais peut-être que cela aide d'une manière ou d'une autre?

Dan K.
la source
2
je pense que votre dernière variante manque un point-virgule::(){ : | :; }; :
adonis

Réponses:

22

AVERTISSEMENT N'ESSAYEZ PAS DE L'EXÉCUTER SUR UNE MACHINE DE PRODUCTION. JUSTE PAS. Avertissement: pour essayer des "bombes", assurez-vous qu'elles ulimit -usont en cours d'utilisation. Lisez ci-dessous [a] .

Définissons une fonction pour obtenir le PID et la date (heure):

bize:~$ d(){ printf '%7s %07d %s\n' "$1" "$BASHPID" "$(date +'%H:%M:%S')"; }

Une fonction simple et sans problème bombpour le nouvel utilisateur (protégez-vous: lisez [a] ):

bize:~$ bomb() { d START; echo "yes"; sleep 1; d END; } >&2

Lorsque cette fonction est appelée pour être exécutée, cela fonctionne comme suit:

bize:~$ bomb
  START 0002786 23:07:34
yes
    END 0002786 23:07:35
bize:~$

La commande dateest exécutée, puis un "oui" est imprimé, un sommeil pendant 1 seconde, puis la commande de fermeture dateet, enfin, la fonction quitte l'impression d'une nouvelle invite de commande. Rien d'extraordinaire.

| tuyau

Lorsque nous appelons la fonction comme ceci:

bize:~$ bomb | bomb
  START 0003365 23:11:34
yes
  START 0003366 23:11:34
yes
    END 0003365 23:11:35
    END 0003366 23:11:35
bize:~$

Deux commandes démarrent à un moment donné, les deux se terminent 1 seconde plus tard, puis l'invite revient.

C'est la raison pour laquelle la pipe |, pour démarrer deux processus en parallèle.

& Contexte

Si nous changeons l'appel en ajoutant une fin &:

bize:~$ bomb | bomb &
[1] 3380
bize:~$
  START 0003379 23:14:14
yes
  START 0003380 23:14:14
yes
    END 0003379 23:14:15
    END 0003380 23:14:15

L'invite revient immédiatement (toute l'action est envoyée en arrière-plan) et les deux commandes sont exécutées comme précédemment. Veuillez noter la valeur du "numéro de travail" [1]imprimé avant le PID du processus 3380. Plus tard, le même numéro sera imprimé pour indiquer que le tuyau est terminé:

[1]+  Done                    bomb | bomb

C'est l'effet de &.

C'est la raison de &: accélérer le démarrage des processus.

Nom plus simple

Nous pouvons créer une fonction appelée simplement bpour exécuter les deux commandes. Tapé en trois lignes:

bize:~$ b(){
> bomb | bomb
> }

Et exécuté comme:

bize:~$ b
  START 0003563 23:21:10
yes
  START 0003564 23:21:10
yes
    END 0003564 23:21:11
    END 0003563 23:21:11

Notez que nous avons utilisé non ;dans la définition de b(les retours à la ligne ont été utilisés pour séparer les éléments). Cependant, pour une définition sur une ligne, il est habituel d'utiliser ;, comme ceci:

bize:~$ b(){ bomb | bomb ; }

La plupart des espaces ne sont pas non plus obligatoires, nous pouvons écrire l'équivalent (mais moins clair):

bize:~$ b(){ bomb|bomb;}

Nous pouvons également utiliser un &pour séparer le }(et envoyer les deux processus en arrière-plan).

La bombe.

Si nous faisons mordre sa queue (en se faisant appeler), nous obtenons la "bombe fourchette":

bize:~$ b(){ b|b;}       ### May look better as b(){ b | b ; } but does the same.

Et pour le faire appeler plus de fonctions plus rapidement, envoyez le tuyau en arrière-plan.

bize:~$ b(){ b|b&}       ### Usually written as b(){ b|b& }

Si nous ajoutons le premier appel à la fonction après un requis ;et changeons le nom, :nous obtenons:

bize:~$ :(){ :|:&};:

Habituellement écrit comme :(){ :|:& }; :

Ou, écrit d'une manière amusante, avec un autre nom (un bonhomme de neige):

☃(){ ☃|☃&};☃

L'ulimit (que vous auriez dû définir avant d'exécuter ceci) fera revenir l'invite assez rapidement après beaucoup d'erreurs (appuyez sur Entrée lorsque la liste d'erreurs s'arrête pour obtenir l'invite).

La raison pour laquelle on appelle cela une "bombe fork" est que la façon dont le shell démarre un sous-shell est de forker le shell en cours d'exécution, puis d'appeler exec () au processus forké avec la commande à exécuter.

Un tuyau va "bifurquer" deux nouveaux processus. Le faire à l'infini provoque une bombe.
Ou un lapin comme on l'appelait à l'origine parce qu'il se reproduit si rapidement.


Horaire:

  1. :(){ (:) | (:) }; time :
    Terminé
    réel 0m45.627s

  2. :(){ : | :; }; time :
    Terminé
    réel 0m15.283s

  3. :(){ : | :& }; time :
    réel 0m00.002 s
    Toujours en marche


Vos exemples:

  1. :(){ (:) | (:) }; :

    Là où la deuxième fermeture )sépare, }c'est une version plus complexe de :(){ :|:;};:. Chaque commande d'un tube est de toute façon appelée dans un sous-shell. Quel est l'effet du ().

  2. :(){ : | :& }; :

    Est la version la plus rapide, écrite sans espaces: :(){(:)|:&};:(13 caractères).

  3. :(){ : | : }; : ### fonctionne en zsh mais pas en bash.

    A une erreur de syntaxe (en bash), un métacaractère est nécessaire avant la fermeture },
    comme ceci:

    :(){ : | :; }; :

[a] Créez un nouvel utilisateur propre (j'appellerai le mienbize). Connectez-vous à ce nouvel utilisateur dans une consolesudo -i -u bize, ou:

$ su - bize
Password: 
bize:~$

Vérifiez puis modifiez la max user processeslimite:

bize:~$ ulimit -a           ### List all limits (I show only `-u`)
max user processes              (-u) 63931
bize:~$ ulimit -u 10        ### Low
bize:~$ ulimit -a
max user processes              (-u) 1000

En utilisant seulement 10 fonctionne comme qu'un seul nouvel utilisateur solitaire: bize. Il est plus facile d'appeler killall -u bizeet de débarrasser le système de la plupart des bombes (pas toutes). S'il vous plaît ne demandez pas lesquels fonctionnent toujours, je ne le dirai pas. Mais quand même: est assez faible mais du côté sûr, adaptez-vous à votre système .
Cela garantira qu'une "bombe à fourche" n'effondrera pas votre système .

Lectures complémentaires:

Communauté
la source