Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,44 +63,47 @@ sandboxed Python code.

Choose a place for the new virtualenv, call it **<SANDENV>**. It will be
automatically detected and used if you put it right alongside your existing
virtualenv, but with `-sandbox` appended. So if your existing virtualenv is in
`/home/chris/ve/myproj`, make **<SANDENV>** be `/home/chris/ve/myproj-sandbox`.
virtualenv, but with ``-sandbox`` appended. So if your existing virtualenv is in
``/home/chris/ve/myproj``, make **<SANDENV>** be ``/home/chris/ve/myproj-sandbox``.

The user running the LMS is **<SANDBOX_CALLER>**, for example, you on
your dev machine, or `www-data` on a server.
your dev machine, or ``www-data`` on a server.

Other details here that depend on your configuration:

1. Create the new virtualenv::
1. Create the new virtualenv, using ``--copies`` so that there's a distinct Python executable to limit::

$ sudo virtualenv <SANDENV>
$ sudo python3.8 -m venv --copies <SANDENV>

By default, the virtualenv would just symlink against the system Python, and apparmor's default configuration on some operating systems may prevent confinement from being appled to that.

2. (Optional) If you have particular packages you want available to your
sandboxed code, install them by activating the sandbox virtual env, and
using pip to install them::

$ source <SANDENV>/bin/activate
$ pip install -r requirements/sandbox.txt
$ <SANDENV>/bin/pip install -r requirements/sandbox.txt

3. Add a sandbox user::

$ sudo addgroup sandbox
$ sudo adduser --disabled-login sandbox --ingroup sandbox

4. Let the web server run the sandboxed Python as sandbox. Create the file
`/etc/sudoers.d/01-sandbox`::
``/etc/sudoers.d/01-sandbox``::

$ sudo visudo -f /etc/sudoers.d/01-sandbox

<SANDBOX_CALLER> ALL=(sandbox) SETENV:NOPASSWD:<SANDENV>/bin/python
<SANDBOX_CALLER> ALL=(sandbox) SETENV:NOPASSWD:/usr/bin/find
<SANDBOX_CALLER> ALL=(ALL) NOPASSWD:/usr/bin/pkill

(Note that the ``find`` binary can run arbitrary code, so this is not a safe sudoers file for non-codejail purposes.)

5. Edit an AppArmor profile. This is a text file specifying the limits on the
sandboxed Python executable. The file must be in `/etc/apparmor.d` and must
sandboxed Python executable. The file must be in ``/etc/apparmor.d`` and must
be named based on the executable, with slashes replaced by dots. For
example, if your sandboxed Python is at `/home/chris/ve/myproj-sandbox/bin/python`,
then your AppArmor profile must be `/etc/apparmor.d/home.chris.ve.myproj-sandbox.bin.python`::
example, if your sandboxed Python is at ``/home/chris/ve/myproj-sandbox/bin/python``,
then your AppArmor profile must be ``/etc/apparmor.d/home.chris.ve.myproj-sandbox.bin.python``::

$ sudo vim /etc/apparmor.d/home.chris.ve.myproj-sandbox.bin.python

Expand All @@ -110,6 +113,7 @@ Other details here that depend on your configuration:
#include <abstractions/base>
#include <abstractions/python>

<CODEJAIL_CHECKOUT>/** mr,
<SANDENV>/** mr,
# If you have code that the sandbox must be able to access, add lines
# pointing to those directories:
Expand All @@ -132,9 +136,11 @@ If your CodeJail is properly configured to use safe_exec, try these
commands at your Python terminal::

import codejail.jail_code
codejail.jail_code.configure('python', '<SANDENV>/bin/python')
codejail.jail_code.configure('python', '<SANDENV>/bin/python', user='sandbox')
import codejail.safe_exec
codejail.safe_exec.safe_exec("import os\nos.system('ls /etc')", {})
jailed_globals = {}
codejail.safe_exec.safe_exec("output=open('/etc/passwd').read()", jailed_globals)
print(jailed_globals) # should be unreachable if codejail is working properly

This should fail with an exception.

Expand Down Expand Up @@ -180,10 +186,10 @@ Design
CodeJail is general-purpose enough that it can be used in a variety of projects
to run untrusted code. It provides two layers:

* `jail_code.py` offers secure execution of subprocesses. It does this by
* ``jail_code.py`` offers secure execution of subprocesses. It does this by
running the program in a subprocess managed by AppArmor.

* `safe_exec.py` offers specialized handling of Python execution, using
* ``safe_exec.py`` offers specialized handling of Python execution, using
jail_code to provide the semantics of Python's exec statement.

CodeJail runs programs under AppArmor. AppArmor is an OS-provided feature to
Expand All @@ -194,12 +200,12 @@ execute the provided Python program with that executable, and AppArmor will
automatically limit the resources it can access. CodeJail also uses setrlimit
to limit the amount of CPU time and/or memory available to the process.

`CodeJail.jail_code` takes a program to run, files to copy into its
``CodeJail.jail_code`` takes a program to run, files to copy into its
environment, command-line arguments, and a stdin stream. It creates a
temporary directory, creates or copies the needed files, spawns a subprocess to
run the code, and returns the output and exit status of the process.

`CodeJail.safe_exec` emulates Python's exec statement. It takes a chunk of
``CodeJail.safe_exec`` emulates Python's exec statement. It takes a chunk of
Python code, and runs it using jail_code, modifying the globals dictionary as a
side-effect. safe_exec does this by serializing the globals into and out of
the subprocess as JSON.
Expand Down
2 changes: 2 additions & 0 deletions codejail/jail_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ def jail_code(command, code=None, files=None, extra_files=None, argv=None,
rm_cmd.extend(['sudo', '-u', user])

# Point TMPDIR at our temp directory.
# FIXME: This breaks command execution unless user param has been set.
# Issue: https://github.com/openedx/codejail/issues/162
cmd.extend(['TMPDIR=tmp'])
# Start with the command line dictated by "python" or whatever.
cmd.extend(COMMANDS[command]['cmdline_start'])
Expand Down