Greboca  

LinuxFr.org : les journaux  -  Comment rendre le shebang plus festif

 -  14 octobre - 

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 <

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 !

Commentaires : voir le flux atom ouvrir dans le navigateur

par Xavier Claude

LinuxFr.org : les journaux

LinuxFr.org : Journaux

Conception d’un circuit intégré avec Qflow

 -  17 novembre - 

Sommaire Partie relou Cours magistral RTLVerilog VHDL SystemC Chisel MyHDL ImplémentationFPGA ASIC Implémentation custom Implémentation basée sur (...)


Le bloboscope

 -  16 novembre - 

Cher Nal', Si tu t'intéresses à l'actualité scientifique, tu as sans doute déjà entendu parlé du blob. Pourvu de 720 sexes et dépourvu de système (...)


Openclipart est en mode maintenance, vive FreeSVG !

 -  16 novembre - 

Dire qu'Openclipart est mort serait un peu exagéré mais on peut dire que l'accès aux fichiers SVG n'est plus disponible depuis plusieurs mois (voir (...)


k1g1 : le premier FPGA Libre…

 -  4 novembre - 

Sommaire Un FPGA, c'est qu'est-ce quoi ?ASIC Processeur FPGA Mais pourquoi me suis-je lancé dans cette aventure ? Open FPGA Platform, kFPGA et (...)


Atlantic

 -  20 octobre - 

Atlantic est une application sous licence MIT que j'ai développé permettant de suivre en temps réel l'état d'un système industriel. Cette application (...)