You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
406 lines
17 KiB
406 lines
17 KiB
commit 8f9450a0b7a9e78267e8ae1ab1000ebca08e473e |
|
Author: Torvald Riegel <triegel@redhat.com> |
|
Date: Sat Dec 24 00:40:46 2016 +0100 |
|
|
|
Add compiler barriers around modifications of the robust mutex list. |
|
|
|
Any changes to the per-thread list of robust mutexes currently acquired as |
|
well as the pending-operations entry are not simply sequential code but |
|
basically concurrent with any actions taken by the kernel when it tries |
|
to clean up after a crash. This is not quite like multi-thread concurrency |
|
but more like signal-handler concurrency. |
|
This patch fixes latent bugs by adding compiler barriers where necessary so |
|
that it is ensured that the kernel crash handling sees consistent data. |
|
|
|
This is meant to be easy to backport, so we do not use C11-style signal |
|
fences yet. |
|
|
|
* nptl/descr.h (ENQUEUE_MUTEX_BOTH, DEQUEUE_MUTEX): Add compiler |
|
barriers and comments. |
|
* nptl/pthread_mutex_lock.c (__pthread_mutex_lock_full): Likewise. |
|
* nptl/pthread_mutex_timedlock.c (pthread_mutex_timedlock): Likewise. |
|
* nptl/pthread_mutex_unlock.c (__pthread_mutex_unlock_full): Likewise. |
|
|
|
Index: glibc-2.17-c758a686/nptl/descr.h |
|
=================================================================== |
|
--- glibc-2.17-c758a686.orig/nptl/descr.h |
|
+++ glibc-2.17-c758a686/nptl/descr.h |
|
@@ -180,7 +180,16 @@ struct pthread |
|
but the pointer to the next/previous element of the list points |
|
in the middle of the object, the __next element. Whenever |
|
casting to __pthread_list_t we need to adjust the pointer |
|
- first. */ |
|
+ first. |
|
+ These operations are effectively concurrent code in that the thread |
|
+ can get killed at any point in time and the kernel takes over. Thus, |
|
+ the __next elements are a kind of concurrent list and we need to |
|
+ enforce using compiler barriers that the individual operations happen |
|
+ in such a way that the kernel always sees a consistent list. The |
|
+ backward links (ie, the __prev elements) are not used by the kernel. |
|
+ FIXME We should use relaxed MO atomic operations here and signal fences |
|
+ because this kind of concurrency is similar to synchronizing with a |
|
+ signal handler. */ |
|
# define QUEUE_PTR_ADJUST (offsetof (__pthread_list_t, __next)) |
|
|
|
# define ENQUEUE_MUTEX_BOTH(mutex, val) \ |
|
@@ -192,6 +201,8 @@ struct pthread |
|
mutex->__data.__list.__next = THREAD_GETMEM (THREAD_SELF, \ |
|
robust_head.list); \ |
|
mutex->__data.__list.__prev = (void *) &THREAD_SELF->robust_head; \ |
|
+ /* Ensure that the new list entry is ready before we insert it. */ \ |
|
+ __asm ("" ::: "memory"); \ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list, \ |
|
(void *) (((uintptr_t) &mutex->__data.__list.__next) \ |
|
| val)); \ |
|
@@ -206,6 +217,9 @@ struct pthread |
|
((char *) (((uintptr_t) mutex->__data.__list.__prev) & ~1ul) \ |
|
- QUEUE_PTR_ADJUST); \ |
|
prev->__next = mutex->__data.__list.__next; \ |
|
+ /* Ensure that we remove the entry from the list before we change the \ |
|
+ __next pointer of the entry, which is read by the kernel. */ \ |
|
+ __asm ("" ::: "memory"); \ |
|
mutex->__data.__list.__prev = NULL; \ |
|
mutex->__data.__list.__next = NULL; \ |
|
} while (0) |
|
@@ -220,6 +234,8 @@ struct pthread |
|
do { \ |
|
mutex->__data.__list.__next \ |
|
= THREAD_GETMEM (THREAD_SELF, robust_list.__next); \ |
|
+ /* Ensure that the new list entry is ready before we insert it. */ \ |
|
+ __asm ("" ::: "memory"); \ |
|
THREAD_SETMEM (THREAD_SELF, robust_list.__next, \ |
|
(void *) (((uintptr_t) &mutex->__data.__list) | val)); \ |
|
} while (0) |
|
@@ -240,6 +256,9 @@ struct pthread |
|
} \ |
|
\ |
|
runp->__next = next->__next; \ |
|
+ /* Ensure that we remove the entry from the list before we change the \ |
|
+ __next pointer of the entry, which is read by the kernel. */ \ |
|
+ __asm ("" ::: "memory"); \ |
|
mutex->__data.__list.__next = NULL; \ |
|
} \ |
|
} while (0) |
|
Index: glibc-2.17-c758a686/nptl/pthread_mutex_lock.c |
|
=================================================================== |
|
--- glibc-2.17-c758a686.orig/nptl/pthread_mutex_lock.c |
|
+++ glibc-2.17-c758a686/nptl/pthread_mutex_lock.c |
|
@@ -181,6 +181,9 @@ __pthread_mutex_lock_full (pthread_mutex |
|
case PTHREAD_MUTEX_ROBUST_ADAPTIVE_NP: |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
&mutex->__data.__list.__next); |
|
+ /* We need to set op_pending before starting the operation. Also |
|
+ see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
|
|
oldval = mutex->__data.__lock; |
|
/* This is set to FUTEX_WAITERS iff we might have shared the |
|
@@ -228,7 +231,12 @@ __pthread_mutex_lock_full (pthread_mutex |
|
/* But it is inconsistent unless marked otherwise. */ |
|
mutex->__data.__owner = PTHREAD_MUTEX_INCONSISTENT; |
|
|
|
+ /* We must not enqueue the mutex before we have acquired it. |
|
+ Also see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
ENQUEUE_MUTEX (mutex); |
|
+ /* We need to clear op_pending after we enqueue the mutex. */ |
|
+ __asm ("" ::: "memory"); |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
|
|
/* Note that we deliberately exit here. If we fall |
|
@@ -250,6 +258,8 @@ __pthread_mutex_lock_full (pthread_mutex |
|
int kind = PTHREAD_MUTEX_TYPE (mutex); |
|
if (kind == PTHREAD_MUTEX_ROBUST_ERRORCHECK_NP) |
|
{ |
|
+ /* We do not need to ensure ordering wrt another memory |
|
+ access. Also see comments at ENQUEUE_MUTEX. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
NULL); |
|
return EDEADLK; |
|
@@ -257,6 +267,8 @@ __pthread_mutex_lock_full (pthread_mutex |
|
|
|
if (kind == PTHREAD_MUTEX_ROBUST_RECURSIVE_NP) |
|
{ |
|
+ /* We do not need to ensure ordering wrt another memory |
|
+ access. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
NULL); |
|
|
|
@@ -309,12 +321,19 @@ __pthread_mutex_lock_full (pthread_mutex |
|
mutex->__data.__count = 0; |
|
int private = PTHREAD_ROBUST_MUTEX_PSHARED (mutex); |
|
lll_unlock (mutex->__data.__lock, private); |
|
+ /* FIXME This violates the mutex destruction requirements. See |
|
+ __pthread_mutex_unlock_full. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
return ENOTRECOVERABLE; |
|
} |
|
|
|
mutex->__data.__count = 1; |
|
+ /* We must not enqueue the mutex before we have acquired it. |
|
+ Also see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
ENQUEUE_MUTEX (mutex); |
|
+ /* We need to clear op_pending after we enqueue the mutex. */ |
|
+ __asm ("" ::: "memory"); |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
break; |
|
|
|
@@ -331,10 +350,15 @@ __pthread_mutex_lock_full (pthread_mutex |
|
int robust = mutex->__data.__kind & PTHREAD_MUTEX_ROBUST_NORMAL_NP; |
|
|
|
if (robust) |
|
- /* Note: robust PI futexes are signaled by setting bit 0. */ |
|
- THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
- (void *) (((uintptr_t) &mutex->__data.__list.__next) |
|
- | 1)); |
|
+ { |
|
+ /* Note: robust PI futexes are signaled by setting bit 0. */ |
|
+ THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
+ (void *) (((uintptr_t) &mutex->__data.__list.__next) |
|
+ | 1)); |
|
+ /* We need to set op_pending before starting the operation. Also |
|
+ see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
+ } |
|
|
|
oldval = mutex->__data.__lock; |
|
|
|
@@ -343,12 +367,16 @@ __pthread_mutex_lock_full (pthread_mutex |
|
{ |
|
if (kind == PTHREAD_MUTEX_ERRORCHECK_NP) |
|
{ |
|
+ /* We do not need to ensure ordering wrt another memory |
|
+ access. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
return EDEADLK; |
|
} |
|
|
|
if (kind == PTHREAD_MUTEX_RECURSIVE_NP) |
|
{ |
|
+ /* We do not need to ensure ordering wrt another memory |
|
+ access. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
|
|
/* Just bump the counter. */ |
|
@@ -411,7 +439,12 @@ __pthread_mutex_lock_full (pthread_mutex |
|
/* But it is inconsistent unless marked otherwise. */ |
|
mutex->__data.__owner = PTHREAD_MUTEX_INCONSISTENT; |
|
|
|
+ /* We must not enqueue the mutex before we have acquired it. |
|
+ Also see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
ENQUEUE_MUTEX_PI (mutex); |
|
+ /* We need to clear op_pending after we enqueue the mutex. */ |
|
+ __asm ("" ::: "memory"); |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
|
|
/* Note that we deliberately exit here. If we fall |
|
@@ -439,6 +472,8 @@ __pthread_mutex_lock_full (pthread_mutex |
|
PTHREAD_ROBUST_MUTEX_PSHARED (mutex)), |
|
0, 0); |
|
|
|
+ /* To the kernel, this will be visible after the kernel has |
|
+ acquired the mutex in the syscall. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
return ENOTRECOVERABLE; |
|
} |
|
@@ -446,7 +481,12 @@ __pthread_mutex_lock_full (pthread_mutex |
|
mutex->__data.__count = 1; |
|
if (robust) |
|
{ |
|
+ /* We must not enqueue the mutex before we have acquired it. |
|
+ Also see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
ENQUEUE_MUTEX_PI (mutex); |
|
+ /* We need to clear op_pending after we enqueue the mutex. */ |
|
+ __asm ("" ::: "memory"); |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
} |
|
} |
|
Index: glibc-2.17-c758a686/nptl/pthread_mutex_timedlock.c |
|
=================================================================== |
|
--- glibc-2.17-c758a686.orig/nptl/pthread_mutex_timedlock.c |
|
+++ glibc-2.17-c758a686/nptl/pthread_mutex_timedlock.c |
|
@@ -140,6 +140,9 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
case PTHREAD_MUTEX_ROBUST_ADAPTIVE_NP: |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
&mutex->__data.__list.__next); |
|
+ /* We need to set op_pending before starting the operation. Also |
|
+ see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
|
|
oldval = mutex->__data.__lock; |
|
/* This is set to FUTEX_WAITERS iff we might have shared the |
|
@@ -177,7 +180,12 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
/* But it is inconsistent unless marked otherwise. */ |
|
mutex->__data.__owner = PTHREAD_MUTEX_INCONSISTENT; |
|
|
|
+ /* We must not enqueue the mutex before we have acquired it. |
|
+ Also see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
ENQUEUE_MUTEX (mutex); |
|
+ /* We need to clear op_pending after we enqueue the mutex. */ |
|
+ __asm ("" ::: "memory"); |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
|
|
/* Note that we deliberately exit here. If we fall |
|
@@ -193,6 +201,8 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
int kind = PTHREAD_MUTEX_TYPE (mutex); |
|
if (kind == PTHREAD_MUTEX_ROBUST_ERRORCHECK_NP) |
|
{ |
|
+ /* We do not need to ensure ordering wrt another memory |
|
+ access. Also see comments at ENQUEUE_MUTEX. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
NULL); |
|
return EDEADLK; |
|
@@ -200,6 +210,8 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
|
|
if (kind == PTHREAD_MUTEX_ROBUST_RECURSIVE_NP) |
|
{ |
|
+ /* We do not need to ensure ordering wrt another memory |
|
+ access. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
NULL); |
|
|
|
@@ -294,12 +306,19 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
mutex->__data.__count = 0; |
|
int private = PTHREAD_ROBUST_MUTEX_PSHARED (mutex); |
|
lll_unlock (mutex->__data.__lock, private); |
|
+ /* FIXME This violates the mutex destruction requirements. See |
|
+ __pthread_mutex_unlock_full. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
return ENOTRECOVERABLE; |
|
} |
|
|
|
mutex->__data.__count = 1; |
|
+ /* We must not enqueue the mutex before we have acquired it. |
|
+ Also see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
ENQUEUE_MUTEX (mutex); |
|
+ /* We need to clear op_pending after we enqueue the mutex. */ |
|
+ __asm ("" ::: "memory"); |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
break; |
|
|
|
@@ -316,10 +335,15 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
int robust = mutex->__data.__kind & PTHREAD_MUTEX_ROBUST_NORMAL_NP; |
|
|
|
if (robust) |
|
- /* Note: robust PI futexes are signaled by setting bit 0. */ |
|
- THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
- (void *) (((uintptr_t) &mutex->__data.__list.__next) |
|
- | 1)); |
|
+ { |
|
+ /* Note: robust PI futexes are signaled by setting bit 0. */ |
|
+ THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
+ (void *) (((uintptr_t) &mutex->__data.__list.__next) |
|
+ | 1)); |
|
+ /* We need to set op_pending before starting the operation. Also |
|
+ see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
+ } |
|
|
|
oldval = mutex->__data.__lock; |
|
|
|
@@ -328,12 +352,16 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
{ |
|
if (kind == PTHREAD_MUTEX_ERRORCHECK_NP) |
|
{ |
|
+ /* We do not need to ensure ordering wrt another memory |
|
+ access. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
return EDEADLK; |
|
} |
|
|
|
if (kind == PTHREAD_MUTEX_RECURSIVE_NP) |
|
{ |
|
+ /* We do not need to ensure ordering wrt another memory |
|
+ access. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
|
|
/* Just bump the counter. */ |
|
@@ -420,7 +448,12 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
/* But it is inconsistent unless marked otherwise. */ |
|
mutex->__data.__owner = PTHREAD_MUTEX_INCONSISTENT; |
|
|
|
+ /* We must not enqueue the mutex before we have acquired it. |
|
+ Also see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
ENQUEUE_MUTEX_PI (mutex); |
|
+ /* We need to clear op_pending after we enqueue the mutex. */ |
|
+ __asm ("" ::: "memory"); |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
|
|
/* Note that we deliberately exit here. If we fall |
|
@@ -443,6 +476,8 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
PTHREAD_ROBUST_MUTEX_PSHARED (mutex)), |
|
0, 0); |
|
|
|
+ /* To the kernel, this will be visible after the kernel has |
|
+ acquired the mutex in the syscall. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
return ENOTRECOVERABLE; |
|
} |
|
@@ -450,7 +485,12 @@ pthread_mutex_timedlock (pthread_mutex_t |
|
mutex->__data.__count = 1; |
|
if (robust) |
|
{ |
|
+ /* We must not enqueue the mutex before we have acquired it. |
|
+ Also see comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
ENQUEUE_MUTEX_PI (mutex); |
|
+ /* We need to clear op_pending after we enqueue the mutex. */ |
|
+ __asm ("" ::: "memory"); |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
} |
|
} |
|
Index: glibc-2.17-c758a686/nptl/pthread_mutex_unlock.c |
|
=================================================================== |
|
--- glibc-2.17-c758a686.orig/nptl/pthread_mutex_unlock.c |
|
+++ glibc-2.17-c758a686/nptl/pthread_mutex_unlock.c |
|
@@ -143,6 +143,9 @@ __pthread_mutex_unlock_full (pthread_mut |
|
/* Remove mutex from the list. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
&mutex->__data.__list.__next); |
|
+ /* We must set op_pending before we dequeue the mutex. Also see |
|
+ comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
DEQUEUE_MUTEX (mutex); |
|
|
|
mutex->__data.__owner = newowner; |
|
@@ -159,6 +162,14 @@ __pthread_mutex_unlock_full (pthread_mut |
|
& FUTEX_WAITERS) != 0)) |
|
lll_futex_wake (&mutex->__data.__lock, 1, private); |
|
|
|
+ /* We must clear op_pending after we release the mutex. |
|
+ FIXME However, this violates the mutex destruction requirements |
|
+ because another thread could acquire the mutex, destroy it, and |
|
+ reuse the memory for something else; then, if this thread crashes, |
|
+ and the memory happens to have a value equal to the TID, the kernel |
|
+ will believe it is still related to the mutex (which has been |
|
+ destroyed already) and will modify some other random object. */ |
|
+ __asm ("" ::: "memory"); |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
break; |
|
|
|
@@ -223,6 +234,9 @@ __pthread_mutex_unlock_full (pthread_mut |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, |
|
(void *) (((uintptr_t) &mutex->__data.__list.__next) |
|
| 1)); |
|
+ /* We must set op_pending before we dequeue the mutex. Also see |
|
+ comments at ENQUEUE_MUTEX. */ |
|
+ __asm ("" ::: "memory"); |
|
DEQUEUE_MUTEX (mutex); |
|
} |
|
|
|
@@ -247,6 +261,9 @@ __pthread_mutex_unlock_full (pthread_mut |
|
__lll_private_flag (FUTEX_UNLOCK_PI, private)); |
|
} |
|
|
|
+ /* This happens after the kernel releases the mutex but violates the |
|
+ mutex destruction requirements; see comments in the code handling |
|
+ PTHREAD_MUTEX_ROBUST_NORMAL_NP. */ |
|
THREAD_SETMEM (THREAD_SELF, robust_head.list_op_pending, NULL); |
|
break; |
|
|
|
|