Home > Enterprise >  How to execute deterministic code from an untrusted third party safely?
How to execute deterministic code from an untrusted third party safely?

Time:08-02

I understand that there are several questions similar to mine, however my problem is a little different and I haven't found a proper answer.

I know a simple way to run code from untrusted sources is to create a container, a jail with limited resources, and wait for a timeout; but I would like a different solution. I need the result to be deterministic, that is, this code cannot have any side effects, not even in an isolated environment. The code will receive an input and must always return the same output based on that input.

The natural way I thought was to require this code to be purely functional with no side effects. I thought of the Haskell language. Is it possible to somehow disable side effects in Haskell (monads) and run code purely functional? How do I execute code in Haskell disabling any possible side effects and all sorts of I/O?

I don't mind at first if the code goes into an infinite loop and uses a lot of memory, but if it were possible to limit the execution time and memory usage it would be even a plus.

CodePudding user response:

Out of the box Haskell doesn't give you the kind of safety you're looking for. unsafePerformIO is an obvious security hole (but there are others too).

unsafePerformIO takes a value of type IO a and converts it to a value of type a; exactly what you're not supposed to be able to do with IO! It does so by "cheating"; hooking the runtime system to actually execute the IO action (of type IO a) if it ever needs the result value (of type a). From a purity point of view it's utterly broken; since it allows you to lie with the type system and give a non-IO type to a computation that will actually perform arbitrary side effects. But sometimes in advanced usage you can build pure interfaces around things that use impurity internally, and that's what it's for.

"Polite" Haskell doesn't use unsafePerformIO to smuggle side effects that matter1 into pure computations, so we genuinely ignore it when reasoning about pure code. But you're talking about running untrusted code; you can't trust it to be "polite". Using unsafePerformIO to smuggle side effects into pure functions is exactly the sort of thing an adversary will put into their code to break out of your jail. So you can't ignore it (nor other unsafe functions; the known-unsafe ones provided by GHC will have unsafe in the name). Basically, Haskell is not inherently safer than C in this regard (indeed someone can use the FFI to call arbitrary C from Haskell and call it pure!); it uses purity as a language feature to help developers write code, not as a security feature to restrict code you don't trust. Indeed even compiling untrusted Haskell code is not actually safe in this sense; compile-time code (e.g. using TemplateHaskell) can execute arbitrary side effects!

You may be interested in Safe Haskell; this is an opt-in system (through language extensions) in GHC that tries to lock down the "back door" features of Haskell, so that (among other guarantees) you can trust that a pure computation (that does not have an IO type) is actually pure.

WARNING: I've never actually attempted to use Safe Haskell, and I can't speak to its suitability for your purpose. My understanding is that you cannot simply turn on LANGUAGE Safe and compile and run any old code. It's not that safe. It is a tool that hardens up Haskell's type system guarantees so that you can use those guarantees as part of the restrictions you need to build a sandbox for running untrusted code, but I don't believe Haskell's type system guarantees are sufficient on their own. You should definitely do further research if you want to use Safe Haskell for this purpose.


1 Of course, which side effects "matter" is a matter of taste and context-dependency, and upstream code might not always agree with you on this.

  • Related