Пример исправления crash'а, взятого с syzbot и отправка патча в upstream ядра

Материал из ALT Linux Wiki

Для обеспечения стабильности и безопасности ядра Linux необходимо регулярно выявлять и устранять потенциальные уязвимости. Syzkaller — это фреймворк, который помогает в этом процессе, автоматически генерируя случайные тесты с использованием метода фаззинга для выявления скрытых ошибок. Когда такие тесты приводят к сбою системы, это называется crash. Эти сбои затем автоматически отслеживаются и анализируются системой syzbot , которая уведомляет разработчиков о найденных проблемах, способствуя улучшению качества кода ядра.

Например, рассмотрим WARNING in mark_buffer_dirty :

1) Имеются репродюссеры на языке Си (C repro ) и на языке сизколлера, нам удобен Си - копируем исх код и компилируем программу:

$ gcc repro.c -pthread -o repro

2) Делаем пробный запуск с разными правами на интересующем ядре. В данном случае ошибка воспроизводится при запуске от рута. (Еще можно экспериментировать с capability для оценки уровня критичности ошибки.)

3) Согласно отчетам (ссылки report), к предупреждению приводит выполнение след. строки:

WARN_ON_ONCE(!buffer_uptodate(bh));

функции void mark_buffer_dirty(struct buffer_head *bh)

Сам вызов этой функции из модуля файловой системы bfs:

  new = sb_getblk(sb, to);
  memcpy(new->b_data, bh->b_data, bh->b_size);
  mark_buffer_dirty(new);

Здесь же рядом в коде при запуске репродюссера обнаружено еще одно падение, связанное с попыткой разыменовать указатель struct buffer_head *new, содержащий нулевое значение после выполнения функции sb_getblk().

Для поиска мест падения в исходном коде, можно воспользоваться скриптом decode_stacktrace.sh из дерева ядра, который переводит строки с hex смещением

bfs_get_block+0x320/0xdf0 [bfs]
bfs_write_begin+0xd0/0xd0 [bfs]

в удобочитаемые

bfs_get_block (fs/bfs/file.c:125) bfs
bfs_write_begin (fs/bfs/file.c:66) bfs

Сам запуск скрипта, например, на ядре 6.1.105:

$ cat log.txt | ./scripts/decode_stacktrace.sh /usr/lib/debug/lib/modules/6.1.105-un-def-alt1/vmlinux /usr/src/debug/kernel-image-un-def-6.1.105-alt1/kernel-source-6.1 /usr/lib/debug/lib/modules/6.1.105-un-def-alt1/kernel/

Здесь мы передаем стектрейс (log.txt) на вход скрипту и указываем пути для vmlinux, каталогов исходного кода и модулей, которые поставляются пакетами с суффиксом -debuginfo.

4) Исправляем. Если есть сложности - смотрим на реализацию похожего функционала других подсистем.

В данном случае, добавим проверку на NULL и корректно выйдем из функции (1-й патч ):

   new = sb_getblk(sb, to);
+  if (!new) {
+    brelse(bh);
+    return -EIO;
+  }
   memcpy(new->b_data, bh->b_data, bh->b_size);

Касательно самой ошибки, пометим буфер (2-й патч ):

+  set_buffer_uptodate(new);
   mark_buffer_dirty(new);

5) Генерим патчи (2 шт):

$ git format-patch -2

6) Проверяем оформление патчей скриптом checkpatch.pl :

$ ./scripts/checkpatch.pl 000*

7) Получаем список мейнтейнеров скриптом get_maintainer.pl :

$ ./scripts/get_maintainer.pl 000*

8) Настраиваем почту и отправляем, например, эти патчи отправлялись как 3-я версия командой:

$ git send-email --from=kovalev@altlinux.org --to=linux-fsdevel@vger.kernel.org,linux-kernel@vger.kernel.org,aivazian.tigran@gmail.com,stable@vger.kernel.org --cc=lvc-patches@linuxtesting.org,dutyrok@altlinux.org,kovalev@altlinux.org --compose ./000*

--compose здесь как раз для добавления сопроводительного письма в окно ввода:

Subject: [PATCH v3 0/2] bfs: fix null-ptr-deref and possible warning in bfs_move_block() func

https://syzkaller.appspot.com/bug?extid=d98fd19acd08b36ff422

[PATCH v3 1/2] bfs: prevent null pointer dereference in bfs_move_block()
v3: Changed the error handling

[PATCH v3 2/2] bfs: ensure buffer is marked uptodate before marking it dirty
v3: Replaced the buffer up-to-date check with an error exit by forcefully
setting the buffer as up-to-date before call mark_buffer_dirty()

Обычно при отправке патчей утилита send-email добавляет в копию всех адресатов, которых найдет в коммит сообщении. Если это не нужно, можно указать дополнительно опцию --suppress-cc=all