diff options
-rwxr-xr-x | python/runner/interpreter.py (renamed from python/interpreter.py) | 0 | ||||
-rw-r--r-- | python/runner/sandbox.c | 47 | ||||
-rw-r--r-- | python/runner/terminator.c | 32 | ||||
-rw-r--r-- | readme.md | 11 | ||||
-rw-r--r-- | server/python_session.py | 28 |
5 files changed, 110 insertions, 8 deletions
diff --git a/python/interpreter.py b/python/runner/interpreter.py index dae4d59..dae4d59 100755 --- a/python/interpreter.py +++ b/python/runner/interpreter.py diff --git a/python/runner/sandbox.c b/python/runner/sandbox.c new file mode 100644 index 0000000..12e2720 --- /dev/null +++ b/python/runner/sandbox.c @@ -0,0 +1,47 @@ +#include <fcntl.h> +#include <pwd.h> +#include <stdio.h> +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +int main(int argc, char* argv[]) +{ + if (argc < 3) { + fprintf(stderr, "usage: %s USERNAME FILE\n", argv[0]); + return 1; + } + + // switch user (requires root or "setcap cap_setuid,cap_setgid+ep") + char const* username = argv[1]; + struct passwd const* pw = getpwnam(username); + if (!pw) { + fprintf(stderr, "no such user: %s\n", username); + return 1; + } + int ret = 0; + if ((ret = setuid(pw->pw_uid)) != 0) + fprintf(stderr, "setuid returned %d\n", ret); + if ((ret = setgid(pw->pw_gid)) != 0) + fprintf(stderr, "setgid returned %d\n", ret); + + // limit CPU time to 1 second + struct rlimit const cpu_limit = { .rlim_cur = 1, .rlim_max = 1 }; + if ((ret = setrlimit(RLIMIT_CPU, &cpu_limit)) != 0) + fprintf(stderr, "setrlimit(CPU) returned %d\n", ret); + + // don't allow writing files of any size + struct rlimit const fsize_limit = { .rlim_cur = 0, .rlim_max = 0 }; + if ((ret = setrlimit(RLIMIT_FSIZE, &fsize_limit)) != 0) + fprintf(stderr, "setrlimit(FSIZE) returned %d\n", ret); + + // there will be no fork + struct rlimit const nproc_limit = { .rlim_cur = 0, .rlim_max = 0 }; + if ((ret = setrlimit(RLIMIT_NPROC, &nproc_limit)) != 0) + fprintf(stderr, "setrlimit(NPROC) returned %d\n", ret); + + char* const args[] = { argv[2], (char*)0 }; + return execvp(argv[2], args); +} diff --git a/python/runner/terminator.c b/python/runner/terminator.c new file mode 100644 index 0000000..a994bde --- /dev/null +++ b/python/runner/terminator.c @@ -0,0 +1,32 @@ +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> + +int main(int argc, char* argv[]) +{ + if (argc < 4) { + fprintf(stderr, "usage: %s USERNAME PID SIGNAL\n", argv[0]); + return 1; + } + + // switch user (requires root or "setcap cap_setuid,cap_setgid+ep") + char const* username = argv[1]; + struct passwd const* pw = getpwnam(username); + if (!pw) { + fprintf(stderr, "no such user: %s\n", username); + return 1; + } + int ret = 0; + if ((ret = setuid(pw->pw_uid)) != 0) + fprintf(stderr, "setuid returned %d\n", ret); + if ((ret = setgid(pw->pw_gid)) != 0) + fprintf(stderr, "setgid returned %d\n", ret); + + pid_t pid = atol(argv[2]); + int signum = atoi(argv[3]); + kill(pid, signum); + return 0; +} @@ -49,6 +49,17 @@ nodejs Run "npm install" inside the codeq-server/web directory to install all dependencies (they will be installed inside the web directory) +sandbox +------- + +Go to directory codeq-server/python/runner and run the following commands to +build the sandbox and set appropriate permissions: + + make sandbox + mate terminator + sudo setcap cap_setuid,cap_setgid+ep sandbox + sudo setcap cap_setuid,cap_setgid+ep terminator + Settings ======== diff --git a/server/python_session.py b/server/python_session.py index 24d33b0..f4c482c 100644 --- a/server/python_session.py +++ b/server/python_session.py @@ -116,15 +116,27 @@ class PythonSession(server.LanguageSession): self._sent_hints.extend(hints) def _interpreter(control, callback): - directory = os.path.dirname(os.path.realpath(__file__)) - # TODO drop privileges using a wrapper - script = os.path.join(directory, '..', 'python', 'interpreter.py') + basedir = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] + script = os.path.join(basedir, 'python', 'runner', 'interpreter.py') + + # If the sandbox wrapper exists, use it to switch to user "nobody" and + # enforce additional limits. Unless the daemon is running as root we are + # not able to signal nobody's PIDs, so switch user again for the killing. + sandbox = os.path.join(basedir, 'python', 'runner', 'sandbox') + terminator = os.path.join(basedir, 'python', 'runner', 'terminator') + if os.path.exists(sandbox) and os.path.exists(terminator): + newuser = 'nobody' # TODO make this configurable + args = [sandbox, newuser, script] + kill = lambda proc, sig: subprocess.call([terminator, newuser, str(proc.pid), str(sig)]) + else: + args = [script] + kill = lambda proc, sig: proc.send_signal(sig) proc = None while True: # Ensure the interpreter process is running. if proc is None: - proc = subprocess.Popen([script], + proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) @@ -143,7 +155,7 @@ def _interpreter(control, callback): proc.stdin.write(data.encode('utf-8')) proc.stdin.flush() elif cmd == 'stop': - proc.send_signal(signal.SIGINT) + kill(proc, signal.SIGINT) elif cmd == 'done': break except: @@ -155,7 +167,7 @@ def _interpreter(control, callback): data = proc.stdout.read() if data: if len(data) > 20000: - proc.kill() + kill(proc, signal.SIGKILL) proc = None callback('Child killed for talking too much.\n') else: @@ -169,7 +181,7 @@ def _interpreter(control, callback): elif retcode == -31: # killed by seccomp callback('Child killed due to sandbox misbehavior.\n') else: - callback('Child exited with status "{}".\n'.format(retcode)) + callback('Child exited with status {}.\n'.format(retcode)) proc = None # TODO we should select() on control and proc.stdout instead of polling @@ -177,7 +189,7 @@ def _interpreter(control, callback): # We are done, kill the child. if proc is not None: - proc.kill() + kill(proc, signal.SIGKILL) # Execute [code] and evaluate [expr]. Input is given by the string [stdin]. # Return result of evaluation, the contents of stdout and stderr, and the |