
Análise de uma vulnerabilidade de estouro de heap no utilitário split do GNU coreutils. CVE-2024-0684
Uma vulnerabilidade no programa "split" do GNU coreutils permite um estouro de buffer no heap com dados controlados pelo usuário.
Ela foi introduzida em 40bf1591bb4362fa91e501bcec7c2029c5f65a43 em 2023-03-04. Uma correção foi lançada com c4c5ed8f4e9cd55a12966d4f520e3a13101637d9 em 2024-01-17.
Versões afetadas: GNU coreutils v9.4; v9.3; v9.2
Prova de conceito:
O arquivo de exemplo split_me neste repositório pode ser usado para acionar uma falha nas versões afetadas.
split -C 1024 ./split_me
Isso fará o split falhar com uma falha de segmentação (SIGABRT).
Descobri esta vulnerabilidade ao tentar automatizar a extração de dados de sistemas isolados (air-gapped) usando códigos QR.
Códigos QR gerados com qrencode têm capacidade de ~4000 caracteres, então exigiam uso intensivo de split.
Em um caso de teste específico, o split falhou com uma falha de segmentação.
Como o GNU coreutils é open source, podemos usar o código-fonte para identificar o bug, em vez de ter que fazer engenharia reversa de um binário. Em projetos open source, você quer ser o mais específico possível no relatório de bug, idealmente fornecendo o commit e a linha exatos que introduziram o bug, bem como uma correção proposta. Isso permite que os mantenedores verifiquem seu relatório rapidamente e reduz o tempo de resposta.
Ao verificar o bug em diferentes sistemas,
notei que a falha só ocorria em versões relativamente recentes do split.
Se você tem um commit bom e um ruim,
isso permite fazer uma busca binária no histórico de commits,
a fim de encontrar o commit que realmente introduziu o bug.
O Git fornece uma ferramenta específica para esse caso de uso: git bisect.
Ela sugerirá automaticamente commits para testar e permitirá que você os marque como bons ou ruins.
Eventualmente, você chega ao commit que introduziu o bug; no nosso caso:
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(-)
Podemos então compilar o programa (idealmente com um address sanitizer),
a fim de encontrar a linha exata em que ele falha.
Com apenas ~50 linhas para analisar,
fica fácil identificar o bug.
No nosso caso, a falha ocorreu em uma chamada memcpy() com índices incorretos.
E de fato, se verificarmos a área ao redor da chamada memcpy(),
encontramos um diff que altera os cálculos de índice logo antes:
@@ -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;
Se revertemos essas alterações e recompilamos,
o split processa todos os nossos casos de teste sem erro.
O que resta é examinar a lógica para verificar o bug e desenvolver uma correção.