
Описание уязвимости переполнения кучи в программе split из состава GNU coreutils. CVE-2024-0684
Уязвимость в программе "split" из набора GNU coreutils позволяет переполнение кучи с контролируемыми пользователем данными.
Она была внесена в 40bf1591bb4362fa91e501bcec7c2029c5f65a43 4 марта 2023 года. Исправление было выпущено с c4c5ed8f4e9cd55a12966d4f520e3a13101637d9 17 января 2024 года.
Затронутые версии: GNU coreutils v9.4; v9.3; v9.2
Подтверждение концепции:
Пример файла split_me в этом репозитории можно использовать для вызова аварийного завершения в затронутых версиях.
split -C 1024 ./split_me
Это приведет к аварийному завершению split с ошибкой сегментации (SIGABRT).
Я обнаружил эту уязвимость при попытке автоматизировать извлечение данных из систем, изолированных от сети, используя QR-коды.
QR-коды, сгенерированные с помощью qrencode, имеют емкость около 4000 символов, поэтому потребовалось интенсивное использование split.
В одном конкретном тестовом случае split аварийно завершился с ошибкой сегментации.
Поскольку GNU coreutils являются открытым исходным кодом, мы можем использовать исходный код для выявления ошибки, вместо того чтобы обратно проектировать бинарный файл. В проектах с открытым исходным кодом нужно быть максимально конкретным в отчете об ошибке, желательно указать точный коммит и строку, которые внесли ошибку, а также предложить исправление. Это позволяет мейнтейнерам быстро проверить ваш отчет и сокращает время ответа.
При проверке ошибки в разных системах,
я заметил, что аварийное завершение происходит только в относительно недавних версиях split.
Если у вас есть хороший и плохой коммиты,
это позволяет выполнить бинарный поиск по истории коммитов,
чтобы найти коммит, который фактически внес ошибку.
Git предоставляет специальный инструмент для этого случая: git bisect.
Он автоматически предложит коммиты для тестирования и позволит отметить их как хорошие или плохие.
В итоге вы получите коммит, который внес ошибку; в нашем случае:
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(-)
Затем мы можем скомпилировать программу (желательно с адресным санитайзером),
чтобы найти точную строку, на которой происходит аварийное завершение.
Имея всего около 50 строк для просмотра,
становится легко выявить ошибку.
В нашем случае аварийное завершение произошло в вызове memcpy() с неверными индексами.
И действительно, если проверить область вокруг вызова memcpy(),
мы находим diff, который изменяет вычисления индексов непосредственно перед:
@@ -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;
Если откатить эти изменения и перекомпилировать,
split обрабатывает все наши тестовые случаи без ошибок.
Осталось только пройтись по логике, чтобы проверить ошибку и разработать исправление.