From a97400df69de416fc445ed234e0d78d4a673c165 Mon Sep 17 00:00:00 2001 From: Ryan Yin Date: Sun, 1 Nov 2026 20:49:51 +0800 Subject: [PATCH] fix(systemd): sandbox privileged hardware oneshots Assisted-by: DeepSeek Harness (deepseek-flash) --- hosts/12kingdoms-shoukei/apple-silicon.nix | 3 +- hosts/12kingdoms-youko/default.nix | 5 ++- hosts/idols-aquamarine/hardware-amd-gpu.nix | 3 +- lib/systemdHardening.nix | 31 +++++++++++++++++++ .../security-service-hardening/expected.nix | 20 ++++++++++++ .../tests/security-service-hardening/expr.nix | 23 ++++++++++++++ 6 files changed, 82 insertions(+), 3 deletions(-) diff --git a/hosts/12kingdoms-shoukei/apple-silicon.nix b/hosts/12kingdoms-shoukei/apple-silicon.nix index 3ef23c62..e0716d80 100644 --- a/hosts/12kingdoms-shoukei/apple-silicon.nix +++ b/hosts/12kingdoms-shoukei/apple-silicon.nix @@ -1,5 +1,6 @@ { lib, + mylib, pkgs, nixos-apple-silicon, my-asahi-firmware, @@ -76,7 +77,7 @@ wantedBy = [ "multi-user.target" ]; after = [ "systemd-modules-load.service" ]; path = [ pkgs.coreutils ]; - serviceConfig = { + serviceConfig = mylib.systemdHardening.privilegedOneshot // { Type = "oneshot"; RemainAfterExit = true; ExecStart = pkgs.writeShellScript "apple-charge-limit" '' diff --git a/hosts/12kingdoms-youko/default.nix b/hosts/12kingdoms-youko/default.nix index d51ee170..9e236e1d 100644 --- a/hosts/12kingdoms-youko/default.nix +++ b/hosts/12kingdoms-youko/default.nix @@ -119,9 +119,12 @@ in description = "Disable APM/standby on the USB HDDs"; after = [ "local-fs.target" ]; wantedBy = [ "multi-user.target" ]; - serviceConfig = { + serviceConfig = mylib.systemdHardening.privilegedOneshot // { Type = "oneshot"; RemainAfterExit = true; + CapabilityBoundingSet = [ "CAP_SYS_RAWIO" ]; + PrivateDevices = false; + DeviceAllow = [ "block-sd rw" ]; }; script = '' for d in ${hddPublic} ${hddEncrypted}; do diff --git a/hosts/idols-aquamarine/hardware-amd-gpu.nix b/hosts/idols-aquamarine/hardware-amd-gpu.nix index b11ae411..b980396a 100644 --- a/hosts/idols-aquamarine/hardware-amd-gpu.nix +++ b/hosts/idols-aquamarine/hardware-amd-gpu.nix @@ -1,5 +1,6 @@ { pkgs, + mylib, myvars, ... }: @@ -83,7 +84,7 @@ wantedBy = [ "multi-user.target" ]; after = [ "systemd-modules-load.service" ]; path = [ pkgs.coreutils ]; - serviceConfig = { + serviceConfig = mylib.systemdHardening.privilegedOneshot // { Type = "oneshot"; RemainAfterExit = true; ExecStart = pkgs.writeShellScript "amdgpu-power-cap" '' diff --git a/lib/systemdHardening.nix b/lib/systemdHardening.nix index 5d333dad..272417c7 100644 --- a/lib/systemdHardening.nix +++ b/lib/systemdHardening.nix @@ -42,6 +42,37 @@ rec { ]; }; + # For short-lived root oneshots that perform one narrowly scoped privileged + # operation. Callers add only the capabilities and device access they need. + # ProtectKernelTunables is deliberately unset because it makes /sys read-only. + privilegedOneshot = { + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateNetwork = true; + PrivateTmp = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ "AF_UNIX" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + UMask = "0077"; + }; + # Apply a preset at mkOptionDefault priority, so nixpkgs module values win. mkDefaults = lib.mapAttrs (_: lib.mkOptionDefault); } diff --git a/outputs/x86_64-linux/tests/security-service-hardening/expected.nix b/outputs/x86_64-linux/tests/security-service-hardening/expected.nix index 246fc288..1bca34e2 100644 --- a/outputs/x86_64-linux/tests/security-service-hardening/expected.nix +++ b/outputs/x86_64-linux/tests/security-service-hardening/expected.nix @@ -9,6 +9,17 @@ let "~@clock @cpu-emulation @debug @module @mount @obsolete @privileged @raw-io @reboot @swap" ]; }; + + privilegedOneshot = { + CapabilityBoundingSet = ""; + DeviceAllow = null; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateNetwork = true; + ProtectKernelTunables = null; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ "AF_UNIX" ]; + }; in { # Only 80/443. @@ -21,4 +32,13 @@ in whisper-asr = sandbox // { PrivateDevices = false; }; + + privilegedOneshot = { + amdgpu-power-cap = privilegedOneshot; + hdd-no-spindown = privilegedOneshot // { + CapabilityBoundingSet = [ "CAP_SYS_RAWIO" ]; + DeviceAllow = [ "block-sd rw" ]; + PrivateDevices = false; + }; + }; } diff --git a/outputs/x86_64-linux/tests/security-service-hardening/expr.nix b/outputs/x86_64-linux/tests/security-service-hardening/expr.nix index cf7acb9b..fbd43600 100644 --- a/outputs/x86_64-linux/tests/security-service-hardening/expr.nix +++ b/outputs/x86_64-linux/tests/security-service-hardening/expr.nix @@ -12,6 +12,23 @@ let lib.genAttrs names ( n: lib.getAttrs keys outputs.nixosConfigurations.${host}.config.systemd.services.${n}.serviceConfig ); + + privilegedKeys = [ + "CapabilityBoundingSet" + "DeviceAllow" + "NoNewPrivileges" + "PrivateDevices" + "PrivateNetwork" + "ProtectKernelTunables" + "ProtectSystem" + "RestrictAddressFamilies" + ]; + privilegedUnit = + host: name: + let + cfg = outputs.nixosConfigurations.${host}.config.systemd.services.${name}.serviceConfig; + in + lib.genAttrs privilegedKeys (key: cfg.${key} or null); in units "youko" [ "caddy" @@ -19,3 +36,9 @@ units "youko" [ "vmalert-homelab" ] // units "aquamarine" [ "whisper-asr" ] +// { + privilegedOneshot = { + amdgpu-power-cap = privilegedUnit "aquamarine" "amdgpu-power-cap"; + hdd-no-spindown = privilegedUnit "youko" "hdd-no-spindown"; + }; +}