diff --git a/deploy/files/etc/systemd/system/left4me-server@.service.d/10-hardening.conf b/deploy/files/etc/systemd/system/left4me-server@.service.d/10-hardening.conf index 6408963..7bb47d0 100644 --- a/deploy/files/etc/systemd/system/left4me-server@.service.d/10-hardening.conf +++ b/deploy/files/etc/systemd/system/left4me-server@.service.d/10-hardening.conf @@ -52,7 +52,15 @@ PrivateIPC=true RestrictNamespaces=true RestrictRealtime=true ProtectProc=invisible -ProcSubset=pid +# ProcSubset=pid intentionally OMITTED — it hides /proc/cpuinfo and +# /proc/sys/*, which breaks Source's tier0/cpu.cpp and (downstream) +# SteamAPI_Init's pipe-creation step. Server then registers as LAN-only +# and rejects external clients with "LAN servers are restricted to +# local clients (class C)". PrivatePIDs=true (kernel PID namespace) is +# the load-bearing peer-process isolation; ProtectProc=invisible is the +# foreign-uid /proc hide. Losing ProcSubset=pid only exposes host kernel +# info (cpuinfo, meminfo, sysctls), which is not sensitive in this +# threat model. See ckn-bw commit 4339289 for the original fix. ProtectKernelTunables=true ProtectKernelModules=true ProtectKernelLogs=true diff --git a/deploy/tests/test_example_units.py b/deploy/tests/test_example_units.py index e1ece09..7850856 100644 --- a/deploy/tests/test_example_units.py +++ b/deploy/tests/test_example_units.py @@ -286,7 +286,6 @@ def test_server_hardening_dropin_present_with_directives(): "CapabilityBoundingSet=", "AmbientCapabilities=", "SystemCallArchitectures=native x86", - "ProcSubset=pid", "TemporaryFileSystem=/var/lib /etc /opt /home /root /srv /mnt /media", "BindReadOnlyPaths=/var/lib/left4me/installation", "BindReadOnlyPaths=/var/lib/left4me/overlays", @@ -299,3 +298,9 @@ def test_server_hardening_dropin_present_with_directives(): assert "SystemCallFilter=~@debug @mount @raw-io @reboot @swap @cpu-emulation @obsolete @privileged" in text # MemoryDenyWriteExecute must remain absent (Source engine compat). assert "MemoryDenyWriteExecute" not in text + # ProcSubset=pid must remain absent — hides /proc/cpuinfo and breaks + # SteamAPI master-server registration (LAN-only fallback). See + # ckn-bw 4339289 and the comment block in the drop-in itself. + for line in text.splitlines(): + bare = line.split("#", 1)[0].strip() + assert bare != "ProcSubset=pid", "ProcSubset=pid must not be active in the server drop-in"