[patch v1] nptl: namespace-safe pthread keys implementation
DJ Delorie
dj@redhat.com
Wed May 13 01:00:42 GMT 2026
Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> writes:
> The old design stored a generation sequence number in both the global slot and
> the per-thread slot (KEY_UNUSED and KEY_USABLE). When a key was deleted and its
> slot reused, per-thread values from the old key failed the sequence-number check
> in pthread_getspecific and deallocate_tsd, and were silently ignored.
Were the ramifications of this memory leak fully understood?
>> + for (i = 0; i <slots; i ++)
>> + {
>> + void (*d)(void *) = atomic_load_relaxed (&destr_buckets[b]->keys[i]);
>> + if (d != PTHREAD_KEY_SLOT_FREE && d != NULL
>> + && bucket->keys[i] != NULL)
>
> Wouldn't this skip keys created with dest == NULL? I think
> get_cached_stack might reuses it for a new thread without calling
> _pthread_key_init.
We can't call a NULL destructor. The only side effect would be that a
key with a NULL destructor doesn't get cleared, but that also doesn't
set specific_used, so the loop would ignore it a fixed number of times
before the non-NULL-destructor keys are all destructed. Then we unmap
the memory, so all trace of the non-cleared keys vanishes.
Unless the destructor for one key calls setspecific for a different
key... but the destructor is only called on thread exit, so why would
it?
>> int
>> ___pthread_key_create (pthread_key_t *key, void (*destr) (void *))
>> {
>> - /* Find a slot in __pthread_keys which is unused. */
>> - for (size_t cnt = 0; cnt < PTHREAD_KEYS_MAX; ++cnt)
>> + int b, i;
>> + list_t *runp;
>> +
>> + PTHREAD_KEY_LOCK;
>> + struct pthread_key_bucket **buckets = GL(dl_pthread_keys_data);
>> + if (buckets == NULL)
>
> I think this does not address the namespace issue you are trying to
> fix. The __pthread_key_lock is defined as attribute_hidden in libc.so
> and when multiple libc instances are loaded with dlmopen, each
> namespace has its own copy of the lock. However, they all share the
> same key table in ldso via GL(dl_pthread_keys_data).
Right, move lock to ld.so too...
>> - if (__glibc_likely (key < PTHREAD_KEYS_MAX))
>> + if (buckets[b] == NULL)
>> {
>> - unsigned int seq = __pthread_keys[key].seq;
>> + PTHREAD_KEY_UNLOCK;
>> + return EAGAIN;
>> + }
>
> Not sure if POSIX allows return EGAIN here, I think it should be EINVAL.
1003.1-2024 allows but doesn't list explicit errors, other than to
forbid EINTR. I could switch them all to EINVAL though, but do we want
some way to tell the difference between a key which *could* be valid (in
bounds but not created), vs a key which *couldn't* be valid (out of
bounds) ?
https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_key_delete.html
More information about the Libc-alpha
mailing list