Skip to content

Improve fork helper robustness - #297

Open
gilles-peskine-arm wants to merge 12 commits into
Mbed-TLS:mainfrom
gilles-peskine-arm:fork-helper-unit-tests-create-framework
Open

Improve fork helper robustness#297
gilles-peskine-arm wants to merge 12 commits into
Mbed-TLS:mainfrom
gilles-peskine-arm:fork-helper-unit-tests-create-framework

Conversation

@gilles-peskine-arm

Copy link
Copy Markdown
Contributor

Resolves most of #295, except for the metatests which are added in a consuming branch instead: Mbed-TLS/TF-PSA-Crypto#737.

PR checklist

  • TF-PSA-Crypto development PR Add metatests for the fork test helper as unit tests TF-PSA-Crypto#737
  • TF-PSA-Crypto 1.1 PR not required because: can be updated whenever
  • mbedtls development PR not required because: does not use fork helper
  • mbedtls 4.1 PR not required because: does not use fork helper
  • mbedtls 3.6 PR not required because: can be updated whenever

@gilles-peskine-arm gilles-peskine-arm added needs-ci Needs to pass CI tests priority-high High priority - will be reviewed soon size-xs Estimated task size: extra small (a few hours at most) labels Apr 3, 2026
@gilles-peskine-arm
gilles-peskine-arm force-pushed the fork-helper-unit-tests-create-framework branch 3 times, most recently from bc83f4e to cd6fb18 Compare April 4, 2026 17:55
@bjwtaylor
bjwtaylor self-requested a review April 8, 2026 12:23
Comment thread tests/src/fork_helpers.c
*/
/* Make up a unique name using the current (parent) process ID and
* a stack address inside that process. */
char filename[sizeof("mbedtls_test_fork_run_child-%ld-%p.tmp") +

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this has probably been consider, but just in case. This is a bit of a regression on the previous implementation as it now requires create/unlink permission, whereas before it could run in read only. Not sure it this will effect anyone though? Maybe if not already considered it might be worth using mkstemp()/tmpfile() as it creates a less predictable filename?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already use files in the current directory for tests that use persistent keys. So yes, it's an additional requirement on the runtime environment compared with the previous fork helper code, but it isn't an additional requirement compared for our crypto testing as a whole.

I deliberately don't use tmpfile() or similar to avoid concerns about choosing the right temporary directory, having to worry that it might require unpredictable file names, etc.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, sounds reasonable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output produced by %p is implementation defined, so I'm unsure if the size calculation for it is always correct.

How about using mkstemp() with the template "mbedtls_test_fork_run_child.tmp-XXXXXX"? We can also add the pid to the template if you'd like - but I'm not sure it's necessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not depend on mkstemp, which might not exist on embedded platforms where we might want to test fork().

The output of %p is unspecified, but I think hex for non-null pointers is really common. But I should check the return value of snprintf.

Comment thread tests/src/fork_helpers.c Outdated

/* The child exited normally. Obtain the test result from the child. */
TEST_ASSERT_ERRNO(fseek(file, 0, SEEK_SET) == 0);
TEST_ASSERT_ERRNO(fread(&child_test_info, 1, sizeof(child_test_info), file) > 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would any scenario where less than child_test_info not be a failure here? I think it's minor, however presumably if the file was corrupted or truncated we would want it to fail here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I meant to read one record of the intended size, but I wrote it the other way round out of habit. I'll fix that.

I didn't bother writing a metatest for a truncated file because it's an unlikely failure that would require us to go out of our way. We don't normally test test code to that extent.

@gilles-peskine-arm gilles-peskine-arm added the needs-review Every commit must be reviewed by at least two team members. label Apr 8, 2026
bjwtaylor
bjwtaylor previously approved these changes Apr 9, 2026

@bensze01 bensze01 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few suggestions and typos

Comment thread tests/src/fork_helpers.c Outdated
if (mbedtls_test_get_result() == MBEDTLS_TEST_RESULT_SUCCESS && length != 0) {
/* Write the output. This could fail on a full disk. Remember to
* flush (otherwise the output would likely be truncated). */
errno = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I don't think this is necessary if the implementation of fwrite complies with POSIX.
TEST_ASSERT_ERRNO won't read the (stale) value of errno if n == length since you've switched the order of 1 and length

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POSIX only states that “if a write error occurs, (…) errno shall be set to indicate the error.” It's a classic gotcha that historical implementations don't always set errno = 0 on success: they may leave it unchanged, or even leave a nonzero result from an internal call that failed in a non-fatal way. Modern implementations usually do set errno = 0 on success, but I'm old enough to be in the habit of setting it if I care.

@bensze01 bensze01 Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I was just pointing out that we don't read the undefined value of errno in the success case anyways.

I got the impression that you fixed the issue of fwrite() returning 0 if size == 0 one way (requiring that you overwrite errno), then switched the size and count parameters around so now n == length is always true and we no longer need to reset errno (like we don't anywhere else in this file).

Comment thread tests/src/fork_helpers.c Outdated
* a #mbedtls_test_result_t structure.
* The content of the file has the following format:
* - mbedtls_test_info_t structure;
* - on pass, the ouptut (up to the end of the file).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

Suggested change
* - on pass, the ouptut (up to the end of the file).
* - on pass, the output (up to the end of the file).

Comment thread tests/src/fork_helpers.c
*/
/* Make up a unique name using the current (parent) process ID and
* a stack address inside that process. */
char filename[sizeof("mbedtls_test_fork_run_child-%ld-%p.tmp") +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output produced by %p is implementation defined, so I'm unsure if the size calculation for it is always correct.

How about using mkstemp() with the template "mbedtls_test_fork_run_child.tmp-XXXXXX"? We can also add the pid to the template if you'd like - but I'm not sure it's necessary.

Comment thread tests/src/fork_helpers.c Outdated
TEST_LE_U(length, size);

/* Label called `exit`: this is where TEST_ASSERT() and friends jump to. */
if (mbedtls_test_get_result() == MBEDTLS_TEST_RESULT_SUCCESS && length != 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: && length != 0 is unnecessary, since you've switched the positions of the arguments 1 and length.

Comment on lines +382 to +383
* \brief Record the current test case as a failure based
* on a substring search.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify the behaviour of the function

Suggested change
* \brief Record the current test case as a failure based
* on a substring search.
* \brief Record the current test case as a failure if
* parameter needle is not a substring of parameter haystack.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how this is more informative, since the readers doesn't know the parameters yet when they read the brief description.

Comment thread tests/include/test/helpers.h Outdated
* Alternatively, this can be a null pointer,
* which is treated as if it was an empty string.
*
* \return \c 1 on success, otherwise \c 0.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve wording

Suggested change
* \return \c 1 on success, otherwise \c 0.
* \return \c 0 if the test case was recorded as a failure, otherwise \c 1.

Comment thread tests/include/test/macros.h Outdated
Comment on lines +129 to +139
* \param expr1 An string-valued expression to evaluate.
* \param expr2 Another string-valued expression to evaluate.
*/
#define TEST_STRSTR(expr1, expr2) \
do { \
if (!mbedtls_test_strstr("strstr(" #expr1 ", " #expr2 ")", \
__LINE__, __FILE__, \
expr1, expr2)) { \
goto exit; \
} \
} while (0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is easier to understand with the parameter names from mbedtls_test_strstr().

Suggested change
* \param expr1 An string-valued expression to evaluate.
* \param expr2 Another string-valued expression to evaluate.
*/
#define TEST_STRSTR(expr1, expr2) \
do { \
if (!mbedtls_test_strstr("strstr(" #expr1 ", " #expr2 ")", \
__LINE__, __FILE__, \
expr1, expr2)) { \
goto exit; \
} \
} while (0)
* \param haystack A string-valued expression to evaluate.
* \param needle Another string-valued expression to evaluate.
*/
#define TEST_STRSTR(haystack, needle) \
do { \
if (!mbedtls_test_strstr("strstr(" #haystack ", " #needle ")", \
__LINE__, __FILE__, \
expr1, expr2)) { \
goto exit; \
} \
} while (0)

Assert on strstr(), and print the strings on failure.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
Use a temporary file instead of a pipe to communicate the test result and
the output from the child to the parent. This makes the data flow and the
control flow easier because the code can seek back and forth, whereas a pipe
required doing things in order. In particular, there are now fewer special
cases to manage, and the parent can read the data in the most convenient
order.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
If the child reports an output length that's larger than the buffer, report
it rather than overread the buffer. The parent would catch the excess
length anyway, but having the child catch it makes the behavior more uniform
with respect to the presence or absence of sanitizers.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
In the unlikely event of a corrupted file or I/O error, don't accept a
partially written status.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
Rather than seek forth and back, read up to the buffer size, and then check
that the file wasn't larger than the buffer size.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
In case the `%p` output is larger than expected.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
This block now works even when `length == 0`.

Setting `errno = 0` doesn't really help.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
@gilles-peskine-arm
gilles-peskine-arm force-pushed the fork-helper-unit-tests-create-framework branch from f598590 to fd4cef7 Compare August 12, 2026 19:53
@gilles-peskine-arm

Copy link
Copy Markdown
Contributor Author

There are a lot of CI failures, probably because this branch is too old. I'm going to rebase on top of the head of main. The last reviewed commit is f61edfddcf3f2b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review Every commit must be reviewed by at least two team members. priority-high High priority - will be reviewed soon size-xs Estimated task size: extra small (a few hours at most)

Projects

Development

Successfully merging this pull request may close these issues.

3 participants