Sommaire

Bonjour tout le monde

Ma distribution, Debian sid, m'a proposé de passer sur le noyau 4.13. À l'occasion d'un reboot, je tente un démarrage sur ce nouveau noyau… et suis accueilli par un kernel panic. Expérience déplaisante s'il en est, je décide de repasser sur le 4.12, du travail m'attendant.

Ayant eu plus de temps disponible ce week-end, je décide d'essayer le noyau 4.14-rc7, et découvre que le bug est toujours présent. Il est donc temps pour une enquête et un patch pour que les nouveaux noyaux continuent de démarrer mon système… Et on verra que des rudiments de C peuvent être suffisants pour certains bugs.

L'analyse du kernel panic

J'ai été contraint de prendre une photo du kernel panic (ma carte mère n'a pas le nécessaire pour sauvegarder un kernel panic hélas).

Le kernel panic est visible ici : http://host.pinaraf.info/~pinaraf/kernel-panic-4.13-crypto.jpg (notez le cadrage excellent de la photo… je l'ai prise très rapidement car un autre message d'erreur non lié allait apparaître)

Les éléments intéressants sont les suivants :

l'erreur en tant que telle

BUG: unable to handle kernel pointer dereference at 00000000000000xx IP: ecc_gen_privkey+0xa9 [ecdh_generic]

la call trace,

les registres.

La call trace et l'IP (Instruction Pointer) montrent que l'erreur a lieu dans le module ecdh_generic, dans la fonction ecc_gen_privkey à l'adresse 0xa9 (relative au début de la fonction).

L'erreur est une déréférence de pointeur NULL.

Nous allons devoir regarder le code pour comprendre ce qu'il se passe dans ce module.

Les pré-requis pour analyser le plantage

J'ai installé les paquets suivants pour travailler à partir du noyau 4.13 : gdb , linux-image-4.13.0-1-amd64-dbg et linux-source-4.13 .

Ces paquets sont TRÈS volumineux. Ils installent un ensemble de symboles de debug dans /usr/lib/debug pour le premier, et le second nous met à disposition dans /usr/src une archive du code du noyau avec les patchs correspondant de debian.

Je décompresse le code dans ma partition /data (les symboles de debug me prennent déjà 4GB, je peux pas beaucoup plus sur le SSD), et je dégaine gdb. Le module concerné est ecdh_generic, donc…

L'analyse du problème

Lançons GDB !

$ gdb /usr/lib/debug/lib/modules/4.13.0-1-amd64/kernel/crypto/ecdh_generic.ko GNU gdb (Debian 7.12-6+b1) 7.12.0.20161007-git Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from /usr/lib/debug/lib/modules/4.13.0-1-amd64/kernel/crypto/ecdh_generic.ko...done.

Il ne reste plus qu'à indiquer à gdb où se trouve le code, et lui demander de désassembler la fonction ecc_gen_privkey en indiquant le code source correspondant. On sait que l'erreur a lieu à l'offset 0xa9, soit 169 en décimal. Je ne vous mettrai donc que le code correspondant à cette partie ici.

(gdb) set directories /data/pierre/kernel-panic/linux-source-4.13/ (gdb) disassemble /s ecc_gen_privkey Dump of assembler code for function ecc_gen_privkey: ./crypto/ecc.c: … 949 int err; 950 951 /* Check that N is included in Table 1 of FIPS 186-4, section 6.1.1 */ 952 if (nbits < 160) 0x000000000000226d <+141>: jbe 0x22c7 <ecc_gen_privkey+231> 0x000000000000226f <+143>: mov %r9,-0x38(%rbp) 954 955 /* 956 * FIPS 186-4 recommends that the private key should be obtained from a 957 * RBG with a security strength equal to or greater than the security 958 * strength associated with N. 959 * 960 * The maximum security strength identified by NIST SP800-57pt1r4 for 961 * ECC is 256 (N >= 512). 962 * 963 * This condition is met by the default RNG because it selects a favored 964 * DRBG with a security strength of 256. 965 */ 966 if (crypto_get_default_rng()) 0x0000000000002273 <+147>: callq 0x2278 <ecc_gen_privkey+152> 967 err = -EFAULT; 968 969 err = crypto_rng_get_bytes(crypto_default_rng, (u8 *)priv, nbytes); 0x0000000000002278 <+152>: mov 0x0(%rip),%rdi # 0x227f <ecc_gen_privkey+159> ./include/crypto/rng.h: 143 return crypto_rng_alg(tfm)->generate(tfm, src, slen, dst, dlen); 0x000000000000227f <+159>: mov %r15d,%r8d 0x0000000000002282 <+162>: xor %edx,%edx 0x0000000000002284 <+164>: xor %esi,%esi 0x0000000000002286 <+166>: mov %rbx,%rcx 0x0000000000002289 <+169>: mov 0x38(%rdi),%rax 0x000000000000228d <+173>: callq *-0x20(%rax) 0x0000000000002290 <+176>: mov %eax,%r15d

On voit bien à l'offset 169 une utilisation du registre RDI qui était à 0 dans le kernel panic. Le bug est donc assez évident : crypto_rng_alg(tfm) doit renvoyer un pointeur null, donc la méthode generate est à une adresse incorrecte, et la tentative d'appel échoue.

Le code d'ecc.c correspondant est le suivant :

if ( crypto_get_default_rng ()) err = - EFAULT ; err = crypto_rng_get_bytes ( crypto_default_rng , ( u8 * ) priv , nbytes );

On note là une erreur de logique : si l'appel à crypto_get_default_rng échoue, on stocke une valeur dans une variable sans l'utiliser par la suite ? Cela semble incorrect.

Voici le code de crypto_get_default_rng :

int crypto_get_default_rng ( void ) { struct crypto_rng * rng ; int err ; mutex_lock ( & crypto_default_rng_lock ); if ( ! crypto_default_rng ) { rng = crypto_alloc_rng ( "stdrng" , 0 , 0 ); err = PTR_ERR ( rng ); if ( IS_ERR ( rng )) goto unlock ; err = crypto_rng_reset ( rng , NULL , crypto_rng_seedsize ( rng )); if ( err ) { crypto_free_rng ( rng ); goto unlock ; } crypto_default_rng = rng ; } crypto_default_rng_refcnt ++ ; err = 0 ; unlock : mutex_unlock ( & crypto_default_rng_lock ); return err ; }

Si cet appel échoue, la variable crypto_default_rng reste à NULL. Donc l'appel par la suite à crypto_rng_get_bytes avec un pointeur NULL déclenche la déréférence de pointeur NULL.

Le correctif est donc très simple : si crypto_get_default_rng renvoie une erreur, il faut renvoyer -EFAULT et pas juste ignorer l'erreur…

Correction du problème et envoi du patch…

Le bug a lieu dans la partie crypto du noyau. Regardons qui s'en occupe…

$ ./scripts/get_maintainer.pl crypto/ecc.c Herbert Xu <herbert@gondor.apana.org.au> (maintainer:CRYPTO API) "David S. Miller" <davem@davemloft.net> (maintainer:CRYPTO API) linux-crypto@vger.kernel.org (open list:CRYPTO API) linux-kernel@vger.kernel.org (open list)

Donc nous avons là la mailing list, les mainteneurs, et nous pouvons déduire de https://git.kernel.org/ qu'il faut cloner le dépôt https://git.kernel.org/pub/scm/linux/kernel/git/herbert/crypto-2.6.git/ pour disposer du dernier code de cette partie du noyau.

Après un long git clone (le noyau, c'est beaucoup), on peut voir que le bug est toujours présent dans le dernier noyau.

On fait donc un patch de la ligne err = -EFAULT; pour la changer en return -EFAULT;… Et il n'y a plus qu'à préparer le tout.

$ git commit -sa # Je vous laisse imaginer un beau message $ git format-patch origin/master -o ../patchs

On a dans le dossier ../patchs un beau fichier .patch, qu'il n'y a plus qu'à envoyer aux adresses précédemment citées. (Paquet git-email pour les debianneux)

git send-email --to=linux-crypto@vger.kernel.org -cc=davem@davemloft.net -cc=herbert@gondor.apana.org.au ../patchs/0001-Fix-NULL-pointer-deref.-on-no-default_rng.patch

Et voilà !

J'espère que le 4.14 sera corrigé avant sa sortie, je n'aime pas avoir une version plus maintenue du noyau…