Защита памяти и ядра в ОС ROSA

Материал из Rosalab Wiki
Перейти к: навигация, поиск
Idea.png
Примечание
Статья в процессе написания. Переносится из написанного ранее документа PDF.

Защита памяти и ядра в ОС ROSA.Свод требований

Здесь приведены рекомендации по настройке ядра и опций защиты памяти. Критически важно защищать ядро и системную память. Если допустить, что ядро или память скомпрометированы, то нет никаких гарантий в правильной работе любых механизмов безопасности и системы в целом.

Сводная информация с рекомендуемыми настройками для ядра ОС и проекция к Методическому документу ФСТЭК «Рекомендации по обеспечению безопасной настройки операционных систем Linux» от 25 декабря 2022 г. — приведена в таблице ниже:

№ п/п Параметр и рекомендуемое значение Интерфейс См. документ ФСТЭК См. раздел Значение по умолчанию
1 kernel.dmesg_restrict=1 /etc/sysctl.conf 2.4.1 См. 0
2 kernel.kptr_restrict=2 /etc/sysctl.conf 2.4.2 Текст ячейки 0
3 init_on_alloc=1 /etc/default/grub 2.4.3 Текст ячейки Неактивно
4 slab_nomerge /etc/default/grub 2.4.4 Текст ячейки Неактивно
5 iommu=force
iommu.strict=1
iommu.passthrough=0
/etc/default/grub 2.4.5 Текст ячейки Неактивно
6 randomize_kstack_offset=1 /etc/default/grub 2.4.6 Текст ячейки
7 mitigations=auto,nosmt /etc/default/grub 2.4.7 Текст ячейки Неактивно
8 net.core.bpf_jit_harden=2 /etc/sysctl.conf 2.4.8 Текст ячейки 0
9 vsyscall=none /etc/default/grub 2.5.1 Текст ячейки Неактивно
10 kernel.perf_event_paranoid=3 /etc/sysctl.conf 2.5.2 Нет 2
11 debugfs=no-mount /etc/default/grub 2.5.3 Текст ячейки Неактивно
12 kernel.kexec_load_disabled=1 /etc/sysctl.conf 2.5.4 Текст ячейки 0
13 user.max_user_namespaces=0 /etc/sysctl.conf 2.5.5 Текст ячейки 509894
14 kernel.unprivileged_bpf_disabled=1 /etc/sysctl.conf 2.5.6 Текст ячейки 2
15 vm.unprivileged_userfaultfd=0 /etc/sysctl.conf 2.5.7 Текст ячейки 1
16 dev.tty.ldisc_autoload=0 /etc/sysctl.conf 2.5.8 Текст ячейки 1
17 tsx=off /etc/default/grub 2.5.9 Текст ячейки Неактивно
18 vm.mmap_min_addr=4096 /etc/sysctl.conf 2.5.10 Текст ячейки 65536
19 kernel.randomize_va_space=2 /etc/sysctl.conf 2.5.11 Текст ячейки 2
20 kernel.yama.ptrace_scope=3 /etc/sysctl.conf 2.6.1 Текст ячейки 1

Защита памяти и ядра ОС

В чем опасность?

Опасность заключается в возможности получения доступа к данным, обрабатывающимся ядром. Данные, которые обрабатывает ядро носят критическую ценность для ОС и её пользователей. Если они неправомочно доступны, то с их помощью можно реализовать атаки любого вида. По умолчанию в Linux имеются возможности загрузки другого ядра (подмены), или использовании потенциально опасных модулей (драйверов) ядра, в том числе без перезагрузки. Это может привести к обходу механизмов защиты и любой другой опасной активности.

Что можно сделать для повышения безопасности?

Можно активизировать собственные механизмы защиты ядра ОС, направленные на противодействие атакам разного рода, такие как: защита памяти, защита от переполнения буфера, контроль целостности ядра и загружаемых драйверов, ограничить отладку и т. п. Информация о механизмах защиты ядра с пояснениями приведена далее.

Ядро является основным и наиболее критическим компонентом любого дистрибутива операционной системы Linux, в том числе и с точки зрения выполнения функций безопасности. Большинство их сосредоточено именно в ядре, либо так или иначе выполняется при посредничестве ядра ОС.

Ядро обрабатывает данные программ, управляет страницами памяти и взаимодействием между процессами, управляет файловыми системами, сетью, всей периферией, занимается разграничением доступа, генерирует первичные сообщения аудита, может контролировать целостность программ, управляет жизненным циклом каждого процесса, управляет вводом и выводом, и т. п. Следовательно, во время работы, в ядре обрабатывается или хранится множество важнейшей информации — ключи шифрования, пароли или хеши паролей (аутентификационная информация), защищаемые данные, а также принимаются решения о доступе.

Кроме того, поскольку ядро еще и отслеживает все другие компоненты ОС. Важно, чтобы выполнялось только то ядро, которое является доверенным. Иначе может произойти раскрытие информации, и данные пользователей, а также пароли или ключи шифрования, могут быть скомпрометированы. Кроме того, если не защищать ядро ОС, то нарушитель может попытаться изменить состав модулей (драйверов) ядра. Это может привести к самым непредсказуемым последствиям. Под угрозой окажутся любые обрабатываемые данные и функции безопасности. Так можно преодолеть защиту, отключив важную подсистему ядра ОС Linux, например механизм аудита, функции разграничения доступа (SELinux), выключить защиту памяти или фильтр пакетов. Любым возможностям несанкционированного взаимодействия с ядром необходимо препятствовать.

Подробная информация об известных техниках эксплуатации уязвимостей в ядре ОС Linux приведена по ссылке: https://github.com/xairy/linux-kernel-exploitation

Защита от просмотра адресов указателей

Переменная ядра ОС, отвечающая за доступ к интерфейсам ядра /proc/kallsyms и /proc/modules — это kernel.kptr_restrict. Просматривая эти файлы, можно получать значения адресации памяти для указателей ядра. То есть можно, например, узнать, на какой адрес в памяти ссылается указатель той или иной программы, или загруженного модуля ядра. Переменная может принимать значения 0, 1 и 2. Более подробное описание рисков, связанных с атакой на адреса указателей и прототип эксплойта, демонстрирующий такую возможность, приведены по ссылке:

https://kernsec.org/wiki/index.php/Bug_Classes/Kernel_pointer_leak

Если для переменной kernel.kptr_restrict определено значение 0, то просматривать значения адресов в памяти может любой пользователь ОС. Если задано значение 1, то просматривать адресацию функций может только root. Если значение равно 2, то никто не получит информацию об адресации. Рекомендуемое значение — два. При значении единица — отображение адресов заменяется на нули для всех пользователей, кроме root. При значении два — отображение адресов заменяется на нули для всех пользователей, включая root.

Для проверки текущего значения этой переменной выполнить:

# sysctl -a | grep kptr kernel.kptr_restrict = 2

Если значение отличается, то выполнить установку этой переменной:

# sysctl -w kernel.kptr_restrict=2

# echo 'kernel.kptr_restrict = 2' >> /etc/sysctl.conf

Отключение трассировки процессов

На первом этапе нужно проверить, есть ли в выполняющемся ядре LSM модуль YAMA. Для этого можно выполнить поиск нужной опции в конфигурационном файле ядра, например:

$ cat /boot/config-5.10.118-generic-2rosa2021.1-x86_64 | grep YAMA

CONFIG_SECURITY_YAMA=y

Если есть, то в ОС может использоваться настройка, позволяющая производить ограничения на трассировку процессов. Если нет (выдано значение CONFIG_SECURITY_YAMA is not set), то можно пропустить эту настройку.

Для проверки текущих значений трассировки выполнить (от имени администратора root):

# sysctl -a | grep ptrace

kernel.yama.ptrace_scope = 2

Рекомендуется установить запрет трассировки, используя значение 2 для переменной kernel.yama.ptrace_scope, или более строгое. Значение 2 определяет, что трассировку процессов может осуществлять только root. Значение \commandbox{3} полностью отключает трассировку, для всех пользователей, в том числе и для root. В описанной ниже конфигурации трассировка разрешается только пользователю root:

# sysctl -w kernel.yama.ptrace_scope=2

# echo 'kernel.yama.ptrace_scope = 2' >> /etc/sysctl.conf

Ограничения на просмотр сообщений ядра

Команда dmesg является очень популярной в ОС Linux. Она выводит на экран сообщения ядра ОС. По умолчанию эту команду может использовать любой пользователь в системе, следовательно её может применять злоумышленник, чтобы получить информацию о системе и некоторых её характеристиках. Соответственно, любой пользователь ОС сможет или напрямую обратиться к этому файлу ($ cat /dev/kmsg), или использовать программы чтения кольцевого буфера аудита ядра, такие как dmesg или rsyslog. Рекомендуется ограничивать пользователей в возможности получать сообщения кольцевого буфера аудита ядра, чтобы снизить вероятность получения информации о работающей системе или ее параметрах.

Переменная ядра ОС kernel.dmesg_restrict отвечает за доступ к интерфейсу кольцевого буфера аудита ядра (файлу /dev/kmsg). По умолчанию доступ пользователей к этому интерфейсу не запрещен. Значение 1 предписывает, что обращаться к нему может только root. Если установлено значение 0, то доступ пользователей к буферу аудита ядра не ограничивается.

Проверить текущее значение политики доступа к интерфейсу кольцевого буфера аудита ядра можно так:

# sysctl -a | grep dmesg

kernel.dmesg_restrict = 1

Если при проверке значение отличается от единицы, то нужно выполнить настройку запрета чтения из этого интерфейса всем, кроме root:

  1. sysctl -w kernel.dmesg_restrict=1
  1. echo 'kernel.dmesg_restrict = 1' >> /etc/sysctl.conf

Противодействие уязвимостям типа Meltdown и Spectre

Использование уязвимостей типа Meltdown или Spectre заключается в возможности несанкционированного выполнения кода в пространстве ядра. Например, возникающих при спекулятивном выполнении инструкций процессора (уязвимости типа Meltdown), и/или связанных с особенностями функционирования модуля прогнозирования ветвлений (уязвимости типа Spectre). Эти уязвимости были обнаружены в 2017 году, но все еще продолжают появляться различные их варианты.

Современные процессоры семейства x86 (производства компаний Intel и AMD) предоставляют возможность параллельного использования нескольких «нитей» или «потоков» на каждом процессорном ядре. Такая возможность называется SMT (Symmetric Multi-Threading, симметричная многопоточность). Поскольку «нити» или «потоки» (исходя из конструктивных особенностей ЦП) сохраняют возможности обмена информацией между собой, то это потенциально может привести к несанкционированному обмену информацией между процессами, выполняющимися в разных потоках, но на одном ядре ЦП. Либо может привести к нежелательному раскрытию информации в памяти и т. п. Поэтому в защищаемой системе опции процессора, отвечающие за SMT желательно отключить. При этом настоятельно рекомендуется использовать комплексный подход — отключать поддержку SMT как на уровне системы ввода-вывода, так и на уровне операционной системы. Данный подход обеспечивает бОльшую уверенность в том, что уязвимости, связанные с недостатками SMT не будут проэксплуатированы, например в том случае, если производитель оборудования предоставит ошибочное обновление системы ввода-вывода, повторно включающее SMT после отключения.

Уязвимости типа Meltdown и Spectre могут привести к реализации атак, при которых злоумышленник сможет получить доступ к защищенной памяти из программы, не обладающей соответствующими привилегиями (путём анализа данных, записываемых в кэш процессора).

Одним из наиболее эффективных способов противостояния атакам семейств Meltdown и Spectre является отключение SMT (помимо обновлений инструкций самих процессоров и разнообразных патчей к ядру Linux и компиляторам). Для этого ядро ОС Linux имеет нужные параметры, которые позволяют отключить SMT.

С другой стороны, минусом этого решения является снижение производительности. Отключение SMT (любым способом, программным или аппаратным), приведет к тому, что количество виртуальных процессорных ядер (vCPU) уменьшится кратно числу потоков в каждом ядре ЦП (то есть, не менее, чем вдвое). Наличие большого количества ядер vCPU особенно важно при необходимости использовать виртуальные машины.

Но если без SMT все же можно обойтись — то лучше отключить эту функциональность. Желательно планировать покупку оборудования с учетом того, что SMT будет отключаться.

Для проверки того, используется ли технология SMT, требуется от имени любого пользователя выполнить следующую команду:

$ cat /sys/devices/system/cpu/smt/active

0

Где 0 свидетельствует об отсутствии поддержки.

Иначе, если вывод 1, то рекомендуется отключить поддержку SMT, сначала в BIOS/UEFI, если это поддерживается оборудованием. А затем выключить её и в ядре операционной системы.

Для отключения поддержки SMT в ОС, требуется от имени root выполнить изменение строки GRUB_CMDLINE_LINUX_DEFAULT конфигурационного файла /etc/default/grub, дописав в ее конец следующие директивы:

GRUB_CMDLINE_LINUX_DEFAULT='splash smem=1 mitigations=auto,nosmt'

После чего нужно обновить конфигурацию загрузчика (# update-grub) и перезагрузить ОС.

Отключение технологии Intel TSX

Технология Intel TSX применялась в процессорах Intel до 8-го поколения. Эта технология была направлена на увеличение производительности вычислений, путем попыток предсказания и слияния данных в памяти процессора. Ядро ОС Linux по умолчанию поддерживает эту технологию. Однако эта технология небезопасная, устаревшая. Даже сама компания Intel её больше не использует, и не рекомендует. Поддержку TSX рекомендуется отключить, если ваш процессор Intel поколения 8 или ниже.

Для отключения поддержки TSX в ОС, требуется от имени root выполнить изменение строки GRUB_CMDLINE_LINUX_DEFAULT} конфигурационного файла /etc/default/grub, дописав в ее конец следующую директиву:

GRUB_CMDLINE_LINUX_DEFAULT='splash smem=1 tsx=off'

После чего нужно обновить конфигурацию загрузчика (# update-grub) и перезагрузить ОС.

Защита от переполнения буфера

Обычно в стеке памяти программы содержится критически важный для безопасности данных адрес, который может привести к выполнению произвольного кода. Им является сохраненный адрес возврата, то есть адрес памяти, по которому выполнение должно продолжиться после завершения выполнения некоей текущей функции. Злоумышленник может перезаписать это значение нужным ему адресом памяти, к которому у него также есть доступ на запись, и в который он помещает свой код. Этот код будет запускаться с полными привилегиями уязвимой программы. Например, он может подставить адрес нужного ему вызова, скажем, POSIX system(), оставив аргументы вызова в стеке. Это часто называют эксплойтом возврата в libc, поскольку злоумышленник обычно заставляет программу во время возврата перейти к интересующей его процедуре в стандартной библиотеке C libc). Другие важные данные, обычно находящиеся в стеке, включают указатели стека и кадра — два значения, которые указывают смещения для вычисления адресов памяти. Изменение этих значений также может использовать злоумышленник для организации деструктивных воздействий на программу. Чтобы этому противостоять рекомендуется использовать все доступные способы защиты от переполнения буфера.

Защиту от переполнения буфера важно использовать как на уровне «железа», так и на уровне операционной системы. Это важно потому, что исполняемые файлы в системе, могут быть скомпилированы без использования техник защиты от переполнения буфера (например, опции компилятора GCC типа fstack-protector не задействовались). Либо некоторые процессоры (особенно старые или дешёвые) могут не иметь соответствующих аппаратных возможностей.

Аппаратная защита от переполнения буфера и ее проверка

Современные процессоры семейства x86 (производства компаний Intel и AMD) предоставляют возможность запрета выполнения кода в некоторых страницах памяти. У процессоров такая возможность активизируется включением специального бита, который у AMD называется No Execute Bit (NX bit), а у Intel — Execute Disable Bit (XD bit). Включение таких битов процессора — это способ минимизации негативных последствий, вызванных переполнением буфера.

Для проверки того, задействованы ли в BIOS или UEFI функции аппаратной защиты от переполнения буфера, требуется выполнить:

# journalctl | grep "protection: active"

kernel: NX (Execute Disable) protection: active

В том случае, если вывод отличается от приведенного выше, то требуется включить соответствующие опции в BIOS/UEFI (если такая возможность предоставлена изготовителем оборудования) и перепроверить.

Программная защита от переполнения буфера («канарейка»)

Техника программной защиты от переполнения буфера на жаргоне программистов называется «канарейка» (canary). Такое название употребляется по аналогии с канарейками, которые использовались шахтерами в угольных шахтах до появления газоанализаторов. Задача «канарейки» -- умереть прежде шахтера. То есть, в данном случае -- прервать выполнение программы до того, как будет осуществлено какое-то деструктивное воздействие. Для этого небольшое целое число (значение которого случайно выбирается при запуске программы) записывается в память непосредственно перед указателем возврата адреса в стеке, и все дальнейшие обращения к нему отслеживаются.

Это хорошо автоматизируемая техника, и она может быть выполнена не программистом, а прямо компилятором во время сборки исходного кода. Естественно, такая концепция защиты немного влияет на производительность программы. Но влияние это не слишком значительное, и лучше немного пренебречь производительностью во имя защиты. Однако эта защита пока никак не настраивается прямо в операционной системе. Она, как уже было сказано, осуществляется на этапе сборки прямо в сборочной среде с помощью специальных опций компилятора GCC типа fstack-protector.

Проиллюстрировать поведение этой опции можно на следующем примере. Можно подготовить простую программу на языке Си, откомпилировать с нужной опцией fstack-protector и запустить. Так можно убедиться в том, что при сборке программы с такой опцией отслеживается переполнение стека (вследствие контроля к областям оперативной памяти системным вызовом \mprotect()) и выполнение программы прерывается.

Пример такой проверочной программы на языке Си указан ниже:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

struct no_chars {

unsigned int len;

unsigned int data;

};

int main(int argc, char * argv[])

{

struct no_chars info = { };

if (argc < 3) {

fprintf(stderr, "Usage: %s LENGTH DATA...\n", argv[0]);

return 1;

}

info.len = atoi(argv[1]);

memcpy(&info.data, argv[2], info.len);

return 0;

}

Если эту программу сохранить под именем, например, program1.c, затем откомпилировать с опцией защиты от переполнения буфера и запустить, то можно убедиться в том, что в результате срабатывания защиты, программа прерывается:

$ gcc -O0 -fstack-protector-strong -o ./program1 ./program1.c

$ ./program1 64 AAA

*** stack smashing detected ***: terminated

Aborted

$ echo $?

134

Как видно, программа получила шестой сигнал (128 + 6 = 134) SIGABRT. Для получения списка сигналов можно выполнить $ kill -l или прочесть справку signal(7).

Кроме того, можно откомпилировать программу и без опции защиты от переполнения буфера и повторно проверить, что будет. Для проверки опций (в том числе опций защиты от переполнения буфера), с которыми была собрана та или иная программа, существует несколько способов.

Например, можно посмотреть опции сборки, указанные в метаданных RPM пакета с программой. Допустим, требуется проверить опции сборки для программы /bin/ls. Для этого нужно сделать запрос к менеджеру пакетов rpm, например:

# rpm -q --queryformat="%{NAME}: %{OPTFLAGS}\n" binutils

В ответ система сообщит опции сборки, например для пакета binutils (куда входит программа /bin/ls):

binutils: -O2 -fomit-frame-pointer -gdwarf-4 -Wstrict-aliasing=2 -pipe -Wformat

-Werror=format-security -D_FORTIFY_SOURCE=2 -fPIC -fstack-protector-strong

--param=ssp-buffer-size=4 -m64 -mtune=generic -fstack-protector-strong

Как видно из вывода запроса rpm, он содержит нужную для защиты от переполнения буфера опцию -fstack-protector-strong. Некоторым неудобством этого подхода является то, что нужно знать или уметь выяснять, к какому именно пакету относится та, или иная программа. А если нужно проверить несколько программ в одном каталоге, но которые могут принадлежать разным пакетам, задача проверки может стать совсем мучительной без познаний в программировании на языке оболочки.

Кроме этого способа существует анализатор опций сборки, который представлен в виде сценария checksec, доступный по ссылке: https://github.com/slimm609/checksec.sh

Этот сценарий проверяет содержимое бинарных файлов и библиотек в соответствующих метаданных ELF формата, и выводит опции сборки. Например, можно посмотреть на его вывод для проверки того же бинарного файла /bin/ls:

$ checksec --output=csv --file=/bin/ls

Partial RELRO,Canary found,NX enabled,PIE enabled,No RPATH,No RUNPATH,No Symbols,Yes,5,17,/bin/ls

Как видно из вывода, для программы найдено применение техники защиты от переполнения буфера, т.н. «канарейка»: Canary found.

Есть мнение, что применение этого сценария значительно удобнее, чем получение данных из RPM пакета, так как и результат нагляднее, и проверять можно сразу все файлы в каталоге, и выводить результаты можно еще и в форматах CSV или XML. В любом случае, каждый сам для себя может решить, какой способ проверки защиты от переполнения буфера удобнее использовать.

Естественно, что и само ядро ОС тоже компилируется с опциями защиты от переполнения буфера.