Skip to content

sp, tfm, sakke: default cache-resistant modexp copy to address-oblivious - #11967

Open
SparkiDev wants to merge 1 commit into
wolfSSL:masterfrom
SparkiDev:rsa_dh_sc_fixes
Open

SparkiDev wants to merge 1 commit into
wolfSSL:masterfrom
SparkiDev:rsa_dh_sc_fixes

Conversation

@SparkiDev

Copy link
Copy Markdown
Contributor

Description

The cache-resistant modular exponentiation and ECC/SAKKE scalar-mult ladders selected the working value from one of two objects (t[0]/t[1], R[0]/R[1], c[0]/c[1]) on each secret exponent bit with address masking:

_sp_copy((sp_int*)(((size_t)t[0] & sp_off_on_addr[j^1]) +
                   ((size_t)t[1] & sp_off_on_addr[j  ])), t[3]);

The mask removes a secret-dependent branch but still resolves to a single secret-dependent address, so only the selected object is touched. After the first set exponent bit the selector equals the raw exponent bit, so each copy discloses it at cache-line granularity, letting a co-resident observer on an SMT sibling sharing L1D recover RSA CRT exponents dP/dQ (and DH/ECC/SAKKE secrets) by Prime+Probe.

Make the address-oblivious copy the default for all builds: _sp_cond_copy (sp_int.c), sp_cond_select/sp_cond_memcpy (generated sp_c64.c/sp_c32.c), fp_cond_copy (tfm.c) and mp_cond_copy (sakke.c), which touch both candidates every iteration with a data-dependent mask so the accessed address does not depend on the secret. This was previously reached only under WC_NO_PTR_INT_CAST.

The faster pointer-masking copy stays available behind the new opt-in WOLFSSL_SP_FAST_MODEXP_COPY, for deployments where a co-resident cache observer is outside the threat model. It rebuilds a pointer from integer arithmetic on two addresses, so it is forced off on capability targets (WC_NO_PTR_INT_CAST), leaving their behaviour unchanged. Cost of the default is ~2.5-4% on RSA private operations (less at larger key sizes); the specialised SP ladder cost is within benchmark noise. The windowed SP assembly back ends are unaffected: they select from a fully scanned table, not two objects.

sp_c64.c and sp_c32.c are regenerated from the wolfSSL sp scripts. The other SP targets (sp_arm*, sp_cortexm, sp_riscv64) share the generator and still need regeneration to pick up the change.

Add os-check CI legs exercising both the oblivious default and the opt-in across sp_int.c, the generated ladder, fastmath and SAKKE, and document WOLFSSL_SP_FAST_MODEXP_COPY in doc/ASM_AND_MATH_DEFINES.md.

Fixes zd#22721

Testing

./configure --enable-sakke --enable-sp-math-all — sp_int.c + sakke.c, oblivious default
./configure - --enable-sakke --enable-sp-math-all -DWOLFSSL_SP_FAST_MODEXP_COPY — sp_int.c + sakke.c, pointer opt-in
./configure - --enable-sp --disable-sp-asm -DWOLFSSL_SP_LADDER_MODEXP — generated sp_c64.c/sp_c32.c ladder, oblivious default
./configure - --enable-sp --disable-sp-asm -DWOLFSSL_SP_LADDER_MODEXP -DWOLFSSL_SP_FAST_MODEXP_COPY — sp_c64.c/sp_c32.c ladder, pointer opt-in
./configure - --enable-fastmath — tfm.c, oblivious default
./configure - --enable-fastmath -DWOLFSSL_SP_FAST_MODEXP_COPY — tfm.c, pointer opt-in
./configure - --enable-fastmath -DWC_PROTECT_ENCRYPTED_MEM — tfm.c protect-encrypted-mem ladder (validated locally)
./configure - --enable-fastmath -DWC_RSA_NONBLOCK — tfm.c nonblock ladder, both modes (compile-checked locally)

@SparkiDev SparkiDev self-assigned this Nov 3, 2026
Copilot AI balanced review requested due to automatic review settings November 3, 2026 02:24
@SparkiDev

Copy link
Copy Markdown
Contributor Author

PR changes generated with:
https://github.com/wolfSSL/scripts/pull/703

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

gcc-arm-cortex-m0plus

  • FLASH: .text +36 B (+0.0%, 70,707 B / 262,144 B, total: 27% used)

gcc-arm-cortex-m3

  • FLASH: .text +64 B (+0.0%, 131,505 B / 262,144 B, total: 50% used)

gcc-arm-cortex-m4

  • FLASH: .text +64 B (+0.0%, 208,688 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m4-baremetal

  • FLASH: .text +64 B (+0.1%, 73,391 B / 262,144 B, total: 28% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text +64 B (+0.0%, 181,707 B / 262,144 B, total: 69% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .rodata -8 B, .text +64 B (+0.0%, 195,408 B / 1,048,576 B, total: 19% used)

gcc-arm-cortex-m4-min-ecc

  • FLASH: .text +64 B (+0.1%, 68,049 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .rodata -8 B, .text +64 B (+0.0%, 797,364 B / 1,048,576 B, total: 76% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +64 B (+0.0%, 222,860 B / 262,144 B, total: 85% used)

gcc-arm-cortex-m4-pq

  • FLASH: .rodata -8 B, .text +64 B (+0.0%, 312,072 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .rodata -8 B, .text +64 B (+0.0%, 342,656 B / 1,048,576 B, total: 33% used)

gcc-arm-cortex-m4-sp-math

  • FLASH: .text +64 B (+0.1%, 68,049 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +64 B (+0.0%, 132,297 B / 262,144 B, total: 50% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +64 B (+0.0%, 248,986 B / 262,144 B, total: 95% used)

gcc-arm-cortex-m7

  • FLASH: .text +64 B (+0.0%, 208,688 B / 262,144 B, total: 80% used)

gcc-arm-cortex-m7-pq

  • FLASH: .rodata -8 B, .text +64 B (+0.0%, 312,904 B / 1,048,576 B, total: 30% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text +64 B (+0.0%, 248,986 B / 262,144 B, total: 95% used)

stm32-sim-stm32h753

  • FLASH: .text +1,720 B (+0.8%, 206,308 B / 2,097,152 B, total: 10% used)

zephyr-frdm_rw612

  • FLASH: log_const_area -8 B, rodata -16 B, text +1,624 B (+0.6%, 284,064 B / 67,108,864 B, total: 0% used)

Catch memory regressions in CI with MemBrowse. Free for open source.

Copilot AI 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.

🟡 Changes recommended

Critical issues in the newly default path can corrupt digit counts, read uninitialized digits, and suppress multiplication or copy failures.

3 open issues
What changed in this PR

This PR hardens wolfCrypt’s ladder-based cryptographic operations by making copies that access both candidate values the default, reducing secret-dependent cache access.

Changes:

  • Updates SP integer math, portable SP C, fastmath, and SAKKE copy selection.
  • Adds WOLFSSL_SP_FAST_MODEXP_COPY as an opt-in, disabled on capability targets.
  • Documents the security trade-off and adds six CI configurations covering both modes.
File Description
wolfcrypt/​src/​tfm.c Defaults fastmath ladders to conditional copies.
wolfcrypt/​src/​sp_int.c Defaults SP integer exponentiation to conditional copies.
wolfcrypt/​src/​sp_c64.c Updates generated 64-bit C exponentiation and ECC copy selection.
wolfcrypt/​src/​sp_c32.c Updates generated 32-bit C exponentiation and ECC copy selection.
wolfcrypt/​src/​sakke.c Defaults the SAKKE ladder to conditional copies.
doc/​ASM_AND_MATH_DEFINES.md Documents the opt-in and its cache-security implications.
.github/​configs/​os-check-linux.json Adds default and opt-in coverage across math implementations.

Review effort: Balanced

Comment thread wolfcrypt/src/sakke.c Resolved
Comment thread wolfcrypt/src/sp_int.c Resolved
Comment thread wolfcrypt/src/sp_int.c Resolved
@SparkiDev

Copy link
Copy Markdown
Contributor Author

PR changes generated with:
https://github.com/wolfSSL/scripts/pull/703

Copilot AI 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.

🔵 Needs a closer look

The side-channel changes span multiple math backends and require cryptographic maintainer review of optimized machine code and cache-access traces, since source inspection and functional CI cannot establish address-oblivious execution.

No open issues 🎉

3 resolved since last review

Review effort: Balanced

@SparkiDev

Copy link
Copy Markdown
Contributor Author

Jenkins: retest this please

@SparkiDev
SparkiDev force-pushed the rsa_dh_sc_fixes branch 2 times, most recently from bb35c84 to cc12a71 Compare November 3, 2026 23:13
The cache-resistant modular exponentiation and ECC/SAKKE scalar-mult ladders
selected the working value from one of two objects (t[0]/t[1], R[0]/R[1],
c[0]/c[1]) on each secret exponent bit with address masking:

    _sp_copy((sp_int*)(((size_t)t[0] & sp_off_on_addr[j^1]) +
                       ((size_t)t[1] & sp_off_on_addr[j  ])), t[3]);

The mask removes a secret-dependent branch but still resolves to a single
secret-dependent address, so only the selected object is touched. After the
first set exponent bit the selector equals the raw exponent bit, so each copy
discloses it at cache-line granularity, letting a co-resident observer on an
SMT sibling sharing L1D recover RSA CRT exponents dP/dQ (and DH/ECC/SAKKE
secrets) by Prime+Probe.

Make the address-oblivious copy the default for all builds: _sp_cond_copy
(sp_int.c), sp_cond_select/sp_cond_memcpy (generated sp_c64.c/sp_c32.c),
fp_cond_copy (tfm.c) and mp_cond_copy (sakke.c), which touch both candidates
every iteration with a data-dependent mask so the accessed address does not
depend on the secret. This was previously reached only under WC_NO_PTR_INT_CAST.

The faster pointer-masking copy stays available behind the new opt-in
WOLFSSL_SP_FAST_MODEXP_COPY, for deployments where a co-resident cache observer
is outside the threat model. It rebuilds a pointer from integer arithmetic on
two addresses, so it is forced off on capability targets (WC_NO_PTR_INT_CAST),
leaving their behaviour unchanged. Cost of the default is ~2.5-4% on RSA private
operations (less at larger key sizes); the specialised SP ladder cost is within
benchmark noise. The windowed SP assembly back ends are unaffected: they select
from a fully scanned table, not two objects.

sp_c64.c and sp_c32.c are regenerated from the wolfSSL sp scripts. The other SP
targets (sp_arm*, sp_cortexm, sp_riscv64) share the generator and still need
regeneration to pick up the change.

Add os-check CI legs exercising both the oblivious default and the opt-in across
sp_int.c, the generated ladder, fastmath and SAKKE, and document
WOLFSSL_SP_FAST_MODEXP_COPY in doc/ASM_AND_MATH_DEFINES.md.

Harden the promoted oblivious copy helpers, which were previously only built
under WC_NO_PTR_INT_CAST:
- _sp_cond_copy()/fp_cond_copy(): mark WC_MAYBE_UNUSED - the definition guard is
  broader than the exptmod ladders that use them (e.g. ECC-with-comp-key or
  RSA-verify-only builds define them but compile no private exponentiation),
  which tripped -Werror=unused-function (e.g. tinytls13-cert-rsaverify).
- _sp_cond_copy(): derive the 'used'/'sign' masks at their own field width. The
  digit-wide mask zero-extended to sp_size_t dropped the high bits of 'used'
  when SP_WORD_SIZE==8 and an operand had >=256 digits, copying it as length 0.
- sakke_modexp_loop(): guard each mp_cond_copy() on err so a failure in the
  multiply or an earlier copy is preserved instead of overwritten.

Follow-up CI fixes for the now-default oblivious copy:
- Initialise the full digit span of the exptmod ladder temporaries via a new
  _sp_init_size_ct() helper. _sp_init_size() only zeroes dp[0], but
  _sp_cond_copy() scans a fixed m->used digits, so a working value with fewer
  significant digits left indeterminate padding that the copy read - reported
  by valgrind (3.22) on the Ada wrapper RSA test. Applied to both
  _sp_exptmod_mont_ex variants and both _sp_exptmod_ex variants.
- _sp_cond_copy(): build the 'used' mask with an explicit cast over unsigned
  arithmetic so it is -Warith-conversion clean (smallstack-intelasm-precalc).
- sakke.c: declare the local wc_off_on_addr only for the pointer-copy opt-in;
  the default oblivious copy does not use it, which was an unused variable under
  WC_NO_GLOBAL_OBJECT_POINTERS (linuxkm -Werror).
@SparkiDev

Copy link
Copy Markdown
Contributor Author

Jenkins: retest this please

@SparkiDev SparkiDev assigned wolfSSL-Bot and unassigned SparkiDev Nov 4, 2026
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.

3 participants