tests: add more test_clear tests
What changed, and why it matters
This commit only adds new test cases to an existing test file. It does not change any production code, fix a bug, or alter behavior. The tests check whether sensitive secret bytes are cleared from the call stack after using certain cryptographic functions, which is a defensive security practice, but the commit itself is purely a testing improvement.
No security action required. Treat as a normal test-quality improvement. If reviewing related production code, consider whether the functions under test already guarantee stack clearing and whether the new tests pass on all supported platforms and build configurations.
Security signals we found
Adds stack-clearing regression tests for secret-handling functions
Uses a distinct sentinel value to avoid false positives from BIP39 tests
Targets sensitive operations: BIP32 seed derivation, EC private key verification, ECDSA signing, and HMAC-SHA256
Evidence from the diff
The diff adds four new test functions to src/ctest/test_clear.c: test_bip32_from_seed, test_ec_private_key_verify, test_ec_sig_from_bytes, and test_hmac_sha256. These use a 32-byte sentinel secret (0xa5 repeated) and verify, via in_stack(), that the secret does not remain on the thread stack after calling wally functions. The commit includes wally_crypto.h and adds RUN() invocations in run_tests(). No library implementation code is modified.
Changed components
src/ctest/test_clear.cInspect captured patch +67 / −0
diff --git a/src/ctest/test_clear.c b/src/ctest/test_clear.c
index 4a2441e..ad3c723 100644
--- a/src/ctest/test_clear.c
+++ b/src/ctest/test_clear.c
@@ -8,6 +8,7 @@
#undef free
#include <wally_bip32.h>
#include <wally_bip39.h>
+#include <wally_crypto.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
@@ -157,6 +158,60 @@ static bool test_bip39(void)
return true;
}
+/* Sentinel for non-bip39 secret-handling tests: 32 bytes of 0xa5.
+ * Distinct from BIP39_SECRET so test_search-style false positives can't
+ * mask a real leak. */
+static const unsigned char SECRET32[32] = {
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5
+};
+
+static bool test_bip32_from_seed(void)
+{
+ /* Derive an extended key from an all-0xa5 seed. The seed bytes
+ * pass through libc memcpy when stored into the ext_key struct
+ * and the HMAC-SHA512 input. */
+ struct ext_key key;
+ if (bip32_key_from_seed(SECRET32, sizeof(SECRET32),
+ BIP32_VER_MAIN_PRIVATE, 0, &key))
+ return false;
+ return !in_stack("bip32_key_from_seed", SECRET32, sizeof(SECRET32));
+}
+
+static bool test_ec_private_key_verify(void)
+{
+ /* The privkey is fed through libc on its way to libsecp. */
+ if (wally_ec_private_key_verify(SECRET32, sizeof(SECRET32)))
+ return false;
+ return !in_stack("wally_ec_private_key_verify", SECRET32, sizeof(SECRET32));
+}
+
+static bool test_ec_sig_from_bytes(void)
+{
+ static unsigned char msg[32] = { 0x11 };
+ static unsigned char sig[EC_SIGNATURE_LEN];
+ /* Privkey is the secret. */
+ if (wally_ec_sig_from_bytes(SECRET32, sizeof(SECRET32),
+ msg, sizeof(msg),
+ EC_FLAG_ECDSA,
+ sig, sizeof(sig)))
+ return false;
+ return !in_stack("wally_ec_sig_from_bytes", SECRET32, sizeof(SECRET32));
+}
+
+static bool test_hmac_sha256(void)
+{
+ static unsigned char out[32];
+ static unsigned char msg[32] = { 0x22 };
+ if (wally_hmac_sha256(SECRET32, sizeof(SECRET32),
+ msg, sizeof(msg),
+ out, sizeof(out)))
+ return false;
+ return !in_stack("wally_hmac_sha256", SECRET32, sizeof(SECRET32));
+}
+
static void *run_tests(void *passed_stack)
{
if (passed_stack != gstack) {
@@ -180,6 +235,18 @@ static void *run_tests(void *passed_stack)
ASAN_UNPOISON_MEMORY_REGION(passed_stack, PTHREAD_STACK_MIN);
RUN(test_bip39);
+ ASAN_UNPOISON_MEMORY_REGION(passed_stack, PTHREAD_STACK_MIN);
+ RUN(test_bip32_from_seed);
+
+ ASAN_UNPOISON_MEMORY_REGION(passed_stack, PTHREAD_STACK_MIN);
+ RUN(test_ec_private_key_verify);
+
+ ASAN_UNPOISON_MEMORY_REGION(passed_stack, PTHREAD_STACK_MIN);
+ RUN(test_ec_sig_from_bytes);
+
+ ASAN_UNPOISON_MEMORY_REGION(passed_stack, PTHREAD_STACK_MIN);
+ RUN(test_hmac_sha256);
+
return NULL;
}
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.