diff --git a/tests/api.c b/tests/api.c index 238ea67397..c08df43043 100644 --- a/tests/api.c +++ b/tests/api.c @@ -27309,6 +27309,7 @@ static int test_wolfSSL_dup_CA_list(void) return res; } +#ifndef WOLFSSL_NO_FORCE_ZERO static int test_ForceZero(void) { EXPECT_DECLS; @@ -27339,6 +27340,7 @@ static int test_ForceZero(void) return EXPECT_RESULT(); } +#endif /* !WOLFSSL_NO_FORCE_ZERO */ #ifndef NO_BIO @@ -36864,7 +36866,9 @@ TEST_CASE testCases[] = { * wolfcrypt *********************************/ +#ifndef WOLFSSL_NO_FORCE_ZERO TEST_DECL(test_ForceZero), +#endif TEST_DECL(test_wolfCrypt_Init), diff --git a/tests/api/test_aes.c b/tests/api/test_aes.c index 6c2adab96f..ed078ef648 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -10583,7 +10583,9 @@ static int test_CryptoCb_Aes_Cb(int devId, wc_CryptoInfo* info, void* ctx) if (aes != NULL && aes->devCtx == cryptoCbAesMockHandle) { /* "Delete" key from simulated SE */ +#ifndef WOLFSSL_NO_FORCE_ZERO ForceZero(&mockSeKey, sizeof(mockSeKey)); +#endif cryptoCbAesFreeCalled++; } @@ -10963,7 +10965,9 @@ static int test_CryptoCb_AesGcm_Offload_Cb(int devId, wc_CryptoInfo* info, void* if (aes != NULL && aes->devCtx == cryptoCbAesGcmMockHandle) { /* "Delete" key from simulated SE */ +#ifndef WOLFSSL_NO_FORCE_ZERO ForceZero(&mockSeKeyOffload, sizeof(mockSeKeyOffload)); +#endif cryptoCbAesGcmFreeCalled++; } @@ -11619,8 +11623,11 @@ static int test_Tls13Zero_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) Aes* aes = (Aes*)info->free.obj; if (aes != NULL && aes->devCtx != NULL) { +#ifndef WOLFSSL_NO_FORCE_ZERO Tls13ZeroKeySlot* slot = (Tls13ZeroKeySlot*)aes->devCtx; + /* slot is static; zero, don't free. */ ForceZero(slot, sizeof(*slot)); +#endif aes->devCtx = NULL; } return 0; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index ed5ce88edc..f3349865e8 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -7111,12 +7111,16 @@ int test_tls13_fragmented_session_ticket(void) if (EXPECT_SUCCESS() && ssl_c->arrays != NULL) { /* Zero before freeing so WOLFSSL_CHECK_MEM_ZERO builds don't abort. */ if (ssl_c->arrays->preMasterSecret != NULL) { +#ifndef WOLFSSL_NO_FORCE_ZERO ForceZero(ssl_c->arrays->preMasterSecret, ENCRYPT_LEN); +#endif XFREE(ssl_c->arrays->preMasterSecret, ssl_c->heap, DYNAMIC_TYPE_SECRET); ssl_c->arrays->preMasterSecret = NULL; } +#ifndef WOLFSSL_NO_FORCE_ZERO ForceZero(ssl_c->arrays, sizeof(Arrays)); +#endif XFREE(ssl_c->arrays, ssl_c->heap, DYNAMIC_TYPE_ARRAYS); ssl_c->arrays = NULL; } diff --git a/wolfcrypt/src/misc.c b/wolfcrypt/src/misc.c index 1642f9b1ca..efc14e4926 100644 --- a/wolfcrypt/src/misc.c +++ b/wolfcrypt/src/misc.c @@ -693,10 +693,12 @@ WC_MISC_STATIC WC_INLINE void xorbuf(void* buf, const void* mask, word32 count) with zeros. It ensures compiler optimization doesn't skip it. */ WC_MISC_STATIC WC_INLINE void ForceZero(void* mem, size_t len) { - byte *zb = (byte *)mem; - unsigned long *zl; + /* Volatile pointers prevent dead-store elimination. + * WC_BARRIER() prevents compiler reordering. */ + volatile byte *zb = (volatile byte *)mem; + volatile unsigned long *zl; - XFENCE(); + WC_BARRIER(); while ((wc_ptr_t)zb & (wc_ptr_t)(sizeof(unsigned long) - 1U)) { if (len == 0) @@ -705,21 +707,21 @@ WC_MISC_STATIC WC_INLINE void ForceZero(void* mem, size_t len) --len; } - zl = (unsigned long *)zb; + zl = (volatile unsigned long *)zb; while (len >= sizeof(unsigned long)) { *zl++ = 0; len -= sizeof(unsigned long); } - zb = (byte *)zl; + zb = (volatile byte *)zl; while (len) { *zb++ = 0; --len; } - XFENCE(); + WC_BARRIER(); } #endif diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index e346fc47f4..f2d76ec58c 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -772,6 +772,11 @@ typedef struct testVector { #ifndef WC_TEST_EXPORT_SUBTESTS WOLFSSL_TEST_SUBROUTINE wc_test_ret_t macro_test(void); +/* Under NO_INLINE, ForceZero() is WOLFSSL_LOCAL (hidden visibility) inside + * libwolfssl and unreachable from this separate test binary. */ +#if !defined(WOLFSSL_NO_FORCE_ZERO) && !defined(NO_INLINE) +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t forcezero_test(void); +#endif WOLFSSL_TEST_SUBROUTINE wc_test_ret_t error_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base16_test(void); @@ -2409,6 +2414,13 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ else TEST_PASS("macro test passed!\n"); +#if !defined(WOLFSSL_NO_FORCE_ZERO) && !defined(NO_INLINE) + if ( (ret = forcezero_test()) != 0) + TEST_FAIL("forcezero test failed!\n", ret); + else + TEST_PASS("forcezero test passed!\n"); +#endif + if ( (ret = error_test()) != 0) TEST_FAIL("error test failed!\n", ret); else @@ -4097,6 +4109,47 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t macro_test(void) return ret; } +/* Under NO_INLINE, ForceZero() is WOLFSSL_LOCAL (hidden visibility) inside + * libwolfssl and unreachable from this separate test binary. */ +#if !defined(WOLFSSL_NO_FORCE_ZERO) && !defined(NO_INLINE) +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t forcezero_test(void) +{ + /* Test unaligned offsets and lengths. Oversized buffer prevents OOB writes. */ + byte buf[64]; + static const size_t offsets[] = { 0, 1, 2, 3, 7 }; + static const size_t lens[] = { 0, 1, 3, 7, 8, 9, 16, 31 }; + size_t oi, li; + ForceZero(NULL, 0); + + for (oi = 0; oi < XELEM_CNT(offsets); oi++) { + for (li = 0; li < XELEM_CNT(lens); li++) { + size_t off = offsets[oi]; + size_t len = lens[li]; + size_t i; + + XMEMSET(buf, 0xA5, sizeof(buf)); + ForceZero(buf + off, len); + + for (i = 0; i < len; i++) { + if (buf[off + i] != 0x00) + return WC_TEST_RET_ENC_NC; + } + /* bytes outside [off, off+len) must be untouched */ + for (i = 0; i < off; i++) { + if (buf[i] != (byte)0xA5) + return WC_TEST_RET_ENC_NC; + } + for (i = off + len; i < sizeof(buf); i++) { + if (buf[i] != (byte)0xA5) + return WC_TEST_RET_ENC_NC; + } + } + } + + return 0; +} +#endif + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t error_test(void) { const char* errStr; diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index eab78ac9ce..56b527590e 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -115,6 +115,11 @@ wc_static_assert(-(long)MIN_CODE_E < 0x7ffL); #endif extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t macro_test(void); +/* Under NO_INLINE, ForceZero() is WOLFSSL_LOCAL (hidden visibility) inside + * libwolfssl and unreachable from this separate test binary. */ +#if !defined(WOLFSSL_NO_FORCE_ZERO) && !defined(NO_INLINE) +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t forcezero_test(void); +#endif extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t error_test(void); extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void); extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base16_test(void); diff --git a/wolfssl/wolfcrypt/wc_port.h b/wolfssl/wolfcrypt/wc_port.h index e84b724ae0..3dd82b104f 100644 --- a/wolfssl/wolfcrypt/wc_port.h +++ b/wolfssl/wolfcrypt/wc_port.h @@ -1919,14 +1919,17 @@ WOLFSSL_ABI WOLFSSL_API int wolfCrypt_Cleanup(void); #endif #ifdef WOLF_C99 - /* use alternate keyword for compatibility with -std=c99 */ - #define XASM_VOLATILE(a) __asm__ volatile(a) + /* -std=c99 compat; "memory" clobber creates a compiler barrier. */ + #define XASM_VOLATILE(a) __asm__ volatile(a ::: "memory") /* NOLINT(bugprone-macro-parentheses) */ #elif defined(__IAR_SYSTEMS_ICC__) - #define XASM_VOLATILE(a) asm volatile(a) + #define XASM_VOLATILE(a) asm volatile(a) /* NOLINT(bugprone-macro-parentheses) */ #elif defined(__KEIL__) - #define XASM_VOLATILE(a) __asm volatile(a) + /* No "memory" clobber: ARM Compiler 5 inline asm rejects GCC clobber + * syntax. */ + #define XASM_VOLATILE(a) __asm volatile(a) /* NOLINT(bugprone-macro-parentheses) */ #else - #define XASM_VOLATILE(a) __asm__ __volatile__(a) + /* "memory" clobber strengthens XFENCE() into a full compiler barrier. */ + #define XASM_VOLATILE(a) __asm__ __volatile__(a ::: "memory") /* NOLINT(bugprone-macro-parentheses) */ #endif #ifndef WOLFSSL_NO_FENCE