Development record
SourceContext.ListDir opens directory entries with root.Open (no O_NONBLOCK), so a statically-planted FIFO named at a path a probe adapter lists (e.g. docs) hangs abcd disembark probe/plan/pack forever over an untrusted target repo — no race required, breaking the package's stated no-hang-on-FIFO invariant
SourceContext.ListDir opens directory entries with root.Open (no O_NONBLOCK), so a statically-planted FIFO named at a path a probe adapter lists (e.g. docs) hangs abcd disembark probe/plan/pack forever over an untrusted target repo — no race required, breaking the package's stated no-hang-on-FIFO invariant
Evidence
internal/core/lifeboat/probe.go:286--c.root.Open(rel)(nononBlock, no Lstat), unlike the exemplarSourceContext.ReadFileatprobe.go:230.- 18 call sites via adapters (
sources_conventions.go,sources_native.go,plan.go,graveyard_abandoned.go);cli.go:515/596/644pass an arbitrary user-named target repo. nonblock_unix.go:16definesnonBlock = O_NOFOLLOW | O_NONBLOCK.
Verifier verdict -- CONFIRMED (substantive, no race)
Live exploit against the HEAD build: a target repo containing README.md + a FIFO named docs hangs abcd disembark probe/plan/pack (EXIT 124 under timeout; control repo 0.031s). SIGQUIT dump shows adapter goroutines wedged in ListDir. Breaks the package's stated invariant (probe.go:128-129 "cannot ... hang on a FIFO"). Fix (verified): c.root.OpenFile(rel, os.O_RDONLY|nonBlock, 0) -- ListDir's existing len(entries)==0 -> nil turns the resulting not a directory into absent; directory listing unaffected; self-probe byte-identical.
Frontmatter
| id | iss-342 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | major |
.abcd/work/issues/resolved/iss-342-sourcecontext-listdir-opens-directory.md
open on GitHub ↗ · commit history ↗
mentions2
- dcn-26082000000000012026-08-20 — bughunt round 2 (branch bughunt-a/round-2): captured iss-331..iss-343 and fixed (ids 339-343 re-minted from an initial 326-330 after main concurrently took those numbers in the v0.6.1 cut)
- iss-337probe.go's inner directory descent (dirRoot.OpenRoot(name)) is guarded only by the parent ReadDir's IsDir, so a directory swapped for a FIFO in that window blocks — same TOCTOU shape as readLifeboatFile but race-only and unfixable via the nonBlock const (os.Root.OpenRoot takes no flags); needs a raw openat restructure, recorded not fixed