]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
linux: Add support for getrandom vDSO
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Wed, 18 Sep 2024 14:01:22 +0000 (16:01 +0200)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Tue, 12 Nov 2024 17:42:12 +0000 (14:42 -0300)
Linux 6.11 has getrandom() in vDSO. It operates on a thread-local opaque
state allocated with mmap using flags specified by the vDSO.

Multiple states are allocated at once, as many as fit into a page, and
these are held in an array of available states to be doled out to each
thread upon first use, and recycled when a thread terminates. As these
states run low, more are allocated.

To make this procedure async-signal-safe, a simple guard is used in the
LSB of the opaque state address, falling back to the syscall if there's
reentrancy contention.

Also, _Fork() is handled by blocking signals on opaque state allocation
(so _Fork() always sees a consistent state even if it interrupts a
getrandom() call) and by iterating over the thread stack cache on
reclaim_stack. Each opaque state will be in the free states list
(grnd_alloc.states) or allocated to a running thread.

The cancellation is handled by always using GRND_NONBLOCK flags while
calling the vDSO, and falling back to the cancellable syscall if the
kernel returns EAGAIN (would block). Since getrandom is not defined by
POSIX and cancellation is supported as an extension, the cancellation is
handled as 'may occur' instead of 'shall occur' [1], meaning that if
vDSO does not block (the expected behavior) getrandom will not act as a
cancellation entrypoint. It avoids a pthread_testcancel call on the fast
path (different than 'shall occur' functions, like sem_wait()).

It is currently enabled for x86_64, which is available in Linux 6.11,
and aarch64, powerpc32, powerpc64, loongarch64, and s390x, which are
available in Linux 6.12.

Link: https://pubs.opengroup.org/onlinepubs/9799919799/nframe.html
Co-developed-by: Jason A. Donenfeld <Jason@zx2c4.com>
Tested-by: Jason A. Donenfeld <Jason@zx2c4.com> # x86_64
Tested-by: Adhemerval Zanella <adhemerval.zanella@linaro.org> # x86_64, aarch64
Tested-by: Xi Ruoyao <xry111@xry111.site> # x86_64, aarch64, loongarch64
Tested-by: Stefan Liebler <stli@linux.ibm.com> # s390x
23 files changed:
NEWS
elf/libc_early_init.c
malloc/malloc.c
nptl/allocatestack.c
nptl/descr.h
nptl/pthread_create.c
stdlib/Makefile
stdlib/tst-getrandom2.c [new file with mode: 0644]
sysdeps/generic/getrandom-internal.h [new file with mode: 0644]
sysdeps/generic/not-cancel.h
sysdeps/mach/hurd/not-cancel.h
sysdeps/nptl/_Fork.c
sysdeps/nptl/fork.h
sysdeps/unix/sysv/linux/aarch64/sysdep.h
sysdeps/unix/sysv/linux/dl-vdso-setup.c
sysdeps/unix/sysv/linux/dl-vdso-setup.h
sysdeps/unix/sysv/linux/getrandom-internal.h [new file with mode: 0644]
sysdeps/unix/sysv/linux/getrandom.c
sysdeps/unix/sysv/linux/loongarch/sysdep.h
sysdeps/unix/sysv/linux/not-cancel.h
sysdeps/unix/sysv/linux/powerpc/sysdep.h
sysdeps/unix/sysv/linux/s390/sysdep.h
sysdeps/unix/sysv/linux/x86_64/sysdep.h

diff --git a/NEWS b/NEWS
index 1d048de0e69a97abd10b0bf143297f996ff5df85..dfc87079d82208c9a7d94cd05da57313fe33a466 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -49,7 +49,9 @@ Deprecated and removed features, and other changes affecting compatibility:
 
 Changes to build and runtime requirements:
 
-  [Add changes to build and runtime requirements here]
+* On recent Linux kernels with vDSO getrandom support, getrandom does not
+  act as a "shall occur" cancellation point, in which case it might
+  not issue a syscall or trigger a deferred cancellation event.
 
 Security related changes:
 
index 575b837f8f43cb758ef811b00a3b2fce2f7aaa9e..20c71fd48b5bd6043b0314ffd198838e652a195c 100644 (file)
@@ -23,6 +23,7 @@
 #include <lowlevellock.h>
 #include <pthread_early_init.h>
 #include <sys/single_threaded.h>
+#include <getrandom-internal.h>
 
 #ifdef SHARED
 _Bool __libc_initial;
@@ -43,6 +44,8 @@ __libc_early_init (_Bool initial)
 
   __pthread_early_init ();
 
+  __getrandom_early_init (initial);
+
 #if ENABLE_ELISION_SUPPORT
   __lll_elision_init ();
 #endif
index bcb6e5b83ca9777df3c1ade273d821d5b742b2fb..9e577ab90010a0f110143d09f533f29ae9604a60 100644 (file)
@@ -3140,8 +3140,8 @@ static void
 tcache_key_initialize (void)
 {
   /* We need to use the _nostatus version here, see BZ 29624.  */
-  if (__getrandom_nocancel_nostatus (&tcache_key, sizeof(tcache_key),
-                                    GRND_NONBLOCK)
+  if (__getrandom_nocancel_nostatus_direct (&tcache_key, sizeof(tcache_key),
+                                           GRND_NONBLOCK)
       != sizeof (tcache_key))
     {
       tcache_key = random_bits ();
index 2cb562f8eac8af11393ebd9ae36b111c7bca4191..d9adb5856cefa53376261ba519e9fcae8d9c8785 100644 (file)
@@ -132,6 +132,8 @@ get_cached_stack (size_t *sizep, void **memp)
   __libc_lock_init (result->exit_lock);
   memset (&result->tls_state, 0, sizeof result->tls_state);
 
+  result->getrandom_buf = NULL;
+
   /* Clear the DTV.  */
   dtv_t *dtv = GET_DTV (TLS_TPADJ (result));
   for (size_t cnt = 0; cnt < dtv[-1].counter; ++cnt)
index 66e4be1fa7c7eab839401941878b00dbfaef0865..a69f5c9697fce4c97394f9a36d6f05ff4c1f1437 100644 (file)
@@ -404,6 +404,9 @@ struct pthread
   /* Used on strsignal.  */
   struct tls_internal_t tls_state;
 
+  /* getrandom vDSO per-thread opaque state.  */
+  void *getrandom_buf;
+
   /* rseq area registered with the kernel.  Use a custom definition
      here to isolate from kernel struct rseq changes.  The
      implementation of sched_getcpu needs acccess to the cpu_id field;
index 1d3665d5edb684e3de0e070763914a90b47545c7..ef3ec3329027ac9f595c029c071a354e5b49410f 100644 (file)
@@ -38,6 +38,7 @@
 #include <version.h>
 #include <clone_internal.h>
 #include <futex-internal.h>
+#include <getrandom-internal.h>
 
 #include <shlib-compat.h>
 
@@ -549,6 +550,10 @@ start_thread (void *arg)
     }
 #endif
 
+  /* Release the vDSO getrandom per-thread buffer with all signal blocked,
+     to avoid creating a new free-state block during thread release.  */
+  __getrandom_vdso_release (pd);
+
   if (!pd->user_stack)
     advise_stack_range (pd->stackblock, pd->stackblock_size, (uintptr_t) pd,
                        pd->guardsize);
index 347491de53b8eef0deb7e242763bd8af6b7abdac..9c492051bfa7d7706a92b1a04aa772ae3c8023c8 100644 (file)
@@ -278,6 +278,7 @@ tests := \
   tst-cxa_atexit \
   tst-environ \
   tst-getrandom \
+  tst-getrandom2 \
   tst-labs \
   tst-limits \
   tst-llabs \
@@ -627,3 +628,4 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
 $(objpfx)tst-qsort5: $(libm)
 $(objpfx)tst-concurrent-exit: $(shared-thread-library)
 $(objpfx)tst-concurrent-quick_exit: $(shared-thread-library)
+$(objpfx)tst-getrandom2: $(shared-thread-library)
diff --git a/stdlib/tst-getrandom2.c b/stdlib/tst-getrandom2.c
new file mode 100644 (file)
index 0000000..f085b4b
--- /dev/null
@@ -0,0 +1,47 @@
+/* Tests for the getrandom functions.
+   Copyright (C) 2024 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
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <gnu/lib-names.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <support/xthread.h>
+#include <sys/random.h>
+
+static __typeof (getrandom) *getrandom_ptr;
+
+static void *
+threadfunc (void *ignored)
+{
+  char buffer;
+  TEST_COMPARE (getrandom_ptr (&buffer, 1, 0), 1);
+  return NULL;
+}
+
+static int
+do_test (void)
+{
+  /* Check if issuing getrandom in the secondary libc.so works when
+     the vDSO might be potentially used.  */
+  void *handle = xdlmopen (LM_ID_NEWLM, LIBC_SO, RTLD_NOW);
+  getrandom_ptr = xdlsym (handle, "getrandom");
+  for (int i = 0; i < 1000; ++i)
+    xpthread_join (xpthread_create (NULL, threadfunc, NULL));
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/generic/getrandom-internal.h b/sysdeps/generic/getrandom-internal.h
new file mode 100644 (file)
index 0000000..3fe4653
--- /dev/null
@@ -0,0 +1,26 @@
+/* Internal definitions for getrandom implementation.
+   Copyright (C) 2024 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
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _GETRANDOM_INTERNAL_H
+#define _GETRANDOM_INTERNAL_H
+
+static inline void __getrandom_early_init (_Bool)
+{
+}
+
+#endif
index 2dd10646004611cfc511acb8a88a00a3183d5796..8e3f49cc07c85d76902680ea1e7d35a72d1883cd 100644 (file)
@@ -51,7 +51,9 @@
   __fcntl64 (fd, cmd, __VA_ARGS__)
 #define __getrandom_nocancel(buf, size, flags) \
   __getrandom (buf, size, flags)
-#define __getrandom_nocancel_nostatus(buf, size, flags) \
+#define __getrandom_nocancel_direct(buf, size, flags) \
+  __getrandom (buf, size, flags)
+#define __getrandom_nocancel_nostatus_direct(buf, size, flags) \
   __getrandom (buf, size, flags)
 #define __poll_infinity_nocancel(fds, nfds) \
   __poll (fds, nfds, -1)
index 69fb3c00ef774d004f55dca9c5a921313556405b..ec5f5aa8954baa4d698d3485018e089364fb3372 100644 (file)
@@ -79,7 +79,7 @@ __typeof (__fcntl) __fcntl_nocancel;
 /* Non cancellable getrandom syscall that does not also set errno in case of
    failure.  */
 static inline ssize_t
-__getrandom_nocancel_nostatus (void *buf, size_t buflen, unsigned int flags)
+__getrandom_nocancel_nostatus_direct (void *buf, size_t buflen, unsigned int flags)
 {
   int save_errno = errno;
   ssize_t r = __getrandom (buf, buflen, flags);
@@ -90,6 +90,8 @@ __getrandom_nocancel_nostatus (void *buf, size_t buflen, unsigned int flags)
 
 #define __getrandom_nocancel(buf, size, flags) \
   __getrandom (buf, size, flags)
+#define __getrandom_nocancel_direct(buf, size, flags) \
+  __getrandom (buf, size, flags)
 
 #define __poll_infinity_nocancel(fds, nfds) \
   __poll (fds, nfds, -1)
index 52c90e61e30f522ec6fcb25bf86ee99834f0c0c9..9ae4c378e2e89437325a58c4a2e6f34d955034e0 100644 (file)
@@ -19,6 +19,7 @@
 #include <arch-fork.h>
 #include <libc-lock.h>
 #include <pthreadP.h>
+#include <getrandom-internal.h>
 
 pid_t
 _Fork (void)
@@ -50,6 +51,7 @@ _Fork (void)
       self->robust_head.list = &self->robust_head;
       INTERNAL_SYSCALL_CALL (set_robust_list, &self->robust_head,
                             sizeof (struct robust_list_head));
+      call_function_static_weak (__getrandom_fork_subprocess);
     }
 
   __abort_lock_unlock (&original_sigmask);
index 7643926df9e3a22ed47601af42ed05b506c1f367..eabf3c81b0127b3489bdfba36d0fc9deba0663a5 100644 (file)
@@ -26,6 +26,7 @@
 #include <mqueue.h>
 #include <pthreadP.h>
 #include <sysdep.h>
+#include <getrandom-internal.h>
 
 static inline void
 fork_system_setup (void)
@@ -46,6 +47,7 @@ fork_system_setup_after_fork (void)
 
   call_function_static_weak (__mq_notify_fork_subprocess);
   call_function_static_weak (__timer_fork_subprocess);
+  call_function_static_weak (__getrandom_fork_subprocess);
 }
 
 /* In case of a fork() call the memory allocation in the child will be
@@ -128,9 +130,19 @@ reclaim_stacks (void)
                    curp->specific_used = true;
                  }
            }
+
+         call_function_static_weak (__getrandom_reset_state, curp);
        }
     }
 
+  /* Also reset stale getrandom states for user stack threads.  */
+  list_for_each (runp, &GL (dl_stack_user))
+    {
+      struct pthread *curp = list_entry (runp, struct pthread, list);
+      if (curp != self)
+       call_function_static_weak (__getrandom_reset_state, curp);
+    }
+
   /* Add the stack of all running threads to the cache.  */
   list_splice (&GL (dl_stack_used), &GL (dl_stack_cache));
 
index bbbe35723cac80ef1b1202fe4c173397d0d41af9..974b503b2f93511d0315b03ee6a7f14e77afd9ed 100644 (file)
 # define HAVE_CLOCK_GETRES64_VSYSCALL  "__kernel_clock_getres"
 # define HAVE_CLOCK_GETTIME64_VSYSCALL "__kernel_clock_gettime"
 # define HAVE_GETTIMEOFDAY_VSYSCALL    "__kernel_gettimeofday"
+# define HAVE_GETRANDOM_VSYSCALL        "__kernel_getrandom"
 
 # define HAVE_CLONE3_WRAPPER           1
 
index 3a44944dbb56a55dd750d1fe197e9f8b1fb410a3..476c6db75ac09e42d818aac98d9f84cfc762306e 100644 (file)
@@ -66,6 +66,11 @@ PROCINFO_CLASS int (*_dl_vdso_clock_getres) (clockid_t,
 PROCINFO_CLASS int (*_dl_vdso_clock_getres_time64) (clockid_t,
                                                    struct __timespec64 *) RELRO;
 # endif
+# ifdef HAVE_GETRANDOM_VSYSCALL
+PROCINFO_CLASS ssize_t (*_dl_vdso_getrandom) (void *buffer, size_t len,
+                                              unsigned int flags, void *state,
+                                              size_t state_len) RELRO;
+# endif
 
 /* PowerPC specific ones.  */
 # ifdef HAVE_GET_TBFREQ
index 8aee5a8212750140a11ff45964a137e4c9572601..cde99f608c82ea3cba1c9042686aa51fef256abd 100644 (file)
@@ -50,6 +50,9 @@ setup_vdso_pointers (void)
 #ifdef HAVE_RISCV_HWPROBE
   GLRO(dl_vdso_riscv_hwprobe) = dl_vdso_vsym (HAVE_RISCV_HWPROBE);
 #endif
+#ifdef HAVE_GETRANDOM_VSYSCALL
+  GLRO(dl_vdso_getrandom) = dl_vdso_vsym (HAVE_GETRANDOM_VSYSCALL);
+#endif
 }
 
 #endif
diff --git a/sysdeps/unix/sysv/linux/getrandom-internal.h b/sysdeps/unix/sysv/linux/getrandom-internal.h
new file mode 100644 (file)
index 0000000..37e6c9b
--- /dev/null
@@ -0,0 +1,29 @@
+/* Internal definitions for Linux getrandom implementation.
+   Copyright (C) 2024 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
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _GETRANDOM_INTERNAL_H
+#define _GETRANDOM_INTERNAL_H
+
+#include <pthreadP.h>
+
+extern void __getrandom_early_init (_Bool) attribute_hidden;
+
+extern void __getrandom_fork_subprocess (void) attribute_hidden;
+extern void __getrandom_vdso_release (struct pthread *curp) attribute_hidden;
+extern void __getrandom_reset_state (struct pthread *curp) attribute_hidden;
+#endif
index 777d1decf0fa50ea215b1279dae63a3388727e2c..c8c578263da456b24798d40b188bf522db6f5a14 100644 (file)
 #include <unistd.h>
 #include <sysdep-cancel.h>
 
+static inline ssize_t
+getrandom_syscall (void *buffer, size_t length, unsigned int flags,
+                  bool cancel)
+{
+  return cancel
+        ? SYSCALL_CANCEL (getrandom, buffer, length, flags)
+        : INLINE_SYSCALL_CALL (getrandom, buffer, length, flags);
+}
+
+#ifdef HAVE_GETRANDOM_VSYSCALL
+# include <assert.h>
+# include <ldsodefs.h>
+# include <libc-lock.h>
+# include <list.h>
+# include <setvmaname.h>
+# include <sys/mman.h>
+# include <sys/sysinfo.h>
+# include <tls-internal.h>
+
+/* These values will be initialized at loading time by calling the
+   _dl_vdso_getrandom with a special value.  The 'state_size' is the opaque
+   state size per-thread allocated with a mmap using 'mmap_prot' and
+   'mmap_flags' argument.  */
+static uint32_t state_size;
+static uint32_t state_size_cache_aligned;
+static uint32_t mmap_prot;
+static uint32_t mmap_flags;
+
+/* The function below are used on reentracy handling with (i.e. SA_NODEFER).
+   Before allocating a new state or issue the vDSO, atomically read the
+   current thread buffer, and if this is already reserved (is_reserved_ptr)
+   fallback to the syscall.  Otherwise, reserve the buffer by atomically
+   setting the LSB of the opaque state pointer.  The bit is cleared after the
+   vDSO is called, or before issuing the fallback syscall.  */
+
+static inline void *reserve_ptr (void *p)
+{
+  return (void *) ((uintptr_t) (p) | 1UL);
+}
+
+static inline void *release_ptr (void *p)
+{
+  return (void *) ((uintptr_t) (p) & ~1UL);
+}
+
+static inline bool is_reserved_ptr (void *p)
+{
+  return (uintptr_t) (p) & 1UL;
+}
+
+static struct
+{
+  __libc_lock_define (, lock);
+
+  void **states;  /* Queue of opaque states allocated with the kernel
+                    provided flags and used on getrandom vDSO call.  */
+  size_t len;    /* Number of available free states in the queue.  */
+  size_t total;          /* Number of states allocated from the kernel.  */
+  size_t cap;     /* Total number of states that 'states' can hold before
+                    needed to be resized.  */
+} grnd_alloc = {
+  .lock = LLL_LOCK_INITIALIZER
+};
+
+static bool
+vgetrandom_get_state_alloc (void)
+{
+  /* Start by allocating one page for the opaque states.  */
+  size_t block_size = ALIGN_UP (state_size_cache_aligned, GLRO(dl_pagesize));
+  size_t states_per_page = GLRO (dl_pagesize) / state_size_cache_aligned;
+  void *block = __mmap (NULL, GLRO(dl_pagesize), mmap_prot, mmap_flags, -1, 0);
+  if (block == MAP_FAILED)
+    return false;
+  __set_vma_name (block, block_size, " glibc: getrandom");
+
+  if (grnd_alloc.total + states_per_page > grnd_alloc.cap)
+    {
+      /* Use a new mmap instead of trying to mremap.  It avoids a
+        potential multithread fork issue where fork is called just after
+        mremap returns but before assigning to the grnd_alloc.states,
+        thus making the its value invalid in the child.  */
+      void *old_states = grnd_alloc.states;
+      size_t new_states_size = ALIGN_UP ((grnd_alloc.total + states_per_page)
+                                        * sizeof (*grnd_alloc.states),
+                                        GLRO(dl_pagesize));
+
+      /* There is no need to memcpy any opaque state information because
+        all the allocated opaque states are assigned to running threads
+        (meaning that if we iterate over them we can reconstruct the state
+        list).  */
+      void **states = __mmap (NULL, new_states_size, PROT_READ | PROT_WRITE,
+                             MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+      if (states == MAP_FAILED)
+       {
+         __munmap (block, block_size);
+         return false;
+       }
+
+      /* Atomically replace the old state, so if a fork happens the child
+        process will see a consistent free state buffer.  The size might
+        not be updated, but it does not really matter since the buffer is
+        always increased.  */
+      grnd_alloc.states = states;
+      atomic_thread_fence_seq_cst ();
+      if (old_states != NULL)
+       __munmap (old_states, grnd_alloc.cap * sizeof (*grnd_alloc.states));
+
+      __set_vma_name (states, new_states_size, " glibc: getrandom states");
+      grnd_alloc.cap = new_states_size / sizeof (*grnd_alloc.states);
+      atomic_thread_fence_seq_cst ();
+    }
+
+  for (size_t i = 0; i < states_per_page; ++i)
+    {
+      /* There is no need to handle states that straddle a page because
+        we allocate only one page.  */
+      grnd_alloc.states[i] = block;
+      block += state_size_cache_aligned;
+    }
+  /* Concurrent fork should not observe the previous pointer value.  */
+  grnd_alloc.len = states_per_page;
+  grnd_alloc.total += states_per_page;
+  atomic_thread_fence_seq_cst ();
+
+  return true;
+}
+
+/* Allocate an opaque state for vgetrandom.  If the grnd_alloc does not have
+   any, mmap() another page of them using the vgetrandom parameters.  */
+static void *
+vgetrandom_get_state (void)
+{
+  void *state = NULL;
+
+  /* The signal blocking avoid the potential issue where _Fork() (which is
+     async-signal-safe) is called with the lock taken.  The function is
+     called only once during thread lifetime, so the overhead should be
+     minimal.  */
+  internal_sigset_t set;
+  internal_signal_block_all (&set);
+  __libc_lock_lock (grnd_alloc.lock);
+
+  if (grnd_alloc.len > 0 || vgetrandom_get_state_alloc ())
+    state = grnd_alloc.states[--grnd_alloc.len];
+
+  __libc_lock_unlock (grnd_alloc.lock);
+  internal_signal_restore_set (&set);
+
+  return state;
+}
+
+/* Returns true when vgetrandom is used successfully.  Returns false if the
+   syscall fallback should be issued in the case the vDSO is not present, in
+   the case of reentrancy, or if any memory allocation fails.  */
+static ssize_t
+getrandom_vdso (void *buffer, size_t length, unsigned int flags, bool cancel)
+{
+  if (__glibc_unlikely (state_size == 0))
+    return getrandom_syscall (buffer, length, flags, cancel);
+
+  struct pthread *self = THREAD_SELF;
+
+  void *state = atomic_load_relaxed (&self->getrandom_buf);
+  if (is_reserved_ptr (state))
+    return getrandom_syscall (buffer, length, flags, cancel);
+  atomic_store_relaxed (&self->getrandom_buf, reserve_ptr (state));
+  __atomic_signal_fence (__ATOMIC_ACQ_REL);
+
+  bool r = false;
+  if (state == NULL)
+    {
+      state = vgetrandom_get_state ();
+      if (state == NULL)
+        goto out;
+    }
+
+  /* Since the vDSO implementation does not issue the syscall with the
+     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);
+  if (INTERNAL_SYSCALL_ERROR_P (ret))
+    {
+      /* Fallback to the syscall if the kernel would block.  */
+      int err = INTERNAL_SYSCALL_ERRNO (ret);
+      if (err == EAGAIN && !(flags & GRND_NONBLOCK))
+        goto out;
+
+      __set_errno (err);
+      ret = -1;
+    }
+  r = true;
+
+out:
+  __atomic_signal_fence (__ATOMIC_ACQ_REL);
+  atomic_store_relaxed (&self->getrandom_buf, state);
+  return r ? ret : getrandom_syscall (buffer, length, flags, cancel);
+}
+#endif
+
+void
+__getrandom_early_init (_Bool initial)
+{
+#ifdef HAVE_GETRANDOM_VSYSCALL
+  /* libcs loaded for audit modules, dlmopen, etc. fallback to syscall.  */
+  if (initial && (GLRO (dl_vdso_getrandom) != NULL))
+    {
+      /* Used to query the vDSO for the required mmap flags and the opaque
+        per-thread state size.  Defined by linux/random.h.  */
+      struct vgetrandom_opaque_params
+      {
+       uint32_t size_of_opaque_state;
+       uint32_t mmap_prot;
+       uint32_t mmap_flags;
+       uint32_t reserved[13];
+      } params;
+      if (GLRO(dl_vdso_getrandom) (NULL, 0, 0, &params, ~0UL) == 0)
+       {
+         /* Align each opaque state to L1 data cache size to avoid false
+            sharing.  If the size can not be obtained, use the kernel
+            provided one.  */
+         state_size = params.size_of_opaque_state;
+
+         long int ld1sz = __sysconf (_SC_LEVEL1_DCACHE_LINESIZE);
+         if (ld1sz <= 0)
+           ld1sz = 1;
+         state_size_cache_aligned = ALIGN_UP (state_size, ld1sz);
+         /* Do not enable vDSO if the required opaque state size is larger
+            than a page because we only allocate one page per time to hold
+            the states.  */
+         if (state_size_cache_aligned > GLRO(dl_pagesize))
+           {
+             state_size = 0;
+             return;
+           }
+         mmap_prot = params.mmap_prot;
+         mmap_flags = params.mmap_flags;
+       }
+    }
+#endif
+}
+
+/* Re-add the state state from CURP on the free list.  This function is
+   called after fork returns in the child, so no locking is required.  */
+void
+__getrandom_reset_state (struct pthread *curp)
+{
+#ifdef HAVE_GETRANDOM_VSYSCALL
+  if (grnd_alloc.states == NULL || curp->getrandom_buf == NULL)
+    return;
+  assert (grnd_alloc.len < grnd_alloc.cap);
+  grnd_alloc.states[grnd_alloc.len++] = release_ptr (curp->getrandom_buf);
+  curp->getrandom_buf = NULL;
+#endif
+}
+
+/* Called when a thread terminates, and adds its random buffer back into the
+   allocator pool for use in a future thread.  This is called by
+   pthread_create during thread termination, and after signal has been
+   blocked. */
+void
+__getrandom_vdso_release (struct pthread *curp)
+{
+#ifdef HAVE_GETRANDOM_VSYSCALL
+  if (curp->getrandom_buf == NULL)
+    return;
+
+  __libc_lock_lock (grnd_alloc.lock);
+  grnd_alloc.states[grnd_alloc.len++] = curp->getrandom_buf;
+  __libc_lock_unlock (grnd_alloc.lock);
+#endif
+}
+
+/* Reset the internal lock state in case another thread has locked while
+   this thread calls fork.  The stale thread states will be handled by
+   reclaim_stacks which calls __getrandom_reset_state on each thread.  */
+void
+__getrandom_fork_subprocess (void)
+{
+#ifdef HAVE_GETRANDOM_VSYSCALL
+  grnd_alloc.lock = LLL_LOCK_INITIALIZER;
+#endif
+}
+
+ssize_t
+__getrandom_nocancel (void *buffer, size_t length, unsigned int flags)
+{
+#ifdef HAVE_GETRANDOM_VSYSCALL
+  return getrandom_vdso (buffer, length, flags, false);
+#else
+  return getrandom_syscall (buffer, length, flags, false);
+#endif
+}
+
 /* Write up to LENGTH bytes of randomness starting at BUFFER.
    Return the number of bytes written, or -1 on error.  */
 ssize_t
 __getrandom (void *buffer, size_t length, unsigned int flags)
 {
-  return SYSCALL_CANCEL (getrandom, buffer, length, flags);
+#ifdef HAVE_GETRANDOM_VSYSCALL
+  return getrandom_vdso (buffer, length, flags, true);
+#else
+  return getrandom_syscall (buffer, length, flags, true);
+#endif
 }
 libc_hidden_def (__getrandom)
 weak_alias (__getrandom, getrandom)
index eb0ba790daa6e27c3ce9e8ec2d4b476ae382d9df..e2d853ae3e3c77fb06c8f86196ed461ce7860461 100644 (file)
 #define HAVE_CLOCK_GETTIME64_VSYSCALL "__vdso_clock_gettime"
 #define HAVE_GETTIMEOFDAY_VSYSCALL "__vdso_gettimeofday"
 #define HAVE_GETCPU_VSYSCALL "__vdso_getcpu"
+#define HAVE_GETRANDOM_VSYSCALL "__vdso_getrandom"
 
 #define HAVE_CLONE3_WRAPPER 1
 
index 2a7585b73f2b23f76b0dc5cd526a17bc64600c8c..12f26912d3f03640da9acdee2860e4c3fa895cb7 100644 (file)
@@ -27,6 +27,7 @@
 #include <sys/syscall.h>
 #include <sys/wait.h>
 #include <time.h>
+#include <sys/random.h>
 
 /* Non cancellable open syscall.  */
 __typeof (open) __open_nocancel;
@@ -84,15 +85,17 @@ __writev_nocancel_nostatus (int fd, const struct iovec *iov, int iovcnt)
 }
 
 static inline ssize_t
-__getrandom_nocancel (void *buf, size_t buflen, unsigned int flags)
+__getrandom_nocancel_direct (void *buf, size_t buflen, unsigned int flags)
 {
   return INLINE_SYSCALL_CALL (getrandom, buf, buflen, flags);
 }
 
+__typeof (getrandom) __getrandom_nocancel attribute_hidden;
+
 /* Non cancellable getrandom syscall that does not also set errno in case of
    failure.  */
 static inline ssize_t
-__getrandom_nocancel_nostatus (void *buf, size_t buflen, unsigned int flags)
+__getrandom_nocancel_nostatus_direct (void *buf, size_t buflen, unsigned int flags)
 {
   return INTERNAL_SYSCALL_CALL (getrandom, buf, buflen, flags);
 }
index a69b7db33843d4881305c901c5843333916a0ad8..48f3d0d1b2c271cf930c15a316c88e186a05fffe 100644 (file)
 #define HAVE_TIME_VSYSCALL             "__kernel_time"
 #define HAVE_GETTIMEOFDAY_VSYSCALL      "__kernel_gettimeofday"
 #define HAVE_GET_TBFREQ                 "__kernel_get_tbfreq"
+#define HAVE_GETRANDOM_VSYSCALL         "__kernel_getrandom"
 
 #endif /* _LINUX_POWERPC_SYSDEP_H  */
index 9b3000ca62a0e00d9587a4f47a04ad07eb01699d..9698c57a03d1960711988e9036b8cba38e913d1f 100644 (file)
@@ -72,6 +72,7 @@
 #ifdef __s390x__
 #define HAVE_CLOCK_GETRES64_VSYSCALL   "__kernel_clock_getres"
 #define HAVE_CLOCK_GETTIME64_VSYSCALL  "__kernel_clock_gettime"
+#define HAVE_GETRANDOM_VSYSCALL                "__kernel_getrandom"
 #else
 #define HAVE_CLOCK_GETRES_VSYSCALL     "__kernel_clock_getres"
 #define HAVE_CLOCK_GETTIME_VSYSCALL    "__kernel_clock_gettime"
index a2b021bd86f5d47284958eed11d6024a4027b698..7dc072ae2da8f7c3ea0e5cc005daf774b7824c21 100644 (file)
 # define HAVE_TIME_VSYSCALL             "__vdso_time"
 # define HAVE_GETCPU_VSYSCALL          "__vdso_getcpu"
 # define HAVE_CLOCK_GETRES64_VSYSCALL   "__vdso_clock_getres"
+# define HAVE_GETRANDOM_VSYSCALL        "__vdso_getrandom"
 
 # define HAVE_CLONE3_WRAPPER                   1