guile-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: modify environments to make sandboxes


From: Alan Grover
Subject: Re: modify environments to make sandboxes
Date: Thu, 22 Jun 2006 14:36:54 -0400
User-agent: Mozilla Thunderbird 1.0.6 (X11/20050716)

Ludovic Courtès wrote:
> Hi,
> 
> Neil Jerram <address@hidden> writes:
> 
> 
>>address@hidden (Ludovic Courtès) writes:
>>
>>
>>>Code confinement is indeed an interesting feature.  Fortunately, Guile
>>>offers various ways to do it (AFAIK, there's no standard way to do this
>>>in R5RS Scheme).  :-) [...]

One approach is to write your own security-manager/white-list. You scan
the s-expr, check the symbols in the head of any (sub-)list, and fail if
they aren't approved. The comments of
http://schemecookbook.org/Cookbook/DynamicUntrustedEval gives an outline
of how to do this (minus parsing). You actually have to parse a bit: so
you can ignore argument-lists to lambda, the variables in "let", etc. An
example parser is in your guile distribution, scripts/lint, see the
detect-free-variables procedure (usually somewhere like
/usr/share/guile/1.6/scripts).  Of course, you don't have to parse if
you don't allow let/lambda and friends.

I think there is a R5RS way of doing code-confinement. The R5RS spec
says that the null-environment "is empty except for the (syntactic)
bindings for all syntactic keywords defined in this report that are
either required or both optional and supported by the implementation." I
take it to mean that you are guaranteed the required keywords, might
have the optional keywords, but won't have anything else.

So, you could do this:

(define (safe-eval s-expr)
    (let* (
            (lambda-expr-that-provides-some-procs
                ; extend null-environment with "+"
                `(lambda (+)
                    ,s-expr))
            (actual-lambda
                (eval
                    lambda-expr-that-provides-some-procs
                    (null-environment 5))))
    (actual-lambda +))) ; provide +
(safe-eval '(begin (define (x y) 1) (x 2)))
(safe-eval '1)
(safe-eval '(+ 1 2))
(safe-eval '(- 1 2)) ; fails, there is no "-" available

You can provide more procedures where I just provided "+". The trick has
two parts:

* Eval the untrusted expression in null-environment. A minimum of
procedures/etc. is available.
* "import" the other-procedures by providing them to a lambda, which
provides the lexical environment.

You could "black-list" keywords, maybe like this:
(define (safe-eval s-expr)
    (let* (
            (lambda-expr-that-provides-some-procs
                `(lambda (+ error)
                    ; re-bind "define" to something that won't work
                    (let ((define (lambda args (error "Not allowed"))))
                    ,s-expr)))
            (actual-lambda
                (eval
                    lambda-expr-that-provides-some-procs
                    (null-environment 5))))
    (actual-lambda + error)))
(safe-eval '1)
(safe-eval '(+ 1 2))
(safe-eval '(begin (define (x y) 1) (x 2)))

But, note that this doesn't quite fail the way you want: it will eval (x
y) and then call the local "define", which means you'll get an unbound
error on x. Well, at least you disabled "define".

FYI, here's the list of symbols in null-environment for guile 1.6:

    and begin case cond define define-syntax delay do if
    lambda let let* let-syntax letrec letrec-syntax or
    quasiquote quote set!

Beware, you have to provide a comprehensive black-list. Imagine if the
guile 1.6 null-environment didn't include every optional-keyword, but
1.8 did. Untrusted code could take advantage of the difference. You also
have to carefully consider the impact of allowing any keyword. For
example, "let" would allow the untrusted code to do an infinite loop
(via named-let).

I can't find the place where I originally found this method, but there
is another example at
http://www.cap-lore.com/CapTheory/Language/Scheme/SchemeFactory.html
and the preceding pages. You may want to read these pages to understand
some other issues around untrusted code.
And in thread:
http://ecos.sourceware.org/ml/guile/1999-02/msg00233.html


> Note that control over a module's name space (as described in my post)
> is not the only thing needed to safely evaluate untrusted code.  The
> user would also need finer control over all the resources used by the
> code at hand (in order to prevent DoS attacks), particularly memory
> (heap and stack) and CPU.
> 
> Unfortunately, I don't think this can be realized using Guile, except
> maybe by running the untrusted code in a separate process and relying on
> the OS' resource accounting mechanisms (e.g., `setrlimit' --- but Guile
> core doesn't provide bindings for it).  However, running untrusted code
> in a separate process would preclude, practically, resource sharing with
> the user's trusted code (e.g., an Xchat Scheme plug-in would be useless
> as a separate process because it would be unable to access the data
> structures of the "real" Xchat).
> 
> The "ideal" solution would imply things like:
> 
>   * changing the evaluator so that several evaluators with different
>     `eval-options' can be instantiated (pretty much like the
>     `guile-reader' approach); this way, untrusted code could be
>     evaluated with an evaluator that has custom stack limits;
> 
>   * having, roughly, a `current-heap' fluid that would be referred to
>     anytime heap is allocated (hmm...);
> 
>   * similarly, have CPU time slice capabilities that would be passed
>     to `eval' either explicitly or via a fluid.

You can also write a safe-interpreter in scheme, and thus in the same
process. You solve the above issues by charging the expression each time
it applies a procedure, each time it consumes heap (roughly at each
"cons"), etc.

This one is for chicken:
http://www.call-with-current-continuation.org/eggs/sandbox.scm
And, again, I can't locate a reference to the original example of this,
I believe it was R5RS though.





reply via email to

[Prev in Thread] Current Thread [Next in Thread]