
Эксплойт-примитив в ядре Linux, вдохновлённый DirtyPipe
Эксплуатационный примитив в ядре Linux, вдохновлённый DirtyPipe (CVE-2022-0847).
На днях я, как и многие уважаемые коллеги по безопасности, изучил и воспроизвёл уязвимость DirtyPipe (CVE-2022-0847) и глубоко прочувствовал, насколько она удобна. Эта уязвимость начинается с неинициализированной области памяти, а заканчивается модификацией произвольных файлов, при этом на всём пути не требуется утечка KASLR и такие операции, как ROP или JOP. Поэтому нет необходимости обходить такие защиты, как SMEP, SMAP.
После воспроизведения я начал размышлять: почему DirtyPipe так удобна? Неужели с исправлением DirtyPipe она, как вспышка, исчезнет и не будет иметь никакой ценности для будущих эксплойтов?
Внезапно я осознал, что структура, в которой находится DirtyPipe, — struct pipe_buffer, — кажется мне очень знакомой. Ах! Это ведь та самая структура, которая выделяется с флагом GFP_KERNEL_ACCOUNT, находится в slab-1k и содержит поле ops, которое я так часто использовал для утечки KASLR и перехвата RIP?
Тут же я почувствовал себя дураком: зачем я тогда менял ops у pipe_buffer для ROP? Достаточно было изменить flags у pipe_buffer и в сочетании с splice сразу получить запись в произвольный файл! Тогда не нужно утекать KASLR, не нужно адаптировать эксплойт под разные версии ядра, не нужно думать о гаджетах и не нужно обходить SMEP, SMAP, KPTI и прочие защиты.
Я тут же достал свои предыдущие эксплойты, написанные при изучении CVE-2021-22555 и других, затрагивающих структуру pipe_buffer, немного модифицировал их — и они сразу успешно сработали на разных незапатченных версиях ядра. Вот несколько примеров:
Поскольку до этого я не видел, чтобы кто-то использовал это как примитив в эксплойтах для Linux kernel, я позволил себе назвать это «Pipe Primitive» XD.
Резюмируя, требуется получить возможность модифицировать структуру Pipe. Например, это может быть UAF в slab-1k или выход за границы записи в slab-1k со смещением (то есть не прямое последовательное переполнение).
Таким образом, в kernel >= 5.8 нам достаточно изменить flag |= PIPE_BUF_FLAG_CAN_MERGE у страницы splice в pipe_buffer (при возможности можно заодно выставить offset и len в 0, чтобы писать с начала файла); в kernel < 5.8 сначала нужно утечь anon_pipe_ops из pipe_buffer, а затем заменить ops у страницы splice на anon_pipe_ops (потому что в версиях < 5.8 возможность merge определяется по ops) (при возможности также можно заодно выставить offset и len в 0).
А если вы спросите, как при таком подходе делать эскейп из контейнера? Я думаю, нужно сделать примерно то же, что и в CVE-2019-5736, XD.