Skip to content

Commit

Permalink
docs: Various fixes to codejail setup instructions (copies, user) (#164)
Browse files Browse the repository at this point in the history
This still doesn't work as described, as codejail's confinement still
denies it the ability to create a temp directory, but it's closer.

- Specify the use of `--copies` when setting up the sandbox virtualenv
- Include the `user` argument to work around TMPDIR bug
  (#162) and link to the issue
  from a code comment
- Use an unambiguous test for safe/usafe configuration (return the value
  using jailed globals, and bypass some issues with output streams)
- Ensure that reading codejail checkout is permitted by apparmor

Also:

- Use the venv module rather than the virtualenv command, for better
  compatibility with varying system configurations
- Don't bother activating virtualenv, just call its pip directly
- Note the ordinarily-unsafe presence of find in the sudoers example
- Use correct inline code syntax for rst
  • Loading branch information
timmc-edx authored Dec 5, 2023
1 parent 4ac44e7 commit 96abed1
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 17 deletions.
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

0 comments on commit 96abed1

Please sign in to comment.