30
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

Изоляция процессов средствами chroot

Время чтения 3 минуты
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
30
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

Привет, вАЙТИ. Меня зовут Егор Орлов, я более 24 лет в ИТ, преподаю в СПбПУ.

В этой статье мы разберем системный вызов chroot() в UNIX-подобных ОС. Разберемся, как использовать данный системный вызов для перевода процесса в специальную изолированную часть файловой системы, ограничив тем самым взаимодействие приложения с остальной системой. Выросший из chroot() механизм namespaces в Linux является сейчас основой для построения песочниц (sandboxing), а также технологий контейнеризации, таких как docker и lxc.

Изоляция процессов средствами chroot

Системный вызов chroot()

Chroot — сокращение от change root, то есть «изменить корень». Как можно понять из названия, это механизм изоляции процессов в специальной части файловой системы, то есть формирование отдельной от всей системы иерархии каталогов для работы конкретного приложения. 

Системный вызов chroot() был добавлен еще в Unix Version 7 по меркам ИТ достаточно давно — 45 лет назад, в 1979 году, и с тех пор используется в UNIX-подобных операционных системах для создания безопасной (безопасной для остальной части системы) среды выполнения приложений. 

$ man 2 chroot

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

Механизм chroot-изации (изоляции процесса на ФС) является предшественником таких технологий в ядре Linux, как namespaces и cgroups, лежащих в основе современной контейнеризации, а именно docker, CRI-O, lxc/lxd.

Также механизм chroot-изации применяется в решениях по созданию изолированных окружений — «песочниц» (sanbox, sandboxing), подходящих для запуска недоверенных приложений и вообще для исследования вредоносного программного обеспечения. Примеры таких sandbox-решений для ОС на основе ядра Linux — firejail и bubblewrap

Кроме контейнеризации и «песочниц», chroot-изация оказалась полезной при создании изолированных от системы сред для независимого от системы развертывания приложений со всеми их зависимостями. В ОС на основе ядра Linux мы можем увидеть их в таких решениях, как snap и flatpak.

В других UNIX-подобных операционных системах наличие системного вызова chroot() помогло реализовать их собственные контейнеры и песочницы, например FreeBSD jail, Solaris Zones.

Использование системного вызова chroot()

Давайте подготовим отдельную chroot-среду для запуска приложения и запустим там специальное приложение, позволяющее оценить особенности этой среды. Создадим chroot-каталог с двумя подкаталогами. По имени подкаталога мы легко узнаем, что попали в него.

$ mkdir -p /tmp/chroot/{subdir1,subdir2}

Имя каталога chroot-изации произвольно, каталог /tmp выбран для его размещения исключительно ради удобства. 

Код приложения (chroot.c) на языке C представлен ниже:


#include <stdio.h>
#include <unistd.h>
#include <dirent.h>

int main(void) {
    // Текущий каталог процесса до chroot()
    char t_cwd[PATH_MAX];
    getcwd(t_cwd, sizeof(t_cwd));
    printf("Текущий каталог до chroot(): %sn", t_cwd);

    // Выполнение chroot()
    chdir("/tmp/chroot/");
    if (chroot("/tmp/chroot/") != 0) {
        perror("chroot /tmp/chroot/");
        return 1;
    }

    // Текущий каталог процесса после chroot()
    char a_cwd[PATH_MAX];
    getcwd(a_cwd, sizeof(a_cwd));
    printf("Текущий каталог после chroot(): %sn", a_cwd);

    // Получаем указатель на корень файловой системы
    struct dirent *de;
    DIR *dr = opendir("/");

    // Смотрим содержание корня ФС
    while ((de = readdir(dr)) != NULL)
        printf("%sn", de->d_name);

    // Пытаемся открыть /etc/passwd хостовой ФС
    FILE *f;
    f = fopen("/etc/passwd", "r");
    if (f == NULL) {
        perror("/etc/passwd");
        return 1;
    } else {
        char buf[100];
        while (fgets(buf, sizeof(buf), f)) {
             printf("%s", buf);
        }
    }
    return 0;
}

 

Сборка и запуск указанного примера выполняется командами:

$ make chroot
$ sudo ./chroot

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

Данное приложение:

  1. Выводит текущий каталог, в котором было запущено приложение.
  2. Выполняет системный вызов chroot().
  3. Выводит текущий каталог после выполнения системного вызова chroot().
  4. Пытается перейти в корневой каталог (“/”).
  5. Выводит содержимое текущего каталога.
  6. Пытается открыть файл с базой учетных записей пользователей по его абсолютному пути (/etc/passwd).

После сборки и выполнения приложения по результату в пунктах 4–7 вы увидите, что приложение оказалось изолировано в каталоге /tmp/chroot, видит только его содержимое (два созданных подкаталога) и не имеет возможности выхода за пределы каталога chroot-изации, а также не может открывать файлы по абсолютным путям за пределами этого каталога.

Использование chroot() разработчиками сетевых приложений 

Изоляцией chroot-изированных процессов активно пользуются разработчики сетевых служб для повышения безопасности своих продуктов. Основной процесс службы обычно запускается системой стандартно, без chroot-изации, выполняет необходимые ему действия, работая с системной иерархией каталогов, например считывает свою конфигурацию из каталога /etc. После чего переходит системным вызовам chroot() в отдельный, специально подготовленный каталог и уже в нем, изолированно от основной части системы, начинает потенциально уязвимое для сетевых атак взаимодействие с внешним миром.

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

Финальные соображения

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

Именно поэтому полагаться безопасность приложения исключительно по причине его работы в chroot-окружении не стоит. Необходимо помнить и о других уровнях обеспечения информационной безопасности системы, например о своевременной установке обновлений безопасности.

Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники