CVE-2026-72372
This vulnerability exposes a design-level confusion about RCU semantics that goes beyond a simple forgotten lock. The bug was in `afs_cell_destroy()` removing entries from `net->cells_dyn_ino` — a dynamic inode tracking structure — in RCU cleanup context where sleeping locks are prohibited. The fix required moving that cleanup to `afs_destroy_cell_work()`, a workqueue context, because you cannot acquire `cells_lock` from an RCU callback. The critical lesson here isn't the missing lock itself — it's that the remediation couldn't just be 'add locking.' The original developer likely understood RCU well enough to use it for the hot read path but either didn't recognize or deliberately ignored that `afs_cell_destroy()` runs in RCU cleanup context. RCU's read-side API is so elegant and low-friction that it creates a cognitive trap: developers carry the 'RCU = fast reads, no locking overhead' mental model into write-side and cleanup code where it becomes dangerous. The RCU callback path doesn't signal 'you cannot take sleeping locks here' until runtime — that's an ergonomic failure in the abstraction. The failure mode compounds because `cells_dyn_ino` is accounting state. When inode tracking corruption occurs, you don't get an obvious crash — you get silent inode lifecycle corruption: inodes that never get reclaimed, or worse, inodes reclaimed while references still exist. This is a use-after-free or leak that manifests far from the trigger point. Since `net->cells_dyn_ino` lives in network namespace context, tracking corruption in one cell's destruction poisons the namespace-level inode accounting that all cells in that namespace depend on. The blast radius extends beyond AFS itself. This pattern — restructuring cleanup to move it out of RCU context into a workqueue — isn't novel; it's the standard remediation when RCU's read-side elegance gets conflated with write-side cleanup obligations. The question is whether other AFS structures suffer from similar RCU-context cleanup that ignores write-side modifications, and whether `cells_lock` has correct acquisition ordering with respect to other locks in the cell lifecycle. The bug likely rotted wrong rather than was born wrong: as the subsystem evolved, the cleanup path wasn't updated to match new accounting state or refactored synchronization contracts. Temporal drift between write-side invariants and read-side guarantees is how these vulnerabilities persist — they're not visibly broken, just orphaned from the assumptions that once made them safe.
Reviewed through automated stages and approved by a human before publication.