Guix

Please sustain Guix by donating

Privilege Escalation Vulnerabilities (CVE-2025-46415, CVE-2025-46416)

Caleb Ristvedt — June 24, 2025

Two security issues, known as CVE-2025-46415 and CVE-2025-46416, have been identified in guix-daemon, which allow 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, as well as to subsequently gain the privileges of the daemon user. You are strongly advised to upgrade your daemon now (see instructions below), especially on multi-user systems.

Both exploits require the ability to start a derivation build. CVE-2025-46415 requires the ability to create files in /tmp in the root mount namespace on the machine the build occurs on, and CVE-2025-46416 requires the ability to run arbitrary code in the root PID and network namespaces on the machine the build occurs on. 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

One of the longstanding oversights of Guix's build environment isolation is what has become known as the abstract Unix-domain socket hole: a Linux-specific feature that enables any two processes in the same network namespace to communicate via Unix-domain sockets, regardless of all other namespace state. Unix-domain sockets are perhaps the single most powerful form of interprocess communication (IPC) that Unix-like systems have to offer, for the reason that they allow file descriptors to be passed between processes.

This behavior had played a crucial role in CVE-2024-27297, in which it was possible to smuggle a writable file descriptor to one of the output files of a fixed-output derivation to a process outside of the build environment sandbox. More specifically, this would use a fixed-output derivation that doesn't use a builtin builder; examples of this class of derivation include derivations produced by origins using svn-fetch and hg-fetch, but not git-fetch or url-fetch, since those are implemented using builtin builders. The process could then wait for the daemon to validate the hash and register the output, and subsequently modify the file to contain any contents it desired.

The fix for CVE-2024-27297 seems to have made the assumption that once the build was finished, no more processes could be running as that build user. This is unfortunately incorrect: the builder could also smuggle out the file descriptor of a setuid program, which could subsequently be executed either using /proc/self/fd/N or execveat to gain the privileges of the build user. This assumption was likely believed to hold in Nix because Nix had a seccomp filter that attempted to forbid the creation of setuid programs entirely by blocking the necessary chmod calls. The security researchers who discovered CVE-2025-46415 and CVE-2025-46416 discovered ways around Nix's seccomp filter, but Guix never had any such filter to begin with. It was therefore possible to run arbitrary code as the build user outside of the isolated build environment at any time.

Because it is possible to run arbitrary code as the build user even after the build has finished, many assumptions made in the design of the build daemon — not only in fixing CVE-2024-27297 but going way back — can be violated and exploited. One such assumption is that directories being deleted by deletePath — for instance the build tree of a build that has just failed — won't be modified while it is recursing through them. By violating this assumption, it is possible to exploit race conditions in deletePath to get the daemon to delete arbitrary files. One such file is a build directory of the form /tmp/guix-build-PACKAGE-X.Y.drv-0. If this is done between when the build directory is created and when it is chowned to the build user, an attacker can put a symbolic link in the appropriate place and get it to chown any file owned by the daemon's user to now be owned by the build user. In the case of a daemon running as root, that includes files such as /etc/passwd. The build users, as mentioned before, are easily compromised, so an attacker can at this point write to the target file.

When guix-daemon is not running as root, the attacker would gain privileges of the guix-daemon user, giving write access to the store and nothing else.

In short, there are two separate problems here:

  1. It is possible to take over build users by exfiltrating setuid programs (CVE-2025-46416).
  2. Race conditions in the daemon make it possible to elevate privileges when other processes can concurrently modify files it operates on (CVE-2025-46415).

Mitigation

This security issue has been fixed by 6 commits (7173c2c0ca, be8aca0651, fb42611b8f, c659f977bb, 0e79d5b655, and 30a5d140aa as part of pull request #788). Users should make sure they have upgraded to commit 30a5d140aa or any later commit to be protected from this vulnerability. Upgrade instructions are in the following section.

The fix was accomplished primarily by closing the "abstract Unix-domain socket hole" entirely. To do this, the daemon was modified so that all builds — even fixed-output ones – occur in a fresh network namespace. To keep networking functional despite the separate network namespace, a userspace networking stack, slirp4netns, is used. Additionally, some of the daemon's file deletion and copying helper procedures were modified to use the openat family of system calls, so that even in cases where build users can be taken over (for example, when the daemon is run with --disable-chroot), those particular helper procedures can't be exploited to escalate privileges.

A test for the presence of the abstract Unix-domain socket hole is available at the end of this post. One can run this code with:

guix repl -- abstract-socket-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 Abstract unix socket hole is CLOSED, otherwise the last line will contain Abstract unix socket hole is OPEN, guix-daemon is VULNERABLE.

Note that this will properly report that the hole is still open for daemons running with --disable-chroot, which is, as before, still insecure wherever untrusted users can access the daemon's socket.

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.

Timeline

On March 27th, the NixOS/Nixpkgs security team forwarded a detailed report about two vulnerabilities from Snyk Security Labs to the Guix security team and to Ludovic Courtès and Reepca Russelstein (as contributors to guix-daemon). A 90-day disclosure timeline was agreed upon with Snyk and all the affected projects: Nix, Lix, and Guix.

During that time, development of the fixes in Guix was led by Reepca Russelstein with peer review happening on the private guix-security mailing list. Coordination with the other projects and for this security advisory was managed by the Guix security team.

A pre-disclosure announcement was sent by the NixOS/Nixpkgs and the Guix security teams on June 19th–20th, giving June 24th as the full public disclosure date.

Some other CVEs that were included in the report were CVE-2025-52991, CVE-2025-52992, and CVE-2025-52993. These don't represent direct vulnerabilities so much as missed opportunities to mitigate the attack the researchers identified — that is, it has to be possible to do things like exfiltrate file descriptors (for CVE-2025-52992) and trick the daemon into deleting arbitrary files (for CVE-2025-52991 and CVE-2025-52993) before these start mattering.

Conclusion

More information concerning the fix for this vulnerability and the design choices made for it will be provided in a follow-up blog post.

We thank the Security Labs team at Snyk for discovering similar-but-not-quite-the-same vulnerabilities in Nix, and the NixOS/Nixpkgs security team for sharing this information with the Guix security team, which led us to realize our own related vulnerabilities.

Test for presence of vulnerability

Below is code to check if your guix-daemon is vulnerable to this exploit. Save this file as abstract-socket-vuln-check.scm and run following the instructions above, in "Mitigation."

;; Checking for CVE-2025-46415 and CVE-2025-46416.

(use-modules (guix)
 (gcrypt hash)
 ((rnrs bytevectors) #:select (string->utf8))
 (ice-9 match)
 (ice-9 threads)
 (srfi srfi-34))
(define nonce
 (string-append "-" (number->string (car (gettimeofday)) 16)
 "-" (number->string (getpid))))
(define socket-name
 (string-append "0円" nonce))
(define test-message nonce)
(define check
 (computed-file
 "check-abstract-socket-hole"
 #~(begin
 (use-modules (ice-9 textual-ports))
 (let ((sock (socket AF_UNIX SOCK_STREAM 0)))
 ;; Attempt to connect to the abstract Unix-domain socket outside.
 (connect sock AF_UNIX #$socket-name)
 ;; If we reach this line, then we successfully managed to connect to
 ;; the abstract Unix-domain socket.
 (call-with-output-file #$output
 (lambda (port)
 (display (get-string-all sock) port)))))
 #:options
 `(#:hash-algo sha256
 #:hash ,(sha256 (string->utf8 test-message))
 #:local-build? #t)))
(define build-result
 ;; Listen on the abstract Unix-domain socket at SOCKET-NAME and build
 ;; CHECK. If CHECK succeeds, then it managed to connect to SOCKET-NAME.
 (let ((sock (socket AF_UNIX SOCK_STREAM 0)))
 (bind sock AF_UNIX socket-name)
 (listen sock 1)
 (call-with-new-thread
 (lambda ()
 (match (accept sock)
 ((connection . peer)
 (format #t "accepted connection on abstract Unix-domain socket~%")
 (display test-message connection)
 (close-port connection)))))
 (with-store store
 (let ((drv (run-with-store store (lower-object check))))
 (guard (c ((store-protocol-error? c) c))
 (build-derivations store (list drv))
 #t)))))
(if (store-protocol-error? build-result)
 (format (current-error-port)
 "Abstract Unix-domain socket hole is CLOSED, build failed with ~S.~%"
 (store-protocol-error-message build-result))
 (format (current-error-port)
 "Abstract Unix-domain socket hole is OPEN, guix-daemon is VULNERABLE!~%"))

Related topics:

Security Advisory

Unless otherwise stated, blog posts on this site are copyrighted by their respective authors and published under the terms of the CC-BY-SA 4.0 license and those of the GNU Free Documentation License (version 1.3 or later, with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts).

AltStyle によって変換されたページ (->オリジナル) /