Merge remote-tracking branch 'main' into mldsa-pqcp-driver-framework

This commit is contained in:
Gilles Peskine
2026-04-07 14:28:56 +02:00
6 changed files with 370 additions and 3 deletions
+5 -3
View File
@@ -8,9 +8,11 @@ Please write a few sentences describing the overall goals of the pull request's
Please add the numbers (or links) of the associated pull requests for consuming branches. You can omit branches where this pull request is not needed.
- [ ] **TF-PSA-Crypto PR** provided # | not required because:
- [ ] **development PR** provided # | not required because:
- [ ] **3.6 PR** provided # | not required because:
- [ ] **TF-PSA-Crypto development PR** provided # | not required because:
- [ ] **TF-PSA-Crypto 1.1 PR** provided # | not required because:
- [ ] **mbedtls development PR** provided # | not required because:
- [ ] **mbedtls 4.1 PR** provided # | not required because:
- [ ] **mbedtls 3.6 PR** provided # | not required because:
+61
View File
@@ -0,0 +1,61 @@
/** Helper functions for testing with subprocesses.
*/
/*
* Copyright The Mbed TLS Contributors
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
*/
#ifndef TEST_FORK_HELPERS_H
#define TEST_FORK_HELPERS_H
#include "test/helpers.h"
/** Type of a function to run in a child process.
*
* The function can mark the test case as failed by calling
* mbedtls_test_fail(). This information will be reported to the parent.
*
* \param param Parameter passed to the callback.
* \param[out] output Buffer for data to pass to the parent.
* This data is ignored if the test case is marked
* as failed.
* \param output_size Size of \p output in bytes.
* \param[out] output_length Number of bytes written to \p output, to be
* passed to the parent. The default is \c 0.
*/
typedef void mbedtls_test_fork_child_callback_t(
void *param,
unsigned char *output, size_t output_size, size_t *output_length);
/* Fork a child process and wait for it to collect some data.
*
* This is similar to backquotes or `$(...)` in a shell.
*
* This function blocks until the child exits.
*
* If the child marks the test as failed or skipped, the child's test
* information (test result and failure location) is propagated to the
* parent.
*
* \note Memory leak detection is disabled in the child.
*
* \param child_callback Callback function to run in the child.
* \param param Parameter to pass to the callback function.
* \param[out] child_output On success, data retrieved from the child.
* Note that the data is only available if the
* child did not mark the test case as failed
* or skipped.
* \param child_output_size Size of \p child_output in bytes.
* \param[out] child_output_length On success, the number of bytes collected
* from the child in \c child_output.
*
* \return \c 0 on success.
* A nonzero value if the test case is marked as failed or skipped.
*/
int mbedtls_test_fork_run_child(
mbedtls_test_fork_child_callback_t *child_callback,
void *param,
unsigned char *child_output, size_t child_output_size,
size_t *child_output_length);
#endif /* TEST_FORK_HELPERS_H */
+19
View File
@@ -147,6 +147,25 @@ void mbedtls_test_get_line1(char *line);
*/
void mbedtls_test_get_line2(char *line);
/**
* \brief Get a copy of the test result information.
*
* \param[out] out On output, contains a copy of the current test info.
*/
void mbedtls_test_info_save(mbedtls_test_info_t *out);
/**
* \brief Overwrite the test result information.
* This is intended for some unusual scenarios.
* You probably shouldn't use this in a test function.
*
* \param[in] replacement
* The test info to use instead of the current one.
* The function copies the data, so the pointer does
* not need to be valid after this function returns.
*/
void mbedtls_test_info_overwrite(const mbedtls_test_info_t *replacement);
#if defined(MBEDTLS_TEST_MUTEX_USAGE)
/**
* \brief Get the current mutex usage error message
+244
View File
@@ -0,0 +1,244 @@
/** \file fork_helpers.c
*
* \brief Helper functions for testing with subprocesses.
*/
/*
* Copyright The Mbed TLS Contributors
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
*/
#include "test_common.h"
#include <test/helpers.h>
#include <test/macros.h>
#if defined(MBEDTLS_PLATFORM_IS_UNIXLIKE)
#include <test/fork_helpers.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
/** Child exit code for mbedtls_test_fork_run_child().
*/
typedef enum {
/** Reporting of the child output or the child test result through
* the pipe succeeded.
*
* The content sent on the pipe has the following format:
* - [1 byte] #mbedtls_test_result_t \c test_result
* - Case \c test_result:
* - If \c test_result == #MBEDTLS_TEST_RESULT_SUCCESS:
* the output from the child body function.
* - Otherwise:
* the child failure (or skip) information, a direct write of
* a #mbedtls_test_result_t structure.
*/
CHILD_EXIT_CODE_OK = 0,
/** Something went wrong, e.g. a write error on the pipe. */
CHILD_EXIT_CODE_REPORTING_FAILED = 122,
} child_exit_code_t;
static int env_contains_substring(const char *name, const char *substring)
{
const char *value = getenv(name);
if (value == NULL) {
return 0;
} else {
return strstr(value, substring) != NULL;
}
}
static int probably_running_under_valgrind(void)
{
if (env_contains_substring("LD_PRELOAD", "/vgpreload_")) {
return 1;
}
if (env_contains_substring("DYLD_INSERT_LIBRARIES", "/vgpreload_")) {
return 1;
}
return 0;
}
#if defined(__GNUC__)
__attribute__((__noreturn__))
#endif
static void run_child(
int write_fd,
mbedtls_test_fork_child_callback_t *child_callback,
void *param,
unsigned char *buf, size_t size)
{
/* If something goes wrong while trying to report what happened
* in the child, exit with a nonzero status. */
int child_exit_code = CHILD_EXIT_CODE_REPORTING_FAILED;
/* We'll use stdio to write to the pipe, so we don't have to
* manage EINTR and such. */
FILE *file = fdopen(write_fd, "a");
size_t length = 0;
if (file == NULL) {
/* There's no way we can report anything other than the exit code.
* So we might as well quit without even running the child callback. */
goto write_done;
}
child_callback(param, buf, size, &length);
TEST_LE_U(length, size);
/* Label called `exit`: this is where TEST_ASSERT() and friends jump to. */
exit:
; // label followed by a declaration is not portable C
char result_char = mbedtls_test_get_result();
if (fputc(result_char, file) == EOF) {
goto write_done;
}
if (result_char == MBEDTLS_TEST_RESULT_SUCCESS) {
if (fwrite(buf, length, 1, file) != 1) {
goto write_done;
}
} else {
mbedtls_test_info_t test_info;
mbedtls_test_info_save(&test_info);
if (fwrite(&test_info, sizeof(test_info), 1, file) != 1) {
goto write_done;
}
}
if (fflush(file) != 0) {
goto write_done;
}
child_exit_code = CHILD_EXIT_CODE_OK;
/* Label for `_exit()` call: this is where we jump to if the failure
* reporting fails. */
write_done:
/* We must call _exit(), not exit(), because the child must not run the
* things that normally run at exit.
*
* - Do not flush any stdio buffers! Any unflushed buffers are inherited
* from our parent, and if we flushed them, we'd get duplicate output
* since the parent would also write the same buffer content.
* - Do not run atexit hooks, e.g. leak detection code from sanitizers
* such as ASan. The child leaks any number of resources which are
* inherited from the parent but not used in the child. It's the
* parent's job to check for resource leaks.
* (We deliberately do not clean up in the child. One reason is that
* we try to minimize what happens in the child, because it's difficult
* to debug. Another reason is that we must not cause external effects
* such as destroying a PSA persistent key.)
*/
if (probably_running_under_valgrind()) {
/* Valgrind overloads _exit(), and this makes it do weird things,
* including an lseek() call to rewind the pointer on the file
* description for the `.datax` file, causing the same test cases
* to run again (or parse errors, depending on the exact amount
* of rewinding).
*
* Valgrind doesn't overload execve() and friends. So instead of
* _exit(), execute a shell command that returns the same status.
*/
char cmd[20];
snprintf(cmd, sizeof(cmd), "exit %d", child_exit_code);
execlp("sh", "sh", "-c", cmd, NULL);
}
_exit(child_exit_code);
}
int mbedtls_test_fork_run_child(
mbedtls_test_fork_child_callback_t *child_callback,
void *param,
unsigned char *child_output, size_t child_output_size,
size_t *child_output_length)
{
*child_output_length = 0;
int ret = -1;
pid_t pid = -1;
int pipe_fd[2] = { -1, -1 };
/* Set up a pipe. The child will write to pipe_fd[1], and the
* parent will read from pipe_fd[0]. */
TEST_ASSERT_ERRNO(pipe(pipe_fd) != -1);
pid = fork();
TEST_ASSERT_ERRNO(pid != -1);
if (pid == 0) {
/* The child code */
close(pipe_fd[0]);
run_child(pipe_fd[1], child_callback, param,
child_output, child_output_size);
/* Unreachable */
}
/* Beyond this point, we're in the parent (original) process. */
close(pipe_fd[1]);
pipe_fd[1] = -1;
unsigned char result_char;
struct {
mbedtls_test_info_t child_test_info;
unsigned char excess;
} reading_on_failure;
/* Normally, the child should give us a 1-byte result, then either
* the child body's output or a test info. */
ssize_t n = read(pipe_fd[0], &result_char, 1);
TEST_EQUAL(n, 1);
/* Tentatively read what we were promised. Don't commit to anything
* until we have the child's exit status. */
size_t bytes_read = 0;
if (result_char == MBEDTLS_TEST_RESULT_SUCCESS) {
do {
n = read(pipe_fd[0],
child_output + bytes_read,
child_output_size - bytes_read);
if (n > 0) {
bytes_read += n;
}
} while (n > 0 && bytes_read < child_output_size);
TEST_ASSERT_ERRNO(n != -1);
} else {
do {
n = read(pipe_fd[0],
(unsigned char *) &reading_on_failure + bytes_read,
sizeof(reading_on_failure) - bytes_read);
if (n > 0) {
bytes_read += n;
}
} while (n > 0 && bytes_read < sizeof(reading_on_failure));
TEST_ASSERT_ERRNO(n != -1);
/* Check that the child wrote the amount of data that what we expect. */
TEST_EQUAL(bytes_read, sizeof(reading_on_failure.child_test_info));
}
/* Close the pipe. If we left it open, there could be a deadlock if the
* child tried to write more than it should, while the parent is just
* waiting for the child to exit. */
close(pipe_fd[0]);
pipe_fd[0] = -1;
int wstatus;
TEST_ASSERT_ERRNO(waitpid(pid, &wstatus, 0) == pid);
if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == CHILD_EXIT_CODE_OK) {
if (result_char == MBEDTLS_TEST_RESULT_SUCCESS) {
*child_output_length = bytes_read;
ret = 0;
} else {
mbedtls_test_info_overwrite(&reading_on_failure.child_test_info);
}
} else {
/* Weird status, just report it. */
TEST_EQUAL(wstatus, 0);
}
exit:
close(pipe_fd[0]);
close(pipe_fd[1]);
return ret;
}
#endif /* MBEDTLS_PLATFORM_IS_UNIXLIKE */
+22
View File
@@ -460,6 +460,28 @@ void mbedtls_test_info_reset(void)
#endif /* MBEDTLS_THREADING_C */
}
void mbedtls_test_info_save(mbedtls_test_info_t *out)
{
#ifdef MBEDTLS_THREADING_C
mbedtls_mutex_lock(&mbedtls_test_info_mutex);
#endif /* MBEDTLS_THREADING_C */
memcpy(out, &mbedtls_test_info, sizeof(mbedtls_test_info));
#ifdef MBEDTLS_THREADING_C
mbedtls_mutex_unlock(&mbedtls_test_info_mutex);
#endif /* MBEDTLS_THREADING_C */
}
void mbedtls_test_info_overwrite(const mbedtls_test_info_t *replacement)
{
#ifdef MBEDTLS_THREADING_C
mbedtls_mutex_lock(&mbedtls_test_info_mutex);
#endif /* MBEDTLS_THREADING_C */
memcpy(&mbedtls_test_info, replacement, sizeof(mbedtls_test_info));
#ifdef MBEDTLS_THREADING_C
mbedtls_mutex_unlock(&mbedtls_test_info_mutex);
#endif /* MBEDTLS_THREADING_C */
}
int mbedtls_test_equal(const char *test, int line_no, const char *filename,
unsigned long long value1, unsigned long long value2)
{
+19
View File
@@ -14,6 +14,18 @@
#ifndef TEST_TEST_COMMON_H
#define TEST_TEST_COMMON_H
#if !defined(_POSIX_C_SOURCE)
/* For standards-compliant access to
* clock_gettime(), gmtime_r(), ...
*/
#define _POSIX_C_SOURCE 200112L
#endif
/* With GNU libc, define all the things, even when compiling with -pedantic. */
#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
/* On Mingw-w64, force the use of a C99-compliant printf() and friends.
* This is necessary on older versions of Mingw and/or Windows runtimes
* where snprintf does not always zero-terminate the buffer, and does
@@ -33,4 +45,11 @@
* is deemed necessary in test headers. */
#include <test/build_info.h>
/* Give test code access to internal macros of the library. */
#if defined(TF_PSA_CRYPTO_VERSION_NUMBER)
#include "tf_psa_crypto_common.h"
#else
#include "common.h"
#endif
#endif /* TEST_TEST_COMMON_H */