Состояние гонки в сборщике мусора ядра Linux, способное привести к повышению привилегий

Янн Хорн (Jann Horn) из команды Google Project Zero, в своё время выявивший уязвимости Spectre и Meltdown, опубликовал технику эксплуатации уязвимости (CVE-2021-4083) в сборщике мусора ядра Linux. Уязвимость вызвана состоянием гонки при чистке файловых дескрипторов unix-сокетов и потенциально позволяет локальному непривилегированному пользователю добиться выполнения своего кода на уровне ядра.

Проблема интересна тем, что временное окно, во течение которого проявляется состояние гонки, оценивалось как слишком незначительное для создания реальных эксплоитов, но автор исследования показал, что даже подобные изначально скептически рассматриваемые уязвимости могут стать источником реальных атак, если у создателя эксплоита есть необходимые навыки и время. Янн Хорн показал, как при помощи филигранных манипуляций можно свести состояние гонки, возникающее при одновременном вызове функций close() и fget(), к полноценно эксплуатируемой уязвимости класса use-after-free и добиться обращения к уже освобождённой структуре данных внутри ядра.

Состояние гонки возникает в процессе закрытия файлового дескриптора при одновременном вызове функций close() и fget(). Вызов close() может отработать до выполнения fget(), что введёт сборщик мусора в замешательство так как в соответствии со счётчиком refcount у структуры file не будет внешних ссылок, но она останется прикреплена к файловому дескриптору, т.е. сборщик мусора посчитает, что имеет эксклюзивный доступ к структуре, но фактически небольшой промежуток времени остающаяся в таблице файловых дескрипторов запись ещё будет указывать на освобождаемую структуру.

Для увеличения вероятности попадания в состояние гонки использовано несколько трюков, которые позволили довести вероятность успеха эксплуатации до 30% при внесении специфичных для конкретной системы оптимизаций. Например, для увеличения времени обращения к структуре с файловыми дескрипторами не несколько сотен наносекунд выполнено вытеснение данных из процессорного кэша через замусоривание кэша активностью на другом ядре CPU, что позволило добиться отдачи структуры из памяти, а не из быстрого кэша CPU.

Второй важной особенностью стало использование для увеличения времени состояния гонки прерываний, генерируемых аппаратным таймером. Подбирался такой момент, чтобы обработчик прерывания срабатывал во время возникновения состояния гонки и на какое-то время прерывал выполнение кода. Для дополнительного затягивания возвращения управления при помощи epoll генерировалось около 50 тысяч записей в waitqueue, требующих перебора в обработчике прерываний.

Техника эксплуатации уязвимости раскрыта после 90-дневного периода неразглашения. Проблема проявляется начиная с ядра 2.6.32 и устранена в начале декабря. Исправление вошло в состав ядра 5.16, а также перенесено в LTS-ветки ядра и пакеты с ядром, поставляемые в дистрибутивах. Примечательно, что уязвимость была выявлена в ходе анализа похожей проблемы CVE-2021-0920, проявляющейся в сборщике мусора при обработке флага MSG_PEEK.

Release. Ссылка here.