From 24dfab91c8da2a2db210ed57bf17f13eba75cd3b Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 3 Apr 2026 17:58:16 +0200 Subject: [PATCH 1/9] Update framework with TEST_STRSTR and fork helper using a regular file Signed-off-by: Gilles Peskine --- framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework b/framework index dff9da0443..cd6fb181a2 160000 --- a/framework +++ b/framework @@ -1 +1 @@ -Subproject commit dff9da04438d712f7647fd995bc90fadd0c0e2ce +Subproject commit cd6fb181a2d1ec0718958ce3a675aae3cf4216df From 3a45eae762dc02531051f29d07d6e0f8b2e2732d Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 3 Apr 2026 11:40:12 +0200 Subject: [PATCH 2/9] Create a metatest test suite `test_suite_metatest` is intended for tests of test code. We generally don't test the test code, but there are a few cases of test code that is especially complicated or fragile where it's good to have tests to ensure that failures are captured and are reported accurately. This complements the existing test program `metatest.c`. The test program can validate failure behavior that crashes the program (e.g. sanitizer ensuring a crash on an invalid pointer dereference) or that relies on exit-time checks (e.g. leak detector), but it's not good at asserting anything beyond the fact that a failure was detected. The unit test framework has a better structure for metatests that are shaped as a unit test with assertions, where we want to report failures of the metatest assertions accurately, but it can't allow crashes. Even though the code tested by `test_suite_metatest` is version-independent and located in the framework repository, we keep `test_suite_metatest` in a consuming branch, because we don't have any testing inside the framework, and we don't look for unit tests inside the framework. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_metatest.basic.data | 50 ++++++ tests/suites/test_suite_metatest.function | 163 ++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 tests/suites/test_suite_metatest.basic.data create mode 100644 tests/suites/test_suite_metatest.function diff --git a/tests/suites/test_suite_metatest.basic.data b/tests/suites/test_suite_metatest.basic.data new file mode 100644 index 0000000000..61d8895a44 --- /dev/null +++ b/tests/suites/test_suite_metatest.basic.data @@ -0,0 +1,50 @@ +metatest: pass +metatest_simple:DO_PASS:0:MBEDTLS_TEST_RESULT_SUCCESS:"" + +metatest: don't skip +metatest_simple:DO_ASSUME:1:MBEDTLS_TEST_RESULT_SUCCESS:"" + +metatest: skip +metatest_simple:DO_ASSUME:0:MBEDTLS_TEST_RESULT_SKIPPED:"param" + +metatest: fail +metatest_simple:DO_FAIL:0:MBEDTLS_TEST_RESULT_FAILED:"hello" + +metatest: first failure sets the message +metatest_simple:DO_FAIL_TWICE:0:MBEDTLS_TEST_RESULT_FAILED:"first failure" + +metatest: assert true +metatest_simple:DO_ASSERT:1:MBEDTLS_TEST_RESULT_SUCCESS:"" + +metatest: assert false +metatest_simple:DO_ASSERT:0:MBEDTLS_TEST_RESULT_FAILED:"param" + +metatest: 42==42 pass +metatest_simple:DO_EQUAL_42:42:MBEDTLS_TEST_RESULT_SUCCESS:"" + +metatest: 41==42 fail +metatest_simple:DO_EQUAL_42:41:MBEDTLS_TEST_RESULT_FAILED:" == " + +metatest: 42<=42 signed pass +metatest_simple:DO_LE_S_42:42:MBEDTLS_TEST_RESULT_SUCCESS:"" + +metatest: 41<=42 signed pass +metatest_simple:DO_LE_S_42:41:MBEDTLS_TEST_RESULT_SUCCESS:"" + +metatest: 43<=42 signed fail +metatest_simple:DO_LE_S_42:43:MBEDTLS_TEST_RESULT_FAILED:" <= " + +metatest: -1<=42 signed pass +metatest_simple:DO_LE_S_42:-1:MBEDTLS_TEST_RESULT_SUCCESS:"" + +metatest: 42<=42 unsigned pass +metatest_simple:DO_LE_U_42:42:MBEDTLS_TEST_RESULT_SUCCESS:"" + +metatest: 41<=42 unsigned pass +metatest_simple:DO_LE_U_42:41:MBEDTLS_TEST_RESULT_SUCCESS:"" + +metatest: 43<=42 unsigned fail +metatest_simple:DO_LE_U_42:43:MBEDTLS_TEST_RESULT_FAILED:" <= " + +metatest: -1<=42 unsigned fail +metatest_simple:DO_LE_U_42:-1:MBEDTLS_TEST_RESULT_FAILED:" <= " diff --git a/tests/suites/test_suite_metatest.function b/tests/suites/test_suite_metatest.function new file mode 100644 index 0000000000..d5b71fb822 --- /dev/null +++ b/tests/suites/test_suite_metatest.function @@ -0,0 +1,163 @@ +/* BEGIN_HEADER */ + +/* This test suite contains test of some features of the test framework. + * + * Overview of metatests + * --------------------- + * + * As a general rule, we don't test the tests. However, a few parts of + * the test framework are fragile or complicated, so we want to have + * some validation that they behave as expected. This is what metatests + * are for. + * + * Mostly, we want to make sure that failure conditions are expected. + * Occasionally we also check the reported failure reason. + * + * We have metatests in two places: + * + * - `tests/suites/test_suite_metatest.function` contains test code that is + * unit tests with the usual reporting, with the particularity that what + * they are testing is some code in the test framework, rather than + * some library code. + * - `framework/test/programs/metatest.c` checks that certain failure + * conditions are properly detected, without caring how they are detected: + * marking a test as failed, exiting with a nonzero status, or crashing. + * + * How to decide where to put a metatest: + * + * - If it reacts to failures by crashing or exiting, it must go in + * `metatest.c`. + * - If it relies on a specific linking or execution context and not just + * compile-time choices, it must go in `metatest.c` with a suitable + * platform indication. + * - If it validates more than just whether an expected failure is + * detected, it should go in the test suite, which has better tools + * for reporting how a metatest failed, and has some infrastructure in + * place for inspecting how the test code reports a failure. + * + * General structure of tests in `test_suite_metatest` + * --------------------------------------------------- + * + * The test framework records failures through a global variable that + * contains the test result. A test is initially in the passing state, + * and can change to a skipping or failed state if a test assertion fails. + * + * Metatests are structured in three parts: + * + * 1. Do things that may cause the test case to be marked as failed or skipped. + * 2. Call conclude() (generally just after the `exit:` label in the + * test entry point function). This function saves the result of the + * code under test, and overwrites the status inside the test framework + * to reflect whether the code under test had the expected pass/skip/fail + * result. + * 3. Optionally call expect_message() and similar functions to check the + * reporting information in the result of the code under test. + */ + +#include +#include + +typedef enum { + DO_PASS, + DO_ASSUME, + DO_FAIL, + DO_FAIL_TWICE, + DO_ASSERT, + DO_EQUAL_42, + DO_LE_S_42, + DO_LE_U_42, +} metatest_simple_instruction_t; + +static mbedtls_test_info_t saved_info; + +/* Call this function after running the code under test. + * + * This function overwrites the result of the current test to a passing state. + * Then it asserts whether the result of the code under test is + * `expected_result`. + * + * The result of the code under test is saved so that you can call + * functions like expect_message() to check if failure reporting contains + * the expected information. + */ +static void conclude(int expected_result) +{ + mbedtls_test_info_save(&saved_info); + mbedtls_test_info_reset(); + + TEST_EQUAL(saved_info.result, expected_result); + +exit: + ; +} + +static void expect_message(const char *substring) +{ + if (substring[0] != 0) { + TEST_ASSERT(saved_info.test != NULL); + TEST_ASSERT(strstr(saved_info.test, substring) != NULL); + } + +exit: + ; +} + +static void expect_file(const char *substring) +{ + if (saved_info.result != MBEDTLS_TEST_RESULT_SUCCESS) { + TEST_ASSERT(saved_info.test != NULL); + TEST_ASSERT(strstr(saved_info.filename, substring) != NULL); + } + +exit: + ; +} + +/* END_HEADER */ + +/* BEGIN_CASE */ +void metatest_simple(int instruction, int param, + int expected_result, const char *expected_substring) +{ + const char *message = "hello"; + + switch (instruction) { + case DO_PASS: + break; + case DO_ASSUME: + TEST_ASSUME(param); + break; + case DO_FAIL: + TEST_FAIL(message); + break; + case DO_FAIL_TWICE: + mbedtls_test_fail("first failure", __LINE__, __FILE__); + TEST_FAIL("cursed, failed again!"); + break; + case DO_ASSERT: + TEST_ASSERT(param); + break; + case DO_EQUAL_42: + TEST_EQUAL(param, 42); + break; + case DO_LE_S_42: + TEST_LE_S(param, 42); + break; + case DO_LE_U_42: + TEST_LE_U((unsigned) param, 42); + break; + default: + /* This is a failure to set up the metatest conditions. + * So don't jump to exit. */ + mbedtls_test_fail("unknown instruction", __LINE__, __FILE__); + return; + } + +exit: + conclude(expected_result); + expect_message(expected_substring); + if (expected_result != MBEDTLS_TEST_RESULT_SUCCESS) { + expect_file(__FILE__); + } +} +/* END_CASE */ From c982f90c1b260436f701e2f6bed15ca87eb2da15 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 3 Apr 2026 15:19:51 +0200 Subject: [PATCH 3/9] Test mbedtls_test_fork_run_child: normal child behavior, exit, signal Signed-off-by: Gilles Peskine --- tests/suites/test_suite_metatest.fork.data | 35 +++++ tests/suites/test_suite_metatest.function | 157 +++++++++++++++++++-- 2 files changed, 184 insertions(+), 8 deletions(-) create mode 100644 tests/suites/test_suite_metatest.fork.data diff --git a/tests/suites/test_suite_metatest.fork.data b/tests/suites/test_suite_metatest.fork.data new file mode 100644 index 0000000000..11f5df8e11 --- /dev/null +++ b/tests/suites/test_suite_metatest.fork.data @@ -0,0 +1,35 @@ +metatest fork: pass, 0/0 bytes +metatest_fork_output:MBEDTLS_TEST_RESULT_SUCCESS:"":0:0:"" + +metatest fork: pass, 0/42 bytes +metatest_fork_output:MBEDTLS_TEST_RESULT_SUCCESS:"":42:0:"" + +metatest fork: pass, 41/42 bytes +metatest_fork_output:MBEDTLS_TEST_RESULT_SUCCESS:"49276d20426f6262696e20546872656164626172652c2061726520796f75206d79206d6f746865723f":1:0:"" + +metatest fork: pass, 42/42 bytes +metatest_fork_output:MBEDTLS_TEST_RESULT_SUCCESS:"0049276d20426f6262696e20546872656164626172652c2061726520796f75206d79206d6f746865723f":0:0:"" + +metatest fork: large output +metatest_fork_large_output:2 * PIPE_BUF + +metatest fork: report excess length +metatest_fork_output:MBEDTLS_TEST_RESULT_SUCCESS:"0049276d20426f6262696e20546872656164626172652c2061726520796f75206d79206d6f746865723f":0:1:" <= " + +metatest fork: skip +metatest_fork_output:MBEDTLS_TEST_RESULT_SKIPPED:"":0:0:"metatesting skipping in child" + +metatest fork: fail +metatest_fork_output:MBEDTLS_TEST_RESULT_FAILED:"":0:0:"metatesting failure in child" + +metatest fork: SIGHUP +metatest_fork_die:SIGHUP:-1:"wstatus" + +metatest fork: SIGKILL +metatest_fork_die:SIGKILL:-1:"wstatus" + +metatest fork: exit(0) +metatest_fork_die:0:0:"died without reporting" + +metatest fork: exit(1) +metatest_fork_die:0:1:"wstatus" diff --git a/tests/suites/test_suite_metatest.function b/tests/suites/test_suite_metatest.function index d5b71fb822..3a4cd01a51 100644 --- a/tests/suites/test_suite_metatest.function +++ b/tests/suites/test_suite_metatest.function @@ -57,6 +57,13 @@ #include #include +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) +#include +#include +#include +#include +#endif + typedef enum { DO_PASS, DO_ASSUME, @@ -93,10 +100,7 @@ exit: static void expect_message(const char *substring) { - if (substring[0] != 0) { - TEST_ASSERT(saved_info.test != NULL); - TEST_ASSERT(strstr(saved_info.test, substring) != NULL); - } + TEST_STRSTR(saved_info.test, substring); exit: ; @@ -104,15 +108,60 @@ exit: static void expect_file(const char *substring) { - if (saved_info.result != MBEDTLS_TEST_RESULT_SUCCESS) { - TEST_ASSERT(saved_info.test != NULL); - TEST_ASSERT(strstr(saved_info.filename, substring) != NULL); - } + TEST_STRSTR(saved_info.filename, substring); exit: ; } +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) +typedef struct { + mbedtls_test_result_t result; + unsigned char *data; + size_t data_size; + size_t reported_length; + int signal; + int exit; +} child_instructions_t; + +static const child_instructions_t child_instructions_init = { + .result = 0, + .data = NULL, + .data_size = 0, + .reported_length = 0, + .signal = 0, + .exit = -1, +}; + +/* Child callback function for testing mbedtls_test_fork_run_child(). */ +static void child_callback(void *param, + unsigned char *output, size_t output_size, + size_t *output_length) +{ + child_instructions_t *instructions = param; + + (void) output_size; + if (instructions->data_size > 0) { + memcpy(output, instructions->data, instructions->data_size); + } + *output_length = instructions->reported_length; + + if (instructions->result == MBEDTLS_TEST_RESULT_SKIPPED) { + TEST_ASSUME(!"metatesting skipping in child"); + } else if (instructions->result == MBEDTLS_TEST_RESULT_FAILED) { + TEST_FAIL("metatesting failure in child"); + } + +exit: + if (instructions->signal > 0) { + kill(getpid(), instructions->signal); + } + if (instructions->exit >= 0) { + _exit(instructions->exit); + } +} +#endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */ + /* END_HEADER */ /* BEGIN_CASE */ @@ -161,3 +210,95 @@ exit: } } /* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_PLATFORM_IS_UNIXLIKE */ +void metatest_fork_output(int result, + data_t *data, int buffer_size_delta, + int reported_length_delta, + const char *failure_substring) +{ + size_t output_size = data->len + buffer_size_delta; + unsigned char *output = NULL; + int expected_result = result; + if (result == MBEDTLS_TEST_RESULT_SUCCESS && failure_substring[0] != 0) { + /* We're telling the child to succeed, but we're setting up for + * a failure in the test code. */ + expected_result = MBEDTLS_TEST_RESULT_FAILED; + } + + TEST_CALLOC(output, output_size); + + child_instructions_t instructions = child_instructions_init; + instructions.result = result; + instructions.data = data->x; + instructions.data_size = data->len; + instructions.reported_length = data->len + reported_length_delta; + size_t output_length = SIZE_MAX; + int ret = mbedtls_test_fork_run_child(child_callback, &instructions, + output, output_size, &output_length); + + conclude(expected_result); + expect_message(failure_substring); + if (expected_result == MBEDTLS_TEST_RESULT_SUCCESS) { + TEST_EQUAL(ret, 0); + TEST_MEMORY_COMPARE(data->x, data->len, + output, output_length); + } else { + TEST_ASSERT(ret != 0); + TEST_EQUAL(output_length, 0); + } + +exit: + mbedtls_free(output); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_PLATFORM_IS_UNIXLIKE */ +void metatest_fork_large_output(int size) +{ + child_instructions_t instructions = child_instructions_init; + unsigned char *output = NULL; + TEST_CALLOC(instructions.data, size); + instructions.data_size = size; + instructions.reported_length = size; + memset(instructions.data, 'c', size); + TEST_CALLOC(output, size); + + size_t output_length = SIZE_MAX; + int ret = mbedtls_test_fork_run_child(child_callback, &instructions, + output, size, &output_length); + + conclude(MBEDTLS_TEST_RESULT_SUCCESS); + TEST_EQUAL(ret, 0); + TEST_MEMORY_COMPARE(instructions.data, size, + output, output_length); + +exit: + mbedtls_free(output); + mbedtls_free(instructions.data); +} +/* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_PLATFORM_IS_UNIXLIKE */ +void metatest_fork_die(int signum, int exit_code, + const char *failure_substring) +{ + child_instructions_t instructions = child_instructions_init; + unsigned char data[] = "abc"; + unsigned char output[sizeof(data) - 1]; + instructions.data = data; + instructions.data_size = sizeof(output); + instructions.reported_length = sizeof(output); + instructions.signal = signum; + instructions.exit = exit_code; + + size_t output_length = SIZE_MAX; + int ret = mbedtls_test_fork_run_child(child_callback, &instructions, + output, sizeof(output), &output_length); + + conclude(MBEDTLS_TEST_RESULT_FAILED); + expect_message(failure_substring); + TEST_ASSERT(ret != 0); + TEST_EQUAL(output_length, 0); +} +/* END_CASE */ From f2d48e8da7918a036603a056fdff9d6ec8ed77bf Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 3 Apr 2026 17:55:34 +0200 Subject: [PATCH 4/9] mbedtls_test_fork_run_child: test common system failures Test what happens on plausible system failures: running out of resources to fork a process (memory, process table), to open a file (memory, file descriptor table) or to write to a file (disk space). Signed-off-by: Gilles Peskine --- tests/suites/test_suite_metatest.fork.data | 19 +++++ tests/suites/test_suite_metatest.function | 83 ++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/tests/suites/test_suite_metatest.fork.data b/tests/suites/test_suite_metatest.fork.data index 11f5df8e11..377c19cdfe 100644 --- a/tests/suites/test_suite_metatest.fork.data +++ b/tests/suites/test_suite_metatest.fork.data @@ -33,3 +33,22 @@ metatest_fork_die:0:0:"died without reporting" metatest fork: exit(1) metatest_fork_die:0:1:"wstatus" + +metatest fork: fault fork() +metatest_fork_system_failure:FORK_FAIL_FORK:"pid" + +metatest fork: fault parent creating temporary file +metatest_fork_system_failure:FORK_FAIL_OPEN:"file != NULL" + +metatest fork: fault parent preparing temporary file +# The write failure may be detected in fwrite() or fflush(). +metatest_fork_system_failure:FORK_FAIL_WRITE_INIT:"file" + +metatest fork: fault child writing result +# The child fails to write its result, so it exits with a nonzero status. +metatest_fork_system_failure:FORK_FAIL_WRITE_RESULT:"wstatus" + +metatest fork: fault child writing output +# The write failure may be detected in fwrite() or fflush(). +metatest_fork_system_failure:FORK_FAIL_WRITE_OUTPUT:"file" + diff --git a/tests/suites/test_suite_metatest.function b/tests/suites/test_suite_metatest.function index 3a4cd01a51..b920fa9882 100644 --- a/tests/suites/test_suite_metatest.function +++ b/tests/suites/test_suite_metatest.function @@ -62,6 +62,7 @@ #include #include #include +#include #endif typedef enum { @@ -115,6 +116,14 @@ exit: } #if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) +typedef enum { + FORK_FAIL_FORK, + FORK_FAIL_OPEN, + FORK_FAIL_WRITE_INIT, + FORK_FAIL_WRITE_RESULT, + FORK_FAIL_WRITE_OUTPUT, +} fork_system_failure_t; + typedef struct { mbedtls_test_result_t result; unsigned char *data; @@ -122,6 +131,7 @@ typedef struct { size_t reported_length; int signal; int exit; + int fail_write_result; } child_instructions_t; static const child_instructions_t child_instructions_init = { @@ -131,6 +141,7 @@ static const child_instructions_t child_instructions_init = { .reported_length = 0, .signal = 0, .exit = -1, + .fail_write_result = 0, }; /* Child callback function for testing mbedtls_test_fork_run_child(). */ @@ -152,6 +163,11 @@ static void child_callback(void *param, TEST_FAIL("metatesting failure in child"); } + if (instructions->fail_write_result) { + extern int mbedtls_test_fork_child_fd; + close(mbedtls_test_fork_child_fd); + } + exit: if (instructions->signal > 0) { kill(getpid(), instructions->signal); @@ -302,3 +318,70 @@ void metatest_fork_die(int signum, int exit_code, TEST_EQUAL(output_length, 0); } /* END_CASE */ + +/* BEGIN_CASE depends_on:MBEDTLS_PLATFORM_IS_UNIXLIKE */ +void metatest_fork_system_failure(int fault, + const char *failure_substring) +{ + child_instructions_t instructions = child_instructions_init; + unsigned char data[] = "abc"; + unsigned char output[sizeof(data) - 1]; + instructions.data = data; + instructions.data_size = sizeof(output); + instructions.reported_length = sizeof(output); + int resource = -1; + unsigned long limit = 0; + struct rlimit saved_limit, new_limit; + void (*saved_handler)(int) = SIG_DFL; + + switch (fault) { + case FORK_FAIL_FORK: + resource = RLIMIT_NPROC; + break; + case FORK_FAIL_OPEN: + resource = RLIMIT_NOFILE; + break; + case FORK_FAIL_WRITE_INIT: + resource = RLIMIT_FSIZE; + limit = sizeof(mbedtls_test_info_t) - 1; + break; + case FORK_FAIL_WRITE_RESULT: + instructions.fail_write_result = 1; + break; + case FORK_FAIL_WRITE_OUTPUT: + resource = RLIMIT_FSIZE; + limit = sizeof(mbedtls_test_info_t); + break; + default: + TEST_FAIL("Unknown fault in test data"); + } + + if (resource >= 0) { + TEST_ASSERT_ERRNO(getrlimit(resource, &saved_limit) == 0); + new_limit = saved_limit; + new_limit.rlim_cur = limit; + TEST_ASSERT_ERRNO(setrlimit(resource, &new_limit) == 0); + } + if (resource == RLIMIT_FSIZE) { + saved_handler = signal(SIGXFSZ, SIG_IGN); + TEST_ASSERT_ERRNO(saved_handler != SIG_ERR); + } + + size_t output_length = SIZE_MAX; + int ret = mbedtls_test_fork_run_child(child_callback, &instructions, + output, sizeof(output), &output_length); + + conclude(MBEDTLS_TEST_RESULT_FAILED); + expect_message(failure_substring); + TEST_ASSERT(ret != 0); + TEST_EQUAL(output_length, 0); + +exit: + if (resource >= 0) { + setrlimit(resource, &saved_limit); + } + if (resource == RLIMIT_FSIZE && saved_handler != SIG_ERR) { + signal(SIGXFSZ, saved_handler); + } +} +/* END_CASE */ From b25dcea0c23da67687f0b6e77483041f81301e6d Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 5 Apr 2026 16:04:24 +0200 Subject: [PATCH 5/9] Fix the build on non-UNIXLIKE platforms Signed-off-by: Gilles Peskine --- tests/suites/test_suite_metatest.function | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/suites/test_suite_metatest.function b/tests/suites/test_suite_metatest.function index b920fa9882..9a9e231f9b 100644 --- a/tests/suites/test_suite_metatest.function +++ b/tests/suites/test_suite_metatest.function @@ -115,7 +115,8 @@ exit: ; } -#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) +/* These enum constants are used in test data, so they must be defined + * unconditionally. */ typedef enum { FORK_FAIL_FORK, FORK_FAIL_OPEN, @@ -124,6 +125,7 @@ typedef enum { FORK_FAIL_WRITE_OUTPUT, } fork_system_failure_t; +#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE) typedef struct { mbedtls_test_result_t result; unsigned char *data; @@ -176,6 +178,22 @@ exit: _exit(instructions->exit); } } +#else /* MBEDTLS_PLATFORM_IS_UNIXLIKE */ + +/* Identifiers used in test data must be defined in all configurations, even + * if the test function that would use the test case is disabled. + * So make sure that some identifiers are defined. + */ +#if !defined(PIPE_BUF) +#define PIPE_BUF 512 +#endif +#if !defined(SIGHUP) +#define SIGHUP 1 +#endif +#if !defined(SIGKILL) +#define SIGKILL 9 +#endif + #endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */ /* END_HEADER */ From 9e9f91379fe72dfb2a88d4e50609db448f572f30 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Sun, 5 Apr 2026 16:04:43 +0200 Subject: [PATCH 6/9] Fix the build on FreeBSD Signed-off-by: Gilles Peskine --- tests/suites/test_suite_metatest.function | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/suites/test_suite_metatest.function b/tests/suites/test_suite_metatest.function index 9a9e231f9b..d624f7a76c 100644 --- a/tests/suites/test_suite_metatest.function +++ b/tests/suites/test_suite_metatest.function @@ -62,8 +62,11 @@ #include #include #include + +#if defined(__linux__) #include #endif +#endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */ typedef enum { DO_PASS, @@ -337,7 +340,15 @@ void metatest_fork_die(int signum, int exit_code, } /* END_CASE */ -/* BEGIN_CASE depends_on:MBEDTLS_PLATFORM_IS_UNIXLIKE */ +/* The system failure metatests cause a system function to fail, and assert + * that the test support code reacted as desired. Most of these metatests + * rely on resource limits, which behave slightly differently on different + * Unix variants. To keep things simple, only build and run these tests + * on Linux. The code under test only use portable Unix features, so coverage + * on Linux gives us sufficient confidence that it will also behave correctly + * on other Unix variants. + */ +/* BEGIN_CASE depends_on:MBEDTLS_PLATFORM_IS_UNIXLIKE:__linux__ */ void metatest_fork_system_failure(int fault, const char *failure_substring) { From aef97012ef1ea069c6b5c948904683f741b7b4f6 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Thu, 9 Apr 2026 11:48:23 +0200 Subject: [PATCH 7/9] Make signal testing more robust Ensure that the signal we're testing with isn't ignored. It might be masked though, depending on the surrounding infrastructure. Test with SIGTERM rather than SIGHUP: it's less likely to be masked. This fixes a failure of the SIGHUP test case on FreeBSD in our CI. Signed-off-by: Gilles Peskine --- tests/suites/test_suite_metatest.fork.data | 4 ++-- tests/suites/test_suite_metatest.function | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/suites/test_suite_metatest.fork.data b/tests/suites/test_suite_metatest.fork.data index 377c19cdfe..66bb72864f 100644 --- a/tests/suites/test_suite_metatest.fork.data +++ b/tests/suites/test_suite_metatest.fork.data @@ -22,8 +22,8 @@ metatest_fork_output:MBEDTLS_TEST_RESULT_SKIPPED:"":0:0:"metatesting skipping in metatest fork: fail metatest_fork_output:MBEDTLS_TEST_RESULT_FAILED:"":0:0:"metatesting failure in child" -metatest fork: SIGHUP -metatest_fork_die:SIGHUP:-1:"wstatus" +metatest fork: SIGTERM +metatest_fork_die:SIGTERM:-1:"wstatus" metatest fork: SIGKILL metatest_fork_die:SIGKILL:-1:"wstatus" diff --git a/tests/suites/test_suite_metatest.function b/tests/suites/test_suite_metatest.function index d624f7a76c..eba2ca3ed1 100644 --- a/tests/suites/test_suite_metatest.function +++ b/tests/suites/test_suite_metatest.function @@ -175,7 +175,8 @@ static void child_callback(void *param, exit: if (instructions->signal > 0) { - kill(getpid(), instructions->signal); + signal(instructions->signal, SIG_DFL); + raise(instructions->signal); } if (instructions->exit >= 0) { _exit(instructions->exit); @@ -190,8 +191,8 @@ exit: #if !defined(PIPE_BUF) #define PIPE_BUF 512 #endif -#if !defined(SIGHUP) -#define SIGHUP 1 +#if !defined(SIGTERM) +#define SIGTERM 15 #endif #if !defined(SIGKILL) #define SIGKILL 9 From 156eb2def896d799222153f6ae6d092e2ffade87 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 10 Apr 2026 17:19:20 +0200 Subject: [PATCH 8/9] Improve robustness of tests using signals if the signal is blocked on entry Signed-off-by: Gilles Peskine --- tests/suites/test_suite_metatest.function | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/suites/test_suite_metatest.function b/tests/suites/test_suite_metatest.function index eba2ca3ed1..a7de0ca7ed 100644 --- a/tests/suites/test_suite_metatest.function +++ b/tests/suites/test_suite_metatest.function @@ -155,6 +155,18 @@ static void child_callback(void *param, size_t *output_length) { child_instructions_t *instructions = param; + sigset_t sigs; + sigemptyset(&sigs); + + /* If we're supposed to signal ourselves at the end, arrange to block + * the signal during this function (to avoid weird behavior if the signal + * is already blocked but pending for some reason), and unblock it later. + */ + if (instructions->signal > 0) { + TEST_ASSERT_ERRNO(sigaddset(&sigs, instructions->signal) == 0); + TEST_ASSERT_ERRNO(sigprocmask(SIG_BLOCK, &sigs, NULL) == 0); + TEST_ASSERT_ERRNO(signal(instructions->signal, SIG_DFL)); + } (void) output_size; if (instructions->data_size > 0) { @@ -175,7 +187,12 @@ static void child_callback(void *param, exit: if (instructions->signal > 0) { - signal(instructions->signal, SIG_DFL); + if (sigprocmask(SIG_UNBLOCK, &sigs, NULL) != 0) { + /* We couldn't unblock the signal. This is really weird. + * We don't have much of a way to report what's happening. + * So exit with a unique status, which the parent can report. */ + _exit(128); + } raise(instructions->signal); } if (instructions->exit >= 0) { From 0ab1f463ae4926bf88c920fb400132c859f2e829 Mon Sep 17 00:00:00 2001 From: Gilles Peskine Date: Fri, 10 Apr 2026 17:21:11 +0200 Subject: [PATCH 9/9] Note that some metatests may fail if you run as root Signed-off-by: Gilles Peskine --- tests/suites/test_suite_metatest.function | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/suites/test_suite_metatest.function b/tests/suites/test_suite_metatest.function index a7de0ca7ed..de1cc7c59f 100644 --- a/tests/suites/test_suite_metatest.function +++ b/tests/suites/test_suite_metatest.function @@ -365,6 +365,10 @@ void metatest_fork_die(int signum, int exit_code, * on Linux. The code under test only use portable Unix features, so coverage * on Linux gives us sufficient confidence that it will also behave correctly * on other Unix variants. + * + * Note that some of these tests may fail if the code runs as root or similar + * (e.g. admin capability), as the resource limits may not have the normal + * effect for privileged users. */ /* BEGIN_CASE depends_on:MBEDTLS_PLATFORM_IS_UNIXLIKE:__linux__ */ void metatest_fork_system_failure(int fault,