merge revision(s) 1181a682a6, 0cec4a14fb, d84a811f31: [Backport #21448]

[Bug #21448] Use `getentropy(2)` only on macOS

	If this is not a system call, then it is using getrandom (which would
	have been tried already), and cannot be used as a replacement for the
	random devices.

	Restore getrandom(2) path for Linux with glibc 2.36 or later

	This is a follow-up to commit b120f5e38d (avoid fork-unsafe arc4random
	implementations, 2018-09-04).

	Avoid defining a no-op fill_random_bytes_syscall() if arc4random_buf(3)
	exists, but we are unsure if it is fork-safe. Check for other options
	instead. IOW, see if getrandom(2) is available.

	glibc 2.36, released in 2022, started to provide arc4random_buf(3) on
	Linux. This causes fill_random_bytes_syscall() to use neither of them
	and makes Random.urandom solely rely on getentropy(3) via
	fill_random_bytes_urandom().

	While the glibc implementation is safe, I did not add it to the list
	because using getrandom(2) directly is preferable on Linux.

	[Bug #21448] Reorder trials in `fill_random_bytes`

	First try dedicated system calls, such as `getrandom` or `getentropy`,
	next possible libraries, then fallback to `/dev/urandom`.
This commit is contained in:
nagachika 2025-07-20 15:43:31 +09:00
parent f4de78f2b4
commit 9b9f244b98
2 changed files with 53 additions and 37 deletions

View file

@ -437,23 +437,17 @@ random_init(int argc, VALUE *argv, VALUE obj)
# define USE_DEV_URANDOM 0
#endif
#ifdef HAVE_GETENTROPY
# define MAX_SEED_LEN_PER_READ 256
static int
fill_random_bytes_urandom(void *seed, size_t size)
{
unsigned char *p = (unsigned char *)seed;
while (size) {
size_t len = size < MAX_SEED_LEN_PER_READ ? size : MAX_SEED_LEN_PER_READ;
if (getentropy(p, len) != 0) {
return -1;
}
p += len;
size -= len;
}
return 0;
}
#elif USE_DEV_URANDOM
#if ! defined HAVE_GETRANDOM && defined __linux__ && defined __NR_getrandom
# ifndef GRND_NONBLOCK
# define GRND_NONBLOCK 0x0001 /* not defined in musl libc */
# endif
# define getrandom(ptr, size, flags) \
(ssize_t)syscall(__NR_getrandom, (ptr), (size), (flags))
# define HAVE_GETRANDOM 1
#endif
/* fill random bytes by reading random device directly */
#if USE_DEV_URANDOM
static int
fill_random_bytes_urandom(void *seed, size_t size)
{
@ -493,15 +487,7 @@ fill_random_bytes_urandom(void *seed, size_t size)
# define fill_random_bytes_urandom(seed, size) -1
#endif
#if ! defined HAVE_GETRANDOM && defined __linux__ && defined __NR_getrandom
# ifndef GRND_NONBLOCK
# define GRND_NONBLOCK 0x0001 /* not defined in musl libc */
# endif
# define getrandom(ptr, size, flags) \
(ssize_t)syscall(__NR_getrandom, (ptr), (size), (flags))
# define HAVE_GETRANDOM 1
#endif
/* fill random bytes by library */
#if 0
#elif defined MAC_OS_X_VERSION_10_7 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7
@ -519,7 +505,7 @@ fill_random_bytes_urandom(void *seed, size_t size)
# endif
static int
fill_random_bytes_syscall(void *seed, size_t size, int unused)
fill_random_bytes_lib(void *seed, size_t size)
{
#if USE_COMMON_RANDOM
CCRNGStatus status = CCRandomGenerateBytes(seed, size);
@ -546,18 +532,16 @@ fill_random_bytes_syscall(void *seed, size_t size, int unused)
}
return 0;
}
#elif defined(HAVE_ARC4RANDOM_BUF)
#elif defined(HAVE_ARC4RANDOM_BUF) && \
((defined(__OpenBSD__) && OpenBSD >= 201411) || \
(defined(__NetBSD__) && __NetBSD_Version__ >= 700000000) || \
(defined(__FreeBSD__) && __FreeBSD_version >= 1200079))
// [Bug #15039] arc4random_buf(3) should used only if we know it is fork-safe
static int
fill_random_bytes_syscall(void *buf, size_t size, int unused)
fill_random_bytes_lib(void *buf, size_t size)
{
#if (defined(__OpenBSD__) && OpenBSD >= 201411) || \
(defined(__NetBSD__) && __NetBSD_Version__ >= 700000000) || \
(defined(__FreeBSD__) && __FreeBSD_version >= 1200079)
arc4random_buf(buf, size);
return 0;
#else
return -1;
#endif
}
#elif defined(_WIN32)
@ -633,11 +617,17 @@ fill_random_bytes_bcrypt(void *seed, size_t size)
}
static int
fill_random_bytes_syscall(void *seed, size_t size, int unused)
fill_random_bytes_lib(void *seed, size_t size)
{
if (fill_random_bytes_bcrypt(seed, size) == 0) return 0;
return fill_random_bytes_crypt(seed, size);
}
#else
# define fill_random_bytes_lib(seed, size) -1
#endif
/* fill random bytes by dedicated syscall */
#if 0
#elif defined HAVE_GETRANDOM
static int
fill_random_bytes_syscall(void *seed, size_t size, int need_secure)
@ -661,6 +651,31 @@ fill_random_bytes_syscall(void *seed, size_t size, int need_secure)
}
return -1;
}
#elif defined(HAVE_GETENTROPY)
/*
* The Open Group Base Specifications Issue 8 - IEEE Std 1003.1-2024
* https://pubs.opengroup.org/onlinepubs/9799919799/functions/getentropy.html
*
* NOTE: `getentropy`(3) on Linux is implemented using `getrandom`(2),
* prefer the latter over this if both are defined.
*/
#ifndef GETENTROPY_MAX
# define GETENTROPY_MAX 256
#endif
static int
fill_random_bytes_syscall(void *seed, size_t size, int need_secure)
{
unsigned char *p = (unsigned char *)seed;
while (size) {
size_t len = size < GETENTROPY_MAX ? size : GETENTROPY_MAX;
if (getentropy(p, len) != 0) {
return -1;
}
p += len;
size -= len;
}
return 0;
}
#else
# define fill_random_bytes_syscall(seed, size, need_secure) -1
#endif
@ -670,6 +685,7 @@ ruby_fill_random_bytes(void *seed, size_t size, int need_secure)
{
int ret = fill_random_bytes_syscall(seed, size, need_secure);
if (ret == 0) return ret;
if (fill_random_bytes_lib(seed, size) == 0) return 0;
return fill_random_bytes_urandom(seed, size);
}

View file

@ -11,7 +11,7 @@
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
#define RUBY_VERSION_TEENY 8
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
#define RUBY_PATCHLEVEL 164
#define RUBY_PATCHLEVEL 165
#include "ruby/version.h"
#include "ruby/internal/abi.h"