Numerous websites allow us to build and execute C code from web browsers (repl.it, onlinegdb.com, ideone.com...). For my own application (education purposes) I would like to do the same on my web backend.
My current solution is to use an Alpine Docker container with gcc constrained with ulimits
. To avoid mounting files I simply use gcc in stdin/stdout with :
protected $container = "frolvlad/alpine-gcc";
protected $cc = "gcc";
protected $cflags = "--static -std=c99 -Wall -pedantic";
protected $ulimit = [
'locks' => 10,
'sigpending' => 100,
'rttime' => 1,
'nproc' => 1,
'nofile' => 50,
'msgqueue' => 1000,
'core' => 1,
'cpu' => 2,
'fsize' => 1000000,
'memlock' => 1000,
];
protected function execute($cmd, $args=[], $stdin=null, $env=[])
{
$descriptorspec = [
0 => ["pipe", "r"], // stdin
1 => ["pipe", "w"], // stdout
2 => ["pipe", "w"], // stderr
];
$cwd = '/tmp';
$ulimits = $this->getUlimitOptions();
$docker = "docker run --stop-timeout 1 -i --rm $ulimits $this->container";
$process = proc_open("$docker $cmd", $descriptorspec, $pipes, $cwd, $env);
if (is_resource($process)) {
if ($stdin) {
fwrite($pipes[0], $stdin);
fclose($pipes[0]);
}
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$exit_status = proc_get_status($process)['exitcode'];
}
return (object)[
'stdout' => $stdout,
'stderr' => $stderr,
'exit_status' => $exit_status
];
}
public function build($code, $args=[])
{
return $this->execute("$this->cc -xc $this->cflags -o/dev/fd/1 -", $args, $code);
}
The execution is done the same with
public function run($executable, $args=[])
{
return $this->execute("cat > a.out && chmod +x a.out && timeout 1 ./a.out", $args, $executable);
}
Would this solution be secure enough and what would be the possible improvements?
Of course, the backend API is throttled, and only authenticated users can access the build interface.
Obviously codes such as the following would be still an issue:
#include <stdio.h>
int main() {
while(1) printf("hello, world!\\n");
}