I want to safely run untrusted user-submitted code (for example a single c++ file, without anything but standard libraries, and without multithreading) on my server. I want the program to be able to read from an input file and write to an output file, but have no access to anything else ony my server. It should not be able to break anything on my system.
I tried to achieve this by:
- using chroot to separate the program from my file system
- starting the user program with the following c program, that should block unsafe syscalls
#include <stdio.h>
#include <stdlib.h>
#include <seccomp.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(execve), 0);
seccomp_load(ctx) < 0;
system("./userProgram");
}
Now my issue is that I didn't know how many other syscalls the program would make. Even for a really simple userProgram dozens of syscalls such as mmap, access, openat, pread64, set_robust_list, etc. were made.
Is it safe to allow all of these syscalls? And would I have to manually run potential user programs with strace to see which syscalls are made?
system()greatly increases the number of syscalls you need to allow, because instead of starting your child process directly you're starting a shell, and telling the shell to start the child in turn. Much safer to directly useexecve().open()&c., you can limit what content exists in the namespace to be opened.-net userfunctionality is disabled), snapshot mode to ensure that all writes are instance-specific, &c.system()is also a bad idea because it requires your process to be able to run/bin/sh. Since one of the very first things an attacker is likely to do is try to set up a reverse shell, that's something you want to lock off very quickly.