From 26d7eb8a4adafc648ef035e91b6bbc4945d4c962 Mon Sep 17 00:00:00 2001 From: Reepca Russelstein Date: Mon, 15 Jun 2026 20:37:53 -0500 Subject: scripts: substitute: restored nars only appear after verification. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hash of a nar is only known once 'download-nar' completes. By that point the nar may already have been partially-restored to its destination for some time. Additionally, after a hash mismatch is detected, 'guix substitute' leaves the invalid contents at the specified destination. This means that untrusted, attacker-controlled contents are present at what at some point may have been or at some point may become a valid store path. It is entirely possible that a user or program won't check that a given store item is valid before trying to use it (it may be that 'guix gc' was run while an obscured reference was held). Let's protect them in that case by ensuring that unverified contents are kept elsewhere, in a temporary directory. While implementing this one such program was discovered: tests/substitute.scm. It actually incorrectly computes the hashes of the nars it supplies to (guix scripts substitute), computing the hash of the plain contents instead of the hash of the nar. These tests nevertheless passed because they didn't check what (guix scripts substitute) gave as output, instead only checking that the expected contents were at the specified destination. * guix/scripts/substitute.scm (call-with-temporary-directory-in): new procedure. (guix-substitute): use it to create a temporary directory next to the final destination to restore the nar inside of. Once it is fully restored, if the hash matches, move it to the final destination, deleting the file currently there if it already exists. * tests/substitute.scm (plain-file-nar-sha256): new procedure. (%narinfo, "substitute, narinfo with several URLS" test): use it. Change-Id: Ifeb9b4c912f9d9b2e7477d237da04e7d5175ff83 Signed-off-by: Ludovic Courtès --- tests/substitute.scm | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/substitute.scm b/tests/substitute.scm index 10b74db084e..086bd588358 100644 --- a/tests/substitute.scm +++ b/tests/substitute.scm @@ -39,6 +39,7 @@ #:use-module (rnrs io ports) #:use-module (web uri) #:use-module (ice-9 regex) + #:use-module (ice-9 binary-ports) #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) #:use-module (srfi srfi-34) @@ -142,6 +143,21 @@ version identifier.." ;; . "http://203.0.113.1") +(define (plain-file-nar-sha256 string) + (sha256 (call-with-output-bytevector + (lambda (port) + (let ((bv (string->utf8 string))) + (call-with-input-bytevector + bv + (lambda (contents) + (write-file-tree #t port + #:file-type+size + (lambda (_) + (values 'regular + (bytevector-length + bv))) + #:file-port + (const contents))))))))) (define %narinfo ;; Skeleton of the narinfo used below. @@ -150,7 +166,7 @@ version identifier.." URL: example.nar Compression: none NarHash: sha256:" (bytevector->nix-base32-string - (sha256 (string->utf8 "Substitutable data."))) " + (plain-file-nar-sha256 "Substitutable data.")) " NarSize: 42 References: bar baz Deriver: " (%store-prefix) "/foo.drv @@ -819,7 +835,7 @@ Compression: lzip URL: example.nar Compression: none NarHash: sha256:" (bytevector->nix-base32-string - (sha256 (string->utf8 "Substitutable data."))) " + (plain-file-nar-sha256 "Substitutable data.")) " NarSize: 42 References: bar baz Deriver: " (%store-prefix) "/foo.drv -- cgit v1.2.3