From b8897e7c7a27e7fcbbe0e89ead954e1e7254ab35 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 16 Oct 2026 14:11:55 +0200 Subject: [PATCH] hive-priv: lock personality, deny realtime, kernel logs and module loading Adds LockPersonality, RestrictRealtime, ProtectKernelLogs and ProtectKernelModules to the hive-priv unit. Nothing in hive-priv's sources or its children uses personality(2), real-time scheduling, /dev/kmsg, /proc/kmsg or explicit module loading. A module-eval case pins the four directives on the rendered unit. --- docs/trust-boundary/security.md | 8 +++++++- nix/host-modules/hive-priv.nix | 9 +++++++++ nix/module-eval/core-toggle.nix | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/trust-boundary/security.md b/docs/trust-boundary/security.md index 7f63df48..bc1eb353 100644 --- a/docs/trust-boundary/security.md +++ b/docs/trust-boundary/security.md @@ -322,7 +322,13 @@ above for how the hard-coded unit name scopes its access instead. **Socket-activated** — systemd starts `hive-priv` on the first incoming connection (`LISTEN_FDS=1`); it's not running between calls. The `ProtectSystem=strict` + `ReadWritePaths` sandbox limits filesystem -writes to only the paths `hive-priv` legitimately needs. +writes to only the paths `hive-priv` legitimately needs. The unit also sets +`LockPersonality`, `RestrictRealtime`, `ProtectKernelLogs` and +`ProtectKernelModules`: it changes no execution domain, schedules nothing in +real time, reads no kernel log, and loads no module. It sets none of +`RestrictNamespaces` (`nsenter` into a container needs `setns`), +`RestrictAddressFamilies`, `PrivateDevices`, `CapabilityBoundingSet` or +`SystemCallFilter`; those need a watched deploy to judge. ### Privilege boundary summary diff --git a/nix/host-modules/hive-priv.nix b/nix/host-modules/hive-priv.nix index 69b31f63..69bbee78 100644 --- a/nix/host-modules/hive-priv.nix +++ b/nix/host-modules/hive-priv.nix @@ -148,6 +148,15 @@ in # meta/applied repos; libgit2 refuses them without safe.directory. # See safeDirGitconfig above. ExecStartPre = "+-${pkgs.coreutils}/bin/cp ${safeDirGitconfig} /var/lib/hive-priv/.gitconfig"; + # Kernel surfaces neither hive-priv nor its children use: no + # personality(2), no real-time scheduling, no /dev/kmsg or + # /proc/kmsg, no explicit module loading (kernel-initiated autoload + # still works). Container start/stop run in `container@.service`, + # not under this unit. + LockPersonality = true; + RestrictRealtime = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; }; }; }; diff --git a/nix/module-eval/core-toggle.nix b/nix/module-eval/core-toggle.nix index d782051f..422aa7ae 100644 --- a/nix/module-eval/core-toggle.nix +++ b/nix/module-eval/core-toggle.nix @@ -1083,6 +1083,20 @@ let { unit, ... }: !(bare.systemd.services ? ${unit}) && !(bare.systemd.timers ? ${unit}) ) storeRefreshed; } + { + # `or false` so a dropped key fails this case by name instead of + # aborting eval. Only the rendered unit is pinned; eval cannot show + # that hive-priv still works under the directives. + name = "hive-priv locks personality and denies real-time scheduling, kernel logs and module loading"; + ok = + let + sc = allLocal.systemd.services.hive-priv.serviceConfig; + in + (sc.LockPersonality or false) + && (sc.RestrictRealtime or false) + && (sc.ProtectKernelLogs or false) + && (sc.ProtectKernelModules or false); + } ] # Starting a unit that stays `active (exited)` is a no-op, so a timer aimed # at one fires and fetches nothing.