(I’m trying to recall the Lightspeed bug from memory so I may have some of it wrong.)
It all boils down to poor state management in a single algorithm.
The algorithm allocates a kernel object, then sends off a subroutine to do some work.
(The subroutine happens to run in another thread but that’s not really relevant to the bug.) As part of its job duty, the subroutine is supposed to free the object after its work is done, but only if condition A is true.
If A is false, the subroutine won’t free the memory, and it’s implied that the main routine is supposed to free the memory instead.
Now the issue is that there’s no common code that checks condition A. Instead, the main routine and the subroutine have slightly different ideas about whether condition A is true or not. The condition’s logic is pretty simple so it’s understandable that the kernel developer decided to write the same condition in two different places and two different forms (instead of e. g. factoring it out into a macro).
Still, they managed to get it wrong.
The result is that in one particular case, the subroutine thinks A is true. So it frees the object. When the main routine gets back control, it thinks A is false (due to the duplicated, slightly wrong logic), and frees the object, too.
There’s only a small time window between those two frees. But the window is large enough that a userspace thread, if it tries often enough, can force its own object into the place where the kernel object used to be,
just in time before the double free happens.