There is a good deal of complexity in the object structure that is detailed in the post that I’m going to gloss over. Effectively you have an array of entry objects, and each entry has a pointer to a
user_data_value_element. Since there is generally a one-to-one mapping of entries to these user data values only the entry is reference counted.
There is a race condition in the allocation process for a new entry where a pointer to the
user_data_value_element has been claimed but not yet accounted for by the entry. During this window, another thread can drop the last reference to the entry resulting in the
user_data_value_element getting unexpectedly freed.
To prevent this the value element tracks the number of pointers handed out in an
e_made field, and the entry tracks the count. Then when the last reference is dropped before it free’s the
user_data_value_element it checks these two counts, and if there is a desync it knows that it is not safe to free the
And so we have the core bug in that the
e_made field of
user_data_value_element is incremented without holding the appropriate lock. So you can have threads that should increment
e_made both read the same value and add one to it, resulting in an
e_made that is too low. Creating a situation where the value element can be inappropriately freed.