1

How can I get the old root back after chroot(2) given some open file descriptor to it?

In the below program, I first open a file descriptor to /, and then chroot(2) to . and chdir(2) to /.

However, if I then chdir(2) to the old root of fd, Python complains that it cannot print the current working directory getcwd(), since it doesn't exist.

Why can't I change the current working directory to the old root and print it?

This is why we have "pivot_root()" and "chroot()", which can both be used to do what you want to do. You mount the new root somewhere else, and then you chroot (or pivot-root) to it. And THEN you do 'chdir("/")' to move the cwd into the new root too (and only at that point have you "lost" the old root - although you can actually get it back if you have some file descriptor open to it).

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

Program:

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()}') 

Output:

$ 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 Answer 1

1

There are three issues I can see:

  • you're using chdir(2) on a file descriptor. The correct system call should be fchdir(2).

    Although it might be possible python is smart enough to use fchdir() instead.

  • After using fchdir(2) you have to chroot(2) again so you current directory is again inside the "known space" of the current root tree.

  • You can't distinguish the result with getcwd(). In both cases you would get / even if it's not the same /. You could for example display the inode number (assuming it's the same filesystem so the comparison stands) or anything else that can differ.

Here, with above's changes, from a debian bullseye/sid, I chroot inside the root of an LXC buster container. I display the content of /etc/debian_version twice:

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() 

Result:

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

Trivia

It's possible to abuse the "unknown space" to escape a chroot() without having kept back a file descriptor. It's described in this page from 2005:

How to break out of a chroot() jail

Except for two issues in the given code (one has to add #include <stdlib.h> and correct the double quote typos at the fprintf() in line 62), it still works fine on today's Debian (and certainly a few *nixes).

From my understanding, it appears that when being in "unknown space" (ie: situation leading to OP's error when the cwd is outside the current root) one can then move blindly back to the actual root.

7
  • Thank you! Do you know why it’s necessary to chroot(2) again? I mean, do you know why is it necessary for Linux to have the cwd inside the “known space” of the current root for operations like os.getcwd() to succeed? Commented Mar 8, 2021 at 19:18
  • Yes: because when you're outside of the current (ch)root tree, this directory can't be resolved from / Commented Mar 8, 2021 at 19:19
  • 1
    Python should indeed be smart enough to call fchdir(), since Python 3.3.: docs.python.org/3/library/os.html#os.chdir Commented Mar 8, 2021 at 22:19
  • @ilkkachu - help(os.chdir) says that os.chdir() may be given a file descriptor on platforms that support it. It was therefore I used this function. Commented Mar 9, 2021 at 8:25
  • @A.B - Do you have any idea on how to accomplish what Linus describes as "The smallest part, in fact. And you should be aware that root can always get out of a chroot() if he just has enough tools - and the tools aren't even very big. "mknod" + "mount" will do it even in the absence of a way to add binaries, as will /proc access)."? How can mknod+mount be used to to break out of chroot, if the /dev directory is not present? Also, /proc may be used to escape the jail by cd /proc/<pid>/root? Commented Mar 9, 2021 at 9:28

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.