When should I use `protect-out` in Racket?
Asked Answered
I

1

7

Racket provides protect-out to prevent module exports from being used with eval (or a deconstructed syntax object), unless the module has enough privileges (aka, has a strong enough code inspector). The docs also provide a good example for what it does:

> (module nest racket
    (provide num-eggs (protect-out num-chicks))
    (define num-eggs 2)
    (define num-chicks 3))
> (define weak-inspector (make-inspector (current-code-inspector)))
> (define (weak-eval x)
    (parameterize ([current-code-inspector weak-inspector])
      (define weak-ns (make-base-namespace))
      (namespace-attach-module (current-namespace)
                               ''nest
                               weak-ns)
      (parameterize ([current-namespace weak-ns])
        (namespace-require ''nest)
        (eval x))))
> (require 'nest)
> (list num-eggs num-chicks)
'(2 3)
> (weak-eval 'num-eggs)
2
> (weak-eval 'num-chicks)
?: access disallowed by code inspector to protected variable
  from module: 'nest
  at: num-chicks

That is, eval had a powerful enough code inspector (because it was called in the same scope that required the module originally) and therefore was able to get the export. However, weak-eval did not, because it got the same instance of the module, but with a weaker inspector to use for evaling.

My question is, when should we use protect-out? Should it always be used (or at least whenever possible)? Or is there a specific work flow that protect-out is designed for?

Idiosyncrasy answered 8/8, 2018 at 17:28 Comment(0)
P
6

Use protect-out for unsafe exports, where unsafe means something that has the capability of violating the rules of the Racket language or virtual machine. In particular, it is often possible to crash the Racket VM by misusing unsafe features.

Examples of unsafe features:

  • unsafe-vector-set! more or less lets you write to arbitrary memory locations, potentially violating the invariants of the GC, the invariants of Racket's value representations in memory, etc
  • unsafe-vector-ref seems less immediately dangerous, but you could use it for example to extract the value of a free variable from a closure, even though the Racket language does not allow inspecting the implementation of a procedure
  • most of ffi/unsafe is unsafe, obviously

Here's a less obvious example of an unsafe procedure:

#lang racket
(require ffi/unsafe ffi/unsafe/define)
(define-ffi-definer define-c #f)  ;; searches libc, etc
(define-c fopen (_fun _path (_bytes = #"a") -> _pointer))
(define-c fclose (_fun _pointer -> _void))
(define-c fwrite (_fun _bytes _size _size _pointer -> _size))
(define (append-to-file path buf)
  (define fp (fopen path))
  (unless fp (error "couldn't open file"))
  (fwrite buf (bytes-length buf) 1 fp)
  (fclose fp))
(provide append-to-file)

Consider the append-to-file procedure. It's defined using unsafe features (the FFI), but the FFI types _path and _bytes will reject bad Racket values, so it shouldn't be possible to crash Racket by using append-to-file. The append-to-file procedure is still unsafe (although perhaps not as catastrophically unsafe as unsafe-vector-set!) because it circumvents Racket's security guard mechanism. The procedure is unsafe in another way because fopen and fwrite can block, and Racket code is not supposed to block.

The unsafe operations in Racket's core libraries are provided with protect-out. If you use them to define your own unsafe operations, you should provide the derived unsafe operations using protect-out too. If you use unsafe features to define a safe feature, then you can provide that normally (that is, without protect-out), but think about it carefully first!

If you capture a privileged code inspector (either directly or indirectly via #%variable-reference), you should also protect that, because it can be used to dynamically gain access to unsafe features.

The goal is this: If a namespace contains only modules that have property protected their unsafe exports, then any additional code evaluated with a weaker code inspector should not be able to violate the invariants of the Racket VM (eg make it crash, circumvent security guard checks, etc).

Permeability answered 8/8, 2018 at 19:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.