Hello Guix, ‘guix substitute’ checks the signature over everything that precedes the “Signature:” field of a narinfo: (define (narinfo-sha256 narinfo) "Return the sha256 hash of NARINFO as a bytevector, or #f if NARINFO lacks a 'Signature' field." (let ((contents (narinfo-contents narinfo))) (match (string-contains contents "Signature:") (#f #f) (index (let ((above-signature (string-take contents index))) ;<-- here! (sha256 (string->utf8 above-signature))))))) (define* (valid-narinfo? narinfo #:optional (acl (current-acl)) #:key verbose?) "Return #t if NARINFO's signature is not valid." (or %allow-unauthenticated-substitutes? (let ((hash (narinfo-sha256 narinfo)) ;<-- here! (signature (narinfo-signature narinfo)) (uri (uri->string (narinfo-uri narinfo)))) (and hash signature (signature-case (signature hash acl) …))))) Narinfos produced by ‘guix publish’ look like this: --8<---------------cut here---------------start------------->8--- StorePath: /gnu/store/nrkm1683p1cqnkcmhlmhiig9q9qd7xqh-sed-4.5 URL: nar/gzip/nrkm1683p1cqnkcmhlmhiig9q9qd7xqh-sed-4.5 Compression: gzip NarHash: sha256:17ymma68kh0l5hrsc6w1ma0ixb52lbwwm43wdhl0mm1q19j69s9n NarSize: 704040 References: 4sqps8d… FileSize: 240878 Signature: 1;berlin.guixsd.org;KHNp… --8<---------------cut here---------------end--------------->8--- So ‘guix substitutes’ expects the signature to be computed over everything. However, a server could well send this: --8<---------------cut here---------------start------------->8--- Signature: 1;EVIL.example.org;ABCd… StorePath: /gnu/store/nrkm1683p1cqnkcmhlmhiig9q9qd7xqh-sed-4.5 URL: nar/gzip/nrkm1683p1cqnkcmhlmhiig9q9qd7xqh-sed-4.5 Compression: gzip NarHash: sha256:17ymma68kh0l5hrsc6w1ma0ixb52lbwwm43wdhl0mm1q19j69s9n NarSize: 704040 References: 4sqps8d… FileSize: 240878 --8<---------------cut here---------------end--------------->8--- … in which case the signature is expected to be computed over the empty string (thus it’s the same for all the narinfos it serves.) The problem is that ‘guix substitute’ will accept such narinfos (when they are signed by an authorized key), even though the signature doesn’t cover the important parts (namely: StorePath, NarHash, and References; the rest is mostly informative.) A fix is attached with tests that illustrate the problem. I think the main consequence is repudiation: if you receive a narinfo where the signature comes first, that doesn’t prove anything; the server operator could pretend it never sent it since in essence its contents are unsigned. It’s not clear to me whether/how this could be exploited. Also keep in mind that this is limited to servers with a key present in the user’s /etc/guix/acl (“trusted” servers.) In this context, servers are in a position to do more harm to the user anyway since they serve substitutes. TIA, Ludo’. PS: Thanks to Leo and Ricardo for their quick feedback on the guix-security mailing list!