Как я могу вернуть старый корень после того, как chroot(2) дал ему некоторый дескриптор открытого файла?

В приведенной ниже программе я сначала открываю дескриптор файла для /, затем chroot(2) для . и chdir(2) для /.

Однако, если я затем chdir(2) в старый корень fd, Python жалуется, что не может распечатать текущий рабочий каталог getcwd(), так как он не существует.

Почему я не могу изменить текущий рабочий каталог на старый корень и распечатать его?

Вот почему у нас есть «pivot_root()» и «chroot()», которые можно использовать для того, что вы хотите сделать. Вы монтируете новый корень в другом месте, а затем выполняете chroot (или поворотный корень) к нему. И ТОГДА вы делаете 'chdir("/")', чтобы также переместить cwd в новый корень (и только в этот момент вы «потеряли» старый корень, хотя на самом деле вы можете вернуть его, если у вас открыт какой-либо дескриптор файла к нему).

https://yarchive.net/comp/linux/pivot_root.html

< Сильный > Программа :

import os

print(f'cwd: {os.getcwd()}')

fd = os.open('/', os.R_OK, os.X_OK)

os.chroot('.')
os.chdir('/')

print(f'cwd: {os.getcwd()}')

os.chdir(fd)

print(f'cwd: {os.getcwd()}')

Вывод :

$ sudo python3 chdir_fd.py
cwd: /home/admin/projects/linux-pwn
cwd: /
Traceback (most recent call last):
  File "chdir_fd.py", line 14, in <module>
FileNotFoundError: [Errno 2] No such file or directory
1
Shuzheng 8 Мар 2021 в 18:31

1 ответ

Лучший ответ

Я вижу три проблемы:

  • вы используете chdir(2) в файловом дескрипторе. Правильный системный вызов должен быть fchdir(2).

    Хотя вполне возможно, что Python достаточно умен, чтобы использовать вместо этого fchdir().

  • После использования fchdir(2) вам нужно снова chroot(2), чтобы ваш текущий каталог снова оказался внутри "известного пространства" текущего корневого дерева.

  • Вы не можете отличить результат с getcwd(). В обоих случаях вы получите /, даже если это не одно и то же /. Например, вы можете отобразить номер инода (при условии, что это одна и та же файловая система, поэтому сравнение остается в силе) или что-то еще, что может отличаться.

Здесь, с указанными выше изменениями, из debian bullseye/sid я выполняю chroot внутри корня контейнера LXC buster. Я показываю содержимое /etc/debian_version дважды:

import os

print(f'cwd: {os.getcwd()}')

fd = os.open('/', os.R_OK, os.X_OK)

os.chroot('.')
os.chdir('/')

print(f'cwd: {os.getcwd()}')

debfd=open("/etc/debian_version","r")
print(debfd.read())
debfd.close()

os.fchdir(fd)
os.chroot('.')

print(f'cwd: {os.getcwd()}')

debfd=open("/etc/debian_version","r")
print(debfd.read())
debfd.close()

Результат:

root@glasswalker:/var/lib/lxc/buster-amd64/rootfs# /tmp/chrootback.py 
cwd: /var/lib/lxc/buster-amd64/rootfs
cwd: /
10.8

cwd: /
bullseye/sid

Мелочи

Можно злоупотреблять «неизвестным пространством», чтобы избежать chroot() без сохранения дескриптора файла. Это описано на этой странице с 2005 года:

Как вырваться из тюрьмы chroot()

За исключением двух проблем в данном коде (нужно добавить #include <stdlib.h> и исправить опечатки в двойных кавычках в fprintf() в строке 62), он по-прежнему отлично работает на сегодняшнем Debian (и, конечно, на некоторых *nix). ).

Насколько я понимаю, кажется, что, находясь в «неизвестном пространстве» (то есть: ситуация, приводящая к ошибке OP, когда cwd находится за пределами текущего root), можно двигаться вслепую вернуться к фактическому корню.

1
A.B 8 Мар 2021 в 20:03
Спасибо! Вы знаете, почему необходимо снова chroot(2)? Я имею в виду, знаете ли вы, почему для Linux необходимо иметь cwd внутри «известного пространства» текущего корня для успешного выполнения таких операций, как os.getcwd()?
 – 
Shuzheng
8 Мар 2021 в 22:18
Да: потому что, когда вы находитесь за пределами текущего (ch) корневого дерева, этот каталог не может быть разрешен из /
 – 
A.B
8 Мар 2021 в 22:19
1
Python действительно должен быть достаточно умен, чтобы вызывать fchdir(), начиная с Python 3.3.: docs.python.org/3/library/os.html#os.chdir
 – 
ilkkachu
9 Мар 2021 в 01:19
- help(os.chdir) говорит, что os.chdir() может быть присвоен файловый дескриптор на платформах, которые его поддерживают. Именно поэтому я использовал эту функцию.
 – 
Shuzheng
9 Мар 2021 в 11:25
@AB - Есть ли у вас какие-либо идеи о том, как выполнить то, что Линус описывает как «наименьшую часть, на самом деле. И вы должны знать, что root всегда может выйти из chroot(), если у него достаточно инструментов - и инструменты даже не очень большие. "mknod" + "mount" сделают это даже при отсутствии возможности добавить двоичные файлы, как и доступ /proc)."? Как можно использовать mknod+mount для выхода из chroot, если каталог /dev отсутствует? Кроме того, /proc может быть использован cd /proc/<pid>/root для побега из тюрьмы?
 – 
Shuzheng
9 Мар 2021 в 12:28