Running fully isolated commands in Linux

To use unshare to run a Python command that cannot see or touch files outside its own folder, you must combine unshare with chroot.

This is a multi-step process because python itself (and its libraries) must be present inside the folder it's "jailed" in. unshare creates the isolated environment, and chroot changes the process's root directory to your sandbox.

Here is the complete process.

1. Create the Sandbox Environment#

First, we create a folder (e.g., ~/python_sandbox) and copy the python3 binary and all its required libraries into it.

Bash

# --- 1. Create the directory structure ---
mkdir -p ~/python_sandbox/{bin,work}
cd ~/python_sandbox

# --- 2. Create your Python script inside the jail ---
# This script tries to list the jail's root (/) and the real root's /home
cat << 'EOF' > ./work/test.py
import os
import sys

print("--- Python Script Started ---")
print(f"My PID: {os.getpid()}")
print(f"My UID: {os.getuid()}")

print("\nListing jail's root directory (/):")
# This will list {'bin', 'work', 'lib', 'lib64', ...}
print(os.listdir('/'))

print("\nAttempting to list host's home (/home):")
try:
# This will fail, as /home doesn't exist in our jail
print(os.listdir('/home'))
except FileNotFoundError as e:
print(f"SAFE: Could not access /home. Error: {e}")

print("--- Python Script Finished ---")
EOF

# --- 3. Copy the binaries ---
# We need python and a shell (sh)
cp /usr/bin/python3 ./bin/
cp /bin/sh ./bin/

# --- 4. Copy all required libraries ---
# Get the list of binaries we need to check
EXE_LIST="./bin/python3 ./bin/sh"

# Find all library dependencies
LIB_LIST=$(ldd $EXE_LIST | grep "=> /" | awk '{print $3}' | sort -u)

# Find the dynamic loader (e.g., ld-linux.so)
LOADER_LIST=$(ldd $EXE_LIST | grep "ld-linux" | awk '{print $1}' | sort -u)

# Copy all libs and the loader into the jail, preserving their paths
echo $LIB_LIST $LOADER_LIST | xargs -n 1 -I_ cp --parents -v _ .

# Go back to your home directory
cd ~

You now have a self-contained sandbox at ~/python_sandbox.

2. Run the Isolated Command#

Now, use unshare to run the chroot command in a fully isolated set of namespaces.

-r (--map-root-user): Creates a new user namespace where you are root (UID 0) inside the namespace, but a normal user outside. This is vital as chroot requires root.

--mount: Creates a new mount namespace, so the chroot doesn't affect your host system.

--pid --fork --mount-proc: Creates a new PID namespace (and mounts a new /proc) so the script is PID 1 and can't see other host processes.

--net=none: Disables all network access.

chroot ~/python_sandbox: The command that changes the root directory.

/bin/sh -c "/bin/python3 /work/test.py": The command to run inside the jail.

Bash

unshare -r --mount --pid --fork --mount-proc --net=none\
chroot ~/python_sandbox\
/bin/sh -c "/bin/python3 /work/test.py"

Expected Output#

When you run the command, you will see output like this, proving the script was isolated:

--- Python Script Started ---
My PID: 1
My UID: 0
Listing jail's root directory (/):
['bin', 'work', 'lib64', 'lib', 'usr']

Attempting to list host's home (/home):
SAFE: Could not access /home. Error: [Errno 2] No such file or directory: '/home'
--- Python Script Finished ---

As you can see, the script's root (/) was the python_sandbox folder, and it had no way to "see" or "touch" your real /home directory because, from its perspective, it didn't exist.