Edit: edited thanks to @timothy-baldwin.
Mounting new_root
over /
changes the root directory of the mount namespace, chrooting without overmounting /
will cause the system to be in a chroot
environment (where the root directory does not match the root directory of the mount namespace).
This will cause some problems, for example:
1. Creating user namespaces is not allowed when inside chroot.
According to man 2 unshare
, unshare
ing user namespace will fail with EPERM
when in chroot environment.
EPERM (since Linux 3.9)
CLONE_NEWUSER was specified in flags and the caller is in a chroot environment
(i.e., the caller's root directory does not match the root directory of the
mount namespace in which it resides).
$ unshare -U
unshare: unshare failed: Operation not permitted
2. Entering a mount namespace will set the root directory to the namespace's root directory
Entering a mount namespace sets the root directory of the process to the root directory of the mount namespace, so doing setns
to our mount namespace will set our root directory to the rootfs directory.
$ nsenter -m/proc/self/ns/mnt /bin/sh
$ ls -ld /new_root
new_root
I can see new_root directory which is outside my chroot.
Mounting over /
does not really prevent escaping the chroot
The root user can umount
this directory, re-entering its mount namespace (setns
) and see the rootfs:
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
int main() {
int ns = open("/proc/self/ns/mnt", O_RDONLY);
if (ns == -1) {
perror("open");
goto out;
}
if (umount2("/", MNT_DETACH)) {
perror("umount2");
goto out;
}
if (setns(ns, CLONE_NEWNS)) {
perror("setns");
goto out;
}
char *a[] = { "/bin/sh", NULL };
char *e[] = { NULL };
execve(a[0], a, e);
perror("execve");
out:
return 1;
}
$ gcc -o main main.c
$ unshare -m ./main
/ # ls -d new_root
new_root
/ # mount -t proc proc /proc
/ # cat /proc/mounts
none / rootfs rw 0 0
proc /proc proc rw,relatime 0 0
Mounting new_root
over /
is necessary in order to prevent chroot escapes.
Created a minimal initramfs and replaces switch_root
binary with this shell script to get a shell:
#!/bin/sh
exec /bin/sh
Also changed /bin/sh
inside the initramfs to a statically linked busybox
.
Compiled the following code and linked it statically:
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open(".", O_RDONLY | O_CLOEXEC);
if (fd < 0) {
perror("open");
goto out0;
}
if (chroot("tmp")) {
perror("chroot");
goto out1;
}
if (fchdir(fd)) {
perror("fchdir");
goto out1;
}
if (chdir("..")) {
perror("chdir");
goto out1;
}
char *const argvp[] = { "sh", NULL };
char *const envp[] = { NULL };
execve("bin/sh", argvp, envp);
perror("execve");
out1:
close(fd);
out0:
return 1;
}
Places in my real root filesystem's root directory as /escape
.
Rebooted and got a shell just before the switch_root
takes place.
Without overmounting root
$ mount --move proc new_root/proc
$ mount --move dev new_root/dev
$ mount --move sys new_root/sys
$ mount --move run new_root/run
$ exec chroot new_root
$ ./escape
$ ls -d new_root
new_root
I escaped the chroot.
With overmounting root
$ mount --move proc new_root/proc
$ mount --move dev new_root/dev
$ mount --move sys new_root/sys
$ mount --move run new_root/run
$ cd new_root
$ mount --move . /
$ exec chroot .
$ ./escape
$ ls -d new_root
ls: cannot access 'new_root': No such file or directory
I cannot escape the chroot.