Title

Two Safer Subsets of R7RS

Author

John Cowan <cowan@ccil.org>

Status

This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-172@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

This SRFI provides two libraries for use with R7RS that provide a way to sandbox the eval procedure to make it safer to use in evaluating Scheme expressions of doubtful provenance. The intention is to call eval, passing it an S-expression representing a Scheme procedure and the environment defined by one of these libraries. Since code evaluated by eval runs in a null lexical environment, the resulting procedure can then be invoked with less concern about possible side effects.

Use of these libraries does not provide any sort of safety guarantee. There are still many loopholes uncaught, including attempts to process circular structure and over-allocation of memory. The claim is only that the probability of such an attack is reduced, not that it is eliminated. However, using these libraries is a simple provision that is easy to implement and easy to use. For higher safety, it can readily be combined with other provisions.

Rationale

The Scheme eval procedure, though part of the language since R5RS and commonplace in Scheme implementations long before that, has always been considered radically unsafe. In the R6RS and R7RS, this is not entirely true. With the help of the environment procedure, code can be evaluated in an immutable global environment containing only specified libraries. This SRFI provides two such environments, structured as R6RS or R7RS libraries.

The intention is that the programmer call (eval s (environment '(srfi 172))), where s is an S-expression representing a procedure (typically a list whose car is lambda or case-lambda). The resulting procedure can be called with increased assurance that it will not gain access to or modify any Scheme object except those that are reachable from whatever is passed in as an argument. If (srfi 172 functional) is passed to eval in place of (srfi 172), the procedure most likely cannot mutate anything at all.

Note that while some I/O operations are provided by these libraries, the only kinds of ports that can be created are string and bytevector ports.

Warning: If an implementation coalesces two constants (quoted or self-evaluating) that are the same in the sense of equal? so that they also become constant in the sense of eq?, then a mutation of one constant will affect the value of the other unless they are marked immutable when read in; Scheme allows this but does not require it:

(define (foo x)
  (set-car! '(a . b) x)
  (car '(a . b)))
; compile the file with MIT Scheme and load it
(foo 5) => 5
; instead of a because the two (a . b) constants are coalesced

Such changes provide a covert channel between the sandbox and an object outside it that has been made the same object under the covers, and there is nothing this SRFI can do about it. (Example due to Taylor Campbell.)

Specification

The library (srfi 172) includes the following 284 variables and syntax keywords from R7RS-small. They are believed to be safe in the sense that they cannot affect or be affected by the outside world except through the arguments passed to them.

No distinctions are made between the individual R7RS-small libraries, though implementations that lack one or more libraries would have to bind the missing names to calls on error or syntax-error.

This SRFI does not require that the procedures and syntax definitions it exports are exactly the same as those exported by the various R7RS standard libraries. In particular, wrappers or alternative versions that signal errors on implementation-specified behavior are recommended.

- * / + < <= = => > >= abs acos and angle append apply asin assoc assq assv atan begin boolean? boolean=? bytevector bytevector? bytevector-append bytevector-copy bytevector-copy! bytevector-length bytevector-u8-ref bytevector-u8-set! caaaar caaadr caaar caadar caaddr caadr caar cadaar cadadr cadar caddar cadddr caddr cadr call/cc call-with-current-continuation call-with-port call-with-values car case case-lambda cdaaar cdaadr cdaar cdadar cdaddr cdadr cdar cddaar cddadr cddar cdddar cddddr cdddr cddr cdr ceiling char? char<? char<=? char=? char>? char>=? char->integer char-alphabetic? char-ci<? char-ci<=? char-ci=? char-ci>? char-ci>=? char-downcase char-foldcase char-lower-case? char-numeric? char-upcase char-upper-case? char-whitespace? close-input-port close-output-port close-port complex? cond cond-expand cons cos delay delay-force denominator digit-value do dynamic-wind else eof-object eof-object? eq? equal? eqv? error error-object? error-object-irritants error-object-message even? exact exact? exact-integer? exact-integer-sqrt exp expt finite? floor floor/ floor-quotient floor-remainder force for-each gcd get-output-bytevector get-output-string guard if imag-part inexact inexact? infinite? input-port? integer? integer->char lambda lcm length let let* let*-values letrec letrec* let-values list list? list->string list->vector list-copy list-ref list-set! list-tail log magnitude make-bytevector make-list make-parameter make-polar make-promise make-rectangular make-string make-vector map max member memq memv min modulo nan? negative? newline not null? number? number->string numerator odd? open-input-bytevector open-input-string open-output-bytevector open-output-string or output-port? pair? peek-char peek-u8 parameterize port? positive? procedure? promise? quasiquote quote quotient raise raise-continuable rational? rationalize read-bytevector read-bytevector! read-char read-error? read-line read-string read-u8 real? real-part remainder reverse round set! set-car! set-cdr! sin sqrt square string string? string<? string<=? string=? string>? string>=? string->list string->number string->utf8 string->vector string-append string-ci<? string-ci<=? string-ci=? string-ci>? string-ci>=? string-copy string-copy! string-downcase string-fill! string-foldcase string-for-each string-length string-map string-ref string-set! string-upcase substring symbol? symbol=? symbol->string tan textual-port? truncate truncate/ truncate-quotient truncate-remainder unless unquote unquote-splicing utf8->string values vector vector? vector->list vector->string vector-append vector-copy vector-copy! vector-fill! vector-for-each vector-length vector-map vector-ref vector-set! when with-exception-handler write-bytevector write-char write-string write-u8 zero?

The library (srfi 172 functional) exports all of the exports of (srfi 172) except for:

It therefore exports 251 identifiers.

A conforming implementation could be created entirely from this description.

Implementation

Implementations of the two libraries for Chibi can be found in the repository of this SRFI and in srfi-172.tgz.

Copyright

Copyright © John Cowan (2019).

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Arthur A. Gleckler