Last active
May 18, 2026 19:18
-
-
Save mid-kid/9293f4f0617052b9c3aa45422fb89f90 to your computer and use it in GitHub Desktop.
Create and enter a chroot without root permissions, using unshare + pivot_root
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S unshare -fpmr --map-auto sh | |
| # vim:ft=sh | |
| set -eu | |
| # Create and enter a chroot, without root permissions, using only programs | |
| # available in coreutils and util-linux. | |
| # In order to make sure UIDs and GIDs are mapped correctly, the rootfs has to | |
| # be extracted from within an unshare environment. Executing this script | |
| # without parameters will aid with this. | |
| # | |
| # Setting up a gentoo chroot: | |
| # $ wget https://distfiles.gentoo.org/releases/amd64/autobuilds/XXXXXXXXXXXXXXXZ/stage3-amd64-openrc-XXXXXXXXXXXXXXXZ.tar.xz | |
| # $ SHELL=bash chrootless | |
| # $ mkdir -p rootfs | |
| # $ tar xpvf stage3-*.tar.xz --xattrs-include='*.*' --numeric-owner -C rootfs | |
| # $ exit | |
| # $ chrootless rootfs | |
| # $ emerge-webrsync | |
| # $ emerge -auDU @world | |
| # Unshare without chroot | |
| test -n "${1:-}" || exec "${SHELL:-bash}" | |
| umask 022 | |
| cd "$1"; shift | |
| # Common mounts as suggested by the gentoo handbook | |
| mount --rbind /dev dev | |
| mount --make-rslave dev | |
| mount --rbind /sys sys | |
| mount --make-rslave sys | |
| mount -t proc proc proc | |
| mount --make-slave proc | |
| # Not bind-mounting /run, we cannot control the host's init | |
| # Allow the processes inside the chroot to create new PTYs | |
| # required for emerge to work | |
| mount -t devpts devpts dev/pts | |
| # Many container technologies will create a tmpfs on /dev, and only bind-mount | |
| # some of the nodes. This has the side-effect of solving some permission | |
| # issues. In the interest of keeping the script simple, we --rbind it, but | |
| # still override the dev/pts mount. | |
| # Make sure /etc/resolv.conf is updated alongside the host's | |
| mkdir -p etc | |
| test -f etc/resolv.conf || touch etc/resolv.conf | |
| mount --bind /etc/resolv.conf etc/resolv.conf | |
| # Roughly replicate what bubblewrap, systemd-nspawn, etc do | |
| # This allows for recursive "unshare" inside the chroot to work correctly | |
| # https://github.com/containers/bubblewrap/blob/main/bubblewrap.c | |
| # https://bugs.gentoo.org/922960 | |
| # Assuming /mnt exists inside the chroot, and the umount command is available | |
| # in the chroot's $PATH | |
| mount --rbind . . | |
| cd . | |
| exec env -i TERM="$TERM" HOME=/root sh -ec 'pivot_root="$1"; shift | |
| "$pivot_root" . mnt | |
| exec /bin/sh -l -ec "umount -l /mnt | |
| exec \"\$@\"" -- "$@"' -- "$(which pivot_root)" "${@:-bash}" | |
| # Alternative, simpler chroot invocation | |
| # "env -i" allows us to clear the environment, so we don't pollute it with host | |
| # variables. Combining this with "/bin/sh -l", we read /etc/profile and | |
| # populate the chroot's $PATH and other variables, before executing an | |
| # arbitrary command. | |
| exec env -i TERM="$TERM" HOME=/root "$(which chroot)" . /bin/sh -l -c 'exec "$@"' -- "${@:-bash}" | |
| # Alternative, direct-syscall implementation of the pivot_root method above | |
| # Doesn't rely on /mnt or "umount" inside the chroot, and doesn't leave the | |
| # original rootfs mounted while /etc/profile is being evaluated. Instead relies | |
| # on the host's C compiler and /tmp. | |
| mount -t tmpfs tmpfs /tmp | |
| printf '_(%s,(%s))' \ | |
| mount '".",".",0,MS_BIND|MS_REC,0' \ | |
| chdir 'getcwd((char[PATH_MAX]){0},PATH_MAX)' \ | |
| pivot_root '".","."' \ | |
| umount2 '"/",MNT_DETACH' \ | |
| execv '*++v,v' | \ | |
| xargs -0 printf 'int main(int _,char**v){%s}' | cc -xc - -o /tmp/pivot_root \ | |
| -D'_(a,b)=if(a b<0)write(2,#a" failed\n",sizeof(#a)+8),_exit(1);' \ | |
| -D'pivot_root(a,b)=syscall(SYS_pivot_root,a,b)' \ | |
| $(printf ' -include %s.h' unistd sys/mount sys/syscall linux/limits) | |
| exec env -i TERM="$TERM" HOME=/root /tmp/pivot_root /bin/sh -l -c 'exec "$@"' -- "${@:-bash}" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S unshare -fpmr --map-auto sh | |
| # vim:ft=sh | |
| set -eu | |
| test -n "${1:-}" || exec "${SHELL:-bash}" | |
| umask 022 | |
| cd "$1"; shift | |
| mount --rbind /dev dev | |
| mount --rbind /sys sys | |
| mount -t proc proc proc | |
| mount -t devpts devpts dev/pts | |
| mkdir -p etc | |
| test -f etc/resolv.conf || touch etc/resolv.conf | |
| mount --bind /etc/resolv.conf etc/resolv.conf | |
| mount --rbind . . | |
| cd . | |
| exec env -i TERM="$TERM" HOME=/root sh -ec 'pivot_root="$1"; shift | |
| "$pivot_root" . mnt | |
| exec /bin/sh -l -ec "umount -l /mnt | |
| exec \"\$@\"" -- "$@"' -- "$(which pivot_root)" "${@:-bash}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment