diff options
Diffstat (limited to 'sysdeps/unix/sysv/linux/getrandom.c')
-rw-r--r-- | sysdeps/unix/sysv/linux/getrandom.c | 45 |
1 files changed, 35 insertions, 10 deletions
diff --git a/sysdeps/unix/sysv/linux/getrandom.c b/sysdeps/unix/sysv/linux/getrandom.c index c8c5782..6b7be5e 100644 --- a/sysdeps/unix/sysv/linux/getrandom.c +++ b/sysdeps/unix/sysv/linux/getrandom.c @@ -1,5 +1,5 @@ /* Implementation of the getrandom system call. - Copyright (C) 2016-2024 Free Software Foundation, Inc. + Copyright (C) 2016-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -20,6 +20,8 @@ #include <errno.h> #include <unistd.h> #include <sysdep-cancel.h> +#include <sysdep.h> +#include <sysdep-vdso.h> static inline ssize_t getrandom_syscall (void *buffer, size_t length, unsigned int flags, @@ -166,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); @@ -201,11 +208,12 @@ getrandom_vdso (void *buffer, size_t length, unsigned int flags, bool cancel) cancellation bridge (__syscall_cancel_arch), use GRND_NONBLOCK so there is no potential unbounded blocking in the kernel. It should be a rare situation, only at system startup when RNG is not initialized. */ - ssize_t ret = GLRO (dl_vdso_getrandom) (buffer, - length, - flags | GRND_NONBLOCK, - state, - state_size); + long int ret = INTERNAL_VSYSCALL_CALL (GLRO (dl_vdso_getrandom), 5, + buffer, + length, + flags | GRND_NONBLOCK, + state, + state_size); if (INTERNAL_SYSCALL_ERROR_P (ret)) { /* Fallback to the syscall if the kernel would block. */ @@ -241,7 +249,9 @@ __getrandom_early_init (_Bool initial) uint32_t mmap_flags; uint32_t reserved[13]; } params; - if (GLRO(dl_vdso_getrandom) (NULL, 0, 0, ¶ms, ~0UL) == 0) + long int ret = INTERNAL_VSYSCALL_CALL (GLRO(dl_vdso_getrandom), + 5, NULL, 0, 0, ¶ms, ~0UL); + if (ret == 0) { /* Align each opaque state to L1 data cache size to avoid false sharing. If the size can not be obtained, use the kernel @@ -273,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); @@ -289,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 } |