Locking down embedded interpreter for security

Hi there,

We have an embedded system which supports running user plugins for our application. We already have the ability to do this in C with a user’s shared object. However, we don’t want the user to be able to grab any information from the system or do anything they absolutely don’t have to do, such as open files, sockets, and that sort of thing. As such, we have implemented a Linux seccomp filter for the program that is in charge of running plugins. Effectively, the program loads any required resources and files, then removes all syscall permissions such that any attempt to open a file, open a socket, access hardware, and so on will result in SIGKILL.

We are now trying to embed the Python interpreter (using 3.9, though we’re not really hard set on any particular version at the moment) and allow the user to write plugins in Python. However, I can’t find any good way to limit syscalls and such. So far, I have considered just using seccmp, but this will fail when modules are loaded by the plugin. I have also found the auditing API, but it seems very difficult to disallow any opening of files since it also generates and audit event for open when opening a module file. Additionally, it would require a lot of elbow grease, as there are a huge number of audit events we would like to disallow. Finally, I don’t see any particular details on how to stop something from happening when an audit event is received.

Is there a good way to achieve what we are looking to do using the auditing API? If not, I may look into using something like bubblewrap (albeit custom-designed), but I would rather avoid that if possible.

Thanks!

It sounds like you are looking for a way to sandbox Python. PEP 578 auditing hooks are really just for auditing. They are not designed for sandboxing. Steve and I explained the design in a talk at EuroPython two years ago, Christian Heimes, Steve Dower - Auditing hooks and security transparency for CPython - YouTube

Can you restrict user code to verified and signed code? The PyFile_SetOpenCodeHook gives you an API to intercept and verify code files, before they are loaded. You can use it to implement code signing. For technical reasons it doesn’t work for shared libraries – dlopen() takes a file path and Linux does not have dlopenfd().

In my experience an isolated, sandboxed process with bubblewrap, libseccomp, or similar (e)BPF based syscall filtering is the most secure approach. It’s also painful and tedious to implement.

I asked in the Discord server and received a similar response. I don’t really mind implementing a wrapper like that (we already have about 70% of it done for the C plugins anyway), but it seemed like there might be some way to use this to avoid having to do that extra work. However, it is good to know that we could do some sort of code signing type of thing here. I will look into that hook. Thanks!

This might be slightly off-topic for this thread, but is there some other way of loading an arbitrary .py script than using PyImport_Import? At the moment I am just adding the directory we want to the path and then importing the module. I feel like there must be some other way to do this, but I just can’t seem to find it if there is.

spython/spython.c at master · zooba/spython · GitHub is my old code signing PoC for Linux. It uses seccomp, OpenSSL, and xattr.

From the top of my head I don’t know if Python has a high level API for that. cpython/pythonrun.c at master · python/cpython · GitHub might contain some useful bits.

1 Like

Thanks! I’ll take a look at these.

1 Like