Pull to refresh

Эскалация привилегий в десктопном линуксе: Получение рутового доступа из GUI-приложений

Reading time4 min
Views2.1K
Пару месяцев назад Rafal Wojtczuk придумал серьёзный эксплойт, позволяющий получить права суперюзера из непривилегированного процесса, имеющего доступ к X-серверу (то есть, из GUI-приложения, работающего под обычным пользователем). Другими словами, любая GUI-программа (например, читалка PDF-файлов), если она скомпроментирована (например, специально подготовленным PDF-файлом), может пробить все барьеры защиты на пути к полному обладанию компьютером. Не спасает даже песочница SElinux (SElinux «sandbox -X»). И проблема существует много лет — по-видимому, с первых версий ядра 2.6.

Обзор этой уязвимости вышел 17 августа в [2], и я хочу о ней рассказать местами в упрощённой, местами в развёрнутой форме.

Как это работает



Основная причина этого безобразия известна довольно давно [3]. Проблема — в особенности линуксовских алгоритмов распределения памяти. Как всем известно, есть два основных места, где процесс может получить кусочек динамической памяти — стек и куча (heap). А ещё можно выделить кусок адресного пространства, которому не соответствует физическая память — для этого существует отображение виртуальной памяти процесса на файлы в файловой системе (mmap) или просто на абстрактные объекты ядра (System-V style shared memory). Не обязательно даже, чтобы выделенный кусок памяти физически существовал, по крайней мере, до того момента, когда мы начнём в него писать.


Распределение памяти на x86-32, картинка из [3]. На x86-64 всё очень похоже, только масштаб побольше :)

Стек — это самый простой способ выделения памяти. По мере необходимости, он растёт от старших адресов к младшим, и если вдруг оказывается, что очередная страница стека кончилась, при попытке туда записать процессор кидает page fault, и ядро выделяет для стека ещё несколько страниц (либо убивает процесс, если выделить не получилось). Изначально на стек отводится (на x86-32) участок памяти в верхней части адресного пространства, по умолчанию 128 Mбайт, но реально эта память выделяется по мере необходимости, и если вдруг в других местах всё занято, malloc() и mmap() начинают использовать «стековую» память.

В результате агрессивного выделения может получиться так, что один из выделенных участков вплотную примыкает к нижней границе стека (это и есть эксплуатируемое свойство). Предположим теперь, что мы вызвали какую-то глубоко рекурсивную функцию. Указатель стека начинает приближаться к границе, в конце концов пересекает её и… ничего не происходит, потому что сразу за стеком начинается выделенная нами память. Итак, у нас есть часть стека, которая на самом деле не стек, а участок, выделенный другим образом.

Пока всё происходило в границах одного процесса, максимум что мы можем сделать — это нагадить этому процессу и сделать что-нибудь плохое с аккаунтом юзера, от которого он запускается.

Как вырваться из своего процесса и залезть в X-сервер


Процесс X-сервера (тот самый /usr/X11/bin/X) работает с привилегиями рута. И если мы найдём способ выполнить свой код в контексте этого процесса, мы достигнем намеченной цели.

Чтобы получить кусок памяти в адресном пространстве X-сервера, мы будем использовать расширение MIT-SHM. Вообще протокол, по которому X-сервер обменивается с клиентскими программами, велик и развесист, и содержит многочисленные расширения, придуманные в разные времена с разными целями. В частности, MIT-SHM решает очень простую задачу. Допустим, нам надо отобразить на экране большую растровую картинку. Для этого, надо каким-то образом переслать из клиентского процесса на сервер много байт, составляющих этот растр (pixmap). Но если клиент и сервер живут на одной машине, совсем необязательно тратить ресурсы на копирование всех эти байтов в ядро и обратно, вместо этого можно выделить участок разделённой памяти, который будет отображаться на виртуальную память обоих процессов — этим MIT-SHM и занимается.

Мы можем коварно воспользоваться этим API для своих целей и отобразить всю доступную память X-сервера в свое адресное пространство примерно таким образом:
shm_seg_size=shmmax
while shm_seg_size >= PAGE_SIZE
   shm_seg=shmget(..., shm_seg_size,...)
   подготовить и вызвать XShmAttach(..., shm_seg...)
   если не получилось, shm_seg_size/=2

(shmmax это максимально возможный размер памяти, который можно выделить shmget, можно узнать в /proc/sys/kernel/shmmax)

Если всё хорошо, один из выделенных участков окажется точно за границей стекового пространства, но мы пока не знаем, какой именно, поскольку адреса разделённой памяти на стороне сервера совсем не такие, как у нас. Чтобы найти лакомый кусочек, мы можем нагромоздить обычными средствами X-протокола пирамиду из виджетов, а потом отправить команду, которая вызовет рекурсивный обход (в статье [2] говорится о некоей функции F, которая работает рекурсивно, наверное, такую можно найти, покопавшись в исходниках иксов ;) ). В результате процесс вылезет за границу стека, но никто ничего не заметит, поскольку следующие адреса уже выделены XShmAttach. А мы потом со своей стороны просмотрим память и найдём участок, в котором есть ненулевые байты (shmget забивает выделенные участки нулями).

А теперь ещё раз запустим пресловутую рекурсивную функцию F, и пока она будет работать, запишем в стек то, что нам нравится, то есть, новые адреса возврата. Тут уже можно применять классические технологии эксплойтов, такие как return-to-libc, главное — подменить стек вовремя, пока функция выполняется (да, я знаю про ASLR, но в рамках этой статьи будем считать, что мы его как-то победили ;) )

И что теперь делать?


Ну, во-первых, можно ждать апдейтов. Линус Торвальдс (лично!) добавил патч, создающий барьер из невыделенной страницы между стеком и адресным пространством, доступным для кучи и mmap-ов. В результате, если не хватает памяти для стека, программа попытается записать по несуществующему адресу и свалится в SIGBUS (что, конечно, тоже не очень хорошо, но явно лучше эскалации привилегий). В ядрах 2.6.35.2 и 2.6.34.4 этот фикс уже внесён.

Если же по какой-то причине менять ядро не хочется, можно выключить MIT-SHM в файле xorg.conf:

Section "Extensions"
Option "MIT-SHM" "disable"
EndSection

Правда, это не спасёт от других вероятных вариаций на тему той же уязвимости.

Ссылки


  • [1] Новость на The Invisible Things Lab's blog — собственно, введение написано именно по мотивам
  • [2] Exploiting large memory management vulnerabilities in Xorg server running on Linux by Rafal Wojtczuk (PDF)
  • [3] Gael Delalleu, Large memory management vulnerabilities (PDF) — прекрасный обзор об известных уязвимостях памяти в разных ОС.
Tags:
Hubs:
Total votes 103: ↑94 and ↓9+85
Comments56

Articles