fix(left4me): move ProcSubset=pid from COMMON to SERVER-only

Discovered post-deploy: ProcSubset=pid hides /proc/sys/kernel/random/boot_id
which journalctl reads at startup. The web app invokes
`sudo -n left4me-journalctl` to stream live server logs into the UI;
journalctl bails with "Failed to get boot ID" before producing any
output. Web log streaming was silently broken.

Server unit keeps ProcSubset=pid (srcds doesn't invoke journalctl);
web unit drops it. ProtectProc=invisible remains in COMMON — that's
the load-bearing D4 defense (foreign-uid /proc hidden).

Reproducer that confirms the diagnosis:
  systemd-run --pipe --uid=left4me --gid=left4me \
    -p ProcSubset=pid -p ProtectProc=invisible \
    -p ProtectSystem=strict -p PrivateTmp=true \
    [...rest of web hardening...] \
    -- sh -c 'sudo -n left4me-journalctl 2 --lines 3 --follow >/var/lib/left4me/tmp/out 2>&1'
  # cat /var/lib/left4me/tmp/out → "Failed to get boot ID: No such file or directory"
  # rc → 1
With ProcSubset=all: timeout 124 (helper running), 3 lines streamed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
CroneKorkN 2026-05-15 16:14:33 +02:00
parent 3ce1ee486e
commit 656be1cf69
Signed by: cronekorkn
SSH key fingerprint: SHA256:v0410ZKfuO1QHdgKBsdQNF64xmTxOF8osF1LIqwTcVw

View file

@ -128,9 +128,14 @@ defaults = {
# (paths in the left4me repo) # (paths in the left4me repo)
# Directives both managed units take verbatim. # Directives both managed units take verbatim.
#
# ProcSubset=pid is intentionally NOT in COMMON: it hides
# /proc/sys/kernel/random/boot_id which journalctl reads at startup,
# and the web unit invokes `sudo -n left4me-journalctl ...` to stream
# live server logs into the UI. Server unit adds it back in
# HARDENING_SERVER (srcds doesn't read journalctl).
HARDENING_COMMON = { HARDENING_COMMON = {
'ProtectProc': 'invisible', 'ProtectProc': 'invisible',
'ProcSubset': 'pid',
'ProtectKernelTunables': 'true', 'ProtectKernelTunables': 'true',
'ProtectKernelModules': 'true', 'ProtectKernelModules': 'true',
'ProtectKernelLogs': 'true', 'ProtectKernelLogs': 'true',
@ -154,6 +159,7 @@ HARDENING_COMMON = {
# socket binds. # socket binds.
HARDENING_SERVER = { HARDENING_SERVER = {
**HARDENING_COMMON, **HARDENING_COMMON,
'ProcSubset': 'pid',
'NoNewPrivileges': 'true', 'NoNewPrivileges': 'true',
'RestrictSUIDSGID': 'true', 'RestrictSUIDSGID': 'true',
'PrivateUsers': 'true', 'PrivateUsers': 'true',