pythonsecuritysandboxtracebackseccomp

How to get full Traceback messages when the 'open' syscall is banned?


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.


Solution

  • 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