
Analyse d'une vulnérabilité de débordement de tas dans le programme split de GNU coreutils. CVE-2024-0684
Une vulnérabilité dans le programme « split » de GNU coreutils permet un débordement de tas avec des données contrôlées par l'utilisateur.
Elle a été introduite dans 40bf1591bb4362fa91e501bcec7c2029c5f65a43 le 2023-03-04. Un correctif a été publié avec c4c5ed8f4e9cd55a12966d4f520e3a13101637d9 le 2024-01-17.
Versions affectées : GNU coreutils v9.4 ; v9.3 ; v9.2
Preuve de concept :
Le fichier exemple split_me dans ce dépôt peut être utilisé pour déclencher un crash dans les versions affectées.
split -C 1024 ./split_me
Cela fera crasher split avec une erreur de segmentation (SIGABRT).
J'ai découvert cette vulnérabilité en essayant d'automatiser l'extraction de données depuis des systèmes isolés (air-gapped) en utilisant des codes QR.
Les codes QR générés avec qrencode ont une capacité d'environ 4000 caractères, ce qui nécessitait une utilisation intensive de split.
Sur un cas de test spécifique, split a planté avec une erreur de segmentation.
Comme les GNU coreutils sont open source, nous pouvons utiliser le code source pour identifier le bogue, au lieu d'avoir à rétro-concevoir un binaire. Sur les projets open source, vous voulez être aussi précis que possible dans votre rapport de bogue, idéalement en fournissant le commit exact et la ligne qui a introduit le bogue, ainsi qu'un correctif proposé. Cela permet aux mainteneurs de vérifier votre rapport rapidement et de réduire le temps de réponse.
En vérifiant le bogue sur différents systèmes,
j'ai remarqué que le crash ne se produisait que sur des versions relativement récentes de split.
Si vous avez un bon commit et un mauvais commit,
cela vous permet d'effectuer une recherche binaire dans l'historique des commits,
afin de trouver le commit qui a effectivement introduit le bogue.
Git fournit un outil spécifique pour ce cas d'usage : git bisect.
Il suggérera automatiquement des commits à tester et vous permettra de les marquer comme bons ou mauvais.
Finalement, vous obtenez le commit qui a introduit le bogue ; dans notre cas :
commit 40bf1591bb4362fa91e501bcec7c2029c5f65a43
Author: Paul Eggert <[email protected]>
Date: Sat Mar 4 11:42:16 2023 -0800
split: prefer signed integers to size_t
This allows for better runtime checking with gcc
-fsanitize=undefined.
* src/split.c: Include idx.h.
(open_pipes_alloc, n_open_pipes, suffix_length)
(set_suffix_length, input_file_size, sufindex, outbase_length)
(outfile_length, addsuf_length, create, cwrite, bytes_split)
(lines_split, line_bytes_split, lines_chunk_split)
(bytes_chunk_extract, ofile_open, lines_rr, main):
Prefer signed integers (typically idx_t) to size_t.
src/split.c | 105 ++++++++++++++++++++++++++++++------------------------------
1 file changed, 52 insertions(+), 53 deletions(-)
Nous pouvons ensuite compiler le programme (idéalement avec un sanitizer d'adresse),
afin de trouver la ligne exacte sur laquelle il plante.
Avec seulement ~50 lignes à parcourir,
il devient facile d'identifier le bogue.
Dans notre cas, le crash s'est produit dans un appel memcpy() avec des indices incorrects.
Et effectivement, si nous vérifions la zone autour de l'appel memcpy(),
nous trouvons un diff qui modifie les calculs d'indices juste avant :
@@ -816,15 +820,10 @@
/* Update hold if needed. */
if ((eoc && split_rest) || (!eoc && n_left))
{
- size_t n_buf = eoc ? split_rest : n_left;
+ idx_t n_buf = eoc ? split_rest : n_left;
if (hold_size - n_hold < n_buf)
- {
- if (hold_size <= SIZE_MAX - bufsize)
- hold_size += bufsize;
- else
- xalloc_die ();
- hold = xrealloc (hold, hold_size);
- }
+ hold = xpalloc (hold, &hold_size, n_buf - (hold_size - n_hold),
+ -1, sizeof *hold);
memcpy (hold + n_hold, sob, n_buf);
n_hold += n_buf;
n_left -= n_buf;
Si nous annulons ces modifications et recompilons,
split traite tous nos cas de test sans erreur.
Il ne reste plus qu'à parcourir la logique afin de vérifier le bogue et de développer un correctif.