![](https://habrastorage.org/webt/k6/lu/2v/k6lu2vlsdgn2d9qlu-2htsld_yw.png)
В мире победившей контейнеризации и виртуализации об утилите chroot вспоминают лишь брутальные админы суровых физических серверов, а про лежащий в основе системный вызов, кажется, забыли как страшный сон.
Этот простой системный вызов подменяет местонахождение «корня» файловой системы, «заключая» программу в специально созданное ограниченное окружение. Самая распространенная ситуация — восстановление загрузки операционной системы с помощью live-образа. Но при создании chroot о таком применении не задумывались.
Чтобы найти истоки появления chroot в *NIX-подобных операционных системах, нужно пройти немалый путь по истории IT. В этой статье я расскажу про появление chroot и его применение в современном мире. А еще покажу проекты, которые позволяют прикоснуться к операционным системам эпохи, когда Интернета не было.
Предполагается, что читатель имеет как минимум базовое умение работать с операционными системами семейства Linux.
Что ты такое?
Сперва познакомимся с «героем» рассказа. Чаще всего администраторы сталкиваются с утилитой chroot, которая позволяет подменить местонахождение корня для дочерних программ.
Представим ситуацию: перед системным администратором стоит задача восстановить ОС после сбоя. Администратор достает из кармана загрузочную флешку и начинается магия. Провайдеры инфраструктуры для экстренных случаев предоставляют заготовленный образ для восстановления — Rescue. Для выделенных серверов Selectel загрузить образ восстановления можно прямо по сети.
![](https://habrastorage.org/webt/jz/ld/wc/jzldwcvkc_fzjkfqz5xkzgk0pbo.jpeg)
Из Rescue администратор имеет доступ ко всем файлам на диске и может вносить исправления. В случае загрузчика недостаточно просто обновить файлы конфигурации, необходимо применить изменения. Для загрузчика GNU GRUB2, например, следует выполнить утилиту grub2-install.
Возникает проблема: утилита сканирует доступные ядра текущей операционной системы и сохраняет результат в подкаталогах /boot. В результате обновляется загрузчик «спасительной» флешки, а не пострадавшей системы. Нужно «обмануть» утилиту, чтобы она считала корнем накопитель сервера, а не флешку.
На этом моменте в игру вступает chroot. Администратор выполняет команду chroot /mnt, где /mnt — это точка монтирования накопителя с ОС, и происходит чудо: отныне все программы, включая интерпретатор, действуют от лица «неисправной» системы. Теперь grub2-install отработает корректно.
Утилита chroot в современных дистрибутивах Linux является частью проекта GNU Coreutils, а исходный код умещается в один файл. Утилита выполняет следующие действия:
- разбирает аргументы командной строки;
- выполняет системный вызов chroot;
- выполняет переход в другой каталог;
- изменяет пользователя, от имени которого необходимо выполнить действия;
- выполняет заданную команду, а если команды нет, то запускает интерпретатор командной строки по умолчанию.
Утилита основана на одноименном системном вызове, который делает единственную вещь: меняет один из компонентов разрешения имен файлов. Назначение других системных вызовов, связанных с файловыми системами, очевидно: программы хотят уметь работать с файлами и каталогами. Но кому и зачем пришла в голову идея подменять корень?
Чтобы ответить на вопрос, нужно плавно погрузиться в историю. Восстановление исторических событий — сложное дело, так как вместо фактов можно получить мнения. К счастью, у нас история программного обеспечения, а значит, мы можем обратиться к исходным кодам, комментариям и копирайтам.
Linux
Начнем с чего-то привычного широкой аудитории, что не требует дополнительного представления: Linux.
Исходный код ядра Linux доступен на нескольких ресурсах, в том числе на Github. На текущий момент в репозитории более миллиона коммитов, но, вопреки ожиданиям, история репозитория начинается в 2005 году с версии 2.6.12-rc2. В первом коммите Линус Торвальдс отмечает, что не обеспокоен историей и, если потребуется, будет создан «исторический» репозиторий.
commit 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (tag: v2.6.12-rc2)
Author: Linus Torvalds <torvalds@ppc970.osdl.org>
Date: Sat Apr 16 15:20:36 2005 -0700
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Исторический репозиторий был создан командой Linux History group. Он содержит коммиты начиная с версии 0.01. К сожалению, наиболее интересные коммиты в репозитории имеют сбитые даты. Так, тэг 0.10 имеет время 23 ноября 2007 года, а в описании указано 11 ноября 1991 года. Переключаемся на самый ранний коммит, соответствующий версии 0.01, и ищем упоминания chroot.
git checkout fa1ec1000cf9954b8e78216c11b0c3f86336d488
grep -rn chroot
Для наших задач хватит и исторического репозитория, но, если хочется посмотреть на историю ядра Linux от начала и до конца, есть проект по слиянию нескольких репозиториев в один.
В файле fs/open.c находится функция sys_chroot, которая реализует логику системного вызова. Современная реализация обросла макросами и дополнительными проверками, но по сей день находится в том же файле. К сожалению, первая версия ядра Linux сделана одним коммитом, комментариев в коде практически нет, а release notes не содержит таких тонкостей.
Тем не менее, при создании Linux Линус Торвальдс вдохновлялся операционной системой MINIX, которая была создана профессором Эндрю Таненбаумом для обучения своих студентов. Кроме того, в man-странице chroot есть ссылки на System V Release 4 (SVr4), 4.4BSD и спецификацию Single Unix Specification (SUSv2).
Здесь начинается наш путь по UNIX-системам.
UNIX
![](https://habrastorage.org/webt/8-/ue/3i/8-ue3iftbsfkfrexglulitwy7us.png)
Первая версия ядра Linux появилась в 1991 году. Один беглый взгляд на генеалогическое дерево ОС Unix, и становится понятно, что упомянутая в man-странице BSD 4.4 была позже, а System V Release 4 является проприетарной и ее исходный код недоступен. Остается два кандидата: Minix и ранние версии BSD.
Minix
Minix — это Unix-подобная операционная система, разработанная профессором Эндрю Таненбаумом в качестве «иллюстрации» для учебника «Операционные системы: Разработка и реализация». Первая версия Minix увидела свет в 1987 году, а исходные коды распространялась в печатном виде в учебнике.
Технический прогресс не стоял на месте, и «учебная» операционная система развивалась и адаптировалась к современным реалиям. На данный момент актуальная версия Minix — 3.3.0. Исходные коды третьей мажорной версии Minix можно найти с третьим изданием учебника Таненбаума, но теперь на компакт-диске.
Учебник 1987 года найти затруднительно, но, к счастью, существует официальный ресурс с исходными кодами Minix, где можно скачать не только актуальную версию, но и предыдущие. Самая ранняя версия на сайте — 1.1. Возможно, с учебником распространялась версия 1.0, но файлы версии 1.1 датированы 1987 годом — этого нам достаточно.
В исторических исходниках Minix есть неконсистентность. Так, сборка версии 1.3 сделана сторонним человеком, и один образ дискеты имеет битый 512-байтовый сектор, а версия 1.6 распространяется в виде хитрых sh-патчей и требует установленной версии 1.5. Версия 1.5, в свою очередь, поставляется в виде десятка бинарных файлов без инструкции по применению.
Беглый поиск по исходникам разных версий привел к следующим фактам:
- в Minix 1.1 (1987) системного вызова chroot не было;
- исходя из п.1 в Minix 1.0, допустимо предположение, что его тоже не было;
- в Minix 1.7 (1988) имеется простая реализация chroot;
- в Minix 3.3.0 в man-файлах, посвященных chroot, имеется копирайт, указывающий на Университет Калифорнии, Беркли (Berkeley), и 1983 год.
Так как Linux появился позднее (1991) и Линус создал Linux совместимым с Minix, то, вероятно, системный вызов «переехал» из одной системы в другую.
Что касается копирайта, то в 1983 году существовал проект BSD (Berkeley Software Distribution), созданный для обмена опытом между учебными заведениями. У проекта была операционная система BSD-UNIX, известная сейчас как BSD.
В 1983 году вышли 2.9BSD и 4.2BSD.
BSD-UNIX
История BSD началась, когда один из профессоров университета Калифорнии, Боб Фабри (Bob Fabry), входящий в программный комитет симпозиума по принципам операционных систем (Symposium on Operating Systems Principles), заинтересовался впервые представленной операционной системой Unix.
Для запуска новой операционной системы был приобретен компьютер PDP-11/45, но из соображений экономии доступ к нему был также у математиков и статистиков. Разделение доступа привело к тому, что Unix работал всего 8 часов в сутки.
В 1975 году Кен Томпсон (Ken Thompson) из Bell Labs взял длительный отпуск и приехал в Беркли в качестве приглашенного профессора. Он помог установить Unix v6 и начал работу над реализацией Pascal, а студенты занимались другими компонентами, в том числе улучшенным текстовым редактором — ex. Другие университеты заинтересовались разработками в Беркли и в 1977 году появилась идея сделать первую «сборку» — 1BSD. Она увидела свет 9 марта 1978 года.
Фактически, 1BSD не является «клоном» Unix, это и есть Unix, но распространяемый с расширениями. Спустя несколько мажорных версий BSD столкнулась с юридическими трудностями, так как Unix — проприетарная система. К версии 2.79 BSD был полностью переписан, чтобы не использовать код Unix. Это значительно замедлило разработку новых версий, а вскоре привело к закрытию проекта.
После завершения судебных тяжб исходный код BSD-UNIX доступен по лицензии BSD, которая накладывает минимальные ограничения на использование кода. Это позволило появиться множеству проектов, основанных на BSD.
Именно в BSD впервые появилась такая абстракция, как сокет Беркли (Berkeley Socket). Сокеты с этим интерфейсом до сих пор используются в *NIX системах, и даже в Microsoft Windows есть частички кода BSD.
Исходный код BSD открыт, но во время его создания не было привычных систем контроля версий, поэтому искать нужно у энтузиастов, которые чтут историю. Существует общество The Unix Heritage Society, которое предоставляет доступ к «древнему UNIX» (Ancient UNIX) и тематическим wiki-страницам.
У этого общества есть географически распределенные «зеркала», в том числе в России. Зеркала предоставляют исходные коды в виде архивов, что упрощает поиск. Заходим в каталог Distributions и выбираем подкаталог UCB, что означает University of California, Berkeley.
Поиск по нескольким архивам разных версий привел к следующим выводам:
- в 1BSD (1978) системный вызов chroot не упоминается;
- в 2BSD (1979) также нет упоминаний;
- в «переписанной» 2.79BSD (приблизительно 1983 год) присутствуют два бинарных файла с расширением v7, которые содержат список системных вызовов, в том числе chroot. Упоминаний в man-страницах или исходниках отсутствуют.
- в 2.9BSD (1983) chroot уже присутствует, но описан в man-странице другого системного вызова, chdir.
Мне удалось связаться с архитектором из Computer Systems Research Group, который занимался 2BSD и 4.4BSD, и задать ему несколько вопросов. Как оказалось, в 2.9BSD chroot появился как порт из 4BSD. Тем не менее, в 1980-х, когда архитектор принялся за работу над BSD, chroot уже являлся частью системы.
Как отмечалось ранее, 1BSD была основана на Unix v6, а значит, стоит заглянуть к «прародителю» всех *NIX-подобных систем.
UNIX
Unix зародился в Bell Labs, подразделении AT&T, в конце 1960-х. Авторство названия приписывают Брайану Кернигану (Brian Kernighan), который придумал игру слов Unics — Uniplexed Information and Computing Service — c отсылкой на многозадачную систему Multics — Multiplexed Information and Computer Services.
Первая версия (Unix v1) была попыткой воссоздать Multics на менее мощном железе — PDP-7. Эта ОС написана на ассемблере и не имеет встроенного компилятора высокоуровневого языка. К 1973 году была выпущена третья редакция Unix, которая содержала ранний компилятор языка Си, а позже в том же году вышла четвертая редакция с ядром на языке Си.
В Unix v1 «начало» отсчета системного времени «прибили» к 1 января 1970 года 00:00:00 UTC. Это описание времени прижилось и используется до сих пор во всех NIX-подобных операционных системах.
С 1974 года Unix стал распространяться по учебным заведениям, а спустя год появились сторонние модификации Unix. В 1975 году вышла шестая редакция Unix, которая стала основой для BSD. Исходники Unix доступны в каталоге Research хранилища The Unix Heritage Society.
В отличие от BSD, старый Unix распространяется чаще в виде «образов» магнитной ленты, чем в архивах, поэтому быстро пройтись по исходникам не получится. На этом этапе стоит начать знакомство с проектом SimH (History Simulator). Многие исторические версии ПО были проприетарными, но после множества юридических процессов исходный код был открыт для изучения.
На главной странице проекта можно скачать сам симулятор и программное обеспечение к нему. Среди подготовленных образов есть и три версии Unix: пятая, шестая и седьмая редакции. У проекта также есть документация, в частности, Sample Software Documentation позволит запустить подготовленный образ желаемой операционной системы.
Скачиваем исходники SimH и собираем. Некоторым симуляторам нужны дополнительные библиотеки, например, для поддержки сетевых операций. Собираем симулятор.
# Инициирует сборку всех доступных симуляторов, займет некоторое время
make
# Если хочется только один симулятор
make pdp11
В каталоге BIN находятся скомпилированные симуляторы. Для запуска Unix v6 нужно выполнить следующие действия:
BIN/pdp11
set cpu u18
att rk0 unix0_v6_rk.dsk
att rk1 unix1_v6_rk.dsk
att rk2 unix2_v6_rk.dsk
att rk3 unix3_v6_rk.dsk
boot rk0
unix
$ BIN/pdp11
PDP-11 simulator V4.0-0 Current git commit id: 9f5e40e2
sim> set cpu u18
Disabling XQ
sim> att rk0 unix0_v6_rk.dsk
%SIM-INFO: RK0: Amount of data in use in disk container 'unix0_v6_rk.dsk' cannot be determined, skipping autosizing
sim> att rk1 unix1_v6_rk.dsk
%SIM-INFO: RK1: Amount of data in use in disk container 'unix1_v6_rk.dsk' cannot be determined, skipping autosizing
sim> att rk2 unix2_v6_rk.dsk
%SIM-INFO: RK2: Amount of data in use in disk container 'unix2_v6_rk.dsk' cannot be determined, skipping autosizing
sim> att rk3 unix3_v6_rk.dsk
%SIM-INFO: RK3: Amount of data in use in disk container 'unix3_v6_rk.dsk' cannot be determined, skipping autosizing
sim> boot rk0
@unix
login:
Для запуска Unix v7 нужно выполнить следующие команды:
BIN/pdp11
set cpu u18
set rl0 RL02
att rl0 unix_v7_rl.dsk
boot rl0
boot
rl(0,0)rl2unix
^D
$ BIN/pdp11
PDP-11 simulator V4.0-0 Current git commit id: 9f5e40e2
sim> set cpu u18
Disabling XQ
sim> set rl0 RL02
sim> att rl0 unix_v7_rl.dsk
%SIM-INFO: RL0: Amount of data in use in disk container 'unix_v7_rl.dsk' cannot be determined, skipping autosizing
sim> boot rl0
@boot
New Boot, known devices are hp ht rk rl rp tm vt
: rl(0,0)rl2unix
mem = 177856
# Restricted rights: Use, duplication, or disclosure
is subject to restrictions stated in your contract with
Western Electric Company, Inc.
Thu Sep 22 07:56:23 EDT 1988
login:
Unix v6 имеет суперпользователя (root) без пароля, у Unix v7 есть пользователь dmr без пароля, а у суперпользователя пароль совпадает с логином.
Первое открытие командного интерпретатора Unix создает впечатление, что старые системы суровы и не прощают ошибок. Так, попытка стереть символ клавишей backspace приведет к полному сбросу строки. Для того, чтобы стереть последний символ, нужно отправить символ #. При этом вместо стирания отобразится символ решетки.
# echo Hello, World#####Habr!
Hello, Habr!
Дальнейшее использование Unix только подтверждает первое впечатление. Единственный интерактивный текстовый редактор, ed, по современным меркам имеет плохой UX. Редактор не задает лишних вопросов для подтверждения действия, а если и задает, то весь вопрос умещается в один символ — вопросительный знак.
У Unix v7 есть команда man, а у Unix v6 ее нет. Поэтому проще всего воспользоваться компилятором языка С и проверить наличие системного вызова chroot. Если его нет, то компилятор нам об этом скажет. К слову, первый стандарт языка С, называемый ANSI C, принят в 1989 году, а «на дворе» нашей операционной системы 1979 год.
Симулятор PDP-11 запускается с системным временем 22 сентября 1988 года. Внутреннее представление времени хранится в секундах в 32-битном типе данных. Таким образом, Unix v7 способен корректно обрабатывать дату вплоть до 7 февраля 2106 года.
Сделаем простую программу, которая вызовет chroot. По умолчанию компилятор производит компиляцию и линковку, что позволяет использовать функции без включения заголовочных файлов с определением функций. Хотя данная программа написана в совсем старом стиле, современные компиляторы способны ее «переварить».
main(argc, argv)
int argc;
char* argv[];
{
chroot("/tmp");
}
В Unix v7 сработало, а вот компилятор Unix v6 выводит ошибку:
# cc test.c
Undefined:
_chroot
Получается, что впервые chroot появился в Unix v7, примерно в 1979 году. В документации jail(8) к 4.4BSD есть сноска, которая гласит, что 18 марта 1982 года, примерно за полтора года до релиза 4.2BSD, Билл Джой (Bill Joy) добавил системный вызов chroot в BSD для удобства тестирования новых сборок.
Билл Джой — основатель Computer Systems Research Group, которая занималась ранними версиями Unix и BSD. Вероятно, что в Unix v7 системный вызов появился также благодаря Биллу с той же целью: удобство тестирования новых версий ОС.
Системный вызов появился достаточно давно. Рассмотрим, какие ему нашли применения.
Применения
Первое применение освещалось в начале статьи: системный администратор может «переключиться» в каталог с другой системой с целью восстановления. Это похоже на оригинальное применение chroot при тестировании новых сборок ОС, когда необходимо изолироваться от файлов текущей операционной системы.
Одно из возможных применений — ограничение программы в заданном каталоге. Так, популярный ftp-сервер VSFTPD позволяет «заточить» пользователя в его домашнем каталоге.
Более редкий случай — отслеживание злых намерений других пользователей интернета. Уильям Чесвик (William Cheswick) использовал chroot для создания «ловушки», в которую попался хакер.
Хотя системный вызов chroot влияет только на уровне файловых систем, он стал толчком к разработке утилиты jail в FreeBSD. Она ограничивала доступ не только к файловой системе, но также к сети, общей памяти и видимым переменным ядра. Таким образом, софт в «тюрьме» никак не мог навредить основной системе или другим программам в аналогичных клетках.
Вскоре концепт «тюрьмы» переехал в Linux, затем появилась реализация Linux Namespaces, а потом появились привычные контейнеры.
В современной документации к системному вызову отмечено, что его не следует использовать для изоляции программ и других решений, связанных с изоляцией программ.
Безопасность
Системный вызов chroot меняет один компонент системы разрешения имен на файловых системах. Рассмотрим абсолютный путь до абстрактного файла habr.png:
/home/web/html/static/habr.png
Операционная система разбирает путь по компонентам. Путь абсолютный, это значит, что он начинается с символа /. В обычных условиях магии не происходит. Но если сделать chroot(“/home/web/html”), то абсолютный путь до файла будет выглядеть так:
/static/habr.png
Если упростить, то при разрешении такого пути внутри chroot корень «подменится» на /home/web/html и файл будет найден. Однако это приводит нас к первой проблеме в безопасности, так как программы внутри chroot не могут знать о подмене окружения.
Рассмотрим гипотетическую ситуацию, что администратор сервера решил зачем-то «ограничить» своих пользователей и настроил делать chroot в домашний каталог пользователя при логине. Умный пользователь может принести с собой файл /etc/passwd и исполняемый файл su со своего личного компьютера и разместить их в домашнем каталоге.
Файл /etc/password использован в качестве простейшего примера. В современных системах используется /etc/shadow и более сложный механизм.
Пользователь находится в своем домашнем каталоге, где имеет максимальные права, поэтому он может создать любую структуру каталогов. Пользователь создает собственный файл /etc/password, который содержит хэш пароля от суперпользователя. Далее с помощью утилиты su, которая проверяет пароль с помощью ранее указанного файла, пользователь получает права суперпользователя. Именно из-за этого «фокуса» с повышением прав системный вызов chroot может выполнять только суперпользователь.
В 1999 году впервые была затронута тема побега из тюрьмы (jailbreak). Некорректная обработка вложенных chroot приводит к возможности выбраться из chroot в «оригинальный» корень. Несмотря на то, что эта
Уязвимость основана на том, что chroot подменяет корень, но не изменяет текущий каталог. Так, если сделать chroot("/home/web/static/"), находясь в каталоге /home, то текущий рабочий каталог останется вне нового корня. В этом случае обработка ядром относительных путей не задействует новый корень и есть возможность добраться до корня системы. Общая последовательность выглядит так:
- получить права суперпользователя;
- создать каталог tmp, но не переходить в него;
- выполнить chroot(«tmp»);
- выполнить переход в каталог «..» максимально возможное количество раз;
- выполнить chroot(".");
- готово.
Рассмотрим в виде таблицы эти действия.
![](https://habrastorage.org/webt/vb/ny/xw/vbnyxwpam8x_tr0shd_w7xm1q34.png)
Так как chroot не меняет каталога, то после выполнения этого системного вызова попытка получить текущий рабочий каталог будет заканчиваться ошибкой «нет такого каталога», но это не помешает выполнить переход в родительский каталог. Излишнее количество попыток перейти в родительский каталог не помеха: корень сам себе родитель.
Тем не менее, для эксплуатации этой уязвимости нужны права суперпользователя, чтобы сделать вложенный chroot, что значительно усложняет эксплуатацию.
Заключение
Мы узнали, что chroot имеет большую историю и множество применений, но изначально появился во благо удобства программиста. История Unix — сложное и запутанное переплетение гениальных инженерных мыслей, коммерции и энтузиастов, продвигающих принцип открытости.
Несмотря на множество негативных факторов, у нас есть возможность прикоснуться к истокам семейства операционных систем UNIX и буквально «потрогать» историю на своих современных компьютерах.
Комментарии (7)
victor_1212
14.03.2022 23:11+3> Именно в BSD впервые появилась такая абстракция, как сокет Беркли (Berkeley Socket).
приятная статья, спасибо Владимир,
возможно история BSD будет более понятной, если уточнить, что разработка BSD в том числе координировалась darpa, дело в том, что была нужна os для рабочих станций на которых предполагалось разрабатывать VLSI, примерно как описывается в известной книге Mead и Conway, предполагалось также, что станции будут работать в сети (arpanet) связанной с первыми фабриками, именно для этого в Unix добавили sockets, начиная по памяти BSD 4.3 1983, все это было в рамках одной большой программы darpa, включая поддержку Sun ответственную за станции, интеграцию sw, разработку cad, обучение студентов и пр. в результате стало возможным массовое применение VLSI что совершило типа революции в технологии, естественно не только для компьютеров и сетей, см. короткую выдержку из отчета того времени:
рабочая станция Sun 1, HD17H 17-inch video display 1024×800, 10 MHz Motorola 68000,
Ethernet 3 Mbit/s , позже 10 Mbit/s
kvazimoda24
15.03.2022 09:19Странно, что в статье несколько раз упоминается о "самом распространённом способе использования" утилиты chroot, и ни слова не сказано про initrd, по крайней мере, в Дебиан. Там переключение на реальный корень осуществляется именно с помощью chroot.
Thomas_Hanniball
15.03.2022 17:36Это статья перевод или кто-то в Selectel, действительно, так сильно заморочился вопросом появления chroot, что перелопатил столько исходников самостоятельно?
Firemoon Автор
15.03.2022 17:58+3Это кто-то в Selectel, действительно, так сильно заморочился.
Спойлер: это я :)
Thomas_Hanniball
15.03.2022 23:17+1Супер! Статья очень глубокая и достойна того, что быть переведённой на английский язык, чтобы и там аудитория могла с ней ознакомиться. Как вариант, можно перевести на английский язык и опубликовать здесь на хабре либо на каком-нибудь Medium. С меня лайк и плюсик в карму за текущую статью. Работа проделана колоссальная!
Eugeeny
Не может, чтобы получить права, владельцем
su
должен бытьroot
, аchown()
обычным пользователям недоступен.