Skip to content

Instantly share code, notes, and snippets.

@robshep
Last active July 26, 2024 13:42
Show Gist options
  • Save robshep/3ca42b4aacf98b58aee646ef9f8ecafd to your computer and use it in GitHub Desktop.
Save robshep/3ca42b4aacf98b58aee646ef9f8ecafd to your computer and use it in GitHub Desktop.
Run untrusted python scripts using gvisor

Use RUNSC from GVisor to run untrusted python files

Testing the HTTP service

TLDR

curl -X POST -d 'print("hello untrusted world")'  "http://51.15.86.10:8000"

HTTP testing interface

### POST request to demo server
#
#
# set output.resp to be an object you want back in output: response
# print(...) statements will be added to log:[...] response
POST http://51.15.86.10:8000
Content-Type: text/plain

import time
import os
import tempfile
print('rob')
print(os.getuid())
output.resp = { "level": 42 }


#
# {
#  "output": {
#    "logs": [
#      "rob",
#      999
#    ],
#    "resp": {
#      "level": 42
#    }
#  },
#  "stderr": [
#    ""
#  ]
# }
#

OCI container setup

Taken from https://gvisor.dev/docs/user_guide/quick_start/oci/

Files

  • runner.py The main program that reads code from STDIN compiles and executes it
  • config.json The OCI runtime spec
  • Dockerfile Builds a tiny python docker image
  • script.py Any untrusted user code we want to run on the system

Steps

  • sudo docker build -t python-tiny:latest .
  • mkdir bundle
  • cd bundle
  • mkdir --mode=0755 rootfs
  • sudo docker export $(sudo docker create python-tiny:latest) | sudo tar -xf - -C rootfs --same-owner --same-permissions
  • cp runner.py rootfs
  • cat script.py | sudo runsc -debug run mycontainer
  • sudo runsc list
  • sudo runsc delete <contid>

Notes

  • RLIMIT_CPU doesn't seem to work
  • sockets sometimes get left in /var/run/runsc/ preventing containers with the same ID starting
# consume RAM
print('hello, untrusted python')
import os
print(os.getuid())
import time
print("rob")
a = []
while True:
print(len(a))
a.append(' ' * 10**6)
time.sleep(0.33)
{
"ociVersion": "1.0.0",
"process": {
"user": {
"uid": 999,
"gid": 999
},
"args": [
"/python3",
"-u",
"/runner.py"
],
"env": [
"TERM=xterm"
],
"cwd": "/",
"capabilities": {
"bounding": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"effective": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"inheritable": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"permitted": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
]
},
"rlimits": [
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024
},
{ "type": "RLIMIT_CPU", "hard": 10, "soft": 5 },
{ "type": "RLIMIT_AS", "hard": 152428800, "soft": 226214400}
]
},
"root": {
"path": "rootfs",
"readonly": true
},
"hostname": "runsc",
"mounts": [
{
"destination": "/proc",
"type": "proc",
"source": "proc"
},
{
"destination": "/dev",
"type": "tmpfs",
"source": "tmpfs"
},
{
"destination": "/sys",
"type": "sysfs",
"source": "sysfs",
"options": [
"nosuid",
"noexec",
"nodev",
"ro"
]
}
],
"linux": {
"namespaces": [
{
"type": "pid"
},
{
"type": "network"
},
{
"type": "ipc"
},
{
"type": "uts"
},
{
"type": "mount"
}
]
}
}
# tiny python image
# taken from https://github.com/CrafterKolyan/tiny-python-docker-image/blob/main/Dockerfile.scratch-minimal
ARG PYTHON_VERSION=3.12
FROM alpine:3.20 AS builder
ARG PYTHON_VERSION
RUN apk add --no-cache python3~=${PYTHON_VERSION}
WORKDIR /usr/lib/python${PYTHON_VERSION}
RUN python -m compileall -o 2 .
RUN find . -name "*.cpython-*.opt-2.pyc" | awk '{print $1, $1}' | sed 's/__pycache__\///2' | sed 's/.cpython-[0-9]\{2,\}.opt-2//2' | xargs -n 2 mv
#RUN find . -mindepth 1 | grep -v -E '^\./(encodings)([/.].*)?$' | xargs rm -rf
RUN find . -name "*.py" -delete
RUN find . -name "__pycache__" -exec rm -r {} +
FROM scratch
ARG PYTHON_VERSION
COPY --from=builder /usr/bin/python3 /
# hardcoded ARCH
COPY --from=builder /lib/ld-musl-aarch64.so.1 /lib/ld-musl-aarch64.so.1
# don't need all these bit some python lib won't run
COPY --from=builder /lib/libcrypto.so.3 /lib/libcrypto.so.3
COPY --from=builder /lib/libssl.so.3 /lib/libssl.so.3
COPY --from=builder /lib/libz.so.1 /lib/libz.so.1
COPY --from=builder /usr/lib/libbz2.so.1 /usr/lib/libbz2.so.1
COPY --from=builder /usr/lib/libexpat.so.1 /usr/lib/libexpat.so.1
COPY --from=builder /usr/lib/libffi.so.8 /usr/lib/libffi.so.8
COPY --from=builder /usr/lib/libgdbm.so.6 /usr/lib/libgdbm.so.6
COPY --from=builder /usr/lib/libgdbm_compat.so.4 /usr/lib/libgdbm_compat.so.4
COPY --from=builder /usr/lib/liblzma.so.5 /usr/lib/liblzma.so.5
COPY --from=builder /usr/lib/libmpdec.so.4 /usr/lib/libmpdec.so.4
COPY --from=builder /usr/lib/libncursesw.so.6 /usr/lib/libncursesw.so.6
COPY --from=builder /usr/lib/libpanelw.so.6 /usr/lib/libpanelw.so.6
COPY --from=builder /usr/lib/libreadline.so.8 /usr/lib/libreadline.so.8
COPY --from=builder /usr/lib/libsqlite3.so.0 /usr/lib/libsqlite3.so.0
COPY --from=builder /usr/lib/libpython${PYTHON_VERSION}.so.1.0 /usr/lib/libpython${PYTHON_VERSION}.so.1.0
COPY --from=builder /usr/lib/python${PYTHON_VERSION}/ /usr/lib/python${PYTHON_VERSION}/
ENTRYPOINT ["/python3"]
# read and execute python from STDIN
import os
import base64
import sys
eval(compile(sys.stdin.read(), '<script>', 'exec'), {}, {})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment