# Run Python in a sandbox with nsjail Recently I have been thinking about different ways of building a sandbox environment to run untrusted code. So far, I have been exploring the [[2021-02-28-run-a-firecracker-on-nomad|micro virtual machines approach]], which works amazingly well; however, if one needs to run a few lines of Python code, it could be an overkill to spin up a new Linux environment. As as alternative I decided to try isolation. At the same time there are lots think to consider about it and to name a few: - Isolation of network interfaces - Isolation of local processes - Resource limits such as CPU time limits, memory address space limits - Syscalls filtering - File system constraints Luckily, most of those things could be handled by Linux already out-of-the-box. For instance, Linux namespace subsystem, cgroups, and the seccomp-bpf syscall filters of the Linux kernel. Network exposure is very important unless I shut it down completely. I need to do some extra readings how people sandbox network these days. Sidenote: a while ago I was solving Google Foobar puzzles, and it occurred to me that Google uses [nsjail](https://nsjail.dev) under the hood to run untrusted Python code. Furthermore, I believe that other companies (e.g. CoderPad) also uses a very similar approach. Anyway, my point is if someone needs to run a simple project, it could be that she doesn’t need full control over the environment and could get by with some reasonable defaults and preinstalled libraries. Now, how difficult would it be to implement something similar? `nsjail` requires [protoc](https://github.com/protocolbuffers/protobuf), which wasn’t available for my system, and I had to compile it from sources. Also, there is no recent `nsjail` release, and I compiled it from sources too: https://github.com/google/nsjail. I didn’t write down which dependencies I had to install, so I am leaving this exercise to a reader to figure out. But trust me, it doesn’t require anything fancy. Now, I can draft a configuration file for nsjail. This particular configuration I borrowed from [snekbox](https://github.com/python-discord/snekbox/tree/main/snekbox) project and adjusted to match my environment. Cool project, check it out: ``` name: "sandbox" description: "Execute Python" mode: ONCE hostname: "sandbox" cwd: "/sandbox" time_limit: 6 keep_env: false envar: "LANG=en_US.UTF-8" envar: "OMP_NUM_THREADS=1" envar: "OPENBLAS_NUM_THREADS=1" envar: "MKL_NUM_THREADS=1" envar: "VECLIB_MAXIMUM_THREADS=1" envar: "NUMEXPR_NUM_THREADS=1" envar: "PYTHONPATH=/usr/lib/python3.8/site-packages/" envar: "PYTHONIOENCODING=utf-8:strict" keep_caps: false rlimit_as: 700 clone_newnet: true clone_newuser: true clone_newns: true clone_newpid: true clone_newipc: true clone_newuts: true clone_newcgroup: true uidmap { inside_id: "65534" outside_id: "65534" } gidmap { inside_id: "65534" outside_id: "65534" } mount_proc: false mount { src: "/etc/ld.so.cache" dst: "/etc/ld.so.cache" is_bind: true rw: false } mount { src: "/lib" dst: "/lib" is_bind: true rw: false } mount { src: "/lib64" dst: "/lib64" is_bind: true rw: false } mount { src: "/sandbox" dst: "/sandbox" is_bind: true rw: false } mount { src: "/usr/lib" dst: "/usr/lib" is_bind: true rw: false } mount { src: "/usr/local/lib" dst: "/usr/local/lib" is_bind: true rw: false } mount { src: "/usr/bin/python" dst: "/usr/local/bin/python" is_bind: true rw: false } mount { src: "/usr/bin/python3" dst: "/usr/local/bin/python3" is_bind: true rw: false } mount { src: "/usr/bin/python3.8" dst: "/usr/local/bin/python3.8" is_bind: true rw: false } mount { src: "/bin/sh" dst: "/bin/sh" is_bind: true rw: false } mount { src: "/dev/random" dst: "/dev/random" is_bind: true rw: false } cgroup_mem_max: 52428800 cgroup_mem_mount: "/sys/fs/cgroup/memory" cgroup_pids_max: 1 cgroup_pids_mount: "/sys/fs/cgroup/pids" iface_no_lo: true ``` That’s basically it. Now, I can run Python code in a sandbox environment. It is fast, and it is (hopefully) secure. I will have to play with other security policies that nsjail provides to make sure that I’m not missing anything obvious, but so far it is looking promising. ```bash $ cat hello.py print('Hello from nsjail!') $ nsjail --config sandbox.cfg -- python -Su ./hello.py Hello from nsjail! ``` I can also run everything inside a Firecracker or Docker instance to make sure that even if someone manages to escape a sandbox she won’t be able to do anything malicious to the host system.