···11-From 787e012f26761e1455e711ab4ceedaa2c740621c Mon Sep 17 00:00:00 2001
22-From: Eelco Dolstra <edolstra@gmail.com>
33-Date: Thu, 19 Jun 2025 16:20:34 +0200
44-Subject: [PATCH] Fixes for GHSA-g948-229j-48j3
55-MIME-Version: 1.0
66-Content-Type: text/plain; charset=UTF-8
77-Content-Transfer-Encoding: 8bit
88-99-Squashed commit of the following:
1010-1111-commit 04fff3a637d455cbb1d75937a235950e43008db9
1212-Author: Eelco Dolstra <edolstra@gmail.com>
1313-Date: Thu Jun 12 12:30:32 2025 +0200
1414-1515- Chown structured attr files safely
1616-1717-commit 5417ad445e414c649d0cfc71a05661c7bf8f3ef5
1818-Author: Eelco Dolstra <edolstra@gmail.com>
1919-Date: Thu Jun 12 12:14:04 2025 +0200
2020-2121- Replace 'bool sync' with an enum for clarity
2222-2323- And drop writeFileAndSync().
2424-2525-commit 7ae0141f328d8e8e1094be24665789c05f974ba6
2626-Author: Eelco Dolstra <edolstra@gmail.com>
2727-Date: Thu Jun 12 11:35:28 2025 +0200
2828-2929- Drop guessOrInventPathFromFD()
3030-3131- No need to do hacky stuff like that when we already know the original path.
3232-3333-commit 45b05098bd019da7c57cd4227a89bfd0fa65bb08
3434-Author: Eelco Dolstra <edolstra@gmail.com>
3535-Date: Thu Jun 12 11:15:58 2025 +0200
3636-3737- Tweak comment
3838-3939-commit 0af15b31209d1b7ec8addfae9a1a6b60d8f35848
4040-Author: Raito Bezarius <raito@lix.systems>
4141-Date: Thu Mar 27 12:22:26 2025 +0100
4242-4343- libstore: ensure that temporary directory is always 0o000 before deletion
4444-4545- In the case the deletion fails, we should ensure that the temporary
4646- directory cannot be used for nefarious purposes.
4747-4848- Change-Id: I498a2dd0999a74195d13642f44a5de1e69d46120
4949- Signed-off-by: Raito Bezarius <raito@lix.systems>
5050-5151-commit 2c20fa37b15cfa03ac6a1a6a47cdb2ed66c0827e
5252-Author: Raito Bezarius <raito@lix.systems>
5353-Date: Wed Mar 26 12:42:55 2025 +0100
5454-5555- libutil: ensure that `_deletePath` does NOT use absolute paths with dirfds
5656-5757- When calling `_deletePath` with a parent file descriptor, `openat` is
5858- made effective by using relative paths to the directory file descriptor.
5959-6060- To avoid the problem, the signature is changed to resist misuse with an
6161- assert in the prologue of the function.
6262-6363- Change-Id: I6b3fc766bad2afe54dc27d47d1df3873e188de96
6464- Signed-off-by: Raito Bezarius <raito@lix.systems>
6565-6666-commit d3c370bbcae48bb825ce19fd0f73bb4eefd2c9ea
6767-Author: Raito Bezarius <raito@lix.systems>
6868-Date: Wed Mar 26 01:07:47 2025 +0100
6969-7070- libstore: ensure that `passAsFile` is created in the original temp dir
7171-7272- This ensures that `passAsFile` data is created inside the expected
7373- temporary build directory by `openat()` from the parent directory file
7474- descriptor.
7575-7676- This avoids a TOCTOU which is part of the attack chain of CVE-????.
7777-7878- Change-Id: Ie5273446c4a19403088d0389ae8e3f473af8879a
7979- Signed-off-by: Raito Bezarius <raito@lix.systems>
8080-8181-commit 45d3598724f932d024ef6bc2ffb00c1bb90e6018
8282-Author: Raito Bezarius <raito@lix.systems>
8383-Date: Wed Mar 26 01:06:03 2025 +0100
8484-8585- libutil: writeFile variant for file descriptors
8686-8787- `writeFile` lose its `sync` boolean flag to make things simpler.
8888-8989- A new `writeFileAndSync` function is created and all call sites are
9090- converted to it.
9191-9292- Change-Id: Ib871a5283a9c047db1e4fe48a241506e4aab9192
9393- Signed-off-by: Raito Bezarius <raito@lix.systems>
9494-9595-commit 732bd9b98cabf4aaf95a01fd318923de303f9996
9696-Author: Raito Bezarius <raito@lix.systems>
9797-Date: Wed Mar 26 01:05:34 2025 +0100
9898-9999- libstore: chown to builder variant for file descriptors
100100-101101- We use it immediately for the build temporary directory.
102102-103103- Change-Id: I180193c63a2b98721f5fb8e542c4e39c099bb947
104104- Signed-off-by: Raito Bezarius <raito@lix.systems>
105105-106106-commit 962c65f8dcd5570dd92c72370a862c7b38942e0d
107107-Author: Raito Bezarius <raito@lix.systems>
108108-Date: Wed Mar 26 01:04:59 2025 +0100
109109-110110- libstore: open build directory as a dirfd as well
111111-112112- We now keep around a proper AutoCloseFD around the temporary directory
113113- which we plan to use for openat operations and avoiding the build
114114- directory being swapped out while we are doing something else.
115115-116116- Change-Id: I18d387b0f123ebf2d20c6405cd47ebadc5505f2a
117117- Signed-off-by: Raito Bezarius <raito@lix.systems>
118118-119119-commit c9b42462b75b5a37ee6564c2b53cff186c8323da
120120-Author: Raito Bezarius <raito@lix.systems>
121121-Date: Wed Mar 26 01:04:12 2025 +0100
122122-123123- libutil: guess or invent a path from file descriptors
124124-125125- This is useful for certain error recovery paths (no pun intended) that
126126- does not thread through the original path name.
127127-128128- Change-Id: I2d800740cb4f9912e64c923120d3f977c58ccb7e
129129- Signed-off-by: Raito Bezarius <raito@lix.systems>
130130-131131-Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
132132----
133133- src/libstore/local-store.cc | 6 +--
134134- .../unix/build/local-derivation-goal.cc | 46 ++++++++++++++----
135135- .../unix/build/local-derivation-goal.hh | 20 ++++++++
136136- src/libutil/file-content-address.cc | 2 +-
137137- src/libutil/file-system.cc | 47 +++++++++++--------
138138- src/libutil/file-system.hh | 14 ++++--
139139- 6 files changed, 99 insertions(+), 36 deletions(-)
140140-141141-diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
142142-index 9a7a941b6..c0c808e0a 100644
143143---- a/src/libstore/local-store.cc
144144-+++ b/src/libstore/local-store.cc
145145-@@ -116,7 +116,7 @@ LocalStore::LocalStore(
146146- state->stmts = std::make_unique<State::Stmts>();
147147-148148- /* Create missing state directories if they don't already exist. */
149149-- createDirs(realStoreDir);
150150-+ createDirs(realStoreDir.get());
151151- if (readOnly) {
152152- experimentalFeatureSettings.require(Xp::ReadOnlyLocalStore);
153153- } else {
154154-@@ -248,7 +248,7 @@ LocalStore::LocalStore(
155155- else if (curSchema == 0) { /* new store */
156156- curSchema = nixSchemaVersion;
157157- openDB(*state, true);
158158-- writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, true);
159159-+ writeFile(schemaPath, fmt("%1%", curSchema), 0666, FsSync::Yes);
160160- }
161161-162162- else if (curSchema < nixSchemaVersion) {
163163-@@ -299,7 +299,7 @@ LocalStore::LocalStore(
164164- txn.commit();
165165- }
166166-167167-- writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, true);
168168-+ writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, FsSync::Yes);
169169-170170- lockFile(globalLock.get(), ltRead, true);
171171- }
172172-diff --git a/src/libstore/unix/build/local-derivation-goal.cc b/src/libstore/unix/build/local-derivation-goal.cc
173173-index 5b9bc0bb0..80309e332 100644
174174---- a/src/libstore/unix/build/local-derivation-goal.cc
175175-+++ b/src/libstore/unix/build/local-derivation-goal.cc
176176-@@ -559,7 +559,14 @@ void LocalDerivationGoal::startBuilder()
177177- } else {
178178- tmpDir = topTmpDir;
179179- }
180180-- chownToBuilder(tmpDir);
181181-+
182182-+ /* The TOCTOU between the previous mkdir call and this open call is unavoidable due to
183183-+ POSIX semantics.*/
184184-+ tmpDirFd = AutoCloseFD{open(tmpDir.c_str(), O_RDONLY | O_NOFOLLOW | O_DIRECTORY)};
185185-+ if (!tmpDirFd)
186186-+ throw SysError("failed to open the build temporary directory descriptor '%1%'", tmpDir);
187187-+
188188-+ chownToBuilder(tmpDirFd.get(), tmpDir);
189189-190190- for (auto & [outputName, status] : initialOutputs) {
191191- /* Set scratch path we'll actually use during the build.
192192-@@ -1157,9 +1164,7 @@ void LocalDerivationGoal::initTmpDir()
193193- } else {
194194- auto hash = hashString(HashAlgorithm::SHA256, i.first);
195195- std::string fn = ".attr-" + hash.to_string(HashFormat::Nix32, false);
196196-- Path p = tmpDir + "/" + fn;
197197-- writeFile(p, rewriteStrings(i.second, inputRewrites));
198198-- chownToBuilder(p);
199199-+ writeBuilderFile(fn, rewriteStrings(i.second, inputRewrites));
200200- env[i.first + "Path"] = tmpDirInSandbox + "/" + fn;
201201- }
202202- }
203203-@@ -1264,11 +1269,9 @@ void LocalDerivationGoal::writeStructuredAttrs()
204204-205205- auto jsonSh = writeStructuredAttrsShell(json);
206206-207207-- writeFile(tmpDir + "/.attrs.sh", rewriteStrings(jsonSh, inputRewrites));
208208-- chownToBuilder(tmpDir + "/.attrs.sh");
209209-+ writeBuilderFile(".attrs.sh", rewriteStrings(jsonSh, inputRewrites));
210210- env["NIX_ATTRS_SH_FILE"] = tmpDirInSandbox + "/.attrs.sh";
211211-- writeFile(tmpDir + "/.attrs.json", rewriteStrings(json.dump(), inputRewrites));
212212-- chownToBuilder(tmpDir + "/.attrs.json");
213213-+ writeBuilderFile(".attrs.json", rewriteStrings(json.dump(), inputRewrites));
214214- env["NIX_ATTRS_JSON_FILE"] = tmpDirInSandbox + "/.attrs.json";
215215- }
216216- }
217217-@@ -1779,6 +1782,24 @@ void setupSeccomp()
218218- #endif
219219- }
220220-221221-+void LocalDerivationGoal::chownToBuilder(int fd, const Path & path)
222222-+{
223223-+ if (!buildUser) return;
224224-+ if (fchown(fd, buildUser->getUID(), buildUser->getGID()) == -1)
225225-+ throw SysError("cannot change ownership of file '%1%'", path);
226226-+}
227227-+
228228-+void LocalDerivationGoal::writeBuilderFile(
229229-+ const std::string & name,
230230-+ std::string_view contents)
231231-+{
232232-+ auto path = std::filesystem::path(tmpDir) / name;
233233-+ AutoCloseFD fd{openat(tmpDirFd.get(), name.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, 0666)};
234234-+ if (!fd)
235235-+ throw SysError("creating file %s", path);
236236-+ writeFile(fd, path, contents);
237237-+ chownToBuilder(fd.get(), path);
238238-+}
239239-240240- void LocalDerivationGoal::runChild()
241241- {
242242-@@ -3038,6 +3059,15 @@ void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo
243243- void LocalDerivationGoal::deleteTmpDir(bool force)
244244- {
245245- if (topTmpDir != "") {
246246-+ /* As an extra precaution, even in the event of `deletePath` failing to
247247-+ * clean up, the `tmpDir` will be chowned as if we were to move
248248-+ * it inside the Nix store.
249249-+ *
250250-+ * This hardens against an attack which smuggles a file descriptor
251251-+ * to make use of the temporary directory.
252252-+ */
253253-+ chmod(topTmpDir.c_str(), 0000);
254254-+
255255- /* Don't keep temporary directories for builtins because they
256256- might have privileged stuff (like a copy of netrc). */
257257- if (settings.keepFailed && !force && !drv->isBuiltin()) {
258258-diff --git a/src/libstore/unix/build/local-derivation-goal.hh b/src/libstore/unix/build/local-derivation-goal.hh
259259-index 1ea247661..74a1e1c50 100644
260260---- a/src/libstore/unix/build/local-derivation-goal.hh
261261-+++ b/src/libstore/unix/build/local-derivation-goal.hh
262262-@@ -37,6 +37,11 @@ struct LocalDerivationGoal : public DerivationGoal
263263- */
264264- Path topTmpDir;
265265-266266-+ /**
267267-+ * The file descriptor of the temporary directory.
268268-+ */
269269-+ AutoCloseFD tmpDirFd;
270270-+
271271- /**
272272- * The path of the temporary directory in the sandbox.
273273- */
274274-@@ -244,9 +249,24 @@ struct LocalDerivationGoal : public DerivationGoal
275275-276276- /**
277277- * Make a file owned by the builder.
278278-+ *
279279-+ * SAFETY: this function is prone to TOCTOU as it receives a path and not a descriptor.
280280-+ * It's only safe to call in a child of a directory only visible to the owner.
281281- */
282282- void chownToBuilder(const Path & path);
283283-284284-+ /**
285285-+ * Make a file owned by the builder addressed by its file descriptor.
286286-+ */
287287-+ void chownToBuilder(int fd, const Path & path);
288288-+
289289-+ /**
290290-+ * Create a file in `tmpDir` owned by the builder.
291291-+ */
292292-+ void writeBuilderFile(
293293-+ const std::string & name,
294294-+ std::string_view contents);
295295-+
296296- int getChildStatus() override;
297297-298298- /**
299299-diff --git a/src/libutil/file-content-address.cc b/src/libutil/file-content-address.cc
300300-index 69301d9c8..2b6839346 100644
301301---- a/src/libutil/file-content-address.cc
302302-+++ b/src/libutil/file-content-address.cc
303303-@@ -93,7 +93,7 @@ void restorePath(
304304- {
305305- switch (method) {
306306- case FileSerialisationMethod::Flat:
307307-- writeFile(path, source, 0666, startFsync);
308308-+ writeFile(path, source, 0666, startFsync ? FsSync::Yes : FsSync::No);
309309- break;
310310- case FileSerialisationMethod::NixArchive:
311311- restorePath(path, source, startFsync);
312312-diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc
313313-index 6fe93b63a..b3183f495 100644
314314---- a/src/libutil/file-system.cc
315315-+++ b/src/libutil/file-system.cc
316316-@@ -258,7 +258,7 @@ void readFile(const Path & path, Sink & sink)
317317- }
318318-319319-320320--void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
321321-+void writeFile(const Path & path, std::string_view s, mode_t mode, FsSync sync)
322322- {
323323- AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT
324324- // TODO
325325-@@ -268,22 +268,29 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
326326- , mode));
327327- if (!fd)
328328- throw SysError("opening file '%1%'", path);
329329-+
330330-+ writeFile(fd, path, s, mode, sync);
331331-+
332332-+ /* Close explicitly to propagate the exceptions. */
333333-+ fd.close();
334334-+}
335335-+
336336-+void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode, FsSync sync)
337337-+{
338338-+ assert(fd);
339339- try {
340340- writeFull(fd.get(), s);
341341-+
342342-+ if (sync == FsSync::Yes)
343343-+ fd.fsync();
344344-+
345345- } catch (Error & e) {
346346-- e.addTrace({}, "writing file '%1%'", path);
347347-+ e.addTrace({}, "writing file '%1%'", origPath);
348348- throw;
349349- }
350350-- if (sync)
351351-- fd.fsync();
352352-- // Explicitly close to make sure exceptions are propagated.
353353-- fd.close();
354354-- if (sync)
355355-- syncParent(path);
356356- }
357357-358358--
359359--void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
360360-+void writeFile(const Path & path, Source & source, mode_t mode, FsSync sync)
361361- {
362362- AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT
363363- // TODO
364364-@@ -307,11 +314,11 @@ void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
365365- e.addTrace({}, "writing file '%1%'", path);
366366- throw;
367367- }
368368-- if (sync)
369369-+ if (sync == FsSync::Yes)
370370- fd.fsync();
371371- // Explicitly close to make sure exceptions are propagated.
372372- fd.close();
373373-- if (sync)
374374-+ if (sync == FsSync::Yes)
375375- syncParent(path);
376376- }
377377-378378-@@ -374,7 +381,8 @@ static void _deletePath(Descriptor parentfd, const fs::path & path, uint64_t & b
379379- #ifndef _WIN32
380380- checkInterrupt();
381381-382382-- std::string name(baseNameOf(path.native()));
383383-+ std::string name(path.filename());
384384-+ assert(name != "." && name != ".." && !name.empty());
385385-386386- struct stat st;
387387- if (fstatat(parentfd, name.c_str(), &st,
388388-@@ -415,7 +423,7 @@ static void _deletePath(Descriptor parentfd, const fs::path & path, uint64_t & b
389389- throw SysError("chmod %1%", path);
390390- }
391391-392392-- int fd = openat(parentfd, path.c_str(), O_RDONLY);
393393-+ int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
394394- if (fd == -1)
395395- throw SysError("opening directory %1%", path);
396396- AutoCloseDir dir(fdopendir(fd));
397397-@@ -427,7 +435,7 @@ static void _deletePath(Descriptor parentfd, const fs::path & path, uint64_t & b
398398- checkInterrupt();
399399- std::string childName = dirent->d_name;
400400- if (childName == "." || childName == "..") continue;
401401-- _deletePath(dirfd(dir.get()), path + "/" + childName, bytesFreed);
402402-+ _deletePath(dirfd(dir.get()), path / childName, bytesFreed);
403403- }
404404- if (errno) throw SysError("reading directory %1%", path);
405405- }
406406-@@ -445,14 +453,13 @@ static void _deletePath(Descriptor parentfd, const fs::path & path, uint64_t & b
407407-408408- static void _deletePath(const fs::path & path, uint64_t & bytesFreed)
409409- {
410410-- Path dir = dirOf(path.string());
411411-- if (dir == "")
412412-- dir = "/";
413413-+ assert(path.is_absolute());
414414-+ assert(path.parent_path() != path);
415415-416416-- AutoCloseFD dirfd = toDescriptor(open(dir.c_str(), O_RDONLY));
417417-+ AutoCloseFD dirfd = toDescriptor(open(path.parent_path().string().c_str(), O_RDONLY));
418418- if (!dirfd) {
419419- if (errno == ENOENT) return;
420420-- throw SysError("opening directory '%1%'", path);
421421-+ throw SysError("opening directory %s", path.parent_path());
422422- }
423423-424424- _deletePath(dirfd.get(), path, bytesFreed);
425425-diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh
426426-index 204907339..b2db8869e 100644
427427---- a/src/libutil/file-system.hh
428428-+++ b/src/libutil/file-system.hh
429429-@@ -194,21 +194,27 @@ std::string readFile(const Path & path);
430430- std::string readFile(const std::filesystem::path & path);
431431- void readFile(const Path & path, Sink & sink);
432432-433433-+enum struct FsSync { Yes, No };
434434-+
435435- /**
436436- * Write a string to a file.
437437- */
438438--void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, bool sync = false);
439439--static inline void writeFile(const std::filesystem::path & path, std::string_view s, mode_t mode = 0666, bool sync = false)
440440-+void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
441441-+
442442-+static inline void writeFile(const std::filesystem::path & path, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No)
443443- {
444444- return writeFile(path.string(), s, mode, sync);
445445- }
446446-447447--void writeFile(const Path & path, Source & source, mode_t mode = 0666, bool sync = false);
448448--static inline void writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 0666, bool sync = false)
449449-+void writeFile(const Path & path, Source & source, mode_t mode = 0666, FsSync sync = FsSync::No);
450450-+
451451-+static inline void writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 0666, FsSync sync = FsSync::No)
452452- {
453453- return writeFile(path.string(), source, mode, sync);
454454- }
455455-456456-+void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
457457-+
458458- /**
459459- * Flush a path's parent directory to disk.
460460- */
461461---
462462-2.49.0
463463-
+2
pkgs/top-level/aliases.nix
···412412 cassandra_3_0 = throw "'cassandra_3_0' has been removed has it reached end-of-life"; # Added 2025-03-23
413413 cassandra_3_11 = throw "'cassandra_3_11' has been removed has it reached end-of-life"; # Added 2025-03-23
414414 cawbird = throw "cawbird has been abandoned upstream and is broken anyways due to Twitter closing its API";
415415+ catalyst-browser = throw "'catalyst-browser' has been removed due to a lack of maintenance and not satisfying our security criteria for browsers."; # Added 2025-06-25
415416 cde = throw "'cde' has been removed as it is unmaintained and broken"; # Added 2025-05-17
416417 centerim = throw "centerim has been removed due to upstream disappearing"; # Added 2025-04-18
417418 certmgr-selfsigned = certmgr; # Added 2023-11-30
···20532054 ventoy-bin = ventoy; # Added 2023-04-12
20542055 ventoy-bin-full = ventoy-full; # Added 2023-04-12
20552056 verilog = iverilog; # Added 2024-07-12
20572057+ vieb = throw "'vieb' has been removed as it doesn't satisfy our security criteria for browsers."; # Added 2025-06-25
20562058 ViennaRNA = viennarna; # Added 2023-08-23
20572059 vimHugeX = vim-full; # Added 2022-12-04
20582060 vim_configurable = vim-full; # Added 2022-12-04