[Перевод] Серьёзная безопасность: всплывшие спустя 15 лет баги в ядре Linux
12 марта исследователи кибербезопасности GRIMM опубликовали информацию о трёх интересных багах в ядре Linux. В коде, который игнорировали около 15 лет. К счастью, кажется, всё это время никто не присматривался к коду; по крайней мере, не так усердно, чтобы заметить ошибки. CVE из списка ниже уже исправлены.
CVE-2021–27365. Переполнение буфера динамической памяти из-за sprintf ().
CVE-2021–27363. Утечка адреса ядра из-за указателя в качестве уникального ID.
CVE-2021–27364. Чтение памяти за пределами буфера, приводящее к утечке данных или к панике ядра.
Найденные баги затрагивают iSCSI: компонент, ответственный за почтенный интерфейс данных SCSI в смысле сети: через него вы можете вести коммуникацию с устройством SCSI, например с лентой или дисковым накопителем, которые не подключены к компьютеру напрямую.
Если iSCSI и SCSI в вашей сети уже нет, вы, наверное, пожимаете плечами и думаете: «Не беспокойтесь, в моём ядре нет ни одного драйвера iSCSI: я просто не пользуюсь ими».
В конце концов, код ядра с уязвимостями нельзя эксплуатировать, когда он просто лежит на диске — до того, как этот код станет проблемой, он должен загрузиться в память и активно действовать в системе.
За исключением, конечно, того, что большинство (или, по крайней мере, многие) системы Linux поставляются с сотнями и даже тысячами модулями ядра в папке lib/modules
; они готовы к использованию, когда понадобится, и даже сконфигурированы так, чтобы авторизованные приложения по требованию могли запускать загрузку модуля.
Примечание: насколько нам известно, эти баги исправлены в следующих официально поддерживаемых ядрах Linux, датированных 7 марта 2021 года: 5.11.4, 5.10.21, 5.4.103, 4.19.179, 4.1.4.224, 4.9.260, 4.4.260. Если ваше ядро изменено вендором, оно неофициальное, или его нет в этом списке, проконсультируйтесь с разработчиком своего ядра. Чтобы проверить версию ядра, выполните в терминале uname -r
Мой Linux поставлялся с около 4500 модулями ядра просто на всякий случай:
root@slack:/lib/modules/5.10.23# find . -name '*.ko'
./kernel/arch/x86/crypto/aegis128-aesni.ko
./kernel/arch/x86/crypto/blake2s-x86_64.ko
./kernel/arch/x86/crypto/blowfish-x86_64.ko
[...4472 lines deleted...]
./kernel/sound/usb/usx2y/snd-usb-usx2y.ko
./kernel/sound/x86/snd-hdmi-lpe-audio.ko
./kernel/virt/lib/irqbypass.ko
#
И, хотя я действительно не возражал бы против крутой звуковой карты Tascam Ux2y (например, US122, US224, US428), у меня нет в ней необходимости или места для неё, поэтому сомневаюсь, что мне когда-нибудь понадобится любой драйвер snd-usb-usx2y.ko
.
Тем не менее, ненужные драйверы есть, и, случайно или преднамеренно, какой-то может загрузиться автоматически, в зависимости от используемого по случаю ПО, даже если я не под рутом.
Неплохо посмотреть дважды
Исходящий от бесполезных, а в большинстве случаев упущенных из виду драйверов риск заставил GRIMM дважды обратить внимание на ошибки. Исследователи нашли ПО, при помощи которого непривилегированный злоумышленник может активировать забагованный код драйвера, и добиться таких результатов:
Эскалация привилегий, то есть они дали обычному пользователю привилегии уровня ядра.
Извлечение адресов памяти ядра, чтобы облегчить другие атаки, подразумевающие владение информацией о том, куда загружается ядро.
Сбой ядра, а значит, и всей системы.
Считывание фрагментов данных из памяти ядра, когда предполагается, что они недосягаемы.
Каким бы неопределённым и ограниченным ни был последний эксплойт, похоже, непривилегированный пользователь сможет случайно подсмотреть реальные передаваемые через iSCSI данные. Теоретически это означает, что злоумышленник с непривилегированной учётной записью на сервере, где использовался iSCSI, в фоновом режиме может запустить невинную программу, которая компрометирует случайно выбранные в памяти привилегированные данные.
Даже фрагментированный и неструктурированный поток конфиденциальных данных, периодически похищаемый из привилегированного процесса (помните печально известную ошибку Heartbleed?), может раскрыть наши секреты. Не забывайте о том, насколько легко программы распознаёт и «наскребает» паттерны данных, пролетающие в RAM: номера кредитных карт, адреса электронной почты.
Посмотрим на баги внимательнее
Выше упоминалось, что первая ошибка вызвана применением sprintf()
. Это функция языка Си, сокращение от formatted print into string — форматированная печать в строку, так текстовое сообщение распечатывают в блок памяти, чтобы воспользоваться им позже. Посмотрим на этот код:
char buf[64]; /* Reserve a 64-byte block of bytes */
char *str = "42"; /* Actually has 3 bytes, thus: '4' '2' NUL */
/* Trailing zero auto-added: 0x34 0x32 0x00 */
sprintf(buf,"Answer is %s",str)
Он резервирует блок памяти buf, содержащий 12 символов «Answer is 42», за которым следует нулевой терминальный нулевой байт ASCII NUL, а в конце 64-байтового буфера — 51 нетронутый байт.
Однако sprintf()
опасна и не должна использоваться никогда: она не проверяет, достаточно ли распечатанным данным места в конечном блоке памяти. Выше, если строка в переменной str
длиннее 54 байт, включая нулевой байт в конце, то вместе с текстом «Answer is» она не поместится в buf…
Хуже, когда текстовые данные str
не имеют нулевого байта в конце: на C это знак остановки копирования строки, то есть вы можете случайно скопировать тысячи или даже миллионы байт памяти после str
и копировать их до тех пор, пока не встретится нулевой байт, а к этому времени крах ядра случится почти наверняка.
Современный код не должен содержать функции Си, работающие с копиями памяти неограниченной длины. Используйте snprintf()
: это форматированная строка максимум в N байт, а также работайте функциями семейства snprintf()
.
Не выдавайте свои адреса
Вторая ошибка возникла из-за использования адресов памяти в качестве уникальных идентификаторов.
Идея звучит хорошо: если нужно обозначить в коде ядра объект данных с ID без конфликтов с другими ID, можно воспользоваться числами 1, 2, 3 и так далее.
Но если вы хотите уникальный идентификатор, не конфликтующий с другими пронумерованными объектами в ядре, то можете подумать: «Почему бы не использовать адрес памяти, в котором хранится мой объект: он, очевидно, уникален, учитывая, что два объекта не могут одновременно находиться в одном месте в RAM?» (если только с использованием памяти уже не случился кризис).
Проблема в том, что, если ваш идентификатор объекта в какой-то момент виден вне ядра, например, чтобы на него могли ссылаться неавторизованные программы в так называемом пользовательском пространстве, получается, что вы просто выдаёте информацию о внутреннем расположении памяти ядра, а такого происходить не должно.
Современные ядра используют так называемое KASLR, сокращение от kernel address space layout randomisation (рандомизация адресного пространства ядра), специально для того, чтобы помешать непривилегированным пользователям выяснить структуру ядра.
Если вы когда-нибудь взламывали замки (это популярное, удивительно расслабляющее хобби среди хакеров и исследователей кибербезопасности: можно просто купить прозрачный замок и получать удовольствие), то поймёте, что всё становится проще, когда вы уже знаете механизм замка.
Аналогично, знание о том, что и где загружено внутри ядра, почти всегда упрощает эксплуатацию других ошибок, например переполнения буфера.
Что делать?
Обновите ядро. Если вы полагаетесь на разработчика ядра, не забудьте установить последнее обновление. Выше смотрите версии ядра, в которых перечисленные уязвимости устранены.
Не используйте проблемные функции Си. Избегайте всех функций, которые не отслеживают максимальный объём используемых данных. В выбранной вами операционной системы или IDE придерживайтесь функций, официально документированных как «безопасные функции C-строк», работайте с безопасными функциями везде, где это возможно. Такой подход даёт больше шансов предотвратить переполнение буфера.
Чтобы избежать сюрпризов, блокируйте загрузку модулей ядра. Если вы установите системную переменную Linux
kernel.modules_disable=1
после того, как ваш сервер загрузился и корректно заработал, то не загрузятся никакие модули; ни случайно, ни по замыслу. Эта настройка отключается только при перезагрузке. Вот два варианта:sysctl -w kernel.modules_disable=1 echo 1 > /proc/sys/kernel/modules_disable
Поймите, какие модули ядра необходимы, и используйте только их. Можно собрать статическое ядро, скомпилировав только нужные модули, или создать пакет с ядром для своих серверов, при этом удалив все ненужные модули. При желании со статическим ядром загрузку модулей можно отключить полностью.
На нашем комплексном курсе Этичный хакер мы учим студентов искать уязвимости и поддерживать безопасность любых IT-систем. Если чувствуете в себе силы и склонность к сфере кибербезопасности — приходите учиться. Будет сложно, но интересно!
Узнайте, как прокачаться в других специальностях или освоить их с нуля:
Другие профессии и курсыПРОФЕССИИ
КУРСЫ