Skip to content

AES-GCM: constant-time output clear on decrypt auth failure#10895

Merged
Frauschi merged 5 commits into
wolfSSL:masterfrom
danielinux:aes-gcm-fix-ct-compare
Jul 15, 2026
Merged

AES-GCM: constant-time output clear on decrypt auth failure#10895
Frauschi merged 5 commits into
wolfSSL:masterfrom
danielinux:aes-gcm-fix-ct-compare

Conversation

@danielinux

Copy link
Copy Markdown
Member

Description

AES_GCM_decrypt_C cleared the output on a tag mismatch with 'if (ret != 0) ForceZero(out, sz)'. That is a conditional branch on the secret-dependent authentication result, which is not constant time and is flagged by the ct-valgrind constant-time test (Conditional jump depends on uninitialised value in AES_GCM_decrypt_C).

Mask the output with 'res' (already computed as all-ones on tag mismatch, zero on match) instead of branching, matching the constant-time idiom used for the tag comparison itself. C path only; the AES-NI/ASM paths are unaffected.

Testing

Found while troubleshooting a recurring failing nightly test nightly-ConstantTimeTest-v2

AES_GCM_decrypt_C cleared the output on a tag mismatch with
'if (ret != 0) ForceZero(out, sz)'. That is a conditional branch on the
secret-dependent authentication result, which is not constant time and is
flagged by the ct-valgrind constant-time test (Conditional jump depends on
uninitialised value in AES_GCM_decrypt_C).

Mask the output with 'res' (already computed as all-ones on tag mismatch,
zero on match) instead of branching, matching the constant-time idiom used
for the tag comparison itself. C path only; the AES-NI/ASM paths are
unaffected.

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.

Pull request overview

This PR updates the AES-GCM C decryption path to clear (mask) decrypted output on authentication-tag mismatch without a secret-dependent conditional branch, addressing constant-time analysis findings from ct-valgrind in AES_GCM_decrypt_C.

Changes:

  • Replace if (ret != 0) ForceZero(out, sz) with a constant-time masking loop driven by the tag-compare result mask (res).
  • Add local variables (mask, i) to support branchless output clearing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

@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 #10895

Scan targets checked: wolfcrypt-bugs, wolfcrypt-src

No new issues found in the changed files. ✅

@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: 2 total — 2 posted, 0 skipped

Posted findings

  • [Low] Output masking now runs a full pass over out[] on every successful decryptwolfcrypt/src/aes.c:11446-11449
  • [Low] No test asserts output buffer is zeroed on tag mismatch for the C pathwolfcrypt/src/aes.c:11444-11449

Review generated by Skoll via Claude/Codex

Comment thread wolfcrypt/src/aes.c
Comment thread wolfcrypt/src/aes.c
Frauschi
Frauschi previously approved these changes Jul 14, 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.

LGTM

Review follow-ups for the constant-time AES-GCM decrypt output clear:

- Guard the output-masking pass with #ifndef WC_AES_GCM_DEC_AUTH_EARLY. In
  that configuration the tag is verified before decryption and a mismatch
  returns before any output is written, so the masking pass is a guaranteed
  no-op; skipping it avoids a wasted O(sz) pass.

- Add a test in aesgcm_test: decrypt with a corrupted tag into a pre-filled
  buffer and assert wc_AesGcmDecrypt returns AES_GCM_AUTH_E and, on the
  software C path, that the output buffer is cleared to zero. The AES-NI/asm
  decrypt paths and the FIPS module do not clear the output on auth failure,
  so the zero check forces the C path (use_aesni = 0) and is limited to it
  (and skipped under HAVE_FIPS). The AES_GCM_AUTH_E comparison uses
  WC_NO_ERR_TRACE().

Verified (gcc 15.2): make check passes on the default (C path) build;
testwolfcrypt AES-GCM passes with --enable-aesni and with
-DWC_AES_GCM_DEC_AUTH_EARLY; ct-valgrind aes_gcm reports 0 errors.
check-source-text reports these as unneeded because the macros are now
defined in the checked build config, so their known-extra whitelist entries
are redundant:
  WOLFSSL_ASN_TEMPLATE_NEED_SET_INT32
  WOLFSSL_ASYNC_CERT_YIELD
  WOLFSSL_MLKEM_DYNAMIC_KEYS

@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: 4 total — 2 posted, 2 skipped

Posted findings

  • [High] Zero-check test fails on WC_AES_GCM_DEC_AUTH_EARLY buildswolfcrypt/test/test.c:19197-19214
  • [Medium] Zero-check denylist may fail under WOLFSSL_ASYNC_CRYPT with a real devicewolfcrypt/test/test.c:19180-19214
Skipped findings
  • [Low] Unrelated macro-list deletions bundled into AES-GCM fix
  • [Low] Unconditional output pass runs even on auth success

Review generated by Skoll via Claude/Codex

Comment thread wolfcrypt/test/test.c Outdated
Comment thread wolfcrypt/test/test.c
Skoll review of the auth-fail zero-check test in aesgcm_test:

- The guard listed WOLFSSL_ARMASM_NO_HW_CRYPTO and __aarch64__, which are
  defined on default x86-64 builds, so the zero-check block was compiled out
  and the assertion never actually ran there. They are subsumed by
  WOLFSSL_ARMASM (the condition under which AES_GCM_decrypt_C is not the
  decrypt path), so use that instead and the check runs on the C path.

- Exclude WC_AES_GCM_DEC_AUTH_EARLY (out is not written on an early-auth
  failure) and WOLFSSL_ASYNC_CRYPT (a real async device may offload the
  decrypt and not clear the output).

Verified: default make check passes with the zero-check now executing;
testwolfcrypt AES-GCM passes with --enable-aesni and with
-DWC_AES_GCM_DEC_AUTH_EARLY.
The RISC-V ASM build provides its own AES-GCM implementation
(wolfcrypt/src/port/riscv/riscv-64-aes.c) rather than AES_GCM_decrypt_C, so
it does not clear the output buffer on authentication failure. Exclude it
from the zero-check, matching the other non-C decrypt paths. Fixes the
riscv64 multi-arch testwolfcrypt failure.
@danielinux

Copy link
Copy Markdown
Member Author

Jenkins retest this please

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

LGTM

@Frauschi
Frauschi merged commit 2494da4 into wolfSSL:master Jul 15, 2026
339 checks passed
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