From: Mikael Jansson
Subject: Sandboxing user code?
Date: 
Message-ID: <93f9a6af-9869-434b-b6f4-812894aa06b2@t54g2000hsg.googlegroups.com>
For an application, I want users to be able to load in their own code,
but I don't want them to perform anything related to I/O (unless I
provide with functionality to do that).  They will be able to use the
rest of CL + the functions I've made available.

First thing I was thinking of was making only a small language and
interpret it myself. Then I was thinking of code walking. They all
seem very tedious, though.

Does anyone have a suggestion?

From: Jimmy Miller
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <54807e49-871e-4007-82cd-eb74b8439252@u72g2000hsf.googlegroups.com>
On Mar 22, 3:57 am, Mikael Jansson <··············@gmail.com> wrote:
> For an application, I want users to be able to load in their own code,
> but I don't want them to perform anything related to I/O (unless I
> provide with functionality to do that).  They will be able to use the
> rest of CL + the functions I've made available.
>
> First thing I was thinking of was making only a small language and
> interpret it myself. Then I was thinking of code walking. They all
> seem very tedious, though.
>
> Does anyone have a suggestion?

I was thinking about just this problem the other day, and it seems
like creating a new package would be the best solution.  If you wanted
your users to have access to all functions except a few, say write-
line and format, you would simply create a new packages that shadows
those symbols:

(defpackage :user-package
   (:use :common-lisp)
   (:shadow :write-line :format))

Now the functions write-line and format in user-package will be
undefined.  You can either leave them that way, or provide your own
implementation.
From: Mikael Jansson
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <da981489-d332-4db2-97dd-560ae8a45874@i29g2000prf.googlegroups.com>
On 22 Mar, 13:28, Jimmy Miller <··············@gmail.com> wrote:
> On Mar 22, 3:57 am, Mikael Jansson <··············@gmail.com> wrote:
>
> > For an application, I want users to be able to load in their own code,
> > but I don't want them to perform anything related to I/O (unless I
> > provide with functionality to do that).  They will be able to use the
> > rest of CL + the functions I've made available.
>
> > First thing I was thinking of was making only a small language and
> > interpret it myself. Then I was thinking of code walking. They all
> > seem very tedious, though.
>
> > Does anyone have a suggestion?
>
> I was thinking about just this problem the other day, and it seems
> like creating a new package would be the best solution.  If you wanted
> your users to have access to all functions except a few, say write-
> line and format, you would simply create a new packages that shadows
> those symbols:
>
> (defpackage :user-package
>    (:use :common-lisp)
>    (:shadow :write-line :format))
>
> Now the functions write-line and format in user-package will be
> undefined.  You can either leave them that way, or provide your own
> implementation.

That could work.. Except, I'd want to shadow /all/ functions that do I/
O, and they shouldn't be able to use any other packages that could
possibly perform I/O -- so shadowing everything would be impossible :/

Thanks for the suggestion, though!

--
Mikael Jansson
http://mikael.jansson.be
From: Pascal Bourguignon
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <873aqfu52m.fsf@thalassa.informatimago.com>
Mikael Jansson <··············@gmail.com> writes:

> On 22 Mar, 13:28, Jimmy Miller <··············@gmail.com> wrote:
>> On Mar 22, 3:57 am, Mikael Jansson <··············@gmail.com> wrote:
>>
>> > For an application, I want users to be able to load in their own code,
>> > but I don't want them to perform anything related to I/O (unless I
>> > provide with functionality to do that). �They will be able to use the
>> > rest of CL + the functions I've made available.
>>
>> > First thing I was thinking of was making only a small language and
>> > interpret it myself. Then I was thinking of code walking. They all
>> > seem very tedious, though.
>>
>> > Does anyone have a suggestion?
>>
>> I was thinking about just this problem the other day, and it seems
>> like creating a new package would be the best solution. �If you wanted
>> your users to have access to all functions except a few, say write-
>> line and format, you would simply create a new packages that shadows
>> those symbols:
>>
>> (defpackage :user-package
>> � �(:use :common-lisp)
>> � �(:shadow :write-line :format))
>>
>> Now the functions write-line and format in user-package will be
>> undefined. �You can either leave them that way, or provide your own
>> implementation.
>
> That could work.. Except, I'd want to shadow /all/ functions that do I/
> O, and they shouldn't be able to use any other packages that could
> possibly perform I/O -- so shadowing everything would be impossible :/

Not only I/O functions you have to shadow, but also functions that can
give you access to I/O functions:  

 (cl:with-open-file ...)
 #.(cl:with-open-file ...)
 (funcall (intern "OPEN" "CL") ...)

So you need to define your own reader, to prevent using symbols from
random packages.   Have a look at the following reader which has a
hook to parse tokens: READTABLE-PARSE-TOKEN.
http://darcs.informatimago.com/darcs/public/lisp/common-lisp/reader.lisp
http://darcs.informatimago.com/darcs/public/lisp/common-lisp/ 

And you need to shadow funtions such as INTERN that deal with packages.

Once you've prevented the user to get out of the given package, you
can populate a package with the functions you want to allow, and let
the users loose in it.



-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

"Indentation! -- I will show you how to indent when I indent your skull!"
From: Mikael Jansson
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <cadc7876-38a5-449b-8918-9c1b83e1bb85@d21g2000prf.googlegroups.com>
On Mar 24, 11:31 pm, Pascal Bourguignon <····@informatimago.com>
wrote:
> Mikael Jansson <··············@gmail.com> writes:
> > On 22 Mar, 13:28, Jimmy Miller <··············@gmail.com> wrote:
> >> On Mar 22, 3:57 am, Mikael Jansson <··············@gmail.com> wrote:
>
> >> > For an application, I want users to be able to load in their own code,
> >> > but I don't want them to perform anything related to I/O (unless I
> >> > provide with functionality to do that).  They will be able to use the
> >> > rest of CL + the functions I've made available.
>
> >> > First thing I was thinking of was making only a small language and
> >> > interpret it myself. Then I was thinking of code walking. They all
> >> > seem very tedious, though.
>
> >> > Does anyone have a suggestion?
>
> >> I was thinking about just this problem the other day, and it seems
> >> like creating a new package would be the best solution.  If you wanted
> >> your users to have access to all functions except a few, say write-
> >> line and format, you would simply create a new packages that shadows
> >> those symbols:
>
> >> (defpackage :user-package
> >>    (:use :common-lisp)
> >>    (:shadow :write-line :format))
>
> >> Now the functions write-line and format in user-package will be
> >> undefined.  You can either leave them that way, or provide your own
> >> implementation.
>
> > That could work.. Except, I'd want to shadow /all/ functions that do I/
> > O, and they shouldn't be able to use any other packages that could
> > possibly perform I/O -- so shadowing everything would be impossible :/
>
> Not only I/O functions you have to shadow, but also functions that can
> give you access to I/O functions:
>
>  (cl:with-open-file ...)
>  #.(cl:with-open-file ...)
>  (funcall (intern "OPEN" "CL") ...)
>
> So you need to define your own reader, to prevent using symbols from
> random packages.   Have a look at the following reader which has a
> hook to parse tokens: READTABLE-PARSE-TOKEN.http://darcs.informatimago.com/darcs/public/lisp/common-lisp/reader.lisphttp://darcs.informatimago.com/darcs/public/lisp/common-lisp/
>
> And you need to shadow funtions such as INTERN that deal with packages.
>
> Once you've prevented the user to get out of the given package, you
> can populate a package with the functions you want to allow, and let
> the users loose in it.
>
Is there an easy way to disallow people from escaping the package?  Or
is that what I'd do w/ R-P-T?

--
Mikael Jansson
http://mikael.jansson.be
From: Pascal J. Bourguignon
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <7cskyfszib.fsf@pbourguignon.anevia.com>
Mikael Jansson <··············@gmail.com> writes:

> On Mar 24, 11:31 pm, Pascal Bourguignon <····@informatimago.com>
> wrote:
>> [...]
>> So you need to define your own reader, to prevent using symbols from
>> random packages.   Have a look at the following reader which has a
>> hook to parse tokens: READTABLE-PARSE-TOKEN.http://darcs.informatimago.com/darcs/public/lisp/common-lisp/reader.lisphttp://darcs.informatimago.com/darcs/public/lisp/common-lisp/
>>
>> And you need to shadow funtions such as INTERN that deal with packages.
>>
>> Once you've prevented the user to get out of the given package, you
>> can populate a package with the functions you want to allow, and let
>> the users loose in it.
>>
> Is there an easy way to disallow people from escaping the package?  
> Or is that what I'd do w/ R-P-T?

No, in standard CL, there's no easy way to prevent it.  You have to
implement your own reader to prevent reading abc:def as the symbol DEF
in the package ABC.  You could implement your reader by putting a
reader macro on all the characters, but I think that would be rather
awkward to program a reader like this, and inefficient.

My reader provides this readtable-parse-token hook that takes a token
object and should return two values: (values T lisp-object) or 
(values NIL error-message-string).

I've not exported yet the accesors for the token object so you'll have
to use ::

(reader::token-text        token) --> string
(reader::token-traits      token) --> (vector reader::constituent-trait)
(reader::token-length      token) --> integer
(reader::token-char        token index) --> character
(reader::token-char-traits token index) --> reader::constituent-trait

READER::CONSTITUENT-TRAIT is integer, with bits encoding character
constituent traits (see reader.lisp).

(I need some more experience or user opinions about the API to access
these 'internal' structures, before :EXPORTing them).




Here is reader:parse-token, the standard token parser:

(defun parse-token (token)
  "
RETURN:  okp ; the parsed lisp object if okp, or an error message if (not okp)
"
  (let ((message nil))
    (macrolet
        ((rom (&body body)
           "Result Or Message"
           (if (null body)
               'nil
               (let ((vals (gensym)))
                 `(let ((,vals (multiple-value-list ,(car body))))
                    ;; (format *trace-output* "~S --> ~S~%" ',(car body) ,vals)
                    (if (first ,vals)
                        (values-list ,vals)
                        (progn
                          (when (second ,vals)
                            (setf message  (third ,vals)))
                          (rom ,@(cdr body)))))))))
      (multiple-value-bind (ok type object)
          (rom (parse-decimal-integer-token token)
               (parse-integer-token         token)
               (parse-ratio-token           token)
               (parse-float-1-token         token)
               (parse-float-2-token         token)
               ;; (parse-consing-dot-token     token)
               (parse-symbol-token          token))
        (declare (ignorable type))
        ;; (format *trace-output* "ok = ~S ; type = ~S ; object = ~S~%"
        ;;         ok type object)
        (values ok (if ok object message))))))


To disable reading qualified symbols, you would just change the last clause of ROM:

(defvar *user-package* (make-package "RESTRICTED-CL-USER" :uses '("RESTRICTED-CL")))


(defun parse-token/disabled-packages (token)
  "
RETURN:  okp ; the parsed lisp object if okp, or an error message if (not okp)
"
  (let ((message nil))
    (macrolet
        ((rom (&body body)
           "Result Or Message"
           (if (null body)
               'nil
               (let ((vals (gensym)))
                 `(let ((,vals (multiple-value-list ,(car body))))
                    ;; (format *trace-output* "~S --> ~S~%" ',(car body) ,vals)
                    (if (first ,vals)
                        (values-list ,vals)
                        (progn
                          (when (second ,vals)
                            (setf message  (third ,vals)))
                          (rom ,@(cdr body)))))))))
      (multiple-value-bind (ok type object)
          (rom (reader::parse-decimal-integer-token token)
               (reader::parse-integer-token         token)
               (reader::parse-ratio-token           token)
               (reader::parse-float-1-token         token)
               (reader::parse-float-2-token         token)
               (values t 'symbol (intern (reader::token-text token) *user-package*)))
        (declare (ignorable type))
        (values ok (if ok object message))))))

(setf (reader:reader-parse-token reader:*readtable*) (function parse-token/disabled-packages))


And as indicated by Stanisław, if you want to avoid DoS, you would
also have to change the reader macros that accept variable sized
objects to impose some limit on memory use.


-- 
__Pascal Bourguignon__
From: Barry Margolin
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <barmar-D88580.10521322032008@newsgroups.comcast.net>
In article 
<····································@u72g2000hsf.googlegroups.com>,
 Jimmy Miller <··············@gmail.com> wrote:

> On Mar 22, 3:57 am, Mikael Jansson <··············@gmail.com> wrote:
> > For an application, I want users to be able to load in their own code,
> > but I don't want them to perform anything related to I/O (unless I
> > provide with functionality to do that).  They will be able to use the
> > rest of CL + the functions I've made available.
> >
> > First thing I was thinking of was making only a small language and
> > interpret it myself. Then I was thinking of code walking. They all
> > seem very tedious, though.
> >
> > Does anyone have a suggestion?
> 
> I was thinking about just this problem the other day, and it seems
> like creating a new package would be the best solution.  If you wanted
> your users to have access to all functions except a few, say write-
> line and format, you would simply create a new packages that shadows
> those symbols:
> 
> (defpackage :user-package
>    (:use :common-lisp)
>    (:shadow :write-line :format))
> 
> Now the functions write-line and format in user-package will be
> undefined.  You can either leave them that way, or provide your own
> implementation.

This is fine if you're just trying to prevent accidental escape from the 
sandbox.  But if you're really trying to be secure it won't work at all, 
since they can trivially get around it by using package qualifiers: 
COMMON-LISP:OPEN, COMMON-LISP:WRITE-LINE, etc.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE don't copy me on replies, I'll read them in the group ***
From: Thomas A. Russ
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <ymifxufq1ws.fsf@blackcat.isi.edu>
Mikael Jansson <··············@gmail.com> writes:

> First thing I was thinking of was making only a small language and
> interpret it myself. Then I was thinking of code walking. They all
> seem very tedious, though.

I would expect that an interpreter would be the way to go.  That way you
really have control over what gets executed.  For lisp-like languages,
it isn't really that bad, unless you really, really want to try to put
most of Common Lisp in there.

There are a number of examples of Lisp interpreters, so you could start
with that and then extend it to handle the additional functions that you
want to be able to support.


-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Stanisław Halik
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <fsasse$hcu$1@news2.task.gda.pl>
thus spoke Mikael Jansson <··············@gmail.com>:

> For an application, I want users to be able to load in their own code,
> but I don't want them to perform anything related to I/O (unless I
> provide with functionality to do that).  They will be able to use the
> rest of CL + the functions I've made available.

Unless you're only worried about privilege escalaton, resource
exhaustion is one possible DoS. The Lisp image may not recover after a
malloc failure caused by sandboxed code.

-- 
Nawet świnka wejdzie na drzewo kiedy ją chwalą.
From: Mikael Jansson
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <ee2fa6c9-9d22-429c-9fb0-b911917ab906@y24g2000hsd.googlegroups.com>
On Mar 25, 1:57 pm, Stanis³aw Halik <··············@tehran.lain.pl>
wrote:
> thus spoke Mikael Jansson <··············@gmail.com>:
>
> > For an application, I want users to be able to load in their own code,
> > but I don't want them to perform anything related to I/O (unless I
> > provide with functionality to do that).  They will be able to use the
> > rest of CL + the functions I've made available.
>
> Unless you're only worried about privilege escalaton, resource
> exhaustion is one possible DoS. The Lisp image may not recover after a
> malloc failure caused by sandboxed code.
>
True. It seems that running a copy of the Lisp image completely
sandboxed itself would be the easiest way to prevent this from
happening. I still wonder how Other Languages(tm) do it. Custom
language and carefully track resource usage to avoid the DoS
mentioned?

> Nawet ¶winka wejdzie na drzewo kiedy j± chwal±.

:)

--
Miku¶
From: Stanisław Halik
Subject: Re: Sandboxing user code?
Date: 
Message-ID: <fskvmq$27g$1@news2.task.gda.pl>
thus spoke Mikael Jansson <··············@gmail.com>:

>> Unless you're only worried about privilege escalaton, resource
>> exhaustion is one possible DoS. The Lisp image may not recover after a
>> malloc failure caused by sandboxed code.

> True. It seems that running a copy of the Lisp image completely
> sandboxed itself would be the easiest way to prevent this from
> happening. I still wonder how Other Languages(tm) do it. Custom
> language and carefully track resource usage to avoid the DoS
> mentioned?

At the very minimum, limit stack depth as well as heap and terminate
gracefully in case of overstep. This has to be tightly integrated with
the language, even something as banal as interned symbols may cause
resource exhaustion DoS. Basically, each running copy has to be
encapsulated and isolated.

If you don't need to share the address space, isolating the Lisp image
sounds OK to me. You know, setuid it nobody, chroot it, setrlimit(2)
nproc, file descriptors.

-- 
Nawet świnka wejdzie na drzewo kiedy ją chwalą.