8

I've been working on a personal project in C# whose purpose is more or less to allow the user to execute scripts written by other users and restrict the permissions of that script. My program compiles the scripts using a third-party library, sandboxes them using the .NET Code Access Security mechanisms and makes sure that they have only those permissions that the user wants to give them.

Broadly speaking, my security requirements are:

  • the user should be able to restrict the untrusted script's access to only certain parts of the filesystem, including forbidding all filesystem access
  • the user should be able to restrict the untrusted script's network connections to only certain IP addresses or hostnames, including forbidding all network connections
  • it is okay if the user script manages to hang or terminate the host application, but the user script must not be able to circumvent the permission restrictions (i.e. denial of service is okay, breach isn't)

I'm contemplating trying to do something similar in C++, as a sort of a personal exercise. Obviously, things are more complicated when running native code directly, even if the user scripts are written in a scripting language like Lua.

The first approach I can think of is to insert my own hooks in the scripting environments standard library functions. For example, if the scripting language is Lua, instead of exposing io.open normally, I would have to expose a wrapper that checked the arguments against the script's permissions before passing them to the original implementation.

My concern with this approach is that it drastically increases the amount of my own code that is in charge of security and, therefore, a potential security vulnerability that I wrote myself. In other words, when working with .NET CAS, I can trust that Microsoft did its job well in the sandboxing code, as opposed to having to trust my own sandboxing code.

Are there any alternatives I'm unaware of?

  • 1
  • @RobertHarvey Linux containerization techniques like Seccomp generally only help when running the untrusted code in a separate process, but aren't applicable for in-process isolation. Of course, a fairly simple solution to the problem could include exactly that, a separate process that acts as a service for the main program and only communicates over IPC mechanisms. I wonder if Windows has similar OS-level isolation features? – amon Mar 13 at 21:51
  • 1
    @amon: You mean like a... Windows process? Sure it does. – Robert Harvey Mar 13 at 21:58
  • First off you've introduced the greatest vulnerability into your system already: Foreign Code. Everything else is an attempt to mitigate that harm. Essentially you are constructing an interpreter that hopefully executes that foreign code safely. Go and brush up on your understanding of interpretation then apply those ideas to enforce the behaviours and relative safety you desire. Secondly do not presume that a sandbox is safe, always add extra layers of defence. – Kain0_0 Mar 14 at 5:19
  • 1
    @VojislavStojkovic Can't you just use a javascript engine? By default these engines don't have access to filesystem (not entirely sure about the network but you can check). One example is QJSEngine. Also with these engines you can provide custom c++ code functions to javascript for interoperability. – RandomGuy Mar 14 at 14:53
1

As others have stated, running foreign code is the greatest issue regarding this sort of implementation. Like Kain0_0's comment suggested, a VM would be the most appropriate way to maintain foreign code liberty without compromising the host machine (too much). This is basically what Continuous Integration services like CircleCI do.

This also makes it much easier to implement the interface since you can pull images with all the configuration and security features you want. You also don't have to worry if their code is going to manage to run in your host.

So for this, I would:

  • Make snapshots of user script environments I want to cover with Docker (one environment for C#, one for Python, etc, with the appropriate security configurations)

  • Upon user request, spin up Docker relevant instances through the code trigger, injecting the foreign script in Docker instance's entrypoint.

  • The code is run in the Docker instance with user permissions, files are written, maybe a connection is made here and there, get the output and the environment is then destroyed

  • Since Docker containers run as processes, they can be terminated fairly easily i.e. if they exceed a certain time limit. If there's an error, they can terminate immediately.

Basically, have your main code manage the user trigger, entrypoint injection and automated destruction logic for Docker, which is in charge of doing the sandboxing for this sort of operation.

  • 1
    Great answer. If there is something to add to your answer, it would be that all popular sandbox solutions showed numerous flaws allowing the malicious code to escape its sandbox. This is true for CAS (for instance CVE-2015-2504), and even more true for Java (exploits for browser Java plugin too numerous to list here). While Docker does have vulnerabilities too, I have an impression that the attack surface is much lower compared to anything implemented at the level of a given language/framework. – Arseni Mourzenko Jun 9 at 16:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.