From 275416559d0f2e2521c459c5bee3875712dedc01 Mon Sep 17 00:00:00 2001 From: Tim McCormack Date: Wed, 29 Nov 2023 22:35:59 +0000 Subject: [PATCH] docs: Various fixes to codejail setup instructions (copies, user) 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 (https://github.com/openedx/codejail/issues/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 --- README.rst | 40 +++++++++++++++++++++++----------------- codejail/jail_code.py | 2 ++ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index 43bcbc233..fa1fb375f 100644 --- a/README.rst +++ b/README.rst @@ -63,24 +63,25 @@ sandboxed Python code. Choose a place for the new virtualenv, call it ****. 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 **** be `/home/chris/ve/myproj-sandbox`. +virtualenv, but with ``-sandbox`` appended. So if your existing virtualenv is in +``/home/chris/ve/myproj``, make **** be ``/home/chris/ve/myproj-sandbox``. The user running the LMS is ****, 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 + $ sudo python3.8 -m venv --copies + + 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 /bin/activate - $ pip install -r requirements/sandbox.txt + $ /bin/pip install -r requirements/sandbox.txt 3. Add a sandbox user:: @@ -88,7 +89,7 @@ Other details here that depend on your configuration: $ 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 @@ -96,11 +97,13 @@ Other details here that depend on your configuration: ALL=(sandbox) SETENV:NOPASSWD:/usr/bin/find 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 @@ -110,6 +113,7 @@ Other details here that depend on your configuration: #include #include + /** mr, /** mr, # If you have code that the sandbox must be able to access, add lines # pointing to those directories: @@ -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', '/bin/python') + codejail.jail_code.configure('python', '/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) + jailed_globals This should fail with an exception. @@ -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 @@ -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. diff --git a/codejail/jail_code.py b/codejail/jail_code.py index cd27d0dc4..97957bd4d 100644 --- a/codejail/jail_code.py +++ b/codejail/jail_code.py @@ -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'])