Isolate a process using chroot

Summary

For some reason an user might want to limit a process to access the whole / . Using chroot you can run a process in a 'jailled' environment.

That article will explain how to isolate an user for them to been chrooted when loggin using ssh.

Adjust the following commands regarding your OS. For example, using Alpine GNU/Linux, bash is located ni /bin/ instead of /usr/bin/

Scenario

We'll offer to the user random_user_name an access to bash, ls and chronyc.

Roadmap

  1. Locate the files permitting the process to run.
  2. Create the target jail folder and its dependency.
  3. Copy all the files into the jail.
  4. Chroot and exit.
  5. Chroot a ssh user.

Quick & Dirty

  1. Locate the files linked to the program we want to isolate using ldd

ldd man page explains that for security reason, do not use ldd on an untrusted executable, use: objdump -p /path/to/program | grep NEEDED; instead.

ldd $( which bash)
        linux-vdso.so.1 (0x00007f31c3573000)
        libtinfo.so.6 => /lib64/libtinfo.so.6 (0x00007f31c33dc000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f31c3203000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f31c3575000)

ldd $( which ls | grep '/')
        linux-vdso.so.1 (0x00007fe78234f000)
        libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fe7822ee000)
        libcap.so.2 => /lib64/libcap.so.2 (0x00007fe7822e1000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fe782108000)
        libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007fe782068000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fe782351000)

ldd $( which chronyc)
        linux-vdso.so.1 (0x00007fe364d95000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fe364cc7000)
        libgnutls.so.30 => /lib64/libgnutls.so.30 (0x00007fe364800000)
        libedit.so.0 => /lib64/libedit.so.0 (0x00007fe364c8c000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fe364627000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fe364d97000)
        libp11-kit.so.0 => /lib64/libp11-kit.so.0 (0x00007fe364b26000)
        libidn2.so.0 => /lib64/libidn2.so.0 (0x00007fe364605000)
        libunistring.so.5 => /lib64/libunistring.so.5 (0x00007fe364457000)
        libtasn1.so.6 => /lib64/libtasn1.so.6 (0x00007fe364441000)
        libtinfo.so.6 => /lib64/libtinfo.so.6 (0x00007fe364414000)
        libffi.so.8 => /lib64/libffi.so.8 (0x00007fe364403000)

ldd permits to identify the shared libraries used by the programs to run.

  1. Let's create the jail into /chroot/

Find the folder to create, here it will be only /lib64, but you might need some subdirectory as well (ex: lib/x86_64)

ldd $(which bash) | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'
ldd $(which ls | grep '/') | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'
ldd $(which chronyc) | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'
export CHROOT_PATH=/chroot
mkdir -p $CHROOT_PATH/{bin,etc,lib64} #create every requiered directory found using ldd + bin, etc
mkdir -p $CHROOT_PATH/dev/pts/
  1. Copy the files to the jail

Copy the executable file to the $CHROOT_PATH//bin and the configuration file to its linked $CHROOT_PATH/etc if exists.

for ELFNAME in {ls,bash,chronyc}
do
  cp -n $(which $ELFNAME | grep '/' | tr -d '\t') $CHROOT_PATH/bin
  for i in $(ldd $(which $ELFNAME | grep '/' | tr -d '\t') | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'); do cp -n --parent $i $CHROOT_PATH; done
done
  1. Chroot tests.

To chroot, as root run chroot destination_path program_to_run

chroot $CHROOT_PATH /bin/bash

Remember that executables are located at bin, to launch a program add the full chrooted path /bin/program_name

To exit from the chroot enter exit or ctrl+d

exit
  1. Isolate a SSH user

To chroot an user login via ssh

Create the minimum file set that are mandatory to create a session

export CHROOT_USERNAME=<changeme to username>
cd $CHROOT_PATH/dev
mknod -m 666 null c 1 3
mknod -m 666 tty c 5 0
mknod -m 666 zero c 1 5
mknod -m 666 random c 1 8
cd pts
mknod -m 666 0 c 5 0

Append to the /etc/ssh/sshd_config file the line

echo "Match User $CHROOT_USERNAME" >> /etc/ssh/sshd_config
echo "ChrootDirectory $CHROOT_PATH" >> /etc/ssh/sshd_config
systemctl restart sshd #Restart the service according to your distribution

Tests

$ ssh testuser@57.129.106.133 -p 1234
Last login: Thu Dec 18 20:36:01 2025 from x.y.z.w
-bash-5.2$ /bin/ls /
bin  dev  etc  lib64  usr
-bash-5.2$ chronyc
-bash: chronyc: command not found
-bash-5.2$ /bin/chronyc 
chrony version 4.6.1
Copyright (C) 1997-2003, 2007, 2009-2024 Richard P. Curnow and others
chrony comes with ABSOLUTELY NO WARRANTY.  This is free software, and
you are welcome to redistribute it under certain conditions.  See the
GNU General Public License version 2 for details.

Cannot read termcap database;
using dumb terminal settings.
chronyc> 

All in one script

export CHROOT_USERNAME=<changeme to username> # please set the username you want to chroot
export CHROOT_PATH=/chroot

for names_file in {ls,bash,chronyc} # Change here the list of programs
do
 for list_libs in $(ldd $(which $(echo $names_file)) | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/')
 do
  mkdir -p $CHROOT_PATH/$(python3 -c "u=str('$(echo $list_libs)').split('/');print('/'.join(u[:-1]).lstrip('/'))")
 done
done

mkdir -p $CHROOT_PATH/{bin,etc}
mkdir -p $CHROOT_PATH/dev/pts/

cd $CHROOT_PATH/dev
mknod -m 666 null c 1 3
mknod -m 666 tty c 5 0
mknod -m 666 zero c 1 5
mknod -m 666 random c 1 8
cd pts
mknod -m 666 0 c 5 0

for ELFNAME in {ls,bash,chronyc} # Change here the list of programs
do
  cp -n $(which $ELFNAME | grep '/' | tr -d '\t') $CHROOT_PATH/bin
  for i in $(ldd $(which $ELFNAME | grep '/' | tr -d '\t') | tr -d '\t' | cut -d '(' -f 1 | cut -d '>' -f 2 | tr -d ' ' | grep '/'); do cp -n --parent $i $CHROOT_PATH; done
done

echo "Match User $CHROOT_USERNAME" >> /etc/ssh/sshd_config
echo "ChrootDirectory $CHROOT_PATH" >> /etc/ssh/sshd_config

systemctl restart $(ss -antp | grep ssh | head -n 1 | awk '{ print $6}' | cut -d '"' -f 2)