I am working on providing an environment for running users' untrusted python code. I use the python bindings of libseccomp
library to avoid triggering unsafe system calls, and the service is running in a docker container.
Here is the script that will be executed in my environment.
P.S. The list of banned syscalls is from this project: https://github.com/langgenius/dify-sandbox/blob/f40de1f6bc5f87d0e847cbf52076280bf61c05d5/internal/static/python_syscall/syscalls_amd64.go
import sys
from seccomp import *
import json
import requests
import datetime
import math
import re
import os
import signal
import urllib.request
allowed_syscalls_str = "syscall.SYS_NEWFSTATAT, syscall.SYS_IOCTL, syscall.SYS_LSEEK, syscall.SYS_GETDENTS64,syscall.SYS_WRITE, syscall.SYS_CLOSE, syscall.SYS_OPENAT, syscall.SYS_READ,syscall.SYS_FUTEX,syscall.SYS_MMAP, syscall.SYS_BRK, syscall.SYS_MPROTECT, syscall.SYS_MUNMAP, syscall.SYS_RT_SIGRETURN,syscall.SYS_MREMAP,syscall.SYS_SETUID, syscall.SYS_SETGID, syscall.SYS_GETUID,syscall.SYS_GETPID, syscall.SYS_GETPPID, syscall.SYS_GETTID,syscall.SYS_EXIT, syscall.SYS_EXIT_GROUP,syscall.SYS_TGKILL, syscall.SYS_RT_SIGACTION, syscall.SYS_IOCTL,syscall.SYS_SCHED_YIELD,syscall.SYS_SET_ROBUST_LIST, syscall.SYS_GET_ROBUST_LIST, syscall.SYS_RSEQ,syscall.SYS_CLOCK_GETTIME, syscall.SYS_GETTIMEOFDAY, syscall.SYS_NANOSLEEP,syscall.SYS_EPOLL_CREATE1,syscall.SYS_EPOLL_CTL, syscall.SYS_CLOCK_NANOSLEEP, syscall.SYS_PSELECT6,syscall.SYS_TIME,syscall.SYS_RT_SIGPROCMASK, syscall.SYS_SIGALTSTACK, syscall.SYS_CLONE,syscall.SYS_MKDIRAT,syscall.SYS_MKDIR,syscall.SYS_SOCKET, syscall.SYS_CONNECT, syscall.SYS_BIND, syscall.SYS_LISTEN, syscall.SYS_ACCEPT, syscall.SYS_SENDTO, syscall.SYS_RECVFROM,syscall.SYS_GETSOCKNAME, syscall.SYS_RECVMSG, syscall.SYS_GETPEERNAME, syscall.SYS_SETSOCKOPT, syscall.SYS_PPOLL, syscall.SYS_UNAME,syscall.SYS_SENDMSG, syscall.SYS_SENDMMSG, syscall.SYS_GETSOCKOPT,syscall.SYS_FSTAT, syscall.SYS_FCNTL, syscall.SYS_FSTATFS, syscall.SYS_POLL, syscall.SYS_EPOLL_PWAIT"
allowed_syscalls_tmp = allowed_syscalls_str.split(',')
L = []
for item in allowed_syscalls_tmp:
item = item.strip()
parts = item.split('.')[1][4:].lower()
L.append(parts)
# create a filter object with a default KILL action
f = SyscallFilter(defaction=KILL)
for item in L:
f.add_rule(ALLOW, item)
f.add_rule(ALLOW, 307)
f.add_rule(ALLOW, 318)
f.add_rule(ALLOW, 334)
f.load()
#User's code, triggers ZeroDivision
a = 10 / 0
However, since the syscall open
is banned, I can't provide the full error message for users. Is it safe to provide both open
and write
for users? Or is there another way to get the full traceback message? Thanks.
EDIT: You will have to grant write access to stdout
and stderr
. Since these files are opened as the process is started, you can selectively restrict write access to these files only without having to worry about untrusted code modifying other files.
You can add write permissions to stdout
and stderr
in your code like this:
f.add_rule(ALLOW, "open")
f.add_rule(ALLOW, "close")
f.add_rule(ALLOW, "write", Arg(0, EQ, sys.stdout.fileno()))
f.add_rule(ALLOW, "write", Arg(0, EQ, sys.stderr.fileno()))
In case you would like read access from stdin, it can be added as:
f.add_rule(ALLOW, "read", Arg(0, EQ, sys.stdin.fileno()))
You can see an example using these filter rules from the seccomp
library source code here. You might also find this blog on python sandboxes useful too.
Original approach:
You can use the traceback
library for this. It has a try/except block where you can place the user's code in try and catch and print any exceptions.
This code example shows the use of this library with your example:
# importing module
import traceback
try:
a = 10/0
except:
# printing stack trace
traceback.print_exc()
The output would be similar to:
Traceback (most recent call last):
File "example.py", line 5, in <module>
a = 10/0
ZeroDivisionError: division by zero