Skip to content

harden forcezero and compiler barriers - #10935

Open
stenslae wants to merge 1 commit into
wolfSSL:masterfrom
stenslae:fix/wcForceZero
Open

harden forcezero and compiler barriers#10935
stenslae wants to merge 1 commit into
wolfSSL:masterfrom
stenslae:fix/wcForceZero

Conversation

@stenslae

@stenslae stenslae commented Jul 16, 2026

Copy link
Copy Markdown
Member

Description

Hardens ForceZero() against dead-store elimination by using volatile pointers and WC_BARRIER(). Also strengthens XASM_VOLATILE's asm barriers with a "memory" clobber, via a new internal XASM_VOLATILE_MB() macro used by XFENCE().

Testing

Cross compiled architectures: x86_64, aarch64, arm32, and riscv64. Statically analyzed and passed forcezero testing.

Checklist

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

@stenslae stenslae self-assigned this Jul 16, 2026

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #10935

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

No new issues found in the changed files. ✅

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

gcc-arm-cortex-m0plus

  • FLASH: .text -48 B (-0.1%, 64,159 B / 262,144 B, total: 24% used)

gcc-arm-cortex-m3

  • FLASH: .text -12 B (-0.0%, 122,599 B / 262,144 B, total: 47% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text -64 B (-0.0%, 174,928 B / 262,144 B, total: 67% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text -64 B (-0.0%, 181,028 B / 1,048,576 B, total: 17% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text -64 B (-0.0%, 236,679 B / 262,144 B, total: 90% used)

stm32-sim-stm32h753

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #10935

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Findings are non-blocking.

Comment thread wolfssl/wolfcrypt/wc_port.h Outdated
Comment thread wolfssl/wolfcrypt/wc_port.h Outdated

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #10935

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

No new issues found in the changed files. ✅

@stenslae

Copy link
Copy Markdown
Member Author

jenkins retest this please

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #10935

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

No new issues found in the changed files. ✅

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #10935

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

No new issues found in the changed files. ✅

@stenslae
stenslae force-pushed the fix/wcForceZero branch 2 times, most recently from 1844d19 to 6bf96b1 Compare July 20, 2026 20:37
@stenslae

Copy link
Copy Markdown
Member Author

jenkins retest this please

@stenslae

Copy link
Copy Markdown
Member Author

jenkins retest this please

@stenslae
stenslae requested review from wolfSSL-Fenrir-bot and removed request for wolfSSL-Fenrir-bot July 23, 2026 17:50

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #10935

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

No new issues found in the changed files. ✅

@stenslae stenslae removed their assignment Jul 27, 2026

@Frauschi Frauschi 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.

🐺 Skoll Code Review

Overall recommendation: REQUEST_CHANGES
Findings: 25 total — 8 posted, 17 skipped

Posted findings

  • [High] Gating out ForceZero() in the CryptoCb free callbacks breaks the mock-SE zeroization assertionstests/api/test_aes.c:10586-10588
  • [High] WOLF_C99 branch precedes the KEIL branch, making the new no-clobber carve-out unreachablewolfssl/wolfcrypt/wc_port.h:1921-1933
  • [Medium] Gating out ForceZero() defeats the stated WOLFSSL_CHECK_MEM_ZERO purpose in the TLS 1.3 ticket testtests/api/test_tls13.c:7112-7125
  • [Medium] Replacing XFENCE() with WC_BARRIER() silently drops the hardware fence from ForceZero()wolfcrypt/src/misc.c:696-724
  • [Medium] New forcezero_test() duplicates weaker coverage and tests nothing the PR actually changedwolfcrypt/test/test.c:4112-4149
  • [Medium] WOLFSSL_NO_FORCE_ZERO gating in tests rests on an incorrect premise - ForceZero() is always declaredtests/api/test_aes.c:11626-11630
  • [Medium] XASM_VOLATILE converted from basic to extended asm, changing '%' semantics and requiring clobber-list supportwolfssl/wolfcrypt/wc_port.h:1923
  • [Low] New XASM_VOLATILE lines exceed the 80-column conventionwolfssl/wolfcrypt/wc_port.h:1932
Skipped findings
  • [Medium] WOLFSSL_NO_FORCE_ZERO guard removes zeroization of preMasterSecret and Arrays before XFREE
  • [Medium] ForceZero() loses its hardware memory/speculation fence on all GCC and clang targets
  • [Medium] New WOLFSSL_NO_FORCE_ZERO guard in test_aes.c breaks an unguarded assertion in the same test
  • [Medium] New KEIL carve-out for GCC clobber syntax is unreachable because WOLF_C99 is tested first
  • [Low] forcezero_test buffer has no guaranteed alignment, so the 'unaligned' offsets are not deterministic
  • [Low] Guarded-out ForceZero leaves TLS 1.3 traffic key in static storage and orphans the slot
  • [Low] Guarded-out ForceZero leaves simulated-SE key populated and breaks the asserting test
  • [Low] Early return in ForceZero alignment loop bypasses the trailing WC_BARRIER()
  • [Low] WOLFSSL_NO_FORCE_ZERO guard disables TLS 1.3 crypto-callback slot reclamation, exhausting the pool
  • [Low] WOLFSSL_NO_FORCE_ZERO guard removes the zeroing the comment says is required to avoid a WOLFSSL_CHECK_MEM_ZERO abort
  • [Low] IAR branch of XASM_VOLATILE left without the memory clobber, silently weakening XFENCE() on IAR
  • [Low] New WOLFSSL_NO_FORCE_ZERO guards are unnecessary; ForceZero() is declared in both misc.h branches
  • [Info] forcezero_test() does not exercise the dead-store elimination it was added to verify
  • [Info] ForceZero no longer emits a hardware fence on GNU toolchains; barrier strength now varies by compiler
  • [Info] XASM_VOLATILE now appends ::: "memory", breaking any caller that supplies its own operands
  • [Info] forcezero_test() is compiled out under NO_INLINE, skipping the new coverage in --disable-inline builds
  • [Info] forcezero_test() enshrines an undocumented ForceZero(NULL, 0) contract

Review generated by Skoll via Claude/Codex

Comment thread tests/api/test_aes.c Outdated
Comment thread wolfssl/wolfcrypt/wc_port.h Outdated
Comment thread tests/api/test_tls13.c
Comment thread wolfcrypt/src/misc.c Outdated
Comment thread wolfcrypt/test/test.c Outdated
Comment thread tests/api/test_aes.c Outdated
Comment thread wolfssl/wolfcrypt/wc_port.h Outdated
Comment thread wolfssl/wolfcrypt/wc_port.h Outdated

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #10935

Scan targets checked: wolfcrypt-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

No new issues found in the changed files. ✅

@stenslae stenslae assigned wolfSSL-Bot and unassigned stenslae Jul 28, 2026
@stenslae
stenslae requested a review from Frauschi July 28, 2026 22:15

@Frauschi Frauschi 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.

🐺 Skoll Code Review

Overall recommendation: APPROVE
Findings: 11 total — 8 posted, 3 skipped

Posted findings

  • [Medium] Early return in the alignment loop bypasses the trailing WC_BARRIER()wolfcrypt/src/misc.c:703-708
  • [Medium] Re-adding volatile reverts a deliberate performance decision and blocks memset/vector idiom recognitionwolfcrypt/src/misc.c:696-722
  • [Medium] Follow-up on XFENCE() to WC_BARRIER(): the speculation-barrier case is not covered by the ordering argumentwolfcrypt/src/misc.c:701
  • [Low] PR checklist still marks 'added tests' after the test was withdrawnwolfcrypt/src/misc.c:694-725
  • [Medium] XFENCE() silently keeps the un-hardened (no memory clobber) form on IAR and Keil, based on a rationale contradicted by wolfSSL's own SP assemblywolfssl/wolfcrypt/wc_port.h:1925-1934
  • [Low] XASM_VOLATILE_MB()'s generic #else fallback now requires GCC extended-asm support from any unrecognized compiler that reaches an inline-asm XFENCE branchwolfssl/wolfcrypt/wc_port.h:1938-1941
  • [Info] XASM_VOLATILE() now has zero in-tree userswolfssl/wolfcrypt/wc_port.h:1925-1941
  • [Info] ForceZero comment overstates the WC_BARRIER() guarantee on non-GNUC / no-fence configurationswolfcrypt/src/misc.c:696-701
Skipped findings
  • [Low] Misaligned line-continuation backslashes in the ARMASM_BARRIER_DETECT XFENCE macro
  • [Low] ForceZero loses its hardware fence on GCC/clang after XFENCE() to WC_BARRIER() swap
  • [Low] ForceZero() swapped XFENCE() for WC_BARRIER(), removing the speculation barrier on all GCC/clang targets

Review generated by Skoll via Claude/Codex

Comment thread wolfcrypt/src/misc.c Outdated
XFENCE();
WC_BARRIER();

while ((wc_ptr_t)zb & (wc_ptr_t)(sizeof(unsigned long) - 1U)) {

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.

🟡 [Medium] Early return in the alignment loop bypasses the trailing WC_BARRIER()
💡 SUGGEST bug

The PR adds the comment WC_BARRIER() prevents compiler reordering and brackets the whole body with WC_BARRIER() at entry and exit, but the head-alignment loop still contains an unconditional return that escapes before the closing barrier. For a misaligned mem with a small len (e.g. ForceZero(p + 3, 3) on a 8-byte-aligned p), the loop writes real bytes and then returns via if (len == 0) return; with no trailing barrier at all. The volatile stores still guarantee the writes happen, but volatile accesses may still be reordered relative to non-volatile accesses, so surrounding code can be scheduled across the zeroization on exactly this path. The structure predates the PR, but the PR explicitly re-states the barrier-bracketing contract on these lines and leaves the one path that violates it.

Measured: running this exact function body with WC_BARRIER() replaced by a counter gives 2 barriers on the normal path but only 1 whenever the head-alignment loop consumes the whole length:

aligned,  len=16 (normal path)             -> 2 barrier(s)
unaligned buf+1, len=3  (small secret)     -> 1 barrier(s)   <== trailing barrier SKIPPED
unaligned buf+5, len=1                     -> 1 barrier(s)   <== trailing barrier SKIPPED
unaligned buf+1, len=7  (fills to align)   -> 2 barrier(s)

Low severity in practice, since the volatile stores carry the anti-DSE guarantee on their own - but the trailing barrier is the one that matters for DSE, and it is the path that skips it.

Suggestion:

Suggested change
while ((wc_ptr_t)zb & (wc_ptr_t)(sizeof(unsigned long) - 1U)) {
while ((len > 0) &&
((wc_ptr_t)zb & (wc_ptr_t)(sizeof(unsigned long) - 1U))) {
*zb++ = 0;
--len;
}
/* remaining loops are already no-ops when len == 0, so control always
* reaches the trailing WC_BARRIER() */

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Applied suggested fix

Comment thread wolfcrypt/src/misc.c Outdated
{
byte *zb = (byte *)mem;
unsigned long *zl;
/* Volatile pointers prevent dead-store elimination.

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.

🟡 [Medium] Re-adding volatile reverts a deliberate performance decision and blocks memset/vector idiom recognition
💡 SUGGEST question

Commit 6617f2edf (Aug 2025, douzzer) deliberately removed volatile from ForceZero and replaced it with XFENCE() bracketing — the pre-6617f2edf implementation used volatile byte* / volatile word64* exactly as this PR reintroduces. This PR reverses that decision without referencing it. The practical cost is real: with volatile unsigned long *zl, the compiler must emit one store instruction per word and cannot recognize the loop as a memset idiom or vectorize it. ForceZero has ~1036 call sites in-tree, including large-buffer calls such as ForceZero(ssl->arrays, sizeof(Arrays)) and ForceZero(ssl_c->arrays->preMasterSecret, ENCRYPT_LEN). The PR description reports cross-compilation and static analysis but no benchmark data. Note that reverting to volatile is a defensible hardening trade — the concern is that it silently undoes a recent, intentional tuning change by the maintainer.

Measured on x86_64 (clang -O2), both function bodies copied verbatim from their respective revisions, ns per call:

   bytes   volatile ns      plain ns    ratio
     128           4.2           1.3     3.2x
     512          15.2           3.7     4.1x
    4096         120.1          28.5     4.2x
   65536        1807.6         306.8     5.9x

At 64 KB the non-volatile version runs at memset speed (~213 GB/s, vectorized) while the volatile version is pinned to scalar 8-byte stores (~36 GB/s), which is exactly the idiom-recognition loss described above. At 16-32 bytes the two are within call overhead of each other, so short wipes are unaffected. Typical hot call sites such as ForceZero(ssl->arrays, sizeof(Arrays)) land in the 3-4x band.

Recommendation: Confirm with the author of 6617f2edf that the volatile-vs-fence trade-off is being intentionally reversed, and include throughput numbers for a large ForceZero (e.g. 4 KB) at -O2 on x86_64 and aarch64 in the PR description. If the regression is material, consider keeping non-volatile word stores for the bulk loop and relying on WC_BARRIER() plus a volatile-pointer read of mem after the loop to defeat DSE.

@stenslae stenslae Jul 29, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

STM32's forcezero seemed to be getting DSEd, I think we should keep volatile.

Comment thread wolfcrypt/src/misc.c
volatile unsigned long *zl;

XFENCE();
WC_BARRIER();

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.

🟡 [Medium] Follow-up on XFENCE() to WC_BARRIER(): the speculation-barrier case is not covered by the ordering argument
💡 SUGGEST question

Following up on your reply that a hardware fence "would matter if there were a second thread/core about to read that memory" and is therefore out of scope. Agreed for the ordering/visibility case - making the zeros visible to another core is the caller's problem, not ForceZero()'s, and the restored volatile stores carry the anti-DSE guarantee on their own. This is not a zeroization-correctness regression.

The one sub-case that framing does not reach: the architecture-specific XFENCE() definitions in this header do not select cross-core ordering fences, they select speculation barriers - lfence on x86, isb (or sb) on ARM, isync; sync on PPC. Their purpose is bounding speculative execution, not inter-thread visibility, so the second-core argument does not apply to them. Concretely, on aarch64 the WOLFSSL_ARMASM_BARRIER_SB / WOLFSSL_ARMASM_BARRIER_DETECT machinery (wc_port.h:1970-1985) exists specifically to emit sb, and after this change it no longer applies to ForceZero() at all.

Two smaller notes in the same area:

  1. The strength is now inverted across platforms. On GCC/clang WC_BARRIER() is __asm__ __volatile__("" ::: "memory") (wc_port.h:1999-2000) - compiler-only, no instruction emitted. On non-GNUC compilers the fallback at wc_port.h:2005 still calls XFENCE() and keeps the full fence. So the mainstream builds are the ones that lose it.
  2. WC_BARRIER is user-overridable via #ifdef WC_BARRIER (wc_port.h:1997), separately from XFENCE's own override at wc_port.h:1936. A downstream no-op WC_BARRIER override now silently removes both barriers from ForceZero().

No change requested if the speculation barrier is deliberately considered out of scope too - but it is worth one line in the code comment or PR description saying so, since the swap is otherwise indistinguishable from an oversight to the next reader.

Recommendation: State explicitly in the PR description and in the code comment why a compiler barrier now suffices (volatile stores supply the DSE guarantee that the fence previously supplied), so the difference from the XFENCE() speculation-barrier uses in asn.c/ssl_sess.c is documented rather than implied.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@douzzer Questions about this one:

  1. Do any of wolfSSL's own internal ForceZero() call sites lack a lock before the memory becomes cross-thread-visible? I didn't see any call sites that weren't routed through a mutex.
  2. Should the public wc_ForceZero() API provide a hardware fence as part of its documented contract, independent of whether wolfSSL's internal callers need it?

Comment thread 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)

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.

🔵 [Low] PR checklist still marks 'added tests' after the test was withdrawn
🔧 NIT test

Minor bookkeeping: the checklist still marks [X] added tests, but the diff is now wolfcrypt/src/misc.c and wolfssl/wolfcrypt/wc_port.h only (2 files, 36 insertions, 22 deletions). That is a direct consequence of removing forcezero_test() in response to earlier review feedback, so the code change is right - only the checkbox is stale.

Worth noting for the record what is now untested: the existing test_ForceZero (tests/api.c:27312) verifies functional zeroing and non-overrun across offsets and lengths, but cannot observe either property this PR is about - that the stores survive dead-store elimination at -O2/-O3, or that the "memory" clobber reaches XFENCE(). Nothing exercises XASM_VOLATILE_MB() or the reordered __IAR_SYSTEMS_ICC__ / __KEIL__ / WOLF_C99 arms. Untick the box, or note in the description that the test was dropped deliberately.

Suggestion:

Suggested change
WC_MISC_STATIC WC_INLINE void ForceZero(void* mem, size_t len)
/* Suggested addition to test_ForceZero: DSE-resistance check.
* A stack buffer zeroed then never read is the classic case an
* optimizer removes; scan the frame via a noinline helper. */
static WC_NOINLINE void zero_stack_secret(void)
{
unsigned char secret[64];
unsigned int i;
for (i = 0; i < sizeof(secret); i++)
secret[i] = (unsigned char)(i + 1);
ForceZero(secret, sizeof(secret));
}
/* Also add an unaligned small-len case that exercises the
* early-return path: */
ForceZero(data + 1, 1);
ForceZero(data + 3, 3);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Suggested test would be very compiler dependent, just updated pr summary.

* it requires '%%' escaping. IAR/KEIL checked before WOLF_C99 since both may
* define __STDC_VERSION__ under --c99. */
/* NOLINTBEGIN(bugprone-macro-parentheses) */
#if defined(__IAR_SYSTEMS_ICC__)

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.

🟡 [Medium] XFENCE() silently keeps the un-hardened (no memory clobber) form on IAR and Keil, based on a rationale contradicted by wolfSSL's own SP assembly
💡 SUGGEST

The PR's entire purpose for XASM_VOLATILE_MB() is to add a "memory" clobber so the compiler cannot move loads/stores across XFENCE(). For the __IAR_SYSTEMS_ICC__ and __KEIL__ branches, XASM_VOLATILE_MB(a) is defined identically to XASM_VOLATILE(a) — no clobber — justified by the in-tree comments "IAR's asm volatile() doesn't take a GCC-style clobber list" and "ARM Compiler 5 inline asm rejects GCC clobber syntax".

That rationale is contradicted by shipped wolfSSL code compiled with those exact toolchains. wolfcrypt/src/sp_cortexm.c, sp_arm32.c, and sp_arm64.c each begin with:

#ifdef __IAR_SYSTEMS_ICC__
#define __asm__        asm
#define __volatile__   volatile
#endif
#ifdef __KEIL__
#define __asm__        __asm
#define __volatile__   volatile
#endif

and then emit 241 / 250 / 206 inline-asm blocks respectively that all end in : "memory", "cc", ... clobber lists (e.g. sp_cortexm.c:671). Those expand to exactly asm volatile(... ::: "memory", ...) for IAR and __asm volatile(... ::: "memory", ...) for Keil. The WOLFSSL_NO_VAR_ASSIGN_REG guard in those files only changes operand binding — it never strips the clobber list. So both toolchains demonstrably accept GCC-style clobbers in this codebase.

Reachability: IAR EWARM/EWRISCV do not define __GNUC__, and under --c99 __STDC_VERSION__ is 199901L, so they fall past every atomics/GNU branch of the XFENCE() chain straight into the inline-asm branches (__arm__ && __ARM_ARCH > 6"isb", __riscv"fence", __aarch64__"isb"). Keil/armcc likewise. The result is that on precisely the toolchains that do reach the inline-asm path, XFENCE() remains a bare instruction-stream barrier the compiler is free to reorder memory accesses across, while every other toolchain got hardened. This affects the existing XFENCE() hardening sites wolfcrypt/src/asn.c:1812,1821,1890 (commented "Prevent memory access") and src/ssl_sess.c:1772,1786.

Recommendation: Add the "memory" clobber on IAR and Keil too, matching the SP asm precedent:

#if defined(__IAR_SYSTEMS_ICC__)
    #define XASM_VOLATILE(a) asm volatile(a)
    #define XASM_VOLATILE_MB(a) asm volatile(a ::: "memory")
#elif defined(__KEIL__)
    #define XASM_VOLATILE(a) __asm volatile(a)
    #define XASM_VOLATILE_MB(a) __asm volatile(a ::: "memory")

If a specific IAR or armcc version genuinely rejects this, narrow the exception with a version guard (the file already uses __VER__ < 9000000 for IAR elsewhere, e.g. sp_cortexm.c:2384) rather than disabling the clobber for the whole toolchain, and correct the comments to name the version. Either way, leaving XASM_VOLATILE_MB identical to XASM_VOLATILE makes the _MB suffix actively misleading at the call sites.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Gave IAR and KIEL the clobber following the precedent

Comment thread wolfssl/wolfcrypt/wc_port.h Outdated
#elif defined(WOLF_C99)
#define XASM_VOLATILE(a) __asm__ volatile(a)
#define XASM_VOLATILE_MB(a) __asm__ volatile(a ::: "memory")
#else

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.

🔵 [Low] XASM_VOLATILE_MB()'s generic #else fallback now requires GCC extended-asm support from any unrecognized compiler that reaches an inline-asm XFENCE branch
💡 SUGGEST

Before this PR, every inline-asm XFENCE() variant used XASM_VOLATILE(), i.e. basic asm (__asm__ __volatile__("isync; sync")), which is accepted by a wide range of non-GNU compilers. After this PR all of them use XASM_VOLATILE_MB(), whose generic #else arm emits GCC extended asm with a clobber list (__asm__ __volatile__(a ::: "memory")). Basic and extended asm are distinct language extensions; a compiler can support the first and not the second.

The compilers that actually reach these branches are, by construction, non-GNU: the XFENCE() chain checks C11 <stdatomic.h>, then __GNUC__ 4.1+, then __GNUC__ >= 5 || __clang__ before falling through to inline asm. So GCC and clang never get here; only toolchains such as Green Hills (__PPC__), Wind River Diab (__PPC__), TI, and TASKING do. The PR author recognized this class of problem — that is exactly why the IAR and Keil escape hatches were added — but only those two got an escape. Any other non-GNU compiler that previously compiled the basic-asm form will now hit a syntax error on the ::: clobber list, and the only remedy is a new compiler-specific #elif in this header.

Severity is held at Low because these branches are near-dead for mainstream toolchains and no in-tree build configuration is known to hit them; the concern is that the required compiler capability was silently raised with no fallback.

Recommendation: Make the clobbered form conditional on a compiler that is actually known to support extended asm, and degrade gracefully otherwise:

#elif defined(__GNUC__) || defined(__clang__)
    #define XASM_VOLATILE(a) __asm__ __volatile__(a)
    #define XASM_VOLATILE_MB(a) __asm__ __volatile__(a ::: "memory")
#else
    /* Unknown compiler: basic asm only, no clobber list. */
    #define XASM_VOLATILE(a) __asm__ __volatile__(a)
    #define XASM_VOLATILE_MB(a) XASM_VOLATILE(a)
#endif

Alternatively, expose a WOLFSSL_NO_ASM_CLOBBER opt-out so a port can fall back without patching wc_port.h.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added suggested else. Didn't add the opt-out, since else will degrade any unrecognized compiler to basic asm.

* it requires '%%' escaping. IAR/KEIL checked before WOLF_C99 since both may
* define __STDC_VERSION__ under --c99. */
/* NOLINTBEGIN(bugprone-macro-parentheses) */
#if defined(__IAR_SYSTEMS_ICC__)

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.

⚪ [Info] XASM_VOLATILE() now has zero in-tree users
🔧 NIT question

Before this PR, XASM_VOLATILE() was used only by the XFENCE() definitions. After the switch to XASM_VOLATILE_MB(), a grep across the whole tree finds no remaining use of XASM_VOLATILE() outside its own definition in wc_port.h. It stays defined for out-of-tree port/user code, which is reasonable, but the result is two near-identical macros where only one is exercised by any build — so the no-clobber variant will get no CI coverage and can silently rot. The header comment also says XASM_VOLATILE_MB() is XFENCE()-internal only, but nothing enforces that; both are plain public macros in a public header.

Recommendation: Either note in the comment that XASM_VOLATILE() is retained purely for backward compatibility with out-of-tree ports, or drop it if no such consumers are supported.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Just added a comment, probably should keep for compatibility purposes

Comment thread wolfcrypt/src/misc.c Outdated
{
byte *zb = (byte *)mem;
unsigned long *zl;
/* Volatile pointers prevent dead-store elimination.

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.

⚪ [Info] ForceZero comment overstates the WC_BARRIER() guarantee on non-GNUC / no-fence configurations
🔧 NIT

The new comment states WC_BARRIER() prevents compiler reordering, but on the non-GNUC fallback (wc_port.h:2005-2006) WC_BARRIER() expands to do { volatile byte _xfence = 0; (void)_xfence; XFENCE(); } while(0). When XFENCE() itself degenerates to WC_DO_NOTHING — which happens under WOLFSSL_NO_FENCE (wc_port.h:1994), WOLFSSL_NO_STDATOMIC_FENCE (set for a platform at settings.h:330), WOLFSSL_NO_ASM on a non-GNUC compiler, or any unrecognized architecture reaching the final #else at wc_port.h:1991 — the whole macro reduces to a store to a function-local volatile byte. A volatile access to a local object the compiler can prove is not otherwise referenced imposes no ordering constraint on unrelated memory, so no barrier is actually established on those targets. This is not a missed zeroization: the volatile byte * / volatile unsigned long * qualifiers the PR adds independently guarantee every zero store is emitted, which is the load-bearing part of the hardening. The issue is that the newly added comment asserts a property the macro does not deliver on every supported configuration, which invites future callers to rely on it. The WC_BARRIER() definition itself is pre-existing (also used by blake2b.c:128,147 and blake2s.c:125,145); what is new here is ForceZero() depending on it and documenting a guarantee around it.

Recommendation: Soften the comment at misc.c:696-697 to state what is actually guaranteed everywhere — e.g. 'Volatile pointers guarantee the zero stores are emitted; WC_BARRIER() prevents compiler reordering on targets where a barrier is available.' No code change is required in ForceZero(), since correctness of the clear rests on the volatile qualifiers, not on the barrier.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Changed comment

@Frauschi Frauschi assigned stenslae and unassigned wolfSSL-Bot Jul 29, 2026
@Frauschi
Frauschi requested a review from douzzer July 29, 2026 12:00
@Frauschi

Copy link
Copy Markdown
Contributor

Tagging @douzzer to take a look at the volatile and barrier issues as well.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants