2013-12-18 Итак, как же изнасиловать корневой раздел без перезагрузки
(Новая страница: «Итак, задача - не перезагружаясь, перейти в такой режим, в котором ваш Linux не будет использ…») |
(нет различий)
|
Версия 02:02, 18 декабря 2013
Итак, задача - не перезагружаясь, перейти в такой режим, в котором ваш Linux не будет использовать диски совсем. Чтобы, например, иметь возможность с ними что-то поделать.
Главная загвоздка - это init, первый (PID=1) и неубиваемый процесс в системе, запущенный с корневого раздела. Разделы, кроме корневого, отмонтировать обычно довольно легко - достаточно убить все процессы в системе. Однако init убить нельзя, большую часть сигналов, в том числе SIGKILL, он игнорирует (PID'у 1 можно игнорировать SIGKILL), а если у вас и получится его убить - ядро запаникует и остановит работу системы.
Так что init нужно, не выключая, "вытащить" в другой корневой раздел. Способ это сделать, по сути, один - подменить запущенный образом exec()ом. Однако как заставить init сделать exec() того, чего нам надо? А вот так - все init'ы (даже несчастный systemd) умеют перезапускать себя. И хотя это сделано вовсе для другой цели, а именно - для применения обновлений в самом init или системных библиотеках (libc?) без перезапуска системы - это вполне можно заюзать в наших целях... достаточно поверх /sbin/init смонтировать свой файл --bind'ом. После чего команда "init U" перезапустит вместо init'а наш файл. Только сам инит надо куда-то скопировать, типа cp /sbin/init /sbin/init1, мы ж его только что перемонтировали.
А дальше нужно переключать корень. Это, на самом деле, не такое уж и сложное действие - если нет задачи сделать корнем обратно rootfs (это то, куда initramfs распаковывается), то для переключения корня достаточно pivot_root - это такая хитрая фича, которая подменяет корень другой точкой монтирования, а текущий / помещает в подпапку нового, причём (!) насколько я понял, это единственный системный вызов, нагло подменяющий корень всем процессам в системе! В коде ядра прямо так и написано - foreach (по всем задачам) { если корень == предыдущему, подменить на новый }.
Никаким другим образом подменить корень другому процессу, видимо, нельзя. pivot_root раньше использовался initrd для переключения в "реальный" корень - рамдиск при этом попадал в подпапку корня и потом отмонтировался init'ом, запускаемым exec()ом на месте "старого". Но сейчас это уже не так, initrd теперь называется initramfs, и работает из специальной НЕотмонтируемой и НЕперемещаемой файловой системы "rootfs", которая, по сути, представляет собой просто tmpfs (живущий в памяти), но по-другому названный, монтируемый в / автоматически при старте системы, и недоступный для монтирования потом. В него распаковывается содержимое initramfs (gzip -d | cpio -i, только внутри ядра), в качестве PID 1 оттуда запускается /init, а из-за того, что rootfs НЕотмонтируемый и НЕперемещаемый (так сделано тупо для удобства программирования) отличается метод переключения в новый корень - с помощью утилиты run-init (в терминах klibc) или switch_root (в терминах busybox) initramfs удаляет всё содержимое rootfs, потом монтирует (на самом деле перемещает, mount --move) новый корень прямо поверх старого, а потом делает в него chroot и выполняет там /sbin/init.
Именно поэтому на свежих ядрах в /proc/mounts корень (/) смонтирован всегда дважды. В системе не остаётся процессов, видящих старый пустой rootfs, смонтировать его куда-то ещё раз невозможно (есть явный запрет его повторного монтирования в коде ядра), поэтому он висит и практически не отсвечивает. Пустой tmpfs, как говорят, памяти почти не занимает, и оверхед нестрашный.
Однако, если после завершения работы initramfs оставить работающий процесс, запущенный из старого корня - возможность туда попасть у вас сразу же появляется, достаточно сделать cd (а то и chroot) /proc/PID/root (где PID - это PID того самого процесса). Если же это dropbear - вы вообще можете в него зайти путём ssh. Это, кстати, да - ещё одно откровение: разные процессы в Linux могут видеть файловую систему по-разному, причём при монтировании нескольких файловых систем поверх одной точки - даже в рамках одного namespace'а! (в разных namespace'ах изоляция точек монтирования вообще полная, это часть функционала контейнеров)
И если выйти в "старый корень" (rootfs), получается очень забавно - новый корень будет виден в "/..", либо "/<любая_папка>/..". То есть, "ls /.." покажет вам содержимое нового корня. Но при этом cd в него таким образом сделать будет нельзя - по "cd /.." вы останетесь в старом корне (потому что, видимо, он уже "открыт"). Зато /.. можно переместить обратно в подпапку rootfs путём команды "mount --move /.. /root".
Очевидно, чтобы что-то делать с системой в таком режиме, нужен какой-то легковесный рамдиск с базовыми утилитами - обычно с busybox'ом, утилитами для работы с ФС (разные mkfs/fsck), и dropbear'ом, если нужен лёгкий ssh сервер.