Skip to content

Fix writable shadow ADDFD_AT race#61

Merged
jserv merged 1 commit intomainfrom
fixup
Apr 18, 2026
Merged

Fix writable shadow ADDFD_AT race#61
jserv merged 1 commit intomainfrom
fixup

Conversation

@jserv
Copy link
Copy Markdown
Contributor

@jserv jserv commented Apr 18, 2026

try_writeback_shadow_open pre-allocated a fast-range FD slot via insert_fast, then used ADDFD_AT to inject a memfd at that number. This raced with concurrent close(2) CONTINUE: the supervisor removed its bookkeeping before the kernel replayed the close, so another thread could reuse that FD number and have the older close tear down the newly injected memfd (observable as EBADF on the next write).

This fixes the race with a post-allocation pattern:

  1. allocate_writable_shadow_fd scans the fast-shadow band and checks child_fd_is_open() via /proc/pid/fdinfo to skip slots with pending kernel-side closes.
  2. ADDFD_AT injects the memfd at the validated slot.
  3. The fd-table entry is published only after ADDFD_AT succeeds.

The two post-ADDFD error paths (injected!=target_fd, insert_at failure) are unreachable by construction: SECCOMP_ADDFD_FLAG_SETFD guarantees exact-target-or-negative, and the allocator validates the slot is in-range and free. Replace the old misleading EMFILE recovery with abort so a broken kernel contract crashes loudly instead of leaking untracked tracee FD.

Add remove_fd_table_entry_with_writeback so dup2, dup3, and exec paths sync writable shadow content back to LKL before clobbering fd-table entries.

Change-Id: I3e72df3a9c0ba3f1d8b6e4c7a5290e1fbc834d60


Summary by cubic

Fixes a race in writable shadow FD promotion by switching to post-ADDFD_AT allocation and delaying fd-table publish. Prevents EBADF from close(2) replay and keeps writeback consistent on dup/exec; stress tests now run in seccomp mode.

  • Bug Fixes

    • Scan the fast-shadow band for a free slot and skip FDs with pending closes via /proc/pid/fdinfo.
    • Use ADDFD_AT at that validated slot and publish the fd-table entry only after success (restricted to fast-shadow band).
    • Abort if the kernel returns a different FD or if insert_at fails to avoid leaking untracked FDs.
    • Sync writable shadows before dup2/dup3/exec clobber entries to keep LKL state consistent.
  • Refactors

    • Added close_cloexec_with_writeback and remove_fd_table_entry_with_writeback; applied in exec, dup2, and dup3 paths.
    • Stress infra: run tests in seccomp mode, enforce STRESS_TIMEOUT=120, and make failures blocking.

Written for commit 0000d31. Summary will update on new commits.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 5 files

try_writeback_shadow_open pre-allocated a fast-range FD slot via
insert_fast, then used ADDFD_AT to inject a memfd at that number. This
raced with concurrent close(2) CONTINUE: the supervisor removed its
bookkeeping before the kernel replayed the close, so another thread
could reuse that FD number and have the older close tear down the newly
injected memfd (observable as EBADF on the next write).

This fixes the race with a post-allocation pattern:
1. allocate_writable_shadow_fd scans the fast-shadow band and checks
   child_fd_is_open() via /proc/pid/fdinfo to skip slots with pending
   kernel-side closes.
2. ADDFD_AT injects the memfd at the validated slot.
3. The fd-table entry is published only after ADDFD_AT succeeds.

The two post-ADDFD error paths (injected!=target_fd, insert_at failure)
are unreachable by construction: SECCOMP_ADDFD_FLAG_SETFD guarantees
exact-target-or-negative, and the allocator validates the slot is
in-range and free. Replace the old misleading EMFILE recovery with abort
so a broken kernel contract crashes loudly instead of leaking untracked
tracee FD.

Add remove_fd_table_entry_with_writeback so dup2, dup3, and exec paths
sync writable shadow content back to LKL before clobbering fd-table
entries.

Change-Id: I3e72df3a9c0ba3f1d8b6e4c7a5290e1fbc834d60
@jserv jserv merged commit cf5df9c into main Apr 18, 2026
5 checks passed
@jserv jserv deleted the fixup branch April 18, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant