Since CheriBSD is derived from FreeBSD, it sounds likely that FreeBSD's random() function is equally not multithread-safe.
To verify this hypothesis, I wrote a MT-safety test for random(). The result (with a REPEAT_COUNT of 1000000): glibc OK musl libc OK FreeBSD 11.0 Expected value #932913 not found in multithreaded results. (18 failures among 100 runs) FreeBSD 13.2 Expected value #712890 not found in multithreaded results. (2 failures among 100 runs) NetBSD OK OpenBSD Expected value #0 not found in multithreaded results. => SKIP macOS Expected value #16 not found in multithreaded results. AIX OK Solaris10 Expected value #4 not found in multithreaded results. Solaris11 Expected value #46 not found in multithreaded results. Cygwin 2.9.0 Expected value #2499 not found in multithreaded results. Cygwin 3.4.6 Expected value #367 not found in multithreaded results. mingw OK (uses gnulib implementation) MSVC OK (uses gnulib implementation) Haiku Expected value #188587 not found in multithreaded results. (1 failure among 100 runs) Minix SKIP Android OK So, in general, it requires 100'000'000 repetitions to determine whether the function is MT-safe. But that's too long for a unit test. I'm therefore reducing it to 100'000 repetitions and hardcoding the known platforms in the configure test. Even these 100'000 repetitions take 3 seconds on Haiku or 1 second on mingw or MSVC. 2023-11-10 Bruno Haible <br...@clisp.org> doc: Mention an srandom limitation on OpenBSD. * doc/posix-functions/srandom.texi: Mention the OpenBSD limitation. 2023-11-10 Bruno Haible <br...@clisp.org> random tests: Add multithread-safety test. * tests/test-random-mt.c: New file. * modules/random-tests (Files): Add it. (Depends-on): Add xalloc, thread, yield. (Makefile.am): Also build and test test-random-mt. random: Fix multithread-safety bug in general. * m4/random.m4 (gl_FUNC_RANDOM): Override also macOS, FreeBSD, Solaris, Cygwin, Haiku. * doc/posix-functions/random.texi: Mention the wider scope of the multithread-safety bug.
>From 53483b103f8d733df604e03f5260486ebf1a4496 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Fri, 10 Nov 2023 16:46:17 +0100 Subject: [PATCH 1/3] random: Fix multithread-safety bug in general. * m4/random.m4 (gl_FUNC_RANDOM): Override also macOS, FreeBSD, Solaris, Cygwin, Haiku. * doc/posix-functions/random.texi: Mention the wider scope of the multithread-safety bug. --- ChangeLog | 8 ++++++++ doc/posix-functions/random.texi | 4 ++-- m4/random.m4 | 11 +++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5c92da54ec..d38e5b5665 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2023-11-10 Bruno Haible <br...@clisp.org> + + random: Fix multithread-safety bug in general. + * m4/random.m4 (gl_FUNC_RANDOM): Override also macOS, FreeBSD, Solaris, + Cygwin, Haiku. + * doc/posix-functions/random.texi: Mention the wider scope of the + multithread-safety bug. + 2023-11-09 Bruno Haible <br...@clisp.org> sigsegv: Improve port to CHERI. diff --git a/doc/posix-functions/random.texi b/doc/posix-functions/random.texi index e0bb8e76bc..84cacbe8b5 100644 --- a/doc/posix-functions/random.texi +++ b/doc/posix-functions/random.texi @@ -15,8 +15,8 @@ This function is only defined as an inline function on some platforms: Android 4.4. @item -This function crashes when used in multithreaded programs on some platforms: -CheriBSD. +This function is not multithread-safe on some platforms: +macOS 12.5, FreeBSD 13.2, Solaris 11.4, Cygwin 3.4.6, Haiku. @end itemize Portability problems not fixed by Gnulib: diff --git a/m4/random.m4 b/m4/random.m4 index 69bac2459a..4e4f01b8ed 100644 --- a/m4/random.m4 +++ b/m4/random.m4 @@ -1,4 +1,4 @@ -# random.m4 serial 7 +# random.m4 serial 8 dnl Copyright (C) 2012-2023 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -44,10 +44,13 @@ AC_DEFUN([gl_FUNC_RANDOM] future*) REPLACE_SETSTATE=1 ;; esac fi - dnl On CheriBSD, random() lacks locking, leading to an out-of-bounds read - dnl inside random_r. + dnl On several platforms, random() is not multithread-safe. if test $ac_cv_func_initstate = no || test $ac_cv_func_setstate = no \ - || case "$host" in aarch64c-*-freebsd*) true;; *) false;; esac; then + || case "$host_os" in \ + darwin* | freebsd* | solaris* | cygwin* | haiku*) true ;; \ + *) false ;; \ + esac + then dnl In order to define initstate or setstate, we need to define all the dnl functions at once. REPLACE_RANDOM=1 -- 2.34.1
>From b5eede6ff76d45112ea1973f12a131228f36a92e Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Fri, 10 Nov 2023 16:48:18 +0100 Subject: [PATCH 2/3] random tests: Add multithread-safety test. * tests/test-random-mt.c: New file. * modules/random-tests (Files): Add it. (Depends-on): Add xalloc, thread, yield. (Makefile.am): Also build and test test-random-mt. --- ChangeLog | 6 ++ modules/random-tests | 9 ++- tests/test-random-mt.c | 169 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 tests/test-random-mt.c diff --git a/ChangeLog b/ChangeLog index d38e5b5665..6be74ca3c1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2023-11-10 Bruno Haible <br...@clisp.org> + random tests: Add multithread-safety test. + * tests/test-random-mt.c: New file. + * modules/random-tests (Files): Add it. + (Depends-on): Add xalloc, thread, yield. + (Makefile.am): Also build and test test-random-mt. + random: Fix multithread-safety bug in general. * m4/random.m4 (gl_FUNC_RANDOM): Override also macOS, FreeBSD, Solaris, Cygwin, Haiku. diff --git a/modules/random-tests b/modules/random-tests index 27e5fb9387..d82e73c8c8 100644 --- a/modules/random-tests +++ b/modules/random-tests @@ -1,12 +1,17 @@ Files: tests/test-random.c +tests/test-random-mt.c tests/signature.h tests/macros.h Depends-on: +xalloc +thread +yield configure.ac: Makefile.am: -TESTS += test-random -check_PROGRAMS += test-random +TESTS += test-random test-random-mt +check_PROGRAMS += test-random test-random-mt +test_random_mt_LDADD = $(LDADD) $(LIBINTL) $(LIBMULTITHREAD) $(YIELD_LIB) diff --git a/tests/test-random-mt.c b/tests/test-random-mt.c new file mode 100644 index 0000000000..9e95477ce0 --- /dev/null +++ b/tests/test-random-mt.c @@ -0,0 +1,169 @@ +/* Multithread-safety test for random(). + Copyright (C) 2023 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2023. */ + +#include <config.h> + +#if USE_ISOC_THREADS || USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS || USE_WINDOWS_THREADS + +/* Whether to help the scheduler through explicit yield(). + Uncomment this to see if the operating system has a fair scheduler. */ +#define EXPLICIT_YIELD 1 + +/* Number of simultaneous threads. */ +#define THREAD_COUNT 4 + +/* Number of random() invocations operations performed in each thread. + This value is chosen so that the unit test terminates quickly. + To reliably determine whether a random() implementation is multithread-safe, + set REPEAT_COUNT to 1000000 and run the test 100 times: + $ for i in `seq 100`; do ./test-random-mt; done + */ +#define REPEAT_COUNT 100000 + +/* Specification. */ +#include <stdlib.h> + +#include <stdio.h> + +#if EXPLICIT_YIELD +# include <sched.h> +#endif + +#include "glthread/thread.h" +#include "xalloc.h" + +#if EXPLICIT_YIELD +# define yield() sched_yield () +#else +# define yield() +#endif + +/* This test runs REPEAT_COUNT invocations of random() in each thread and stores + the result, then compares the first REPEAT_COUNT among these + THREAD_COUNT * REPEAT_COUNT + random numbers against a precomputed sequence with the same seed. */ + +static void * +random_invocator_thread (void *arg) +{ + long *storage = (long *) arg; + int repeat; + + for (repeat = 0; repeat < REPEAT_COUNT; repeat++) + { + storage[repeat] = random (); + yield (); + } + + return NULL; +} + +int +main () +{ + unsigned int seed = 19891109; + + /* First, get the expected sequence of random() results. */ + srandom (seed); + long *expected = XNMALLOC (REPEAT_COUNT, long); + { + int repeat; + for (repeat = 0; repeat < REPEAT_COUNT; repeat++) + expected[repeat] = random (); + } + + /* Then, run REPEAT_COUNT invocations of random() each, in THREAD_COUNT + separate threads. */ + gl_thread_t threads[THREAD_COUNT]; + long *thread_results[THREAD_COUNT]; + srandom (seed); + { + int i; + for (i = 0; i < THREAD_COUNT; i++) + thread_results[i] = XNMALLOC (REPEAT_COUNT, long); + for (i = 0; i < THREAD_COUNT; i++) + threads[i] = + gl_thread_create (random_invocator_thread, thread_results[i]); + } + + /* Wait for the threads to terminate. */ + { + int i; + for (i = 0; i < THREAD_COUNT; i++) + gl_thread_join (threads[i], NULL); + } + + /* Finally, determine whether the threads produced the same sequence of + random() results. */ + { + int expected_index; + int result_index[THREAD_COUNT]; + int i; + + for (i = 0; i < THREAD_COUNT; i++) + result_index[i] = 0; + + for (expected_index = 0; expected_index < REPEAT_COUNT; expected_index++) + { + long expected_value = expected[expected_index]; + + for (i = 0; i < THREAD_COUNT; i++) + { + if (thread_results[i][result_index[i]] == expected_value) + { + result_index[i]++; + break; + } + } + if (i == THREAD_COUNT) + { + if (expected_index == 0) + { + /* This occurs on platforms like OpenBSD, where srandom() has no + effect and random() always return non-deterministic values. + Mark the test as SKIP. */ + fprintf (stderr, "Skipping test: random() is non-deterministic.\n"); + return 77; + } + else + { + fprintf (stderr, "Expected value #%d not found in multithreaded results.\n", + expected_index); + return 1; + } + } + } + } + + return 0; +} + +#else + +/* No multithreading available. */ + +#include <stdio.h> + +int +main () +{ + fputs ("Skipping test: multithreading not enabled\n", stderr); + return 77; +} + +#endif -- 2.34.1
>From 6c7ea007d01115b6727e16bb070abbcf2dbef5ea Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Fri, 10 Nov 2023 16:51:02 +0100 Subject: [PATCH 3/3] doc: Mention an srandom limitation on OpenBSD. * doc/posix-functions/srandom.texi: Mention the OpenBSD limitation. --- ChangeLog | 5 +++++ doc/posix-functions/srandom.texi | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/ChangeLog b/ChangeLog index 6be74ca3c1..27c6791238 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2023-11-10 Bruno Haible <br...@clisp.org> + + doc: Mention an srandom limitation on OpenBSD. + * doc/posix-functions/srandom.texi: Mention the OpenBSD limitation. + 2023-11-10 Bruno Haible <br...@clisp.org> random tests: Add multithread-safety test. diff --git a/doc/posix-functions/srandom.texi b/doc/posix-functions/srandom.texi index 12df9161ae..395ce927a8 100644 --- a/doc/posix-functions/srandom.texi +++ b/doc/posix-functions/srandom.texi @@ -26,4 +26,8 @@ The parameter is @code{unsigned long} instead of @code{unsigned int} on some platforms: MidnightBSD 2.0. +@item +This function has no effect on some platforms: +OpenBSD 7.4. +This platform has, instead, a function @code{srandom_deterministic}. @end itemize -- 2.34.1