Add integration test for setuid runtime cleanup leak - #1404
Draft
jrray wants to merge 1 commit into
Draft
Conversation
jrray
force-pushed
the
setuid-runtime-cleanup-leak-test
branch
5 times, most recently
from
August 18, 2026 01:51
121b6eb to
a8dd141
Compare
A runtime whose entry command is a setuid binary is never cleaned up.
spfs-monitor identifies the runtime's mount namespace by reading
/proc/<owner>/ns/mnt. For a non-dumpable process that read does not fail
the way the code expects -- it succeeds and yields an empty string. So
identify_mount_namespace_of_process returns Some("") and the monitor
adopts "" as the runtime's namespace identity, which compares equal to
every non-dumpable process on the host. Its tracked set never empties
and the runtime is never removed.
A setuid entry command makes this deterministic rather than racy: the
payload is non-dumpable from its own exec, roughly 30ms before the
monitor reads. Measured locally, the monitor adopts ~380 unrelated pids
including kernel threads, and neither the runtime nor the monitor ever
goes away.
The test is expected to fail until this is fixed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jrray
force-pushed
the
setuid-runtime-cleanup-leak-test
branch
from
August 18, 2026 04:01
a8dd141 to
a69158b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
crates/spfs/tests/integration/privileged/test_runtime_cleanup_setuid_command.sh, a reproducer for a permanent runtime leak.This test is expected to fail. It is the regression test for a bug that is not fixed yet, opened as a draft so the red build is not mistaken for a broken PR.
The bug
A runtime whose entry command is a setuid binary is never cleaned up.
spfs-monitoridentifies the runtime's mount namespace by reading/proc/<owner>/ns/mnt. For a non-dumpable process that read does not fail the way the code expects — it succeeds and yields an empty string:So
identify_mount_namespace_of_processreturnsSome("")and the monitor adopts""as the runtime's namespace identity. Every non-dumpable process on the host then compares equal to it, kernel threads included, so the tracked set can never empty and the runtime is never removed.RetryOnPermissionDenied(monitor.rs:99-120) cannot catch this — it only fires onErrorKind::PermissionDenied, and this call succeeds.Why setuid makes it deterministic
Measured timings for the owner pid, sampled every 0.5ms:
spfs-enter, ns reads''''become_root()mnt:[…]become_original_user()— readable againFor an ordinary command this is safe by construction:
become_original_user()runs insideinitialize_runtime, andspawn_monitor_for_runtimeis only called after it returns. Across 8 runs the monitor read 25.8–28.7 ms after the owner became readable (mean +27.2), safe every time.A setuid payload is non-dumpable from its own exec, ~30 ms before the monitor reads, so it loses every time. A payload that self-marks non-dumpable at startup is a coin flip instead — one measured run lost by 3.0 ms and leaked, another won.
What the test does
Three stages, so a failure is unambiguous:
sleepruntime, asserted to clean up. Proves the fixture works.Sample local output:
That last line comes from tracing the monitor at
RUST_LOG=spfs=traceduring the reproducer. Because cleanup never runs,spfs-enter --exitnever truncates the log, so the evidence survives into CI output.Notes
privileged/because the bug needs both halves: root to create the setuid helper, and an unprivileged user to run spfs. A root monitor could read the payload's/procand this would not reproduce.run_privileged_tests.sh; no workflow change. Uses theuser1that runner already creates, overridable viaSPFS_TEST_USERfor local runs.wait_for_*idiom would hang the suite.Fix direction
An empty namespace read means unknown, never an identity — hold previous state and retry. That needs to land together with explicit helper registration, because
spfs-fuseis currently excluded from occupancy by this same empty-string behaviour; closing the hole alone would make fuse processes count as occupants and stop fuse runtimes from ever cleaning up.🤖 Generated with Claude Code