Перейти до вмісту

Захист і безпека

Захист не є окремою підсистемою, це властивість, розлита по всьому, що вже було в курсі. Ізоляцію пам’яті дає MMU (модуль 2), межу дають режими виконання (модуль 3), контроль доступу — права в inode (модуль 15).

Тут ми складемо все це разом і додамо те, що з’явилося пізніше: дрібні привілеї замість всемогутнього root, обов’язковий контроль доступу, фільтрацію системних викликів і захист від того, що всі ці механізми обходить.

Передумови. Межа user/kernel (модуль 3), адресний простір (модуль 10), права доступу (модуль 15).

Говорити про захист без моделі загроз безглуздо: слово «безпечно» не означає нічого, поки не сказано, від кого саме.

Операційна система загального призначення захищає щонайменше чотири межі:

Межа Від чого Механізм
процес ↔ процес читання чужої пам’яті MMU, окремі адресні простори
процес ↔ ядро довільний код у режимі ядра режими виконання, перевірка аргументів
користувач ↔ користувач доступ до чужих файлів права, ACL
програма ↔ її власні дані експлуатація помилок ASLR, NX, канарки

Останній рядок стоїть окремо від решти. Перші три захищають від зовнішнього порушника, а четвертий — від того, що ваша ж програма через власну помилку буде змушена виконати чужий код.

Скільки перевірок проходить виклик

Section titled “Скільки перевірок проходить виклик”
Системний виклик послідовно проходить перевірки DAC, capabilities, модуля безпеки та seccomp процесopen()DACправа rwx, власникEACCEScapabilitiesдрібні привілеї rootEPERMLSMSELinux, AppArmorEACCESseccompфільтр за номером викликуSIGSYSвідмова на будь-якому кроці зупиняє виклик; root минає DAC, але не seccomp і не LSM
Механізми не замінюють один одного, а складаються. Root минає перевірку прав, але не проходить крізь seccomp і не обходить SELinux.

DAC, дискреційний контроль доступу, є класичною моделлю Unix. Дискреційний тут означає «на розсуд власника»: власник файлу сам вирішує, кому дати доступ. Просто, і водночас недостатньо, бо скомпрометований процес успадковує всі права свого користувача.

Capabilities розбивають всемогутність root на окремі привілеї. Веб-серверу потрібно всього лише зайняти порт нижче 1024, а це CAP_NET_BIND_SERVICE, і решта прав root йому ні до чого. Ідея правильна, хоча застосовують її рідше, ніж варто було б.

MAC, мандатний контроль доступу, задає адміністратор, і власник файлу змінити цю політику не може. SELinux і AppArmor реалізують її через LSM, точки перевірки всередині ядра. Правило звучить приблизно як «процесу веб-сервера дозволено читати лише каталог із сайтом» і діє навіть тоді, коли процес працює від root.

seccomp обмежує не об’єкти, а дії, тобто перелік дозволених системних викликів. Програмі, яка лише рахує, не потрібні ні socket, ні execve, і заборонивши їх, ви робите цілі класи експлуатації безглуздими. Так ізолюють вкладки браузера, на цьому ж побудовані профілі контейнерів.

Захист від власних помилок

Section titled “Захист від власних помилок”

Переповнення буфера дозволяє перезаписати адресу повернення в стеку й змусити програму перейти на код зловмисника. Ускладнюють це три механізми.

NX — це біт у записі таблиці сторінок (модуль 11), який забороняє виконання. Стек і купа позначаються як дані, тож записаний туди код просто не виконається.

Атакувальники відповіли на це технікою ROP: не приносити свій код, а зібрати потрібне з наявних шматків самої програми. NX такого не зупиняє.

ASLR випадково зсуває базові адреси коду, бібліотек, купи й стека при кожному запуску. Щоб зібрати ROP-ланцюг, треба знати адреси, а вони щоразу інші. Через це дві копії однієї програми й показують різні адреси в /proc/*/maps (модуль 10).

Канарка стека — це випадкове значення між локальними змінними й адресою повернення. Перед виходом із функції воно перевіряється, а переповнення, яке дійшло до адреси повернення, неминуче зіпсувало канарку по дорозі.

Жоден із трьох механізмів помилку не усуває. Вони роблять експлуатацію дорожчою, і разом піднімають планку так, що надійні експлойти стають рідкістю.

Коли ламається саме залізо

Section titled “Коли ламається саме залізо”

Meltdown і Spectre у 2018 році скористалися спекулятивним виконанням. Процесор виконує інструкції наперед, і хоча результат потім відкидається, слід у кеші лишається. Вимірюючи час доступу, можна відновити дані, читати які ви права не мали.

Зламали вони саме те, на чому все трималося, — апаратну ізоляцію. Мітигації довелося вносити в ядро: розділити таблиці сторінок так, щоб пам’ять ядра взагалі не була відображена в процес.

Заплатили за це вартістю системного виклику, бо кожен перехід тепер вимагає перемикання таблиць сторінок і скидання частини TLB. Це та сама регресія продуктивності, яку тоді помітили всі (модуль 3).

Урок тут ширший за конкретні уразливості: абстракції течуть. Модель, у якій ізоляція гарантована апаратно, виявилася неповною, бо не враховувала часу виконання як каналу передавання інформації.

Terminal window
id; capsh --print 2>/dev/null | head -4

Ваш користувач і groups, а також capabilities поточного процесу.

Terminal window
getcap -r /usr/bin 2>/dev/null | head

Програми з окремими привілеями замість setuid. Сучасний ping зазвичай саме тут, а не в наступному списку.

Terminal window
find /usr/bin -perm -4000 -type f 2>/dev/null | head

Програми з бітом setuid. Що коротший цей список, то краще.

Terminal window
sestatus 2>/dev/null || aa-status 2>/dev/null | head -5

Стан SELinux або AppArmor. enforcing означає, що політика застосовується, permissive — лише журналюється.

Terminal window
grep Seccomp /proc/self/status; grep -E 'NoNewPrivs' /proc/self/status

Режим seccomp поточного процесу: 0 — вимкнено, 2 — активний фільтр.

Terminal window
cat /proc/sys/kernel/randomize_va_space
for i in 1 2; do awk '/\[stack\]/{print $1}' /proc/self/maps; done

Режим ASLR (2 — повний) і дві різні адреси стека в двох запусках.

Terminal window
checksec --file=/bin/ls 2>/dev/null || readelf -lW /bin/ls | grep -E 'GNU_STACK|GNU_RELRO'

Які захисти зібрано у виконуваний файл: NX, канарки, PIE, RELRO.

Terminal window
grep . /sys/devices/system/cpu/vulnerabilities/* 2>/dev/null | head

Апаратні уразливості процесора і застосовані до них мітигації.

Типові помилки розуміння

Section titled “Типові помилки розуміння”

«Root може все». У сучасному Linux уже ні. SELinux здатен заборонити root дію, seccomp — системний виклик, а root у контейнері взагалі не є root господаря (модуль 17).

«Права 777 — це просто зручно». Це дозвіл будь-якому процесу в системі переписати файл. Скомпрометований процес будь-якого користувача отримує право запису.

«ASLR захищає від переповнення буфера». Не захищає, помилка нікуди не дівається. Він робить експлуатацію ненадійною, бо адреси щоразу інші.

«NX робить переповнення нешкідливим». Не робить, бо ROP збирає атаку з наявного коду програми, нічого нікуди не записуючи.

«SELinux заважає, вимкну». Режим permissive замість enforcing залишає журналювання й дає побачити, що саме блокується. А повне вимкнення прибирає цілий рівень захисту заради економії години на налаштування.

«Апаратна ізоляція абсолютна». Meltdown і Spectre показали, що час виконання теж є каналом передавання інформації, а жодна апаратна межа цього не враховувала.

Перевір себе

1. Процес працює від root, але SELinux у режимі enforcing забороняє йому читати файл. Хто переможе?
2. Навіщо потрібні POSIX capabilities, якщо є setuid?
3. NX забороняє виконання коду в стеку. Чому цього недостатньо?
4. Що робить seccomp?
5. Чому мітигації Meltdown сповільнили системні виклики?
6. Дві копії однієї програми показують різні адреси стека в /proc/*/maps. Що це?

B4 — контейнер руками (модуль 17) прямо використовує capabilities і seccomp. Додаткова частина: зібрати програму з -fno-stack-protector і без PIE, порівняти вивід checksec і поведінку при переповненні буфера з захищеною збіркою.