CVE-2021-32773: Externally Controlled Reference to a Resource in Another Sphere (CWE-610)
value: Racket is a general-purpose programming language and an ecosystem for language-oriented programming. In versions prior to 8.2, code evaluated using the Racket sandbox could cause system modules to incorrectly use attacker-created modules instead of their intended dependencies. This could allow system functions to be controlled by the attacker, giving access to facilities intended to be restricted. This problem is fixed in Racket version 8.2. A workaround is available, depending on system settings. For systems that provide arbitrary Racket evaluation, external sandboxing such as containers limit the impact of the problem. For multi-user evaluation systems, such as the `handin-server` system, it is not possible to work around this problem and upgrading is required.
CWE description: The product uses an externally controlled name or reference that resolves to a resource that is outside of the intended control sphere.
CVSS v3 base score: 7.5
Published: 2021-07-20T00:15Z
Repository: https://github.com/racket/racket
Language: Other
Files changed: pkgs/racket-doc/scribblings/reference/sandbox.scrbl, pkgs/racket-test-core/tests/racket/sandbox.rktl
Commit date: 2021-07-01 15:55:40 -0600
Commit message: racket/sandbox: documentation and tests for reader and require checking

Diff: diff --git a/pkgs/racket-doc/scribblings/reference/sandbox.scrbl b/pkgs/racket-doc/scribblings/reference/sandbox.scrbl
index c88a2362c6..ce489ea0d9 100644
--- a/pkgs/racket-doc/scribblings/reference/sandbox.scrbl
+++ b/pkgs/racket-doc/scribblings/reference/sandbox.scrbl
@@ -27,17 +27,20 @@ for the common use case where sandboxes are very limited.
                                         (listof (or/c module-path? path-string? 
                                                       (cons/c 'for-syntax (listof module-path?))))
                                         null]
                             [#:allow-for-require allow-for-require (listof (or/c module-path? path?)) null]
                             [#:allow-for-load allow-for-load (listof path-string?) null]
-                            [#:allow-read allow-read (listof (or/c module-path? path-string?)) null])
+                            [#:allow-read allow-read (listof (or/c module-path? path-string?)) null]
+                            [#:allow-syntactic-requires allow-syntactic-requires (or/c #f (listof module-path?)) #f])
             (any/c . -> . any)]
            [(make-module-evaluator [module-decl (or/c syntax? pair? path? input-port? string? bytes?)]
-                                   [#:language   lang  (or/c #f module-path?) #f]
+                                   [#:language  lang  (or/c #f module-path?) #f]
+                                   [#:readers   readers  (or/c #f (listof module-path?)) (and lang (default-language-readers lang))]
                                    [#:allow-for-require allow-for-require (listof (or/c module-path? path?)) null]
                                    [#:allow-for-load allow-for-load (listof path-string?) null]
-                                   [#:allow-read allow-read (listof (or/c module-path? path-string?)) null])
+                                   [#:allow-read allow-read (listof (or/c module-path? path-string?)) null]
+                                   [#:allow-syntactic-requires allow-syntactic-requires (or/c #f (listof module-path?)) #f])
             (any/c . -> . any)])]{
 
 The @racket[make-evaluator] function creates an evaluator with a
 @racket[language] and @racket[requires] specification, and starts
 evaluating the given @racket[input-program]s. The
@@ -48,15 +51,19 @@ function for further evaluation.
 The returned evaluator operates in an isolated and limited
 environment. In particular, filesystem access is restricted, which may
 interfere with using modules from the filesystem that are not
 in a @tech{collection}.  See below for
 information on the @racket[allow-for-require],
-@racket[allow-for-load], and @racket[allow-read] arguments.  When
+@racket[allow-for-load], and @racket[allow-read] arguments; collection-based
+module files typically do not need to be included in those lists.  When
 @racket[language] is a module path or when @racket[requires] is
 provided, the indicated modules are implicitly included in the
-@racket[allow-for-require] list. (For backward compatibility,
-non-@racket[module-path?] path strings are allowed in
+@racket[allow-for-require] list. When @racket[allow-syntactic-requires]
+is not @racket[#f], it constraints the set of modules that can be directly
+referenced in a module; see below for more information.
+(For backward compatibility,
+non-@racket[module-path?] path strings are allowed in arguments like
 @racket[requires]; they are implicitly converted to paths before
 addition to @racket[allow-for-require].)
 
 Each @racket[input-program] or @racket[module-decl] argument provides
 a program in one of the following forms:
@@ -140,10 +147,17 @@ argument:
 ]
 
 The @racket[requires] list adds additional imports to the module or
 namespace for the @racket[input-program]s, even in the case that
 @racket[require] is not made available through the @racket[language].
+The @racket[allow-syntactic-requires] argument, if non-@racket[#f],
+constrains @racket[require] references expanded in the module when the
+@racket[language] argument implies a @racket[module] wrapper; more
+precisely, it constrains the module paths that can be resolved when a
+syntax object is provided to the @tech{module name resolver}, which
+will include @racket[require] forms that are created by macro
+expansion.
 
 The following examples illustrate the difference between an evaluator
 that puts the program in a module and one that merely initializes a
 top-level namespace:
 
@@ -168,13 +182,19 @@ top-level namespace:
 
 The @racket[make-module-evaluator] function is essentially a
 restriction of @racket[make-evaluator], where the program must be a
 module, and all imports are part of the program.  In some cases it is
 useful to restrict the program to be a module using a specific module
-in its language position --- use the optional @racket[lang] argument
-to specify such a restriction (the default, @racket[#f], means no
-restriction is enforced). When the program is specified as a path, then
+in its language position; use the optional @racket[lang] argument
+to specify such a restriction, where @racket[#f] means that no
+restriction is enforced. The @racket[readers] argument similarly
+constrains the paths that can follow @hash-lang[] or @schememetafont{#reader}
+if it is not @racket[#f], and the default is based on @racket[lang].
+The @racket[allow-syntactic-requires] argument is treated the same as
+for @racket[make-evaluator] in the module-wrapper case.
+
+When the program is specified as a path, then
 the path is implicitly added to the @racket[allow-for-load] list.
 
 @racketblock[
 (define base-module-eval2
   (code:comment @#,t{equivalent to @racket[base-module-eval]:})
@@ -272,11 +292,12 @@ ports, and works when used from a module (by using a new namespace):
                [current-namespace (make-empty-namespace)])
   (parameterize ([current-eval (make-evaluator 'racket/base)])
     (read-eval-print-loop)))
 ]
 
-}
+@history[#:changed "1.2" @elem{Added the @racket[#:readers] and
+         @racket[#:allow-syntactic-require] arguments.}]}
 
 
 @defproc*[([(exn:fail:sandbox-terminated? [v any/c]) boolean?]
            [(exn:fail:sandbox-terminated-reason [exn exn:fail:sandbox-terminated?])
             symbol?])]{
@@ -822,10 +843,35 @@ A @tech{parameter} that determines the procedure used to create the
 procedure is called when initializing an evaluator, and the default
 parameter value constructs a new @tech{environment variable set} using
 @racket[(environment-variables-copy
 (current-environment-variables))].}
 
+@defproc[(default-language-readers [lang module-path?]) (listof module-path?)]{
+
+Creates a default list of readers that should be allowed to produce a
+module that uses @racket[lang] as the language.
+
+This default list includes the following (and more paths may be added
+in the future):
+
+@itemlist[
+
+ @item{@racket[`(submod ,lang reader)]}
+
+ @item{@racketvalfont{'}@racket[lang]@racketvalfont{/lang/reader} if @racket[lang] is a symbol}
+
+ @item{the module path producing by adding the relative path @racket["lang/reader.rkt"]
+       to @racket[lang] if @racket[lang] is not a symbol}
+
+ @item{@racket['(submod at-exp reader)]}
+
+ @item{@racket['at-exp/lang/reader]}
+
+]
+
+@history[#:added "1.2"]}
+
 @; ----------------------------------------------------------------------
 
 @section{Interacting with Evaluators}
 
 The following functions are used to interact with a sandboxed
diff --git a/pkgs/racket-test-core/tests/racket/sandbox.rktl b/pkgs/racket-test-core/tests/racket/sandbox.rktl
index 09df86253f..692505073e 100644
--- a/pkgs/racket-test-core/tests/racket/sandbox.rktl
+++ b/pkgs/racket-test-core/tests/racket/sandbox.rktl
@@ -720,6 +720,34 @@
   (e '(require racket/sandbox))
   (e '(make-module-evaluator (string-append "#lang racket/base\n"))))
 
 ;; ----------------------------------------
 
+;; Check reader guard on a sandbox:
+(err/rt-test (make-module-evaluator "#lang s-exp something-bad" #:language 'racket/base)
+             exn:fail?
+             #rx"disallowed reader module path: [(]submod s-exp reader[)]")
+
+(void (make-module-evaluator "#lang at-exp racket/base"
+                             #:language 'racket/base))
+(void (make-module-evaluator "#lang s-exp racket/base"
+                             #:language 'racket/base
+                             #:readers '((submod s-exp reader)
+                                         s-exp/lang/reader
+                                         racket/base
+                                         (submod racket/base reader))))
+
+;; ----------------------------------------
+
+;; Check require guard on a sandbox:
+
+(err/rt-test (make-module-evaluator "#lang racket/base (require json)"
+                                    #:allow-syntactic-requires '(racket/runtime-config))
+             exn:fail?
+             #rx"disallowed `require` module path: json")
+(void
+ (make-module-evaluator "#lang racket/base (require json)"
+                        #:allow-syntactic-requires '(racket/runtime-config json)))
+
+;; ----------------------------------------
+
 (report-errs)

Vulnerable code:                             [#:allow-read allow-read (listof (or/c module-path? path-string?)) null])
                                   [#:language   lang  (or/c #f module-path?) #f]
                                   [#:allow-read allow-read (listof (or/c module-path? path-string?)) null])
@racket[allow-for-load], and @racket[allow-read] arguments.  When
@racket[allow-for-require] list. (For backward compatibility,
non-@racket[module-path?] path strings are allowed in
in its language position --- use the optional @racket[lang] argument
to specify such a restriction (the default, @racket[#f], means no
restriction is enforced). When the program is specified as a path, then
}
Fixed code:                             [#:allow-read allow-read (listof (or/c module-path? path-string?)) null]
                            [#:allow-syntactic-requires allow-syntactic-requires (or/c #f (listof module-path?)) #f])
                                   [#:language  lang  (or/c #f module-path?) #f]
                                   [#:readers   readers  (or/c #f (listof module-path?)) (and lang (default-language-readers lang))]
                                   [#:allow-read allow-read (listof (or/c module-path? path-string?)) null]
                                   [#:allow-syntactic-requires allow-syntactic-requires (or/c #f (listof module-path?)) #f])
@racket[allow-for-load], and @racket[allow-read] arguments; collection-based
module files typically do not need to be included in those lists.  When
@racket[allow-for-require] list. When @racket[allow-syntactic-requires]
is not @racket[#f], it constraints the set of modules that can be directly
referenced in a module; see below for more information.
(For backward compatibility,
non-@racket[module-path?] path strings are allowed in arguments like
The @racket[allow-syntactic-requires] argument, if non-@racket[#f],
constrains @racket[require] references expanded in the module when the
@racket[language] argument implies a @racket[module] wrapper; more
precisely, it constrains the module paths that can be resolved when a
syntax object is provided to the @tech{module name resolver}, which
will include @racket[require] forms that are created by macro
expansion.
in its language position; use the optional @racket[lang] argument
to specify such a restriction, where @racket[#f] means that no
restriction is enforced. The @racket[readers] argument similarly
constrains the paths that can follow @hash-lang[] or @schememetafont{#reader}
if it is not @racket[#f], and the default is based on @racket[lang].
The @racket[allow-syntactic-requires] argument is treated the same as
for @racket[make-evaluator] in the module-wrapper case.

When the program is specified as a path, then
@history[#:changed "1.2" @elem{Added the @racket[#:readers] and
         @racket[#:allow-syntactic-require] arguments.}]}
@defproc[(default-language-readers [lang module-path?]) (listof module-path?)]{

Creates a default list of readers that should be allowed to produce a
module that uses @racket[lang] as the language.

This default list includes the following (and more paths may be added
in the future):

@itemlist[

 @item{@racket[`(submod ,lang reader)]}

 @item{@racketvalfont{'}@racket[lang]@racketvalfont{/lang/reader} if @racket[lang] is a symbol}

 @item{the module path producing by adding the relative path @racket["lang/reader.rkt"]
       to @racket[lang] if @racket[lang] is not a symbol}

 @item{@racket['(submod at-exp reader)]}

 @item{@racket['at-exp/lang/reader]}

]

@history[#:added "1.2"]}

;; Check reader guard on a sandbox:
(err/rt-test (make-module-evaluator "#lang s-exp something-bad" #:language 'racket/base)
             exn:fail?
             #rx"disallowed reader module path: [(]submod s-exp reader[)]")

(void (make-module-evaluator "#lang at-exp racket/base"
                             #:language 'racket/base))
(void (make-module-evaluator "#lang s-exp racket/base"
                             #:language 'racket/base
                             #:readers '((submod s-exp reader)
                                         s-exp/lang/reader
                                         racket/base
                                         (submod racket/base reader))))

;; ----------------------------------------

;; Check require guard on a sandbox:

(err/rt-test (make-module-evaluator "#lang racket/base (require json)"
                                    #:allow-syntactic-requires '(racket/runtime-config))
             exn:fail?
             #rx"disallowed `require` module path: json")
(void
 (make-module-evaluator "#lang racket/base (require json)"
                        #:allow-syntactic-requires '(racket/runtime-config json)))

;; ----------------------------------------