diff mbox series

[2/7] stdlib: Add arc4random tests

Message ID 20220413202401.408267-3-adhemerval.zanella@linaro.org
State New
Headers show
Series Add arc4random support | expand

Commit Message

Adhemerval Zanella Netto April 13, 2022, 8:23 p.m. UTC
The basic tst-arc4random-chacha20.c checks if the output of ChaCha20
implementation matches the reference test vectors from RFC8439.

The tst-arc4random-fork.c check if subprocesses generate distinct
streams of randomness (if fork handling is done correctly).

The tst-arc4random-stats.c is a statistical test to the randomness of
arc4random, arc4random_buf, and arc4random_uniform.

The tst-arc4random-thread.c check if threads generate distinct streams
of randomness (if function are thread-safe).

Checked on x86_64-linux-gnu, aarch64-linux, and powerpc64le-linux-gnu.

Co-authored-by: Florian Weimer <fweimer@redhat.com>
---
 stdlib/Makefile                  |   4 +
 stdlib/tst-arc4random-chacha20.c | 225 +++++++++++++++++++++++++
 stdlib/tst-arc4random-fork.c     | 174 +++++++++++++++++++
 stdlib/tst-arc4random-stats.c    | 146 ++++++++++++++++
 stdlib/tst-arc4random-thread.c   | 278 +++++++++++++++++++++++++++++++
 5 files changed, 827 insertions(+)
 create mode 100644 stdlib/tst-arc4random-chacha20.c
 create mode 100644 stdlib/tst-arc4random-fork.c
 create mode 100644 stdlib/tst-arc4random-stats.c
 create mode 100644 stdlib/tst-arc4random-thread.c

Comments

Noah Goldstein April 14, 2022, 6:01 p.m. UTC | #1
On Wed, Apr 13, 2022 at 3:25 PM Adhemerval Zanella via Libc-alpha
<libc-alpha@sourceware.org> wrote:
>
> The basic tst-arc4random-chacha20.c checks if the output of ChaCha20
> implementation matches the reference test vectors from RFC8439.
>
> The tst-arc4random-fork.c check if subprocesses generate distinct
> streams of randomness (if fork handling is done correctly).
>
> The tst-arc4random-stats.c is a statistical test to the randomness of
> arc4random, arc4random_buf, and arc4random_uniform.
>
> The tst-arc4random-thread.c check if threads generate distinct streams
> of randomness (if function are thread-safe).
>
> Checked on x86_64-linux-gnu, aarch64-linux, and powerpc64le-linux-gnu.
>
> Co-authored-by: Florian Weimer <fweimer@redhat.com>
> ---
>  stdlib/Makefile                  |   4 +
>  stdlib/tst-arc4random-chacha20.c | 225 +++++++++++++++++++++++++
>  stdlib/tst-arc4random-fork.c     | 174 +++++++++++++++++++
>  stdlib/tst-arc4random-stats.c    | 146 ++++++++++++++++
>  stdlib/tst-arc4random-thread.c   | 278 +++++++++++++++++++++++++++++++
>  5 files changed, 827 insertions(+)
>  create mode 100644 stdlib/tst-arc4random-chacha20.c
>  create mode 100644 stdlib/tst-arc4random-fork.c
>  create mode 100644 stdlib/tst-arc4random-stats.c
>  create mode 100644 stdlib/tst-arc4random-thread.c
>
> diff --git a/stdlib/Makefile b/stdlib/Makefile
> index 9f9cc1bd7f..4862d008ab 100644
> --- a/stdlib/Makefile
> +++ b/stdlib/Makefile
> @@ -183,6 +183,9 @@ tests := \
>    testmb2 \
>    testrand \
>    testsort \
> +  tst-arc4random-fork \
> +  tst-arc4random-stats \
> +  tst-arc4random-thread \
>    tst-at_quick_exit \
>    tst-atexit \
>    tst-atof1 \
> @@ -252,6 +255,7 @@ tests-internal := \
>    # tests-internal
>
>  tests-static := \
> +  tst-arc4random-chacha20 \
>    tst-secure-getenv \
>    # tests-static
>
> diff --git a/stdlib/tst-arc4random-chacha20.c b/stdlib/tst-arc4random-chacha20.c
> new file mode 100644
> index 0000000000..c5876d3f3b
> --- /dev/null
> +++ b/stdlib/tst-arc4random-chacha20.c
> @@ -0,0 +1,225 @@
> +/* Basic tests for chacha20 cypher used in arc4random.
> +   Copyright (C) 2022 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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <chacha20.c>
> +#include <support/check.h>
> +
> +static int
> +do_test (void)
> +{
> +  /* Reference ChaCha20 encryption test vectors from RFC8439.  */
> +
> +  /* Test vector #1.  */
> +  {
> +    struct chacha20_state state;
> +
> +    uint8_t key[CHACHA20_KEY_SIZE] =
> +      {
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
> +      };
> +    uint8_t iv[CHACHA20_IV_SIZE] =
> +      {
> +       0x0, 0x0, 0x0, 0x0,
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
> +      };
> +    const uint8_t plaintext[CHACHA20_BLOCK_SIZE] = { 0 };
> +    uint8_t ciphertext[CHACHA20_BLOCK_SIZE];
> +
Can you remove this whitespace.
> +    chacha20_init (&state, key, iv);
> +    chacha20_crypt (&state, ciphertext, plaintext, sizeof plaintext);
> +
> +    const uint8_t expected[] =
> +      {
> +       0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90,
> +       0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28,
> +       0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a,
> +       0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7,
> +       0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d,
> +       0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37,
> +       0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
> +       0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86
> +      };
> +    TEST_COMPARE_BLOB (ciphertext, sizeof ciphertext,
> +                       expected, sizeof expected);
> +  }
> +
> +  /* Test vector #2.  */
> +  {
> +    struct chacha20_state state;
> +
> +    uint8_t key[CHACHA20_KEY_SIZE] =
> +      {
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
> +      };
> +    uint8_t iv[CHACHA20_IV_SIZE] =
> +      {
> +       0x1, 0x0, 0x0, 0x0,  /* Block counter is a LE uint32_t  */
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2
> +      };
> +    const uint8_t plaintext[] =
> +      {
> +       0x41, 0x6e, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x73, 0x73,
> +       0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20,
> +       0x49, 0x45, 0x54, 0x46, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x64,
> +       0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43,
> +       0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x20,
> +       0x66, 0x6f, 0x72, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61,
> +       0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x73, 0x20, 0x61, 0x6c, 0x6c,
> +       0x20, 0x6f, 0x72, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66,
> +       0x20, 0x61, 0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, 0x20, 0x49, 0x6e,
> +       0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2d, 0x44, 0x72, 0x61, 0x66,
> +       0x74, 0x20, 0x6f, 0x72, 0x20, 0x52, 0x46, 0x43, 0x20, 0x61, 0x6e,
> +       0x64, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65,
> +       0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x77,
> +       0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63,
> +       0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x61,
> +       0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, 0x20, 0x61, 0x63, 0x74, 0x69,
> +       0x76, 0x69, 0x74, 0x79, 0x20, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6e,
> +       0x73, 0x69, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x20,
> +       0x22, 0x49, 0x45, 0x54, 0x46, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72,
> +       0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2e, 0x20, 0x53,
> +       0x75, 0x63, 0x68, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65,
> +       0x6e, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65,
> +       0x20, 0x6f, 0x72, 0x61, 0x6c, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65,
> +       0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x49, 0x45,
> +       0x54, 0x46, 0x20, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
> +       0x2c, 0x20, 0x61, 0x73, 0x20, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61,
> +       0x73, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x61,
> +       0x6e, 0x64, 0x20, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x6f, 0x6e,
> +       0x69, 0x63, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63,
> +       0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x61, 0x64, 0x65,
> +       0x20, 0x61, 0x74, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x74, 0x69, 0x6d,
> +       0x65, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x2c,
> +       0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, 0x72, 0x65, 0x20,
> +       0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x20, 0x74,
> +       0x6f,
> +      };
> +    uint8_t ciphertext[sizeof plaintext];
> +
> +    chacha20_init (&state, key, iv);
> +    chacha20_crypt (&state, ciphertext, plaintext, sizeof plaintext);
> +
> +    const uint8_t expected[] =
> +      {
> +       0xa3, 0xfb, 0xf0, 0x7d, 0xf3, 0xfa, 0x2f, 0xde, 0x4f, 0x37, 0x6c,
> +       0xa2, 0x3e, 0x82, 0x73, 0x70, 0x41, 0x60, 0x5d, 0x9f, 0x4f, 0x4f,
> +       0x57, 0xbd, 0x8c, 0xff, 0x2c, 0x1d, 0x4b, 0x79, 0x55, 0xec, 0x2a,
> +       0x97, 0x94, 0x8b, 0xd3, 0x72, 0x29, 0x15, 0xc8, 0xf3, 0xd3, 0x37,
> +       0xf7, 0xd3, 0x70, 0x05, 0x0e, 0x9e, 0x96, 0xd6, 0x47, 0xb7, 0xc3,
> +       0x9f, 0x56, 0xe0, 0x31, 0xca, 0x5e, 0xb6, 0x25, 0x0d, 0x40, 0x42,
> +       0xe0, 0x27, 0x85, 0xec, 0xec, 0xfa, 0x4b, 0x4b, 0xb5, 0xe8, 0xea,
> +       0xd0, 0x44, 0x0e, 0x20, 0xb6, 0xe8, 0xdb, 0x09, 0xd8, 0x81, 0xa7,
> +       0xc6, 0x13, 0x2f, 0x42, 0x0e, 0x52, 0x79, 0x50, 0x42, 0xbd, 0xfa,
> +       0x77, 0x73, 0xd8, 0xa9, 0x05, 0x14, 0x47, 0xb3, 0x29, 0x1c, 0xe1,
> +       0x41, 0x1c, 0x68, 0x04, 0x65, 0x55, 0x2a, 0xa6, 0xc4, 0x05, 0xb7,
> +       0x76, 0x4d, 0x5e, 0x87, 0xbe, 0xa8, 0x5a, 0xd0, 0x0f, 0x84, 0x49,
> +       0xed, 0x8f, 0x72, 0xd0, 0xd6, 0x62, 0xab, 0x05, 0x26, 0x91, 0xca,
> +       0x66, 0x42, 0x4b, 0xc8, 0x6d, 0x2d, 0xf8, 0x0e, 0xa4, 0x1f, 0x43,
> +       0xab, 0xf9, 0x37, 0xd3, 0x25, 0x9d, 0xc4, 0xb2, 0xd0, 0xdf, 0xb4,
> +       0x8a, 0x6c, 0x91, 0x39, 0xdd, 0xd7, 0xf7, 0x69, 0x66, 0xe9, 0x28,
> +       0xe6, 0x35, 0x55, 0x3b, 0xa7, 0x6c, 0x5c, 0x87, 0x9d, 0x7b, 0x35,
> +       0xd4, 0x9e, 0xb2, 0xe6, 0x2b, 0x08, 0x71, 0xcd, 0xac, 0x63, 0x89,
> +       0x39, 0xe2, 0x5e, 0x8a, 0x1e, 0x0e, 0xf9, 0xd5, 0x28, 0x0f, 0xa8,
> +       0xca, 0x32, 0x8b, 0x35, 0x1c, 0x3c, 0x76, 0x59, 0x89, 0xcb, 0xcf,
> +       0x3d, 0xaa, 0x8b, 0x6c, 0xcc, 0x3a, 0xaf, 0x9f, 0x39, 0x79, 0xc9,
> +       0x2b, 0x37, 0x20, 0xfc, 0x88, 0xdc, 0x95, 0xed, 0x84, 0xa1, 0xbe,
> +       0x05, 0x9c, 0x64, 0x99, 0xb9, 0xfd, 0xa2, 0x36, 0xe7, 0xe8, 0x18,
> +       0xb0, 0x4b, 0x0b, 0xc3, 0x9c, 0x1e, 0x87, 0x6b, 0x19, 0x3b, 0xfe,
> +       0x55, 0x69, 0x75, 0x3f, 0x88, 0x12, 0x8c, 0xc0, 0x8a, 0xaa, 0x9b,
> +       0x63, 0xd1, 0xa1, 0x6f, 0x80, 0xef, 0x25, 0x54, 0xd7, 0x18, 0x9c,
> +       0x41, 0x1f, 0x58, 0x69, 0xca, 0x52, 0xc5, 0xb8, 0x3f, 0xa3, 0x6f,
> +       0xf2, 0x16, 0xb9, 0xc1, 0xd3, 0x00, 0x62, 0xbe, 0xbc, 0xfd, 0x2d,
> +       0xc5, 0xbc, 0xe0, 0x91, 0x19, 0x34, 0xfd, 0xa7, 0x9a, 0x86, 0xf6,
> +       0xe6, 0x98, 0xce, 0xd7, 0x59, 0xc3, 0xff, 0x9b, 0x64, 0x77, 0x33,
> +       0x8f, 0x3d, 0xa4, 0xf9, 0xcd, 0x85, 0x14, 0xea, 0x99, 0x82, 0xcc,
> +       0xaf, 0xb3, 0x41, 0xb2, 0x38, 0x4d, 0xd9, 0x02, 0xf3, 0xd1, 0xab,
> +       0x7a, 0xc6, 0x1d, 0xd2, 0x9c, 0x6f, 0x21, 0xba, 0x5b, 0x86, 0x2f,
> +       0x37, 0x30, 0xe3, 0x7c, 0xfd, 0xc4, 0xfd, 0x80, 0x6c, 0x22, 0xf2,
> +       0x21,
> +      };
> +    TEST_COMPARE_BLOB (ciphertext, sizeof ciphertext,
> +                       expected, sizeof expected);
> +    }
> +
> +  /* Test vector #3.  */
> +  {
> +    struct chacha20_state state;
> +
> +    uint8_t key[CHACHA20_KEY_SIZE] =
> +      {
> +       0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a,
> +       0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0,
> +       0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09,
> +       0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0
> +      };
> +    uint8_t iv[CHACHA20_IV_SIZE] =
> +      {
> +       0x2a, 0x0, 0x0, 0x0,  /* Block counter is a LE uint32_t  */
> +       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2
> +      };
> +
> +    uint8_t plaintext[] =
> +      {
> +       0x27, 0x54, 0x77, 0x61, 0x73, 0x20, 0x62, 0x72, 0x69, 0x6c, 0x6c,
> +       0x69, 0x67, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65,
> +       0x20, 0x73, 0x6c, 0x69, 0x74, 0x68, 0x79, 0x20, 0x74, 0x6f, 0x76,
> +       0x65, 0x73, 0x0a, 0x44, 0x69, 0x64, 0x20, 0x67, 0x79, 0x72, 0x65,
> +       0x20, 0x61, 0x6e, 0x64, 0x20, 0x67, 0x69, 0x6d, 0x62, 0x6c, 0x65,
> +       0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x61, 0x62,
> +       0x65, 0x3a, 0x0a, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x69, 0x6d, 0x73,
> +       0x79, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20,
> +       0x62, 0x6f, 0x72, 0x6f, 0x67, 0x6f, 0x76, 0x65, 0x73, 0x2c, 0x0a,
> +       0x41, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x6f, 0x6d,
> +       0x65, 0x20, 0x72, 0x61, 0x74, 0x68, 0x73, 0x20, 0x6f, 0x75, 0x74,
> +       0x67, 0x72, 0x61, 0x62, 0x65, 0x2e,
> +      };
> +    uint8_t ciphertext[sizeof plaintext];
> +
> +    chacha20_init (&state, key, iv);
> +    chacha20_crypt (&state, ciphertext, plaintext, sizeof plaintext);
> +
> +    const uint8_t expected[] =
> +      {
> +       0x62, 0xe6, 0x34, 0x7f, 0x95, 0xed, 0x87, 0xa4, 0x5f, 0xfa, 0xe7,
> +       0x42, 0x6f, 0x27, 0xa1, 0xdf, 0x5f, 0xb6, 0x91, 0x10, 0x04, 0x4c,
> +       0x0d, 0x73, 0x11, 0x8e, 0xff, 0xa9, 0x5b, 0x01, 0xe5, 0xcf, 0x16,
> +       0x6d, 0x3d, 0xf2, 0xd7, 0x21, 0xca, 0xf9, 0xb2, 0x1e, 0x5f, 0xb1,
> +       0x4c, 0x61, 0x68, 0x71, 0xfd, 0x84, 0xc5, 0x4f, 0x9d, 0x65, 0xb2,
> +       0x83, 0x19, 0x6c, 0x7f, 0xe4, 0xf6, 0x05, 0x53, 0xeb, 0xf3, 0x9c,
> +       0x64, 0x02, 0xc4, 0x22, 0x34, 0xe3, 0x2a, 0x35, 0x6b, 0x3e, 0x76,
> +       0x43, 0x12, 0xa6, 0x1a, 0x55, 0x32, 0x05, 0x57, 0x16, 0xea, 0xd6,
> +       0x96, 0x25, 0x68, 0xf8, 0x7d, 0x3f, 0x3f, 0x77, 0x04, 0xc6, 0xa8,
> +       0xd1, 0xbc, 0xd1, 0xbf, 0x4d, 0x50, 0xd6, 0x15, 0x4b, 0x6d, 0xa7,
> +       0x31, 0xb1, 0x87, 0xb5, 0x8d, 0xfd, 0x72, 0x8a, 0xfa, 0x36, 0x75,
> +       0x7a, 0x79, 0x7a, 0xc1, 0x88, 0xd1,
> +      };
> +
> +    TEST_COMPARE_BLOB (ciphertext, sizeof ciphertext,
> +                       expected, sizeof expected);
> +  }
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/stdlib/tst-arc4random-fork.c b/stdlib/tst-arc4random-fork.c
> new file mode 100644
> index 0000000000..cd8852c8d3
> --- /dev/null
> +++ b/stdlib/tst-arc4random-fork.c
> @@ -0,0 +1,174 @@
> +/* Test that subprocesses generate distinct streams of randomness.
> +   Copyright (C) 2022 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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +/* Collect random data from subprocesses and check that all the
> +   results are unique.  */
> +
> +#include <array_length.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/xthread.h>
> +#include <support/xunistd.h>
> +#include <unistd.h>
> +
> +/* Perform multiple runs.  The subsequent runs start with an
> +   already-initialized random number generator.  (The number 1500 was
> +   seen to reproduce failures reliable in case of a race condition in
> +   the fork detection code.)  */
> +enum { runs = 1500 };
> +
> +/* One hundred processes in total.  This should be high enough to
> +   expose any issues, but low enough not to tax the overall system too
> +   much.  */
> +enum { subprocesses = 49 };
> +
> +/* The total number of processes.  */
> +enum { processes = subprocesses + 1 };
> +
> +/* Number of bytes of randomness to generate per process.  Large
> +   enough to make false positive duplicates extremely unlikely.  */
> +enum { random_size = 16 };
> +
> +/* Generated bytes of randomness.  */
> +struct result
> +{
> +  unsigned char bytes[random_size];
> +};
> +
> +/* Shared across all processes.  */
> +static struct shared_data
> +{
> +  pthread_barrier_t barrier;
> +  struct result results[runs][processes];
> +} *shared_data;
> +
> +/* Invoked to collect data from a subprocess.  */
> +static void
> +subprocess (int run, int process_index)
> +{
> +  xpthread_barrier_wait (&shared_data->barrier);
> +  arc4random_buf (shared_data->results[run][process_index].bytes, random_size);
> +}
> +
> +/* Used to sort the results.  */
> +struct index
> +{
> +  int run;
> +  int process_index;
> +};
> +
> +/* Used to sort an array of struct index values.  */
> +static int
> +index_compare (const void *left1, const void *right1)
> +{
> +  const struct index *left = left1;
> +  const struct index *right = right1;
> +
> +  return memcmp (shared_data->results[left->run][left->process_index].bytes,
> +                 shared_data->results[right->run][right->process_index].bytes,
> +                 random_size);
> +}
> +
> +static int
> +do_test (void)
> +{
> +  shared_data = support_shared_allocate (sizeof (*shared_data));
> +  {
> +    pthread_barrierattr_t attr;
> +    xpthread_barrierattr_init (&attr);
> +    xpthread_barrierattr_setpshared (&attr, PTHREAD_PROCESS_SHARED);
> +    xpthread_barrier_init (&shared_data->barrier, &attr, processes);
> +    xpthread_barrierattr_destroy (&attr);
> +  }
> +
> +  /* Collect random data.  */
> +  for (int run = 0; run < runs; ++run)
> +    {
> +#if 0
> +      if (run == runs / 2)
> +        {
> +          /* In the middle, desynchronize the block cache by consuming
> +             an odd number of bytes.  */
> +          char buf;
> +          arc4random_buf (&buf, 1);
> +        }
> +#endif
> +
> +      pid_t pids[subprocesses];
> +      for (int process_index = 0; process_index < subprocesses;
> +           ++process_index)
> +        {
> +          pids[process_index] = xfork ();
> +          if (pids[process_index] == 0)
> +            {
> +              subprocess (run, process_index);
> +              _exit (0);
> +            }
> +        }
> +
> +      /* Trigger all subprocesses.  Also add data from the parent
> +         process.  */
> +      subprocess (run, subprocesses);
> +
> +      for (int process_index = 0; process_index < subprocesses;
> +           ++process_index)
> +        {
> +          int status;
> +          xwaitpid (pids[process_index], &status, 0);
> +          if (status != 0)
> +            FAIL_EXIT1 ("subprocess index %d (PID %d) exit status %d\n",
> +                        process_index, (int) pids[process_index], status);
> +        }
> +    }
> +
> +  /* Check for duplicates.  */
> +  struct index indexes[runs * processes];
> +  for (int run = 0; run < runs; ++run)
> +    for (int process_index = 0; process_index < processes; ++process_index)
> +      indexes[run * processes + process_index]
> +        = (struct index) { .run = run, .process_index = process_index };
> +  qsort (indexes, array_length (indexes), sizeof (indexes[0]), index_compare);
> +  for (size_t i = 1; i < array_length (indexes); ++i)
> +    {
> +      if (index_compare (indexes + i - 1, indexes + i) == 0)
> +        {
> +          support_record_failure ();
> +          unsigned char *bytes
> +            = shared_data->results[indexes[i].run]
> +                [indexes[i].process_index].bytes;
> +          char *quoted = support_quote_blob (bytes, random_size);
> +          printf ("error: duplicate randomness data: \"%s\"\n"
> +                  "  run %d, subprocess %d\n"
> +                  "  run %d, subprocess %d\n",
> +                  quoted, indexes[i - 1].run, indexes[i - 1].process_index,
> +                  indexes[i].run, indexes[i].process_index);
> +          free (quoted);
> +        }
> +    }
> +
> +  xpthread_barrier_destroy (&shared_data->barrier);
> +  support_shared_free (shared_data);
> +  shared_data = NULL;
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/stdlib/tst-arc4random-stats.c b/stdlib/tst-arc4random-stats.c
> new file mode 100644
> index 0000000000..9747180c99
> --- /dev/null
> +++ b/stdlib/tst-arc4random-stats.c
> @@ -0,0 +1,146 @@
> +/* Statistical tests for arc4random-related functions.
> +   Copyright (C) 2022 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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <array_length.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <support/check.h>
> +
> +enum
> +{
> +  arc4random_key_size = 32
> +};
> +
> +struct key
> +{
> +  unsigned char data[arc4random_key_size];
> +};
> +
> +/* With 12,000 keys, the probability that a byte in a predetermined
> +   position does not have a predetermined value in all generated keys
> +   is about 4e-21.  The probability that this happens with any of the
> +   16 * 256 possible byte position/values is 1.6e-17.  This results in
> +   an acceptably low false-positive rate.  */
> +enum { key_count = 12000 };
> +
> +static struct key keys[key_count];
> +
> +/* Used to perform the distribution check.  */
> +static int byte_counts[arc4random_key_size][256];
> +
> +/* Bail out after this many failures.  */
> +enum { failure_limit = 100 };
> +
> +static void
> +find_stuck_bytes (bool (*func) (unsigned char *key))
> +{
> +  memset (&keys, 0xcc, sizeof (keys));
> +
> +  int failures = 0;
> +  for (int key = 0; key < key_count; ++key)
> +    {
> +      while (true)
> +        {
> +          if (func (keys[key].data))
> +            break;
> +          ++failures;
> +          if (failures >= failure_limit)
> +            {
> +              printf ("warning: bailing out after %d failures\n", failures);
> +              return;
> +            }
> +        }
> +    }
> +  printf ("info: key generation finished with %d failures\n", failures);
> +
> +  memset (&byte_counts, 0, sizeof (byte_counts));
> +  for (int key = 0; key < key_count; ++key)
> +    for (int pos = 0; pos < arc4random_key_size; ++pos)
> +      ++byte_counts[pos][keys[key].data[pos]];
> +
> +  for (int pos = 0; pos < arc4random_key_size; ++pos)
> +    for (int byte = 0; byte < 256; ++byte)
> +      if (byte_counts[pos][byte] == 0)
> +        {
> +          support_record_failure ();
> +          printf ("error: byte %d never appeared at position %d\n", byte, pos);
> +        }
> +}
> +
> +/* Test adapter for arc4random.  */
> +static bool
> +generate_arc4random (unsigned char *key)
> +{
> +  uint32_t words[arc4random_key_size / 4];
> +  _Static_assert (sizeof (words) == arc4random_key_size, "sizeof (words)");
> +
> +  for (int i = 0; i < array_length (words); ++i)
> +    words[i] = arc4random ();
> +  memcpy (key, &words, arc4random_key_size);
> +  return true;
> +}
> +
> +/* Test adapter for arc4random_buf.  */
> +static bool
> +generate_arc4random_buf (unsigned char *key)
> +{
> +  arc4random_buf (key, arc4random_key_size);
> +  return true;
> +}
> +
> +/* Test adapter for arc4random_uniform.  */
> +static bool
> +generate_arc4random_uniform (unsigned char *key)
> +{
> +  for (int i = 0; i < arc4random_key_size; ++i)
> +    key[i] = arc4random_uniform (256);
> +  return true;
> +}
> +
> +/* Test adapter for arc4random_uniform with argument 257.  This means
> +   that byte 0 happens more often, but we do not perform such a
> +   statistcal check, so the test will still pass */
> +static bool
> +generate_arc4random_uniform_257 (unsigned char *key)
> +{
> +  for (int i = 0; i < arc4random_key_size; ++i)
> +    key[i] = arc4random_uniform (257);
> +  return true;
> +}
> +
> +static int
> +do_test (void)
> +{
> +  puts ("info: arc4random implementation test");
> +  find_stuck_bytes (generate_arc4random);
> +
> +  puts ("info: arc4random_buf implementation test");
> +  find_stuck_bytes (generate_arc4random_buf);
> +
> +  puts ("info: arc4random_uniform implementation test");
> +  find_stuck_bytes (generate_arc4random_uniform);
> +
> +  puts ("info: arc4random_uniform implementation test (257 variant)");
> +  find_stuck_bytes (generate_arc4random_uniform_257);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/stdlib/tst-arc4random-thread.c b/stdlib/tst-arc4random-thread.c
> new file mode 100644
> index 0000000000..b122eaa826
> --- /dev/null
> +++ b/stdlib/tst-arc4random-thread.c
> @@ -0,0 +1,278 @@
> +/* Test that threads generate distinct streams of randomness.
> +   Copyright (C) 2018 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
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <array_length.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +#include <support/namespace.h>
> +#include <support/support.h>
> +#include <support/xthread.h>
> +
> +/* Number of arc4random_buf calls per thread.  */
> +enum { count_per_thread = 5000 };
> +
> +/* Number of threads computing randomness.  */
> +enum { inner_threads = 5 };
> +
> +/* Number of threads launching other threads.  Chosen as to not to
> +   overload the system.  */
> +enum { outer_threads = 7 };
> +
> +/* Number of launching rounds performed by the outer threads.  */
> +enum { outer_rounds = 10 };
> +
> +/* Maximum number of bytes generated in an arc4random call.  */
> +enum { max_size = 32 };
> +
> +/* Sizes generated by threads.  Must be long enough to be unique with
> +   high probability.  */
> +static const int sizes[] = { 12, 15, 16, 17, 24, 31, max_size };
> +
> +/* Data structure to capture randomness results.  */
> +struct blob
> +{
> +  unsigned int size;
> +  int thread_id;
> +  unsigned int index;
> +  unsigned char bytes[max_size];
> +};
> +
> +#define DYNARRAY_STRUCT dynarray_blob
> +#define DYNARRAY_ELEMENT struct blob
> +#define DYNARRAY_PREFIX dynarray_blob_
> +#include <malloc/dynarray-skeleton.c>
> +
> +/* Sort blob elements by length first, then by comparing the data
> +   member.  */
> +static int
> +compare_blob (const void *left1, const void *right1)
> +{
> +  const struct blob *left = left1;
> +  const struct blob *right = right1;
> +
> +  if (left->size != right->size)
> +    /* No overflow due to limited range.  */
> +    return left->size - right->size;
> +  return memcmp (left->bytes, right->bytes, left->size);
> +}
> +
> +/* Used to store the global result.  */
> +static pthread_mutex_t global_result_lock = PTHREAD_MUTEX_INITIALIZER;
> +static struct dynarray_blob global_result;
> +
> +/* Copy data to the global result, with locking.  */
> +static void
> +copy_result_to_global (struct dynarray_blob *result)
> +{
> +  xpthread_mutex_lock (&global_result_lock);
> +  size_t old_size = dynarray_blob_size (&global_result);
> +  TEST_VERIFY_EXIT
> +    (dynarray_blob_resize (&global_result,
> +                           old_size + dynarray_blob_size (result)));
> +  memcpy (dynarray_blob_begin (&global_result) + old_size,
> +          dynarray_blob_begin (result),
> +          dynarray_blob_size (result) * sizeof (struct blob));
> +  xpthread_mutex_unlock (&global_result_lock);
> +}
> +
> +/* Used to assign unique thread IDs.  Accessed atomically.  */
> +static int next_thread_id;
> +
> +static void *
> +inner_thread (void *unused)
> +{
> +  /* Use local result to avoid global lock contention while generating
> +     randomness.  */
> +  struct dynarray_blob result;
> +  dynarray_blob_init (&result);
> +
> +  int thread_id = __atomic_fetch_add (&next_thread_id, 1, __ATOMIC_RELAXED);
> +
> +  /* Determine the sizes to be used by this thread.  */
> +  int size_slot = thread_id % (array_length (sizes) + 1);
> +  bool switch_sizes = size_slot == array_length (sizes);
> +  if (switch_sizes)
> +    size_slot = 0;
> +
> +  /* Compute the random blobs.  */
> +  for (int i = 0; i < count_per_thread; ++i)
> +    {
> +      struct blob *place = dynarray_blob_emplace (&result);
> +      TEST_VERIFY_EXIT (place != NULL);
> +      place->size = sizes[size_slot];
> +      place->thread_id = thread_id;
> +      place->index = i;
> +      arc4random_buf (place->bytes, place->size);
> +
> +      if (switch_sizes)
> +        size_slot = (size_slot + 1) % array_length (sizes);
> +    }
> +
> +  /* Store the blobs in the global result structure.  */
> +  copy_result_to_global (&result);
> +
> +  dynarray_blob_free (&result);
> +
> +  return NULL;
> +}
> +
> +/* Launch the inner threads and wait for their termination.  */
> +static void *
> +outer_thread (void *unused)
> +{
> +  for (int round = 0; round < outer_rounds; ++round)
> +    {
> +      pthread_t threads[inner_threads];
> +
> +      for (int i = 0; i < inner_threads; ++i)
> +        threads[i] = xpthread_create (NULL, inner_thread, NULL);
> +
> +      for (int i = 0; i < inner_threads; ++i)
> +        xpthread_join (threads[i]);
> +    }
> +
> +  return NULL;
> +}
> +
> +static bool termination_requested;
> +
> +/* Call arc4random_buf to fill one blob with 16 bytes.  */
> +static void *
> +get_one_blob_thread (void *closure)
> +{
> +  struct blob *result = closure;
> +  result->size = 16;
> +  arc4random_buf (result->bytes, result->size);
> +  return NULL;
> +}
> +
> +/* Invoked from fork_thread to actually obtain randomness data.  */
> +static void
> +fork_thread_subprocess (void *closure)
> +{
> +  struct blob *shared_result = closure;
> +
> +  pthread_t thr1 = xpthread_create
> +    (NULL, get_one_blob_thread, shared_result + 1);
> +  pthread_t thr2 = xpthread_create
> +    (NULL, get_one_blob_thread, shared_result + 2);
> +  get_one_blob_thread (shared_result);
> +  xpthread_join (thr1);
> +  xpthread_join (thr2);
> +}
> +
> +/* Continuously fork subprocesses to obtain a little bit of
> +   randomness.  */
> +static void *
> +fork_thread (void *unused)
> +{
> +  struct dynarray_blob result;
> +  dynarray_blob_init (&result);
> +
> +  /* Three blobs from each subprocess.  */
> +  struct blob *shared_result
> +    = support_shared_allocate (3 * sizeof (*shared_result));
> +
> +  while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED))
> +    {
> +      /* Obtain the results from a subprocess.  */
> +      support_isolate_in_subprocess (fork_thread_subprocess, shared_result);
> +
> +      for (int i = 0; i < 3; ++i)
> +        {
> +          struct blob *place = dynarray_blob_emplace (&result);
> +          TEST_VERIFY_EXIT (place != NULL);
> +          place->size = shared_result[i].size;
> +          place->thread_id = -1;
> +          place->index = i;
> +          memcpy (place->bytes, shared_result[i].bytes, place->size);
> +        }
> +    }
> +
> +  support_shared_free (shared_result);
> +
> +  copy_result_to_global (&result);
> +  dynarray_blob_free (&result);
> +
> +  return NULL;
> +}
> +
> +/* Launch the outer threads and wait for their termination.  */
> +static void
> +run_outer_threads (void)
> +{
> +  /* Special thread that continuously calls fork.  */
> +  pthread_t fork_thread_id = xpthread_create (NULL, fork_thread, NULL);
> +
> +  pthread_t threads[outer_threads];
> +  for (int i = 0; i < outer_threads; ++i)
> +    threads[i] = xpthread_create (NULL, outer_thread, NULL);
> +
> +  for (int i = 0; i < outer_threads; ++i)
> +    xpthread_join (threads[i]);
> +
> +  __atomic_store_n (&termination_requested, true, __ATOMIC_RELAXED);
> +  xpthread_join (fork_thread_id);
> +}
> +
> +static int
> +do_test (void)
> +{
> +  dynarray_blob_init (&global_result);
> +  int expected_blobs
> +    = count_per_thread * inner_threads * outer_threads * outer_rounds;
> +  printf ("info: minimum of %d blob results expected\n", expected_blobs);
> +
> +  run_outer_threads ();
> +
> +  /* The forking thread delivers a non-deterministic number of
> +     results, which is why expected_blobs is only a minimun number of
> +     results.  */
> +  printf ("info: %zu blob results observed\n",
> +          dynarray_blob_size (&global_result));
> +  TEST_VERIFY (dynarray_blob_size (&global_result) >= expected_blobs);
> +
> +  /* Verify that there are no duplicates.  */
> +  qsort (dynarray_blob_begin (&global_result),
> +         dynarray_blob_size (&global_result),
> +         sizeof (struct blob), compare_blob);
> +  struct blob *end = dynarray_blob_end (&global_result);
> +  for (struct blob *p = dynarray_blob_begin (&global_result) + 1;
> +       p < end; ++p)
> +    {
> +      if (compare_blob (p - 1, p) == 0)
> +        {
> +          support_record_failure ();
> +          char *quoted = support_quote_blob (p->bytes, p->size);
> +          printf ("error: duplicate blob: \"%s\" (%d bytes)\n",
> +                  quoted, (int) p->size);
> +          printf ("  first source: thread %d, index %u\n",
> +                  p[-1].thread_id, p[-1].index);
> +          printf ("  second source: thread %d, index %u\n",
> +                  p[0].thread_id, p[0].index);
> +          free (quoted);
> +        }
> +    }
> +
> +  dynarray_blob_free (&global_result);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> --
> 2.32.0
>
diff mbox series

Patch

diff --git a/stdlib/Makefile b/stdlib/Makefile
index 9f9cc1bd7f..4862d008ab 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -183,6 +183,9 @@  tests := \
   testmb2 \
   testrand \
   testsort \
+  tst-arc4random-fork \
+  tst-arc4random-stats \
+  tst-arc4random-thread \
   tst-at_quick_exit \
   tst-atexit \
   tst-atof1 \
@@ -252,6 +255,7 @@  tests-internal := \
   # tests-internal
 
 tests-static := \
+  tst-arc4random-chacha20 \
   tst-secure-getenv \
   # tests-static
 
diff --git a/stdlib/tst-arc4random-chacha20.c b/stdlib/tst-arc4random-chacha20.c
new file mode 100644
index 0000000000..c5876d3f3b
--- /dev/null
+++ b/stdlib/tst-arc4random-chacha20.c
@@ -0,0 +1,225 @@ 
+/* Basic tests for chacha20 cypher used in arc4random.
+   Copyright (C) 2022 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <chacha20.c>
+#include <support/check.h>
+
+static int
+do_test (void)
+{
+  /* Reference ChaCha20 encryption test vectors from RFC8439.  */
+
+  /* Test vector #1.  */
+  {
+    struct chacha20_state state;
+
+    uint8_t key[CHACHA20_KEY_SIZE] =
+      {
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+      };
+    uint8_t iv[CHACHA20_IV_SIZE] =
+      {
+	0x0, 0x0, 0x0, 0x0,
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+      };
+    const uint8_t plaintext[CHACHA20_BLOCK_SIZE] = { 0 };
+    uint8_t ciphertext[CHACHA20_BLOCK_SIZE];
+				     
+    chacha20_init (&state, key, iv);
+    chacha20_crypt (&state, ciphertext, plaintext, sizeof plaintext);
+
+    const uint8_t expected[] =
+      {
+	0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90,
+	0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28,
+	0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a,
+	0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7,
+	0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d,
+	0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37,
+	0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
+	0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86
+      };
+    TEST_COMPARE_BLOB (ciphertext, sizeof ciphertext,
+			expected, sizeof expected);
+  }
+
+  /* Test vector #2.  */
+  {
+    struct chacha20_state state;
+
+    uint8_t key[CHACHA20_KEY_SIZE] =
+      {
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+      };
+    uint8_t iv[CHACHA20_IV_SIZE] =
+      {
+	0x1, 0x0, 0x0, 0x0,  /* Block counter is a LE uint32_t  */
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2
+      };
+    const uint8_t plaintext[] =
+      {
+	0x41, 0x6e, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x73, 0x73,
+	0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20,
+	0x49, 0x45, 0x54, 0x46, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x64,
+	0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43,
+	0x6f, 0x6e, 0x74, 0x72,	0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x20,
+	0x66, 0x6f, 0x72, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x73, 0x20, 0x61, 0x6c, 0x6c,
+	0x20, 0x6f, 0x72, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66,
+	0x20, 0x61, 0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, 0x20, 0x49, 0x6e,
+	0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2d, 0x44, 0x72, 0x61, 0x66,
+	0x74, 0x20, 0x6f, 0x72, 0x20, 0x52, 0x46, 0x43, 0x20, 0x61, 0x6e,
+	0x64, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65,
+	0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x77,
+	0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63,
+	0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x61,
+	0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, 0x20, 0x61, 0x63, 0x74, 0x69,
+	0x76, 0x69, 0x74, 0x79, 0x20, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6e,
+	0x73, 0x69, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x20,
+	0x22, 0x49, 0x45, 0x54, 0x46, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72,
+	0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2e, 0x20, 0x53,
+	0x75, 0x63, 0x68, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65,
+	0x6e, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65,
+	0x20, 0x6f, 0x72, 0x61, 0x6c, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65,
+	0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x49, 0x45,
+	0x54, 0x46, 0x20, 0x73, 0x65, 0x73, 0x73, 0x69,	0x6f, 0x6e, 0x73,
+	0x2c, 0x20, 0x61, 0x73, 0x20, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61,
+	0x73, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x61,
+	0x6e, 0x64, 0x20, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x6f, 0x6e,
+	0x69, 0x63, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x61, 0x64, 0x65,
+	0x20, 0x61, 0x74, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x74, 0x69, 0x6d,
+	0x65, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x2c,
+	0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, 0x72, 0x65, 0x20,
+	0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x20, 0x74,
+	0x6f,
+      };
+    uint8_t ciphertext[sizeof plaintext];
+
+    chacha20_init (&state, key, iv);
+    chacha20_crypt (&state, ciphertext, plaintext, sizeof plaintext);
+
+    const uint8_t expected[] =
+      {
+	0xa3, 0xfb, 0xf0, 0x7d, 0xf3, 0xfa, 0x2f, 0xde, 0x4f, 0x37, 0x6c,
+	0xa2, 0x3e, 0x82, 0x73, 0x70, 0x41, 0x60, 0x5d, 0x9f, 0x4f, 0x4f,
+	0x57, 0xbd, 0x8c, 0xff, 0x2c, 0x1d, 0x4b, 0x79, 0x55, 0xec, 0x2a,
+	0x97, 0x94, 0x8b, 0xd3, 0x72, 0x29, 0x15, 0xc8, 0xf3, 0xd3, 0x37,
+	0xf7, 0xd3, 0x70, 0x05,	0x0e, 0x9e, 0x96, 0xd6, 0x47, 0xb7, 0xc3,
+	0x9f, 0x56, 0xe0, 0x31, 0xca, 0x5e, 0xb6, 0x25, 0x0d, 0x40, 0x42,
+	0xe0, 0x27, 0x85, 0xec, 0xec, 0xfa, 0x4b, 0x4b, 0xb5, 0xe8, 0xea,
+	0xd0, 0x44, 0x0e, 0x20, 0xb6, 0xe8, 0xdb, 0x09, 0xd8, 0x81, 0xa7,
+	0xc6, 0x13, 0x2f, 0x42, 0x0e, 0x52, 0x79, 0x50,	0x42, 0xbd, 0xfa,
+	0x77, 0x73, 0xd8, 0xa9, 0x05, 0x14, 0x47, 0xb3, 0x29, 0x1c, 0xe1,
+	0x41, 0x1c, 0x68, 0x04, 0x65, 0x55, 0x2a, 0xa6, 0xc4, 0x05, 0xb7,
+	0x76, 0x4d, 0x5e, 0x87, 0xbe, 0xa8, 0x5a, 0xd0, 0x0f, 0x84, 0x49,
+	0xed, 0x8f, 0x72, 0xd0, 0xd6, 0x62, 0xab, 0x05, 0x26, 0x91, 0xca,
+	0x66, 0x42, 0x4b, 0xc8, 0x6d, 0x2d, 0xf8, 0x0e, 0xa4, 0x1f, 0x43,
+	0xab, 0xf9, 0x37, 0xd3, 0x25, 0x9d, 0xc4, 0xb2, 0xd0, 0xdf, 0xb4,
+	0x8a, 0x6c, 0x91, 0x39, 0xdd, 0xd7, 0xf7, 0x69, 0x66, 0xe9, 0x28,
+	0xe6, 0x35, 0x55, 0x3b, 0xa7, 0x6c, 0x5c, 0x87, 0x9d, 0x7b, 0x35,
+	0xd4, 0x9e, 0xb2, 0xe6, 0x2b, 0x08, 0x71, 0xcd, 0xac, 0x63, 0x89,
+	0x39, 0xe2, 0x5e, 0x8a, 0x1e, 0x0e, 0xf9, 0xd5, 0x28, 0x0f, 0xa8,
+	0xca, 0x32, 0x8b, 0x35, 0x1c, 0x3c, 0x76, 0x59, 0x89, 0xcb, 0xcf,
+	0x3d, 0xaa, 0x8b, 0x6c,	0xcc, 0x3a, 0xaf, 0x9f, 0x39, 0x79, 0xc9,
+	0x2b, 0x37, 0x20, 0xfc, 0x88, 0xdc, 0x95, 0xed, 0x84, 0xa1, 0xbe,
+	0x05, 0x9c, 0x64, 0x99, 0xb9, 0xfd, 0xa2, 0x36, 0xe7, 0xe8, 0x18,
+	0xb0, 0x4b, 0x0b, 0xc3, 0x9c, 0x1e, 0x87, 0x6b, 0x19, 0x3b, 0xfe,
+	0x55, 0x69, 0x75, 0x3f, 0x88, 0x12, 0x8c, 0xc0,	0x8a, 0xaa, 0x9b,
+	0x63, 0xd1, 0xa1, 0x6f, 0x80, 0xef, 0x25, 0x54, 0xd7, 0x18, 0x9c,
+	0x41, 0x1f, 0x58, 0x69, 0xca, 0x52, 0xc5, 0xb8, 0x3f, 0xa3, 0x6f,
+	0xf2, 0x16, 0xb9, 0xc1, 0xd3, 0x00, 0x62, 0xbe, 0xbc, 0xfd, 0x2d,
+	0xc5, 0xbc, 0xe0, 0x91, 0x19, 0x34, 0xfd, 0xa7, 0x9a, 0x86, 0xf6,
+	0xe6, 0x98, 0xce, 0xd7, 0x59, 0xc3, 0xff, 0x9b, 0x64, 0x77, 0x33,
+	0x8f, 0x3d, 0xa4, 0xf9, 0xcd, 0x85, 0x14, 0xea, 0x99, 0x82, 0xcc,
+	0xaf, 0xb3, 0x41, 0xb2, 0x38, 0x4d, 0xd9, 0x02, 0xf3, 0xd1, 0xab,
+	0x7a, 0xc6, 0x1d, 0xd2, 0x9c, 0x6f, 0x21, 0xba, 0x5b, 0x86, 0x2f,
+	0x37, 0x30, 0xe3, 0x7c, 0xfd, 0xc4, 0xfd, 0x80, 0x6c, 0x22, 0xf2,
+	0x21,
+      };
+    TEST_COMPARE_BLOB (ciphertext, sizeof ciphertext,
+			expected, sizeof expected);
+    }
+
+  /* Test vector #3.  */
+  {
+    struct chacha20_state state;
+
+    uint8_t key[CHACHA20_KEY_SIZE] =
+      {
+	0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a,
+	0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, 0xf0,
+	0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09,
+	0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, 0x75, 0xc0
+      };
+    uint8_t iv[CHACHA20_IV_SIZE] =
+      {
+	0x2a, 0x0, 0x0, 0x0,  /* Block counter is a LE uint32_t  */
+	0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2
+      };
+
+    uint8_t plaintext[] =
+      {
+	0x27, 0x54, 0x77, 0x61, 0x73, 0x20, 0x62, 0x72, 0x69, 0x6c, 0x6c,
+	0x69, 0x67, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65,
+	0x20, 0x73, 0x6c, 0x69, 0x74, 0x68, 0x79, 0x20, 0x74, 0x6f, 0x76,
+	0x65, 0x73, 0x0a, 0x44, 0x69, 0x64, 0x20, 0x67, 0x79, 0x72, 0x65,
+	0x20, 0x61, 0x6e, 0x64, 0x20, 0x67, 0x69, 0x6d, 0x62, 0x6c, 0x65,
+	0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x61, 0x62,
+	0x65, 0x3a, 0x0a, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x69, 0x6d, 0x73,
+	0x79, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20,
+	0x62, 0x6f, 0x72, 0x6f, 0x67, 0x6f, 0x76, 0x65,	0x73, 0x2c, 0x0a,
+	0x41, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x6f, 0x6d,
+	0x65, 0x20, 0x72, 0x61, 0x74, 0x68, 0x73, 0x20, 0x6f, 0x75, 0x74,
+	0x67, 0x72, 0x61, 0x62, 0x65, 0x2e,
+      };
+    uint8_t ciphertext[sizeof plaintext];
+
+    chacha20_init (&state, key, iv);
+    chacha20_crypt (&state, ciphertext, plaintext, sizeof plaintext);
+
+    const uint8_t expected[] =
+      {
+	0x62, 0xe6, 0x34, 0x7f, 0x95, 0xed, 0x87, 0xa4, 0x5f, 0xfa, 0xe7,
+	0x42, 0x6f, 0x27, 0xa1, 0xdf, 0x5f, 0xb6, 0x91, 0x10, 0x04, 0x4c,
+	0x0d, 0x73, 0x11, 0x8e, 0xff, 0xa9, 0x5b, 0x01, 0xe5, 0xcf, 0x16,
+	0x6d, 0x3d, 0xf2, 0xd7, 0x21, 0xca, 0xf9, 0xb2, 0x1e, 0x5f, 0xb1,
+	0x4c, 0x61, 0x68, 0x71,	0xfd, 0x84, 0xc5, 0x4f, 0x9d, 0x65, 0xb2,
+	0x83, 0x19, 0x6c, 0x7f, 0xe4, 0xf6, 0x05, 0x53, 0xeb, 0xf3, 0x9c,
+	0x64, 0x02, 0xc4, 0x22, 0x34, 0xe3, 0x2a, 0x35, 0x6b, 0x3e, 0x76,
+	0x43, 0x12, 0xa6, 0x1a, 0x55, 0x32, 0x05, 0x57, 0x16, 0xea, 0xd6,
+	0x96, 0x25, 0x68, 0xf8, 0x7d, 0x3f, 0x3f, 0x77, 0x04, 0xc6, 0xa8,
+	0xd1, 0xbc, 0xd1, 0xbf, 0x4d, 0x50, 0xd6, 0x15, 0x4b, 0x6d, 0xa7,
+	0x31, 0xb1, 0x87, 0xb5, 0x8d, 0xfd, 0x72, 0x8a, 0xfa, 0x36, 0x75,
+	0x7a, 0x79, 0x7a, 0xc1, 0x88, 0xd1,
+      };
+
+    TEST_COMPARE_BLOB (ciphertext, sizeof ciphertext,
+			expected, sizeof expected);
+  }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdlib/tst-arc4random-fork.c b/stdlib/tst-arc4random-fork.c
new file mode 100644
index 0000000000..cd8852c8d3
--- /dev/null
+++ b/stdlib/tst-arc4random-fork.c
@@ -0,0 +1,174 @@ 
+/* Test that subprocesses generate distinct streams of randomness.
+   Copyright (C) 2022 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
+   <http://www.gnu.org/licenses/>.  */
+
+/* Collect random data from subprocesses and check that all the
+   results are unique.  */
+
+#include <array_length.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <unistd.h>
+
+/* Perform multiple runs.  The subsequent runs start with an
+   already-initialized random number generator.  (The number 1500 was
+   seen to reproduce failures reliable in case of a race condition in
+   the fork detection code.)  */
+enum { runs = 1500 };
+
+/* One hundred processes in total.  This should be high enough to
+   expose any issues, but low enough not to tax the overall system too
+   much.  */
+enum { subprocesses = 49 };
+
+/* The total number of processes.  */
+enum { processes = subprocesses + 1 };
+
+/* Number of bytes of randomness to generate per process.  Large
+   enough to make false positive duplicates extremely unlikely.  */
+enum { random_size = 16 };
+
+/* Generated bytes of randomness.  */
+struct result
+{
+  unsigned char bytes[random_size];
+};
+
+/* Shared across all processes.  */
+static struct shared_data
+{
+  pthread_barrier_t barrier;
+  struct result results[runs][processes];
+} *shared_data;
+
+/* Invoked to collect data from a subprocess.  */
+static void
+subprocess (int run, int process_index)
+{
+  xpthread_barrier_wait (&shared_data->barrier);
+  arc4random_buf (shared_data->results[run][process_index].bytes, random_size);
+}
+
+/* Used to sort the results.  */
+struct index
+{
+  int run;
+  int process_index;
+};
+
+/* Used to sort an array of struct index values.  */
+static int
+index_compare (const void *left1, const void *right1)
+{
+  const struct index *left = left1;
+  const struct index *right = right1;
+
+  return memcmp (shared_data->results[left->run][left->process_index].bytes,
+                 shared_data->results[right->run][right->process_index].bytes,
+                 random_size);
+}
+
+static int
+do_test (void)
+{
+  shared_data = support_shared_allocate (sizeof (*shared_data));
+  {
+    pthread_barrierattr_t attr;
+    xpthread_barrierattr_init (&attr);
+    xpthread_barrierattr_setpshared (&attr, PTHREAD_PROCESS_SHARED);
+    xpthread_barrier_init (&shared_data->barrier, &attr, processes);
+    xpthread_barrierattr_destroy (&attr);
+  }
+
+  /* Collect random data.  */
+  for (int run = 0; run < runs; ++run)
+    {
+#if 0
+      if (run == runs / 2)
+        {
+          /* In the middle, desynchronize the block cache by consuming
+             an odd number of bytes.  */
+          char buf;
+          arc4random_buf (&buf, 1);
+        }
+#endif
+
+      pid_t pids[subprocesses];
+      for (int process_index = 0; process_index < subprocesses;
+           ++process_index)
+        {
+          pids[process_index] = xfork ();
+          if (pids[process_index] == 0)
+            {
+              subprocess (run, process_index);
+              _exit (0);
+            }
+        }
+
+      /* Trigger all subprocesses.  Also add data from the parent
+         process.  */
+      subprocess (run, subprocesses);
+
+      for (int process_index = 0; process_index < subprocesses;
+           ++process_index)
+        {
+          int status;
+          xwaitpid (pids[process_index], &status, 0);
+          if (status != 0)
+            FAIL_EXIT1 ("subprocess index %d (PID %d) exit status %d\n",
+                        process_index, (int) pids[process_index], status);
+        }
+    }
+
+  /* Check for duplicates.  */
+  struct index indexes[runs * processes];
+  for (int run = 0; run < runs; ++run)
+    for (int process_index = 0; process_index < processes; ++process_index)
+      indexes[run * processes + process_index]
+        = (struct index) { .run = run, .process_index = process_index };
+  qsort (indexes, array_length (indexes), sizeof (indexes[0]), index_compare);
+  for (size_t i = 1; i < array_length (indexes); ++i)
+    {
+      if (index_compare (indexes + i - 1, indexes + i) == 0)
+        {
+          support_record_failure ();
+          unsigned char *bytes
+            = shared_data->results[indexes[i].run]
+                [indexes[i].process_index].bytes;
+          char *quoted = support_quote_blob (bytes, random_size);
+          printf ("error: duplicate randomness data: \"%s\"\n"
+                  "  run %d, subprocess %d\n"
+                  "  run %d, subprocess %d\n",
+                  quoted, indexes[i - 1].run, indexes[i - 1].process_index,
+                  indexes[i].run, indexes[i].process_index);
+          free (quoted);
+        }
+    }
+
+  xpthread_barrier_destroy (&shared_data->barrier);
+  support_shared_free (shared_data);
+  shared_data = NULL;
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdlib/tst-arc4random-stats.c b/stdlib/tst-arc4random-stats.c
new file mode 100644
index 0000000000..9747180c99
--- /dev/null
+++ b/stdlib/tst-arc4random-stats.c
@@ -0,0 +1,146 @@ 
+/* Statistical tests for arc4random-related functions.
+   Copyright (C) 2022 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+
+enum
+{
+  arc4random_key_size = 32
+};
+
+struct key
+{
+  unsigned char data[arc4random_key_size];
+};
+
+/* With 12,000 keys, the probability that a byte in a predetermined
+   position does not have a predetermined value in all generated keys
+   is about 4e-21.  The probability that this happens with any of the
+   16 * 256 possible byte position/values is 1.6e-17.  This results in
+   an acceptably low false-positive rate.  */
+enum { key_count = 12000 };
+
+static struct key keys[key_count];
+
+/* Used to perform the distribution check.  */
+static int byte_counts[arc4random_key_size][256];
+
+/* Bail out after this many failures.  */
+enum { failure_limit = 100 };
+
+static void
+find_stuck_bytes (bool (*func) (unsigned char *key))
+{
+  memset (&keys, 0xcc, sizeof (keys));
+
+  int failures = 0;
+  for (int key = 0; key < key_count; ++key)
+    {
+      while (true)
+        {
+          if (func (keys[key].data))
+            break;
+          ++failures;
+          if (failures >= failure_limit)
+            {
+              printf ("warning: bailing out after %d failures\n", failures);
+              return;
+            }
+        }
+    }
+  printf ("info: key generation finished with %d failures\n", failures);
+
+  memset (&byte_counts, 0, sizeof (byte_counts));
+  for (int key = 0; key < key_count; ++key)
+    for (int pos = 0; pos < arc4random_key_size; ++pos)
+      ++byte_counts[pos][keys[key].data[pos]];
+
+  for (int pos = 0; pos < arc4random_key_size; ++pos)
+    for (int byte = 0; byte < 256; ++byte)
+      if (byte_counts[pos][byte] == 0)
+        {
+          support_record_failure ();
+          printf ("error: byte %d never appeared at position %d\n", byte, pos);
+        }
+}
+
+/* Test adapter for arc4random.  */
+static bool
+generate_arc4random (unsigned char *key)
+{
+  uint32_t words[arc4random_key_size / 4];
+  _Static_assert (sizeof (words) == arc4random_key_size, "sizeof (words)");
+
+  for (int i = 0; i < array_length (words); ++i)
+    words[i] = arc4random ();
+  memcpy (key, &words, arc4random_key_size);
+  return true;
+}
+
+/* Test adapter for arc4random_buf.  */
+static bool
+generate_arc4random_buf (unsigned char *key)
+{
+  arc4random_buf (key, arc4random_key_size);
+  return true;
+}
+
+/* Test adapter for arc4random_uniform.  */
+static bool
+generate_arc4random_uniform (unsigned char *key)
+{
+  for (int i = 0; i < arc4random_key_size; ++i)
+    key[i] = arc4random_uniform (256);
+  return true;
+}
+
+/* Test adapter for arc4random_uniform with argument 257.  This means
+   that byte 0 happens more often, but we do not perform such a
+   statistcal check, so the test will still pass */
+static bool
+generate_arc4random_uniform_257 (unsigned char *key)
+{
+  for (int i = 0; i < arc4random_key_size; ++i)
+    key[i] = arc4random_uniform (257);
+  return true;
+}
+
+static int
+do_test (void)
+{
+  puts ("info: arc4random implementation test");
+  find_stuck_bytes (generate_arc4random);
+
+  puts ("info: arc4random_buf implementation test");
+  find_stuck_bytes (generate_arc4random_buf);
+
+  puts ("info: arc4random_uniform implementation test");
+  find_stuck_bytes (generate_arc4random_uniform);
+
+  puts ("info: arc4random_uniform implementation test (257 variant)");
+  find_stuck_bytes (generate_arc4random_uniform_257);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdlib/tst-arc4random-thread.c b/stdlib/tst-arc4random-thread.c
new file mode 100644
index 0000000000..b122eaa826
--- /dev/null
+++ b/stdlib/tst-arc4random-thread.c
@@ -0,0 +1,278 @@ 
+/* Test that threads generate distinct streams of randomness.
+   Copyright (C) 2018 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/support.h>
+#include <support/xthread.h>
+
+/* Number of arc4random_buf calls per thread.  */
+enum { count_per_thread = 5000 };
+
+/* Number of threads computing randomness.  */
+enum { inner_threads = 5 };
+
+/* Number of threads launching other threads.  Chosen as to not to
+   overload the system.  */
+enum { outer_threads = 7 };
+
+/* Number of launching rounds performed by the outer threads.  */
+enum { outer_rounds = 10 };
+
+/* Maximum number of bytes generated in an arc4random call.  */
+enum { max_size = 32 };
+
+/* Sizes generated by threads.  Must be long enough to be unique with
+   high probability.  */
+static const int sizes[] = { 12, 15, 16, 17, 24, 31, max_size };
+
+/* Data structure to capture randomness results.  */
+struct blob
+{
+  unsigned int size;
+  int thread_id;
+  unsigned int index;
+  unsigned char bytes[max_size];
+};
+
+#define DYNARRAY_STRUCT dynarray_blob
+#define DYNARRAY_ELEMENT struct blob
+#define DYNARRAY_PREFIX dynarray_blob_
+#include <malloc/dynarray-skeleton.c>
+
+/* Sort blob elements by length first, then by comparing the data
+   member.  */
+static int
+compare_blob (const void *left1, const void *right1)
+{
+  const struct blob *left = left1;
+  const struct blob *right = right1;
+
+  if (left->size != right->size)
+    /* No overflow due to limited range.  */
+    return left->size - right->size;
+  return memcmp (left->bytes, right->bytes, left->size);
+}
+
+/* Used to store the global result.  */
+static pthread_mutex_t global_result_lock = PTHREAD_MUTEX_INITIALIZER;
+static struct dynarray_blob global_result;
+
+/* Copy data to the global result, with locking.  */
+static void
+copy_result_to_global (struct dynarray_blob *result)
+{
+  xpthread_mutex_lock (&global_result_lock);
+  size_t old_size = dynarray_blob_size (&global_result);
+  TEST_VERIFY_EXIT
+    (dynarray_blob_resize (&global_result,
+                           old_size + dynarray_blob_size (result)));
+  memcpy (dynarray_blob_begin (&global_result) + old_size,
+          dynarray_blob_begin (result),
+          dynarray_blob_size (result) * sizeof (struct blob));
+  xpthread_mutex_unlock (&global_result_lock);
+}
+
+/* Used to assign unique thread IDs.  Accessed atomically.  */
+static int next_thread_id;
+
+static void *
+inner_thread (void *unused)
+{
+  /* Use local result to avoid global lock contention while generating
+     randomness.  */
+  struct dynarray_blob result;
+  dynarray_blob_init (&result);
+
+  int thread_id = __atomic_fetch_add (&next_thread_id, 1, __ATOMIC_RELAXED);
+
+  /* Determine the sizes to be used by this thread.  */
+  int size_slot = thread_id % (array_length (sizes) + 1);
+  bool switch_sizes = size_slot == array_length (sizes);
+  if (switch_sizes)
+    size_slot = 0;
+
+  /* Compute the random blobs.  */
+  for (int i = 0; i < count_per_thread; ++i)
+    {
+      struct blob *place = dynarray_blob_emplace (&result);
+      TEST_VERIFY_EXIT (place != NULL);
+      place->size = sizes[size_slot];
+      place->thread_id = thread_id;
+      place->index = i;
+      arc4random_buf (place->bytes, place->size);
+
+      if (switch_sizes)
+        size_slot = (size_slot + 1) % array_length (sizes);
+    }
+
+  /* Store the blobs in the global result structure.  */
+  copy_result_to_global (&result);
+
+  dynarray_blob_free (&result);
+
+  return NULL;
+}
+
+/* Launch the inner threads and wait for their termination.  */
+static void *
+outer_thread (void *unused)
+{
+  for (int round = 0; round < outer_rounds; ++round)
+    {
+      pthread_t threads[inner_threads];
+
+      for (int i = 0; i < inner_threads; ++i)
+        threads[i] = xpthread_create (NULL, inner_thread, NULL);
+
+      for (int i = 0; i < inner_threads; ++i)
+        xpthread_join (threads[i]);
+    }
+
+  return NULL;
+}
+
+static bool termination_requested;
+
+/* Call arc4random_buf to fill one blob with 16 bytes.  */
+static void *
+get_one_blob_thread (void *closure)
+{
+  struct blob *result = closure;
+  result->size = 16;
+  arc4random_buf (result->bytes, result->size);
+  return NULL;
+}
+
+/* Invoked from fork_thread to actually obtain randomness data.  */
+static void
+fork_thread_subprocess (void *closure)
+{
+  struct blob *shared_result = closure;
+
+  pthread_t thr1 = xpthread_create
+    (NULL, get_one_blob_thread, shared_result + 1);
+  pthread_t thr2 = xpthread_create
+    (NULL, get_one_blob_thread, shared_result + 2);
+  get_one_blob_thread (shared_result);
+  xpthread_join (thr1);
+  xpthread_join (thr2);
+}
+
+/* Continuously fork subprocesses to obtain a little bit of
+   randomness.  */
+static void *
+fork_thread (void *unused)
+{
+  struct dynarray_blob result;
+  dynarray_blob_init (&result);
+
+  /* Three blobs from each subprocess.  */
+  struct blob *shared_result
+    = support_shared_allocate (3 * sizeof (*shared_result));
+
+  while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED))
+    {
+      /* Obtain the results from a subprocess.  */
+      support_isolate_in_subprocess (fork_thread_subprocess, shared_result);
+
+      for (int i = 0; i < 3; ++i)
+        {
+          struct blob *place = dynarray_blob_emplace (&result);
+          TEST_VERIFY_EXIT (place != NULL);
+          place->size = shared_result[i].size;
+          place->thread_id = -1;
+          place->index = i;
+          memcpy (place->bytes, shared_result[i].bytes, place->size);
+        }
+    }
+
+  support_shared_free (shared_result);
+
+  copy_result_to_global (&result);
+  dynarray_blob_free (&result);
+
+  return NULL;
+}
+
+/* Launch the outer threads and wait for their termination.  */
+static void
+run_outer_threads (void)
+{
+  /* Special thread that continuously calls fork.  */
+  pthread_t fork_thread_id = xpthread_create (NULL, fork_thread, NULL);
+
+  pthread_t threads[outer_threads];
+  for (int i = 0; i < outer_threads; ++i)
+    threads[i] = xpthread_create (NULL, outer_thread, NULL);
+
+  for (int i = 0; i < outer_threads; ++i)
+    xpthread_join (threads[i]);
+
+  __atomic_store_n (&termination_requested, true, __ATOMIC_RELAXED);
+  xpthread_join (fork_thread_id);
+}
+
+static int
+do_test (void)
+{
+  dynarray_blob_init (&global_result);
+  int expected_blobs
+    = count_per_thread * inner_threads * outer_threads * outer_rounds;
+  printf ("info: minimum of %d blob results expected\n", expected_blobs);
+
+  run_outer_threads ();
+
+  /* The forking thread delivers a non-deterministic number of
+     results, which is why expected_blobs is only a minimun number of
+     results.  */
+  printf ("info: %zu blob results observed\n",
+          dynarray_blob_size (&global_result));
+  TEST_VERIFY (dynarray_blob_size (&global_result) >= expected_blobs);
+
+  /* Verify that there are no duplicates.  */
+  qsort (dynarray_blob_begin (&global_result),
+         dynarray_blob_size (&global_result),
+         sizeof (struct blob), compare_blob);
+  struct blob *end = dynarray_blob_end (&global_result);
+  for (struct blob *p = dynarray_blob_begin (&global_result) + 1;
+       p < end; ++p)
+    {
+      if (compare_blob (p - 1, p) == 0)
+        {
+          support_record_failure ();
+          char *quoted = support_quote_blob (p->bytes, p->size);
+          printf ("error: duplicate blob: \"%s\" (%d bytes)\n",
+                  quoted, (int) p->size);
+          printf ("  first source: thread %d, index %u\n",
+                  p[-1].thread_id, p[-1].index);
+          printf ("  second source: thread %d, index %u\n",
+                  p[0].thread_id, p[0].index);
+          free (quoted);
+        }
+    }
+
+  dynarray_blob_free (&global_result);
+
+  return 0;
+}
+
+#include <support/test-driver.c>