From b629536d02e25c84f999de65c05b184f713aad88 Mon Sep 17 00:00:00 2001 From: deadcafe Date: Thu, 22 Oct 2026 04:50:07 +0530 Subject: [PATCH 1/3] Wipe with word stores in wolfCose_ForceZero Every sign, verify, MAC, encrypt and decrypt wipes the whole caller scratch buffer, which defaults to 8192 B with ML-DSA and 11264 B with LMS. The byte-at-a-time volatile loop cannot be merged by the compiler, so for a small MAC0 the wipe costs several times the HMAC itself. Store whole size_t words through a volatile pointer once the address is size_t-aligned, with byte stores for the unaligned head and the tail. This is the technique wolfSSL's own ForceZero() uses. On an Apple Silicon Mac at -Os, an 11264 B wipe drops from 3.6 us to 0.46 us and a 1 MiB wipe from 338 us to 42 us. The alignment test and the word store need MISRA C:2012 deviations for Rules 11.4 and 11.3; both are anchored in scripts/misra-deviations.json and documented in docs/MISRA-Compliance.md. A new unit test checks that exactly [off, off + len) is cleared at every start offset and length across the head, word and tail loops. --- docs/MISRA-Compliance.md | 4 +++- scripts/misra-deviations.json | 20 ++++++++++++++++++++ src/wolfcose_util.c | 15 +++++++++++++-- tests/test_cose.c | 22 ++++++++++++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/docs/MISRA-Compliance.md b/docs/MISRA-Compliance.md index a6be1c9..c0c207a 100644 --- a/docs/MISRA-Compliance.md +++ b/docs/MISRA-Compliance.md @@ -82,7 +82,9 @@ tracked conditional preprocessing directive. | ID | Rule | Location | Rationale and validation | |----|------|----------|--------------------------| -| D-11.5-001 | 11.5 | `wolfCose_ForceZero` | Converting the caller's object pointer to a volatile character pointer is the standard C mechanism for securely erasing its object representation. Character access is alignment-safe and the function allocates no memory. `make zeroize-test`, `make zero-alloc-check`, and `make c99-check` validate the implementation. | +| D-11.5-001 | 11.5 | `wolfCose_ForceZero` | Converting the caller's object pointer to a volatile character pointer is the standard C mechanism for securely erasing its object representation. The function allocates no memory. `make zeroize-test`, `make zero-alloc-check`, and `make c99-check` validate the implementation. | +| D-11.4-002 | 11.4 | `wolfCose_ForceZero` head loop | The secure erase converts the current byte address to `uintptr_t` only to test its alignment, so whole-word stores start on a `size_t` boundary on targets that fault on unaligned access. The integer is never converted back to a pointer. | +| D-11.3-001 | 11.3 | `wolfCose_ForceZero` word loop | The secure erase stores whole `size_t` words through a volatile pointer at addresses the head loop has aligned, the same technique wolfSSL's `ForceZero()` uses. Byte stores cannot be merged by the compiler, so a byte-only loop wipes multi-kilobyte scratch buffers about 8x slower. The store writes zero only and never reads the object. `make test` checks that exactly the requested range is zeroed at every start offset and length. | | D-19.2-001 | 19.2 | `WOLFCOSE_KEY.key` | The public, discriminated union preserves the established ABI and embedded-memory footprint. `kty` and `attachedType` govern member access. Replacing it with a structure would break ABI and increase RAM. The full tests exercise the supported members, and CI anchors the deviation to the exact union boundaries. | | D-11.8-002 | 11.8 | `wolfCose_Hpke0Seal` plaintext input | The pinned wolfSSL `wc_HpkeSealBase()` interface accepts a mutable plaintext pointer even though its implementation only consumes plaintext while writing to its distinct ciphertext output. The wrapper keeps the wolfCOSE public input const-qualified and avoids a payload-sized copy in constrained builds. The deviation is anchored to the one interoperability cast and covered by the HPKE unit and sanitizer CI lanes. | | D-11.8-003 | 11.8 | `wolfCose_Hpke0Open` ciphertext input | The pinned wolfSSL `wc_HpkeOpenBase()` interface accepts a mutable ciphertext pointer even though its implementation only consumes ciphertext while writing to its distinct plaintext output. The wrapper keeps the wolfCOSE public input const-qualified and avoids a ciphertext-sized copy in constrained builds. The deviation is anchored to the one interoperability cast and covered by the HPKE unit and sanitizer CI lanes. | diff --git a/scripts/misra-deviations.json b/scripts/misra-deviations.json index dffe21e..418e177 100644 --- a/scripts/misra-deviations.json +++ b/scripts/misra-deviations.json @@ -11,6 +11,26 @@ ], "expected": 1 }, + { + "id": "D-11.4-002", + "rule": "11.4", + "path": "src/wolfcose_util.c", + "symbol": "wolfCose_ForceZero", + "anchors": [ + " while ((i < len) && ((((uintptr_t)&p[i]) % sizeof(size_t)) != 0u)) {" + ], + "expected": 1 + }, + { + "id": "D-11.3-001", + "rule": "11.3", + "path": "src/wolfcose_util.c", + "symbol": "wolfCose_ForceZero", + "anchors": [ + " *(volatile size_t*)&p[i] = 0u;" + ], + "expected": 1 + }, { "id": "D-11.4-001", "rule": "11.4", diff --git a/src/wolfcose_util.c b/src/wolfcose_util.c index d48291b..38cb2f2 100644 --- a/src/wolfcose_util.c +++ b/src/wolfcose_util.c @@ -66,9 +66,20 @@ WOLFCOSE_LOCAL void wolfCose_ForceZero(void* mem, size_t len) { if ((mem != NULL) && (len > 0u)) { volatile unsigned char* p = (volatile unsigned char*)mem; - size_t i; - for (i = 0u; i < len; i++) { + size_t i = 0u; + /* Bytes up to a size_t boundary, then whole words, then the tail. + * Word stores make multi-KB scratch wipes about 8x faster. */ + while ((i < len) && ((((uintptr_t)&p[i]) % sizeof(size_t)) != 0u)) { p[i] = 0u; + i++; + } + while ((len - i) >= sizeof(size_t)) { + *(volatile size_t*)&p[i] = 0u; + i += sizeof(size_t); + } + while (i < len) { + p[i] = 0u; + i++; } } #ifdef WOLFCOSE_TEST_ZEROIZE_HOOK diff --git a/tests/test_cose.c b/tests/test_cose.c index f2a0f3c..3abc96d 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -521,6 +521,9 @@ static void test_wolfcose_force_zero(void) int allZero; int prefixZero; int suffixUntouched; + size_t off; + size_t len; + int exactRange; TEST_LOG(" [wolfCose_ForceZero]\n"); @@ -560,6 +563,25 @@ static void test_wolfcose_force_zero(void) TEST_ASSERT(prefixZero == 1, "ForceZero prefix zeroed"); TEST_ASSERT(suffixUntouched == 1, "ForceZero suffix untouched"); + /* Every start offset and length across the byte head, word body and + * byte tail: exactly [off, off + len) is zeroed, nothing outside it */ + exactRange = 1; + for (off = 0; off < (2u * sizeof(size_t)); off++) { + for (len = 0; len <= (3u * sizeof(size_t)) + 1u; len++) { + for (i = 0; i < sizeof(buf); i++) { + buf[i] = 0xDDu; + } + wolfCose_ForceZero(&buf[off], len); + for (i = 0; i < sizeof(buf); i++) { + if (((i >= off) && (i < (off + len))) ? + (buf[i] != 0u) : (buf[i] != 0xDDu)) { + exactRange = 0; + } + } + } + } + TEST_ASSERT(exactRange == 1, "ForceZero exact range at every offset"); + /* len == 0: no-op, must not crash, must not modify buffer */ for (i = 0; i < sizeof(buf); i++) { buf[i] = 0xCCu; From 910f83328e2995fbd1a73ee47cd3310a84c50cef Mon Sep 17 00:00:00 2001 From: deadcafe Date: Thu, 22 Oct 2026 05:48:51 +0530 Subject: [PATCH 2/3] Zero with XMEMSET and an opaque barrier call Storing size_t words through a volatile pointer into byte arrays and key structures breaks the C99 effective-type rules (6.5p7), so the previous commit made the secure erase depend on undefined behavior. Zero with XMEMSET instead, then pass the buffer to a no-op function called through a static const volatile function pointer. The compiler cannot tell which function the pointer holds, so it must assume the call reads the buffer and cannot drop the memset as a dead store. memset may write any object, so no aliasing rule is involved. Where GNU asm is available, the no-op holds an empty asm memory barrier, so the stores also survive if the optimizer resolves and inlines the call, as profile-guided call promotion could. The guard matches wolfSSL's WC_BARRIER_DATA, which older releases lack. With clang -O3 -flto, a 256 B dead stack buffer keeps its zeroing stores ahead of the barrier call and loses them without it. Inlined directly, the call keeps the stores only with the asm barrier. An 11264 B wipe at -Os on an Apple Silicon Mac takes 0.12 us, against 0.46 us for the word loop and 3.6 us for the original byte loop. The function no longer casts the caller's pointer, so the Rule 11.5, 11.4 and 11.3 deviations for wolfCose_ForceZero are removed. --- docs/MISRA-Compliance.md | 3 --- scripts/misra-deviations.json | 30 --------------------------- src/wolfcose_util.c | 38 ++++++++++++++++++----------------- tests/test_cose.c | 4 ++-- 4 files changed, 22 insertions(+), 53 deletions(-) diff --git a/docs/MISRA-Compliance.md b/docs/MISRA-Compliance.md index c0c207a..fd1b17b 100644 --- a/docs/MISRA-Compliance.md +++ b/docs/MISRA-Compliance.md @@ -82,9 +82,6 @@ tracked conditional preprocessing directive. | ID | Rule | Location | Rationale and validation | |----|------|----------|--------------------------| -| D-11.5-001 | 11.5 | `wolfCose_ForceZero` | Converting the caller's object pointer to a volatile character pointer is the standard C mechanism for securely erasing its object representation. The function allocates no memory. `make zeroize-test`, `make zero-alloc-check`, and `make c99-check` validate the implementation. | -| D-11.4-002 | 11.4 | `wolfCose_ForceZero` head loop | The secure erase converts the current byte address to `uintptr_t` only to test its alignment, so whole-word stores start on a `size_t` boundary on targets that fault on unaligned access. The integer is never converted back to a pointer. | -| D-11.3-001 | 11.3 | `wolfCose_ForceZero` word loop | The secure erase stores whole `size_t` words through a volatile pointer at addresses the head loop has aligned, the same technique wolfSSL's `ForceZero()` uses. Byte stores cannot be merged by the compiler, so a byte-only loop wipes multi-kilobyte scratch buffers about 8x slower. The store writes zero only and never reads the object. `make test` checks that exactly the requested range is zeroed at every start offset and length. | | D-19.2-001 | 19.2 | `WOLFCOSE_KEY.key` | The public, discriminated union preserves the established ABI and embedded-memory footprint. `kty` and `attachedType` govern member access. Replacing it with a structure would break ABI and increase RAM. The full tests exercise the supported members, and CI anchors the deviation to the exact union boundaries. | | D-11.8-002 | 11.8 | `wolfCose_Hpke0Seal` plaintext input | The pinned wolfSSL `wc_HpkeSealBase()` interface accepts a mutable plaintext pointer even though its implementation only consumes plaintext while writing to its distinct ciphertext output. The wrapper keeps the wolfCOSE public input const-qualified and avoids a payload-sized copy in constrained builds. The deviation is anchored to the one interoperability cast and covered by the HPKE unit and sanitizer CI lanes. | | D-11.8-003 | 11.8 | `wolfCose_Hpke0Open` ciphertext input | The pinned wolfSSL `wc_HpkeOpenBase()` interface accepts a mutable ciphertext pointer even though its implementation only consumes ciphertext while writing to its distinct plaintext output. The wrapper keeps the wolfCOSE public input const-qualified and avoids a ciphertext-sized copy in constrained builds. The deviation is anchored to the one interoperability cast and covered by the HPKE unit and sanitizer CI lanes. | diff --git a/scripts/misra-deviations.json b/scripts/misra-deviations.json index 418e177..2270132 100644 --- a/scripts/misra-deviations.json +++ b/scripts/misra-deviations.json @@ -1,36 +1,6 @@ { "schema": 1, "deviations": [ - { - "id": "D-11.5-001", - "rule": "11.5", - "path": "src/wolfcose_util.c", - "symbol": "wolfCose_ForceZero", - "anchors": [ - "volatile unsigned char* p = (volatile unsigned char*)mem;" - ], - "expected": 1 - }, - { - "id": "D-11.4-002", - "rule": "11.4", - "path": "src/wolfcose_util.c", - "symbol": "wolfCose_ForceZero", - "anchors": [ - " while ((i < len) && ((((uintptr_t)&p[i]) % sizeof(size_t)) != 0u)) {" - ], - "expected": 1 - }, - { - "id": "D-11.3-001", - "rule": "11.3", - "path": "src/wolfcose_util.c", - "symbol": "wolfCose_ForceZero", - "anchors": [ - " *(volatile size_t*)&p[i] = 0u;" - ], - "expected": 1 - }, { "id": "D-11.4-001", "rule": "11.4", diff --git a/src/wolfcose_util.c b/src/wolfcose_util.c index 38cb2f2..7706b4e 100644 --- a/src/wolfcose_util.c +++ b/src/wolfcose_util.c @@ -51,8 +51,10 @@ /* ----- Secure memory zero ----- */ /** - * Portable secure-zero. Volatile pointer prevents the compiler optimising - * the writes away when the buffer is dead at function exit. Used in place + * Portable secure-zero. XMEMSET is followed by a call through a volatile + * function pointer the compiler cannot resolve, so it must assume the callee + * reads the buffer and cannot drop the stores as dead. With GNU asm the + * callee is also an asm memory barrier. Used in place * of wc_ForceZero so wolfCOSE links against the full wolfSSL 5.x range * (wc_ForceZero only became a public WOLFSSL_API symbol in v5.8.4). */ @@ -62,25 +64,25 @@ extern void wolfCose_TestZeroizeRecord(const void* mem, size_t len); #endif +static void wolfCose_ZeroBarrier(const void* mem) +{ +#if defined(__GNUC__) && !defined(__CC_ARM) && !defined(WOLFSSL_NO_ASM) + /* Still a barrier if the optimizer resolves and inlines this call, e.g. + * profile-guided call promotion. Same guard as wolfSSL's WC_BARRIER_DATA, + * which only exists in newer releases. */ + __asm__ __volatile__("" : : "r"(mem) : "memory"); +#else + (void)mem; +#endif +} + WOLFCOSE_LOCAL void wolfCose_ForceZero(void* mem, size_t len) { + static void (*const volatile barrier)(const void*) = wolfCose_ZeroBarrier; + if ((mem != NULL) && (len > 0u)) { - volatile unsigned char* p = (volatile unsigned char*)mem; - size_t i = 0u; - /* Bytes up to a size_t boundary, then whole words, then the tail. - * Word stores make multi-KB scratch wipes about 8x faster. */ - while ((i < len) && ((((uintptr_t)&p[i]) % sizeof(size_t)) != 0u)) { - p[i] = 0u; - i++; - } - while ((len - i) >= sizeof(size_t)) { - *(volatile size_t*)&p[i] = 0u; - i += sizeof(size_t); - } - while (i < len) { - p[i] = 0u; - i++; - } + (void)XMEMSET(mem, 0, len); + barrier(mem); } #ifdef WOLFCOSE_TEST_ZEROIZE_HOOK wolfCose_TestZeroizeRecord(mem, len); diff --git a/tests/test_cose.c b/tests/test_cose.c index 3abc96d..d4ffa7b 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -563,8 +563,8 @@ static void test_wolfcose_force_zero(void) TEST_ASSERT(prefixZero == 1, "ForceZero prefix zeroed"); TEST_ASSERT(suffixUntouched == 1, "ForceZero suffix untouched"); - /* Every start offset and length across the byte head, word body and - * byte tail: exactly [off, off + len) is zeroed, nothing outside it */ + /* Every start offset and length: exactly [off, off + len) is zeroed, + * nothing outside it */ exactRange = 1; for (off = 0; off < (2u * sizeof(size_t)); off++) { for (len = 0; len <= (3u * sizeof(size_t)) + 1u; len++) { From 9e25fee518d1b0e58b9c13530447c21636426c20 Mon Sep 17 00:00:00 2001 From: deadcafe Date: Thu, 22 Oct 2026 23:05:48 +0530 Subject: [PATCH 3/3] Check ForceZero stores survive -O3 in one TU The unit test reads the buffer back after the wipe and links against the library without LTO, so it cannot see the wipe being dropped as a dead store. scripts/check_force_zero.sh compiles a caller in the same translation unit as wolfcose_util.c at -O3, wiping a local buffer that is dead afterwards, and requires zeroing stores in its assembly. A plain XMEMSET control in the same file must lose its stores, so the check fails if the optimizer could not have dropped them anyway. It runs with and without WOLFSSL_NO_ASM and is a prerequisite of make test. Removing the barrier call fails both modes on AArch64 and x86-64 clang; making the function pointer non-volatile fails the WOLFSSL_NO_ASM mode, where no asm barrier remains. Also update the wolfCose_ForceZero declaration comment, which still described the volatile-pointer byte loop. --- Makefile | 8 ++- scripts/check_force_zero.sh | 101 ++++++++++++++++++++++++++++++++++++ src/wolfcose_internal.h | 10 ++-- 3 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 scripts/check_force_zero.sh diff --git a/Makefile b/Makefile index c202416..c156918 100644 --- a/Makefile +++ b/Makefile @@ -166,7 +166,7 @@ SCEN_IOTFLEET = examples/scenarios/iot_fleet_config SCEN_SENSOR = examples/scenarios/sensor_attestation SCEN_BROADCAST = examples/scenarios/group_broadcast_mac -.PHONY: all shared test pkg-config-test ecdsa-policy-test rsapss-policy-test countersign-config-test zero-alloc-check zeroize-test deprecated-algs-test ecc-import-policy-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage eat-psa-test eat-psa-float-test eat-psa-min-buffers-test eat-psa-claim-limits-test eat-psa-profile-test eat-psa-config-check eat-psa-ext-sign-test eat-psa-ext-sign-force-failure eat-psa-coverage eat-psa-coverage-force-failure hpke-coverage-force-failure generic-reduced-alg-test tool tool-test cmdline-test demo demos hpke-demo lean-verify psa-eat-lean-verify psa-eat-demo mldsa-demo mldsa-verify lms-demo lms-verify comprehensive scenarios release-scenarios release-coverage cxx-check valgrind-check release-validate release-artifacts interop-tcose tcose-upstream interop-go-cose interop-python-cwt interop-rust-coset c99-check c99-check-lms c99-hpke-check experimental-check clean FORCE +.PHONY: all shared test pkg-config-test ecdsa-policy-test rsapss-policy-test countersign-config-test zero-alloc-check force-zero-check zeroize-test deprecated-algs-test ecc-import-policy-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage eat-psa-test eat-psa-float-test eat-psa-min-buffers-test eat-psa-claim-limits-test eat-psa-profile-test eat-psa-config-check eat-psa-ext-sign-test eat-psa-ext-sign-force-failure eat-psa-coverage eat-psa-coverage-force-failure hpke-coverage-force-failure generic-reduced-alg-test tool tool-test cmdline-test demo demos hpke-demo lean-verify psa-eat-lean-verify psa-eat-demo mldsa-demo mldsa-verify lms-demo lms-verify comprehensive scenarios release-scenarios release-coverage cxx-check valgrind-check release-validate release-artifacts interop-tcose tcose-upstream interop-go-cose interop-python-cwt interop-rust-coset c99-check c99-check-lms c99-hpke-check experimental-check clean FORCE # --- Core library --- all: $(LIB_A) @@ -356,7 +356,11 @@ countersign-config-test: zero-alloc-check: sh scripts/check_zero_alloc.sh -test: pkg-config-test ecdsa-policy-test rsapss-policy-test zero-alloc-check $(LIB_A) +force-zero-check: + CC='$(CC)' CFLAGS='$(CFLAGS)' sh scripts/check_force_zero.sh + +test: pkg-config-test ecdsa-policy-test rsapss-policy-test zero-alloc-check \ + force-zero-check $(LIB_A) $(CC) $(CFLAGS) -o $(TEST_BIN) $(TEST_SRC) $(LIB_A) $(LDFLAGS) $(LDLIBS) ./$(TEST_BIN) diff --git a/scripts/check_force_zero.sh b/scripts/check_force_zero.sh new file mode 100644 index 0000000..0a5e094 --- /dev/null +++ b/scripts/check_force_zero.sh @@ -0,0 +1,101 @@ +#!/bin/sh +# +# Prove wolfCose_ForceZero survives dead-store elimination. A caller in the +# same translation unit as wolfcose_util.c wipes a local buffer that is dead +# afterwards; at -O3 the zeroing stores must still be in its assembly. A +# plain XMEMSET control in the same file must lose them, which shows the +# optimizer could drop the wipe and the store patterns below are not noise. +# Runs with and without WOLFSSL_NO_ASM, which selects the barrier body. + +set -eu + +CC=${CC:-cc} +CFLAGS=${CFLAGS:-} + +# Keep only include paths and defines from CFLAGS: the optimization level is +# fixed here, and sanitizer or coverage flags would rewrite the stores. +flags="" +take_next=0 +for flag in $CFLAGS; do + if [ "$take_next" -eq 1 ]; then + flags="$flags $flag" + take_next=0 + continue + fi + case "$flag" in + -isystem|-I|-D) flags="$flags $flag"; take_next=1 ;; + -I*|-D*|-isystem*) flags="$flags $flag" ;; + esac +done + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT + +cat >"$work/probe.c" <<'EOF' +#include "wolfcose_util.c" + +void wcProbeFill(unsigned char* buf); +void wcProbeWipe(void); +void wcProbeControl(void); + +void wcProbeWipe(void) +{ + unsigned char buf[256]; + wcProbeFill(buf); + wolfCose_ForceZero(buf, sizeof(buf)); +} + +void wcProbeControl(void) +{ + unsigned char buf[256]; + wcProbeFill(buf); + (void)XMEMSET(buf, 0, sizeof(buf)); +} +EOF + +# Zero sources for x86-64 and AArch64 with GCC and Clang: a memset or bzero +# call, rep stos, an x86 vector zero idiom or $0 immediate store, an AArch64 +# zero-register store, or an AArch64 vector zero. +word='(^|[^[:alnum:]_])' +zero_store="memset|bzero|stos|${word}v?(pxor|xorps)[[:space:]]" +zero_store="$zero_store|${word}mov[a-z]*[[:space:]]+[\$]0," +zero_store="$zero_store|${word}st[a-z]*[[:space:]]+[xw]zr," +zero_store="$zero_store|movi[^,]*,[[:space:]]*#?0+([[:space:]]|$)" +inline_call='(call|jmp|bl|b)[[:space:]]+_?wolfCose_ForceZero([@[:space:]]|$)' + +body() { + awk -v fn="$1" ' + $0 ~ "^_?" fn ":" { on = 1; next } + on && /^[[:space:]]*\.(cfi_endproc|size)/ { exit } + on { print } + ' "$2" +} + +for asm_mode in "" "-DWOLFSSL_NO_ASM"; do + label=${asm_mode:-default} + # shellcheck disable=SC2086 + $CC -std=c99 -O3 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -I./include -Isrc \ + $flags $asm_mode -S -o "$work/probe.s" "$work/probe.c" + + body wcProbeWipe "$work/probe.s" >"$work/wipe.s" + body wcProbeControl "$work/probe.s" >"$work/control.s" + + if [ ! -s "$work/wipe.s" ] || [ ! -s "$work/control.s" ]; then + echo "FAIL ($label): probe functions not found in assembly" + exit 1 + fi + if grep -Eq "$inline_call" "$work/wipe.s"; then + echo "FAIL ($label): wolfCose_ForceZero not inlined, check is moot" + exit 1 + fi + if grep -Eq "$zero_store" "$work/control.s"; then + echo "FAIL ($label): plain memset kept, check cannot tell" + exit 1 + fi + if ! grep -Eq "$zero_store" "$work/wipe.s"; then + echo "FAIL ($label): wolfCose_ForceZero stores optimized out" + cat "$work/wipe.s" + exit 1 + fi + echo "PASS ($label): wolfCose_ForceZero stores kept at -O3" +done diff --git a/src/wolfcose_internal.h b/src/wolfcose_internal.h index 2f9295e..e27548a 100644 --- a/src/wolfcose_internal.h +++ b/src/wolfcose_internal.h @@ -62,10 +62,12 @@ extern "C" { #endif /* ----- Secure memory zero ----- - * Portable secure-zero using a volatile pointer so the compiler cannot - * optimise the writes away. Used in place of wc_ForceZero so wolfCOSE - * links against the full 5.x range (wc_ForceZero only became a public - * WOLFSSL_API symbol in v5.8.4). Definition lives in wolfcose_util.c. */ + * Portable secure-zero: XMEMSET, then a call through a volatile function + * pointer to an asm memory barrier (a no-op under WOLFSSL_NO_ASM), so the + * compiler cannot drop the stores as dead. Used in place of wc_ForceZero + * so wolfCOSE links against the full 5.x range (wc_ForceZero only became a + * public WOLFSSL_API symbol in v5.8.4). Definition lives in + * wolfcose_util.c; scripts/check_force_zero.sh checks it survives -O3. */ WOLFCOSE_LOCAL void wolfCose_ForceZero(void* mem, size_t len); /* ----- Big-endian load/store helpers (bit-shift only, no platform dependencies) ----- */