Ограничиваем файловую систему в Debian
В этой статье я попытаюсь собрать в одну кучу наработки по ограничению доступа в файловой системе Linux. Описание делается с уклоном в сторону Debian и Debian based-систем, но легко может быть адаптировано и для других дистрибутивов.
Основная идея описанного ниже - превращение необходимых каталогов в точки монтирования, после чего ограничение прав уже непосредственно точек монтирования. Кроме этого, активно используется фича, добавленная в ядрах 2.6.26+ - bind'инг каталогов в режиме read only.
Все действия делаются последовательными операциями монтирования и целиком описываются в /etc/fstab:
# Каталог ядра и файлов загрузчика. Не меняется во время работы, тут ни исполняемых файлов, ни устройств, ни SUID-файлов
# Если /boot на отдельном разделе - параметры монтирования переносятся на него.
/boot /boot auto bind 0 0
/boot /boot auto bind,remount,ro,nodev,nosuid,noexec 0 0
# Пример конфигурирования /tmp для использования с tmpfs
# В Debian Wheezy можно сделать /tmp в tmpfs, задав RAMTMP=yes в /etc/default/tmpfs. Там же задаются размеры этой
# и другой точек tmpfs. Однако эта опция не ставит noexec на каталог.
# Еще одна "публично" доступная tmp-точка - это /var/tmp. По FHS там может быть временный контент,
# который не удаляется при перезагрузке. На деле такое встречал только в KDE - там хранился кэш браузера.
# В остальных случаях - а, тем более, на сервере - удобнее сделать /var/tmp симлинком на /tmp
tmpfs /tmp tmpfs defaults,size=1G,noexec,nosuid,nodev 0 0
# Данная точка описывается, если для /usr не выделен отдельный раздел. Если выделен - переносим параметры туда
# /usr не меняется во время работы, поэтому можно держать его в RO. Исключением может быть /usr/src. При необходимости
# его можно вынести в отдельный каталог (например, сделать /var/src), а в /usr сделать симлинк.
# Кроме того, в /usr не бывает файлов устройств
/usr /usr auto bind 0 0
/usr /usr auto bind,remount,nodev,ro 0 0
# Аналогично /usr - если есть отдельный раздел под него - делаем описание параметров там
# Данный раздел в RW, но тут не бывает SUID-бинарников
# Плюс в /var практически нет исполняемых файлов - возможно, стоит подробнее описать каталоги, где они находятся,
# а /var сделать noexec
/var /var auto bind 0 0
/var /var auto bind,remount,nodev,nosuid 0 0
# Некоторый софт ставится в /opt - защищаем его.
/opt /opt auto bind 0 0
/opt /opt auto bind,remount,ro,nodev 0 0
# Защищаем от записи каталоги исполняемых файлов
/bin /bin auto bind 0 0
/bin /bin auto bind,remount,ro,nodev 0 0
/sbin /sbin auto bind 0 0
/sbin /sbin auto bind,remount,ro,nodev 0 0
# Защищаем от записи каталог основных системных библиотек и модулей ядра
/lib /lib auto bind 0 0
/lib /lib auto bind,remount,ro,nodev,nosuid 0 0
# Внутри /var переопределяем параметры отдельного каталога, где хранится основная база установленных пакетов
# Делаем его доступным только на чтение, а также отключаем возможность размещения там файлов устройств и SUID-файлов
/var/lib/dpkg /var/lib/dpkg auto bind 0 0
/var/lib/dpkg /var/lib/dpkg auto bind,remount,ro,nosuid,nodev 0 0
/boot /boot auto bind 0 0
/boot /boot auto bind,remount,ro,nodev,nosuid,noexec 0 0
tmpfs /tmp tmpfs defaults,size=1G,noexec,nosuid,nodev 0 0
/usr /usr auto bind 0 0
/usr /usr auto bind,remount,nodev,ro 0 0
/var /var auto bind 0 0
/var /var auto bind,remount,nodev,nosuid 0 0
/opt /opt auto bind 0 0
/opt /opt auto bind,remount,ro,nodev 0 0
/bin /bin auto bind 0 0
/bin /bin auto bind,remount,ro,nodev 0 0
/sbin /sbin auto bind 0 0
/sbin /sbin auto bind,remount,ro,nodev 0 0
/lib /lib auto bind 0 0
/lib /lib auto bind,remount,ro,nodev,nosuid 0 0
/var/lib/dpkg /var/lib/dpkg auto bind 0 0
/var/lib/dpkg /var/lib/dpkg auto bind,remount,ro,nosuid,nodev 0 0
После применения подобных изменений даже случайное выполнение от рута rm -rf / или рекурсивного chown/chmod от корня оставит шанс на быстрое восстановление системы, пока она еще работает - ведь сохранятся неизменными основные библиотеки, исполняемые файлы, база пакетов и т.п.
В таком же стиле можно описать больше каталогов - например, логичным было бы сделать защиту /etc от изменений (но не забываем, что там есть файлы, изменяющиеся при старте или работе системы, скрипты и так далее), или, например, ограничить домашние каталоги пользователей.
Особенности
При использовании такой конфигурации всплывают 2 особенности:
- Во-первых, некоторые приложения - а особенно APT - требуют, чтобы /tmp был исполняемый - там размещаются и выполняются скрипты настроек в процессе установки и конфигурации пакетов.
- Во-вторых, как ставить пакеты или обновлять систему, если большая часть ФС в Read Only?
Обе проблемы решаются добавлением нескольких строк в конфиг-файл APT - /etc/apt/apt.conf:
Dpkg::Pre-Invoke {"for i in /boot /usr /opt /bin /sbin /lib /var/lib/dpkg ; do mount -o remount,rw $i ; done";};
Dpkg::Post-Invoke {"for i in /boot /usr /opt /bin /sbin /lib /var/lib/dpkg ; do mount -o remount,ro $i ; done";};
APT::ExtractTemplates::TempDir "/root";
Первые 2 строки перемонтируют перечисленные точки монтирования в RW до начала установки пакетов и обратно в RO - после.
Третья строка переопределяет временный каталог размещения скриптов. Это может быть любой exec-каталог, доступный на запись руту.
Заметки
- В Debian Squeeze немного странная организация монтирований ФС при старте системы - есть некий /lib/init/rw, который монтируется в tmpfs и где находятся какие-то флаги. Однако если /lib при старте будет в RO, то вылетает ошибка на создании различных файлов в этом каталоге, в том числе фейлится монтирование /var/run и /var/lock в tmpfs. Возможно, проблема из-за неправильной последовательности монтирования каталогов. В Wheezy этой проблемы нет.
- apt-get update упорно пытается что-то сделать с лок-файлом /var/lib/dpkg/lock во время перечитывания списка пакетов. Причем, файл есть что до обновления, что в процессе, что после. Если туда поместить какой-то контент - он остается. Пересоздается (если не существует) во время чтения списка загруженных пакетов. Опции для задания другого пути к файлу не нашел. Для того, чтобы всякие скрипты, проверяющие обновления по крону, не ругались на недоступный для записи файл, можно выполнять обновление с опцией:
apt-get -o Debug::NoLocking=1 update
- Не забываем, что "все есть файл" - и раз монтировать можно каталоги, то почему бы не монтировать файлы в самих себя? Пример реализации ниже:
rain@elitebook:/tmp$ mkdir dir rain@elitebook:/tmp$ touch dir/file rain@elitebook:/tmp$ sudo mount -o bind dir dir rain@elitebook:/tmp$ sudo mount -o bind,remount,ro dir dir rain@elitebook:/tmp$ sudo mount -o bind dir/file dir/file mount: warning: dir/file seems to be mounted read-only. rain@elitebook:/tmp$ sudo mount -o bind,remount,rw dir/file rain@elitebook:/tmp$ echo 123 > dir/somefile bash: dir/somefile: Файловая система доступна только для чтения rain@elitebook:/tmp$ echo 123 > dir/file rain@elitebook:/tmp$ cat dir/file 123
Т.е., имеем доступный только на чтение каталог с файлами (одним файлом в данном случае), но доступным на запись одним файлом. Например, можно сделать RO каталог /etc, но открыть на запись /etc/passwd, /etc/shadow, чтобы пользователи могли менять пароли или шелл. И так далее в том же духе. В некотором роде такой себе глобальный immunable, но работающий на любой линуксовой ФС, а не только на extN