From 625dce659af3c332aa44ef494665d1cf5654efdd Mon Sep 17 00:00:00 2001 From: John Ericson Date: Tue, 27 May 2025 14:51:39 -0400 Subject: [PATCH] Prepare for FreeBSD sandboxing support This is the utility changes from #9968, which were easier to rebase first. I (@Ericson2314) didn't write this code; I just rebased it. Co-Authored-By: Artemis Tosini Co-Authored-By: Audrey Dutcher --- maintainers/flake-module.nix | 2 +- src/libstore/filetransfer.cc | 2 +- src/libstore/globals.cc | 2 +- src/libstore/include/nix/store/globals.hh | 2 + .../unix/build/linux-derivation-builder.cc | 2 +- src/libstore/unix/user-lock.cc | 2 +- src/libutil/current-process.cc | 25 ++++++- src/libutil/file-system.cc | 66 +++++++++++++++++-- src/libutil/freebsd/freebsd-jail.cc | 52 +++++++++++++++ .../freebsd/include/nix/util/freebsd-jail.hh | 20 ++++++ .../freebsd/include/nix/util/meson.build | 7 ++ src/libutil/freebsd/meson.build | 5 ++ src/libutil/include/nix/util/file-system.hh | 15 ++++- .../{namespaces.hh => linux-namespaces.hh} | 0 .../linux/include/nix/util/meson.build | 2 +- .../{namespaces.cc => linux-namespaces.cc} | 1 + src/libutil/linux/meson.build | 2 +- src/libutil/meson.build | 4 ++ src/nix/main.cc | 2 +- 19 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 src/libutil/freebsd/freebsd-jail.cc create mode 100644 src/libutil/freebsd/include/nix/util/freebsd-jail.hh create mode 100644 src/libutil/freebsd/include/nix/util/meson.build create mode 100644 src/libutil/freebsd/meson.build rename src/libutil/linux/include/nix/util/{namespaces.hh => linux-namespaces.hh} (100%) rename src/libutil/linux/{namespaces.cc => linux-namespaces.cc} (99%) diff --git a/maintainers/flake-module.nix b/maintainers/flake-module.nix index 224f47268..6d8633c4f 100644 --- a/maintainers/flake-module.nix +++ b/maintainers/flake-module.nix @@ -359,7 +359,7 @@ ''^src/libutil/json-utils\.cc$'' ''^src/libutil/include/nix/util/json-utils\.hh$'' ''^src/libutil/linux/cgroup\.cc$'' - ''^src/libutil/linux/namespaces\.cc$'' + ''^src/libutil/linux/linux-namespaces\.cc$'' ''^src/libutil/logging\.cc$'' ''^src/libutil/include/nix/util/logging\.hh$'' ''^src/libutil/memory-source-accessor\.cc$'' diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index 8080fcfdd..7e29d00e6 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -14,7 +14,7 @@ #endif #ifdef __linux__ -# include "nix/util/namespaces.hh" +# include "nix/util/linux-namespaces.hh" #endif #include diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index c2ecc4964..de5128347 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -85,7 +85,7 @@ Settings::Settings() builders = concatStringsSep("\n", ss); } -#if defined(__linux__) && defined(SANDBOX_SHELL) +#if (defined(__linux__) || defined(__FreeBSD__)) && defined(SANDBOX_SHELL) sandboxPaths = tokenizeString("/bin/sh=" SANDBOX_SHELL); #endif diff --git a/src/libstore/include/nix/store/globals.hh b/src/libstore/include/nix/store/globals.hh index 3a677216a..5fae2be23 100644 --- a/src/libstore/include/nix/store/globals.hh +++ b/src/libstore/include/nix/store/globals.hh @@ -682,7 +682,9 @@ public: description of the `size` option of `tmpfs` in mount(8). The default is `50%`. )"}; +#endif +#if defined(__linux__) || defined(__FreeBSD__) Setting sandboxBuildDir{this, "/build", "sandbox-build-dir", R"( *Linux only* diff --git a/src/libstore/unix/build/linux-derivation-builder.cc b/src/libstore/unix/build/linux-derivation-builder.cc index 8d4d973e1..efbe4c8bb 100644 --- a/src/libstore/unix/build/linux-derivation-builder.cc +++ b/src/libstore/unix/build/linux-derivation-builder.cc @@ -2,7 +2,7 @@ # include "nix/store/personality.hh" # include "nix/util/cgroup.hh" -# include "nix/util/namespaces.hh" +# include "nix/util/linux-namespaces.hh" # include "linux/fchmodat2-compat.hh" # include diff --git a/src/libstore/unix/user-lock.cc b/src/libstore/unix/user-lock.cc index ef806af79..6a07cb7cc 100644 --- a/src/libstore/unix/user-lock.cc +++ b/src/libstore/unix/user-lock.cc @@ -197,7 +197,7 @@ bool useBuildUsers() #ifdef __linux__ static bool b = (settings.buildUsersGroup != "" || settings.autoAllocateUids) && isRootUser(); return b; - #elif defined(__APPLE__) + #elif defined(__APPLE__) && defined(__FreeBSD__) static bool b = settings.buildUsersGroup != "" && isRootUser(); return b; #else diff --git a/src/libutil/current-process.cc b/src/libutil/current-process.cc index 4cc5a4218..1afefbcb2 100644 --- a/src/libutil/current-process.cc +++ b/src/libutil/current-process.cc @@ -16,7 +16,12 @@ #ifdef __linux__ # include # include "nix/util/cgroup.hh" -# include "nix/util/namespaces.hh" +# include "nix/util/linux-namespaces.hh" +#endif + +#ifdef __FreeBSD__ +# include +# include #endif namespace nix { @@ -115,6 +120,24 @@ std::optional getSelfExe() return buf; else return std::nullopt; + #elif defined(__FreeBSD__) + int sysctlName[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PATHNAME, + -1, + }; + size_t pathLen = 0; + if (sysctl(sysctlName, sizeof(sysctlName) / sizeof(sysctlName[0]), nullptr, &pathLen, nullptr, 0) < 0) { + return std::nullopt; + } + + std::vector path(pathLen); + if (sysctl(sysctlName, sizeof(sysctlName) / sizeof(sysctlName[0]), path.data(), &pathLen, nullptr, 0) < 0) { + return std::nullopt; + } + + return Path(path.begin(), path.end()); #else return std::nullopt; #endif diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index ad8cef38c..283d19fe6 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -23,6 +23,11 @@ #include +#ifdef __FreeBSD__ +# include +# include +#endif + #ifdef _WIN32 # include #endif @@ -364,6 +369,13 @@ void syncParent(const Path & path) fd.fsync(); } +#ifdef __FreeBSD__ +#define MOUNTEDPATHS_PARAM , std::set &mountedPaths +#define MOUNTEDPATHS_ARG , mountedPaths +#else +#define MOUNTEDPATHS_PARAM +#define MOUNTEDPATHS_ARG +#endif void recursiveSync(const Path & path) { @@ -410,11 +422,19 @@ void recursiveSync(const Path & path) } -static void _deletePath(Descriptor parentfd, const std::filesystem::path & path, uint64_t & bytesFreed) +static void _deletePath(Descriptor parentfd, const std::filesystem::path & path, uint64_t & bytesFreed MOUNTEDPATHS_PARAM) { #ifndef _WIN32 checkInterrupt(); +#ifdef __FreeBSD__ + // In case of emergency (unmount fails for some reason) not recurse into mountpoints. + // This prevents us from tearing up the nullfs-mounted nix store. + if (mountedPaths.find(path) != mountedPaths.end()) { + return; + } +#endif + std::string name(baseNameOf(path.native())); struct stat st; @@ -468,7 +488,7 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path, checkInterrupt(); std::string childName = dirent->d_name; if (childName == "." || childName == "..") continue; - _deletePath(dirfd(dir.get()), path + "/" + childName, bytesFreed); + _deletePath(dirfd(dir.get()), path + "/" + childName, bytesFreed MOUNTEDPATHS_ARG); } if (errno) throw SysError("reading directory %1%", path); } @@ -484,7 +504,7 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path, #endif } -static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed) +static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed MOUNTEDPATHS_PARAM) { Path dir = dirOf(path.string()); if (dir == "") @@ -496,7 +516,7 @@ static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFree throw SysError("opening directory '%1%'", path); } - _deletePath(dirfd.get(), path, bytesFreed); + _deletePath(dirfd.get(), path, bytesFreed MOUNTEDPATHS_ARG); } @@ -529,8 +549,20 @@ void createDirs(const std::filesystem::path & path) void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed) { //Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path); +#ifdef __FreeBSD__ + std::set mountedPaths; + struct statfs *mntbuf; + int count; + if ((count = getmntinfo(&mntbuf, MNT_WAIT)) < 0) { + throw SysError("getmntinfo"); + } + + for (int i = 0; i < count; i++) { + mountedPaths.emplace(mntbuf[i].f_mntonname); + } +#endif bytesFreed = 0; - _deletePath(path, bytesFreed); + _deletePath(path, bytesFreed MOUNTEDPATHS_ARG); } @@ -572,6 +604,30 @@ void AutoDelete::reset(const std::filesystem::path & p, bool recursive) { ////////////////////////////////////////////////////////////////////// +#ifdef __FreeBSD__ +AutoUnmount::AutoUnmount() : del{false} {} + +AutoUnmount::AutoUnmount(Path &p) : path(p), del(true) {} + +AutoUnmount::~AutoUnmount() +{ + try { + if (del) { + if (unmount(path.c_str(), 0) < 0) { + throw SysError("Failed to unmount path %1%", path); + } + } + } catch (...) { + ignoreExceptionInDestructor(); + } +} + +void AutoUnmount::cancel() +{ + del = false; +} +#endif + ////////////////////////////////////////////////////////////////////// std::string defaultTempDir() { diff --git a/src/libutil/freebsd/freebsd-jail.cc b/src/libutil/freebsd/freebsd-jail.cc new file mode 100644 index 000000000..575f9287e --- /dev/null +++ b/src/libutil/freebsd/freebsd-jail.cc @@ -0,0 +1,52 @@ +#ifdef __FreeBSD__ +# include "nix/util/freebsd-jail.hh" + +# include +# include +# include +# include + +# include "nix/util/error.hh" +# include "nix/util/util.hh" + +namespace nix { + +AutoRemoveJail::AutoRemoveJail() + : del{false} +{ +} + +AutoRemoveJail::AutoRemoveJail(int jid) + : jid(jid) + , del(true) +{ +} + +AutoRemoveJail::~AutoRemoveJail() +{ + try { + if (del) { + if (jail_remove(jid) < 0) { + throw SysError("Failed to remove jail %1%", jid); + } + } + } catch (...) { + ignoreExceptionInDestructor(); + } +} + +void AutoRemoveJail::cancel() +{ + del = false; +} + +void AutoRemoveJail::reset(int j) +{ + del = true; + jid = j; +} + +////////////////////////////////////////////////////////////////////// + +} +#endif diff --git a/src/libutil/freebsd/include/nix/util/freebsd-jail.hh b/src/libutil/freebsd/include/nix/util/freebsd-jail.hh new file mode 100644 index 000000000..cb5abc511 --- /dev/null +++ b/src/libutil/freebsd/include/nix/util/freebsd-jail.hh @@ -0,0 +1,20 @@ +#pragma once +///@file + +#include "nix/util/types.hh" + +namespace nix { + +class AutoRemoveJail +{ + int jid; + bool del; +public: + AutoRemoveJail(int jid); + AutoRemoveJail(); + ~AutoRemoveJail(); + void cancel(); + void reset(int j); +}; + +} diff --git a/src/libutil/freebsd/include/nix/util/meson.build b/src/libutil/freebsd/include/nix/util/meson.build new file mode 100644 index 000000000..561c8796c --- /dev/null +++ b/src/libutil/freebsd/include/nix/util/meson.build @@ -0,0 +1,7 @@ +# Public headers directory + +include_dirs += include_directories('../..') + +headers += files( + 'freebsd-jail.hh', +) diff --git a/src/libutil/freebsd/meson.build b/src/libutil/freebsd/meson.build new file mode 100644 index 000000000..8ffdc2832 --- /dev/null +++ b/src/libutil/freebsd/meson.build @@ -0,0 +1,5 @@ +sources += files( + 'freebsd-jail.cc', +) + +subdir('include/nix/util') diff --git a/src/libutil/include/nix/util/file-system.hh b/src/libutil/include/nix/util/file-system.hh index b4cc1567d..0121745ab 100644 --- a/src/libutil/include/nix/util/file-system.hh +++ b/src/libutil/include/nix/util/file-system.hh @@ -310,7 +310,7 @@ typedef std::unique_ptr AutoCloseDir; /** * Create a temporary directory. */ -Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix", +Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix", mode_t mode = 0755); /** @@ -420,4 +420,17 @@ private: std::filesystem::directory_iterator it_; }; +#ifdef __FreeBSD__ +class AutoUnmount +{ + Path path; + bool del; +public: + AutoUnmount(Path&); + AutoUnmount(); + ~AutoUnmount(); + void cancel(); +}; +#endif + } diff --git a/src/libutil/linux/include/nix/util/namespaces.hh b/src/libutil/linux/include/nix/util/linux-namespaces.hh similarity index 100% rename from src/libutil/linux/include/nix/util/namespaces.hh rename to src/libutil/linux/include/nix/util/linux-namespaces.hh diff --git a/src/libutil/linux/include/nix/util/meson.build b/src/libutil/linux/include/nix/util/meson.build index 9587aa916..e28ad8e05 100644 --- a/src/libutil/linux/include/nix/util/meson.build +++ b/src/libutil/linux/include/nix/util/meson.build @@ -4,5 +4,5 @@ include_dirs += include_directories('../..') headers += files( 'cgroup.hh', - 'namespaces.hh', + 'linux-namespaces.hh', ) diff --git a/src/libutil/linux/namespaces.cc b/src/libutil/linux/linux-namespaces.cc similarity index 99% rename from src/libutil/linux/namespaces.cc rename to src/libutil/linux/linux-namespaces.cc index 405866c0b..93f299076 100644 --- a/src/libutil/linux/namespaces.cc +++ b/src/libutil/linux/linux-namespaces.cc @@ -1,3 +1,4 @@ +#include "nix/util/linux-namespaces.hh" #include "nix/util/current-process.hh" #include "nix/util/util.hh" #include "nix/util/finally.hh" diff --git a/src/libutil/linux/meson.build b/src/libutil/linux/meson.build index bfda8b1a6..b8053a5bb 100644 --- a/src/libutil/linux/meson.build +++ b/src/libutil/linux/meson.build @@ -1,6 +1,6 @@ sources += files( 'cgroup.cc', - 'namespaces.cc', + 'linux-namespaces.cc', ) subdir('include/nix/util') diff --git a/src/libutil/meson.build b/src/libutil/meson.build index 04ca06eee..f5ad2b1f6 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -169,6 +169,10 @@ if host_machine.system() == 'linux' subdir('linux') endif +if host_machine.system() == 'freebsd' + subdir('freebsd') +endif + if host_machine.system() == 'windows' subdir('windows') else diff --git a/src/nix/main.cc b/src/nix/main.cc index 0e5ccf34b..6144f746f 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -38,7 +38,7 @@ #endif #ifdef __linux__ -# include "nix/util/namespaces.hh" +# include "nix/util/linux-namespaces.hh" #endif #ifndef _WIN32