summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2026-02-27 17:28:44 +0100
committerLudovic Courtès <ludo@gnu.org>2026-05-10 17:01:08 +0200
commit2d4f290e17c5dbe4b97ece9320eef0a4ad3d821e (patch)
treeff321ed4e31ce3db3ca707deb49d14cb0766fa95
parent882f46bdd7b9d32e62af8c23e77b3992460e3b38 (diff)
ui: ‘load*’ accepts a file name or a port.
* guix/ui.scm (load/isolated): Change ‘file’ parameter to ‘port’ and adjust accordingly. (load*): Change ‘file’ to ‘file-or-port’ and adjust accordingly. * tests/ui.scm ("load/isolated, reading exceeds limits") ("load/isolated, attempt to import module") ("load/isolated, attempt to allocate with 'cons'") ("load/isolated, attempt to allocate with 'make-vector'") ("load/isolated, use of allowed bindings"): New tests. Change-Id: I0ec8fa2717c02041d409f6dc59b753d4501107f9 Signed-off-by: Ludovic Courtès <ludo@gnu.org>
-rw-r--r--guix/ui.scm32
-rw-r--r--tests/ui.scm90
2 files changed, 110 insertions, 12 deletions
diff --git a/guix/ui.scm b/guix/ui.scm
index e6829bd4d60..e953ef23ca1 100644
--- a/guix/ui.scm
+++ b/guix/ui.scm
@@ -250,11 +250,11 @@ arbitrary code execution."
250 (error "can't sever module?")) 250 (error "can't sever module?"))
251 (hashq-remove! (module-submodules parent) tail))))) 251 (hashq-remove! (module-submodules parent) tail)))))
252 252
253(define* (load/isolated file bindings 253(define* (load/isolated port bindings
254 #:key 254 #:key
255 (time-limit 30) 255 (time-limit 30)
256 (allocation-limit #e1e6)) 256 (allocation-limit #e1e6))
257 "Read and evaluate code from FILE in a isolated evaluation environment that 257 "Read and evaluate code from PORT in a isolated evaluation environment that
258only contains the given BINDINGS. Evaluation may not take more than 258only contains the given BINDINGS. Evaluation may not take more than
259TIME-LIMIT seconds and may not allocate more than ALLOCATION-LIMIT bytes." 259TIME-LIMIT seconds and may not allocate more than ALLOCATION-LIMIT bytes."
260 ;; This is similar to 'eval-in-sandbox' except that the time and allocation 260 ;; This is similar to 'eval-in-sandbox' except that the time and allocation
@@ -265,7 +265,7 @@ TIME-LIMIT seconds and may not allocate more than ALLOCATION-LIMIT bytes."
265 (lambda () 265 (lambda ()
266 (call-with-time-and-allocation-limits time-limit allocation-limit 266 (call-with-time-and-allocation-limits time-limit allocation-limit
267 (lambda () 267 (lambda ()
268 (eval (call-with-input-file file read/safe) module)))) 268 (eval (read/safe port) module))))
269 (lambda () 269 (lambda ()
270 (sever-module! module))))) 270 (sever-module! module)))))
271 271
@@ -300,9 +300,10 @@ TIME-LIMIT seconds and may not allocate more than ALLOCATION-LIMIT bytes."
300 #:key 300 #:key
301 (on-error 'nothing-special) 301 (on-error 'nothing-special)
302 (isolated? #f)) 302 (isolated? #f))
303 "Load the user provided Scheme source code FILE. When ISOLATED? is true, 303 "Load the user provided Scheme source code FILE-OR-PORT. When ISOLATED? is true,
304load FILE in an isolated \"sandbox\" where only IMPORTS are available--see the 304load FILE-OR-PORT in an isolated \"sandbox\" where only IMPORTS are
305documentation for (ice-9 sandbox) for what goes into IMPORTS." 305available--see the documentation for (ice-9 sandbox) for what goes into
306IMPORTS."
306 (define (error-string frame args) 307 (define (error-string frame args)
307 (call-with-output-string 308 (call-with-output-string
308 (lambda (port) 309 (lambda (port)
@@ -324,14 +325,23 @@ documentation for (ice-9 sandbox) for what goes into IMPORTS."
324 module)) 325 module))
325 imports))))) 326 imports)))))
326 327
328 (when (and (port? file-or-port) (not isolated?))
329 ;; XXX: This case is not implemented because it's hard to defend and hard
330 ;; to implement due to the use of the compiler instead of the interpreter.
331 (leave (G_ "code coming from a port must be isolated~%")))
332
327 (catch #t 333 (catch #t
328 (lambda () 334 (lambda ()
329 (if isolated? 335 (if isolated?
330 (call-with-prompt tag 336 (call-with-prompt tag
331 (lambda () 337 (lambda ()
332 (load/isolated (try-canonicalize-path file) 338 (let loop ((port file-or-port))
333 (append imports 339 (if (port? port)
334 (force pure-bindings-sans-allocators)))) 340 (load/isolated port
341 (append
342 imports
343 (force pure-bindings-sans-allocators)))
344 (call-with-input-file file-or-port loop))))
335 (const #f)) 345 (const #f))
336 (save-module-excursion 346 (save-module-excursion
337 (lambda () 347 (lambda ()
@@ -351,7 +361,7 @@ documentation for (ice-9 sandbox) for what goes into IMPORTS."
351 ;; compiled, which then allows us to provide better error 361 ;; compiled, which then allows us to provide better error
352 ;; reporting with source line numbers. 362 ;; reporting with source line numbers.
353 (without-compiler-optimizations 363 (without-compiler-optimizations
354 (load (try-canonicalize-path file)))) 364 (load (try-canonicalize-path file-or-port))))
355 (const #f))))))) 365 (const #f)))))))
356 (lambda _ 366 (lambda _
357 ;; XXX: Errors are reported from the pre-unwind handler below, but 367 ;; XXX: Errors are reported from the pre-unwind handler below, but
@@ -363,7 +373,7 @@ documentation for (ice-9 sandbox) for what goes into IMPORTS."
363 (let* ((stack (make-stack #t handle-error tag)) 373 (let* ((stack (make-stack #t handle-error tag))
364 (frame (last-frame-with-source stack))) 374 (frame (last-frame-with-source stack)))
365 375
366 (report-load-error file args frame) 376 (report-load-error file-or-port args frame)
367 377
368 (case on-error 378 (case on-error
369 ((debug) 379 ((debug)
diff --git a/tests/ui.scm b/tests/ui.scm
index 438acae5252..048f8500722 100644
--- a/tests/ui.scm
+++ b/tests/ui.scm
@@ -1,5 +1,5 @@
1;;; GNU Guix --- Functional package management for GNU 1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2013-2017, 2019-2020, 2022 Ludovic Courtès <ludo@gnu.org> 2;;; Copyright © 2013-2017, 2019-2020, 2022, 2026 Ludovic Courtès <ludo@gnu.org>
3;;; Copyright © 2022 Taiju HIGASHI <higashi@taiju.info> 3;;; Copyright © 2022 Taiju HIGASHI <higashi@taiju.info>
4;;; 4;;;
5;;; This file is part of GNU Guix. 5;;; This file is part of GNU Guix.
@@ -26,11 +26,19 @@
26 #:use-module ((gnu packages) #:select (specification->package)) 26 #:use-module ((gnu packages) #:select (specification->package))
27 #:use-module (guix tests) 27 #:use-module (guix tests)
28 #:use-module (guix utils) 28 #:use-module (guix utils)
29 #:use-module ((guix channels)
30 #:select (channel->code
31 %default-guix-channel
32 guix-channel?
33 channel-introduction))
29 #:use-module (srfi srfi-1) 34 #:use-module (srfi srfi-1)
30 #:use-module (srfi srfi-11) 35 #:use-module (srfi srfi-11)
31 #:use-module (srfi srfi-19) 36 #:use-module (srfi srfi-19)
32 #:use-module (srfi srfi-26) 37 #:use-module (srfi srfi-26)
33 #:use-module (srfi srfi-64) 38 #:use-module (srfi srfi-64)
39 #:use-module (rnrs bytevectors)
40 #:use-module (ice-9 binary-ports)
41 #:use-module (ice-9 match)
34 #:use-module (ice-9 regex)) 42 #:use-module (ice-9 regex))
35 43
36;; Test the (guix ui) module. 44;; Test the (guix ui) module.
@@ -369,4 +377,84 @@ Second line" 24))
369 ("PAGER" #false)) 377 ("PAGER" #false))
370 (assert-equals-find-available-pager ""))))) 378 (assert-equals-find-available-pager "")))))
371 379
380(test-equal "load/isolated, reading exceeds limits"
381 'quit ;'limit-exceeded is raised, caught, and then 'quit is raised
382 (let ((port (make-custom-binary-input-port
383 "infinite-paren-stream"
384 (lambda (bv start count)
385 (bytevector-u8-set! bv start (char->integer #\())
386 1)
387 #f #f #f)))
388 (catch #t
389 (lambda ()
390 (load* port '() #:isolated? #t)
391 #f)
392 (lambda (key . args)
393 key))))
394
395(test-equal "load/isolated, attempt to import module"
396 'quit
397 (call-with-input-string (object->string
398 '(begin
399 (use-modules (system foreign))
400 (dereference-pointer (make-pointer 123))))
401 (lambda (port)
402 (catch #t
403 (lambda ()
404 (load* port '() #:isolated? #t)
405 #f)
406 (lambda (key . args)
407 key)))))
408
409(test-equal "load/isolated, attempt to allocate with 'cons'"
410 'quit
411 ;; 'make-list' is not available in the environment so try to allocate memory
412 ;; via macro expansion or repeated calls to 'cons'.
413 (call-with-input-string
414 (object->string
415 '(letrec-syntax ((make-list
416 (lambda (s)
417 (syntax-case s ()
418 ((_ 0) #''())
419 ((_ n)
420 #`(cons #f
421 (make-list
422 #,(- (syntax->datum #'n) 1))))))))
423 (make-list 100000)))
424 (lambda (port)
425 (catch #t
426 (lambda ()
427 (load* port '() #:isolated? #t)
428 #f)
429 (lambda (key . args)
430 key)))))
431
432(test-equal "load/isolated, attempt to allocate with 'make-vector'"
433 'quit
434 ;; 'make-vector' is not available in the environment.
435 (call-with-input-string (object->string '(make-vector 123123123))
436 (lambda (port)
437 (catch #t
438 (lambda ()
439 (load* port '() #:isolated? #t)
440 #f)
441 (lambda (key . args)
442 key)))))
443
444(test-assert "load/isolated, use of allowed bindings"
445 (call-with-input-string
446 (object->string
447 `(list ,(channel->code %default-guix-channel)))
448 (lambda (port)
449 (match (load* port
450 '(((guix channels)
451 channel make-channel-introduction openpgp-fingerprint))
452 #:isolated? #t)
453 ((channel)
454 ;; The channels have a different 'location' field value hence this
455 ;; limited comparison.
456 (and (guix-channel? channel)
457 (equal? (channel-introduction channel)
458 (channel-introduction %default-guix-channel))))))))
459
372(test-end "ui") 460(test-end "ui")