Методика фаззинг-тестирования ядра

Принципы работы syzkaller и его компонентов

Фаззингом называют динамический метод анализа кода, при котором на вход функциям/системным вызовам исследуемого программного обеспечения подаются случайные данные в попытке выявить дефекты.

Для фаззинг-тестирования ядра Linux на системном уровне предлагается использовать инструмент syzkaller. Данный фаззер поддерживает следующие ядра: Linux, Windows (с ограничениями), NetBSD, OpenBSD, FreeBSD, Android, Fuchsia, Akaros. В процессе своей работы syzkaller конструирует случайные программы на основе грамматик системных вызовов, запускает их на исследуемой платформе, получает обратную связь в виде достигнутого покрытия кода по ядру, мониторит лог ядра на предмет записей об ошибках. Случайные программы конструируются таким образом, чтобы увеличивать размер покрытия.

Для того, чтобы фаззинг-тестирование ядра могло эффективно выявлять ошибки в ядре исследуемой ОС должны быть включены отладочные опции и опции самопроверки. В таком случае ядро сможет диагностировать дефект, когда поток управления дойдёт до некорректного кода, и выдать сообщение об ошибке в своем логе. Часто используемыми опциями диагностики являются проверки KASAN, KUBSAN, KMSAN, KCSAN.

KASAN - kernel address sanitizer (детектор ошибок доступа к памяти). Способен обнаруживать ошибки вида use-after-free (использование после освобождения ресурса) и out-of-bounds (доступ за границами объекта).

KUBSAN - kernel undefined behavior sanitizer используется для обнаружения ошибок неопределенного поведения.

KMSAN - kernel memory sanitizer используется для обнаружения ошибок доступа к неинициализированной памяти.

KCSAN - kernel concurrency sanitizer используется для обнаружения состояния гонок.

Существует также большое количество других опций диагностики. Так, в ядре Linux существуют опции для диагностики утечек памяти, некорректного использования блокировок, вызова планировщика в критических секциях, переполнения счетчиков ресурсов и множество иных.

Для измерения покрытия syzkaller использует механизм KCOV. Последний позволяет измерять покрытие по коду ядра с точностью до отдельного системного вызова. На основе данной обратной связи syzkaller строит корпус программ, позволяющий покрыть как можно большее количество блоков кода при анализе.

При обнаружении ошибки в ядре операционной системы, syzkaller пытается сгенерировать минимальную версию программы на языке Си, которая бы надежным образом вызывала срабатывание данной ошибки. В большом количестве случаев это удается сделать, в том числе для ошибок, срабатывающих в мультипоточной среде.

Все это, при достижении хорошего уровня покрытия исследуемого кода, позволяет выявлять большое количество дефектов в коде, в том числе на ранних стадиях разработки драйверов. Для того, чтобы анализ драйвера был эффективен, необходимо описать его "точки входа" (аргументы ioctl, файлы /dev/, mount опции и т.д.) и структуры данных, которые пользовательская программа может использовать для взаимодействия с драйвером. Описание представляет собой грамматику системных вызовов и их потенциальных аргументов, на основе которой фаззер будет конструировать программы для воздействия на код драйвера.

В целом, проектом было выявлено более 1000 ошибок в коде ядра, некоторые из которых представляют собой эксплуатируемые уязвимости.

Инструмент включен в непрерывный цикл тестирования ядра Linux. Большое количество интерфейсов драйверов уже описано. В анализ включаются новые подсистемы ядра, например usb stack (USB slides, USB bugs). Проект оказал существенное влияние на разработку ядра Linux.

Фаззинг своего ядра: инструкция по настройке и запуску

В простейшем случае для запуска фаззинга на своей версии ядра на эмулируемой аппаратуре qemu нужно использовать инструмент syz-manager. syz-manager управляет запуском эмулятора, по ssh копирует фаззинг-программы внутрь эмулируемого окружения и запускает их.

Для подготовки образа виртуальной машины можно использовать скрипт create-image.sh:

$ git clone https://git.linuxtesting.ru/pub/scm/tools/lvc/syzkaller.git
$ cd syzkaller
$ ./tools/create-image.sh

Он создаст файлы stretch.img, stretch.id_rsa, stretch.id_rsa.pub. Первый файл создает окружение Debian системы с использование инструмента debootstrap. Два последних файла необходимы для доступа в виртуальную машину по ssh.

Далее, необходимо подготовить и собрать версию Linux ядра для фаззинга. Например:

$ git clone https://git.linuxtesting.ru/pub/scm/linux/kernel/git/lvc/linux-stable.git
$ cd linux-stable
$ make defconfig
$ ./scripts/config -e CONFIGFS_FS -e SECURITYFS -e KCOV -e KASAN
$ make -j$(nproc)

После сборки ядра появится файл arch/x86/boot/bzImage, который будет использоваться при фаззинге.

Далее можно запускать фаззинг командой:

$ cd syzkaller
$ make fuzzer manager executor execprog
$ ./bin/syz-manager -config manager.cfg

Где файл конфигурации manager.cfg выглядит следующим образом:

{
        "target": "linux/amd64",
        "http": "localhost:56741",
        "workdir": "/home/lvc/syzkaller/workdir",
        "kernel_obj": "/home/lvc/linux-stable",
        "image": "/home/lvc/syzkaller/stretch.img",
        "sshkey": "/home/lvc/syzkaller/stretch.id_rsa",
        "syzkaller": "/home/lvc/syzkaller",
        "procs": 4,
        "type": "qemu",
        "vm": {
                "count": 3,
                "cpu": 2,
                "mem": 2048,
                "kernel": "/home/lvc/linux-stable/arch/x86/boot/bzImage"
        },
        "disable_syscalls": ["keyctl", "add_key", "request_key"]
}

Структурно, это json файл со следующими полями:

  • target - тип ядра и архитектуры
  • http - адрес и порт для веб панели, на которой отображается покрытие и статус фаззинга
  • workdir - путь до директории с корпусом и результатами фаззинга
  • kernel_obj - путь до директории с артефактами сборки ядра (необходимы для отображения покрытия)
  • image - путь до файла с образом виртуальной машины
  • sshkey - путь до файла с ssh ключем для доступа внутрь виртуальной машины
  • syzkaller - путь до директории с исходниками syzkaller
  • procs - количество одновременно запускаемых программ фаззинга внутри виртуальной машины
  • type - тип фаззинга
  • vm - блок с описанием виртуальной машины для фаззинга типа qemu
  • vm.count - количество одновременно работающих виртуальных машин
  • vm.cpu - количество ядер, выделяемых одной виртуальной машине
  • vm.mem - размер оперативной памяти, выделяемой одной виртуальной машине
  • vm.kernel - путь до файла с ядром, на котором будет выполняется фаззинг
  • disable_syscalls - список системных вызовов, которые необходимо исключить из фаззинга

Вместо disable_syscall или в дополнении к нему можно наоборот перечислить системные вызовы для фаззинга.

Например, добавить в конфигурацию enable_syscalls и ограничить фаззинг только ioctl системными вызовами драйвера floppy:

"enable_syscalls": [
        "syz_open_dev$floppy",
        "ioctl$FLOPPY*"
]

Списки системных вызовов можно найти в файлах sys/linux/*.txt. "ioctl$FLOPPY*" в данном случае означает все системные вызовы, описание которых начинается с ioctl$FLOPPY.

После запуска фаззинга в консоли будет отражаться текущий статус фаззинга, найденные ошибки, количество покрытых базовых блоков. По адресу http://localhost:56741 будет доступна веб-панель syzkaller с этой же информацией. Дополнительно в веб-панели можно посмотреть покрытие по исходными кодам, цепочки системных вызовов и все найденные ошибки.

В процессе фаззинга syzkaller измеряет покрытие и строит корпус входных значений, который позволяет покрыть как можно больше базовых блоков в ядре. Корпус сохраняется в файле corpus.db рабочей директории ~/syzkaller/workdir/corpus.db. Эта база входных значений относительно переносима и может подходить под другие ядра. Корпус фаззинга Технологического центра исследования безопасности ядра Linux доступен для Партнёров по ссылке. Скачанный файл необходимо расшифровать и поместить в рабочий каталог workdir. При старте фаззинга syz-manager автоматически загрузит его и отфильтрует значения, неподходящие под актуальное ядро.