diff --git a/api/main.go b/api/main.go index 5ff0d8a..9c10f5d 100644 --- a/api/main.go +++ b/api/main.go @@ -6,10 +6,8 @@ import ( "fmt" "io/ioutil" "net/http" - "os" "os/exec" "strings" - "strconv" "time" ) @@ -25,7 +23,6 @@ type problem struct { } type outbound struct { - Generator string `json:"instance"` Ran bool `json:"ran"` Output string `json:"output"` } @@ -35,15 +32,6 @@ var instance int func main() { port := 2000 - if len(os.Args) > 1 { - if i, err := strconv.Atoi(os.Args[1]); err == nil { - instance = i - port += i - } - } else { - instance = 0 - } - fmt.Println("starting api on port", port) http.HandleFunc("/execute", Execute) @@ -111,13 +99,7 @@ func launch(request inbound, res http.ResponseWriter) { args = append(args, strings.Join(request.Args, "\n")) // set up the execution - //cmd := exec.Command("../docker/execute", args...) cmd := exec.Command("../lxc/execute", args...) - cmd.Env = os.Environ() - - if instance > 0 { - cmd.Env = append(cmd.Env, fmt.Sprintf("SOCKET=unix:///var/run/docker-%d.sock", instance)) - } // capture out/err var stdout, stderr bytes.Buffer @@ -128,7 +110,6 @@ func launch(request inbound, res http.ResponseWriter) { // prepare response outbound := outbound{ - Generator: fmt.Sprintf("docker-%d", instance), Ran: err == nil, Output: strings.TrimSpace(stdout.String()), } diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 1dddc0e..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM ubuntu:18.04 - -WORKDIR /tmp - -RUN useradd -m runner - -RUN echo 'runner soft nproc 256' >> /etc/security/limits.conf -RUN echo 'runner hard nproc 256' >> /etc/security/limits.conf -RUN echo 'runner soft nofile 4096' >> /etc/security/limits.conf -RUN echo 'runner hard nofile 4096' >> /etc/security/limits.conf - -RUN sed -i 's/http:\/\/archive.ubuntu.com\/ubuntu\//http:\/\/mirror.math.princeton.edu\/pub\/ubuntu\//' /etc/apt/sources.list -RUN apt-get update && \ - echo "tzdata tzdata/Areas select America" > /tmp/tzseed.txt; \ - echo "tzdata tzdata/Zones/America select Chicago" >> /tmp/tzseed.txt; \ - debconf-set-selections /tmp/tzseed.txt && \ - apt-get install -y tzdata && \ - rm /tmp/tzseed.txt && \ - apt-get -y install nano dpkg-dev build-essential python python3 ruby nodejs golang php7.2 r-base mono-complete nasm openjdk-8-jdk diff --git a/docker/build b/docker/build deleted file mode 100755 index 1bbbedc..0000000 --- a/docker/build +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker build -t piston . diff --git a/docker/execute b/docker/execute deleted file mode 100755 index a0db8e6..0000000 --- a/docker/execute +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env bash - -dir="$( cd "$( dirname "$0" )" && pwd )" - -if [ -z "$1" ]; then - echo "invalid args" - exit -fi -if [ -z "$2" ]; then - echo "invalid args" - exit -fi - -lang=$1 -filepath=$(realpath $2) -file=$(basename $2) -argpath="/tmp/$(date +%s%N).args" -arg=$(basename $argpath) -socket=${SOCKET:-unix:///var/run/docker.sock} - -# write arg file -echo "${@:3}" > $argpath - -bin= -case "$lang" in -"python2") - bin=executor_python2 - ;; -"python" | "python3") - bin=executor_python3 - ;; -"ruby") - bin=executor_ruby - ;; -"javascript" | "js" | "node") - bin=executor_node - ;; -"c") - bin=executor_c - ;; -"cpp" | "c++") - bin=executor_cpp - ;; -"go") - bin=executor_go - ;; -"c#" | "csharp" | "cs") - bin=executor_csharp - ;; -"r") - bin=executor_r - ;; -"php") - bin=executor_php - ;; -"nasm" | "asm") - bin=executor_nasm - ;; -"java") - bin=executor_java - ;; -*) - echo "invalid language" - exit -esac - -docker \ - -H $socket \ - run \ - -m 64m \ - --network none \ - --rm \ - --log-driver none \ - -v $filepath:/$file:ro \ - -v $argpath:/$arg:ro \ - -v $dir/executors/python2:/executor_python2:ro \ - -v $dir/executors/python3:/executor_python3:ro \ - -v $dir/executors/ruby:/executor_ruby:ro \ - -v $dir/executors/node:/executor_node:ro \ - -v $dir/executors/c:/executor_c:ro \ - -v $dir/executors/cpp:/executor_cpp:ro \ - -v $dir/executors/go:/executor_go:ro \ - -v $dir/executors/csharp:/executor_csharp:ro \ - -v $dir/executors/r:/executor_r:ro \ - -v $dir/executors/php:/executor_php:ro \ - -v $dir/executors/nasm:/executor_nasm:ro \ - -v $dir/executors/java:/executor_java:ro \ - piston \ - runuser \ - -l runner \ - -c "bash /$bin /$file $file 2>&1 | head -c 65536" - -rm -f /tmp/$file -rm -f /tmp/$arg diff --git a/docker/executors/c b/docker/executors/c deleted file mode 100755 index d4467bf..0000000 --- a/docker/executors/c +++ /dev/null @@ -1,2 +0,0 @@ -timeout -s KILL 10 gcc -o binary -x c $1 -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' ./binary" diff --git a/docker/executors/cpp b/docker/executors/cpp deleted file mode 100755 index fd3ed82..0000000 --- a/docker/executors/cpp +++ /dev/null @@ -1,2 +0,0 @@ -timeout -s KILL 10 g++ -o binary -x c++ $1 -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' ./binary" diff --git a/docker/executors/csharp b/docker/executors/csharp deleted file mode 100755 index 410cd60..0000000 --- a/docker/executors/csharp +++ /dev/null @@ -1,3 +0,0 @@ -cp /*.code . -timeout -s KILL 10 mcs $(echo $1 | sed 's/\///') -out:binary -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' mono binary" diff --git a/docker/executors/go b/docker/executors/go deleted file mode 100755 index bcc2d40..0000000 --- a/docker/executors/go +++ /dev/null @@ -1,5 +0,0 @@ -cp /*.code interim.go -file="interim.go" -timeout -s KILL 10 go build $file -file=${file%%.*} -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' ./$file" diff --git a/docker/executors/java b/docker/executors/java deleted file mode 100755 index fe30da1..0000000 --- a/docker/executors/java +++ /dev/null @@ -1,5 +0,0 @@ -cp /*.code interim.java -name=$(cat interim.java | grep -Eo 'public\s+class\s+([A-Za-z0-9]+)' | sed -n 's/ */ /gp' | cut -d' ' -f3) -mv interim.java $name.java -timeout -s KILL 10 javac $name.java -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' java $name" diff --git a/docker/executors/nasm b/docker/executors/nasm deleted file mode 100755 index bbd0923..0000000 --- a/docker/executors/nasm +++ /dev/null @@ -1,3 +0,0 @@ -timeout -s KILL 10 nasm -f elf64 -o binary.o $1 -timeout -s KILL 10 ld binary.o -o binary -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' ./binary" diff --git a/docker/executors/node b/docker/executors/node deleted file mode 100755 index de37518..0000000 --- a/docker/executors/node +++ /dev/null @@ -1 +0,0 @@ -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' nodejs $1" diff --git a/docker/executors/php b/docker/executors/php deleted file mode 100755 index 6baa0ee..0000000 --- a/docker/executors/php +++ /dev/null @@ -1 +0,0 @@ -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' php $1" diff --git a/docker/executors/python2 b/docker/executors/python2 deleted file mode 100755 index a6ef3f2..0000000 --- a/docker/executors/python2 +++ /dev/null @@ -1 +0,0 @@ -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' python2 $1" diff --git a/docker/executors/python3 b/docker/executors/python3 deleted file mode 100755 index 13a99c6..0000000 --- a/docker/executors/python3 +++ /dev/null @@ -1 +0,0 @@ -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' python3.6 $1" diff --git a/docker/executors/r b/docker/executors/r deleted file mode 100755 index 1941193..0000000 --- a/docker/executors/r +++ /dev/null @@ -1 +0,0 @@ -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' Rscript $1" diff --git a/docker/executors/ruby b/docker/executors/ruby deleted file mode 100755 index f058a29..0000000 --- a/docker/executors/ruby +++ /dev/null @@ -1 +0,0 @@ -timeout -s KILL 3 bash -c "cat /*.args | xargs -d '\n' ruby $1" diff --git a/lxc/notes.txt b/lxc/notes.txt deleted file mode 100644 index 41bcf7f..0000000 --- a/lxc/notes.txt +++ /dev/null @@ -1,38 +0,0 @@ -# install -yum install lxc lxc-templates debootstrap libvirt - -# create container -lxc-create -t download -n piston -select ubuntu, bionic, amd64 - -# start container -lxc-start -n piston -d - -# shell in and install stuff -lxc-attach -n piston -export PATH=/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin -sed -i 's/http:\/\/archive.ubuntu.com\/ubuntu/http:\/\/mirror.math.princeton.edu\/pub\/ubuntu/' /etc/apt/sources.list -apt-get update -apt-get -y install git tzdata nano dpkg-dev build-essential python python3 ruby nodejs golang php7.2 r-base mono-complete nasm openjdk-8-jdk -apt-get -y install ubuntu-make -umake swift -set dir to /opt/swift/swift-lang -ln -s /opt/swift/swift-lang/usr/bin/swift /usr/bin/swift - - -# create users and apply limits -for i in {1..150}; do - #userdel runner$i - useradd -M runner$i - usermod -d /tmp runner$i - echo "runner$i soft nproc 64" >> /etc/security/limits.conf - echo "runner$i hard nproc 64" >> /etc/security/limits.conf - echo "runner$i soft nofile 2048" >> /etc/security/limits.conf - echo "runner$i hard nofile 2048" >> /etc/security/limits.conf -done - -# remove home dir -rm -rf /home/ubuntu - -# set tmp -chmod 755 /tmp diff --git a/readme.md b/readme.md index 35dd8bb..ce5f434 100644 --- a/readme.md +++ b/readme.md @@ -4,42 +4,85 @@ from from EMKC contests and challenges. It's also used in the Engineer Man Disco [felix bot](https://github.com/engineer-man/felix). #### Installation -- Install Docker https://www.docker.com/get-started -- `git clone https://github.com/engineer-man/piston` -- `cd piston/docker` -- `./build` +``` +# clone and enter repo +git clone https://github.com/engineer-man/piston +cd piston/lxc + +# install dependencies + +# centos: +yum install epel-release +yum install lxc lxc-templates debootstrap libvirt + +# everything else: +# not documented, please open pull requests with commands for ubuntu/debian/arch/macos + +# start libvirtd +systemctl start libvirtd + +# create and start container +lxc-create -t download -n piston -- --dist ubuntu --release bionic --arch amd64 +./start + +# open a shell to the containr +./shell + +# install all necessary piston dependencies +export PATH=/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin +sed -i 's/http:\/\/archive.ubuntu.com\/ubuntu/http:\/\/mirror.math.princeton.edu\/pub\/ubuntu/' /etc/apt/sources.list +apt-get update +apt-get -y install git tzdata nano \ + dpkg-dev build-essential python python3 \ + ruby nodejs golang php7.2 r-base mono-complete \ + nasm openjdk-8-jdk ubuntu-make +# IMPORTANT: set dir to /opt/swift/swift-lang +umake swift +ln -s /opt/swift/swift-lang/usr/bin/swift /usr/bin/swift +rm -rf /home/ubuntu +chmod 777 /tmp + +# create runnable users and apply limits +for i in {1..150}; do + useradd -M runner$i + usermod -d /tmp runner$i + echo "runner$i soft nproc 64" >> /etc/security/limits.conf + echo "runner$i hard nproc 64" >> /etc/security/limits.conf + echo "runner$i soft nofile 2048" >> /etc/security/limits.conf + echo "runner$i hard nofile 2048" >> /etc/security/limits.conf +done + +# leave container +exit + +# optionally run tests +cd ../tests +./test_all_lxc +``` #### Usage -- `docker/execute [language] [path] [arg]...` +- `lxc/execute [language] [path] [arg]...` #### Supported Languages Currently python2, python3, c, c++, go, node, ruby, r, c#, nasm, php, and java is supported. #### Principle of Operation -Piston utilizes Docker as the primary mechanism for sandboxing. There is a small API written in Go which takes -in execution requests and spawns new containers to execute the source from that request. High level, the API writes -a temporary source file to `/tmp` and that gets mounted read-only along with the execution scripts into the container. +Piston utilizes LXC as the primary mechanism for sandboxing. There is a small API written in Go which takes +in execution requests and executes them in the container. High level, the API writes +a temporary source and args file to `/tmp` and that gets mounted read-only along with the execution scripts into the container. The source file is either ran or compiled and ran (in the case of languages like c, c++, c#, go, etc.). #### Security -Docker provides a great deal of security out of the box. Piston takes additional steps to make it resistant to +LXC provides a great deal of security out of the box in that it's separate from the system. +Piston takes additional steps to make it resistant to various privilege escalation, denial-of-service, and resource saturation threats. These steps include: - Disabling outgoing network interaction -- Capping memory at 64mb (resists RAM saturation) - Capping max processes at 16 (resists `:(){ :|: &}:;`, `while True: os.fork()`, etc.) - Capping max files at 256 (resists various file based attacks) - Mounting all resources read-only (resists `sudo rm -rf --no-preserve-root /`) - Capping runtime execution at 3 seconds - Capping stdout to 65536 characters (resists yes/no bombs and runaway output) - SIGKILLing misbehaving code -- Disabling journald logs (resists log flood) - -#### Performance -One thing that needs investigation is how to spawn containers faster. The Docker daemon is synchronous in its -container spawning. This means the bottleneck for code execution is how fast containers can start. Environments -vary, but, in ours they start at a rate of no more than 1-2 per second. One possibility is Docker in Docker where by -X number of containers stay running all the time and then requests are delivered to each in a round robin and spawn -new Piston containers. #### License Piston is licensed under the MIT license. diff --git a/tests/test.bf b/tests/test.bf new file mode 100644 index 0000000..12c2876 --- /dev/null +++ b/tests/test.bf @@ -0,0 +1 @@ ++[----->+++<]>.++++++++..-----------. diff --git a/tests/test_all_lxc b/tests/test_all_lxc index e39e083..63041d4 100755 --- a/tests/test_all_lxc +++ b/tests/test_all_lxc @@ -24,3 +24,5 @@ echo 'testing r' ../lxc/execute r test.r echo 'testing ruby' ../lxc/execute ruby test.rb +echo 'testing brainfuck' +../lxc/execute bf test.bf diff --git a/var/dock@.service b/var/dock@.service deleted file mode 100644 index 1acd190..0000000 --- a/var/dock@.service +++ /dev/null @@ -1,45 +0,0 @@ -[Unit] -Description=Docker Application Container Engine -Documentation=http://docs.docker.com -After=network.target -Wants=docker-storage-setup.service -Requires=docker-cleanup.timer - -[Service] -Type=notify -NotifyAccess=main -EnvironmentFile=-/run/containers/registries.conf -EnvironmentFile=-/etc/sysconfig/docker -EnvironmentFile=-/etc/sysconfig/docker-storage -EnvironmentFile=-/etc/sysconfig/docker-network -Environment=GOTRACEBACK=crash -Environment=DOCKER_HTTP_HOST_COMPAT=1 -Environment=PATH=/usr/libexec/docker:/usr/bin:/usr/sbin -ExecStart=/usr/bin/dockerd-current \ - --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current \ - --default-runtime=docker-runc \ - --exec-opt native.cgroupdriver=systemd \ - --userland-proxy-path=/usr/libexec/docker/docker-proxy-current \ - --init-path=/usr/libexec/docker/docker-init-current \ - --seccomp-profile=/etc/docker/seccomp.json \ - --exec-root=/var/run/docker-%i \ - --graph=/var/lib/docker-%i \ - --host unix:///var/run/docker-%i.sock \ - --pidfile /var/run/docker-%i.pid \ - $OPTIONS \ - $DOCKER_STORAGE_OPTIONS \ - $DOCKER_NETWORK_OPTIONS \ - $ADD_REGISTRY \ - $BLOCK_REGISTRY \ - $INSECURE_REGISTRY \ - $REGISTRIES -ExecReload=/bin/kill -s HUP $MAINPID -LimitNOFILE=1048576 -LimitNPROC=1048576 -LimitCORE=infinity -TimeoutStartSec=0 -Restart=on-abnormal -KillMode=process - -[Install] -WantedBy=multi-user.target diff --git a/var/multidock b/var/multidock deleted file mode 100755 index 4aa7ebc..0000000 --- a/var/multidock +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker -H unix:///var/run/docker-$1.sock ${@:2} diff --git a/var/notes.txt b/var/notes.txt deleted file mode 100644 index c2e4705..0000000 --- a/var/notes.txt +++ /dev/null @@ -1,25 +0,0 @@ -create vm -git piston - -install dock and piston units -systemctl enable dock@{1..10}.service -systemctl start dock@{1..10}.service -systemctl enable piston@{1..10}.service -systemctl start piston@{1..10}.service - -install nginx config - -export image: -./multidock 1 save -o piston.tar piston:latest - -copy in image: -./multidock 1 load -i piston.tar -./multidock 2 load -i piston.tar -./multidock 3 load -i piston.tar -./multidock 4 load -i piston.tar -./multidock 5 load -i piston.tar -./multidock 6 load -i piston.tar -./multidock 7 load -i piston.tar -./multidock 8 load -i piston.tar -./multidock 9 load -i piston.tar -./multidock 10 load -i piston.tar diff --git a/var/piston.nginx b/var/piston.nginx deleted file mode 100644 index d20052c..0000000 --- a/var/piston.nginx +++ /dev/null @@ -1,21 +0,0 @@ -upstream piston { - server 127.0.0.1:2001; - server 127.0.0.1:2002; - server 127.0.0.1:2003; - server 127.0.0.1:2004; - server 127.0.0.1:2005; - server 127.0.0.1:2006; - server 127.0.0.1:2007; - server 127.0.0.1:2008; - server 127.0.0.1:2009; - server 127.0.0.1:2010; -} - -server { - listen 1337; - - location / { - proxy_buffering off; - proxy_pass http://piston; - } -} diff --git a/var/piston@.service b/var/piston.service similarity index 71% rename from var/piston@.service rename to var/piston.service index c105794..87ac487 100644 --- a/var/piston@.service +++ b/var/piston.service @@ -1,12 +1,12 @@ [Unit] -Description=Piston API +Description=Piston [Service] Type=simple User=root -Environment=PATH=/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin +Environment=PATH=/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/go/bin WorkingDirectory=/root/piston/api -ExecStart=/root/piston/api/start %i +ExecStart=/root/piston/api/start Restart=on-failure [Install] diff --git a/var/start_piston b/var/start_piston deleted file mode 100755 index 7cc6559..0000000 --- a/var/start_piston +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -systemctl start piston@1.service -systemctl start piston@2.service -systemctl start piston@3.service -systemctl start piston@4.service -systemctl start piston@5.service -systemctl start piston@6.service -systemctl start piston@7.service -systemctl start piston@8.service -systemctl start piston@9.service -systemctl start piston@10.service -systemctl start dock@1.service -systemctl start dock@2.service -systemctl start dock@3.service -systemctl start dock@4.service -systemctl start dock@5.service -systemctl start dock@6.service -systemctl start dock@7.service -systemctl start dock@8.service -systemctl start dock@9.service -systemctl start dock@10.service diff --git a/var/stop_piston b/var/stop_piston deleted file mode 100755 index a32c740..0000000 --- a/var/stop_piston +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -systemctl stop piston@{1..10}.service -systemctl stop dock@{1..10}.service