fix: read the replaced listing's watch descriptor before detach can free it (#685)

install() replaces an existing cache entry by calling detach(old), which
destroys the listing when no reader holds it, and then reads old->wd to
decide whether to release the watch. When the old listing has no readers,
that read happens after the free.

Two threads that enumerate the same uncached directory at the same time
reach this path: both build a listing, the first installs an entry with
no readers, and the second replaces it.

Save the watch descriptor before detaching, as drop() already does.

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
William AllenandClaude Opus 5.5 authored and GitHub committed 2026-10-14 13:51:44 -04:00
1 parent 10cdf97480
commit c035351b4f
1 file changed
+2 -1
+2 -1
View File
@@ -256,9 +256,10 @@ static void install(struct listing *l) {
int slot = find(l->dev, l->ino);
if (slot >= 0) {
struct listing *old = table[slot];
int old_wd = old->wd;
table[slot] = l;
detach(old);
if (old->wd != l->wd) release(old->wd);
if (old_wd != l->wd) release(old_wd);
return;
}
int victim = -1;