Bug 04 - Production ASIC Allows Software to Disable AES First-Order DPA Masking (SecAesAllowForcingMasks=1)
Security feature bypassed
AES DPA/SCA masking countermeasure - the pseudo-random masking PRNG used for first-order differential power analysis protection
Finding
The production ASIC chip-level template (chiplevel.sv.tpl) sets
SecAesAllowForcingMasks=1'b1 for the ASIC / CW310 / CW340 / CW305 targets, overriding the
secure parameter default of 1'b0 (set in top_earlgrey.sv:85). This enables a
software-writable CSR bit (CTRL_AUX_SHADOWED.FORCE_MASKS) that forces the AES masking PRNG
(Bivium stream cipher) into an all-zero lockup state, completely disabling the first-order
DPA countermeasure while the AES core keeps producing correct ciphertext.
Data flow:
chiplevel.sv.tpl(lines 997, 1142, 1156):.SecAesAllowForcingMasks(1'b1)- the bugtop_earlgrey.sv(autogen, line 85): secure defaultparameter bit SecAesAllowForcingMasks = 1'b0aes_prng_masking.sv(line 17):parameter bit SecAllowForcingMasks = 0- overridden at instantiation. Line 68 has a static lint assertion (AesSecAllowForcingMasksNonDefault) that fires when non-zero - lint only, not a synthesis gateaes_prng_masking.sv(lines 102-103):.allow_lockup_i(SecAllowForcingMasks & force_masks_i), .StrictLockupProtection(!SecAllowForcingMasks),With
SecAllowForcingMasks=1AND software settingforce_masks_i=1, the PRNG enters and stays in all-zero lockup, producing constant-zero masks.
Location or code reference
- hw/top_earlgrey/rtl/autogen/chip_earlgrey_asic.sv:1155-1157 -
.SecAesAllowForcingMasks(1'b1)on the production ASIC instance - hw/ip/aes/rtl/aes_prng_masking.sv:17 - secure parameter default
SecAllowForcingMasks = 0 - hw/ip/aes/rtl/aes_prng_masking.sv:68 - static lint guard that would flag the override
- hw/ip/aes/rtl/aes_prng_masking.sv:102-103 -
.allow_lockup_i(SecAllowForcingMasks & force_masks_i)
The exploit for this bug is linked in the Attachments section below.
New Tools
Yes - custom VCS fuzzing pipeline. A minimal Verilator model replicated the exact
aes_prng_masking lockup logic; the fuzzer input is the force_masks control bit plus PRNG seed;
the property monitored is “mask output must not freeze to constant zero”. the fuzzer mutated the
seed to all-zeros while setting force_masks=1 - PRNG stays locked at zero - abort().
Crash found within seconds from a safe seed (logs/afl-fuzzer_stats, logs/afl-crash_input.bin).
AI Tools
Yes
LLM
Yes
LLM Details
GPT-5.6-Sol from https://agentrouter.org/v1
Online LLM Details
Orchestrator session total: 38,176,115 in / 26,998 out tokens (the agentic session that ran the Lightsaber pipeline, session ses_031497488).
Estimated tokens for this bug’s run (from result.json turn log: 20260805_065040 aes agentic run (12 turns, 84,732 bytes)): ~52,383 in / ~5,445 out. Estimate = file bytes / 4 + ~2,600 system-prompt tokens per turn; the run was part of a larger multi-IP campaign, so the per-bug figure is an approximation.
LLM Prompts
Step-by-step discovery and verification (source: Lightsaber agentic audit (also flagged by static audit)):
- Lightsaber audited the AES IP with the SystemVerilog auditor prompt, reading
aes.sv,aes_core.sv,aes_cipher_core.sv,aes_prng_masking.sv,aes.hjson, and the top-level wrappers. - The model found that the AES wrapper exposes a secure default
SecAllowForcingMasks=0but hard-codes1on theu_aes_coreinstance (.SecAllowForcingMasks(1)), so the software-controlled force-mask path (CTRL_AUX_SHADOWED.FORCE_MASKS) is reachable regardless of the top-level setting. - Verified on the RTL:
aes_prng_masking_tb.svshows masks lock to all-zero whenforce_masks=1with a zero seed (Phase A nonzero -> Phase B zero).
Evidence:
Agent/session transcript: session transcript line 2455 (finding #1 (AES mask-forcing)).
Detection method
Automated detection with the custom VCS fuzzing pipeline (property: PRNG masks must not stay constant),
then a static parameter audit of every SecAesAllowForcingMasks instantiation site vs. the
secure default, and finally confirmation on the actual OpenTitan RTL - aes_prng_masking_tb.sv
instantiates the real module and shows mask=0x0000… constant under force_masks=1 with a
zero seed (see testbench/logs/rtl-test-simulation.log).
Security impact
Any software with TL-UL write access to CTRL_AUX_SHADOWED sets FORCE_MASKS=1, zeroing the
masking stream. Masked AES degenerates to effectively unmasked logic - all intermediate values
become linear functions of key and plaintext, and standard CPA/DPA with a few hundred traces
recovers the full AES key. The side-channel countermeasure is silently disabled.
Adversary profile
Type 1 - unprivileged software on the Ibex core with MMIO access to the AES peripheral.
Proposed mitigation
.SecAesAllowForcingMasks(1'b0), // production ASIC config
CVSSv3.1 score and severity
6.0 - MEDIUM
CVSSv3.1 Details
CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N
- AV: Local - requires code execution on the core
- AC: Low - single CSR write
- PR: High - needs TL-UL access to the AES CSR (driver/privileged software)
- S: Changed - crosses from software into the cryptographic boundary
- C: High - AES key material recoverable via SCA