aboutsummaryrefslogtreecommitdiff
path: root/sysdeps/unix/sysv/linux
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2025-01-16 18:45:25 +0100
committerFlorian Weimer <fweimer@redhat.com>2025-01-16 19:58:09 +0100
commitabeae3c0061c0599ac2f012b270d6b4c8f59c82f (patch)
tree6c60a0211917dd4a4a010ebf6668c682abaf6b70 /sysdeps/unix/sysv/linux
parent252fc3628bc2dd66b38dff7b5c22432bb34a8829 (diff)
downloadglibc-abeae3c0061c0599ac2f012b270d6b4c8f59c82f.zip
glibc-abeae3c0061c0599ac2f012b270d6b4c8f59c82f.tar.gz
glibc-abeae3c0061c0599ac2f012b270d6b4c8f59c82f.tar.bz2
Linux: Fixes for getrandom fork handling
Careful updates of grnd_alloc.len are required to ensure that after fork, grnd_alloc.states does not contain entries that are also encountered by __getrandom_reset_state in TCBs. For the same reason, it is necessary to overwrite the TCB state pointer with NULL before updating grnd_alloc.states in __getrandom_vdso_release. Before this change, different TCBs could share the same getrandom state after multi-threaded fork. This would be a critical security bug (predictable randomness) if not caught during development. The additional check in stdlib/tst-arc4random-thread makes it more likely that the test fails due to the bugs mentioned above. Both __getrandom_reset_state and __getrandom_vdso_release could put reserved NULL pointers into the states array. This is also fixed with this commit. After these changes, no null pointers were observed in the states array during testing. Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Diffstat (limited to 'sysdeps/unix/sysv/linux')
-rw-r--r--sysdeps/unix/sysv/linux/getrandom.c26
1 files changed, 23 insertions, 3 deletions
diff --git a/sysdeps/unix/sysv/linux/getrandom.c b/sysdeps/unix/sysv/linux/getrandom.c
index eab2b68..6b7be5e 100644
--- a/sysdeps/unix/sysv/linux/getrandom.c
+++ b/sysdeps/unix/sysv/linux/getrandom.c
@@ -168,6 +168,11 @@ vgetrandom_get_state (void)
if (grnd_alloc.len > 0 || vgetrandom_get_state_alloc ())
state = grnd_alloc.states[--grnd_alloc.len];
+ /* Barrier needed by fork: The state must be gone from the array
+ through len update before it becomes visible in the TCB. (There
+ is also a release barrier implied by the unlock, but issue a
+ stronger barrier to help fork.) */
+ atomic_thread_fence_seq_cst ();
__libc_lock_unlock (grnd_alloc.lock);
internal_signal_restore_set (&set);
@@ -278,7 +283,10 @@ void
__getrandom_reset_state (struct pthread *curp)
{
#ifdef HAVE_GETRANDOM_VSYSCALL
- if (grnd_alloc.states == NULL || curp->getrandom_buf == NULL)
+ /* The pointer can be reserved if the fork happened during a
+ getrandom call. */
+ void *buf = release_ptr (curp->getrandom_buf);
+ if (grnd_alloc.states == NULL || buf == NULL)
return;
assert (grnd_alloc.len < grnd_alloc.cap);
grnd_alloc.states[grnd_alloc.len++] = release_ptr (curp->getrandom_buf);
@@ -294,11 +302,23 @@ void
__getrandom_vdso_release (struct pthread *curp)
{
#ifdef HAVE_GETRANDOM_VSYSCALL
- if (curp->getrandom_buf == NULL)
+ /* The pointer can be reserved if the thread was canceled in a
+ signal handler. */
+ void *buf = release_ptr (curp->getrandom_buf);
+ if (buf == NULL)
return;
__libc_lock_lock (grnd_alloc.lock);
- grnd_alloc.states[grnd_alloc.len++] = curp->getrandom_buf;
+
+ size_t len = grnd_alloc.len;
+ grnd_alloc.states[len] = curp->getrandom_buf;
+ curp->getrandom_buf = NULL;
+ /* Barrier needed by fork: The state must vanish from the TCB before
+ it becomes visible in the states array. Also avoid exposing the
+ previous entry value at the same index in the states array (which
+ may be in use by another thread). */
+ atomic_thread_fence_seq_cst ();
+ grnd_alloc.len = len + 1;
__libc_lock_unlock (grnd_alloc.lock);
#endif
}