aboutsummaryrefslogtreecommitdiff
path: root/sysdeps/unix/sysv/linux/getrandom.c
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps/unix/sysv/linux/getrandom.c')
-rw-r--r--sysdeps/unix/sysv/linux/getrandom.c45
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, &params, ~0UL) == 0)
+ long int ret = INTERNAL_VSYSCALL_CALL (GLRO(dl_vdso_getrandom),
+ 5, NULL, 0, 0, &params, ~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
}