Subject: | Race condition in lib/krb5/ccache/cc_memory.c |
Using the MEMORY credentials cache from multiple threads is not
thread-safe and crashes.
The crash was found and can be illustrated on function
krb5_verify_init_creds() that creates and destroys a temporary
credentials cache:
...
krb5_cc_resolve(context, "MEMORY:rd_req", &ccache)
...
krb5_cc_destroy(context, ccache);
...
The krb5_ccache structure contains pointer to a krb5_mcc_data structure
that is shared among ccaches with the same residual ("rd_req"). The
active krb5_mcc_data pointers are stored in a global linked list
mcc_head. This list is protected by a mutex (improvement in version 1.4
- there used to be no mutex before), but this mutex is not enough to
prevent a race condition.
Here is how the program can crash:
1. There are two threads that both call krb5_verify_init_creds at the
same time.
2. Thread 1 creates the "MEMORY:rd_req" cache. It is not yet present in
the mcc_head list, so it allocates new krb5_mcc_data structure and
stores the pointer to mcc_head list.
3. Thread 2 creates the "MEMORY:rd_req" cache. Item with residual
"rd_req" is already present in the mcc_head list, so the pointer is
reused. Now both threads' krb5_ccache structures share pointer to one
krb5_mcc_data structure.
4. Thread 1 calls krb5_cc_destroy. The pointer to krb5_mcc_data is
removed from the mcc_head list and freed.
5. But Thread 2 still uses the pointer that was just freed! Our program
crashes inside krb5_mcc_free when trying to free the already freed memory.
To be really thread-safe, the krb5_mcc_data structure must be reference
counted and freed only when the refcount drops to zero.
thread-safe and crashes.
The crash was found and can be illustrated on function
krb5_verify_init_creds() that creates and destroys a temporary
credentials cache:
...
krb5_cc_resolve(context, "MEMORY:rd_req", &ccache)
...
krb5_cc_destroy(context, ccache);
...
The krb5_ccache structure contains pointer to a krb5_mcc_data structure
that is shared among ccaches with the same residual ("rd_req"). The
active krb5_mcc_data pointers are stored in a global linked list
mcc_head. This list is protected by a mutex (improvement in version 1.4
- there used to be no mutex before), but this mutex is not enough to
prevent a race condition.
Here is how the program can crash:
1. There are two threads that both call krb5_verify_init_creds at the
same time.
2. Thread 1 creates the "MEMORY:rd_req" cache. It is not yet present in
the mcc_head list, so it allocates new krb5_mcc_data structure and
stores the pointer to mcc_head list.
3. Thread 2 creates the "MEMORY:rd_req" cache. Item with residual
"rd_req" is already present in the mcc_head list, so the pointer is
reused. Now both threads' krb5_ccache structures share pointer to one
krb5_mcc_data structure.
4. Thread 1 calls krb5_cc_destroy. The pointer to krb5_mcc_data is
removed from the mcc_head list and freed.
5. But Thread 2 still uses the pointer that was just freed! Our program
crashes inside krb5_mcc_free when trying to free the already freed memory.
To be really thread-safe, the krb5_mcc_data structure must be reference
counted and freed only when the refcount drops to zero.