Using a Chroot

A chroot is an operation that changes the apparent root directory for the current process and its children. It effectively isolates the current process and its children from anything outside of the new root directory. This can be useful for running or building software in a clean environment with only the required dependencies. The resulting environment is called a chroot jail.

Using a Chroot

This is just a short example of creating and entering a chroot jail. I am using Alpine linux because it is ideal for creating a minimal linux userspace. The alpine minrootfs tarball is only 2.7M and the unpacked root filesystem is 6M and only has 12 packages installed.

  1. Download an Alpine linux mini root filesystem tarball from here:

    wget https://dl-cdn.alpinelinux.org/alpine/v3.13/releases/x86_64/alpine-minirootfs-3.13.1-x86_64.tar.gz
    
  2. Create a directory for your chroot and unpack the mini root filesystem into it:

    mkdir my-alpine-chroot
    tar -C my-alpine-chroot -xf alpine-minirootfs-*.tar.gz
    
  3. It’s convenient to have a helper script for setting up and entering a chroot jail. Below is a script named start-chroot.sh originally from here. It mounts the virtual filesystems and sets up a clean environment before calling chroot. It also ensures the filesystems are unmounted when exiting the chroot jail. start-chroot.sh

    #!/bin/sh -e
    
    if [ 0 -ne `id -u` ]; then
      echo "This script needs root access" >&2
      exit 1
    fi
    
    if ! [ -d "$1" ] || [ x-h = x"$*" ] || [ x--help = x"$*" ]; then
      echo "Usage: ${0##*/} " >&2
      exit 1
    fi
    
    if [ x1 = x`sysctl -ne kernel.grsecurity.chroot_deny_chmod` ]; then
      echo "Warning: can't suid/sgid inside chroot" >&2
    fi
    if [ x1 = x`sysctl -ne kernel.grsecurity.chroot_deny_mknod` ]; then
      echo "Warning: can't mknod inside chroot" >&2
    fi
    if [ x1 = x`sysctl -ne kernel.grsecurity.chroot_deny_mount` ]; then
      echo "Warning: can't mount inside chroot" >&2
    fi
    if [ x1 = x`sysctl -ne kernel.grsecurity.chroot_deny_chroot` ]; then
      echo "Warning: can't chroot inside chroot" >&2
    fi
    
    cd "$1"
    if ! [ -d ./etc ]; then
      echo "No etc directory inside $1" >&2
      exit 1
    fi
    shift
    
    MOUNTED=
    umount_all() {
      case $MOUNTED in
      shm\ *) if [ -L ./dev/shm ]; then
                umount ./`readlink ./dev/shm`
              else
                umount ./dev/shm
              fi
              MOUNTED=${MOUNTED#shm };;
      esac
      case $MOUNTED in
      run\ *) umount ./run
              MOUNTED=${MOUNTED#run };;
      esac
      case $MOUNTED in
      tmp\ *) umount ./tmp
              MOUNTED=${MOUNTED#tmp };;
      esac
      case $MOUNTED in
      proc\ *) umount ./proc
              MOUNTED=${MOUNTED#proc };;
      esac
      case $MOUNTED in
      sys\ *) umount ./sys
              MOUNTED=${MOUNTED#sys };;
      esac
      case $MOUNTED in
      pts\ *) umount ./dev/pts
              MOUNTED=${MOUNTED#pts };;
      esac
      case $MOUNTED in
      dev\ *) umount ./dev
              MOUNTED=${MOUNTED#dev };;
      esac
    }
    trap 'umount_all' EXIT
    
    #mkdir -p ./etc ./dev/pts ./sys ./proc ./tmp ./run ./boot ./root
    
    cp -iL /etc/resolv.conf ./etc/ || true  # if ^C, will cancel script
    
    mount --bind /dev ./dev
    MOUNTED="dev $MOUNTED"
    
    mount -t devpts devpts ./dev/pts -o nosuid,noexec
    MOUNTED="pts $MOUNTED"
    
    mount -t sysfs sys ./sys -o nosuid,nodev,noexec,ro
    MOUNTED="sys $MOUNTED"
    
    mount -t proc proc ./proc -o nosuid,nodev,noexec
    MOUNTED="proc $MOUNTED"
    
    mount -t tmpfs tmp ./tmp -o mode=1777,nosuid,nodev,strictatime
    MOUNTED="tmp $MOUNTED"
    mount -t tmpfs run ./run -o mode=0755,nosuid,nodev
    MOUNTED="run $MOUNTED"
    if [ -L ./dev/shm ]; then
      mkdir -p ./`readlink ./dev/shm`
      mount -t tmpfs shm ./`readlink ./dev/shm` -o mode=1777,nosuid,nodev
    else
      #mkdir -p ./dev/shm
      mount -t tmpfs shm ./dev/shm -o mode=1777,nosuid,nodev
    fi
    MOUNTED="shm $MOUNTED"
    
    case $1 in
      -l) shift;;
      -l*) one=${1#-l}; shift; set -- -"$one" "$@";;
    esac
    chroot . /usr/bin/env -i SHELL=/bin/sh HOME=/root TERM="$TERM" \
      PATH=/usr/sbin:/usr/bin:/sbin:/bin PS1='chroot # ' /bin/sh -l "$@"
    
  4. Enter the chroot environment:

    chmod +x ./start-chroot.sh
    sudo ./start-chroot.sh my-alpine-chroot
    
  5. You are now in a chroot with a minimal alpine filesystem, with virtual filesystems such as /dev, /sys, /proc mounted. You can use apk add … to install whatever is needed.