Without the --mount-proc option, calling `systemctl daemon-reload` in the chroot prints out "Running in chroot, ignoring command 'daemon-reload'" and then exits with status 0.
With the --mount-proc option, calling `systemctl daemon-reload` in the chroot fails with "Failed to connect to bus: No data available" and fails with status 100.
To determine if we are running in a chroot, systemd calls fstatat(2) on / and then fstatat(2) on /proc/1/root. It then compares the resulting structures, looking specially at the inode number, inode type and backing device. If anything looks different, systemd assumes we are in a chroot.
Using stat(1), we can observe what happens:
Without the --mount-proc option, the backing device (i.e. "Device") is different, therefore systemd assumes we are in a chroot:
Explanation
-----------
* When we run a command in a ChrootableTarget, we have:
** /proc bind mounted to /target/proc
** /sys bind mounted to /target/sys
** /run bind mounted to /target/run
** /dev bind mounted to /target/dev
* When we run, `unshare --pid --fork chroot /target apt-get ...`
** the content of /target/proc is inherited from outside the chroot, because of the bind-mount.
** /target/proc/1 corresponds to the process with PID 1 in the "parent" PID namespace (which is the systemd/init process)
** /target/proc/1/root is therefore the "root" of the systemd process, which is outside of the chroot
** in other words /target/proc/1/root == /
** systemd effectively compares /target/proc/1/root with /target and since they are different, it assumes we are in a chroot.
* When we run, `unshare --pid --fork --mount-proc=/target chroot /target apt-get ...`
** the content of /target/proc is fresh (the bind-mount is masked)
** /target/proc/1 corresponds to the process with PID 1 in the "child" PID namespace
** /target/proc/1/root is therefore the "root" of the chroot
** in other words /target/proc/1/root == /target
** systemd effectively compares /target/proc/1/root with /target and since they are identical, it assumes we are /not/ in a chroot.
Without the --mount-proc option, calling `systemctl daemon-reload` in the chroot prints out "Running in chroot, ignoring command 'daemon-reload'" and then exits with status 0.
With the --mount-proc option, calling `systemctl daemon-reload` in the chroot fails with "Failed to connect to bus: No data available" and fails with status 100.
To determine if we are running in a chroot, systemd calls fstatat(2) on / and then fstatat(2) on /proc/1/root. It then compares the resulting structures, looking specially at the inode number, inode type and backing device. If anything looks different, systemd assumes we are in a chroot.
Using stat(1), we can observe what happens:
Without the --mount-proc option, the backing device (i.e. "Device") is different, therefore systemd assumes we are in a chroot:
# stat -L / /proc/1/root
File: /
Size: 4096 Blocks: 8 IO Block: 4096 directory
=> Device: 252,0 Inode: 2 Links: 20
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2024-03-11 08:01:50.538756312 +0000
Modify: 2024-03-11 08:01:49.398777854 +0000
Change: 2024-03-11 08:01:49.398777854 +0000
Birth: 2024-03-11 08:00:36.000000000 +0000
File: /proc/1/root
Size: 260 Blocks: 0 IO Block: 4096 directory
=> Device: 0,28 Inode: 2 Links: 1
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2024-03-11 08:06:22.017527026 +0000
Modify: 2024-03-11 08:00:26.458886048 +0000
Change: 2024-03-11 08:00:26.458886048 +0000
Birth: 2024-03-11 07:58:30.876000000 +0000
But with the --mount-proc option, the structures look identical, therefore systemd thinks we are not running in a chroot:
File: /
Size: 4096 Blocks: 8 IO Block: 4096 directory
=> Device: 252,0 Inode: 2 Links: 20
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2024-03-11 08:01:50.538756312 +0000
Modify: 2024-03-11 08:01:49.398777854 +0000
Change: 2024-03-11 08:01:49.398777854 +0000
Birth: 2024-03-11 08:00:36.000000000 +0000
File: /proc/1/root
Size: 4096 Blocks: 8 IO Block: 4096 directory
=> Device: 252,0 Inode: 2 Links: 20
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2024-03-11 08:01:50.538756312 +0000
Modify: 2024-03-11 08:01:49.398777854 +0000
Change: 2024-03-11 08:01:49.398777854 +0000
Birth: 2024-03-11 08:00:36.000000000 +0000
Explanation
-----------
* When we run a command in a ChrootableTarget, we have:
** /proc bind mounted to /target/proc
** /sys bind mounted to /target/sys
** /run bind mounted to /target/run
** /dev bind mounted to /target/dev
* When we run, `unshare --pid --fork chroot /target apt-get ...`
** the content of /target/proc is inherited from outside the chroot, because of the bind-mount.
** /target/proc/1 corresponds to the process with PID 1 in the "parent" PID namespace (which is the systemd/init process)
** /target/proc/1/root is therefore the "root" of the systemd process, which is outside of the chroot
** in other words /target/proc/1/root == /
** systemd effectively compares /target/proc/1/root with /target and since they are different, it assumes we are in a chroot.
* When we run, `unshare --pid --fork --mount- proc=/target chroot /target apt-get ...`
** the content of /target/proc is fresh (the bind-mount is masked)
** /target/proc/1 corresponds to the process with PID 1 in the "child" PID namespace
** /target/proc/1/root is therefore the "root" of the chroot
** in other words /target/proc/1/root == /target
** systemd effectively compares /target/proc/1/root with /target and since they are identical, it assumes we are /not/ in a chroot.