BUG/MEDIUM: cache: retain the primary or secondary entry only when detaching its row
http_action_req_cache_use() retains the entry returned by the lookup, then
takes the shctx lock to detach its row. Until that detach the row is still
in the avail list, and shctx_row_reserve_hot() recycles a row under the
shctx lock alone, without ever taking the cache lock, so the cache read
lock does not protect a retained entry. In that window another thread can
recycle the row: the entry is queued on the cleanup list and its blocks are
refilled with a response body, after which our detach works from a
block_count and a last_reserved that no longer describe that row, splices
the wrong blocks out of the avail list and takes a reference on blocks
owned by another row. A block then sits both in the avail list and in a
live row, and the next thread reserving it reads a body as a cache_entry.
Retain the entry under the same shctx lock that detaches its row, and
only once it is known to be usable. A row recycled in the meantime is no
longer considered complete, which cache_free_blocks() clears under that
lock, so the existing test already rejects it. The entry is still
readable at that point because recycled blocks are only written once
shctx_row_reserve_hot() has returned, which cannot happen before
cache_reserve_finish() has taken the write lock on the tree our read
lock holds.
The other path then holds no reference, so it has nothing to release.