Journal Comment rendre le shebang plus festif

Posté par (page perso) . Licence CC by-sa.
Tags :
69
12
oct.
2019

Sommaire

Suite à une discussion avec un collègue, je me suis lancé dans un correctif du noyau pour remplacer le shebang (#!) par un caractère bien plus adapté : 🍻. Il faut savoir que le shebang est lu par le noyau. Quand on demande d’exécuter un fichier à ce dernier, il essaye toutes les méthodes qu’il connait (script, ELF) jusqu’à ce qu’il y en ait une qui fonctionne. Une de ces méthodes est le script où il exécute le binaire donné par le shebang et passe le script en paramètre.

Mise en place

Compilation d’un noyau vanilla

Déjà, la première chose, c’est de vérifier que j’arrive encore à compiler un noyau. Et surtout que j’arrive à l’amorcer. Comme j’ai un pressentiment que ça peut poser problème dans l’amorçage d’un système classique, je préfère utiliser une machine virtuelle.

Donc, première étape, make oldconfig et make -j16. Bon, depuis la dernière fois que j’ai compilé un noyau, il y a des modules signés. Comme je ne compte pas utiliser de module, je désactive la fonctionnalité de signature avec make menuconfig dans le menu « Enable loadable module support » et les clefs dans « security options → Trusted keys ». Bien, ça compile.

Deuxième étape comment compiler un noyau sans l’installer sur le système (ce que 98,7 % des tutoriels sur le sujet font) ? Après quelques recherches, il suffit de faire un make bzImage. Le noyau généré se trouve dans arch/x86/boot/bzImage.

QEMU permet d’amorcer directement un noyau sans devoir passer par un gestionnaire d’amorçage (bootloader). C’est pratique, ça va me simplifier mes tests. Je peux donc lancer la commande suivante :

qemu-system-x86_64 -kernel arch/x86/boot/bzImage

Et ça démarre, avec un kernel panic parce qu’il ne trouve pas le système de fichiers racine (rootfs), ce qui est plutôt rassurant vu que je ne lui en ai pas donné.

QEMU permet d’utiliser un initrd aussi, mais vu ma configuration assez simple, je préfère m’en passer. Et comme je n’oublie jamais rien, je me dis que je compilerai les modules dont j’ai besoin en statique.

Amorçage avec BusyBox

Donc, il me faut un programme d’initialisation, de préférence simple et sans script Shell. Il me faut aussi un système minimaliste avec un Shell et quelques binaires comme ls et cat pour pouvoir déboguer. Le job parfait pour BusyBox. Donc, je crée une image qcow2 avec virt-make-fs, qui contient BusyBox et un script shell basique pour vérifier que ça fonctionne, et un lien symbolique pour avoir un /bin/sh.

cat > hostdir/shebang.sh <<EOF
#!/bin/sh

echo test
EOF
cp -i /bin/busybox hostdir/
mkdir hostdir/bin
cd hostdir/bin
ln -s ../busybox sh
cd ../..
virt-make-fs --partition --format=qcow2 --size=+200M hostdir hostimg.qcow2

N. D. M. : commandes modifiées suites aux remarques dans les commentaires.

Je peux donc amorcer avec :

qemu-system-x86_64 -kernel  arch/x86/boot/bzImage -append "root=/dev/sda1 init=/busybox sh" -hda hostimg.qcow2

Attention à bien mettre le sh à la fin de la ligne d’amorçage, sinon BusyBox affiche l’aide et quitte, et le noyau panique parce que le processus numéro 1 (PID 1) est parti.

Après quelques essais pour trouver les modules à compiler en statique pour avoir le pilote pour le disque (et le pilote pour ext4 que j’avais oublié), j’ai donc un système qui démarre avec un shell et mon script de test fonctionne (mais ce n’est pas surprenant).

Correctif

Je décide d’abord de modifier un seul caractère du shebang pour vérifier que c’est bien possible, mon shebang sera donc ##. Je trouve le fichier où le shebang est défini grâce à Google : fs/binfmt_script.c. Pratique, on dirait qu’il suffit de changer un caractère. Je fais donc la modification suivante :

--- a/fs/binfmt_script.c
+++ b/fs/binfmt_script.c
@@ -39,7 +39,7 @@ static int load_script(struct linux_binprm *bprm)
        int retval;

        /* Not ours to exec if we don't start with "#!". */
-       if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
+       if ((bprm->buf[0] != '#') || (bprm->buf[1] != '#'))
                return -ENOEXEC;

        /*

Et je peux tester avec un nouveau script qui comment par ##/bin/sh. Il fonctionne bien et un script avec un #!/bin/sh renvoie une erreur. Je peux donc passer à l’étape suivante, un shebang sur quatre caractères, vu que l’émoji prend quatre octets en UFT-8 : f0 9f 8d bb. Comme il y a des opérations sur le tampon qui contient la ligne qui sont faites en décalant le démarrage en se basant sur la longueur de deux octets, on remplace ça par un quatre :

--- a/fs/binfmt_script.c
+++ b/fs/binfmt_script.c
@@ -39,7 +39,8 @@ static int load_script(struct linux_binprm *bprm)
        int retval;

        /* Not ours to exec if we don't start with "#!". */
-       if ((bprm->buf[0] != '#') || (bprm->buf[1] != '#'))
+    // 🍻 f0 9f 8d bb
+       if ((bprm->buf[0] != '\xf0') || (bprm->buf[1] != '\x9f') || (bprm->buf[2] != '\x8d' || bprm->buf[3] != '\xbb'))
                return -ENOEXEC;

        /*
@@ -73,7 +74,7 @@ static int load_script(struct linux_binprm *bprm)
        buf_end = bprm->buf + sizeof(bprm->buf) - 1;
        cp = strnchr(bprm->buf, sizeof(bprm->buf), '\n');
        if (!cp) {
-               cp = next_non_spacetab(bprm->buf + 2, buf_end);
+               cp = next_non_spacetab(bprm->buf + 4, buf_end);
                if (!cp)
                        return -ENOEXEC; /* Entire buf is spaces/tabs */
                /*
@@ -93,7 +94,7 @@ static int load_script(struct linux_binprm *bprm)
                else
                        break;
        }
-       for (cp = bprm->buf+2; (*cp == ' ') || (*cp == '\t'); cp++);
+       for (cp = bprm->buf+4; (*cp == ' ') || (*cp == '\t'); cp++);
        if (*cp == '\0')
                return -ENOEXEC; /* No interpreter name found */
        i_name = cp;

On recompile, relance la machine virtuelle et l’on peut tester avec un script qui contient le nouveau shebang.

Vous pouvez retrouver les deux correctifs ici : https://github.com/claudex/linux-shebang/commits/master.

Et ça marche ?

Premièrement, le sh de BusyBox (et sans doute tous les shells) n’aime pas que la première ligne ne commence pas par #. On se retrouve avec l’erreur « ./beerbang.sh: line 1: 🍻/bin/sh not found. » car, vu que la ligne ne commence pas par #, il ne la traite pas comme un commentaire et donc essaye de l’exécuter et, évidemment, il ne trouve pas de commande pareille à exécuter. Mais il exécute le reste du script sans problème.

Screenshot busybox

Le contenu du beerbang.sh :

🍻/bin/sh

echo "beers!"

Un autre point, mais qui relève plus du détail. Comme on fait de l’Unicode, il faudrait normalement canoniser la séquence de caractères, car il y a peut‐être d’autres façons de l’écrire. Je laisse l’exercice au lecteur.

Et l’intérêt de la chose dans tout ça ?

Strictement aucun !

  • # Tout ça pour rien

    Posté par (page perso) . Évalué à 10 (+13/-0).

    En fait, c'est déjà possible via binfmt-misc https://en.m.wikipedia.org/wiki/Binfmt_misc#Registration et un programme userland qui fait le tri pour envoyer au bon interpréteur.

    « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

    • [^] # Re: Tout ça pour rien

      Posté par (page perso) . Évalué à 10 (+18/-0).

      Avec des 🍻 et des |, tu pourrais faire un biéroduc peut-être ? Ou une version pour Wine ? Par contre ça serait plus sympa de faire un shell qui dégrade peu à peu les affichages, en les rendant flous, puis mouvants, et de remplacer les bips par des burps. Mais ça n'aurait pas plus d'intérêt.

  • # Sympa !

    Posté par . Évalué à 10 (+11/-0).

    Et l’intérêt de la chose dans tout ça

    Strictement aucun.

    Je ne suis pas d'accord. La démarche est très cool. Jouer avec ce qui nous entour c'est le meilleur moyen de les comprendre et de se les approprier. C'est une excellente démarche en info comme dans tous les domaines (prendre le temps de regarder un peu plus en détail notre moyen de locomotion par exemple).

    Après je ne suis pas très bière donc je ne cautionnerais pas :p

    • [^] # Re: Sympa !

      Posté par (page perso) . Évalué à 0 (+1/-4). Dernière modification le 12/10/19 à 22:47.

      Ça aurait été une partie obscure du kernel, ça aurait pu être intéressant pour comprendre son fonctionnement. Là, on se rend juste compte que ça correspond à ce qui est connu et documenté un peu partout.

      « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

      • [^] # Re: Sympa !

        Posté par (page perso) . Évalué à 10 (+13/-0).

        Oui et non, quand je vois le journal je vois que l’auteur a du savoir faire tout ça :

        • comprendre l’arborescence d’un code source existant
        • modifier de manière élémentaire un source en c
        • compiler un noyau
        • créer une image de système de fichier
        • démarrer une machine virtuelle directement avec un noyau
        • démarrer un init custom

        Typiquement cela pourrait être un projet de fin de formation pour valider que les personnes formées sont prêtes à rejoindre une équipe qui fait du développement de système embarqué, étant considéré que les compétences « développement en tel ou tel langage » et « connaissance approfondie de tel ou tel noyau » n’entrent pas dans le cadre de cette formation (cela peut faire l’objet d’autres formations).

        ce commentaire est sous licence cc by 4 et précédentes

        • [^] # Re: Sympa !

          Posté par . Évalué à 7 (+5/-0).

          On apprend aussi que ce n'est pas un hasard si le shebang commence par un dièse et non par 🍻.

          • [^] # Re: Sympa !

            Posté par (page perso) . Évalué à 3 (+1/-1). Dernière modification le 13/10/19 à 08:21.

            Je ne suis pas sûr que ce soit pour ça qu'il commence par un dièse. Mais comme ça fait tellement longtemps que c'est comme ça, les interpréteurs s'attendent à se comportement et pas un autre.

            « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

            • [^] # Re: Sympa !

              Posté par (page perso) . Évalué à 4 (+1/-0).

              D'ailleurs, il faudrait trouver un langage interprété qui n'a pas le dièse comme début de commentaire pour voir comment il gère ce cas là.

              « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

              • [^] # Re: Sympa !

                Posté par . Évalué à 1 (+0/-0).

                lisp par exemple.

              • [^] # Re: Sympa !

                Posté par . Évalué à 4 (+2/-0).

                Java, depuis la version 11:
                https://openjdk.java.net/jeps/330
                ils ont prévu un cas particulier pour le shebang

              • [^] # Re: Sympa !

                Posté par (page perso) . Évalué à 6 (+4/-0).

                D'ailleurs, il faudrait trouver un langage interprété qui n'a pas le dièse comme début de commentaire pour voir comment il gère ce cas là.

                L'interpréteur PHP sait que le #! en tout début d'un fichier correspond au shebang et qu'il faut ignorer cette première ligne. D'après la documentation cela ne fonctionnerait pas sous Windows…

                La documentation en français :
                https://www.php.net/manual/fr/features.commandline.usage.php

                […] la première ligne du script devra être #!/usr/bin/php (à remplacer par le chemin vers le binaire PHP CLI sur le système sous-jacent). Le reste du fichier doit contenir le code PHP normal, compris entre les balises ouvrantes/fermantes. Après avoir mis les droits d'exécution sur le script (chmod +x test), il peut être exécuté comme un script shell ou perl habituel

                Exemple :

                $ cat test.php
                #!/usr/bin/env php
                <?php
                var_dump($argv);
                ?>
                
                $ chmod +x test.php
                
                $ ./test.php coucou DLFP
                array(3) {
                  [0]=>
                  string(10) "./test.php"
                  [1]=>
                  string(6) "coucou"
                  [2]=>
                  string(4) "DLFP"
                }

                On modifie la ligne du shebang :

                $ cat ./test.php
                #!coucou DLFP
                <?php
                var_dump($argv);
                ?>
                
                $ php ./test.php  on_ne_voit_pas_la_ligne_du_shebang
                array(2) {
                  [0]=>
                  string(10) "./test.php"
                  [1]=>
                  string(34) "on_ne_voit_pas_la_ligne_du_shebang"
                }

                On remplace le shebang #! par un commentaire shell # sans le caractère !. Cette fois-ci l'interpréteur PHP ne reconnaît pas le shebang et afficher la ligne :

                $ cat ./test.php
                # coucou DLFP
                <?php
                var_dump($argv);
                ?>
                
                $ php ./test.php  on_voit_la_première_ligne
                # coucou DLFP
                array(2) {
                  [0]=>
                  string(10) "./test.php"
                  [1]=>
                  string(26) "on_voit_la_première_ligne"
                }

                Commentaire sous licence Creative Commons Zero CC0 1.0 Universal (Public Domain Dedication)

      • [^] # Re: Sympa !

        Posté par . Évalué à 4 (+5/-2).

        Hé hé tu joue les blasé. Si tu n'y voyais si peux d'intérêt, tu n'aurais pas pris le temps d'en faire un journal ;)

        Ton journal montre beaucoup de compétences utiles. Les partager au travers d'un cadre trivial permet à ceux qui s'y connaissent moins de découvrir et de pouvoir reproduire.

        1. Toi tu t'es amusé tout en vérifiant qu'une partie du noyau fonctionne effectivement comme ton intuition l'imaginait
        2. T'es lecteurs soir auront le même gain que toi, soit vingt découvrir des techniques et méthodes.

        Ça a beaucoup plus d'intérêt que de passer le triple de temps pour savoir quel type de personne est RMS. En tout cas c'est mon point de vue

        • [^] # Re: Sympa !

          Posté par (page perso) . Évalué à 10 (+8/-0).

          T'es lecteurs soir auront le même gain que toi, soit vingt découvrir des techniques et méthodes.

          Toi t'as abusé du shebang ;)

          « Il vaut mieux mobiliser son intelligence sur des conneries que mobiliser sa connerie sur des choses intelligentes. »

      • [^] # Re: Sympa !

        Posté par . Évalué à 10 (+11/-0).

        Je n'avais aucune idée que le shebang remontait jusqu'au noyau. je pensais que c'était du ressort du shell. Du coup j'ai appris un truc. Donc interet. Meme si ça me sert pas à grand chose de savoir ça au fond…

        • [^] # Re: Sympa !

          Posté par (page perso) . Évalué à 10 (+8/-0).

          Meme si ça me sert pas à grand chose de savoir ça au fond…

          Ça a quand même une importance. Cela veut dire que quelque soit le shell que tu utilise, tu auras ce même comportement (vu que ça ne dépend pas du shell). Mais ça veut aussi dire que si tu utilise autre chose qu'un shell pour lancer ton script (comme le lancer depuis un programme C, Python, Java), tu auras ce comportement. Et même si tu ne programme pas, si un programme te demande un binaire à exécuter, tu peux lui donner un script exécutable avec un shebang et ça fonctionnera.

          « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

  • # Erreurs ?

    Posté par (page perso) . Évalué à 4 (+2/-0). Dernière modification le 13/10/19 à 06:22.

    Dans la partie « Boot avec busybox », deux choses m'intriguent.

    Tu crée un fichier /tmp/test mais tu ne l'utilise jamais. J'imagine que tu voulais le créer dans hostimg ?

    Quand tu utilises virt-make-fs tu l'appelles avec pour paramètre hostdir que tu n'as jamais utilisé avant. J'imagine que tu voulais utiliser hostimg ?

    • [^] # Re: Erreurs ?

      Posté par (page perso) . Évalué à 3 (+0/-0).

      Tu crée un fichier /tmp/test mais tu ne l'utilise jamais. J'imagine que tu voulais le créer dans hostimg ?

      Effectivement

      Quand tu utilises virt-make-fs tu l'appelles avec pour paramètre hostdir que tu n'as jamais utilisé avant. J'imagine que tu voulais utiliser hostimg ?

      En fait, je voulais tout faire dans un dossier hostdir. J'ai fourché quand j'ai réécris les commandes de tête.

      Je vais corriger ça, merci.

      « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

  • # Intérêt de la chose

    Posté par (page perso) . Évalué à 10 (+17/-0).

    Il faut se préparer à l’hypothèse d’un monde dans lequel tous les # auront été consommés sans qu’on sache en reconstruire, mais avec des 🍻 en abondance !

  • # Aller plus vite !

    Posté par . Évalué à 7 (+6/-0).

    Salut,

    Juste une petite remarque sur le make oldconfig, qui j'espère est toujours d'actualité (pas vérifié).

    Du temps où je compilais mon noyau, plutôt que de faire un certain nombre de fois y aux nouvelles questions posées, j'utilisais la commande yes. Ce qui donne :

    yes | make oldconfig

    Et hop, plus de questions !

    Et si ça ne boote pas, retour en arrière pour inspecter les questions plus précisément.

  • # Sondage

    Posté par . Évalué à 7 (+6/-0).

    C'est malin, maintenant il faudrait rajouter une entrée au sondage :

    [ ] je voulais changer mon shebang

    :D

    (Même si techniquement, ce pourrait être
    [x] je voulais tester ou appliquer un patch, pour le commun des mortels
    [x] le noyau précompilé de ma distribution ne répondait pas à mes besoins, pour Xavier Claude)

    Plus sérieusement, j'ai beaucoup aimé le journal, merci.

  • # Mais pourquoi pas le #

    Posté par . Évalué à 3 (+2/-0).

    Bon ton journal est TRèS intéressant

    Mais pourquoi tant de N envers ce pauvre # ?

    Tu pourrais très bien mettre #🍻 comme shebang et ça marcherait sans aucun problèmes…

    # pour le commentaire
    🍻 pour signifier que tu veux consommer … un script

    "Gentoo" is an ancient african word, meaning "Read the F*ckin' Manual". "Gentoo" also means "I am what I am because you all are freaky n3rdz"

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.