Commit 8f2c510f by PLN (Algolia)

fix(protect): root was never enough — the OOM guard could not open the file it guards

Installed the protection daemon and it reported 'protected: ardour[...]
oom:FAILED(need root, have uid 0)' every two seconds. It runs as root; the
message was misdirecting.

uid 0 bypasses file permissions via CAP_DAC_OVERRIDE, and the unit's
CapabilityBoundingSet listed only CAP_SYS_RESOURCE/SYS_NICE/IPC_LOCK — so the
bypass was gone. /proc/<pid>/oom_score_adj is mode 0644 owned by the process
owner, so every write returned EACCES while id -u still said 0.

scsynth and sclang looked fine only because they were already at the target and
no write was attempted. The daemon had never successfully protected anything.

- unit: add CAP_DAC_OVERRIDE to bounding + ambient sets
- failure messages print the effective capabilities, not 'need root'
- a FAILED sweep logs once per distinct message instead of 43200 times a day,
  which is what the file header already said it refuses to do
parent b82215ff
...@@ -34,10 +34,16 @@ StandardOutput=journal ...@@ -34,10 +34,16 @@ StandardOutput=journal
StandardError=journal StandardError=journal
SyslogIdentifier=pv-protect SyslogIdentifier=pv-protect
# It needs exactly two powers: lower oom_score_adj past the caller's floor, and # It needs exactly three powers: lower oom_score_adj past the caller's floor,
# set SCHED_FIFO on processes it does not own. Nothing else. # set SCHED_FIFO on processes it does not own, and OPEN those processes' proc
CapabilityBoundingSet=CAP_SYS_RESOURCE CAP_SYS_NICE CAP_IPC_LOCK # files at all. That last one is not implied by running as root: uid 0 bypasses
AmbientCapabilities=CAP_SYS_RESOURCE CAP_SYS_NICE CAP_IPC_LOCK # file permissions through CAP_DAC_OVERRIDE, and a CapabilityBoundingSet that
# omits it takes the bypass away. /proc/<pid>/oom_score_adj is mode 0644 owned
# by the process owner (pln), so without CAP_DAC_OVERRIDE every write failed
# with EACCES while `id -u` still said 0 — protection that reported itself
# working and did nothing. Found 2026-09-06, minutes after first install.
CapabilityBoundingSet=CAP_SYS_RESOURCE CAP_SYS_NICE CAP_IPC_LOCK CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_SYS_RESOURCE CAP_SYS_NICE CAP_IPC_LOCK CAP_DAC_OVERRIDE
NoNewPrivileges=yes NoNewPrivileges=yes
ProtectSystem=strict ProtectSystem=strict
ProtectHome=read-only ProtectHome=read-only
......
...@@ -60,6 +60,7 @@ TARGETS=( ...@@ -60,6 +60,7 @@ TARGETS=(
) )
OOM_TARGET=-1000 OOM_TARGET=-1000
LAST_FAILURE="" # last logged failure message, to keep a stuck fault quiet
log() { printf '%s %s\n' "$(date '+%H:%M:%S')" "$*"; } log() { printf '%s %s\n' "$(date '+%H:%M:%S')" "$*"; }
...@@ -81,6 +82,16 @@ pids_for() { ...@@ -81,6 +82,16 @@ pids_for() {
esac esac
} }
# Effective capabilities, for failure messages. "need root" was the wrong
# question: this runs AS root and still could not write, because the unit's
# CapabilityBoundingSet had dropped CAP_DAC_OVERRIDE. Print what we actually
# hold so the next failure names its own cause instead of misdirecting.
_caps() {
local eff
eff=$(sed -n 's/^CapEff:\s*//p' /proc/self/status 2>/dev/null)
capsh --decode="$eff" 2>/dev/null | sed -n 's/^0x[0-9a-f]*=//p' || echo "$eff"
}
# protect_pid <pid> <name> <rtprio> -> echoes what it CHANGED, nothing if already fine. # protect_pid <pid> <name> <rtprio> -> echoes what it CHANGED, nothing if already fine.
# Reporting only changes is deliberate: at a 2s poll a "still fine" line would be # Reporting only changes is deliberate: at a 2s poll a "still fine" line would be
# 43200 journal entries a day, which is the same as no logging at all. # 43200 journal entries a day, which is the same as no logging at all.
...@@ -93,7 +104,7 @@ protect_pid() { ...@@ -93,7 +104,7 @@ protect_pid() {
if echo "$OOM_TARGET" > "/proc/$pid/oom_score_adj" 2>/dev/null; then if echo "$OOM_TARGET" > "/proc/$pid/oom_score_adj" 2>/dev/null; then
changed+=" oom:${cur_oom}->${OOM_TARGET}" changed+=" oom:${cur_oom}->${OOM_TARGET}"
else else
changed+=" oom:FAILED(need root, have uid $(id -u))" changed+=" oom:FAILED(uid $(id -u), caps $(_caps))"
fi fi
fi fi
...@@ -127,7 +138,25 @@ sweep() { ...@@ -127,7 +138,25 @@ sweep() {
[ -n "$r" ] && out+="$r; " [ -n "$r" ] && out+="$r; "
done done
done done
[ -n "$out" ] && log "protected: ${out%; }" out="${out%; }"
if [ -z "$out" ]; then
return 0
fi
# A FAILED sweep repeats every poll for as long as the process lives — 43200
# identical lines a day, the exact noise this file's header refuses. Log a
# failure once per distinct message; the pid is in it, so a restart re-arms it.
case "$out" in
*FAILED*)
if [ "$out" != "$LAST_FAILURE" ]; then
LAST_FAILURE="$out"
log "PROTECTION FAILED: $out"
fi
;;
*)
LAST_FAILURE=""
log "protected: $out"
;;
esac
return 0 return 0
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment