GNU Guix: Privilege Escalation Vulnerability
A security issue has been identified in
guix-daemon
,
which allows for a local user to gain the privileges of any of the build users
and subsequently use this to manipulate the output of any build. In the case of
the rootless daemon, this also means gaining the privileges of
guix-daemon
. All systems are affected, whether or not guix-daemon
is running
with root privileges. You are strongly advised to upgrade your daemon
now (see instructions below).
The only requirements to exploit this are the ability to create and build an
arbitrary derivation that has builtin:download
as its builder, and to execute
a setuid program on the system in question. As such, this represents an
increased risk primarily to multi-user systems, but also more generally to any
system in which untrusted code may be able to access guix-daemon's socket, which
is usually located at /var/guix/daemon-socket/socket
.
Vulnerability
guix-daemon
currently supports two so-called built-in builders for
derivations:
builtin:download
and builtin:git-download
. Their primary utility is
currently to circumvent bootstrapping challenges in code-downloading derivations
(that is, "how does one get the programs that are used to download programs?")
by using "host-side" programs for this purpose.
In particular, download
and git-download
are currently implemented in the
(guix scripts perform-download)
module, which can be run as a standalone
program using guix perform-download
. At the time that perform-download was
written, it was believed to be sufficient for security purposes to ensure that
it was run by a build user, and so one of the user-supplied values (the file
named by the derivation's content-addressed-mirrors
environment variable) was
read and evaluated as arbitrary Guile code.
This suffices to protect a regular user from an untrusted derivation they may be
building, since all processes owned by the build user will be killed at the end
of the build, before any other build can be run that uses the same build
user. It does not suffice, however, to protect the daemon's build users – and by
extension the integrity of the daemon's builds – from a regular user. In
particular, a content-addressed-mirrors
file can be written to create a setuid
program that allows a regular user to gain the privileges of the build user that
runs it even after the build has ended. This is similar in impact to
CVE-2025-46416,
though using a somewhat different mechanism.
Mitigation
This security issue has been fixed by 3 commits
(2a33354
,
f607aaa
,
and
9202921
)
as part of
pull request #2419. Users should
make sure they have upgraded to commit
1618ca7
or any later commit to be protected
from this vulnerability. Upgrade instructions are in the following section.
The fix was accomplished by changing (guix scripts perform-download)
to
evaluate the content-addressed-mirrors
file in a Guile isolated
environment,
as well as verifying that this file and the others that perform-download
reads
is neither outside of the store nor a symbolic link to a file outside of the
store, so that this cannot be used to cause arbitrary files (such as
/proc/PID/fd/N
) to be read. Measures were also taken to ensure that calls to
read
never cause code to be evaluated.
A test for the presence of this vulnerability is available at the end of this
post. One can run this code with:
guix repl -- content-addressed-mirrors-vuln-check.scm
This will output whether the current guix-daemon
being used is vulnerable or
not. If it is not vulnerable, the last line will contain guix-daemon is not vulnerable
and guix repl
will exit with status code 0, otherwise the last
line will contain guix-daemon is VULNERABLE
and guix repl
will exit with
status code 1.
Upgrading
Due to the severity of this security advisory, we strongly recommend
all users to upgrade guix-daemon
immediately.
For Guix System, the
procedure
is to reconfigure the system after a guix pull
, either restarting
guix-daemon
or rebooting. For example:
guix pull
sudo guix system reconfigure /run/current-system/configuration.scm
sudo herd restart guix-daemon
where /run/current-system/configuration.scm
is the current system
configuration but could, of course, be replaced by a system
configuration file of a user's choice.
For Guix on another distribution, one
needs to guix pull
with sudo
, as the guix-daemon
runs as root,
and restart the guix-daemon
service, as
documented.
For example, on a system using systemd to manage services, run:
sudo --login guix pull
sudo systemctl restart guix-daemon.service
Note that for users with their distro's package of Guix (as opposed to
having used the install
script)
you may need to take other steps or upgrade the Guix package as per
other packages on your distro. Please consult the relevant
documentation from your distro or contact the package maintainer for
additional information or questions.
Conclusion
Test for presence of vulnerability
Below is code to check if your guix-daemon
is vulnerable to this exploit. Save
this file as content-addressed-mirrors-vuln-check.scm
and run following the
instructions above, in "Mitigation."
(use-modules (guix monads)
(guix derivations)
(guix gexp)
(guix store)
(guix utils)
(guix packages)
(srfi srfi-34))
(define %test-filename
"/tmp/content-addressed-mirrors-vulnerable")
(define %test-content-addressed-mirrors
`(begin
(mkdir ,%test-filename)
(exit 33)))
(define %test-content-addressed-mirrors-file
(plain-file "content-addressed-mirrors"
(object->string %test-content-addressed-mirrors)))
(define (test-content-addressed-mirrors content-addressed-mirrors)
(mlet %store-monad ((content-addressed-mirrors
(lower-object content-addressed-mirrors)))
(raw-derivation "content-addressed-mirrors-vuln-check"
"builtin:download"
'()
#:hash
(base32
"dddddddddddddddddddddddddddddddddddddddddddddddddddd")
#:hash-algo 'sha256
#:sources (list content-addressed-mirrors)
#:env-vars `(("url" . "/doesnotexist")
("content-addressed-mirrors"
. ,content-addressed-mirrors))
#:local-build? #t)))
(with-store store
(let ((drv (run-with-store store (test-content-addressed-mirrors
%test-content-addressed-mirrors-file))))
(guard (c ((and (store-protocol-error? c)
(string-contains (store-protocol-error-message c) "failed"))
(cond
((file-exists? %test-filename)
(format #t "content-addressed-mirrors can evaluate arbitrary code, guix-daemon is VULNERABLE~%")
(exit 1))
(else
(format #t "content-addressed-mirrors can't create files, guix-daemon is not vulnerable~%")
(exit 0)))))
(build-derivations store (list drv)))))