Every day the threats facing our computing environments are getting
worse.  Recent incidents in both gems and npm have shown modules
exfiltrating information from developers' machines or production
servers.  It is likely that soon package managers will also be targeted
to install cryptolockers to attack developers' machines, or worse,
install backdoors that allow an attacker to perform arbitrary execution.
How can we make Racket not only a fun and fulfilling development, but
also a safe one?

Thankfully, Jonathan Rees did the kind work of laying out the recipe for
dramatically improved safety over two decades ago:

  http://mumble.net/~jar/pubs/secureos/secureos.html

It turns out that the lambda calculus already provides the fundamental
layer of security we need; code can only perform actions it has
references to.  This is called "object capability (ocap) security";
a decent (ok, I'm biased) intro to the ideas can be found on this
podcast episode if you are interested:

  
https://librelounge.org/episodes/episode-13-object-capabilities-with-kate-sills.html

Racket already has most of the pieces in place; we just need to add one
more thing.  Consider and contrast the following scenarios:

Conventional Racket scenario:

  ;; Runs a game of solitaire.
  ;; Also has access to your entire filesystem, can secretly exfiltrate
  ;; data or install backdoors, etc etc.
  (solitaire)

A more desirable scenario:

  ;; Runs a game of solitaire.
  ;; *Only* has access to a stream of input when the window is active,
  ;; the ability to draw to a *specific* window on the screen, the
  ;; ability to read/write from a single file on disk, the save file.
  (solitaire get-input
             display-graphics-to-window
             save-file-access)

Now the amount of damage that solitiare can perform is significantly
curtailed.  It can perhaps display images we would not like to see
or write nonsense to its save file, but it cannot do anything else.
(We could also put limits in the save-file-access procedure on how
large of a file it is allowed to write, if we were worried about that.)

The key idea here is that authority flows through the system the same
way that data naturally flows through a program.  You simply don't have
more access than what you allowed to flow through.

So what is necessary to make Racket ocap-secure?  The primary problem is
the assumption of "require", that any module can "reach out" and gain
access to anything it likes, be it network access or file access or etc.

What I would like instead is to allow modules to be "granted" access.
The idea is in fact the same as with the solitaire-as-procedure solution
above: modules gain access not by reaching out and grabbing whatever
they want, but by being "handed" access.  Imagine your module as one big
procedure where we pass in access to other modules/values/macros and
you'll get the idea.



#+BEGIN_SRC racket
  #lang dungeon

  ; passed in, already fully "empowered"
  (require match
           graphics-tools
           keyboard-tools
           ;; the parent module must trust us a lot, they gave us full
           ;; filesystem access!
           general-file-io
           ;; solitaire needs picture constructors to generate graphics
           (empower solitaire graphics-tools))

  (provide run-solitaire)

  (define (get-input input-source)
    ...)

  (define (display-graphics-to-window ...)
    ...)

  (define (make-save-file-access filename)
    (match-lambda
      [(list 'read) (call-with-input-file filename port->bytes)]
      [(list 'write bytes)
       (call-with-output-file filename
         (lambda (p)
            (write-bytes bytes p))
         #:exists 'replace)]))


  (define (run-solitaire [save-file "~/.racket-solitaire"])
    (solitaire get-input display-graphics-to-window
               (make-save-file-access save-file)))
#+END_SRC

I've handwaved over a couple of things, but this module shouldn't be
afraid that the solitaire procedure that comes from the empowered
solitaire module has access to dangerous things such as the full
filesystem (even though the importer of *this* module trusted it to have
full filesystem access), and neither module has network access.

I don't know how to build this fully; I know that:
 - we need code inspectors to prevent evil hygeine-breaking, but good
   news we have that
 - I need a way to make *something like require* work, but where the
   import-er is able to very explicitly pass in which modules are
   allowed.  This seems to mean something *like* controlling the module
   registry.  Note that this also means that the names of the modules
   being imported aren't from a "global registry" anymore, it's the
   names that the empowering-module provided.

There's an alternative, which is the emaker / frozen realms approach.
This approach actually instead gives you an extremely minimal base
syntax that *cannot be extended*.  No new macros.  Instead, the module
that is imported wakes up in a cold world of just a few primitives... no
mutable state, very few authenticated things, etc.  The module then
returns a function that does the interesting things: we can pass in
arguments to get out the "interesting" things that the module can
provide.  You are mostly left with the cold world of lambda and a few
carefully chosen macros.

But that's kind of sad to throw away macros, imo.  I'm not sure the
racket community will be happy enough to use it.  I'm not sure I would
be.

So, how to build this?  It doesn't seem that there's a way to control
the module registry being used in a require-ing context.  But maybe
there's something else.

Thoughts, or solutions, welcome.
 - Chris

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-users/87y310r3b1.fsf%40dustycloud.org.
For more options, visit https://groups.google.com/d/optout.

Reply via email to