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.