guix_mirror_bot pushed a commit to branch master
in repository guix.
commit 247245fbef1923a3bc17a23627e9c016900b4edc
Author: Reepca Russelstein <[email protected]>
AuthorDate: Sun Jun 21 07:28:58 2026 -0500
daemon: libstore: reject invalid store paths in importPath.
Previously an authorized substitute server could produce invalid store
paths -
that is, paths that do denote a top-level file in the store, but that do not
obey the syntax restrictions beyond what that implies. Given that an
authorized substitute server can already potentially do a lot of damage if
it
really wanted to, this isn't a major issue, but closing off this opportunity
does simplify the analysis somewhat.
* nix/libstore/local-store.cc (LocalStore::importPath): use
strict readStorePath(s) variants.
* tests/store.scm ("import path not in store, unsigned", "import path not in
store, signed", "import invalid path, unsigned", "import invalid path,
signed" test cases): new test cases. The "not in store" cases succeeded
previously, while the "invalid path" cases did not succeed prior to this
commit.
Fixes: guix/guix#9078
Change-Id: Ib81c19ec1ae0fff5b7c7268f4f7429b16a870996
Signed-off-by: Ludovic Courtès <[email protected]>
Merges: #9434
---
nix/libstore/local-store.cc | 10 +++--
tests/store.scm | 107 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 114 insertions(+), 3 deletions(-)
diff --git a/nix/libstore/local-store.cc b/nix/libstore/local-store.cc
index b8308b1aae..478f2ccc44 100644
--- a/nix/libstore/local-store.cc
+++ b/nix/libstore/local-store.cc
@@ -1340,12 +1340,16 @@ Path LocalStore::importPath(bool requireSignature,
Source & source)
if (magic != EXPORT_MAGIC)
throw Error("normalized archive cannot be imported; wrong format");
- Path dstPath = readStorePath(hashAndReadSource);
+ /* The path being imported must at least be syntactically valid. This
+ * doesn't guarantee that it can be constructed by some existing method,
+ * but it at least rules out paths like "/gnu/store/nix-12982-1" or
+ * "/gnu/store/." or "/gnu/store/..". */
+ Path dstPath = readStorePathStrict(hashAndReadSource);
- PathSet references = readStorePaths<PathSet>(hashAndReadSource);
+ PathSet references = readStorePathsStrict<PathSet>(hashAndReadSource);
Path deriver = readString(hashAndReadSource);
- if (deriver != "") assertStorePath(deriver);
+ if (deriver != "") assertStorePathStrict(deriver);
Hash hash = hashAndReadSource.hashSink.finish().first;
hashAndReadSource.hashing = false;
diff --git a/tests/store.scm b/tests/store.scm
index 2eb6b77839..a3e67ac7ea 100644
--- a/tests/store.scm
+++ b/tests/store.scm
@@ -37,7 +37,9 @@
#:use-module (guix gexp)
#:use-module (gnu packages)
#:use-module (gnu packages bootstrap)
+ #:use-module (ice-9 binary-ports)
#:use-module (ice-9 match)
+ #:use-module (ice-9 rdelim)
#:use-module (ice-9 regex)
#:use-module (rnrs bytevectors)
#:use-module (rnrs io ports)
@@ -1522,6 +1524,111 @@ System: x86_64-linux~%"
(pk 'corrupt-imported imported)
#f)))))
+
+(define* (plain-dump filename contents #:key signed?)
+ (let* ((contents (if (bytevector? contents)
+ contents
+ (string->utf8 contents)))
+ (item-dump (call-with-bytevector-output-port
+ (lambda (port)
+ (call-with-input-bytevector
+ contents
+ (lambda (contents-port)
+ (write-file-tree #t port
+ #:file-type+size (lambda (_)
+ (values 'regular
+
(bytevector-length
+
contents)))
+ #:file-port (const contents-port))))
+ (write-int #x4558494e port) ;%export-magic
+ (write-string filename port) ;store item
+ (write-string-list '() port) ;references
+ (write-string "" port))))) ;deriver
+ (call-with-bytevector-output-port
+ (lambda (port)
+ (write-int 1 port) ;start
+ (put-bytevector port item-dump)
+ (write-int (if signed? 1 0) port) ;signed
+ (when signed?
+ (let* ((read-canonical-sexp
+ (compose gcrypt:string->canonical-sexp read-string))
+ (public-key (call-with-input-file %public-key-file
+ read-canonical-sexp))
+ (private-key (call-with-input-file %private-key-file
+ read-canonical-sexp)))
+ (write-string (gcrypt:canonical-sexp->string
+ (signature-sexp
+ (gcrypt:bytevector->hash-data
+ (gcrypt:sha256 item-dump)
+ #:key-type (gcrypt:key-type public-key))
+ private-key
+ public-key))
+ port)))
+ (write-int 0 port)))))
+
+(test-assert "import path not in store, unsigned"
+ ;; error should be produced before the signature is even considered
+ (call-with-input-bytevector
+ (plain-dump (string-append (%store-prefix) "/../../notinstore")
+ (random-text))
+ (lambda (port)
+ (guard (c ((store-protocol-error? c)
+ (pk 'c c)
+ (and (not (zero? (store-protocol-error-status c)))
+ (let ((message (store-protocol-error-message c)))
+ (pk 'error-message message)
+ (or (string-contains message "is not a valid store
path")
+ (string-contains message "is not in the store"))))))
+ (import-paths %store port)
+ #f))))
+
+(test-assert "import path not in store, signed"
+ (call-with-input-bytevector
+ (plain-dump (string-append (%store-prefix) "/../../notinstore")
+ (random-text))
+ (lambda (port)
+ (guard (c ((store-protocol-error? c)
+ (pk 'c c)
+ (and (not (zero? (store-protocol-error-status c)))
+ (let ((message (store-protocol-error-message c)))
+ (pk 'error-message message)
+ (or (string-contains message "is not a valid store
path")
+ (string-contains message "is not in the store"))))))
+ (import-paths %store port)
+ #f))))
+
+(test-assert "import invalid path, unsigned"
+ ;; error should be produced before the signature is even considered
+ (call-with-input-bytevector
+ (plain-dump (string-append (%store-prefix)
+ "/!@#$%^&*()_+=-[]{}\\|';:,<>.")
+ (random-text))
+ (lambda (port)
+ (guard (c ((store-protocol-error? c)
+ (pk 'c c)
+ (and (not (zero? (store-protocol-error-status c)))
+ (let ((message (store-protocol-error-message c)))
+ (pk 'error-message message)
+ (string-contains message "is not a valid store
path")))))
+ (import-paths %store port)
+ #f))))
+
+(test-assert "import invalid path, signed"
+ (call-with-input-bytevector
+ (plain-dump (string-append (%store-prefix)
+ "/!@#$%^&*()_+=-[]{}\\|';:,<>.")
+ (random-text)
+ #:signed? #t)
+ (lambda (port)
+ (guard (c ((store-protocol-error? c)
+ (pk 'c c)
+ (and (not (zero? (store-protocol-error-status c)))
+ (let ((message (store-protocol-error-message c)))
+ (pk 'error-message message)
+ (string-contains message "is not a valid store
path")))))
+ (import-paths %store port)
+ #f))))
+
(test-assert "verify-store"
(let* ((text (random-text))
(file1 (add-text-to-store %store "foo" text))