From 8dd890d96f1833a58de6e112a14d63ab44e0a402 Mon Sep 17 00:00:00 2001 From: Adhemerval Zanella Netto Date: Thu, 21 Jul 2022 10:05:00 -0300 Subject: [PATCH] stdlib: Add arc4random tests 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 Checked on x86_64-linux-gnu and aarch64-linux-gnu. --- stdlib/Makefile | 7 + stdlib/tst-arc4random-chacha20.c | 167 +++++++++++++++ stdlib/tst-arc4random-fork.c | 198 ++++++++++++++++++ stdlib/tst-arc4random-stats.c | 147 +++++++++++++ stdlib/tst-arc4random-thread.c | 341 +++++++++++++++++++++++++++++++ 5 files changed, 860 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 62f8253225..a900962685 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 \ @@ -243,6 +246,7 @@ tests := \ # tests tests-internal := \ + tst-arc4random-chacha20 \ tst-strtod1i \ tst-strtod3 \ tst-strtod4 \ @@ -252,6 +256,7 @@ tests-internal := \ # tests-internal tests-static := \ + tst-arc4random-chacha20 \ tst-secure-getenv \ # tests-static @@ -271,6 +276,8 @@ LDLIBS-test-cxa_atexit-race = $(shared-thread-library) LDLIBS-test-cxa_atexit-race2 = $(shared-thread-library) LDLIBS-test-on_exit-race = $(shared-thread-library) LDLIBS-tst-canon-bz26341 = $(shared-thread-library) +LDLIBS-tst-arc4random-fork = $(shared-thread-library) +LDLIBS-tst-arc4random-thread = $(shared-thread-library) LDLIBS-test-dlclose-exit-race = $(shared-thread-library) LDFLAGS-test-dlclose-exit-race = $(LDFLAGS-rdynamic) diff --git a/stdlib/tst-arc4random-chacha20.c b/stdlib/tst-arc4random-chacha20.c new file mode 100644 index 0000000000..45ba54920d --- /dev/null +++ b/stdlib/tst-arc4random-chacha20.c @@ -0,0 +1,167 @@ +/* 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 + . */ + +#include +#include +#include + +/* The test does not define CHACHA20_XOR_FINAL to mimic what arc4random + actual does. */ +#include + +static int +do_test (void) +{ + const 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, + }; + const 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 expected1[CHACHA20_BUFSIZE] = + { + 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, 0x9f, 0x07, + 0xe7, 0xbe, 0x55, 0x51, 0x38, 0x7a, 0x98, 0xba, 0x97, 0x7c, 0x73, + 0x2d, 0x08, 0x0d, 0xcb, 0x0f, 0x29, 0xa0, 0x48, 0xe3, 0x65, 0x69, + 0x12, 0xc6, 0x53, 0x3e, 0x32, 0xee, 0x7a, 0xed, 0x29, 0xb7, 0x21, + 0x76, 0x9c, 0xe6, 0x4e, 0x43, 0xd5, 0x71, 0x33, 0xb0, 0x74, 0xd8, + 0x39, 0xd5, 0x31, 0xed, 0x1f, 0x28, 0x51, 0x0a, 0xfb, 0x45, 0xac, + 0xe1, 0x0a, 0x1f, 0x4b, 0x79, 0x4d, 0x6f, 0x2d, 0x09, 0xa0, 0xe6, + 0x63, 0x26, 0x6c, 0xe1, 0xae, 0x7e, 0xd1, 0x08, 0x19, 0x68, 0xa0, + 0x75, 0x8e, 0x71, 0x8e, 0x99, 0x7b, 0xd3, 0x62, 0xc6, 0xb0, 0xc3, + 0x46, 0x34, 0xa9, 0xa0, 0xb3, 0x5d, 0x01, 0x27, 0x37, 0x68, 0x1f, + 0x7b, 0x5d, 0x0f, 0x28, 0x1e, 0x3a, 0xfd, 0xe4, 0x58, 0xbc, 0x1e, + 0x73, 0xd2, 0xd3, 0x13, 0xc9, 0xcf, 0x94, 0xc0, 0x5f, 0xf3, 0x71, + 0x62, 0x40, 0xa2, 0x48, 0xf2, 0x13, 0x20, 0xa0, 0x58, 0xd7, 0xb3, + 0x56, 0x6b, 0xd5, 0x20, 0xda, 0xaa, 0x3e, 0xd2, 0xbf, 0x0a, 0xc5, + 0xb8, 0xb1, 0x20, 0xfb, 0x85, 0x27, 0x73, 0xc3, 0x63, 0x97, 0x34, + 0xb4, 0x5c, 0x91, 0xa4, 0x2d, 0xd4, 0xcb, 0x83, 0xf8, 0x84, 0x0d, + 0x2e, 0xed, 0xb1, 0x58, 0x13, 0x10, 0x62, 0xac, 0x3f, 0x1f, 0x2c, + 0xf8, 0xff, 0x6d, 0xcd, 0x18, 0x56, 0xe8, 0x6a, 0x1e, 0x6c, 0x31, + 0x67, 0x16, 0x7e, 0xe5, 0xa6, 0x88, 0x74, 0x2b, 0x47, 0xc5, 0xad, + 0xfb, 0x59, 0xd4, 0xdf, 0x76, 0xfd, 0x1d, 0xb1, 0xe5, 0x1e, 0xe0, + 0x3b, 0x1c, 0xa9, 0xf8, 0x2a, 0xca, 0x17, 0x3e, 0xdb, 0x8b, 0x72, + 0x93, 0x47, 0x4e, 0xbe, 0x98, 0x0f, 0x90, 0x4d, 0x10, 0xc9, 0x16, + 0x44, 0x2b, 0x47, 0x83, 0xa0, 0xe9, 0x84, 0x86, 0x0c, 0xb6, 0xc9, + 0x57, 0xb3, 0x9c, 0x38, 0xed, 0x8f, 0x51, 0xcf, 0xfa, 0xa6, 0x8a, + 0x4d, 0xe0, 0x10, 0x25, 0xa3, 0x9c, 0x50, 0x45, 0x46, 0xb9, 0xdc, + 0x14, 0x06, 0xa7, 0xeb, 0x28, 0x15, 0x1e, 0x51, 0x50, 0xd7, 0xb2, + 0x04, 0xba, 0xa7, 0x19, 0xd4, 0xf0, 0x91, 0x02, 0x12, 0x17, 0xdb, + 0x5c, 0xf1, 0xb5, 0xc8, 0x4c, 0x4f, 0xa7, 0x1a, 0x87, 0x96, 0x10, + 0xa1, 0xa6, 0x95, 0xac, 0x52, 0x7c, 0x5b, 0x56, 0x77, 0x4a, 0x6b, + 0x8a, 0x21, 0xaa, 0xe8, 0x86, 0x85, 0x86, 0x8e, 0x09, 0x4c, 0xf2, + 0x9e, 0xf4, 0x09, 0x0a, 0xf7, 0xa9, 0x0c, 0xc0, 0x7e, 0x88, 0x17, + 0xaa, 0x52, 0x87, 0x63, 0x79, 0x7d, 0x3c, 0x33, 0x2b, 0x67, 0xca, + 0x4b, 0xc1, 0x10, 0x64, 0x2c, 0x21, 0x51, 0xec, 0x47, 0xee, 0x84, + 0xcb, 0x8c, 0x42, 0xd8, 0x5f, 0x10, 0xe2, 0xa8, 0xcb, 0x18, 0xc3, + 0xb7, 0x33, 0x5f, 0x26, 0xe8, 0xc3, 0x9a, 0x12, 0xb1, 0xbc, 0xc1, + 0x70, 0x71, 0x77, 0xb7, 0x61, 0x38, 0x73, 0x2e, 0xed, 0xaa, 0xb7, + 0x4d, 0xa1, 0x41, 0x0f, 0xc0, 0x55, 0xea, 0x06, 0x8c, 0x99, 0xe9, + 0x26, 0x0a, 0xcb, 0xe3, 0x37, 0xcf, 0x5d, 0x3e, 0x00, 0xe5, 0xb3, + 0x23, 0x0f, 0xfe, 0xdb, 0x0b, 0x99, 0x07, 0x87, 0xd0, 0xc7, 0x0e, + 0x0b, 0xfe, 0x41, 0x98, 0xea, 0x67, 0x58, 0xdd, 0x5a, 0x61, 0xfb, + 0x5f, 0xec, 0x2d, 0xf9, 0x81, 0xf3, 0x1b, 0xef, 0xe1, 0x53, 0xf8, + 0x1d, 0x17, 0x16, 0x17, 0x84, 0xdb + }; + + const uint8_t expected2[CHACHA20_BUFSIZE] = + { + 0x1c, 0x88, 0x22, 0xd5, 0x3c, 0xd1, 0xee, 0x7d, 0xb5, 0x32, 0x36, + 0x48, 0x28, 0xbd, 0xf4, 0x04, 0xb0, 0x40, 0xa8, 0xdc, 0xc5, 0x22, + 0xf3, 0xd3, 0xd9, 0x9a, 0xec, 0x4b, 0x80, 0x57, 0xed, 0xb8, 0x50, + 0x09, 0x31, 0xa2, 0xc4, 0x2d, 0x2f, 0x0c, 0x57, 0x08, 0x47, 0x10, + 0x0b, 0x57, 0x54, 0xda, 0xfc, 0x5f, 0xbd, 0xb8, 0x94, 0xbb, 0xef, + 0x1a, 0x2d, 0xe1, 0xa0, 0x7f, 0x8b, 0xa0, 0xc4, 0xb9, 0x19, 0x30, + 0x10, 0x66, 0xed, 0xbc, 0x05, 0x6b, 0x7b, 0x48, 0x1e, 0x7a, 0x0c, + 0x46, 0x29, 0x7b, 0xbb, 0x58, 0x9d, 0x9d, 0xa5, 0xb6, 0x75, 0xa6, + 0x72, 0x3e, 0x15, 0x2e, 0x5e, 0x63, 0xa4, 0xce, 0x03, 0x4e, 0x9e, + 0x83, 0xe5, 0x8a, 0x01, 0x3a, 0xf0, 0xe7, 0x35, 0x2f, 0xb7, 0x90, + 0x85, 0x14, 0xe3, 0xb3, 0xd1, 0x04, 0x0d, 0x0b, 0xb9, 0x63, 0xb3, + 0x95, 0x4b, 0x63, 0x6b, 0x5f, 0xd4, 0xbf, 0x6d, 0x0a, 0xad, 0xba, + 0xf8, 0x15, 0x7d, 0x06, 0x2a, 0xcb, 0x24, 0x18, 0xc1, 0x76, 0xa4, + 0x75, 0x51, 0x1b, 0x35, 0xc3, 0xf6, 0x21, 0x8a, 0x56, 0x68, 0xea, + 0x5b, 0xc6, 0xf5, 0x4b, 0x87, 0x82, 0xf8, 0xb3, 0x40, 0xf0, 0x0a, + 0xc1, 0xbe, 0xba, 0x5e, 0x62, 0xcd, 0x63, 0x2a, 0x7c, 0xe7, 0x80, + 0x9c, 0x72, 0x56, 0x08, 0xac, 0xa5, 0xef, 0xbf, 0x7c, 0x41, 0xf2, + 0x37, 0x64, 0x3f, 0x06, 0xc0, 0x99, 0x72, 0x07, 0x17, 0x1d, 0xe8, + 0x67, 0xf9, 0xd6, 0x97, 0xbf, 0x5e, 0xa6, 0x01, 0x1a, 0xbc, 0xce, + 0x6c, 0x8c, 0xdb, 0x21, 0x13, 0x94, 0xd2, 0xc0, 0x2d, 0xd0, 0xfb, + 0x60, 0xdb, 0x5a, 0x2c, 0x17, 0xac, 0x3d, 0xc8, 0x58, 0x78, 0xa9, + 0x0b, 0xed, 0x38, 0x09, 0xdb, 0xb9, 0x6e, 0xaa, 0x54, 0x26, 0xfc, + 0x8e, 0xae, 0x0d, 0x2d, 0x65, 0xc4, 0x2a, 0x47, 0x9f, 0x08, 0x86, + 0x48, 0xbe, 0x2d, 0xc8, 0x01, 0xd8, 0x2a, 0x36, 0x6f, 0xdd, 0xc0, + 0xef, 0x23, 0x42, 0x63, 0xc0, 0xb6, 0x41, 0x7d, 0x5f, 0x9d, 0xa4, + 0x18, 0x17, 0xb8, 0x8d, 0x68, 0xe5, 0xe6, 0x71, 0x95, 0xc5, 0xc1, + 0xee, 0x30, 0x95, 0xe8, 0x21, 0xf2, 0x25, 0x24, 0xb2, 0x0b, 0xe4, + 0x1c, 0xeb, 0x59, 0x04, 0x12, 0xe4, 0x1d, 0xc6, 0x48, 0x84, 0x3f, + 0xa9, 0xbf, 0xec, 0x7a, 0x3d, 0xcf, 0x61, 0xab, 0x05, 0x41, 0x57, + 0x33, 0x16, 0xd3, 0xfa, 0x81, 0x51, 0x62, 0x93, 0x03, 0xfe, 0x97, + 0x41, 0x56, 0x2e, 0xd0, 0x65, 0xdb, 0x4e, 0xbc, 0x00, 0x50, 0xef, + 0x55, 0x83, 0x64, 0xae, 0x81, 0x12, 0x4a, 0x28, 0xf5, 0xc0, 0x13, + 0x13, 0x23, 0x2f, 0xbc, 0x49, 0x6d, 0xfd, 0x8a, 0x25, 0x68, 0x65, + 0x7b, 0x68, 0x6d, 0x72, 0x14, 0x38, 0x2a, 0x1a, 0x00, 0x90, 0x30, + 0x17, 0xdd, 0xa9, 0x69, 0x87, 0x84, 0x42, 0xba, 0x5a, 0xff, 0xf6, + 0x61, 0x3f, 0x55, 0x3c, 0xbb, 0x23, 0x3c, 0xe4, 0x6d, 0x9a, 0xee, + 0x93, 0xa7, 0x87, 0x6c, 0xf5, 0xe9, 0xe8, 0x29, 0x12, 0xb1, 0x8c, + 0xad, 0xf0, 0xb3, 0x43, 0x27, 0xb2, 0xe0, 0x42, 0x7e, 0xcf, 0x66, + 0xb7, 0xce, 0xb7, 0xc0, 0x91, 0x8d, 0xc4, 0x7b, 0xdf, 0xf1, 0x2a, + 0x06, 0x2a, 0xdf, 0x07, 0x13, 0x30, 0x09, 0xce, 0x7a, 0x5e, 0x5c, + 0x91, 0x7e, 0x01, 0x68, 0x30, 0x61, 0x09, 0xb7, 0xcb, 0x49, 0x65, + 0x3a, 0x6d, 0x2c, 0xae, 0xf0, 0x05, 0xde, 0x78, 0x3a, 0x9a, 0x9b, + 0xfe, 0x05, 0x38, 0x1e, 0xd1, 0x34, 0x8d, 0x94, 0xec, 0x65, 0x88, + 0x6f, 0x9c, 0x0b, 0x61, 0x9c, 0x52, 0xc5, 0x53, 0x38, 0x00, 0xb1, + 0x6c, 0x83, 0x61, 0x72, 0xb9, 0x51, 0x82, 0xdb, 0xc5, 0xee, 0xc0, + 0x42, 0xb8, 0x9e, 0x22, 0xf1, 0x1a, 0x08, 0x5b, 0x73, 0x9a, 0x36, + 0x11, 0xcd, 0x8d, 0x83, 0x60, 0x18 + }; + + /* Check with the expected internal arc4random keystream buffer. Some + architecture optimizations expects a buffer with a minimum size which + is a multiple of then ChaCha20 blocksize, so they might not be prepared + to handle smaller buffers. */ + + uint8_t output[CHACHA20_BUFSIZE]; + + uint32_t state[CHACHA20_STATE_LEN]; + chacha20_init (state, key, iv); + + /* Check with the initial state. */ + uint8_t input[CHACHA20_BUFSIZE] = { 0 }; + + chacha20_crypt (state, output, input, CHACHA20_BUFSIZE); + TEST_COMPARE_BLOB (output, sizeof output, expected1, CHACHA20_BUFSIZE); + + /* And on the next round. */ + chacha20_crypt (state, output, input, CHACHA20_BUFSIZE); + TEST_COMPARE_BLOB (output, sizeof output, expected2, CHACHA20_BUFSIZE); + + return 0; +} + +#include diff --git a/stdlib/tst-arc4random-fork.c b/stdlib/tst-arc4random-fork.c new file mode 100644 index 0000000000..019c0a99de --- /dev/null +++ b/stdlib/tst-arc4random-fork.c @@ -0,0 +1,198 @@ +/* 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 + . */ + +/* Collect random data from subprocesses and check that all the + results are unique. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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; + +static void +generate_arc4random (unsigned char *bytes) +{ + for (int i = 0; i < random_size / sizeof (uint32_t); i++) + { + uint32_t x = arc4random (); + memcpy (&bytes[4 * i], &x, sizeof x); + } +} + +static void +generate_arc4random_buf (unsigned char *bytes) +{ + arc4random_buf (bytes, random_size); +} + +static void +generate_arc4random_uniform (unsigned char *bytes) +{ + for (int i = 0; i < random_size; i++) + bytes[i] = arc4random_uniform (256); +} + +/* Invoked to collect data from a subprocess. */ +static void +subprocess (int run, int process_index, void (*func)(unsigned char *)) +{ + xpthread_barrier_wait (&shared_data->barrier); + func (shared_data->results[run][process_index].bytes); +} + +/* 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_func (void (*func)(unsigned char *bytes)) +{ + /* Collect random data. */ + for (int run = 0; run < runs; ++run) + { + 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, func); + _exit (0); + } + } + + /* Trigger all subprocesses. Also add data from the parent + process. */ + subprocess (run, subprocesses, func); + + 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); + } + } + + return 0; +} + +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); + } + + do_test_func (generate_arc4random); + do_test_func (generate_arc4random_buf); + do_test_func (generate_arc4random_uniform); + + xpthread_barrier_destroy (&shared_data->barrier); + support_shared_free (shared_data); + shared_data = NULL; + + return 0; +} + +#define TIMEOUT 40 +#include diff --git a/stdlib/tst-arc4random-stats.c b/stdlib/tst-arc4random-stats.c new file mode 100644 index 0000000000..f6fc1f10c4 --- /dev/null +++ b/stdlib/tst-arc4random-stats.c @@ -0,0 +1,147 @@ +/* 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 + . */ + +#include +#include +#include +#include +#include +#include +#include + +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 diff --git a/stdlib/tst-arc4random-thread.c b/stdlib/tst-arc4random-thread.c new file mode 100644 index 0000000000..3373d4d446 --- /dev/null +++ b/stdlib/tst-arc4random-thread.c @@ -0,0 +1,341 @@ +/* Test that threads 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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]; +}; + +struct subprocess_args +{ + struct blob *blob; + void (*func)(unsigned char *, size_t); +}; + +static void +generate_arc4random (unsigned char *bytes, size_t size) +{ + int i; + for (i = 0; i < size / sizeof (uint32_t); i++) + { + uint32_t x = arc4random (); + memcpy (&bytes[4 * i], &x, sizeof x); + } + int rem = size % sizeof (uint32_t); + if (rem > 0) + { + uint32_t x = arc4random (); + memcpy (&bytes[4 * i], &x, rem); + } +} + +static void +generate_arc4random_buf (unsigned char *bytes, size_t size) +{ + arc4random_buf (bytes, size); +} + +static void +generate_arc4random_uniform (unsigned char *bytes, size_t size) +{ + for (int i = 0; i < size; i++) + bytes[i] = arc4random_uniform (256); +} + +#define DYNARRAY_STRUCT dynarray_blob +#define DYNARRAY_ELEMENT struct blob +#define DYNARRAY_PREFIX dynarray_blob_ +#include + +/* 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 *closure) +{ + void (*func) (unsigned char *, size_t) = closure; + + /* 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; + func (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 *closure) +{ + void (*func) (unsigned char *, size_t) = closure; + + 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, func); + + 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 subprocess_args *arg = closure; + struct blob *result = arg->blob; + + result->size = 16; + arg->func (result->bytes, result->size); + return NULL; +} + +/* Invoked from fork_thread to actually obtain randomness data. */ +static void +fork_thread_subprocess (void *closure) +{ + struct subprocess_args *arg = closure; + struct blob *shared_result = arg->blob; + + struct subprocess_args args[3] = + { + { shared_result + 0, arg->func }, + { shared_result + 1, arg->func }, + { shared_result + 2, arg->func } + }; + + pthread_t thr1 = xpthread_create (NULL, get_one_blob_thread, &args[1]); + pthread_t thr2 = xpthread_create (NULL, get_one_blob_thread, &args[2]); + get_one_blob_thread (&args[0]); + xpthread_join (thr1); + xpthread_join (thr2); +} + +/* Continuously fork subprocesses to obtain a little bit of + randomness. */ +static void * +fork_thread (void *closure) +{ + void (*func)(unsigned char *, size_t) = closure; + + 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. */ + struct subprocess_args arg = { shared_result, func }; + support_isolate_in_subprocess (fork_thread_subprocess, &arg); + + 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 (*func)(unsigned char *, size_t)) +{ + /* Special thread that continuously calls fork. */ + pthread_t fork_thread_id = xpthread_create (NULL, fork_thread, func); + + pthread_t threads[outer_threads]; + for (int i = 0; i < outer_threads; ++i) + threads[i] = xpthread_create (NULL, outer_thread, func); + + 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_func (const char *fname, void (*func)(unsigned char *, size_t)) +{ + dynarray_blob_init (&global_result); + int expected_blobs + = count_per_thread * inner_threads * outer_threads * outer_rounds; + printf ("info: %s: minimum of %d blob results expected\n", + fname, expected_blobs); + + run_outer_threads (func); + + /* The forking thread delivers a non-deterministic number of + results, which is why expected_blobs is only a minimun number of + results. */ + printf ("info: %s: %zu blob results observed\n", fname, + 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: %s: duplicate blob: \"%s\" (%d bytes)\n", + fname, 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; +} + +static int +do_test (void) +{ + do_test_func ("arc4random", generate_arc4random); + do_test_func ("arc4random_buf", generate_arc4random_buf); + do_test_func ("arc4random_uniform", generate_arc4random_uniform); + + return 0; +} + +#include