Skip to content

SP 800-90B verdict preservation & APT/RCT compliance fixes - #11506

Open
kaleb-himes wants to merge 11 commits into
wolfSSL:masterfrom
kaleb-himes:PQ-FS-2026-Part3-SecurityReview-nofallback-T
Open

kaleb-himes wants to merge 11 commits into
wolfSSL:masterfrom
kaleb-himes:PQ-FS-2026-Part3-SecurityReview-nofallback-T

Conversation

@kaleb-himes

Copy link
Copy Markdown
Contributor

Description

What it does

  • Preserves SP 800-90B verdicts (ENTROPY_RT_E / ENTROPY_APT_E) through reseed instead of flattening them to RNG_FAILURE_E; instantiate already did this. Fixes a defect where a stuck source reported -294 at instantiate but -199 at reseed.

  • Fixes the APT: the cutoff was fixed at 325 for every window, unreachable in the 132-byte reseed and 196-byte instantiate seeds, so both APT loops were dead code. Cutoff is now per window size (101 at 132, 140 at 196, 325 at 512), satisfying IG D.K Res 16 (C ≤ W).

  • Replaces the sliding all-symbols scan with the literal SP 800-90B 4.4.2 test: reference sample, non-overlapping windows, cutoff derived for that test.

  • Re-adds all-values coverage as a 4.3 Req 1c additional developer-defined test, with its own derived cutoff at alpha/256 (union bound over the alphabet: 105 / 146 / 334). Implemented as a majority vote, so no histogram, no allocation, nothing to zeroize.

  • Short trailing windows slide back to full length; no window is formed when its cutoff could not be reached.

  • linuxkm: the DRBG reinit-and-retry path now treats a seed health-test alarm as recoverable, alongside RNG_FAILURE_E. DRBG_CONT_FIPS_E is deliberately excluded (catastrophic, must not retry).

  • In wc_linuxkm_drbg_generate()'s loop, when wc_RNG_GenerateBlock() returns one of the three codes, the kernel does not ignore the failure and retry the same state. It:

  • breaks out if any bytes were already delivered (if (slen > 0) break;), so a partially served request never gets a second, differently-seeded half;

  • retries at most once (retried flag), then breaks to -EIO;

  • only retries when it may block (can_wait);

  • recovers by calling wc_rng_bank_inst_reinit(), which frees and re-instantiates the instance, i.e. a fresh wc_InitRng with fresh entropy, not a resumption of the condemned state.

How it maps to the standards

  • SP 800-90B §4.3 Req 2 is the controlling text: "the application is allowed to react differently to different types of failures (e.g., by inhibiting output for a short time)", provided the developer shows the failures handled that way are "extremely likely to be intermittent" and that a permanent failure "will ultimately signal an error condition to the consuming application and cease operation." One bounded retry into a fresh instantiation is exactly that shape: a stuck source fails the second attempt too, and the kernel then returns -EIO.
  • The evidence half of Req 2 is what all the measurement was for: zero alarms in 4·10^9 live instantiates and 10^9-scale seed sweeps, against an analytic per-window rate of 2^-30 by construction and ~2^-700 on real conditioned bytes. A single alarm is overwhelmingly likely to be a genuine source event rather than noise, and either way the retry cannot mask it for long, because a persistent fault fails twice.
  • DRBG_CONT_FIPS_E (-209) is a different animal. It comes from wc_RNG_HealthTestLocal(), the SP 800-90A §11.3 DRBG known-answer test, and §11.4.2 treats that as catastrophic: error state, no output, and recovery only by restart. fips.c enforces that, keying SetConTestFailure() and the move to FIPS_MODE_FAILED off exactly that code. Retrying it would be retrying a broken DRBG, so it is deliberately absent from the recovery condition and falls straight through to -EIO.

Testing

How it was tested

  • False positives, zero in every run: 4,000,000,000 live FIPS instantiates, 10,000,000 live reseeds, and 3.5·10^9 seed-level trials at 132, 196 and 600 bytes.
  • Detection improved at W=132: 0.47% → 52.6% at 76% bias, 2.1% → 88.1% at 80%.
  • Differential tested against an independently written model (histogram vs majority vote): 10M+ cases, 0 mismatches.
  • Negative controls: both tests proven load-bearing by mutants that disable each one (RCT disabled → 3 vectors fail; APT disabled → 4 fail), and each unit test fails when its expected cutoff is perturbed.
  • Bench: 8/8 cells on the pushed commit (fips-ready, fips-v7, debug-memory-all, linuxkm-host, c89-decls, c89-clang-unreachable, unknown-macros, check-source-text-macros), plus valgrind 0 errors and make-dist.
  • Cutoff tables generated by exact rational arithmetic; the generator reproduces the standard's published C=325 at W=512 before emitting.

Checklist

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

Note to self:

Security Policy needs two lines: H=1 is a deliberate floor, and the per-seed alpha is 2^-29 (two tests over one window).

@kaleb-himes kaleb-himes self-assigned this Sep 20, 2026
@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

gcc-arm-cortex-m0plus

  • FLASH: .text +184 B (+1.8%, 69,011 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m3

  • FLASH: .text +148 B (+0.9%, 128,471 B / 262,144 B, total: 49% used)

gcc-arm-cortex-m4

  • FLASH: .text +192 B (+0.6%, 207,266 B / 262,144 B, total: 79% used)

gcc-arm-cortex-m4-baremetal

  • FLASH: .text +128 B (+1.6%, 71,395 B / 262,144 B, total: 27% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text +128 B (+0.6%, 180,765 B / 262,144 B, total: 69% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .rodata +1,024 B, .text +128 B (+0.6%, 189,820 B / 1,048,576 B, total: 18% used)

gcc-arm-cortex-m4-min-ecc

  • FLASH: .text +128 B (+1.8%, 66,181 B / 262,144 B, total: 25% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .rodata +1,024 B, .text +192 B (+0.2%, 787,556 B / 1,048,576 B, total: 75% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +128 B (+0.5%, 219,742 B / 262,144 B, total: 84% used)

gcc-arm-cortex-m4-pq

  • FLASH: .rodata +1,024 B, .text +128 B (+0.4%, 304,720 B / 1,048,576 B, total: 29% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .rodata +1,024 B, .text +128 B (+0.3%, 336,360 B / 1,048,576 B, total: 32% used)

gcc-arm-cortex-m4-sp-math

  • FLASH: .text +128 B (+1.8%, 66,181 B / 262,144 B, total: 25% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +128 B (+0.9%, 129,203 B / 262,144 B, total: 49% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +128 B (+0.5%, 245,204 B / 262,144 B, total: 94% used)

gcc-arm-cortex-m7

  • FLASH: .text +192 B (+0.6%, 207,266 B / 262,144 B, total: 79% used)

gcc-arm-cortex-m7-pq

  • FLASH: .rodata +1,024 B, .text +128 B (+0.4%, 305,680 B / 1,048,576 B, total: 29% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text +192 B (+0.5%, 245,268 B / 262,144 B, total: 94% used)

linuxkm-pie

  • Data: __patchable_function_entries +16 B (+0.1%, 27,808 B)

stm32-sim-stm32h753

  • FLASH: .text +1,408 B (+0.7%, 192,100 B / 2,097,152 B, total: 9% used)
    No memory changes detected for:
  • linuxkm-standard

@kaleb-himes
kaleb-himes force-pushed the PQ-FS-2026-Part3-SecurityReview-nofallback-T branch from 0edd3ee to b04410c Compare September 21, 2026 01:53
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.

1 participant