Data race on fifoCleanupStarted between MountSecrets' writer goroutine and cleanup closure
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 396
- Forks
- 83
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 3
Description
Summary
MountSecrets shares the fifoCleanupStarted flag between the secrets-writer goroutine and the cleanup closure without any synchronization. This is a data race under the Go memory model: when the consumer's cleanup function runs on the main goroutine while the writer goroutine is inside its loop, both can access the plain bool concurrently.
Location
- File:
pkg/controllers/secrets.go - Function:
MountSecrets - Flag declaration: line 151 (
fifoCleanupStarted := false) - Write: line 155 (
cleanupFIFOsetsfifoCleanupStarted = true) - Reads: lines 188, 202, 214 (
if errors.Is(err, fs.ErrNotExist) && fifoCleanupStarted), performed from the writer goroutine
fifoCleanupStarted := false // no mutex/atomic
cleanupFIFO := func() {
fifoCleanupStarted = true // written from whichever goroutine calls it
...
}
go func() { // writer goroutine
for {
f, err := os.OpenFile(mountPath, ...)
if err != nil {
// race: cleanup has already begun; no need to error
if errors.Is(err, fs.ErrNotExist) && fifoCleanupStarted { // unsynchronized read
break
}
...
Problem
cleanupFIFO is returned to the caller and invoked on the caller's goroutine (e.g. when the child process exits or the CLI shuts down), while the writer goroutine reads the same variable in three places to detect that cleanup has begun. There is no mutex, atomic, or channel establishing a happens-before edge between the write in cleanupFIFO and the reads in the goroutine.
Consequences:
- It is a genuine data race (
go test -racewill report it whenever both paths overlap). - The guard is unreliable: even setting correctness-of-synchronization aside, the writer goroutine is typically blocked inside
os.OpenFileon the FIFO (it only unblocks when a reader opens the pipe). After cleanup removes the pipe, a blocked-then-released open may observe a stalefalseand proceed to callcleanupFIFO()again andutils.HandleError, depending on timing — the exact scenario the comment// race: cleanup has already begun; no need to errortries to handle is not actually handled deterministically.
Note that the surrounding code clearly intends these checks as race mitigation (the repeated comments), but implements them with an unsynchronized shared variable.
Trigger / Reproduction
Static analysis finding — not confirmed by execution. Any code path where the returned cleanup function executes while the writer goroutine is active: run doppler run --mount <path> ... with a consumer of the mounted file, terminate the wrapped process, and observe the concurrent access (readily reproducible under -race in a unit test that calls MountSecrets and its cleanup concurrently).
Expected Behavior
The flag should be an atomic.Bool (or protected by a mutex) so the writer goroutine reliably observes that cleanup has started and exits its loop instead of re-entering error handling.
Actual Behavior
Unsynchronized read/write of a shared bool; behavior depends on timing and CPU visibility, and race-detector builds flag it.
Impact
Non-deterministic shutdown behavior of the secrets mount: spurious "Unable to mount secrets file" errors during teardown, missed early-exit from the writer loop, and CI failures under -race. The fix is small and local.
Suggested Direction
Replace fifoCleanupStarted := false with var fifoCleanupStarted atomic.Bool, use Store(true) in cleanupFIFO and Load() at the three read sites. Optionally also close/signal the blocked OpenFile (e.g. by opening the read end locally before removing the pipe) so the goroutine cannot stay blocked past cleanup.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in pkg/controllers/secrets.go at MountSecrets, focusing on fifoCleanupStarted, cleanupFIFO, and the three writer-goroutine read sites. Run the relevant package tests with -race while exercising cleanup concurrently. Done means the shared flag is synchronized and teardown no longer reports a spurious mount error or triggers a race.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100