From 006c7a752b2d7df0bc4b479e516e5c29a9e4c93e Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Mon, 11 Mar 2024 19:20:12 -0500 Subject: [PATCH] Feature: add f_random_array_shuffle() function to f_random(). Provide an array shuffle function using standard random function calls. This is ideal for use in randomizing packet header order for increase network packet security. Add appropriate unit tests. --- level_0/f_random/c/random.c | 49 ++++++ level_0/f_random/c/random.h | 64 ++++++++ level_0/f_random/data/build/dependencies | 2 + level_0/f_random/data/build/settings | 2 +- level_0/f_random/data/build/settings-tests | 2 +- level_0/f_random/tests/unit/c/mock-random.c | 16 ++ level_0/f_random/tests/unit/c/mock-random.h | 6 + .../tests/unit/c/test-random-array_shuffle.c | 182 +++++++++++++++++++++ .../tests/unit/c/test-random-array_shuffle.h | 48 ++++++ level_0/f_random/tests/unit/c/test-random-get.c | 5 +- level_0/f_random/tests/unit/c/test-random-read.c | 15 +- level_0/f_random/tests/unit/c/test-random-seed.c | 9 +- .../f_random/tests/unit/c/test-random-seed_set.c | 3 +- level_0/f_random/tests/unit/c/test-random.c | 5 + level_0/f_random/tests/unit/c/test-random.h | 1 + 15 files changed, 403 insertions(+), 6 deletions(-) create mode 100644 level_0/f_random/tests/unit/c/test-random-array_shuffle.c create mode 100644 level_0/f_random/tests/unit/c/test-random-array_shuffle.h diff --git a/level_0/f_random/c/random.c b/level_0/f_random/c/random.c index 7fda850..7d13ef9 100644 --- a/level_0/f_random/c/random.c +++ b/level_0/f_random/c/random.c @@ -4,6 +4,55 @@ extern "C" { #endif +#ifndef _di_f_random_array_shuffle_ + f_status_t f_random_array_shuffle(const unsigned int flag, const f_number_unsigned_t total, const unsigned short size, f_string_dynamic_t * const cache, void * const indexes) { + #ifndef _di_level_0_parameter_checking_ + if (!cache) return F_status_set_error(F_parameter); + if (!indexes) return F_status_set_error(F_parameter); + #endif // _di_level_0_parameter_checking_ + + if (!size) return F_status_set_error(F_too_small); + if (!total) return F_data_not; + + { + cache->used = 0; + + const f_status_t status = f_memory_array_increase_by(sizeof(f_number_unsigned_t) + 1, sizeof(f_char_t), (void **) &cache->string, &cache->used, &cache->size); + if (F_status_is_error(status)) return status; + } + + f_number_unsigned_t index = 0; + int8_t old[size]; + + for (f_number_unsigned_t max = total - 1; max; --max) { + + if (getrandom((void *) (cache->string), sizeof(f_number_unsigned_t), flag) == -1) { + if (errno == EAGAIN) return F_status_set_error(F_again); + if (errno == EFAULT) return F_status_set_error(F_buffer); + if (errno == EINTR) return F_status_set_error(F_interrupt); + if (errno == EINVAL) return F_status_set_error(F_parameter); + if (errno == ENOSYS) return F_status_set_error(F_support_not); + + return F_status_set_error(F_failure); + } + + index = *((f_number_unsigned_t *) cache->string) % (max + 1); + + if (index == max) continue; + + memset(cache->string, 0, sizeof(f_number_unsigned_t)); + cache->string[cache->used] = 0; + + // Casting (void *) to (uint8_t *) should result in an increment of size 1 and avoids problems with (void *) having arithmetic issues. + memmove((void *) old, (void *) (((uint8_t *) indexes) + (size * max)), size); + memmove((void *) (((uint8_t *) indexes) + (size * max)), (void *) (((uint8_t *) indexes) + (size * index)), size); + memmove((void *) (((uint8_t *) indexes) + (size * index)), (void *) old, size); + } // for + + return F_okay; + } +#endif // _di_f_random_array_shuffle_ + #ifndef _di_f_random_get_ f_status_t f_random_get(long * const destination) { #ifndef _di_level_0_parameter_checking_ diff --git a/level_0/f_random/c/random.h b/level_0/f_random/c/random.h index cdb7c99..08a7c6a 100644 --- a/level_0/f_random/c/random.h +++ b/level_0/f_random/c/random.h @@ -29,6 +29,64 @@ extern "C" { #endif /** + * Re-organize an array in a random order by swapping the values. + * + * The standard behavior of this is to call getrandom(). + * + * The behavior of this function might also be replaced with calls to other libraries that are highly security specialized. + * If this is done, the meaning behind the flags passed should not be changed. + * + * This calls the Linux-specific getrandom() by default. + * If this is not available or pure POSIX is desired, then the implementation must handle accessing the /dev/random and /dev/urandom themselves (or something equivalent). + * + * @param flag + * The flags to be passed to getrandom(). + * + * Flag bits: + * - F_random_seed_flag_block_not_d: Does not block when getting the bits. + * - F_random_seed_flag_source_d: Random data is taken from the random source, such as /dev/random and not the urandom source. + * @param total + * The total number of index positions to randomize. + * @param size + * The type size of the array represented in indexes. + * @param cache + * A string used when allocating space when calling the getrandom() or similar. + * This changes the length of the cache array as needed. + * + * Must not be NULL. + * @param indexes + * The array in which the index positions that must be cast to a (void *). + * The index positions are determined by size parameter. + * The first memory address given represents index 0. + * The values at each index position are swapped. + * + * This must only be the address to a standard array structure, such as those represented by the syntax "[]". + * + * Must not be NULL. + * + * @return + * F_okay on success. + * F_data_not on success, but total is 0. + * + * F_again (with error bit) when in non-blocking mode but reading entropy source would block (such as when the entropy source is busy). + * F_buffer (with error bit) if the address represented by the buffer is outside accessible address space. + * F_interrupt (with error bit) if stopping due to an interrupt. + * F_parameter (with error bit) if a parameter is invalid. + * F_support_not (with error bit) if the running kernel does not support the getrandom() call. + * F_too_small (with error bit) if size is too small. + * + * Errors (with error bit) from: f_memory_array_increase_by(). + * + * @see getrandom() + * @see memmove() + * + * @see f_memory_array_increase_by() + */ +#ifndef _di_f_random_array_shuffle_ + extern f_status_t f_random_array_shuffle(const unsigned int flag, const f_number_unsigned_t total, const unsigned short size, f_string_dynamic_t * const cache, void * const indexes); +#endif // _di_f_random_array_shuffle_ + +/** * Set the destination string by reading from the random or urandom entropy source directly. * * The standard behavior of this is to call random(). @@ -45,6 +103,8 @@ extern "C" { * F_okay on success. * * F_parameter (with error bit) if a parameter is invalid. + * + * @see random() */ #ifndef _di_f_random_get_ extern f_status_t f_random_get(long * const destination); @@ -91,6 +151,8 @@ extern "C" { * F_interrupt (with error bit) if stopping due to an interrupt. * F_parameter (with error bit) if a parameter is invalid. * F_support_not (with error bit) if the running kernel does not support the getrandom() call. + * + * @see getrandom() */ #ifndef _di_f_random_read_ extern f_status_t f_random_read(const unsigned int flag, const f_number_unsigned_t length, f_string_t * const destination, ssize_t * const total); @@ -144,6 +206,8 @@ extern "C" { * F_okay on success. * * F_parameter (with error bit) if a parameter is invalid. + * + * @see srandom() */ #ifndef _di_f_random_seed_set_ extern f_status_t f_random_seed_set(unsigned int seed); diff --git a/level_0/f_random/data/build/dependencies b/level_0/f_random/data/build/dependencies index c06dda5..03d7727 100644 --- a/level_0/f_random/data/build/dependencies +++ b/level_0/f_random/data/build/dependencies @@ -2,4 +2,6 @@ f_type f_status +f_memory +f_type_array f_string diff --git a/level_0/f_random/data/build/settings b/level_0/f_random/data/build/settings index 773a147..f02f552 100644 --- a/level_0/f_random/data/build/settings +++ b/level_0/f_random/data/build/settings @@ -32,7 +32,7 @@ build_indexer_arguments rcs build_language c build_libraries -lc -build_libraries-individual -lf_memory -lf_string +build_libraries-individual -lf_memory -lf_string -lf_type_array build_sources_library random.c diff --git a/level_0/f_random/data/build/settings-tests b/level_0/f_random/data/build/settings-tests index 34eb91c..368ba55 100644 --- a/level_0/f_random/data/build/settings-tests +++ b/level_0/f_random/data/build/settings-tests @@ -25,7 +25,7 @@ build_language c build_libraries -lc -lcmocka build_libraries-individual -lf_memory -lf_random -build_sources_program test-random-get.c test-random-read.c test-random-seed.c test-random-seed_set.c +build_sources_program test-random-array_shuffle.c test-random-get.c test-random-read.c test-random-seed.c test-random-seed_set.c build_sources_program test-random.c build_script no diff --git a/level_0/f_random/tests/unit/c/mock-random.c b/level_0/f_random/tests/unit/c/mock-random.c index ec2dc0c..d14599a 100644 --- a/level_0/f_random/tests/unit/c/mock-random.c +++ b/level_0/f_random/tests/unit/c/mock-random.c @@ -4,8 +4,14 @@ extern "C" { #endif +int mock_unwrap = 0; + ssize_t __wrap_getrandom(void *buf, size_t buflen, unsigned int flags) { + if (mock_unwrap) { + return __real_getrandom(buf, buflen, flags); + } + const bool failure = mock_type(bool); if (failure) { @@ -20,10 +26,20 @@ ssize_t __wrap_getrandom(void *buf, size_t buflen, unsigned int flags) { } long __wrap_random(void) { + + if (mock_unwrap) { + return __real_random(); + } + return mock_type(long); } void __wrap_srandom(unsigned seed) { + + if (mock_unwrap) { + return __real_srandom(seed); + } + // Do nothing. } diff --git a/level_0/f_random/tests/unit/c/mock-random.h b/level_0/f_random/tests/unit/c/mock-random.h index 205f366..c87ca77 100644 --- a/level_0/f_random/tests/unit/c/mock-random.h +++ b/level_0/f_random/tests/unit/c/mock-random.h @@ -28,6 +28,12 @@ extern "C" { const static int mock_errno_generic = 32767; +extern int mock_unwrap; + +extern ssize_t __real_getrandom(void *buf, size_t buflen, unsigned int flags); +extern long __real_random(void); +extern void __real_srandom(unsigned seed); + extern ssize_t __wrap_getrandom(void *buf, size_t buflen, unsigned int flags); extern long __wrap_random(void); extern void __wrap_srandom(unsigned seed); diff --git a/level_0/f_random/tests/unit/c/test-random-array_shuffle.c b/level_0/f_random/tests/unit/c/test-random-array_shuffle.c new file mode 100644 index 0000000..25e6b4c --- /dev/null +++ b/level_0/f_random/tests/unit/c/test-random-array_shuffle.c @@ -0,0 +1,182 @@ +#include "test-random.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void test__f_random_array_shuffle__fails(void **state) { + + mock_unwrap = 0; + + int errnos[] = { + EAGAIN, + EFAULT, + EINTR, + EINVAL, + ENOSYS, + mock_errno_generic, + }; + + f_status_t statuss[] = { + F_again, + F_buffer, + F_interrupt, + F_parameter, + F_support_not, + F_failure, + }; + + f_string_dynamic_t cache = f_string_dynamic_t_initialize; + f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize; + + f_number_unsigned_t indexes_array[5] = { 0, 1, 2, 3, 4 }; + indexes.array = indexes_array; + indexes.used = 5; + + for (uint8_t i = 0; i < 6; ++i) { + + will_return(__wrap_getrandom, true); + will_return(__wrap_getrandom, errnos[i]); + will_return(__wrap_getrandom, -1); + + const f_status_t status = f_random_array_shuffle(0, indexes.used, sizeof(f_number_unsigned_t), &cache, (void *) indexes.array); + + if (status != F_status_set_error(statuss[i])) { + printf("[ ERROR ] --- At index %d, first loop.\n", i); + } + + assert_int_equal(status, F_status_set_error(statuss[i])); + } // for + + if (cache.string) free(cache.string); +} + +void test__f_random_array_shuffle__parameter_checking(void **state) { + + mock_unwrap = 0; + + { + const f_status_t status = f_random_array_shuffle(0, 0, 0, 0, 0); + + assert_int_equal(status, F_status_set_error(F_parameter)); + } + + { + f_string_dynamic_t cache = f_string_dynamic_t_initialize; + + const f_status_t status = f_random_array_shuffle(0, 0, 0, &cache, 0); + + assert_int_equal(status, F_status_set_error(F_parameter)); + } + + { + f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize; + + const f_status_t status = f_random_array_shuffle(0, 0, 0, 0, (void *) indexes.array); + + assert_int_equal(status, F_status_set_error(F_parameter)); + } +} + +void test__f_random_array_shuffle__returns_data_not(void **state) { + + mock_unwrap = 0; + + f_string_dynamic_t cache = f_string_dynamic_t_initialize; + f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize; + + f_number_unsigned_t indexes_array[5] = { 0, 1, 2, 3, 4 }; + indexes.array = indexes_array; + indexes.used = 5; + + { + const f_status_t status = f_random_array_shuffle(0, 0, sizeof(f_number_unsigned_t), &cache, (void *) indexes.array); + assert_int_equal(status, F_data_not); + } + + if (cache.string) free(cache.string); +} + +void test__f_random_array_shuffle__returns_too_small(void **state) { + + mock_unwrap = 0; + + f_string_dynamic_t cache = f_string_dynamic_t_initialize; + f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize; + + f_number_unsigned_t indexes_array[5] = { 0, 1, 2, 3, 4 }; + indexes.array = indexes_array; + indexes.used = 5; + + { + const f_status_t status = f_random_array_shuffle(0, indexes.used, 0, &cache, (void *) indexes.array); + assert_int_equal(status, F_status_set_error(F_too_small)); + } + + if (cache.string) free(cache.string); +} + +void test__f_random_array_shuffle__works(void **state) { + + mock_unwrap = 1; + + f_string_dynamic_t cache = f_string_dynamic_t_initialize; + f_number_unsigneds_t indexes = f_number_unsigneds_t_initialize; + + f_number_unsigned_t indexes_array[5] = { 0, 1, 2, 3, 4 }; + indexes.array = indexes_array; + indexes.used = 5; + + uint8_t unchanged = F_true; + const uint8_t tries = 20; + + for (uint8_t i = 0; i < tries; ++i) { + + const f_status_t status = f_random_array_shuffle(0, indexes.used, sizeof(f_number_unsigned_t), &cache, (void *) indexes.array); + assert_int_equal(status, F_okay); + + uint8_t founds[] = { F_false, F_false, F_false, F_false, F_false }; + + for (uint8_t j = 0; j < indexes.used; ++j) { + founds[indexes.array[j]] = F_true; + + if (indexes.array[j] > indexes.used) { + printf("[ ERROR ] --- value (%lu) at index %d is too large.\n", indexes.array[j], j); + } + + assert_true(indexes.array[j] <= indexes.used); + } // for + + for (uint8_t j = 0; j < indexes.used; ++j) { + + if (!founds[j]) { + printf("[ ERROR ] --- Did not find index %d, value=%lu.\n", j, indexes.array[j]); + } + + assert_int_equal(founds[j], F_true); + } // for + + // Because things are random, the values could randomly be the same { 0, 1, 2, 3, 4 }. + // Try multiple times to get a different value and if a different order is found, then break. + for (uint8_t j = 0; j < indexes.used; ++j) { + + if (indexes.array[j] != j) { + unchanged = F_false; + + break; + } + } // for + } // for + + if (unchanged) { + printf("[ ERROR ] --- Failed to shuffle to a different result at least once in %d tries.\n", tries); + } + + assert_false(unchanged); + + if (cache.string) free(cache.string); +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_0/f_random/tests/unit/c/test-random-array_shuffle.h b/level_0/f_random/tests/unit/c/test-random-array_shuffle.h new file mode 100644 index 0000000..2a7c61e --- /dev/null +++ b/level_0/f_random/tests/unit/c/test-random-array_shuffle.h @@ -0,0 +1,48 @@ +/** + * FLL - Level 0 + * + * Project: Random + * API Version: 0.7 + * Licenses: lgpl-2.1-or-later + * + * Test the array shuffle function in the random project. + */ +#ifndef _TEST__F_random_array_shuffle_h +#define _TEST__F_random_array_shuffle_h + +/** + * Test that the function fails. + * + * @see f_random_array_shuffle() + */ +extern void test__f_random_array_shuffle__fails(void **state); + +/** + * Test that the function correctly fails on invalid parameter. + * + * @see f_random_array_shuffle() + */ +extern void test__f_random_array_shuffle__parameter_checking(void **state); + +/** + * Test that the function returns F_data_not. + * + * @see f_random_array_shuffle() + */ +extern void test__f_random_array_shuffle__returns_data_not(void **state); + +/** + * Test that the function returns F_too_small (with error bit). + * + * @see f_random_array_shuffle() + */ +extern void test__f_random_array_shuffle__returns_too_small(void **state); + +/** + * Test that the function works. + * + * @see f_random_array_shuffle() + */ +extern void test__f_random_array_shuffle__works(void **state); + +#endif // _TEST__F_random_array_shuffle_h diff --git a/level_0/f_random/tests/unit/c/test-random-get.c b/level_0/f_random/tests/unit/c/test-random-get.c index 3ad505b..62a1d3d 100644 --- a/level_0/f_random/tests/unit/c/test-random-get.c +++ b/level_0/f_random/tests/unit/c/test-random-get.c @@ -1,5 +1,4 @@ #include "test-random.h" -#include "test-random-get.h" #ifdef __cplusplus extern "C" { @@ -7,6 +6,8 @@ extern "C" { void test__f_random_get__works(void **state) { + mock_unwrap = 0; + const long mocked_data = 1234; { @@ -24,6 +25,8 @@ void test__f_random_get__works(void **state) { void test__f_random_get__parameter_checking(void **state) { + mock_unwrap = 0; + { const f_status_t status = f_random_get(0); diff --git a/level_0/f_random/tests/unit/c/test-random-read.c b/level_0/f_random/tests/unit/c/test-random-read.c index 9c33377..769c442 100644 --- a/level_0/f_random/tests/unit/c/test-random-read.c +++ b/level_0/f_random/tests/unit/c/test-random-read.c @@ -1,5 +1,4 @@ #include "test-random.h" -#include "test-random-read.h" #ifdef __cplusplus extern "C" { @@ -7,6 +6,8 @@ extern "C" { void test__f_random_read__fails(void **state) { + mock_unwrap = 0; + int errnos[] = { EAGAIN, EFAULT, @@ -37,6 +38,10 @@ void test__f_random_read__fails(void **state) { const f_status_t status = f_random_read(0, 4, &buffer_ptr, &total); + if (status != F_status_set_error(statuss[i])) { + printf("[ ERROR ] --- At index %d, first loop.\n", i); + } + assert_int_equal(status, F_status_set_error(statuss[i])); } // for @@ -51,12 +56,18 @@ void test__f_random_read__fails(void **state) { const f_status_t status = f_random_read(0, 4, &buffer_ptr, 0); + if (status != F_status_set_error(statuss[i])) { + printf("[ ERROR ] --- At index %d, second loop.\n", i); + } + assert_int_equal(status, F_status_set_error(statuss[i])); } // for } void test__f_random_read__works(void **state) { + mock_unwrap = 0; + const f_number_unsigned_t length = 4; { @@ -103,6 +114,8 @@ void test__f_random_read__works(void **state) { void test__f_random_read__parameter_checking(void **state) { + mock_unwrap = 0; + { const f_status_t status = f_random_read(0, 0, 0, 0); diff --git a/level_0/f_random/tests/unit/c/test-random-seed.c b/level_0/f_random/tests/unit/c/test-random-seed.c index 2edf1a3..a105f45 100644 --- a/level_0/f_random/tests/unit/c/test-random-seed.c +++ b/level_0/f_random/tests/unit/c/test-random-seed.c @@ -1,5 +1,4 @@ #include "test-random.h" -#include "test-random-seed.h" #ifdef __cplusplus extern "C" { @@ -7,6 +6,8 @@ extern "C" { void test__f_random_seed__fails(void **state) { + mock_unwrap = 0; + int errnos[] = { EAGAIN, EFAULT, @@ -33,12 +34,18 @@ void test__f_random_seed__fails(void **state) { const f_status_t status = f_random_seed(0); + if (status != F_status_set_error(statuss[i])) { + printf("[ ERROR ] --- At index %d.\n", i); + } + assert_int_equal(status, F_status_set_error(statuss[i])); } // for } void test__f_random_seed__works(void **state) { + mock_unwrap = 0; + const f_number_unsigned_t length = 4; { diff --git a/level_0/f_random/tests/unit/c/test-random-seed_set.c b/level_0/f_random/tests/unit/c/test-random-seed_set.c index 928c7bf..fffbc45 100644 --- a/level_0/f_random/tests/unit/c/test-random-seed_set.c +++ b/level_0/f_random/tests/unit/c/test-random-seed_set.c @@ -1,5 +1,4 @@ #include "test-random.h" -#include "test-random-seed_set.h" #ifdef __cplusplus extern "C" { @@ -7,6 +6,8 @@ extern "C" { void test__f_random_seed_set__works(void **state) { + mock_unwrap = 0; + { const f_status_t status = f_random_seed_set(1); diff --git a/level_0/f_random/tests/unit/c/test-random.c b/level_0/f_random/tests/unit/c/test-random.c index f5d04b5..990dcc8 100644 --- a/level_0/f_random/tests/unit/c/test-random.c +++ b/level_0/f_random/tests/unit/c/test-random.c @@ -19,15 +19,20 @@ int setdown(void **state) { int main(void) { const struct CMUnitTest tests[] = { + cmocka_unit_test(test__f_random_array_shuffle__fails), cmocka_unit_test(test__f_random_read__fails), cmocka_unit_test(test__f_random_seed__fails), + cmocka_unit_test(test__f_random_array_shuffle__returns_data_not), + cmocka_unit_test(test__f_random_array_shuffle__returns_too_small), + cmocka_unit_test(test__f_random_array_shuffle__works), cmocka_unit_test(test__f_random_get__works), cmocka_unit_test(test__f_random_read__works), cmocka_unit_test(test__f_random_seed__works), cmocka_unit_test(test__f_random_seed_set__works), #ifndef _di_level_0_parameter_checking_ + cmocka_unit_test(test__f_random_array_shuffle__parameter_checking), cmocka_unit_test(test__f_random_get__parameter_checking), cmocka_unit_test(test__f_random_read__parameter_checking), diff --git a/level_0/f_random/tests/unit/c/test-random.h b/level_0/f_random/tests/unit/c/test-random.h index 546118d..35b0e4a 100644 --- a/level_0/f_random/tests/unit/c/test-random.h +++ b/level_0/f_random/tests/unit/c/test-random.h @@ -26,6 +26,7 @@ #include "mock-random.h" // Test includes. +#include "test-random-array_shuffle.h" #include "test-random-get.h" #include "test-random-read.h" #include "test-random-seed.h" -- 1.8.3.1