Skip to content

os: make recursive directory deletion symlink-race safe - #776

Merged
davidgmbb merged 7 commits into
mainfrom
codex/95-directory-delete-fd-walk-20260917
Sep 18, 2026
Merged

davidgmbb merged 7 commits into
mainfrom
codex/95-directory-delete-fd-walk-20260917

Conversation

@davidgmbb

@davidgmbb davidgmbb commented Sep 17, 2026

Copy link
Copy Markdown
Member

Summary

  • replace the POSIX path-based delete worklist with an explicit descriptor-relative DFS
  • retain each parent DIR * until its child has been removed, open child directories with openat(..., O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC), enumerate with fdopendir, inspect with fstatat(..., AT_SYMLINK_NOFOLLOW), and remove with unlinkat
  • verify the opened and still-named directory identities before descent/removal, while preserving missing-path idempotence and the non-recursive deep-tree contract
  • leave the Windows implementation and its reparse-point handling unchanged
  • add a bounded POSIX stress regression that repeatedly swaps a child directory with a symlink to an outside directory and verifies the outside sentinel survives

Security invariant

After the root parent is opened, every descendant operation uses a single entry name relative to a retained directory descriptor. Directory opens refuse final-component links, and removals never follow links. A concurrent replacement can make deletion report failure or leave work for a retry, but it cannot redirect traversal into the symlink target.

openat2 is not required for this portable single-component walk: the retained descriptor plus O_NOFOLLOW/AT_SYMLINK_NOFOLLOW/unlinkat combination enforces the containment boundary on both Linux and Darwin.

Validation

  • main 734f1d4597fb705b1643da1280b84283c1485ead is integrated at edb5cd81a1badd7087b7a14a767bda68ad3e6497
  • all 55 retirement-contract, 23 materializer, and 15 rebinding tests passed locally
  • materializer and contract self-tests passed
  • complete pinned-file closure rematerialized; rebinding check reports current and a second refresh leaves all binding files unchanged
  • GCC syntax checks passed for the combined os.c and os_test.c
  • git diff --check passed
  • fresh full CI and the new rebinding workflow are queued/running on this head; their completion is still required for full merge readiness

Fixes #95.

Latest integration

Preserves the descriptor-relative deletion implementation together with main's transactional resource-failure handling from #787. Source and test changes merged cleanly. The four retirement-binding file conflicts were resolved using identities rebuilt from the combined source tree.

The new #804 rebinding parser rejected this repository's valid project-only descriptor because it required a resources section. This integration makes that section optional, consistent with the existing materializer, without adding or changing resource records. A regression covers read-only checking, refresh, retained omission, and repeat-refresh idempotence. Existing pinned-input rejection checks remain in place.

This is a history-preserving merge update. The available GitHub connection does not support the lease-protected force-push required to publish a history-rewriting rebase.

@github-actions
github-actions Bot force-pushed the codex/95-directory-delete-fd-walk-20260917 branch from 0baa06a to 1955a82 Compare September 17, 2026 18:20
@davidgmbb
davidgmbb force-pushed the codex/95-directory-delete-fd-walk-20260917 branch from 1955a82 to 3c2cce4 Compare September 17, 2026 18:22
@github-actions
github-actions Bot force-pushed the codex/95-directory-delete-fd-walk-20260917 branch from 30bc59f to 81c8d6c Compare September 17, 2026 18:31
Walk POSIX directory trees relative to retained descriptors, refuse links at every directory open, verify directory identities, and remove entries with unlinkat. Add a bounded adversarial directory-to-symlink swap regression and rebind the authenticated native-retirement closure exercised by the runtime change.

Fixes #95.
@davidgmbb
davidgmbb force-pushed the codex/95-directory-delete-fd-walk-20260917 branch from 81c8d6c to 5928369 Compare September 17, 2026 18:32
Preserve the descriptor-relative deletion fix alongside main's transactional resource-failure handling. Recompute the four evidence identities from the combined os.c.

Fix the new rebind parser rejecting the repository's valid project-only descriptor: resources remains optional, and refresh preserves its absence. Add check/refresh/idempotence regression coverage.

Validation: 55 contract, 23 materializer, and 15 rebind tests passed; both tool self-tests passed; exact combined-closure check and repeat refresh passed; GCC syntax checks for os.c and os_test.c passed.
@davidgmbb
davidgmbb merged commit b7a0f87 into main Sep 18, 2026
43 checks passed
@davidgmbb
davidgmbb deleted the codex/95-directory-delete-fd-walk-20260917 branch September 18, 2026 22:02
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.

os: make recursive directory deletion symlink-race safe

1 participant