1
0
mirror of https://git.savannah.gnu.org/git/guix.git synced 2026-07-30 14:51:32 +02:00

ui: Add #:isolated? parameter to ‘load*’.

* guix/ui.scm (read/safe, sever-module!, load/isolated): New procedures.
(pure-bindings-sans-allocators): New variable.
(load*): Add #:isolated? parameter and honor it.

Change-Id: I0164a7a28997e01c6948ed32d26900b5bf524b27
Signed-off-by: Ludovic Courtès <ludo@gnu.org>
This commit is contained in:
Ludovic Courtès
2026-02-26 20:53:51 +01:00
parent 94ae360ab4
commit 568f8e97a7
+113 -21
View File
@@ -79,6 +79,9 @@
#:use-module (ice-9 format)
#:use-module (ice-9 regex)
#:autoload (ice-9 popen) (open-pipe* close-pipe)
#:autoload (ice-9 sandbox) (all-pure-bindings
make-sandbox-module
call-with-time-and-allocation-limits)
#:autoload (system repl repl) (start-repl)
#:autoload (system repl debug) (make-debug stack->vector)
#:use-module (texinfo)
@@ -222,9 +225,83 @@ symlink to a non-existent file like 'pipe:[1234]', as in this example:
(canonicalize-path file))
(const file)))
(define* (load* file imports
#:key (on-error 'nothing-special))
"Load the user provided Scheme source code FILE."
(define (read/safe port)
"Read code (one or more sexps) from PORT, wrapping the sexps in a 'begin'
form if needed, ensuring that reader extensions are all disabled to prevent
arbitrary code execution."
(with-fluids ((read-eval? #f))
(parameterize ((read-hash-procedures '()))
(let loop ((result '()))
(match (read port)
((? eof-object?)
(match result
((exp) exp) ;a single expression
(_ `(begin ,@(reverse result))))) ;multiple expressions
(exp
(loop (cons exp result))))))))
(define (sever-module! m) ;copied from (ice-9 sandbox)
"Remove @var{m} from its container module."
(match (module-name m)
((head ... tail)
(let ((parent (resolve-module head #f)))
(unless (eq? m (module-ref-submodule parent tail))
(error "can't sever module?"))
(hashq-remove! (module-submodules parent) tail)))))
(define* (load/isolated file bindings
#:key
(time-limit 30)
(allocation-limit #e1e6))
"Read and evaluate code from FILE in a isolated evaluation environment that
only contains the given BINDINGS. Evaluation may not take more than
TIME-LIMIT seconds and may not allocate more than ALLOCATION-LIMIT bytes."
;; This is similar to 'eval-in-sandbox' except that the time and allocation
;; limits apply to both reading and evaluating.
(let ((module (make-sandbox-module bindings)))
(dynamic-wind
(const #t)
(lambda ()
(call-with-time-and-allocation-limits time-limit allocation-limit
(lambda ()
(eval (call-with-input-file file read/safe) module))))
(lambda ()
(sever-module! module)))))
(define pure-bindings-sans-allocators
;; Work around <https://codeberg.org/guile/guile/issues/181> by filtering
;; out procedures that can allocate large amounts of memory without being
;; interrupted by 'call-with-allocation-limit'.
(delay (let ((banned (setq 'make-list
'make-array
'make-typed-array
'make-shared-array
'make-vector
'make-bitvector
'make-string
'make-f32vector
'make-f64vector
'make-s16vector
'make-s32vector
'make-s64vector
'make-s8vector
'make-u16vector
'make-u32vector
'make-u64vector
'make-u8vector)))
(map (match-lambda
((module . bindings)
(cons module
(remove (cut set-contains? banned <>) bindings))))
all-pure-bindings))))
(define* (load* file-or-port imports
#:key
(on-error 'nothing-special)
(isolated? #f))
"Load the user provided Scheme source code FILE. When ISOLATED? is true,
load FILE in an isolated \"sandbox\" where only IMPORTS are available--see the
documentation for (ice-9 sandbox) for what goes into IMPORTS."
(define (error-string frame args)
(call-with-output-string
(lambda (port)
@@ -238,29 +315,43 @@ symlink to a non-existent file like 'pipe:[1234]', as in this example:
(begin
(warning (G_ "using deprecated calling convention of 'load*'~%"))
imports)
(make-user-module imports)))
(and (not isolated?)
(make-user-module (map (match-lambda
(((module ...) bindings ...)
module)
((module ...)
module))
imports)))))
(catch #t
(lambda ()
;; XXX: Force a recompilation to avoid ABI issues.
(set! %fresh-auto-compile #t)
(set! %load-should-auto-compile #t)
(if isolated?
(call-with-prompt tag
(lambda ()
(load/isolated (try-canonicalize-path file)
(append imports
(force pure-bindings-sans-allocators))))
(const #f))
(save-module-excursion
(lambda ()
;; XXX: Force a recompilation to avoid ABI issues.
(set! %fresh-auto-compile #t)
(set! %load-should-auto-compile #t)
(save-module-excursion
(lambda ()
(set-current-module user-module)
(set-current-module user-module)
;; Hide the "auto-compiling" messages.
(parameterize ((current-warning-port (%make-void-port "w")))
(call-with-prompt tag
(lambda ()
;; Give 'load' an absolute file name so that it doesn't try to
;; search for FILE in %LOAD-PATH. Note: use 'load', not
;; 'primitive-load', so that FILE is compiled, which then allows
;; us to provide better error reporting with source line numbers.
(without-compiler-optimizations
(load (try-canonicalize-path file))))
(const #f))))))
;; Hide the "auto-compiling" messages.
(parameterize ((current-warning-port (%make-void-port "w")))
(call-with-prompt tag
(lambda ()
;; Give 'load' an absolute file name so that it doesn't
;; try to search for FILE in %LOAD-PATH. Note: use
;; 'load', not 'primitive-load', so that FILE is
;; compiled, which then allows us to provide better error
;; reporting with source line numbers.
(without-compiler-optimizations
(load (try-canonicalize-path file))))
(const #f)))))))
(lambda _
;; XXX: Errors are reported from the pre-unwind handler below, but
;; calling 'exit' from there has no effect, so we call it here.
@@ -2416,6 +2507,7 @@ and signal handling have already been set up."
;;; Local Variables:
;;; eval: (put 'guard* 'scheme-indent-function 2)
;;; eval: (put 'call-with-time-and-allocation-limits 'scheme-indent-function 2)
;;; End:
;;; ui.scm ends here