From 1d57042210cc363f834591ce62f8bb12e31f7c17 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 07:44:15 -0600 Subject: [PATCH 01/10] Add the LINUXKM_RBGC construction, disabled by default --- configure.ac | 20 + linuxkm/Kbuild | 12 +- linuxkm/Makefile | 5 +- linuxkm/RBGC-design.md | 366 +++++ linuxkm/include.am | 30 +- linuxkm/linuxkm_wc_port.h | 33 + linuxkm/module_exports.c.template | 5 + linuxkm/module_hooks.c | 406 +++++- ...atch => WOLFSSL_KERNELv5_10_17_FIPS.patch} | 178 ++- ...tch => WOLFSSL_KERNELv5_10_236_FIPS.patch} | 180 ++- ...h => WOLFSSL_KERNELv5_14_el9_6_FIPS.patch} | 180 ++- ...5.patch => WOLFSSL_KERNELv5_15_FIPS.patch} | 176 ++- .../5.16/WOLFSSL_KERNELv5_16_FIPS.patch | 595 ++++++++ ...h => WOLFSSL_KERNELv5_17_tegra_FIPS.patch} | 180 ++- .../5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch | 595 ++++++++ .../5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch | 604 +++++++++ ...7.patch => WOLFSSL_KERNELv5_17_FIPS.patch} | 180 ++- .../5.18/WOLFSSL_KERNELv5_18_FIPS.patch | 608 +++++++++ .../patches/5.6/WOLFSSL_KERNELv5_6_FIPS.patch | 605 +++++++++ ...patch => WOLFSSL_KERNELv6_1_73_FIPS.patch} | 182 ++- .../6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch | 617 +++++++++ ...2.patch => WOLFSSL_KERNELv6_12_FIPS.patch} | 184 ++- ...5.patch => WOLFSSL_KERNELv6_15_FIPS.patch} | 184 ++- .../6.16/WOLFSSL_KERNELv6_16_FIPS.patch | 635 +++++++++ ...v0.patch => WOLFSSL_KERNELv7_0_FIPS.patch} | 182 ++- linuxkm/patches/README.md | 343 +++++ linuxkm/patches/patch-kernel.sh | 86 ++ linuxkm/patches/regen-patches.sh | 14 +- src/include.am | 8 + wolfcrypt/src/linuxkm_get_entropy.c | 1191 +++++++++++++++++ wolfcrypt/src/random.c | 135 +- wolfcrypt/src/sha512.c | 81 +- wolfssl/wolfcrypt/include.am | 1 + wolfssl/wolfcrypt/linuxkm_get_entropy.h | 135 ++ wolfssl/wolfcrypt/random.h | 8 + 35 files changed, 8697 insertions(+), 247 deletions(-) create mode 100644 linuxkm/RBGC-design.md rename linuxkm/patches/5.10.17/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v17.patch => WOLFSSL_KERNELv5_10_17_FIPS.patch} (60%) rename linuxkm/patches/5.10.236/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v236.patch => WOLFSSL_KERNELv5_10_236_FIPS.patch} (61%) rename linuxkm/patches/5.14.0-570.58.1.el9_6/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v14-570v58v1-el9_6.patch => WOLFSSL_KERNELv5_14_el9_6_FIPS.patch} (61%) rename linuxkm/patches/5.15/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v15.patch => WOLFSSL_KERNELv5_15_FIPS.patch} (60%) create mode 100644 linuxkm/patches/5.16/WOLFSSL_KERNELv5_16_FIPS.patch rename linuxkm/patches/5.17-ubuntu-jammy-tegra/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17-ubuntu-jammy-tegra.patch => WOLFSSL_KERNELv5_17_tegra_FIPS.patch} (61%) create mode 100644 linuxkm/patches/5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch create mode 100644 linuxkm/patches/5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch rename linuxkm/patches/5.17/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17.patch => WOLFSSL_KERNELv5_17_FIPS.patch} (61%) create mode 100644 linuxkm/patches/5.18/WOLFSSL_KERNELv5_18_FIPS.patch create mode 100644 linuxkm/patches/5.6/WOLFSSL_KERNELv5_6_FIPS.patch rename linuxkm/patches/6.1.73/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v1v73.patch => WOLFSSL_KERNELv6_1_73_FIPS.patch} (61%) create mode 100644 linuxkm/patches/6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch rename linuxkm/patches/6.12/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v12.patch => WOLFSSL_KERNELv6_12_FIPS.patch} (61%) rename linuxkm/patches/6.15/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v15.patch => WOLFSSL_KERNELv6_15_FIPS.patch} (63%) create mode 100644 linuxkm/patches/6.16/WOLFSSL_KERNELv6_16_FIPS.patch rename linuxkm/patches/7.0/{WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v0.patch => WOLFSSL_KERNELv7_0_FIPS.patch} (63%) create mode 100644 linuxkm/patches/README.md create mode 100755 linuxkm/patches/patch-kernel.sh create mode 100644 wolfcrypt/src/linuxkm_get_entropy.c create mode 100644 wolfssl/wolfcrypt/linuxkm_get_entropy.h diff --git a/configure.ac b/configure.ac index 884ff3122ad..76d6fa94dcc 100644 --- a/configure.ac +++ b/configure.ac @@ -901,6 +901,25 @@ then fi AC_SUBST([ENABLED_LINUXKM_PIE]) +# LINUXKM_RBGC: SP 800-90C Sec. 7 DRBG tree for the kernel module, serving +# get_random_bytes() from inside the boundary. A root RBGC construction seeds +# per-CPU leaves; those are separate instantiations, so SP 800-90A Sec. 8.6.9 +# self-seeding does not apply. Compiles one SHA-512, the portable C one, so no +# transform is selected at run time. +ENABLED_LINUXKM_RBGC=no +AC_ARG_ENABLE([linuxkm-rbgc], + [AS_HELP_STRING([--enable-linuxkm-rbgc],[Enable the SP 800-90C chained DRBG construction for the Linux kernel module (default: disabled)])], + [ENABLED_LINUXKM_RBGC=$enableval]) +if test "$ENABLED_LINUXKM_RBGC" = "yes" +then + if test "$ENABLED_LINUXKM" != "yes" + then + AC_MSG_ERROR([--enable-linuxkm-rbgc requires --enable-linuxkm.]) + fi + AM_CFLAGS="$AM_CFLAGS -DLINUXKM_RBGC" +fi +AC_SUBST([ENABLED_LINUXKM_RBGC]) + AC_ARG_ENABLE([linuxkm-benchmarks], [AS_HELP_STRING([--enable-linuxkm-benchmarks],[Enable crypto benchmarking autorun at module load time for Linux kernel module (default: disabled)])], [ENABLED_KERNEL_BENCHMARKS=$enableval]) @@ -13451,6 +13470,7 @@ AM_CONDITIONAL([BUILD_TSP],[test "x$ENABLED_TSP" = "xyes" || test "x$ENABLED_USE AM_CONDITIONAL([BUILD_SMIME],[test "x$ENABLED_SMIME" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_HASHFLAGS],[test "x$ENABLED_HASHFLAGS" = "xyes"]) AM_CONDITIONAL([BUILD_LINUXKM],[test "$ENABLED_LINUXKM" = "yes"]) +AM_CONDITIONAL([BUILD_LINUXKM_RBGC],[test "$ENABLED_LINUXKM_RBGC" = "yes"]) AM_CONDITIONAL([BUILD_KERNEL_MODE_DEFAULTS],[test "$KERNEL_MODE_DEFAULTS" != "no"]) AM_CONDITIONAL([BUILD_BSDKM],[test "$ENABLED_BSDKM" = "yes"]) AM_CONDITIONAL([BUILD_KERNEL_MODULE],[test "$ENABLED_BSDKM" = "yes" || test "$ENABLED_LINUXKM" = "yes"]) diff --git a/linuxkm/Kbuild b/linuxkm/Kbuild index 3e8deb9628e..c385ba969a7 100644 --- a/linuxkm/Kbuild +++ b/linuxkm/Kbuild @@ -99,9 +99,15 @@ ifeq "$(KERNEL_ARCH_X86)" "yes" endif endif -ifdef CONFIG_CC_IS_CLANG - WOLFSSL_CFLAGS += -Wno-unused-parameter -endif +# -Wextra (from configure's warning set) turns on -Wunused-parameter, which +# fires on the KERNEL's own headers, not on wolfSSL code: e.g. 6.8's +# include/linux/node.h:88 node_set_perf_attrs() is an empty static inline with +# three named parameters when !CONFIG_HMEM_REPORTING. The kernel never builds +# with that warning on -- scripts/Makefile.extrawarn:90 pairs it as +# "-Wextra -Wunused -Wno-unused-parameter" even at W=1 -- so an out-of-tree +# module that adds -Wextra must pair it the same way. This was clang-only; +# gcc fails identically (measured: gcc 14.3 vs linux-6.8.12 defconfig). +WOLFSSL_CFLAGS += -Wno-unused-parameter # this rule is needed to get build to succeed in 4.x (get_thread_size still doesn't get built) $(obj)/linuxkm/get_thread_size: $(src)/linuxkm/get_thread_size.c diff --git a/linuxkm/Makefile b/linuxkm/Makefile index 00fc70df6a0..66a0ff40bf9 100644 --- a/linuxkm/Makefile +++ b/linuxkm/Makefile @@ -52,7 +52,10 @@ ifndef SRC_TOP SRC_TOP=$(shell dirname $(MODULE_TOP)) endif -WOLFSSL_CFLAGS=-DHAVE_CONFIG_H -I$(SRC_TOP) -DBUILDING_WOLFSSL $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -Wno-declaration-after-statement -Wno-redundant-decls -DLIBWOLFSSL_GLOBAL_EXTRA_CFLAGS="\" $(KERNEL_EXTRA_CFLAGS)\"" +# -Wno-nested-externs: the kernel's _compiletime_assert emits an extern inside a +# function body, which -Werror turns into a build failure. Diagnostic-only, so +# object code (and the FIPS in-core hash) is unchanged. +WOLFSSL_CFLAGS=-DHAVE_CONFIG_H -I$(SRC_TOP) -DBUILDING_WOLFSSL $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -Wno-declaration-after-statement -Wno-redundant-decls -Wno-nested-externs -DLIBWOLFSSL_GLOBAL_EXTRA_CFLAGS="\" $(KERNEL_EXTRA_CFLAGS)\"" ifdef KERNEL_EXTRA_CFLAGS WOLFSSL_CFLAGS += $(KERNEL_EXTRA_CFLAGS) endif diff --git a/linuxkm/RBGC-design.md b/linuxkm/RBGC-design.md new file mode 100644 index 00000000000..4d226aa26ce --- /dev/null +++ b/linuxkm/RBGC-design.md @@ -0,0 +1,366 @@ +# How the kernel module serves `get_random_bytes()` + +Enabled with `--enable-linuxkm-rbgc`. Needs a kernel carrying one of the +patches in `linuxkm/patches/`, which export +`wolfssl_linuxkm_register_random_bytes_handlers()` and its unregister +counterpart from `drivers/char/random.c`. Without one of those patches the +module does not build: `linuxkm/module_hooks.c` stops on an `#error` naming +this directory. + +## The idea in one paragraph + +The Linux kernel has its own random number generator. When wolfSSL is loaded +as a FIPS module, we take over `get_random_bytes()` so the numbers come from +the validated module instead. The hard part is not the cryptography, it is +that the kernel asks for random numbers from places where you are not allowed +to wait for anything: inside interrupt handlers, and inside NMIs. So the +design is arranged so that **no caller ever has to wait, and no caller is ever +turned away**. + +## The shape + +One "root" generator gathers real entropy. It never answers a caller. Its +only job is to feed the leaves. Each CPU gets its own leaves, so two CPUs +never queue behind each other. + +``` + entropy source (SP 800-90B noise) + | + | only the root ever draws from it + v + +-----------+ + | root | refreshes on its own timer + +-----------+ + / | \ + seeds / | \ seeds + v v v + CPU 0 CPU 1 CPU 2 ... + +---------+ +---------+ +---------+ + | leaf | | leaf | | leaf | <- normal work + +---------+ +---------+ +---------+ (apps, softirq, + | nmi A/B | | nmi A/B | | nmi A/B | hardirq) + +---------+ +---------+ +---------+ <- NMI only + ^ ^ ^ + | | | + get_random_bytes() get_random_bytes() get_random_bytes() +``` + +A leaf is a DRBG: give it a seed, it produces an endless stream of random +bytes. After a while it must be re-seeded, or the stream is no longer +considered fresh. + +## Why one leaf per CPU is enough for normal work + +A caller turns interrupts off while it uses its leaf. With interrupts off, a +CPU cannot be pulled away to run something else. So on CPU 0, an application, +a softirq and a hardirq can never be inside the leaf at the same time - they +have to take turns by construction. And no other CPU touches CPU 0's leaf. + +``` +CPU 0 timeline: [ app uses leaf 0 ]---[ hardirq uses leaf 0 ]---[ app ] + ^ interrupts off ^ interrupts off + nothing can interleave here +``` + +## The re-seed problem, and the fix + +Re-seeding rewrites the leaf. If that happened while a caller was reading the +leaf, the caller would get garbage - so the old design made callers wait, and +if they could not wait, turned them away. + +The fix: **do the re-seed on the same CPU that owns the leaf.** A small +background job is pinned to each CPU. When it runs, it turns interrupts off, +which means no caller on that CPU can possibly be inside the leaf. It +re-seeds, turns interrupts back on, and leaves. Nobody waited. Nobody was +refused. + +``` +CPU 0: [ app ]---[ re-seed job, interrupts off ]---[ app ]---[ softirq ] + ^ safe: nothing else on CPU 0 can run right now +``` + +This is the same pattern the kernel's own generator uses for its per-CPU state. + +## NMI is the exception + +An NMI - "non-maskable interrupt" - is the one thing that turning interrupts +off does *not* stop. It can arrive at any instant, even in the middle of the +re-seed job. So an NMI cannot share the normal leaf. + +Each CPU therefore has **two** NMI generators, A and B. Exactly one is "live" +at a time. The re-seed job works on the spare, never on the live one, and +then flips a single switch to make the spare live: + +``` +before: live -> [ A ] [ B ] <- job re-seeds this one, off to the side + ^ + NMI reads A + +flip: live -> [ B ] [ A ] <- becomes the spare, untouched + ^ until the next re-seed cycle + NMI reads B +``` + +Flipping the switch is a single instruction. An NMI landing at any moment +reads either A or B, and both are complete, working generators. There is no +in-between state to catch. + +That leaves one hole. An NMI that started reading A can be stalled +mid-handler - on a virtual machine the whole CPU can be taken away for hundreds +of milliseconds - and if the re-seed job came round twice while it was stalled, +the second pass would be rewriting A underneath it. + +Two things close it. First, the re-seed job for a CPU's pair runs on that CPU, +so a stalled NMI stalls the job along with it: the job cannot come round even +once while the NMI is mid-read, let alone twice. CPU hot-plug is the only +thing that can put that job on a different CPU, and it is turned away when it +lands on one. + +Second, as a backstop, each generator carries a version number, bumped before +it is re-seeded. An NMI notes the version, generates the caller's bytes, and +checks the version again before telling the caller they are good: + +``` + NMI: note version of A -> generate the bytes -> version still same? + | | + yes no + | | + tell the read the other + caller they one over the + are good top, up to 3x +``` + +Nothing is generated before it is asked for and nothing is kept afterwards. +The bytes are written where the caller asked for them, and the caller is +stopped inside the call until it returns; a return of anything but success +means the kernel refills the whole buffer itself, so an interrupted read is +simply overwritten rather than needing to be held anywhere. + +A changed version is not a reason to give up. It means that generator was +re-seeded mid-read, and the *other* one is live now and fully working - so the +NMI simply reads that one instead. Only if three reads in a row are each +interrupted does it give up. + +That matters more than it looks. Giving up does not mean "no bytes"; it means +the kernel answers from its own generator, which is outside the module. Those +bytes are real randomness, but they are not the validated module's output, so +every refusal is a hole in the claim that the module serves this call. A +re-seed lands about 7.6 times a second per CPU and a read takes microseconds, +so three interrupted reads in a row is not something the measured rates can +produce. + +## Big requests are cut into pieces + +While a leaf is producing bytes, interrupts are turned off on that CPU. That +is what makes the whole scheme safe, but it means the CPU cannot respond to +anything else for as long as it takes. + +The catch is that the caller chooses the size. `ip_rt_init()` asks for 65536 +bytes in a single call. A 256-byte piece takes 4.1 microseconds; 65536 bytes +is 256 such pieces, so producing that much in one go would keep interrupts off +for about 1.05 milliseconds - far too long, and on a real-time kernel it is +simply not allowed. + +So a request is served 256 bytes at a time, letting interrupts back in between +pieces: + +``` + one 64 KB request + + before: [============== interrupts off ==============] ~1.05 ms + nothing else + can run + + after: [==] [==] [==] [==] [==] ... [==] each piece is + ^ ^ ^ ^ the same small + interrupts are back on here cost, however + big the ask +``` + +The time interrupts are off is now set by the piece size, not by the request +size. A bigger request takes more pieces, not longer pieces. + +Splitting is safe because each piece is a complete, independent request to the +generator. If something else on that CPU asks for random bytes between two +pieces, it simply gets served and moves the generator on; the next piece +carries on from wherever it now is. Every byte handed back came from the +validated module either way. + +## What this buys + +* Every caller is served, in every context. Nothing waits, and nothing is + handed back to the kernel's own generator: the one path that could refuse now + re-reads the other instance instead, three times before giving up, and with + the on-CPU re-seed rule in place there is no sequence of events left that + makes even the first re-read necessary. +* Large requests are served in 256-byte pieces, so the time spent with + interrupts turned off does not grow with the size of the request. Measured + over 62,504 requests of 64 KB each, the time interrupts are off is the same + as for a small request: 4.1 microseconds at the median, 8.2 at the 99th + percentile. Served in one go the same request would have blocked interrupts + for about 1.05 milliseconds. +* Only the root touches the entropy source. Nothing is generated in advance + and stored for later use - each seed is produced at the moment it is needed + and destroyed immediately after. +* Measured: 133 million calls, zero served by the kernel's generator, other + than the handful that arrive before the module is even loaded. + +## What the module does NOT serve + +Two things are outside the module, and both belong in the Security Policy. +Measured on `linux-6.16.12`, x86_64 and aarch64 guests. + +### 1. Everything before the self-tests finish + +The kernel needs random numbers long before the module is allowed to give it +any. This is not a timing accident that a bigger patch could fix - FIPS 140-3 +forbids a module from providing cryptographic services until its power-on +self-tests pass, and those take real time: + +``` + 0.04s kernel's own generator ready + 1.74s libwolfssl.ko starts loading + | + | <-- FIPS integrity check and self-tests: 1.2s to 4.1s + | + 4.26s our hook goes live +``` + +So there is necessarily a window where the kernel asks and the module may not +answer. Building wolfSSL into the kernel instead of loading it as a module +shortens the window but cannot remove it - the self-tests still have to run. + +**Who asks during that window.** Measured on `linux-6.16.12`: 40 calls on +x86_64, 28 on aarch64. Nothing in this list is a cryptographic key. + +| calls | bytes | caller | what it is | cryptographic? | +|---:|---:|---|---|---| +| 11 | 4 | `key_alloc` | serial number for a kernel keyring key | no, an identifier | +| 10 | 10 | `rc80211_minstrel_init` | wireless rate-control jitter | no, a tie-breaker | +| 5 | 8 | `sys_getrandom` | userspace asked directly | unknown, userspace decides | +| 5 | 16 | `load_elf_binary` | `AT_RANDOM`: stack canary, pointer guard | no, hardening values | +| 4 | 16 | `uuid_gen` | random UUIDs | no, identifiers | +| 1 | 65536 | `ip_rt_init` | IPv4 route cache hash seed | no, anti-collision | +| 1 | 4 | `sysctl_ipv4_init` | IPv4 sysctl setup | no | +| 1 | 4 | `net_ns_init` | network namespace setup | no | +| 1 | 16 | `fill_ptr_key` | key for hashing pointers in log output | no, anti-disclosure | +| 1 | 128 | `kcmp_cookies_init` | `kcmp()` syscall cookies | no, anti-disclosure | + +aarch64 is the same without the wireless and IPv4-sysctl rows, and with one +fewer `key_alloc`. + +The only two that leave the kernel are `load_elf_binary` and `sys_getrandom`, +which become stack canaries and pointer guards for the few processes that start +before the module is up. + +This list is a property of the kernel's configuration - `rc80211_minstrel_init` +appears only because this guest has wireless built in - so re-measure per +target rather than reusing the table. + +### 2. `get_random_u8/u16/u32/u64` - served, one hook call per batch refill + +These do not call `get_random_bytes()`. They keep a per-CPU batch and refill it +from `_get_random_bytes()`, which is exactly where the patches in +`linuxkm/patches/` put the hook. So on a patched kernel the family IS served by +the module - indirectly and in bulk, one hook call per refill rather than one +per `get_random_u32()`: + +``` + get_random_bytes() --> hook --> wolfSSL one hook call per request + get_random_u32() --> per-CPU batch one hook call per refill + `-- refill --> hook --> wolfSSL +``` + +Measured on `linux-6.12.0` x86_64 patched with +`linuxkm/patches/6.12/WOLFSSL_KERNELv6_12_FIPS.patch` (the base the coverage +table gives for 6.12.0, applied at `--fuzz=0`), one vCPU, a kthread bound to +CPU 0 driving exactly 1,000,000 calls of one width. The counters sit in the +kernel's `random.c` at the hook site, not in the module. + +| driven, 1,000,000 calls | batch entries | hook calls | bytes | served by module | declined | +|---|---:|---:|---:|---:|---:| +| `get_random_u8()` | 96 | 10,417 | 1,000,032 | 10,417 | 0 | +| `get_random_u16()` | 48 | 20,834 | 2,000,064 | 20,834 | 0 | +| `get_random_u32()` | 24 | 41,667 | 4,000,032 | 41,667 | 0 | +| `get_random_u64()` | 12 | 83,333 | 7,999,968 | 83,333 | 0 | +| `get_random_bytes(4)` | n/a | 1,000,000 | 4,000,000 | 1,000,000 | 0 | + +Every refill asks for `sizeof(batch->entropy)` = `CHACHA_BLOCK_SIZE * 3 / 2` = +96 bytes whatever the width, so the hook-call count is the call count divided by +how many entries a batch holds. The u64 row is one short of `ceil(1000000/12)` +because CPU 0's u64 batch already held entries when the window opened. + +**Negative control**: same kernel, same workload, module never inserted. +41,667 hook calls for u32 and 83,333 for u64 - the same counts - of which 0 were +served and every one was charged to "no handler registered". Only which +generator answers changes. A second, independent set of counters read out of +the module agrees with the kernel-side ones to the unit: 41,671 - 4 = 41,667 +process-context calls, all served, none failed or declined. + +**The bytes are the module's; the hand-out is the kernel's.** A +`get_random_u32()` call is not itself a module operation - it takes the next 4 +bytes of a 96-byte block the module generated earlier on that CPU. The module +still generates only when asked and keeps nothing; the batch belongs to the +kernel, which may buffer what it has already been given, as any caller may. + +**Not every supported kernel routes the family this way.** Before the "fast key +erasure" rewrite the batch was refilled from `extract_crng()`, which no patch +hooks. Read out of each version's `drivers/char/random.c`: + +| refills the uN batch from | versions read | +|---|---| +| `_get_random_bytes()` - hooked | 5.10.236, 5.10.265, 5.15.216, 5.17.14, 5.18, 6.1.183, 6.12, 6.15, 6.16, 7.0 | +| `extract_crng()` - not hooked | 5.6.19, 5.10.17, 5.16.20 | + +It is not a version ordering. The rewrite was backported into 5.10.y and +5.15.y and never into 5.16.y, so 5.15.216 is on the hooked side while 5.16.20 is +not. Read the tree, not the number. + +Measured on `linux-5.16.20` x86_64 patched with +`linuxkm/patches/5.16/WOLFSSL_KERNELv5_16_FIPS.patch`: 1,000,000 +`get_random_u32()` calls produced **0** hook calls, while 1,000,000 +`get_random_bytes(4)` calls on the same guest produced 1,000,000. That counter +increments on entry to the hook, before any test of whether a handler is +registered, so the zero measures the kernel's routing and nothing else. + +**The blastmatrix numbers describe the simpler hook, not this one.** The +supported patches hook `_get_random_bytes()`; `patches/kernel/apply-grb-hook.sh` +in the blastmatrix harness instead edits the public `get_random_bytes()` wrapper +and `get_random_bytes_user()`, one level above, so the batched family flows past +it and is counted nowhere. Figures taken through that hook - including the +274.5 million below - therefore describe `get_random_bytes()` only. The +harness selects the supported flavour with `GRB_FIPS_PATCH=1`. + +The family is used continuously rather than in a boot window - TCP sequence +numbers, address-space layout randomisation and slab randomisation all draw from +it - so on a kernel from the hooked row it is a steady, and now measured, part +of what the module serves. + +### What can honestly be claimed + +> Every `get_random_bytes()` call after the module's power-on self-tests +> complete is served by the validated module. + +Measured over 274.5 million calls across four architectures: zero fell back to +the kernel's generator. The boot window above is outside that statement. + +The `get_random_uN` family is covered by its own measurement rather than by this +one, and only on kernels whose batch refill reaches `_get_random_bytes()`: + +> On `linux-6.12.0` every byte the per-CPU `get_random_uN` batches dispensed +> was generated by the validated module. + +One million calls of each of the four widths - 156,251 refills in all - none +declined, none served by the kernel. On a kernel from the unhooked row - 5.16.20 was measured - the +family is not offered to the module at all. + +## Where the code is + +| file | what | +|---|---| +| `wolfcrypt/src/linuxkm_get_entropy.c` | the root, the leaves, the service. Inside the FIPS boundary. | +| `wolfssl/wolfcrypt/linuxkm_get_entropy.h` | its declarations. All internal; none of this is public API. | +| `linuxkm/module_hooks.c` | registers the hook and runs the per-CPU re-seed jobs. Outside the boundary. | + +The standards detail - which SP 800-90C rule each step follows - is in the +comment block at the top of `linuxkm_get_entropy.c`. diff --git a/linuxkm/include.am b/linuxkm/include.am index 724014f0b04..aef707b542c 100644 --- a/linuxkm/include.am +++ b/linuxkm/include.am @@ -6,6 +6,7 @@ EXTRA_DIST += m4/ax_linuxkm.m4 \ linuxkm/Kbuild \ linuxkm/Makefile \ linuxkm/README.md \ + linuxkm/RBGC-design.md \ linuxkm/get_thread_size.c \ linuxkm/linuxkm-fips-hash.c \ linuxkm/linuxkm-fips-hash-wrapper.sh \ @@ -24,13 +25,22 @@ EXTRA_DIST += m4/ax_linuxkm.m4 \ linuxkm/lkcapi_ecdh_glue.c \ linuxkm/lkcapi_rsa_glue.c \ linuxkm/wolfcrypt.lds \ - linuxkm/patches/5.10.17/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v17.patch \ - linuxkm/patches/5.10.236/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v236.patch \ - linuxkm/patches/5.14.0-570.58.1.el9_6/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v14-570v58v1-el9_6.patch \ - linuxkm/patches/5.15/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v15.patch \ - linuxkm/patches/5.17/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17.patch \ - linuxkm/patches/5.17-ubuntu-jammy-tegra/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17-ubuntu-jammy-tegra.patch \ - linuxkm/patches/6.1.73/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v1v73.patch \ - linuxkm/patches/6.12/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v12.patch \ - linuxkm/patches/6.15/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v15.patch \ - linuxkm/patches/7.0/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v0.patch + linuxkm/patches/README.md \ + linuxkm/patches/patch-kernel.sh \ + linuxkm/patches/5.6/WOLFSSL_KERNELv5_6_FIPS.patch \ + linuxkm/patches/5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch \ + linuxkm/patches/5.10.236/WOLFSSL_KERNELv5_10_236_FIPS.patch \ + linuxkm/patches/5.14.0-570.58.1.el9_6/WOLFSSL_KERNELv5_14_el9_6_FIPS.patch \ + linuxkm/patches/5.15/WOLFSSL_KERNELv5_15_FIPS.patch \ + linuxkm/patches/5.16/WOLFSSL_KERNELv5_16_FIPS.patch \ + linuxkm/patches/5.17/WOLFSSL_KERNELv5_17_FIPS.patch \ + linuxkm/patches/5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch \ + linuxkm/patches/5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch \ + linuxkm/patches/5.17-ubuntu-jammy-tegra/WOLFSSL_KERNELv5_17_tegra_FIPS.patch \ + linuxkm/patches/5.18/WOLFSSL_KERNELv5_18_FIPS.patch \ + linuxkm/patches/6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch \ + linuxkm/patches/6.12/WOLFSSL_KERNELv6_12_FIPS.patch \ + linuxkm/patches/6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch \ + linuxkm/patches/6.15/WOLFSSL_KERNELv6_15_FIPS.patch \ + linuxkm/patches/6.16/WOLFSSL_KERNELv6_16_FIPS.patch \ + linuxkm/patches/7.0/WOLFSSL_KERNELv7_0_FIPS.patch diff --git a/linuxkm/linuxkm_wc_port.h b/linuxkm/linuxkm_wc_port.h index 51db536567d..13a0ebccbdb 100644 --- a/linuxkm/linuxkm_wc_port.h +++ b/linuxkm/linuxkm_wc_port.h @@ -216,6 +216,27 @@ #endif WOLFSSL_API void wc_linuxkm_relax_long_loop(void); + #ifdef LINUXKM_RBGC + /* Interrupt save/restore as calls. local_irq_save() expands, on arm64 + * before 6.6, to an ALTERNATIVE() asm whose "r" operand cannot be + * satisfied from a PIE-compiled in-boundary object ("impossible + * constraint in 'asm'"); keeping the expansion in non-PIE glue builds + * on every arm64 tree. */ + WOLFSSL_API unsigned long wc_linuxkm_irq_save(void); + WOLFSSL_API void wc_linuxkm_irq_restore(unsigned long flags); + + /* Current CPU index, for the per-CPU DRBG leaves. + * raw_smp_processor_id() reads a kernel symbol the container may not + * reference, so it goes through the redirect table. */ + WOLFSSL_API int wc_linuxkm_cpu_id(void); + + /* Monotonic nanoseconds for the interrupts-off measurement in + * linuxkm_get_entropy.c. ktime_get_mono_fast_ns() is the NMI-safe + * reader (seqcount latch, no lock), so it is legal with interrupts + * off and in NMI. */ + WOLFSSL_API unsigned long long wc_linuxkm_mono_ns(void); + #endif + #ifndef WC_SIG_IGNORE_BEGIN #define WC_SIG_IGNORE_BEGIN() wc_linuxkm_sig_ignore_begin() #endif @@ -1418,6 +1439,12 @@ typeof(wc_linuxkm_sig_ignore_end) *wc_linuxkm_sig_ignore_end; typeof(wc_linuxkm_check_for_intr_signals) *wc_linuxkm_check_for_intr_signals; typeof(wc_linuxkm_relax_long_loop) *wc_linuxkm_relax_long_loop; + #ifdef LINUXKM_RBGC + typeof(wc_linuxkm_irq_save) *wc_linuxkm_irq_save; + typeof(wc_linuxkm_irq_restore) *wc_linuxkm_irq_restore; + typeof(wc_linuxkm_cpu_id) *wc_linuxkm_cpu_id; + typeof(wc_linuxkm_mono_ns) *wc_linuxkm_mono_ns; + #endif #ifdef CONFIG_KASAN typeof(kasan_disable_current) *kasan_disable_current; @@ -1725,6 +1752,12 @@ #define wc_linuxkm_sig_ignore_end WC_PIE_INDIRECT_SYM(wc_linuxkm_sig_ignore_end) #define wc_linuxkm_check_for_intr_signals WC_PIE_INDIRECT_SYM(wc_linuxkm_check_for_intr_signals) #define wc_linuxkm_relax_long_loop WC_PIE_INDIRECT_SYM(wc_linuxkm_relax_long_loop) + #ifdef LINUXKM_RBGC + #define wc_linuxkm_irq_save WC_PIE_INDIRECT_SYM(wc_linuxkm_irq_save) + #define wc_linuxkm_irq_restore WC_PIE_INDIRECT_SYM(wc_linuxkm_irq_restore) + #define wc_linuxkm_cpu_id WC_PIE_INDIRECT_SYM(wc_linuxkm_cpu_id) + #define wc_linuxkm_mono_ns WC_PIE_INDIRECT_SYM(wc_linuxkm_mono_ns) + #endif #ifdef CONFIG_KASAN #define kasan_disable_current WC_PIE_INDIRECT_SYM(kasan_disable_current) diff --git a/linuxkm/module_exports.c.template b/linuxkm/module_exports.c.template index f3583184edf..c51207d68ba 100644 --- a/linuxkm/module_exports.c.template +++ b/linuxkm/module_exports.c.template @@ -46,6 +46,11 @@ #include #include #include +/* Kbuild generates the export list from the WOLFSSL_API wc_ and wolf symbols + * visible here, so the get_random_bytes service declarations must be too. */ +#ifdef LINUXKM_RBGC + #include +#endif #include #include #include diff --git a/linuxkm/module_hooks.c b/linuxkm/module_hooks.c index 888343b9da6..7554a77ba01 100644 --- a/linuxkm/module_hooks.c +++ b/linuxkm/module_hooks.c @@ -51,6 +51,9 @@ #include #endif #include +#ifdef LINUXKM_RBGC + #include +#endif #include #ifdef NO_INLINE #include @@ -323,11 +326,70 @@ MODULE_PARM_DESC(rodata_dump_path, #include "linuxkm/lkcapi_glue.c" #endif +#ifdef LINUXKM_RBGC + +/* Out here in non-PIE glue so the arm64 ALTERNATIVE asm in local_irq_save() + * compiles; see the declarations in linuxkm_wc_port.h. */ +unsigned long wc_linuxkm_irq_save(void) +{ + unsigned long flags; + + local_irq_save(flags); + + return flags; +} + +void wc_linuxkm_irq_restore(unsigned long flags) +{ + local_irq_restore(flags); +} + +/* raw_ because every caller is already non-preemptible: the service path holds + * interrupts off, and NMI cannot be preempted at all. */ +int wc_linuxkm_cpu_id(void) +{ + return (int) raw_smp_processor_id(); +} + +/* Seqcount-latch reader, so it takes no lock and is legal with interrupts off + * and from NMI (kernel/time/timekeeping.c). */ +unsigned long long wc_linuxkm_mono_ns(void) +{ + return (unsigned long long) ktime_get_mono_fast_ns(); +} + +#endif /* LINUXKM_RBGC */ + int wc_linuxkm_can_block(void) { - /* We can't use preemptible() for this, because we need an accurate test - * even in !CONFIG_PREEMPT_COUNT configs where preemptible() is always 0. + /* preempt_count() is NOT an accurate "may I sleep here" test on its own. + * In !CONFIG_PREEMPT_COUNT configs (PREEMPT_VOLUNTARY without + * PREEMPT_DYNAMIC -- the default through 5.15) preempt_disable() expands to + * barrier() and never touches __preempt_count + * (include/linux/preempt.h), so a task inside kernel_fpu_begin() still + * reads 0 here. preempt_count() is exactly as blind as preemptible() in + * that configuration; the earlier comment claimed the opposite. + * + * The hardirq/softirq masks ARE still maintained there, so the irqs and + * interrupt-context halves of this test remain sound. Only the + * preempt-disabled half is blind, which is why an open vector-register + * section has to be tested directly. + * + * Measured consequence when it was not: on 5.7.19 a cond_resched() from + * WC_RELAX_LONG_LOOP() inside an open bracket slept, the task migrated, + * and wc_restore_vector_registers_x86() ran on a CPU with no open section + * -- stranding kernel_fpu_begin()'s section on the origin CPU for the life + * of the module (1 event in 920,727 saves). */ - return (preempt_count() == 0) && (! irqs_disabled()); + return (preempt_count() == 0) && (! irqs_disabled()) +/* Guarded exactly like the declaration at the top of this file and like the + * other caller: wc_linuxkm_in_svr_bracket() only exists when the vector + * register glue is compiled in. Without it there are no brackets to be + * inside, so the term is vacuously true and its absence changes nothing. */ +#if !defined(WOLFSSL_LINUXKM_USE_MUTEXES) && \ + defined(WOLFSSL_USE_SAVE_VECTOR_REGISTERS) + && (! wc_linuxkm_in_svr_bracket()) +#endif + ; } /* for simplicity, we use a global count to suspend signal processing while any @@ -561,6 +623,277 @@ int wc_linuxkm_GenerateSeed_IntelRD(struct OS_Seed* os, byte* output, word32 sz) static int installed_sysfs_FIPS_optest_trig_files = 0; #endif +#ifdef LINUXKM_RBGC + +/* cpus_read_lock()/cpus_read_unlock() around the per-CPU work setup. */ +#include + +/* The boundary cannot schedule work itself, so the glue drives it. + * + * One delayed work PER CPU, queued with queue_delayed_work_on() so it runs on + * the CPU whose leaf it reseeds. That is what lets the reseed take interrupts + * off and know no caller on that CPU can be inside the leaf, which is what + * removes the need for any exclusion flag in the boundary. + * + * The root's own refresh from the noise source is a separate, unpinned work: + * it is the entropy gather and must not be coupled to leaf demand. */ +#define WC_GRB_MAINT_POLL_MS 50 + +struct wc_grb_cpu_work { + struct delayed_work dw; + int cpu; +}; + +static struct wc_grb_cpu_work *wc_grb_cpu_works; +static struct delayed_work wc_grb_root_work; +static int wc_grb_maint_running; + +static void wc_grb_cpu_work_fn(struct work_struct *work) +{ + /* dw is the first member of wc_grb_cpu_work and work is the first member + * of delayed_work, so the work pointer is the wrapper pointer. + * container_of() cannot be used here: it does void* arithmetic and this + * builds with -Werror=pointer-arith. */ + struct wc_grb_cpu_work *cw = (struct wc_grb_cpu_work *) work; + int ret; + + wc_static_assert(offsetof(struct wc_grb_cpu_work, dw) == 0); + wc_static_assert(offsetof(struct delayed_work, work) == 0); + + ret = wc_grb_maintain_cpu(cw->cpu); + + if (ret != 0) { + pr_err("WCGRB: wc_grb_maintain_cpu(%d) failed: %d\n", cw->cpu, ret); + } + + if (READ_ONCE(wc_grb_maint_running)) { + /* queue_delayed_work_on() WARNs on an offline CPU (workqueue.c + * WARN_ON_ONCE !cpu_online). Tick unbound while it is down; the + * leaf's guard defers the reseed, and this re-pins when it returns. */ + if (cpu_online(cw->cpu)) { + queue_delayed_work_on(cw->cpu, system_wq, &cw->dw, + msecs_to_jiffies(WC_GRB_MAINT_POLL_MS)); + } + else { + queue_delayed_work(system_wq, &cw->dw, + msecs_to_jiffies(WC_GRB_MAINT_POLL_MS)); + } + } +} + +static void wc_grb_root_work_fn(struct work_struct *work) +{ + int ret; + + (void)work; + ret = wc_grb_root_tick(); + if (ret != 0) { + pr_err("WCGRB: wc_grb_root_tick() failed: %d\n", ret); + } + + if (READ_ONCE(wc_grb_maint_running)) { + schedule_delayed_work(&wc_grb_root_work, + msecs_to_jiffies(WC_GRB_MAINT_POLL_MS)); + } +} + +static void wc_grb_maint_stop(void) +{ + unsigned int cpu; + + WRITE_ONCE(wc_grb_maint_running, 0); + + /* Both objects are established together in wc_grb_maint_start(): the + * kcalloc() runs first and returns early on failure, so a non-NULL + * wc_grb_cpu_works is exactly the condition under which + * INIT_DELAYED_WORK() ran on wc_grb_root_work. Cancelling the root work + * outside this guard reaches an uninitialised delayed_work whenever + * maint_start() never ran -- which happens on the handler-registration + * failure path -- and cancel_delayed_work_sync() then hits + * WARN_ON(!work->func) in __flush_work() (kernel/workqueue.c), printing a + * kernel warning and stack trace on every rmmod. */ + if (wc_grb_cpu_works != NULL) { + cancel_delayed_work_sync(&wc_grb_root_work); + + for (cpu = 0; cpu < nr_cpu_ids; cpu++) { + cancel_delayed_work_sync(&wc_grb_cpu_works[cpu].dw); + } + + kfree(wc_grb_cpu_works); + wc_grb_cpu_works = NULL; + } +} + +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS + +/* Userspace half of the service: /dev/urandom, /dev/random and getrandom(2) + * all land here. A bounce buffer is required because wc_grb_service() fills a + * kernel buffer while copy_to_iter() may fault or sleep. */ +static ssize_t wc_grb_user(struct iov_iter *iter) +{ + u8 buf[256]; + size_t done = 0; + + while (iov_iter_count(iter)) { + size_t want = min_t(size_t, iov_iter_count(iter), sizeof(buf)); + size_t got; + + if (wc_grb_service(buf, want) != 0) + break; + + got = copy_to_iter(buf, want, iter); + done += got; + if (got != want) { + memzero_explicit(buf, sizeof(buf)); + + return done ? (ssize_t)done : -EFAULT; + } + + cond_resched(); + } + + memzero_explicit(buf, sizeof(buf)); + + return done ? (ssize_t)done : -ENODEV; +} + +/* The same userspace half for a kernel whose drivers/char/random.c predates the + * 5.17 rewrite. There the read path is extract_crng_user(), which takes a raw + * __user pointer rather than an iov_iter; everything behind it is identical, + * including the bounce buffer, which is required because wc_grb_service() fills + * a kernel buffer while the copy out may fault. + * + * Both members are filled in unconditionally. Which one a kernel calls was + * decided when that kernel was built, and both front the same DRBG tree, so + * this is a second entry point, not a run-time choice between implementations. + * + * Deliberately NOT a LINUX_VERSION_CODE test. The 5.17 rewrite was backported + * into some LTS trees and never into the EOL ones, so the two sets interleave: + * 5.10.265 and 5.15.216 want get_random_bytes_user(), while 5.11.22, 5.12.19, + * 5.13.19 and 5.16.20 want extract_crng_user() -- 5.15 passes where 5.16 + * fails. Every kernel patch under linuxkm/patches/ declares both members of + * struct wolfssl_linuxkm_random_bytes_handlers, so supplying both is what makes + * the handler set track what a tree actually has instead of what its version + * number suggests. + * + * Omitting this member was not a scoping decision with a safe failure mode. + * The pre-rewrite register function requires it and rejects the whole + * registration with -EINVAL, after which the module serves nothing and the + * kernel's own CRNG answers every request -- unvalidated output, delivered + * silently. */ +static ssize_t wc_grb_extract_crng_user(void __user *ubuf, size_t nbytes) +{ + u8 buf[256]; + size_t done = 0; + + while (done < nbytes) { + size_t want = min_t(size_t, nbytes - done, sizeof(buf)); + + if (wc_grb_service(buf, want) != 0) + break; + + if (copy_to_user((u8 __user *)ubuf + done, buf, want) != 0) { + memzero_explicit(buf, sizeof(buf)); + + return done ? (ssize_t)done : -EFAULT; + } + + done += want; + + cond_resched(); + } + + memzero_explicit(buf, sizeof(buf)); + + return done ? (ssize_t)done : -ENODEV; +} + +/* Truthful readiness: the tree is usable only once wc_grb_init() has built the + * root and the leaves. Reporting ready early would let the kernel batch from + * a service that is not yet answering. */ +static bool wc_grb_ready(void) +{ + return wc_grb_service_active() ? true : false; +} + +static const struct wolfssl_linuxkm_random_bytes_handlers +wc_grb_handlers = { + ._get_random_bytes = wc_grb_service, + .get_random_bytes_user = wc_grb_user, + .extract_crng_user = wc_grb_extract_crng_user, + .crng_ready = wc_grb_ready + /* .mix_pool_bytes, .credit_init_bits and .crng_reseed are not implemented + * yet: they let the module take part in the kernel's entropy lifecycle and + * are the next step, not a prerequisite for serving. */ +}; + +#endif /* WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS */ + +/* Diagnostics for the out-of-tree load generator, exported from the glue and + * not from the boundary: the boundary has no module API, and a test affordance + * is not a cryptographic service. Prototyped here rather than in a header + * because nothing in the tree includes them. */ +int wc_grb_hook_is_active(void); +int wc_grb_hook_stats(long long *out, int n); +int wc_grb_hook_irq_hist(int ctx, long long *out, int n); + +int wc_grb_hook_is_active(void) +{ + return wc_grb_service_active(); +} +EXPORT_SYMBOL_GPL(wc_grb_hook_is_active); + +int wc_grb_hook_stats(long long *out, int n) +{ + return wc_grb_stat_snapshot(out, n); +} +EXPORT_SYMBOL_GPL(wc_grb_hook_stats); + +int wc_grb_hook_irq_hist(int ctx, long long *out, int n) +{ + return wc_grb_irq_hist(ctx, out, n); +} +EXPORT_SYMBOL_GPL(wc_grb_hook_irq_hist); + +static int wc_grb_maint_start(void) +{ + unsigned int cpu; + + wc_grb_cpu_works = kcalloc(nr_cpu_ids, sizeof(*wc_grb_cpu_works), + GFP_KERNEL); + if (wc_grb_cpu_works == NULL) { + return -ENOMEM; + } + + WRITE_ONCE(wc_grb_maint_running, 1); + + INIT_DELAYED_WORK(&wc_grb_root_work, wc_grb_root_work_fn); + schedule_delayed_work(&wc_grb_root_work, + msecs_to_jiffies(WC_GRB_MAINT_POLL_MS)); + + /* Runtime INIT_DELAYED_WORK, not the static initializer: that expands + * TIMER_ENTRY_STATIC, whose void* arithmetic trips pointer-arith. */ + for (cpu = 0; cpu < nr_cpu_ids; cpu++) { + wc_grb_cpu_works[cpu].cpu = (int) cpu; + INIT_DELAYED_WORK(&wc_grb_cpu_works[cpu].dw, wc_grb_cpu_work_fn); + } + + /* Only online CPUs get a worker. A CPU brought up later is covered by + * the service path's self-help, which reseeds the leaf it is running on. + * Held across the walk so a CPU cannot go down between the iteration and + * the queue, which would WARN exactly as an offline requeue does. */ + cpus_read_lock(); + for_each_online_cpu(cpu) { + queue_delayed_work_on((int) cpu, system_wq, &wc_grb_cpu_works[cpu].dw, + msecs_to_jiffies(WC_GRB_MAINT_POLL_MS)); + } + cpus_read_unlock(); + + return 0; +} + +#endif /* LINUXKM_RBGC */ + #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) static int __init wolfssl_init(void) #else @@ -1125,6 +1458,45 @@ static int wolfssl_init(void) ); #endif +#ifdef LINUXKM_RBGC + /* Hand get_random_bytes() to the in-boundary service. A failure here is + * reported but does not fail the module load: the kernel's own CRNG keeps + * answering, so the rest of the module is still usable. */ + { + int grb_ret = wc_grb_init(num_possible_cpus()); + + if (grb_ret != 0) { + pr_err("WCGRB: wc_grb_init() failed: %d\n", grb_ret); + } + else { +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS + grb_ret = wolfssl_linuxkm_register_random_bytes_handlers( + THIS_MODULE, &wc_grb_handlers); +#else +#error LINUXKM_RBGC requires a kernel carrying one of the patches in\ + linuxkm/patches/. Without it there is no get_random_bytes() hook to\ + register, and the module would leave the native kernel generator in\ + service with no indication that it had. +#endif + if (grb_ret != 0) { + pr_err("WCGRB: wolfssl_linuxkm_register_random_bytes_handlers()" + " failed: %d\n", grb_ret); + wc_grb_cleanup(); + } + else { + wc_grb_set_registered(1); + if (wc_grb_maint_start() != 0) { + pr_err("WCGRB: no memory for per-CPU maintenance work\n"); + } + pr_info("WCGRB: get_random_bytes() hook registered\n"); + /* Module init has finished, so all further demand is the + * running system rather than bring-up. */ + wc_grb_mark_boot_done(); + } + } + } +#endif /* LINUXKM_RBGC */ + return 0; } @@ -1136,6 +1508,28 @@ static void __exit wolfssl_exit(void) static void wolfssl_exit(void) #endif { +#ifdef LINUXKM_RBGC + /* Unregister the hook before anything else is torn down, so no in-flight + * caller can reach a half-freed DRBG. + * wolfssl_linuxkm_unregister_random_bytes_handlers() drains by + * cmpxchg'ing random_bytes_cb_refcnt from 1 to 0, retrying up to 100 times + * with msleep_interruptible(10) while callers are still inside; it does + * not use RCU. It can return -EBUSY, which this call discards -- see + * drivers/char/random.c in the patch for this kernel. */ +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS + /* Guarded like the register call in wolfssl_init(). Without this the + * unpatched-kernel build emits an implicit-declaration error here as well + * as the #error there, and the second one buries the message that tells + * the builder what to do. */ + if (wc_grb_service_active()) { + (void)wolfssl_linuxkm_unregister_random_bytes_handlers(); + wc_grb_set_registered(0); + } +#endif + wc_grb_maint_stop(); + wc_grb_cleanup(); +#endif /* LINUXKM_RBGC */ + #ifdef HAVE_FIPS int ret; @@ -1736,6 +2130,12 @@ static int set_up_wolfssl_linuxkm_pie_redirect_table(void) { wolfssl_linuxkm_pie_redirect_table.wc_linuxkm_sig_ignore_end = wc_linuxkm_sig_ignore_end; wolfssl_linuxkm_pie_redirect_table.wc_linuxkm_check_for_intr_signals = wc_linuxkm_check_for_intr_signals; wolfssl_linuxkm_pie_redirect_table.wc_linuxkm_relax_long_loop = wc_linuxkm_relax_long_loop; +#ifdef LINUXKM_RBGC + wolfssl_linuxkm_pie_redirect_table.wc_linuxkm_irq_save = wc_linuxkm_irq_save; + wolfssl_linuxkm_pie_redirect_table.wc_linuxkm_irq_restore = wc_linuxkm_irq_restore; + wolfssl_linuxkm_pie_redirect_table.wc_linuxkm_cpu_id = wc_linuxkm_cpu_id; + wolfssl_linuxkm_pie_redirect_table.wc_linuxkm_mono_ns = wc_linuxkm_mono_ns; +#endif #ifdef CONFIG_KASAN wolfssl_linuxkm_pie_redirect_table.kasan_disable_current = kasan_disable_current; diff --git a/linuxkm/patches/5.10.17/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v17.patch b/linuxkm/patches/5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch similarity index 60% rename from linuxkm/patches/5.10.17/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v17.patch rename to linuxkm/patches/5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch index 2679e19ea23..959548cd158 100644 --- a/linuxkm/patches/5.10.17/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v17.patch +++ b/linuxkm/patches/5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch @@ -1,6 +1,6 @@ --- 5.10.17/drivers/char/random.c.dist 2020-12-13 16:41:30.000000000 -0600 +++ 5.10.17/drivers/char/random.c 2025-07-02 11:59:07.220250957 -0500 -@@ -344,6 +344,260 @@ +@@ -344,6 +344,303 @@ #include #include @@ -145,6 +145,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -261,7 +304,7 @@ #define CREATE_TRACE_POINTS #include -@@ -461,7 +715,22 @@ static struct crng_state primary_crng = +@@ -461,7 +758,22 @@ static struct crng_state primary_crng = * its value (from 0->1->2). */ static int crng_init = 0; @@ -284,7 +327,7 @@ static int crng_init_cnt = 0; static unsigned long crng_global_init_time = 0; #define CRNG_INIT_CNT_THRESH (2*CHACHA_KEY_SIZE) -@@ -593,6 +862,11 @@ static void mix_pool_bytes(struct entrop +@@ -593,6 +905,11 @@ static void mix_pool_bytes(struct entrop { unsigned long flags; @@ -296,7 +339,7 @@ trace_mix_pool_bytes(r->name, nbytes, _RET_IP_); spin_lock_irqsave(&r->lock, flags); _mix_pool_bytes(r, in, nbytes); -@@ -664,6 +938,10 @@ static void credit_entropy_bits(struct e +@@ -664,6 +981,10 @@ static void credit_entropy_bits(struct e const int pool_size = r->poolinfo->poolfracbits; int nfrac = nbits << ENTROPY_SHIFT; @@ -307,7 +350,7 @@ if (!nbits) return; -@@ -1069,6 +1347,18 @@ static ssize_t extract_crng_user(void __ +@@ -1069,6 +1390,18 @@ static ssize_t extract_crng_user(void __ __u8 tmp[CHACHA_BLOCK_SIZE] __aligned(4); int large_request = (nbytes > 256); @@ -326,7 +369,7 @@ while (nbytes) { if (large_request && need_resched()) { if (signal_pending(current)) { -@@ -1523,7 +1813,7 @@ static void _warn_unseeded_randomness(co +@@ -1523,7 +1856,7 @@ static void _warn_unseeded_randomness(co #endif if (print_once || @@ -335,22 +378,116 @@ (previous && (caller == READ_ONCE(*previous)))) return; WRITE_ONCE(*previous, caller); -@@ -1552,6 +1842,14 @@ static void _get_random_bytes(void *buf, +@@ -1552,6 +1885,108 @@ static void _get_random_bytes(void *buf, trace_get_random_bytes(nbytes, _RET_IP_); +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow falls through to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, nbytes) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, nbytes); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + while (nbytes >= CHACHA_BLOCK_SIZE) { extract_crng(buf); buf += CHACHA_BLOCK_SIZE; -@@ -1638,12 +1936,12 @@ static void try_to_generate_entropy(void +@@ -1638,12 +2073,12 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -365,7 +502,7 @@ if (ret) return ret > 0 ? 0 : ret; -@@ -1665,7 +1963,7 @@ EXPORT_SYMBOL(wait_for_random_bytes); +@@ -1665,7 +2100,7 @@ EXPORT_SYMBOL(wait_for_random_bytes); */ bool rng_is_initialized(void) { @@ -374,7 +511,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -1843,7 +2141,7 @@ urandom_read(struct file *file, char __u +@@ -1843,7 +2278,7 @@ urandom_read(struct file *file, char __u unsigned long flags; static int maxwarn = 10; @@ -383,7 +520,7 @@ maxwarn--; if (__ratelimit(&urandom_warning)) pr_notice("%s: uninitialized urandom read (%zd bytes read)\n", -@@ -1872,6 +2170,11 @@ random_poll(struct file *file, poll_tabl +@@ -1872,6 +2307,11 @@ random_poll(struct file *file, poll_tabl { __poll_t mask; @@ -395,7 +532,7 @@ poll_wait(file, &crng_init_wait, wait); poll_wait(file, &random_write_wait, wait); mask = 0; -@@ -1970,6 +2273,16 @@ static long random_ioctl(struct file *f, +@@ -1970,6 +2410,16 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -412,7 +549,7 @@ if (crng_init < 2) return -ENODATA; crng_reseed(&primary_crng, NULL); -@@ -2022,7 +2335,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -2022,7 +2472,7 @@ SYSCALL_DEFINE3(getrandom, char __user * if (count > INT_MAX) count = INT_MAX; @@ -423,7 +560,7 @@ ret = wait_for_random_bytes(); --- 5.10.17/include/linux/random.h.dist 2020-12-13 16:41:30.000000000 -0600 +++ 5.10.17/include/linux/random.h 2025-06-30 12:05:59.106440700 -0500 -@@ -158,4 +158,37 @@ static inline bool __init arch_get_rando +@@ -158,4 +158,42 @@ static inline bool __init arch_get_rando } #endif @@ -459,5 +596,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.10.236/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v236.patch b/linuxkm/patches/5.10.236/WOLFSSL_KERNELv5_10_236_FIPS.patch similarity index 61% rename from linuxkm/patches/5.10.236/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v236.patch rename to linuxkm/patches/5.10.236/WOLFSSL_KERNELv5_10_236_FIPS.patch index c956c5b4f66..62ddcccb436 100644 --- a/linuxkm/patches/5.10.236/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v10v236.patch +++ b/linuxkm/patches/5.10.236/WOLFSSL_KERNELv5_10_236_FIPS.patch @@ -1,6 +1,6 @@ --- 5.10.236/drivers/char/random.c.dist 2025-04-29 18:54:03.390121890 -0500 +++ 5.10.236/drivers/char/random.c 2025-07-02 11:57:40.176497765 -0500 -@@ -60,6 +60,260 @@ +@@ -60,6 +60,303 @@ #include #include @@ -145,6 +145,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -261,7 +304,7 @@ /********************************************************************* * * Initialization and readiness waiting. -@@ -79,7 +333,15 @@ static enum { +@@ -79,7 +376,15 @@ static enum { CRNG_EARLY = 1, /* At least POOL_EARLY_BITS collected */ CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ } crng_init __read_mostly = CRNG_EMPTY; @@ -277,7 +320,7 @@ /* Various types of waiters for crng_init->CRNG_READY transition. */ static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); static struct fasync_struct *fasync; -@@ -105,7 +367,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis +@@ -105,7 +410,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis */ bool rng_is_initialized(void) { @@ -286,7 +329,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -124,11 +386,11 @@ static void try_to_generate_entropy(void +@@ -124,11 +429,11 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -300,7 +343,7 @@ if (ret) return ret > 0 ? 0 : ret; } -@@ -182,7 +444,7 @@ static void __cold process_random_ready_ +@@ -182,7 +487,7 @@ static void __cold process_random_ready_ } #define warn_unseeded_randomness() \ @@ -309,22 +352,116 @@ printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ __func__, (void *)_RET_IP_, crng_init) -@@ -401,6 +663,14 @@ static void _get_random_bytes(void *buf, +@@ -401,6 +706,108 @@ static void _get_random_bytes(void *buf, if (!len) return; +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow falls through to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, len) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + first_block_len = min_t(size_t, 32, len); crng_make_state(chacha_state, buf, first_block_len); len -= first_block_len; -@@ -450,6 +720,18 @@ static ssize_t get_random_bytes_user(str +@@ -450,6 +857,18 @@ static ssize_t get_random_bytes_user(str if (unlikely(!iov_iter_count(iter))) return 0; @@ -343,7 +480,7 @@ /* * Immediately overwrite the ChaCha key at index 4 with random * bytes, in case userspace causes copy_to_iter() below to sleep -@@ -526,7 +808,7 @@ type get_random_ ##type(void) \ +@@ -526,7 +945,7 @@ type get_random_ ##type(void) \ \ warn_unseeded_randomness(); \ \ @@ -352,7 +489,7 @@ _get_random_bytes(&ret, sizeof(ret)); \ return ret; \ } \ -@@ -650,6 +932,11 @@ static void mix_pool_bytes(const void *b +@@ -650,6 +1069,11 @@ static void mix_pool_bytes(const void *b { unsigned long flags; @@ -364,7 +501,7 @@ spin_lock_irqsave(&input_pool.lock, flags); _mix_pool_bytes(buf, len); spin_unlock_irqrestore(&input_pool.lock, flags); -@@ -701,7 +988,11 @@ static void extract_entropy(void *buf, s +@@ -701,7 +1125,11 @@ static void extract_entropy(void *buf, s memzero_explicit(&block, sizeof(block)); } @@ -376,7 +513,7 @@ static void __cold _credit_init_bits(size_t bits) { -@@ -1229,7 +1520,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1229,7 +1657,7 @@ SYSCALL_DEFINE3(getrandom, char __user * if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) return -EINVAL; @@ -385,7 +522,7 @@ if (flags & GRND_NONBLOCK) return -EAGAIN; ret = wait_for_random_bytes(); -@@ -1245,6 +1536,10 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1245,6 +1673,10 @@ SYSCALL_DEFINE3(getrandom, char __user * static __poll_t random_poll(struct file *file, poll_table *wait) { @@ -396,7 +533,7 @@ poll_wait(file, &crng_init_wait, wait); return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; } -@@ -1286,7 +1581,7 @@ static ssize_t urandom_read_iter(struct +@@ -1286,7 +1718,7 @@ static ssize_t urandom_read_iter(struct { static int maxwarn = 10; @@ -405,7 +542,7 @@ if (!ratelimit_disable && maxwarn <= 0) ++urandom_warning.missed; else if (ratelimit_disable || __ratelimit(&urandom_warning)) { -@@ -1369,6 +1664,14 @@ static long random_ioctl(struct file *f, +@@ -1369,6 +1801,14 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -422,7 +559,7 @@ crng_reseed(); --- 5.10.236/include/linux/random.h.dist 2025-04-29 18:54:07.595202807 -0500 +++ 5.10.236/include/linux/random.h 2025-06-30 12:03:15.263141842 -0500 -@@ -138,4 +138,37 @@ int random_online_cpu(unsigned int cpu); +@@ -138,4 +138,42 @@ int random_online_cpu(unsigned int cpu); extern const struct file_operations random_fops, urandom_fops; #endif @@ -458,5 +595,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.14.0-570.58.1.el9_6/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v14-570v58v1-el9_6.patch b/linuxkm/patches/5.14.0-570.58.1.el9_6/WOLFSSL_KERNELv5_14_el9_6_FIPS.patch similarity index 61% rename from linuxkm/patches/5.14.0-570.58.1.el9_6/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v14-570v58v1-el9_6.patch rename to linuxkm/patches/5.14.0-570.58.1.el9_6/WOLFSSL_KERNELv5_14_el9_6_FIPS.patch index aa1d1b6c68c..d602846880e 100644 --- a/linuxkm/patches/5.14.0-570.58.1.el9_6/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v14-570v58v1-el9_6.patch +++ b/linuxkm/patches/5.14.0-570.58.1.el9_6/WOLFSSL_KERNELv5_14_el9_6_FIPS.patch @@ -1,6 +1,6 @@ --- 5.14.0-570.58.1.el9_6/drivers/char/random.c.dist 2026-01-12 10:50:48.537243229 -0600 +++ 5.14.0-570.58.1.el9_6/drivers/char/random.c 2026-01-12 11:17:45.002091787 -0600 -@@ -63,6 +63,260 @@ +@@ -63,6 +63,303 @@ #include #include @@ -145,6 +145,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -261,7 +304,7 @@ /********************************************************************* * * Initialization and readiness waiting. -@@ -83,7 +337,15 @@ static enum { +@@ -83,7 +380,15 @@ static enum { CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ } crng_init __read_mostly = CRNG_EMPTY; static DEFINE_STATIC_KEY_FALSE(crng_is_ready); @@ -277,7 +320,7 @@ /* Various types of waiters for crng_init->CRNG_READY transition. */ static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); static struct fasync_struct *fasync; -@@ -108,7 +370,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis +@@ -108,7 +413,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis */ bool rng_is_initialized(void) { @@ -286,7 +329,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -132,11 +394,11 @@ static void try_to_generate_entropy(void +@@ -132,11 +437,11 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -300,7 +343,7 @@ if (ret) return ret > 0 ? 0 : ret; } -@@ -165,7 +427,7 @@ int __cold execute_with_initialized_rng( +@@ -165,7 +470,7 @@ int __cold execute_with_initialized_rng( } #define warn_unseeded_randomness() \ @@ -309,22 +352,116 @@ printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ __func__, (void *)_RET_IP_, crng_init) -@@ -388,6 +650,14 @@ static void _get_random_bytes(void *buf, +@@ -388,6 +693,108 @@ static void _get_random_bytes(void *buf, if (!len) return; +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow falls through to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, len) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + first_block_len = min_t(size_t, 32, len); crng_make_state(chacha_state, buf, first_block_len); len -= first_block_len; -@@ -434,6 +704,18 @@ static ssize_t get_random_bytes_user(str +@@ -434,6 +841,18 @@ static ssize_t get_random_bytes_user(str if (unlikely(!iov_iter_count(iter))) return 0; @@ -343,7 +480,7 @@ /* * Immediately overwrite the ChaCha key at index 4 with random * bytes, in case userspace causes copy_to_iter() below to sleep -@@ -510,7 +792,7 @@ type get_random_ ##type(void) \ +@@ -510,7 +929,7 @@ type get_random_ ##type(void) \ \ warn_unseeded_randomness(); \ \ @@ -352,7 +489,7 @@ _get_random_bytes(&ret, sizeof(ret)); \ return ret; \ } \ -@@ -649,6 +931,11 @@ static void mix_pool_bytes(const void *b +@@ -649,6 +1068,11 @@ static void mix_pool_bytes(const void *b { unsigned long flags; @@ -364,7 +501,7 @@ spin_lock_irqsave(&input_pool.lock, flags); _mix_pool_bytes(buf, len); spin_unlock_irqrestore(&input_pool.lock, flags); -@@ -708,7 +995,11 @@ static void extract_entropy(void *buf, s +@@ -708,7 +1132,11 @@ static void extract_entropy(void *buf, s memzero_explicit(&block, sizeof(block)); } @@ -376,7 +513,7 @@ static void __cold _credit_init_bits(size_t bits) { -@@ -1419,7 +1710,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1419,7 +1847,7 @@ SYSCALL_DEFINE3(getrandom, char __user * return ret; } @@ -385,7 +522,7 @@ if (flags & GRND_NONBLOCK) return -EAGAIN; ret = wait_for_random_bytes(); -@@ -1435,6 +1726,10 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1435,6 +1863,10 @@ SYSCALL_DEFINE3(getrandom, char __user * static __poll_t random_poll(struct file *file, poll_table *wait) { @@ -396,7 +533,7 @@ poll_wait(file, &crng_init_wait, wait); return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; } -@@ -1490,7 +1785,7 @@ static ssize_t urandom_read_iter(struct +@@ -1490,7 +1922,7 @@ static ssize_t urandom_read_iter(struct if (!crng_ready()) try_to_generate_entropy(); @@ -405,7 +542,7 @@ if (!ratelimit_disable && maxwarn <= 0) ++urandom_warning.missed; else if (ratelimit_disable || __ratelimit(&urandom_warning)) { -@@ -1573,6 +1868,14 @@ static long random_ioctl(struct file *f, +@@ -1573,6 +2005,14 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -422,7 +559,7 @@ crng_reseed(NULL); --- 5.14.0-570.58.1.el9_6/include/linux/random.h.dist 2026-01-12 10:50:57.004413581 -0600 +++ 5.14.0-570.58.1.el9_6/include/linux/random.h 2026-01-12 10:56:29.124034816 -0600 -@@ -175,4 +175,37 @@ int random_online_cpu(unsigned int cpu); +@@ -175,4 +175,42 @@ int random_online_cpu(unsigned int cpu); extern const struct file_operations random_fops, urandom_fops; #endif @@ -458,5 +595,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.15/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v15.patch b/linuxkm/patches/5.15/WOLFSSL_KERNELv5_15_FIPS.patch similarity index 60% rename from linuxkm/patches/5.15/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v15.patch rename to linuxkm/patches/5.15/WOLFSSL_KERNELv5_15_FIPS.patch index 39da1a9abb3..2e1aa5a158e 100644 --- a/linuxkm/patches/5.15/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v15.patch +++ b/linuxkm/patches/5.15/WOLFSSL_KERNELv5_15_FIPS.patch @@ -1,6 +1,6 @@ --- 5.15/drivers/char/random.c.dist 2021-10-31 15:53:10.000000000 -0500 +++ 5.15/drivers/char/random.c 2025-07-02 11:49:13.836320539 -0500 -@@ -344,6 +344,260 @@ +@@ -344,6 +344,303 @@ #include #include @@ -145,6 +145,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -261,7 +304,7 @@ #define CREATE_TRACE_POINTS #include -@@ -461,7 +715,22 @@ static struct crng_state primary_crng = +@@ -461,7 +758,22 @@ static struct crng_state primary_crng = * its value (from 0->1->2). */ static int crng_init = 0; @@ -284,7 +327,7 @@ static int crng_init_cnt = 0; static unsigned long crng_global_init_time = 0; #define CRNG_INIT_CNT_THRESH (2*CHACHA_KEY_SIZE) -@@ -593,6 +862,12 @@ static void mix_pool_bytes(struct entrop +@@ -593,6 +905,12 @@ static void mix_pool_bytes(struct entrop unsigned long flags; trace_mix_pool_bytes(r->name, nbytes, _RET_IP_); @@ -297,7 +340,7 @@ spin_lock_irqsave(&r->lock, flags); _mix_pool_bytes(r, in, nbytes); spin_unlock_irqrestore(&r->lock, flags); -@@ -1059,6 +1334,18 @@ static ssize_t extract_crng_user(void __ +@@ -1059,6 +1377,18 @@ static ssize_t extract_crng_user(void __ __u8 tmp[CHACHA_BLOCK_SIZE] __aligned(4); int large_request = (nbytes > 256); @@ -316,7 +359,7 @@ while (nbytes) { if (large_request && need_resched()) { if (signal_pending(current)) { -@@ -1499,7 +1786,7 @@ static void _warn_unseeded_randomness(co +@@ -1499,7 +1829,7 @@ static void _warn_unseeded_randomness(co #endif if (print_once || @@ -325,22 +368,116 @@ (previous && (caller == READ_ONCE(*previous)))) return; WRITE_ONCE(*previous, caller); -@@ -1528,6 +1815,14 @@ static void _get_random_bytes(void *buf, +@@ -1528,6 +1858,108 @@ static void _get_random_bytes(void *buf, trace_get_random_bytes(nbytes, _RET_IP_); +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow falls through to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, nbytes) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, nbytes); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + while (nbytes >= CHACHA_BLOCK_SIZE) { extract_crng(buf); buf += CHACHA_BLOCK_SIZE; -@@ -1614,12 +1909,12 @@ static void try_to_generate_entropy(void +@@ -1614,12 +2046,12 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -355,7 +492,7 @@ if (ret) return ret > 0 ? 0 : ret; -@@ -1641,7 +1936,7 @@ EXPORT_SYMBOL(wait_for_random_bytes); +@@ -1641,7 +2073,7 @@ EXPORT_SYMBOL(wait_for_random_bytes); */ bool rng_is_initialized(void) { @@ -364,7 +501,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -1819,7 +2114,7 @@ urandom_read(struct file *file, char __u +@@ -1819,7 +2251,7 @@ urandom_read(struct file *file, char __u unsigned long flags; static int maxwarn = 10; @@ -373,7 +510,7 @@ maxwarn--; if (__ratelimit(&urandom_warning)) pr_notice("%s: uninitialized urandom read (%zd bytes read)\n", -@@ -1848,6 +2143,11 @@ random_poll(struct file *file, poll_tabl +@@ -1848,6 +2280,11 @@ random_poll(struct file *file, poll_tabl { __poll_t mask; @@ -385,7 +522,7 @@ poll_wait(file, &crng_init_wait, wait); poll_wait(file, &random_write_wait, wait); mask = 0; -@@ -1946,6 +2246,16 @@ static long random_ioctl(struct file *f, +@@ -1946,6 +2383,16 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -402,7 +539,7 @@ if (crng_init < 2) return -ENODATA; crng_reseed(&primary_crng, &input_pool); -@@ -1998,7 +2308,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1998,7 +2445,7 @@ SYSCALL_DEFINE3(getrandom, char __user * if (count > INT_MAX) count = INT_MAX; @@ -413,7 +550,7 @@ ret = wait_for_random_bytes(); --- 5.15/include/linux/random.h.dist 2021-10-31 15:53:10.000000000 -0500 +++ 5.15/include/linux/random.h 2025-06-28 13:09:13.392547118 -0500 -@@ -158,4 +158,37 @@ static inline bool __init arch_get_rando +@@ -158,4 +158,42 @@ static inline bool __init arch_get_rando } #endif @@ -449,5 +586,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.16/WOLFSSL_KERNELv5_16_FIPS.patch b/linuxkm/patches/5.16/WOLFSSL_KERNELv5_16_FIPS.patch new file mode 100644 index 00000000000..7b89a29ed52 --- /dev/null +++ b/linuxkm/patches/5.16/WOLFSSL_KERNELv5_16_FIPS.patch @@ -0,0 +1,595 @@ +--- 5.16/drivers/char/random.c.dist 2026-08-21 17:57:30.466211878 -0600 ++++ 5.16/drivers/char/random.c 2026-08-21 17:57:30.472426384 -0600 +@@ -344,6 +344,303 @@ + #include + #include + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ ++#include ++ ++static atomic_long_t random_bytes_cb_owner = ++ ATOMIC_INIT((long)NULL); ++static atomic_t random_bytes_cb_refcnt = ++ ATOMIC_INIT(0); /* 0 if unregistered, 1 if no calls in flight. */ ++static _get_random_bytes_cb_t _get_random_bytes_cb = NULL; ++static extract_crng_user_cb_t extract_crng_user_cb = NULL; ++static crng_ready_cb_t crng_ready_cb = NULL; ++static mix_pool_bytes_cb_t mix_pool_bytes_cb = NULL; ++static credit_init_bits_cb_t credit_init_bits_cb = NULL; ++static crng_reseed_cb_t crng_reseed_cb = NULL; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers) ++{ ++ if ((! new_random_bytes_cb_owner) || ++ (! handlers) || ++ (! handlers->_get_random_bytes) || ++ (! handlers->extract_crng_user)) ++ { ++ return -EINVAL; ++ } ++ ++ /* random_bytes_cb_owner is used to enforce serialization of ++ * wolfssl_register_random_bytes_handlers() and ++ * wolfssl_unregister_random_bytes_handlers(). ++ */ ++ if (atomic_long_cmpxchg(&random_bytes_cb_owner, ++ (long)NULL, ++ (long)new_random_bytes_cb_owner) ++ != (long)NULL) ++ { ++ return -EBUSY; ++ } ++ ++ { ++ int current_random_bytes_cb_refcnt = atomic_read(&random_bytes_cb_refcnt); ++ if (current_random_bytes_cb_refcnt) { ++ pr_err("BUG: random_bytes_cb_refcnt == %d with null random_bytes_cb_owner", current_random_bytes_cb_refcnt); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -EFAULT; ++ } ++ } ++ ++ if (! try_module_get(new_random_bytes_cb_owner)) { ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -ENODEV; ++ } ++ ++ _get_random_bytes_cb = handlers->_get_random_bytes; ++ extract_crng_user_cb = handlers->extract_crng_user; ++ crng_ready_cb = handlers->crng_ready; ++ mix_pool_bytes_cb = handlers->mix_pool_bytes; ++ credit_init_bits_cb = handlers->credit_init_bits; ++ crng_reseed_cb = handlers->crng_reseed; ++ ++ barrier(); ++ atomic_set_release(&random_bytes_cb_refcnt, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_register_random_bytes_handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void) ++{ ++ int current_random_bytes_cb_refcnt; ++ int n_tries; ++ if (! atomic_long_read(&random_bytes_cb_owner)) ++ return -ENODEV; ++ ++ /* we're racing the kernel at large to try to catch random_bytes_cb_refcnt ++ * with no callers in flight -- retry and relax up to 100 times. ++ */ ++ for (n_tries = 0; n_tries < 100; ++n_tries) { ++ current_random_bytes_cb_refcnt = atomic_cmpxchg(&random_bytes_cb_refcnt, 1, 0); ++ if (current_random_bytes_cb_refcnt == 1) ++ break; ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in wolfssl_linuxkm_unregister_random_bytes_handlers.", current_random_bytes_cb_refcnt); ++ break; ++ } ++ if (msleep_interruptible(10) != 0) ++ return -EINTR; ++ } ++ if (current_random_bytes_cb_refcnt != 1) { ++ pr_warn("WARNING: wolfssl_unregister_random_bytes_handlers called with random_bytes_cb_refcnt == %d", current_random_bytes_cb_refcnt); ++ return -EBUSY; ++ } ++ ++ _get_random_bytes_cb = NULL; ++ extract_crng_user_cb = NULL; ++ crng_ready_cb = NULL; ++ mix_pool_bytes_cb = NULL; ++ credit_init_bits_cb = NULL; ++ crng_reseed_cb = NULL; ++ ++ module_put((struct module *)atomic_long_read(&random_bytes_cb_owner)); ++ barrier(); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_unregister_random_bytes_handlers); ++ ++static __always_inline int reserve_random_bytes_cb(void) { ++ int current_random_bytes_cb_refcnt = ++ atomic_read_acquire(&random_bytes_cb_refcnt); ++ ++ if (! current_random_bytes_cb_refcnt) ++ return -ENODEV; ++ ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in reserve_random_bytes_cb.", current_random_bytes_cb_refcnt); ++ return -EFAULT; ++ } ++ ++ for (;;) { ++ int orig_random_bytes_cb_refcnt = ++ atomic_cmpxchg( ++ &random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt + 1); ++ if (orig_random_bytes_cb_refcnt == current_random_bytes_cb_refcnt) ++ return 0; ++ else if (! orig_random_bytes_cb_refcnt) ++ return -ENODEV; ++ else ++ current_random_bytes_cb_refcnt = orig_random_bytes_cb_refcnt; ++ } ++ ++ __builtin_unreachable(); ++} ++ ++static __always_inline void release_random_bytes_cb(void) { ++ atomic_dec(&random_bytes_cb_refcnt); ++} ++ ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ ++static inline int call__get_random_bytes_cb(void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! _get_random_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = _get_random_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline ssize_t call_extract_crng_user_cb(void __user *buf, size_t nbytes) ++{ ++ ssize_t ret; ++ ++ if (! extract_crng_user_cb) ++ return -ECANCELED; ++ ++ ret = (ssize_t)reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = extract_crng_user_cb(buf, nbytes); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline bool call_crng_ready_cb(void) ++{ ++ bool ret; ++ ++ /* Null crng_ready_cb signifies that the DRBG is always ready, i.e. that if ++ * called, it will always have or obtain sufficient entropy to fulfill the ++ * call. ++ */ ++ if (! crng_ready_cb) ++ return 1; ++ ++ if (reserve_random_bytes_cb() != 0) ++ return 0; ++ ++ ret = crng_ready_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_mix_pool_bytes_cb(const void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! mix_pool_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = mix_pool_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_credit_init_bits_cb(size_t bits) ++{ ++ int ret; ++ ++ if (! credit_init_bits_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = credit_init_bits_cb(bits); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_crng_reseed_cb(void) ++{ ++ int ret; ++ ++ if (! crng_reseed_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = crng_reseed_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++#endif /* WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS */ ++ + #define CREATE_TRACE_POINTS + #include + +@@ -462,7 +759,22 @@ static struct crng_state primary_crng = + */ + static int crng_init = 0; + static bool crng_need_final_init = false; ++ + #define crng_ready() (likely(crng_init > 1)) ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ + static int crng_init_cnt = 0; + static unsigned long crng_global_init_time = 0; + #define CRNG_INIT_CNT_THRESH (2*CHACHA_KEY_SIZE) +@@ -594,6 +906,12 @@ static void mix_pool_bytes(struct entrop + unsigned long flags; + + trace_mix_pool_bytes(r->name, nbytes, _RET_IP_); ++ ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ (void)call_mix_pool_bytes_cb(in, nbytes); ++ /* fall through to mix into native pool too. */ ++#endif ++ + spin_lock_irqsave(&r->lock, flags); + _mix_pool_bytes(r, in, nbytes); + spin_unlock_irqrestore(&r->lock, flags); +@@ -1079,6 +1397,18 @@ static ssize_t extract_crng_user(void __ + __u8 tmp[CHACHA_BLOCK_SIZE] __aligned(4); + int large_request = (nbytes > 256); + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ { ++ ssize_t cb_ret = call_extract_crng_user_cb(buf, nbytes); ++ /* If the callback returns -ECANCELED, that signals that iter is ++ * still intact, and flow can safely fall through to the native ++ * implementation. ++ */ ++ if (cb_ret != -ECANCELED) ++ return cb_ret; ++ } ++#endif ++ + while (nbytes) { + if (large_request && need_resched()) { + if (signal_pending(current)) { +@@ -1519,7 +1849,7 @@ static void _warn_unseeded_randomness(co + #endif + + if (print_once || +- crng_ready() || ++ crng_ready_maybe_cb() || + (previous && (caller == READ_ONCE(*previous)))) + return; + WRITE_ONCE(*previous, caller); +@@ -1548,6 +1878,108 @@ static void _get_random_bytes(void *buf, + + trace_get_random_bytes(nbytes, _RET_IP_); + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. ++ */ ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, nbytes); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } ++#endif ++ + while (nbytes >= CHACHA_BLOCK_SIZE) { + extract_crng(buf); + buf += CHACHA_BLOCK_SIZE; +@@ -1634,12 +2066,12 @@ static void try_to_generate_entropy(void + */ + int wait_for_random_bytes(void) + { +- if (likely(crng_ready())) ++ if (likely(crng_ready_maybe_cb())) + return 0; + + do { + int ret; +- ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready(), HZ); ++ ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready_maybe_cb(), HZ); + if (ret) + return ret > 0 ? 0 : ret; + +@@ -1661,7 +2093,7 @@ EXPORT_SYMBOL(wait_for_random_bytes); + */ + bool rng_is_initialized(void) + { +- return crng_ready(); ++ return crng_ready_maybe_cb(); + } + EXPORT_SYMBOL(rng_is_initialized); + +@@ -1841,7 +2273,7 @@ urandom_read(struct file *file, char __u + unsigned long flags; + static int maxwarn = 10; + +- if (!crng_ready() && maxwarn > 0) { ++ if (!crng_ready_maybe_cb() && maxwarn > 0) { + maxwarn--; + if (__ratelimit(&urandom_warning)) + pr_notice("%s: uninitialized urandom read (%zd bytes read)\n", +@@ -1870,6 +2302,11 @@ random_poll(struct file *file, poll_tabl + { + __poll_t mask; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ if (crng_ready_by_cb()) ++ return EPOLLIN | EPOLLRDNORM; ++#endif ++ + poll_wait(file, &crng_init_wait, wait); + poll_wait(file, &random_write_wait, wait); + mask = 0; +@@ -1971,6 +2408,16 @@ static long random_ioctl(struct file *f, + case RNDRESEEDCRNG: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* fall through to reseed native crng too. */ ++ if (call_crng_reseed_cb() == 0) { ++ if (crng_init >= 2) { ++ crng_reseed(&primary_crng, &input_pool); ++ crng_global_init_time = jiffies - 1; ++ } ++ return 0; ++ } ++#endif + if (crng_init < 2) + return -ENODATA; + crng_reseed(&primary_crng, &input_pool); +@@ -2023,7 +2470,7 @@ SYSCALL_DEFINE3(getrandom, char __user * + if (count > INT_MAX) + count = INT_MAX; + +- if (!(flags & GRND_INSECURE) && !crng_ready()) { ++ if (!(flags & GRND_INSECURE) && !crng_ready_maybe_cb()) { + if (flags & GRND_NONBLOCK) + return -EAGAIN; + ret = wait_for_random_bytes(); +--- 5.16/include/linux/random.h.dist 2026-08-21 17:57:30.466703452 -0600 ++++ 5.16/include/linux/random.h 2026-08-21 17:57:30.473099155 -0600 +@@ -158,4 +158,42 @@ static inline bool __init arch_get_rando + } + #endif + ++#ifndef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS 1 ++#endif ++ ++typedef int (*_get_random_bytes_cb_t)(void *buf, size_t len); ++struct iov_iter; ++/* kernels >= 5.17.0 use get_random_bytes_user() */ ++typedef ssize_t (*get_random_bytes_user_cb_t)(struct iov_iter *iter); ++/* kernels < 5.17.0 use extract_crng_user(), though some LTS kernels, ++ * e.g. 5.10.236, have the 5.17+ architecture backported. ++ */ ++typedef ssize_t (*extract_crng_user_cb_t)(void __user *buf, size_t nbytes); ++typedef bool (*crng_ready_cb_t)(void); ++typedef int (*mix_pool_bytes_cb_t)(const void *buf, size_t len); ++typedef int (*credit_init_bits_cb_t)(size_t bits); ++typedef int (*crng_reseed_cb_t)(void); ++ ++struct wolfssl_linuxkm_random_bytes_handlers { ++ _get_random_bytes_cb_t _get_random_bytes; ++ get_random_bytes_user_cb_t get_random_bytes_user; ++ extract_crng_user_cb_t extract_crng_user; ++ crng_ready_cb_t crng_ready; ++ mix_pool_bytes_cb_t mix_pool_bytes; ++ credit_init_bits_cb_t credit_init_bits; ++ crng_reseed_cb_t crng_reseed; ++}; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); ++ + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.17-ubuntu-jammy-tegra/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17-ubuntu-jammy-tegra.patch b/linuxkm/patches/5.17-ubuntu-jammy-tegra/WOLFSSL_KERNELv5_17_tegra_FIPS.patch similarity index 61% rename from linuxkm/patches/5.17-ubuntu-jammy-tegra/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17-ubuntu-jammy-tegra.patch rename to linuxkm/patches/5.17-ubuntu-jammy-tegra/WOLFSSL_KERNELv5_17_tegra_FIPS.patch index eea4a90e833..101b5bf5c3a 100644 --- a/linuxkm/patches/5.17-ubuntu-jammy-tegra/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17-ubuntu-jammy-tegra.patch +++ b/linuxkm/patches/5.17-ubuntu-jammy-tegra/WOLFSSL_KERNELv5_17_tegra_FIPS.patch @@ -1,6 +1,6 @@ --- 5.17-ubuntu-jammy-tegra/drivers/char/random.c.dist 2025-12-10 09:55:51.740854778 -0600 +++ 5.17-ubuntu-jammy-tegra/drivers/char/random.c 2025-12-10 10:19:00.414922381 -0600 -@@ -60,6 +60,260 @@ +@@ -60,6 +60,303 @@ #include #include @@ -145,6 +145,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -261,7 +304,7 @@ /********************************************************************* * * Initialization and readiness waiting. -@@ -79,7 +333,15 @@ static enum { +@@ -79,7 +376,15 @@ static enum { CRNG_EARLY = 1, /* At least POOL_EARLY_BITS collected */ CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ } crng_init __read_mostly = CRNG_EMPTY; @@ -277,7 +320,7 @@ /* Various types of waiters for crng_init->CRNG_READY transition. */ static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); static struct fasync_struct *fasync; -@@ -105,7 +367,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis +@@ -105,7 +410,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis */ bool rng_is_initialized(void) { @@ -286,7 +329,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -124,11 +386,11 @@ static void try_to_generate_entropy(void +@@ -124,11 +429,11 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -300,7 +343,7 @@ if (ret) return ret > 0 ? 0 : ret; } -@@ -182,7 +444,7 @@ static void __cold process_random_ready_ +@@ -182,7 +487,7 @@ static void __cold process_random_ready_ } #define warn_unseeded_randomness() \ @@ -309,22 +352,116 @@ printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ __func__, (void *)_RET_IP_, crng_init) -@@ -401,6 +663,14 @@ static void _get_random_bytes(void *buf, +@@ -401,6 +706,108 @@ static void _get_random_bytes(void *buf, if (!len) return; +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow falls through to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, len) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + first_block_len = min_t(size_t, 32, len); crng_make_state(chacha_state, buf, first_block_len); len -= first_block_len; -@@ -450,6 +720,18 @@ static ssize_t get_random_bytes_user(str +@@ -450,6 +857,18 @@ static ssize_t get_random_bytes_user(str if (unlikely(!iov_iter_count(iter))) return 0; @@ -343,7 +480,7 @@ /* * Immediately overwrite the ChaCha key at index 4 with random * bytes, in case userspace causes copy_to_iter() below to sleep -@@ -526,7 +808,7 @@ type get_random_ ##type(void) \ +@@ -526,7 +945,7 @@ type get_random_ ##type(void) \ \ warn_unseeded_randomness(); \ \ @@ -352,7 +489,7 @@ _get_random_bytes(&ret, sizeof(ret)); \ return ret; \ } \ -@@ -650,6 +932,11 @@ static void mix_pool_bytes(const void *b +@@ -650,6 +1069,11 @@ static void mix_pool_bytes(const void *b { unsigned long flags; @@ -364,7 +501,7 @@ spin_lock_irqsave(&input_pool.lock, flags); _mix_pool_bytes(buf, len); spin_unlock_irqrestore(&input_pool.lock, flags); -@@ -701,7 +988,11 @@ static void extract_entropy(void *buf, s +@@ -701,7 +1125,11 @@ static void extract_entropy(void *buf, s memzero_explicit(&block, sizeof(block)); } @@ -376,7 +513,7 @@ static void __cold _credit_init_bits(size_t bits) { -@@ -1228,7 +1519,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1228,7 +1656,7 @@ SYSCALL_DEFINE3(getrandom, char __user * if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) return -EINVAL; @@ -385,7 +522,7 @@ if (flags & GRND_NONBLOCK) return -EAGAIN; ret = wait_for_random_bytes(); -@@ -1244,6 +1535,10 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1244,6 +1672,10 @@ SYSCALL_DEFINE3(getrandom, char __user * static __poll_t random_poll(struct file *file, poll_table *wait) { @@ -396,7 +533,7 @@ poll_wait(file, &crng_init_wait, wait); return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; } -@@ -1285,7 +1580,7 @@ static ssize_t urandom_read_iter(struct +@@ -1285,7 +1717,7 @@ static ssize_t urandom_read_iter(struct { static int maxwarn = 10; @@ -405,7 +542,7 @@ if (!ratelimit_disable && maxwarn <= 0) ++urandom_warning.missed; else if (ratelimit_disable || __ratelimit(&urandom_warning)) { -@@ -1368,6 +1663,14 @@ static long random_ioctl(struct file *f, +@@ -1368,6 +1800,14 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -422,7 +559,7 @@ crng_reseed(); --- 5.17-ubuntu-jammy-tegra/include/linux/random.h.dist 2025-12-10 10:11:26.642681781 -0600 +++ 5.17-ubuntu-jammy-tegra/include/linux/random.h 2025-12-10 10:14:44.417609545 -0600 -@@ -138,4 +138,37 @@ int random_online_cpu(unsigned int cpu); +@@ -138,4 +138,42 @@ int random_online_cpu(unsigned int cpu); extern const struct file_operations random_fops, urandom_fops; #endif @@ -458,5 +595,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch b/linuxkm/patches/5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch new file mode 100644 index 00000000000..47d0d94c136 --- /dev/null +++ b/linuxkm/patches/5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch @@ -0,0 +1,595 @@ +--- 5.17.0/drivers/char/random.c.dist 2026-08-21 17:59:39.405691675 -0600 ++++ 5.17.0/drivers/char/random.c 2026-08-21 18:00:49.638754144 -0600 +@@ -353,6 +353,303 @@ + #include + #include + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ ++#include ++ ++static atomic_long_t random_bytes_cb_owner = ++ ATOMIC_INIT((long)NULL); ++static atomic_t random_bytes_cb_refcnt = ++ ATOMIC_INIT(0); /* 0 if unregistered, 1 if no calls in flight. */ ++static _get_random_bytes_cb_t _get_random_bytes_cb = NULL; ++static extract_crng_user_cb_t extract_crng_user_cb = NULL; ++static crng_ready_cb_t crng_ready_cb = NULL; ++static mix_pool_bytes_cb_t mix_pool_bytes_cb = NULL; ++static credit_init_bits_cb_t credit_init_bits_cb = NULL; ++static crng_reseed_cb_t crng_reseed_cb = NULL; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers) ++{ ++ if ((! new_random_bytes_cb_owner) || ++ (! handlers) || ++ (! handlers->_get_random_bytes) || ++ (! handlers->extract_crng_user)) ++ { ++ return -EINVAL; ++ } ++ ++ /* random_bytes_cb_owner is used to enforce serialization of ++ * wolfssl_register_random_bytes_handlers() and ++ * wolfssl_unregister_random_bytes_handlers(). ++ */ ++ if (atomic_long_cmpxchg(&random_bytes_cb_owner, ++ (long)NULL, ++ (long)new_random_bytes_cb_owner) ++ != (long)NULL) ++ { ++ return -EBUSY; ++ } ++ ++ { ++ int current_random_bytes_cb_refcnt = atomic_read(&random_bytes_cb_refcnt); ++ if (current_random_bytes_cb_refcnt) { ++ pr_err("BUG: random_bytes_cb_refcnt == %d with null random_bytes_cb_owner", current_random_bytes_cb_refcnt); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -EFAULT; ++ } ++ } ++ ++ if (! try_module_get(new_random_bytes_cb_owner)) { ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -ENODEV; ++ } ++ ++ _get_random_bytes_cb = handlers->_get_random_bytes; ++ extract_crng_user_cb = handlers->extract_crng_user; ++ crng_ready_cb = handlers->crng_ready; ++ mix_pool_bytes_cb = handlers->mix_pool_bytes; ++ credit_init_bits_cb = handlers->credit_init_bits; ++ crng_reseed_cb = handlers->crng_reseed; ++ ++ barrier(); ++ atomic_set_release(&random_bytes_cb_refcnt, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_register_random_bytes_handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void) ++{ ++ int current_random_bytes_cb_refcnt; ++ int n_tries; ++ if (! atomic_long_read(&random_bytes_cb_owner)) ++ return -ENODEV; ++ ++ /* we're racing the kernel at large to try to catch random_bytes_cb_refcnt ++ * with no callers in flight -- retry and relax up to 100 times. ++ */ ++ for (n_tries = 0; n_tries < 100; ++n_tries) { ++ current_random_bytes_cb_refcnt = atomic_cmpxchg(&random_bytes_cb_refcnt, 1, 0); ++ if (current_random_bytes_cb_refcnt == 1) ++ break; ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in wolfssl_linuxkm_unregister_random_bytes_handlers.", current_random_bytes_cb_refcnt); ++ break; ++ } ++ if (msleep_interruptible(10) != 0) ++ return -EINTR; ++ } ++ if (current_random_bytes_cb_refcnt != 1) { ++ pr_warn("WARNING: wolfssl_unregister_random_bytes_handlers called with random_bytes_cb_refcnt == %d", current_random_bytes_cb_refcnt); ++ return -EBUSY; ++ } ++ ++ _get_random_bytes_cb = NULL; ++ extract_crng_user_cb = NULL; ++ crng_ready_cb = NULL; ++ mix_pool_bytes_cb = NULL; ++ credit_init_bits_cb = NULL; ++ crng_reseed_cb = NULL; ++ ++ module_put((struct module *)atomic_long_read(&random_bytes_cb_owner)); ++ barrier(); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_unregister_random_bytes_handlers); ++ ++static __always_inline int reserve_random_bytes_cb(void) { ++ int current_random_bytes_cb_refcnt = ++ atomic_read_acquire(&random_bytes_cb_refcnt); ++ ++ if (! current_random_bytes_cb_refcnt) ++ return -ENODEV; ++ ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in reserve_random_bytes_cb.", current_random_bytes_cb_refcnt); ++ return -EFAULT; ++ } ++ ++ for (;;) { ++ int orig_random_bytes_cb_refcnt = ++ atomic_cmpxchg( ++ &random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt + 1); ++ if (orig_random_bytes_cb_refcnt == current_random_bytes_cb_refcnt) ++ return 0; ++ else if (! orig_random_bytes_cb_refcnt) ++ return -ENODEV; ++ else ++ current_random_bytes_cb_refcnt = orig_random_bytes_cb_refcnt; ++ } ++ ++ __builtin_unreachable(); ++} ++ ++static __always_inline void release_random_bytes_cb(void) { ++ atomic_dec(&random_bytes_cb_refcnt); ++} ++ ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ ++static inline int call__get_random_bytes_cb(void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! _get_random_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = _get_random_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline ssize_t call_extract_crng_user_cb(void __user *buf, size_t nbytes) ++{ ++ ssize_t ret; ++ ++ if (! extract_crng_user_cb) ++ return -ECANCELED; ++ ++ ret = (ssize_t)reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = extract_crng_user_cb(buf, nbytes); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline bool call_crng_ready_cb(void) ++{ ++ bool ret; ++ ++ /* Null crng_ready_cb signifies that the DRBG is always ready, i.e. that if ++ * called, it will always have or obtain sufficient entropy to fulfill the ++ * call. ++ */ ++ if (! crng_ready_cb) ++ return 1; ++ ++ if (reserve_random_bytes_cb() != 0) ++ return 0; ++ ++ ret = crng_ready_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_mix_pool_bytes_cb(const void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! mix_pool_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = mix_pool_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_credit_init_bits_cb(size_t bits) ++{ ++ int ret; ++ ++ if (! credit_init_bits_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = credit_init_bits_cb(bits); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_crng_reseed_cb(void) ++{ ++ int ret; ++ ++ if (! crng_reseed_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = crng_reseed_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++#endif /* WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS */ ++ + #define CREATE_TRACE_POINTS + #include + +@@ -466,7 +763,22 @@ static struct crng_state primary_crng = + */ + static int crng_init = 0; + static bool crng_need_final_init = false; ++ + #define crng_ready() (likely(crng_init > 1)) ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ + static int crng_init_cnt = 0; + static unsigned long crng_global_init_time = 0; + #define CRNG_INIT_CNT_THRESH (2 * CHACHA_KEY_SIZE) +@@ -573,6 +885,12 @@ static void mix_pool_bytes(const void *i + unsigned long flags; + + trace_mix_pool_bytes(nbytes, _RET_IP_); ++ ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ (void)call_mix_pool_bytes_cb(in, nbytes); ++ /* fall through to mix into native pool too. */ ++#endif ++ + spin_lock_irqsave(&input_pool.lock, flags); + _mix_pool_bytes(in, nbytes); + spin_unlock_irqrestore(&input_pool.lock, flags); +@@ -1040,6 +1358,18 @@ static ssize_t extract_crng_user(void __ + u8 tmp[CHACHA_BLOCK_SIZE] __aligned(4); + int large_request = (nbytes > 256); + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ { ++ ssize_t cb_ret = call_extract_crng_user_cb(buf, nbytes); ++ /* If the callback returns -ECANCELED, that signals that iter is ++ * still intact, and flow can safely fall through to the native ++ * implementation. ++ */ ++ if (cb_ret != -ECANCELED) ++ return cb_ret; ++ } ++#endif ++ + while (nbytes) { + if (large_request && need_resched()) { + if (signal_pending(current)) { +@@ -1424,7 +1754,7 @@ static void _warn_unseeded_randomness(co + static bool print_once __read_mostly; + #endif + +- if (print_once || crng_ready() || ++ if (print_once || crng_ready_maybe_cb() || + (previous && (caller == READ_ONCE(*previous)))) + return; + WRITE_ONCE(*previous, caller); +@@ -1452,6 +1782,108 @@ static void _get_random_bytes(void *buf, + + trace_get_random_bytes(nbytes, _RET_IP_); + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. ++ */ ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, nbytes); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } ++#endif ++ + while (nbytes >= CHACHA_BLOCK_SIZE) { + extract_crng(buf); + buf += CHACHA_BLOCK_SIZE; +@@ -1537,12 +1969,12 @@ static void try_to_generate_entropy(void + */ + int wait_for_random_bytes(void) + { +- if (likely(crng_ready())) ++ if (likely(crng_ready_maybe_cb())) + return 0; + + do { + int ret; +- ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready(), HZ); ++ ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready_maybe_cb(), HZ); + if (ret) + return ret > 0 ? 0 : ret; + +@@ -1564,7 +1996,7 @@ EXPORT_SYMBOL(wait_for_random_bytes); + */ + bool rng_is_initialized(void) + { +- return crng_ready(); ++ return crng_ready_maybe_cb(); + } + EXPORT_SYMBOL(rng_is_initialized); + +@@ -1740,7 +2172,7 @@ static ssize_t urandom_read(struct file + { + static int maxwarn = 10; + +- if (!crng_ready() && maxwarn > 0) { ++ if (!crng_ready_maybe_cb() && maxwarn > 0) { + maxwarn--; + if (__ratelimit(&urandom_warning)) + pr_notice("%s: uninitialized urandom read (%zd bytes read)\n", +@@ -1765,6 +2197,11 @@ static __poll_t random_poll(struct file + { + __poll_t mask; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ if (crng_ready_by_cb()) ++ return EPOLLIN | EPOLLRDNORM; ++#endif ++ + poll_wait(file, &crng_init_wait, wait); + poll_wait(file, &random_write_wait, wait); + mask = 0; +@@ -1864,6 +2301,16 @@ static long random_ioctl(struct file *f, + case RNDRESEEDCRNG: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* fall through to reseed native crng too. */ ++ if (call_crng_reseed_cb() == 0) { ++ if (crng_init >= 2) { ++ crng_reseed(&primary_crng, true); ++ WRITE_ONCE(crng_global_init_time, jiffies - 1); ++ } ++ return 0; ++ } ++#endif + if (crng_init < 2) + return -ENODATA; + crng_reseed(&primary_crng, true); +@@ -1916,7 +2363,7 @@ SYSCALL_DEFINE3(getrandom, char __user * + if (count > INT_MAX) + count = INT_MAX; + +- if (!(flags & GRND_INSECURE) && !crng_ready()) { ++ if (!(flags & GRND_INSECURE) && !crng_ready_maybe_cb()) { + if (flags & GRND_NONBLOCK) + return -EAGAIN; + ret = wait_for_random_bytes(); +--- 5.17.0/include/linux/random.h.dist 2026-08-21 17:59:39.406263575 -0600 ++++ 5.17.0/include/linux/random.h 2026-08-21 17:59:46.623138386 -0600 +@@ -158,4 +158,42 @@ static inline bool __init arch_get_rando + } + #endif + ++#ifndef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS 1 ++#endif ++ ++typedef int (*_get_random_bytes_cb_t)(void *buf, size_t len); ++struct iov_iter; ++/* kernels >= 5.17.0 use get_random_bytes_user() */ ++typedef ssize_t (*get_random_bytes_user_cb_t)(struct iov_iter *iter); ++/* kernels < 5.17.0 use extract_crng_user(), though some LTS kernels, ++ * e.g. 5.10.236, have the 5.17+ architecture backported. ++ */ ++typedef ssize_t (*extract_crng_user_cb_t)(void __user *buf, size_t nbytes); ++typedef bool (*crng_ready_cb_t)(void); ++typedef int (*mix_pool_bytes_cb_t)(const void *buf, size_t len); ++typedef int (*credit_init_bits_cb_t)(size_t bits); ++typedef int (*crng_reseed_cb_t)(void); ++ ++struct wolfssl_linuxkm_random_bytes_handlers { ++ _get_random_bytes_cb_t _get_random_bytes; ++ get_random_bytes_user_cb_t get_random_bytes_user; ++ extract_crng_user_cb_t extract_crng_user; ++ crng_ready_cb_t crng_ready; ++ mix_pool_bytes_cb_t mix_pool_bytes; ++ credit_init_bits_cb_t credit_init_bits; ++ crng_reseed_cb_t crng_reseed; ++}; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); ++ + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch b/linuxkm/patches/5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch new file mode 100644 index 00000000000..d029315e3be --- /dev/null +++ b/linuxkm/patches/5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch @@ -0,0 +1,604 @@ +--- 5.17.14/drivers/char/random.c.dist 2026-08-21 17:32:23.832564448 -0600 ++++ 5.17.14/drivers/char/random.c 2026-08-21 17:32:23.833899217 -0600 +@@ -60,6 +60,303 @@ + #include + #include + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ ++#include ++ ++static atomic_long_t random_bytes_cb_owner = ++ ATOMIC_INIT((long)NULL); ++static atomic_t random_bytes_cb_refcnt = ++ ATOMIC_INIT(0); /* 0 if unregistered, 1 if no calls in flight. */ ++static _get_random_bytes_cb_t _get_random_bytes_cb = NULL; ++static get_random_bytes_user_cb_t get_random_bytes_user_cb = NULL; ++static crng_ready_cb_t crng_ready_cb = NULL; ++static mix_pool_bytes_cb_t mix_pool_bytes_cb = NULL; ++static credit_init_bits_cb_t credit_init_bits_cb = NULL; ++static crng_reseed_cb_t crng_reseed_cb = NULL; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers) ++{ ++ if ((! new_random_bytes_cb_owner) || ++ (! handlers) || ++ (! handlers->_get_random_bytes) || ++ (! handlers->get_random_bytes_user)) ++ { ++ return -EINVAL; ++ } ++ ++ /* random_bytes_cb_owner is used to enforce serialization of ++ * wolfssl_register_random_bytes_handlers() and ++ * wolfssl_unregister_random_bytes_handlers(). ++ */ ++ if (atomic_long_cmpxchg(&random_bytes_cb_owner, ++ (long)NULL, ++ (long)new_random_bytes_cb_owner) ++ != (long)NULL) ++ { ++ return -EBUSY; ++ } ++ ++ { ++ int current_random_bytes_cb_refcnt = atomic_read(&random_bytes_cb_refcnt); ++ if (current_random_bytes_cb_refcnt) { ++ pr_err("BUG: random_bytes_cb_refcnt == %d with null random_bytes_cb_owner", current_random_bytes_cb_refcnt); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -EFAULT; ++ } ++ } ++ ++ if (! try_module_get(new_random_bytes_cb_owner)) { ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -ENODEV; ++ } ++ ++ _get_random_bytes_cb = handlers->_get_random_bytes; ++ get_random_bytes_user_cb = handlers->get_random_bytes_user; ++ crng_ready_cb = handlers->crng_ready; ++ mix_pool_bytes_cb = handlers->mix_pool_bytes; ++ credit_init_bits_cb = handlers->credit_init_bits; ++ crng_reseed_cb = handlers->crng_reseed; ++ ++ barrier(); ++ atomic_set_release(&random_bytes_cb_refcnt, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_register_random_bytes_handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void) ++{ ++ int current_random_bytes_cb_refcnt; ++ int n_tries; ++ if (! atomic_long_read(&random_bytes_cb_owner)) ++ return -ENODEV; ++ ++ /* we're racing the kernel at large to try to catch random_bytes_cb_refcnt ++ * with no callers in flight -- retry and relax up to 100 times. ++ */ ++ for (n_tries = 0; n_tries < 100; ++n_tries) { ++ current_random_bytes_cb_refcnt = atomic_cmpxchg(&random_bytes_cb_refcnt, 1, 0); ++ if (current_random_bytes_cb_refcnt == 1) ++ break; ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in wolfssl_linuxkm_unregister_random_bytes_handlers.", current_random_bytes_cb_refcnt); ++ break; ++ } ++ if (msleep_interruptible(10) != 0) ++ return -EINTR; ++ } ++ if (current_random_bytes_cb_refcnt != 1) { ++ pr_warn("WARNING: wolfssl_unregister_random_bytes_handlers called with random_bytes_cb_refcnt == %d", current_random_bytes_cb_refcnt); ++ return -EBUSY; ++ } ++ ++ _get_random_bytes_cb = NULL; ++ get_random_bytes_user_cb = NULL; ++ crng_ready_cb = NULL; ++ mix_pool_bytes_cb = NULL; ++ credit_init_bits_cb = NULL; ++ crng_reseed_cb = NULL; ++ ++ module_put((struct module *)atomic_long_read(&random_bytes_cb_owner)); ++ barrier(); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_unregister_random_bytes_handlers); ++ ++static __always_inline int reserve_random_bytes_cb(void) { ++ int current_random_bytes_cb_refcnt = ++ atomic_read_acquire(&random_bytes_cb_refcnt); ++ ++ if (! current_random_bytes_cb_refcnt) ++ return -ENODEV; ++ ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in reserve_random_bytes_cb.", current_random_bytes_cb_refcnt); ++ return -EFAULT; ++ } ++ ++ for (;;) { ++ int orig_random_bytes_cb_refcnt = ++ atomic_cmpxchg( ++ &random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt + 1); ++ if (orig_random_bytes_cb_refcnt == current_random_bytes_cb_refcnt) ++ return 0; ++ else if (! orig_random_bytes_cb_refcnt) ++ return -ENODEV; ++ else ++ current_random_bytes_cb_refcnt = orig_random_bytes_cb_refcnt; ++ } ++ ++ __builtin_unreachable(); ++} ++ ++static __always_inline void release_random_bytes_cb(void) { ++ atomic_dec(&random_bytes_cb_refcnt); ++} ++ ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ ++static inline int call__get_random_bytes_cb(void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! _get_random_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = _get_random_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline ssize_t call_get_random_bytes_user_cb(struct iov_iter *iter) ++{ ++ ssize_t ret; ++ ++ if (! get_random_bytes_user_cb) ++ return -ECANCELED; ++ ++ ret = (ssize_t)reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = get_random_bytes_user_cb(iter); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline bool call_crng_ready_cb(void) ++{ ++ bool ret; ++ ++ /* Null crng_ready_cb signifies that the DRBG is always ready, i.e. that if ++ * called, it will always have or obtain sufficient entropy to fulfill the ++ * call. ++ */ ++ if (! crng_ready_cb) ++ return 1; ++ ++ if (reserve_random_bytes_cb() != 0) ++ return 0; ++ ++ ret = crng_ready_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_mix_pool_bytes_cb(const void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! mix_pool_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = mix_pool_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_credit_init_bits_cb(size_t bits) ++{ ++ int ret; ++ ++ if (! credit_init_bits_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = credit_init_bits_cb(bits); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_crng_reseed_cb(void) ++{ ++ int ret; ++ ++ if (! crng_reseed_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = crng_reseed_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++#endif /* WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS */ ++ + /********************************************************************* + * + * Initialization and readiness waiting. +@@ -79,7 +376,15 @@ static enum { + CRNG_EARLY = 1, /* At least POOL_EARLY_BITS collected */ + CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ + } crng_init __read_mostly = CRNG_EMPTY; ++ + #define crng_ready() (likely(crng_init >= CRNG_READY)) ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ + /* Various types of waiters for crng_init->CRNG_READY transition. */ + static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); + static struct fasync_struct *fasync; +@@ -105,7 +410,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis + */ + bool rng_is_initialized(void) + { +- return crng_ready(); ++ return crng_ready_maybe_cb(); + } + EXPORT_SYMBOL(rng_is_initialized); + +@@ -124,11 +429,11 @@ static void try_to_generate_entropy(void + */ + int wait_for_random_bytes(void) + { +- while (!crng_ready()) { ++ while (!crng_ready_maybe_cb()) { + int ret; + + try_to_generate_entropy(); +- ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready(), HZ); ++ ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready_maybe_cb(), HZ); + if (ret) + return ret > 0 ? 0 : ret; + } +@@ -182,7 +487,7 @@ static void __cold process_random_ready_ + } + + #define warn_unseeded_randomness() \ +- if (IS_ENABLED(CONFIG_WARN_ALL_UNSEEDED_RANDOM) && !crng_ready()) \ ++ if (IS_ENABLED(CONFIG_WARN_ALL_UNSEEDED_RANDOM) && !crng_ready_maybe_cb()) \ + printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ + __func__, (void *)_RET_IP_, crng_init) + +@@ -401,6 +706,108 @@ static void _get_random_bytes(void *buf, + if (!len) + return; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. ++ */ ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } ++#endif ++ + first_block_len = min_t(size_t, 32, len); + crng_make_state(chacha_state, buf, first_block_len); + len -= first_block_len; +@@ -450,6 +857,18 @@ static ssize_t get_random_bytes_user(str + if (unlikely(!iov_iter_count(iter))) + return 0; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ { ++ ssize_t cb_ret = call_get_random_bytes_user_cb(iter); ++ /* If the callback returns -ECANCELED, that signals that iter is ++ * still intact, and flow can safely fall through to the native ++ * implementation. ++ */ ++ if (cb_ret != -ECANCELED) ++ return cb_ret; ++ } ++#endif ++ + /* + * Immediately overwrite the ChaCha key at index 4 with random + * bytes, in case userspace causes copy_to_user() below to sleep +@@ -526,7 +945,7 @@ type get_random_ ##type(void) \ + \ + warn_unseeded_randomness(); \ + \ +- if (!crng_ready()) { \ ++ if (!crng_ready_maybe_cb()) { \ + _get_random_bytes(&ret, sizeof(ret)); \ + return ret; \ + } \ +@@ -650,6 +1069,11 @@ static void mix_pool_bytes(const void *b + { + unsigned long flags; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ (void)call_mix_pool_bytes_cb(buf, len); ++ /* fall through to mix into native pool too. */ ++#endif ++ + spin_lock_irqsave(&input_pool.lock, flags); + _mix_pool_bytes(buf, len); + spin_unlock_irqrestore(&input_pool.lock, flags); +@@ -701,7 +1125,11 @@ static void extract_entropy(void *buf, s + memzero_explicit(&block, sizeof(block)); + } + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++#define credit_init_bits(bits) do { (void)call_credit_init_bits_cb(bits); if (!crng_ready()) _credit_init_bits(bits); } while (0) ++#else + #define credit_init_bits(bits) if (!crng_ready()) _credit_init_bits(bits) ++#endif + + static void __cold _credit_init_bits(size_t bits) + { +@@ -1224,7 +1652,7 @@ SYSCALL_DEFINE3(getrandom, char __user * + if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) + return -EINVAL; + +- if (!crng_ready() && !(flags & GRND_INSECURE)) { ++ if (!crng_ready_maybe_cb() && !(flags & GRND_INSECURE)) { + if (flags & GRND_NONBLOCK) + return -EAGAIN; + ret = wait_for_random_bytes(); +@@ -1240,6 +1668,10 @@ SYSCALL_DEFINE3(getrandom, char __user * + + static __poll_t random_poll(struct file *file, poll_table *wait) + { ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ if (crng_ready_by_cb()) ++ return EPOLLIN | EPOLLRDNORM; ++#endif + poll_wait(file, &crng_init_wait, wait); + return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; + } +@@ -1281,7 +1713,7 @@ static ssize_t urandom_read_iter(struct + { + static int maxwarn = 10; + +- if (!crng_ready()) { ++ if (!crng_ready_maybe_cb()) { + if (!ratelimit_disable && maxwarn <= 0) + ++urandom_warning.missed; + else if (ratelimit_disable || __ratelimit(&urandom_warning)) { +@@ -1359,6 +1791,14 @@ static long random_ioctl(struct file *f, + case RNDRESEEDCRNG: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* fall through to reseed native crng too. */ ++ if (call_crng_reseed_cb() == 0) { ++ if (crng_ready()) ++ crng_reseed(); ++ return 0; ++ } ++#endif + if (!crng_ready()) + return -ENODATA; + crng_reseed(); +--- 5.17.14/include/linux/random.h.dist 2026-08-21 17:32:23.833316689 -0600 ++++ 5.17.14/include/linux/random.h 2026-08-21 17:32:23.834508500 -0600 +@@ -138,4 +138,42 @@ int random_online_cpu(unsigned int cpu); + extern const struct file_operations random_fops, urandom_fops; + #endif + ++#ifndef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS 1 ++#endif ++ ++typedef int (*_get_random_bytes_cb_t)(void *buf, size_t len); ++struct iov_iter; ++/* kernels >= 5.17.0 use get_random_bytes_user() */ ++typedef ssize_t (*get_random_bytes_user_cb_t)(struct iov_iter *iter); ++/* kernels < 5.17.0 use extract_crng_user(), though some LTS kernels, ++ * e.g. 5.10.236, have the 5.17+ architecture backported. ++ */ ++typedef ssize_t (*extract_crng_user_cb_t)(void __user *buf, size_t nbytes); ++typedef bool (*crng_ready_cb_t)(void); ++typedef int (*mix_pool_bytes_cb_t)(const void *buf, size_t len); ++typedef int (*credit_init_bits_cb_t)(size_t bits); ++typedef int (*crng_reseed_cb_t)(void); ++ ++struct wolfssl_linuxkm_random_bytes_handlers { ++ _get_random_bytes_cb_t _get_random_bytes; ++ get_random_bytes_user_cb_t get_random_bytes_user; ++ extract_crng_user_cb_t extract_crng_user; ++ crng_ready_cb_t crng_ready; ++ mix_pool_bytes_cb_t mix_pool_bytes; ++ credit_init_bits_cb_t credit_init_bits; ++ crng_reseed_cb_t crng_reseed; ++}; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); ++ + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.17/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17.patch b/linuxkm/patches/5.17/WOLFSSL_KERNELv5_17_FIPS.patch similarity index 61% rename from linuxkm/patches/5.17/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17.patch rename to linuxkm/patches/5.17/WOLFSSL_KERNELv5_17_FIPS.patch index b36ff870e2b..53d321b68b1 100644 --- a/linuxkm/patches/5.17/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-5v17.patch +++ b/linuxkm/patches/5.17/WOLFSSL_KERNELv5_17_FIPS.patch @@ -1,6 +1,6 @@ --- 5.17/drivers/char/random.c.dist 2022-05-31 08:33:43.006547419 -0500 +++ 5.17/drivers/char/random.c 2025-07-02 11:42:10.098166804 -0500 -@@ -60,6 +60,260 @@ +@@ -60,6 +60,303 @@ #include #include @@ -145,6 +145,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -261,7 +304,7 @@ /********************************************************************* * * Initialization and readiness waiting. -@@ -80,7 +334,15 @@ static enum { +@@ -80,7 +377,15 @@ static enum { CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ } crng_init __read_mostly = CRNG_EMPTY; static DEFINE_STATIC_KEY_FALSE(crng_is_ready); @@ -277,7 +320,7 @@ /* Various types of waiters for crng_init->CRNG_READY transition. */ static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); static struct fasync_struct *fasync; -@@ -106,7 +368,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis +@@ -106,7 +411,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis */ bool rng_is_initialized(void) { @@ -286,7 +329,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -130,11 +392,11 @@ static void try_to_generate_entropy(void +@@ -130,11 +435,11 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -300,7 +343,7 @@ if (ret) return ret > 0 ? 0 : ret; } -@@ -188,7 +450,7 @@ static void __cold process_random_ready_ +@@ -188,7 +493,7 @@ static void __cold process_random_ready_ } #define warn_unseeded_randomness() \ @@ -309,22 +352,116 @@ printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ __func__, (void *)_RET_IP_, crng_init) -@@ -407,6 +669,14 @@ static void _get_random_bytes(void *buf, +@@ -407,6 +712,108 @@ static void _get_random_bytes(void *buf, if (!len) return; +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow falls through to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, len) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + first_block_len = min_t(size_t, 32, len); crng_make_state(chacha_state, buf, first_block_len); len -= first_block_len; -@@ -456,6 +726,18 @@ static ssize_t get_random_bytes_user(str +@@ -456,6 +863,18 @@ static ssize_t get_random_bytes_user(str if (unlikely(!iov_iter_count(iter))) return 0; @@ -343,7 +480,7 @@ /* * Immediately overwrite the ChaCha key at index 4 with random * bytes, in case userspace causes copy_to_user() below to sleep -@@ -532,7 +814,7 @@ type get_random_ ##type(void) \ +@@ -532,7 +951,7 @@ type get_random_ ##type(void) \ \ warn_unseeded_randomness(); \ \ @@ -352,7 +489,7 @@ _get_random_bytes(&ret, sizeof(ret)); \ return ret; \ } \ -@@ -656,6 +938,11 @@ static void mix_pool_bytes(const void *b +@@ -656,6 +1075,11 @@ static void mix_pool_bytes(const void *b { unsigned long flags; @@ -364,7 +501,7 @@ spin_lock_irqsave(&input_pool.lock, flags); _mix_pool_bytes(buf, len); spin_unlock_irqrestore(&input_pool.lock, flags); -@@ -707,7 +994,11 @@ static void extract_entropy(void *buf, s +@@ -707,7 +1131,11 @@ static void extract_entropy(void *buf, s memzero_explicit(&block, sizeof(block)); } @@ -376,7 +513,7 @@ static void __cold _credit_init_bits(size_t bits) { -@@ -1233,7 +1524,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1233,7 +1661,7 @@ SYSCALL_DEFINE3(getrandom, char __user * if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) return -EINVAL; @@ -385,7 +522,7 @@ if (flags & GRND_NONBLOCK) return -EAGAIN; ret = wait_for_random_bytes(); -@@ -1249,6 +1540,10 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1249,6 +1677,10 @@ SYSCALL_DEFINE3(getrandom, char __user * static __poll_t random_poll(struct file *file, poll_table *wait) { @@ -396,7 +533,7 @@ poll_wait(file, &crng_init_wait, wait); return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; } -@@ -1290,7 +1585,7 @@ static ssize_t urandom_read_iter(struct +@@ -1290,7 +1722,7 @@ static ssize_t urandom_read_iter(struct { static int maxwarn = 10; @@ -405,7 +542,7 @@ if (!ratelimit_disable && maxwarn <= 0) ++urandom_warning.missed; else if (ratelimit_disable || __ratelimit(&urandom_warning)) { -@@ -1368,6 +1663,14 @@ static long random_ioctl(struct file *f, +@@ -1368,6 +1800,14 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -422,7 +559,7 @@ crng_reseed(); --- 5.17/include/linux/random.h.dist 2022-05-31 08:33:43.007547457 -0500 +++ 5.17/include/linux/random.h 2025-06-30 12:06:15.219731761 -0500 -@@ -138,4 +138,37 @@ int random_online_cpu(unsigned int cpu); +@@ -138,4 +138,42 @@ int random_online_cpu(unsigned int cpu); extern const struct file_operations random_fops, urandom_fops; #endif @@ -458,5 +595,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.18/WOLFSSL_KERNELv5_18_FIPS.patch b/linuxkm/patches/5.18/WOLFSSL_KERNELv5_18_FIPS.patch new file mode 100644 index 00000000000..9902cbd9644 --- /dev/null +++ b/linuxkm/patches/5.18/WOLFSSL_KERNELv5_18_FIPS.patch @@ -0,0 +1,608 @@ +--- 5.18/drivers/char/random.c.dist 2026-08-21 17:58:04.593738994 -0600 ++++ 5.18/drivers/char/random.c 2026-08-21 17:58:11.560142932 -0600 +@@ -59,6 +59,303 @@ + #include + #include + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ ++#include ++ ++static atomic_long_t random_bytes_cb_owner = ++ ATOMIC_INIT((long)NULL); ++static atomic_t random_bytes_cb_refcnt = ++ ATOMIC_INIT(0); /* 0 if unregistered, 1 if no calls in flight. */ ++static _get_random_bytes_cb_t _get_random_bytes_cb = NULL; ++static get_random_bytes_user_cb_t get_random_bytes_user_cb = NULL; ++static crng_ready_cb_t crng_ready_cb = NULL; ++static mix_pool_bytes_cb_t mix_pool_bytes_cb = NULL; ++static credit_init_bits_cb_t credit_init_bits_cb = NULL; ++static crng_reseed_cb_t crng_reseed_cb = NULL; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers) ++{ ++ if ((! new_random_bytes_cb_owner) || ++ (! handlers) || ++ (! handlers->_get_random_bytes) || ++ (! handlers->get_random_bytes_user)) ++ { ++ return -EINVAL; ++ } ++ ++ /* random_bytes_cb_owner is used to enforce serialization of ++ * wolfssl_register_random_bytes_handlers() and ++ * wolfssl_unregister_random_bytes_handlers(). ++ */ ++ if (atomic_long_cmpxchg(&random_bytes_cb_owner, ++ (long)NULL, ++ (long)new_random_bytes_cb_owner) ++ != (long)NULL) ++ { ++ return -EBUSY; ++ } ++ ++ { ++ int current_random_bytes_cb_refcnt = atomic_read(&random_bytes_cb_refcnt); ++ if (current_random_bytes_cb_refcnt) { ++ pr_err("BUG: random_bytes_cb_refcnt == %d with null random_bytes_cb_owner", current_random_bytes_cb_refcnt); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -EFAULT; ++ } ++ } ++ ++ if (! try_module_get(new_random_bytes_cb_owner)) { ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -ENODEV; ++ } ++ ++ _get_random_bytes_cb = handlers->_get_random_bytes; ++ get_random_bytes_user_cb = handlers->get_random_bytes_user; ++ crng_ready_cb = handlers->crng_ready; ++ mix_pool_bytes_cb = handlers->mix_pool_bytes; ++ credit_init_bits_cb = handlers->credit_init_bits; ++ crng_reseed_cb = handlers->crng_reseed; ++ ++ barrier(); ++ atomic_set_release(&random_bytes_cb_refcnt, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_register_random_bytes_handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void) ++{ ++ int current_random_bytes_cb_refcnt; ++ int n_tries; ++ if (! atomic_long_read(&random_bytes_cb_owner)) ++ return -ENODEV; ++ ++ /* we're racing the kernel at large to try to catch random_bytes_cb_refcnt ++ * with no callers in flight -- retry and relax up to 100 times. ++ */ ++ for (n_tries = 0; n_tries < 100; ++n_tries) { ++ current_random_bytes_cb_refcnt = atomic_cmpxchg(&random_bytes_cb_refcnt, 1, 0); ++ if (current_random_bytes_cb_refcnt == 1) ++ break; ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in wolfssl_linuxkm_unregister_random_bytes_handlers.", current_random_bytes_cb_refcnt); ++ break; ++ } ++ if (msleep_interruptible(10) != 0) ++ return -EINTR; ++ } ++ if (current_random_bytes_cb_refcnt != 1) { ++ pr_warn("WARNING: wolfssl_unregister_random_bytes_handlers called with random_bytes_cb_refcnt == %d", current_random_bytes_cb_refcnt); ++ return -EBUSY; ++ } ++ ++ _get_random_bytes_cb = NULL; ++ get_random_bytes_user_cb = NULL; ++ crng_ready_cb = NULL; ++ mix_pool_bytes_cb = NULL; ++ credit_init_bits_cb = NULL; ++ crng_reseed_cb = NULL; ++ ++ module_put((struct module *)atomic_long_read(&random_bytes_cb_owner)); ++ barrier(); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_unregister_random_bytes_handlers); ++ ++static __always_inline int reserve_random_bytes_cb(void) { ++ int current_random_bytes_cb_refcnt = ++ atomic_read_acquire(&random_bytes_cb_refcnt); ++ ++ if (! current_random_bytes_cb_refcnt) ++ return -ENODEV; ++ ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in reserve_random_bytes_cb.", current_random_bytes_cb_refcnt); ++ return -EFAULT; ++ } ++ ++ for (;;) { ++ int orig_random_bytes_cb_refcnt = ++ atomic_cmpxchg( ++ &random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt + 1); ++ if (orig_random_bytes_cb_refcnt == current_random_bytes_cb_refcnt) ++ return 0; ++ else if (! orig_random_bytes_cb_refcnt) ++ return -ENODEV; ++ else ++ current_random_bytes_cb_refcnt = orig_random_bytes_cb_refcnt; ++ } ++ ++ __builtin_unreachable(); ++} ++ ++static __always_inline void release_random_bytes_cb(void) { ++ atomic_dec(&random_bytes_cb_refcnt); ++} ++ ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ ++static inline int call__get_random_bytes_cb(void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! _get_random_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = _get_random_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline ssize_t call_get_random_bytes_user_cb(struct iov_iter *iter) ++{ ++ ssize_t ret; ++ ++ if (! get_random_bytes_user_cb) ++ return -ECANCELED; ++ ++ ret = (ssize_t)reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = get_random_bytes_user_cb(iter); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline bool call_crng_ready_cb(void) ++{ ++ bool ret; ++ ++ /* Null crng_ready_cb signifies that the DRBG is always ready, i.e. that if ++ * called, it will always have or obtain sufficient entropy to fulfill the ++ * call. ++ */ ++ if (! crng_ready_cb) ++ return 1; ++ ++ if (reserve_random_bytes_cb() != 0) ++ return 0; ++ ++ ret = crng_ready_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_mix_pool_bytes_cb(const void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! mix_pool_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = mix_pool_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_credit_init_bits_cb(size_t bits) ++{ ++ int ret; ++ ++ if (! credit_init_bits_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = credit_init_bits_cb(bits); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_crng_reseed_cb(void) ++{ ++ int ret; ++ ++ if (! crng_reseed_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = crng_reseed_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++#endif /* WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS */ ++ + /********************************************************************* + * + * Initialization and readiness waiting. +@@ -78,7 +375,15 @@ static enum { + CRNG_EARLY = 1, /* At least POOL_EARLY_BITS collected */ + CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ + } crng_init __read_mostly = CRNG_EMPTY; ++ + #define crng_ready() (likely(crng_init >= CRNG_READY)) ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ + /* Various types of waiters for crng_init->CRNG_READY transition. */ + static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); + static struct fasync_struct *fasync; +@@ -104,7 +409,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis + */ + bool rng_is_initialized(void) + { +- return crng_ready(); ++ return crng_ready_maybe_cb(); + } + EXPORT_SYMBOL(rng_is_initialized); + +@@ -123,11 +428,11 @@ static void try_to_generate_entropy(void + */ + int wait_for_random_bytes(void) + { +- while (!crng_ready()) { ++ while (!crng_ready_maybe_cb()) { + int ret; + + try_to_generate_entropy(); +- ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready(), HZ); ++ ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready_maybe_cb(), HZ); + if (ret) + return ret > 0 ? 0 : ret; + } +@@ -181,7 +486,7 @@ static void __cold process_random_ready_ + } + + #define warn_unseeded_randomness() \ +- if (IS_ENABLED(CONFIG_WARN_ALL_UNSEEDED_RANDOM) && !crng_ready()) \ ++ if (IS_ENABLED(CONFIG_WARN_ALL_UNSEEDED_RANDOM) && !crng_ready_maybe_cb()) \ + printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ + __func__, (void *)_RET_IP_, crng_init) + +@@ -400,6 +705,108 @@ static void _get_random_bytes(void *buf, + if (!len) + return; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. ++ */ ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } ++#endif ++ + first_block_len = min_t(size_t, 32, len); + crng_make_state(chacha_state, buf, first_block_len); + len -= first_block_len; +@@ -449,6 +856,18 @@ static ssize_t get_random_bytes_user(str + if (unlikely(!iov_iter_count(iter))) + return 0; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ { ++ ssize_t cb_ret = call_get_random_bytes_user_cb(iter); ++ /* If the callback returns -ECANCELED, that signals that iter is ++ * still intact, and flow can safely fall through to the native ++ * implementation. ++ */ ++ if (cb_ret != -ECANCELED) ++ return cb_ret; ++ } ++#endif ++ + /* + * Immediately overwrite the ChaCha key at index 4 with random + * bytes, in case userspace causes copy_to_iter() below to sleep +@@ -525,7 +944,7 @@ type get_random_ ##type(void) \ + \ + warn_unseeded_randomness(); \ + \ +- if (!crng_ready()) { \ ++ if (!crng_ready_maybe_cb()) { \ + _get_random_bytes(&ret, sizeof(ret)); \ + return ret; \ + } \ +@@ -649,6 +1068,11 @@ static void mix_pool_bytes(const void *b + { + unsigned long flags; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ (void)call_mix_pool_bytes_cb(buf, len); ++ /* fall through to mix into native pool too. */ ++#endif ++ + spin_lock_irqsave(&input_pool.lock, flags); + _mix_pool_bytes(buf, len); + spin_unlock_irqrestore(&input_pool.lock, flags); +@@ -700,7 +1124,11 @@ static void extract_entropy(void *buf, s + memzero_explicit(&block, sizeof(block)); + } + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++#define credit_init_bits(bits) do { (void)call_credit_init_bits_cb(bits); if (!crng_ready()) _credit_init_bits(bits); } while (0) ++#else + #define credit_init_bits(bits) if (!crng_ready()) _credit_init_bits(bits) ++#endif + + static void __cold _credit_init_bits(size_t bits) + { +@@ -1262,7 +1690,7 @@ SYSCALL_DEFINE3(getrandom, char __user * + if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) + return -EINVAL; + +- if (!crng_ready() && !(flags & GRND_INSECURE)) { ++ if (!crng_ready_maybe_cb() && !(flags & GRND_INSECURE)) { + if (flags & GRND_NONBLOCK) + return -EAGAIN; + ret = wait_for_random_bytes(); +@@ -1278,6 +1706,10 @@ SYSCALL_DEFINE3(getrandom, char __user * + + static __poll_t random_poll(struct file *file, poll_table *wait) + { ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ if (crng_ready_by_cb()) ++ return EPOLLIN | EPOLLRDNORM; ++#endif + poll_wait(file, &crng_init_wait, wait); + return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; + } +@@ -1323,10 +1755,10 @@ static ssize_t urandom_read_iter(struct + * Opportunistically attempt to initialize the RNG on platforms that + * have fast cycle counters, but don't (for now) require it to succeed. + */ +- if (!crng_ready()) ++ if (!crng_ready_maybe_cb()) + try_to_generate_entropy(); + +- if (!crng_ready()) { ++ if (!crng_ready_maybe_cb()) { + if (!ratelimit_disable && maxwarn <= 0) + ++urandom_warning.missed; + else if (ratelimit_disable || __ratelimit(&urandom_warning)) { +@@ -1404,6 +1836,14 @@ static long random_ioctl(struct file *f, + case RNDRESEEDCRNG: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* fall through to reseed native crng too. */ ++ if (call_crng_reseed_cb() == 0) { ++ if (crng_ready()) ++ crng_reseed(); ++ return 0; ++ } ++#endif + if (!crng_ready()) + return -ENODATA; + crng_reseed(); +--- 5.18/include/linux/random.h.dist 2026-08-21 17:58:04.594344033 -0600 ++++ 5.18/include/linux/random.h 2026-08-21 17:58:04.600264075 -0600 +@@ -147,4 +147,42 @@ int random_online_cpu(unsigned int cpu); + extern const struct file_operations random_fops, urandom_fops; + #endif + ++#ifndef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS 1 ++#endif ++ ++typedef int (*_get_random_bytes_cb_t)(void *buf, size_t len); ++struct iov_iter; ++/* kernels >= 5.17.0 use get_random_bytes_user() */ ++typedef ssize_t (*get_random_bytes_user_cb_t)(struct iov_iter *iter); ++/* kernels < 5.17.0 use extract_crng_user(), though some LTS kernels, ++ * e.g. 5.10.236, have the 5.17+ architecture backported. ++ */ ++typedef ssize_t (*extract_crng_user_cb_t)(void __user *buf, size_t nbytes); ++typedef bool (*crng_ready_cb_t)(void); ++typedef int (*mix_pool_bytes_cb_t)(const void *buf, size_t len); ++typedef int (*credit_init_bits_cb_t)(size_t bits); ++typedef int (*crng_reseed_cb_t)(void); ++ ++struct wolfssl_linuxkm_random_bytes_handlers { ++ _get_random_bytes_cb_t _get_random_bytes; ++ get_random_bytes_user_cb_t get_random_bytes_user; ++ extract_crng_user_cb_t extract_crng_user; ++ crng_ready_cb_t crng_ready; ++ mix_pool_bytes_cb_t mix_pool_bytes; ++ credit_init_bits_cb_t credit_init_bits; ++ crng_reseed_cb_t crng_reseed; ++}; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); ++ + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/5.6/WOLFSSL_KERNELv5_6_FIPS.patch b/linuxkm/patches/5.6/WOLFSSL_KERNELv5_6_FIPS.patch new file mode 100644 index 00000000000..6780539d262 --- /dev/null +++ b/linuxkm/patches/5.6/WOLFSSL_KERNELv5_6_FIPS.patch @@ -0,0 +1,605 @@ +--- 5.6/drivers/char/random.c.dist 2026-08-21 17:57:05.627701984 -0600 ++++ 5.6/drivers/char/random.c 2026-08-21 17:57:05.633326576 -0600 +@@ -344,6 +344,303 @@ + #include + #include + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ ++#include ++ ++static atomic_long_t random_bytes_cb_owner = ++ ATOMIC_INIT((long)NULL); ++static atomic_t random_bytes_cb_refcnt = ++ ATOMIC_INIT(0); /* 0 if unregistered, 1 if no calls in flight. */ ++static _get_random_bytes_cb_t _get_random_bytes_cb = NULL; ++static extract_crng_user_cb_t extract_crng_user_cb = NULL; ++static crng_ready_cb_t crng_ready_cb = NULL; ++static mix_pool_bytes_cb_t mix_pool_bytes_cb = NULL; ++static credit_init_bits_cb_t credit_init_bits_cb = NULL; ++static crng_reseed_cb_t crng_reseed_cb = NULL; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers) ++{ ++ if ((! new_random_bytes_cb_owner) || ++ (! handlers) || ++ (! handlers->_get_random_bytes) || ++ (! handlers->extract_crng_user)) ++ { ++ return -EINVAL; ++ } ++ ++ /* random_bytes_cb_owner is used to enforce serialization of ++ * wolfssl_register_random_bytes_handlers() and ++ * wolfssl_unregister_random_bytes_handlers(). ++ */ ++ if (atomic_long_cmpxchg(&random_bytes_cb_owner, ++ (long)NULL, ++ (long)new_random_bytes_cb_owner) ++ != (long)NULL) ++ { ++ return -EBUSY; ++ } ++ ++ { ++ int current_random_bytes_cb_refcnt = atomic_read(&random_bytes_cb_refcnt); ++ if (current_random_bytes_cb_refcnt) { ++ pr_err("BUG: random_bytes_cb_refcnt == %d with null random_bytes_cb_owner", current_random_bytes_cb_refcnt); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -EFAULT; ++ } ++ } ++ ++ if (! try_module_get(new_random_bytes_cb_owner)) { ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -ENODEV; ++ } ++ ++ _get_random_bytes_cb = handlers->_get_random_bytes; ++ extract_crng_user_cb = handlers->extract_crng_user; ++ crng_ready_cb = handlers->crng_ready; ++ mix_pool_bytes_cb = handlers->mix_pool_bytes; ++ credit_init_bits_cb = handlers->credit_init_bits; ++ crng_reseed_cb = handlers->crng_reseed; ++ ++ barrier(); ++ atomic_set_release(&random_bytes_cb_refcnt, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_register_random_bytes_handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void) ++{ ++ int current_random_bytes_cb_refcnt; ++ int n_tries; ++ if (! atomic_long_read(&random_bytes_cb_owner)) ++ return -ENODEV; ++ ++ /* we're racing the kernel at large to try to catch random_bytes_cb_refcnt ++ * with no callers in flight -- retry and relax up to 100 times. ++ */ ++ for (n_tries = 0; n_tries < 100; ++n_tries) { ++ current_random_bytes_cb_refcnt = atomic_cmpxchg(&random_bytes_cb_refcnt, 1, 0); ++ if (current_random_bytes_cb_refcnt == 1) ++ break; ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in wolfssl_linuxkm_unregister_random_bytes_handlers.", current_random_bytes_cb_refcnt); ++ break; ++ } ++ if (msleep_interruptible(10) != 0) ++ return -EINTR; ++ } ++ if (current_random_bytes_cb_refcnt != 1) { ++ pr_warn("WARNING: wolfssl_unregister_random_bytes_handlers called with random_bytes_cb_refcnt == %d", current_random_bytes_cb_refcnt); ++ return -EBUSY; ++ } ++ ++ _get_random_bytes_cb = NULL; ++ extract_crng_user_cb = NULL; ++ crng_ready_cb = NULL; ++ mix_pool_bytes_cb = NULL; ++ credit_init_bits_cb = NULL; ++ crng_reseed_cb = NULL; ++ ++ module_put((struct module *)atomic_long_read(&random_bytes_cb_owner)); ++ barrier(); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_unregister_random_bytes_handlers); ++ ++static __always_inline int reserve_random_bytes_cb(void) { ++ int current_random_bytes_cb_refcnt = ++ atomic_read_acquire(&random_bytes_cb_refcnt); ++ ++ if (! current_random_bytes_cb_refcnt) ++ return -ENODEV; ++ ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in reserve_random_bytes_cb.", current_random_bytes_cb_refcnt); ++ return -EFAULT; ++ } ++ ++ for (;;) { ++ int orig_random_bytes_cb_refcnt = ++ atomic_cmpxchg( ++ &random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt + 1); ++ if (orig_random_bytes_cb_refcnt == current_random_bytes_cb_refcnt) ++ return 0; ++ else if (! orig_random_bytes_cb_refcnt) ++ return -ENODEV; ++ else ++ current_random_bytes_cb_refcnt = orig_random_bytes_cb_refcnt; ++ } ++ ++ __builtin_unreachable(); ++} ++ ++static __always_inline void release_random_bytes_cb(void) { ++ atomic_dec(&random_bytes_cb_refcnt); ++} ++ ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ ++static inline int call__get_random_bytes_cb(void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! _get_random_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = _get_random_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline ssize_t call_extract_crng_user_cb(void __user *buf, size_t nbytes) ++{ ++ ssize_t ret; ++ ++ if (! extract_crng_user_cb) ++ return -ECANCELED; ++ ++ ret = (ssize_t)reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = extract_crng_user_cb(buf, nbytes); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline bool call_crng_ready_cb(void) ++{ ++ bool ret; ++ ++ /* Null crng_ready_cb signifies that the DRBG is always ready, i.e. that if ++ * called, it will always have or obtain sufficient entropy to fulfill the ++ * call. ++ */ ++ if (! crng_ready_cb) ++ return 1; ++ ++ if (reserve_random_bytes_cb() != 0) ++ return 0; ++ ++ ret = crng_ready_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_mix_pool_bytes_cb(const void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! mix_pool_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = mix_pool_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_credit_init_bits_cb(size_t bits) ++{ ++ int ret; ++ ++ if (! credit_init_bits_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = credit_init_bits_cb(bits); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_crng_reseed_cb(void) ++{ ++ int ret; ++ ++ if (! crng_reseed_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = crng_reseed_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++#endif /* WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS */ ++ + #define CREATE_TRACE_POINTS + #include + +@@ -461,7 +758,22 @@ static struct crng_state primary_crng = + * its value (from 0->1->2). + */ + static int crng_init = 0; ++ + #define crng_ready() (likely(crng_init > 1)) ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ + static int crng_init_cnt = 0; + static unsigned long crng_global_init_time = 0; + #define CRNG_INIT_CNT_THRESH (2*CHACHA_KEY_SIZE) +@@ -593,6 +905,11 @@ static void mix_pool_bytes(struct entrop + { + unsigned long flags; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ (void)call_mix_pool_bytes_cb(in, nbytes); ++ /* fall through to mix into native pool too. */ ++#endif ++ + trace_mix_pool_bytes(r->name, nbytes, _RET_IP_); + spin_lock_irqsave(&r->lock, flags); + _mix_pool_bytes(r, in, nbytes); +@@ -664,6 +981,10 @@ static void credit_entropy_bits(struct e + const int pool_size = r->poolinfo->poolfracbits; + int nfrac = nbits << ENTROPY_SHIFT; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ (void)call_credit_init_bits_cb(nbits); ++#endif ++ + if (!nbits) + return; + +@@ -1041,6 +1362,18 @@ static ssize_t extract_crng_user(void __ + __u8 tmp[CHACHA_BLOCK_SIZE] __aligned(4); + int large_request = (nbytes > 256); + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ { ++ ssize_t cb_ret = call_extract_crng_user_cb(buf, nbytes); ++ /* If the callback returns -ECANCELED, that signals that iter is ++ * still intact, and flow can safely fall through to the native ++ * implementation. ++ */ ++ if (cb_ret != -ECANCELED) ++ return cb_ret; ++ } ++#endif ++ + while (nbytes) { + if (large_request && need_resched()) { + if (signal_pending(current)) { +@@ -1495,7 +1828,7 @@ static void _warn_unseeded_randomness(co + #endif + + if (print_once || +- crng_ready() || ++ crng_ready_maybe_cb() || + (previous && (caller == READ_ONCE(*previous)))) + return; + WRITE_ONCE(*previous, caller); +@@ -1524,6 +1857,108 @@ static void _get_random_bytes(void *buf, + + trace_get_random_bytes(nbytes, _RET_IP_); + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. ++ */ ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, nbytes); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } ++#endif ++ + while (nbytes >= CHACHA_BLOCK_SIZE) { + extract_crng(buf); + buf += CHACHA_BLOCK_SIZE; +@@ -1610,12 +2045,12 @@ static void try_to_generate_entropy(void + */ + int wait_for_random_bytes(void) + { +- if (likely(crng_ready())) ++ if (likely(crng_ready_maybe_cb())) + return 0; + + do { + int ret; +- ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready(), HZ); ++ ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready_maybe_cb(), HZ); + if (ret) + return ret > 0 ? 0 : ret; + +@@ -1637,7 +2072,7 @@ EXPORT_SYMBOL(wait_for_random_bytes); + */ + bool rng_is_initialized(void) + { +- return crng_ready(); ++ return crng_ready_maybe_cb(); + } + EXPORT_SYMBOL(rng_is_initialized); + +@@ -1815,7 +2250,7 @@ urandom_read(struct file *file, char __u + unsigned long flags; + static int maxwarn = 10; + +- if (!crng_ready() && maxwarn > 0) { ++ if (!crng_ready_maybe_cb() && maxwarn > 0) { + maxwarn--; + if (__ratelimit(&urandom_warning)) + pr_notice("%s: uninitialized urandom read (%zd bytes read)\n", +@@ -1844,6 +2279,11 @@ random_poll(struct file *file, poll_tabl + { + __poll_t mask; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ if (crng_ready_by_cb()) ++ return EPOLLIN | EPOLLRDNORM; ++#endif ++ + poll_wait(file, &crng_init_wait, wait); + poll_wait(file, &random_write_wait, wait); + mask = 0; +@@ -1942,6 +2382,16 @@ static long random_ioctl(struct file *f, + case RNDRESEEDCRNG: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* fall through to reseed native crng too. */ ++ if (call_crng_reseed_cb() == 0) { ++ if (crng_init >= 2) { ++ crng_reseed(&primary_crng, &input_pool); ++ crng_global_init_time = jiffies - 1; ++ } ++ return 0; ++ } ++#endif + if (crng_init < 2) + return -ENODATA; + crng_reseed(&primary_crng, NULL); +@@ -1994,7 +2444,7 @@ SYSCALL_DEFINE3(getrandom, char __user * + if (count > INT_MAX) + count = INT_MAX; + +- if (!(flags & GRND_INSECURE) && !crng_ready()) { ++ if (!(flags & GRND_INSECURE) && !crng_ready_maybe_cb()) { + if (flags & GRND_NONBLOCK) + return -EAGAIN; + ret = wait_for_random_bytes(); +--- 5.6/include/linux/random.h.dist 2026-08-21 17:57:05.628305926 -0600 ++++ 5.6/include/linux/random.h 2026-08-21 17:57:05.634033589 -0600 +@@ -191,4 +191,42 @@ static inline u32 next_pseudo_random32(u + return seed * 1664525 + 1013904223; + } + ++#ifndef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS 1 ++#endif ++ ++typedef int (*_get_random_bytes_cb_t)(void *buf, size_t len); ++struct iov_iter; ++/* kernels >= 5.17.0 use get_random_bytes_user() */ ++typedef ssize_t (*get_random_bytes_user_cb_t)(struct iov_iter *iter); ++/* kernels < 5.17.0 use extract_crng_user(), though some LTS kernels, ++ * e.g. 5.10.236, have the 5.17+ architecture backported. ++ */ ++typedef ssize_t (*extract_crng_user_cb_t)(void __user *buf, size_t nbytes); ++typedef bool (*crng_ready_cb_t)(void); ++typedef int (*mix_pool_bytes_cb_t)(const void *buf, size_t len); ++typedef int (*credit_init_bits_cb_t)(size_t bits); ++typedef int (*crng_reseed_cb_t)(void); ++ ++struct wolfssl_linuxkm_random_bytes_handlers { ++ _get_random_bytes_cb_t _get_random_bytes; ++ get_random_bytes_user_cb_t get_random_bytes_user; ++ extract_crng_user_cb_t extract_crng_user; ++ crng_ready_cb_t crng_ready; ++ mix_pool_bytes_cb_t mix_pool_bytes; ++ credit_init_bits_cb_t credit_init_bits; ++ crng_reseed_cb_t crng_reseed; ++}; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); ++ + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/6.1.73/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v1v73.patch b/linuxkm/patches/6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch similarity index 61% rename from linuxkm/patches/6.1.73/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v1v73.patch rename to linuxkm/patches/6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch index 326bec0fd0a..df338febff2 100644 --- a/linuxkm/patches/6.1.73/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v1v73.patch +++ b/linuxkm/patches/6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch @@ -1,6 +1,6 @@ --- 6.1.73/drivers/char/random.c.dist 2024-01-19 16:25:03.754138321 -0600 +++ 6.1.73/drivers/char/random.c 2025-07-03 15:51:24.282595676 -0500 -@@ -60,6 +60,260 @@ +@@ -60,6 +60,303 @@ #include #include @@ -145,6 +145,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -261,7 +304,7 @@ /********************************************************************* * * Initialization and readiness waiting. -@@ -80,7 +334,15 @@ static enum { +@@ -80,7 +377,15 @@ static enum { CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ } crng_init __read_mostly = CRNG_EMPTY; static DEFINE_STATIC_KEY_FALSE(crng_is_ready); @@ -277,7 +320,7 @@ /* Various types of waiters for crng_init->CRNG_READY transition. */ static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); static struct fasync_struct *fasync; -@@ -104,7 +366,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis +@@ -104,7 +409,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis */ bool rng_is_initialized(void) { @@ -286,7 +329,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -128,11 +390,11 @@ static void try_to_generate_entropy(void +@@ -128,11 +433,11 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -300,7 +343,7 @@ if (ret) return ret > 0 ? 0 : ret; } -@@ -141,7 +403,7 @@ int wait_for_random_bytes(void) +@@ -141,7 +446,7 @@ int wait_for_random_bytes(void) EXPORT_SYMBOL(wait_for_random_bytes); #define warn_unseeded_randomness() \ @@ -309,22 +352,116 @@ printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ __func__, (void *)_RET_IP_, crng_init) -@@ -362,6 +624,14 @@ static void _get_random_bytes(void *buf, +@@ -362,6 +667,108 @@ static void _get_random_bytes(void *buf, if (!len) return; +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow falls through to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, len) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + first_block_len = min_t(size_t, 32, len); crng_make_state(chacha_state, buf, first_block_len); len -= first_block_len; -@@ -408,6 +678,18 @@ static ssize_t get_random_bytes_user(str +@@ -408,6 +815,18 @@ static ssize_t get_random_bytes_user(str if (unlikely(!iov_iter_count(iter))) return 0; @@ -343,7 +480,7 @@ /* * Immediately overwrite the ChaCha key at index 4 with random * bytes, in case userspace causes copy_to_iter() below to sleep -@@ -484,7 +766,7 @@ type get_random_ ##type(void) \ +@@ -484,7 +903,7 @@ type get_random_ ##type(void) \ \ warn_unseeded_randomness(); \ \ @@ -352,7 +489,7 @@ _get_random_bytes(&ret, sizeof(ret)); \ return ret; \ } \ -@@ -620,6 +902,11 @@ static void mix_pool_bytes(const void *b +@@ -620,6 +1039,11 @@ static void mix_pool_bytes(const void *b { unsigned long flags; @@ -364,7 +501,7 @@ spin_lock_irqsave(&input_pool.lock, flags); _mix_pool_bytes(buf, len); spin_unlock_irqrestore(&input_pool.lock, flags); -@@ -679,7 +966,11 @@ static void extract_entropy(void *buf, s +@@ -679,7 +1103,11 @@ static void extract_entropy(void *buf, s memzero_explicit(&block, sizeof(block)); } @@ -376,7 +513,7 @@ static void __cold _credit_init_bits(size_t bits) { -@@ -1321,7 +1612,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1321,7 +1749,7 @@ SYSCALL_DEFINE3(getrandom, char __user * if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) return -EINVAL; @@ -385,7 +522,7 @@ if (flags & GRND_NONBLOCK) return -EAGAIN; ret = wait_for_random_bytes(); -@@ -1337,6 +1628,10 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1337,6 +1765,10 @@ SYSCALL_DEFINE3(getrandom, char __user * static __poll_t random_poll(struct file *file, poll_table *wait) { @@ -396,7 +533,7 @@ poll_wait(file, &crng_init_wait, wait); return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; } -@@ -1382,10 +1677,10 @@ static ssize_t urandom_read_iter(struct +@@ -1382,10 +1814,10 @@ static ssize_t urandom_read_iter(struct * Opportunistically attempt to initialize the RNG on platforms that * have fast cycle counters, but don't (for now) require it to succeed. */ @@ -409,7 +546,7 @@ if (!ratelimit_disable && maxwarn <= 0) ++urandom_warning.missed; else if (ratelimit_disable || __ratelimit(&urandom_warning)) { -@@ -1402,7 +1697,7 @@ static ssize_t random_read_iter(struct k +@@ -1402,7 +1834,7 @@ static ssize_t random_read_iter(struct k { int ret; @@ -418,7 +555,7 @@ ((kiocb->ki_flags & (IOCB_NOWAIT | IOCB_NOIO)) || (kiocb->ki_filp->f_flags & O_NONBLOCK))) return -EAGAIN; -@@ -1468,6 +1763,14 @@ static long random_ioctl(struct file *f, +@@ -1468,6 +1900,14 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -435,7 +572,7 @@ crng_reseed(); --- 6.1.73/include/linux/random.h.dist 2024-01-19 16:25:07.891223702 -0600 +++ 6.1.73/include/linux/random.h 2025-06-30 12:38:54.353341542 -0500 -@@ -202,4 +202,37 @@ int random_online_cpu(unsigned int cpu); +@@ -202,4 +202,42 @@ int random_online_cpu(unsigned int cpu); extern const struct file_operations random_fops, urandom_fops; #endif @@ -471,5 +608,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch b/linuxkm/patches/6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch new file mode 100644 index 00000000000..5c761d094d9 --- /dev/null +++ b/linuxkm/patches/6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch @@ -0,0 +1,617 @@ +--- 6.12.75/drivers/char/random.c.dist 2026-08-21 17:31:47.928224092 -0600 ++++ 6.12.75/drivers/char/random.c 2026-08-21 17:31:47.929178850 -0600 +@@ -67,6 +67,303 @@ + #include + #include + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ ++#include ++ ++static atomic_long_t random_bytes_cb_owner = ++ ATOMIC_INIT((long)NULL); ++static atomic_t random_bytes_cb_refcnt = ++ ATOMIC_INIT(0); /* 0 if unregistered, 1 if no calls in flight. */ ++static _get_random_bytes_cb_t _get_random_bytes_cb = NULL; ++static get_random_bytes_user_cb_t get_random_bytes_user_cb = NULL; ++static crng_ready_cb_t crng_ready_cb = NULL; ++static mix_pool_bytes_cb_t mix_pool_bytes_cb = NULL; ++static credit_init_bits_cb_t credit_init_bits_cb = NULL; ++static crng_reseed_cb_t crng_reseed_cb = NULL; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers) ++{ ++ if ((! new_random_bytes_cb_owner) || ++ (! handlers) || ++ (! handlers->_get_random_bytes) || ++ (! handlers->get_random_bytes_user)) ++ { ++ return -EINVAL; ++ } ++ ++ /* random_bytes_cb_owner is used to enforce serialization of ++ * wolfssl_register_random_bytes_handlers() and ++ * wolfssl_unregister_random_bytes_handlers(). ++ */ ++ if (atomic_long_cmpxchg(&random_bytes_cb_owner, ++ (long)NULL, ++ (long)new_random_bytes_cb_owner) ++ != (long)NULL) ++ { ++ return -EBUSY; ++ } ++ ++ { ++ int current_random_bytes_cb_refcnt = atomic_read(&random_bytes_cb_refcnt); ++ if (current_random_bytes_cb_refcnt) { ++ pr_err("BUG: random_bytes_cb_refcnt == %d with null random_bytes_cb_owner", current_random_bytes_cb_refcnt); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -EFAULT; ++ } ++ } ++ ++ if (! try_module_get(new_random_bytes_cb_owner)) { ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -ENODEV; ++ } ++ ++ _get_random_bytes_cb = handlers->_get_random_bytes; ++ get_random_bytes_user_cb = handlers->get_random_bytes_user; ++ crng_ready_cb = handlers->crng_ready; ++ mix_pool_bytes_cb = handlers->mix_pool_bytes; ++ credit_init_bits_cb = handlers->credit_init_bits; ++ crng_reseed_cb = handlers->crng_reseed; ++ ++ barrier(); ++ atomic_set_release(&random_bytes_cb_refcnt, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_register_random_bytes_handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void) ++{ ++ int current_random_bytes_cb_refcnt; ++ int n_tries; ++ if (! atomic_long_read(&random_bytes_cb_owner)) ++ return -ENODEV; ++ ++ /* we're racing the kernel at large to try to catch random_bytes_cb_refcnt ++ * with no callers in flight -- retry and relax up to 100 times. ++ */ ++ for (n_tries = 0; n_tries < 100; ++n_tries) { ++ current_random_bytes_cb_refcnt = atomic_cmpxchg(&random_bytes_cb_refcnt, 1, 0); ++ if (current_random_bytes_cb_refcnt == 1) ++ break; ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in wolfssl_linuxkm_unregister_random_bytes_handlers.", current_random_bytes_cb_refcnt); ++ break; ++ } ++ if (msleep_interruptible(10) != 0) ++ return -EINTR; ++ } ++ if (current_random_bytes_cb_refcnt != 1) { ++ pr_warn("WARNING: wolfssl_unregister_random_bytes_handlers called with random_bytes_cb_refcnt == %d", current_random_bytes_cb_refcnt); ++ return -EBUSY; ++ } ++ ++ _get_random_bytes_cb = NULL; ++ get_random_bytes_user_cb = NULL; ++ crng_ready_cb = NULL; ++ mix_pool_bytes_cb = NULL; ++ credit_init_bits_cb = NULL; ++ crng_reseed_cb = NULL; ++ ++ module_put((struct module *)atomic_long_read(&random_bytes_cb_owner)); ++ barrier(); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_unregister_random_bytes_handlers); ++ ++static __always_inline int reserve_random_bytes_cb(void) { ++ int current_random_bytes_cb_refcnt = ++ atomic_read_acquire(&random_bytes_cb_refcnt); ++ ++ if (! current_random_bytes_cb_refcnt) ++ return -ENODEV; ++ ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in reserve_random_bytes_cb.", current_random_bytes_cb_refcnt); ++ return -EFAULT; ++ } ++ ++ for (;;) { ++ int orig_random_bytes_cb_refcnt = ++ atomic_cmpxchg( ++ &random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt + 1); ++ if (orig_random_bytes_cb_refcnt == current_random_bytes_cb_refcnt) ++ return 0; ++ else if (! orig_random_bytes_cb_refcnt) ++ return -ENODEV; ++ else ++ current_random_bytes_cb_refcnt = orig_random_bytes_cb_refcnt; ++ } ++ ++ __builtin_unreachable(); ++} ++ ++static __always_inline void release_random_bytes_cb(void) { ++ atomic_dec(&random_bytes_cb_refcnt); ++} ++ ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ ++static inline int call__get_random_bytes_cb(void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! _get_random_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = _get_random_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline ssize_t call_get_random_bytes_user_cb(struct iov_iter *iter) ++{ ++ ssize_t ret; ++ ++ if (! get_random_bytes_user_cb) ++ return -ECANCELED; ++ ++ ret = (ssize_t)reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = get_random_bytes_user_cb(iter); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline bool call_crng_ready_cb(void) ++{ ++ bool ret; ++ ++ /* Null crng_ready_cb signifies that the DRBG is always ready, i.e. that if ++ * called, it will always have or obtain sufficient entropy to fulfill the ++ * call. ++ */ ++ if (! crng_ready_cb) ++ return 1; ++ ++ if (reserve_random_bytes_cb() != 0) ++ return 0; ++ ++ ret = crng_ready_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_mix_pool_bytes_cb(const void *buf, size_t len) ++{ ++ int ret; ++ ++ if (! mix_pool_bytes_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = mix_pool_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_credit_init_bits_cb(size_t bits) ++{ ++ int ret; ++ ++ if (! credit_init_bits_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = credit_init_bits_cb(bits); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_crng_reseed_cb(void) ++{ ++ int ret; ++ ++ if (! crng_reseed_cb) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = crng_reseed_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++#endif /* WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS */ ++ + /********************************************************************* + * + * Initialization and readiness waiting. +@@ -87,7 +384,15 @@ static enum { + CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ + } crng_init __read_mostly = CRNG_EMPTY; + static DEFINE_STATIC_KEY_FALSE(crng_is_ready); ++ + #define crng_ready() (static_branch_likely(&crng_is_ready) || crng_init >= CRNG_READY) ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ + /* Various types of waiters for crng_init->CRNG_READY transition. */ + static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); + static struct fasync_struct *fasync; +@@ -111,7 +416,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis + */ + bool rng_is_initialized(void) + { +- return crng_ready(); ++ return crng_ready_maybe_cb(); + } + EXPORT_SYMBOL(rng_is_initialized); + +@@ -135,11 +440,11 @@ static void try_to_generate_entropy(void + */ + int wait_for_random_bytes(void) + { +- while (!crng_ready()) { ++ while (!crng_ready_maybe_cb()) { + int ret; + + try_to_generate_entropy(); +- ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready(), HZ); ++ ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready_maybe_cb(), HZ); + if (ret) + return ret > 0 ? 0 : ret; + } +@@ -159,7 +464,7 @@ int __cold execute_with_initialized_rng( + int ret = 0; + + spin_lock_irqsave(&random_ready_notifier.lock, flags); +- if (crng_ready()) ++ if (crng_ready_maybe_cb()) + nb->notifier_call(nb, 0, NULL); + else + ret = raw_notifier_chain_register((struct raw_notifier_head *)&random_ready_notifier.head, nb); +@@ -395,6 +700,108 @@ static void _get_random_bytes(void *buf, + if (!len) + return; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. ++ */ ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } ++#endif ++ + first_block_len = min_t(size_t, 32, len); + crng_make_state(chacha_state, buf, first_block_len); + len -= first_block_len; +@@ -440,6 +847,18 @@ static ssize_t get_random_bytes_user(str + if (unlikely(!iov_iter_count(iter))) + return 0; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ { ++ ssize_t cb_ret = call_get_random_bytes_user_cb(iter); ++ /* If the callback returns -ECANCELED, that signals that iter is ++ * still intact, and flow can safely fall through to the native ++ * implementation. ++ */ ++ if (cb_ret != -ECANCELED) ++ return cb_ret; ++ } ++#endif ++ + /* + * Immediately overwrite the ChaCha key at index 4 with random + * bytes, in case userspace causes copy_to_iter() below to sleep +@@ -514,7 +933,7 @@ type get_random_ ##type(void) \ + struct batch_ ##type *batch; \ + unsigned long next_gen; \ + \ +- if (!crng_ready()) { \ ++ if (!crng_ready_maybe_cb()) { \ + _get_random_bytes(&ret, sizeof(ret)); \ + return ret; \ + } \ +@@ -650,6 +1069,11 @@ static void mix_pool_bytes(const void *b + { + unsigned long flags; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ (void)call_mix_pool_bytes_cb(buf, len); ++ /* fall through to mix into native pool too. */ ++#endif ++ + spin_lock_irqsave(&input_pool.lock, flags); + _mix_pool_bytes(buf, len); + spin_unlock_irqrestore(&input_pool.lock, flags); +@@ -709,7 +1133,11 @@ static void extract_entropy(void *buf, s + memzero_explicit(&block, sizeof(block)); + } + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++#define credit_init_bits(bits) do { (void)call_credit_init_bits_cb(bits); if (!crng_ready()) _credit_init_bits(bits); } while (0) ++#else + #define credit_init_bits(bits) if (!crng_ready()) _credit_init_bits(bits) ++#endif + + static void __cold _credit_init_bits(size_t bits) + { +@@ -1390,7 +1818,7 @@ SYSCALL_DEFINE3(getrandom, char __user * + if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) + return -EINVAL; + +- if (!crng_ready() && !(flags & GRND_INSECURE)) { ++ if (!crng_ready_maybe_cb() && !(flags & GRND_INSECURE)) { + if (flags & GRND_NONBLOCK) + return -EAGAIN; + ret = wait_for_random_bytes(); +@@ -1406,6 +1834,10 @@ SYSCALL_DEFINE3(getrandom, char __user * + + static __poll_t random_poll(struct file *file, poll_table *wait) + { ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ if (crng_ready_by_cb()) ++ return EPOLLIN | EPOLLRDNORM; ++#endif + poll_wait(file, &crng_init_wait, wait); + return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; + } +@@ -1451,10 +1883,10 @@ static ssize_t urandom_read_iter(struct + * Opportunistically attempt to initialize the RNG on platforms that + * have fast cycle counters, but don't (for now) require it to succeed. + */ +- if (!crng_ready()) ++ if (!crng_ready_maybe_cb()) + try_to_generate_entropy(); + +- if (!crng_ready()) { ++ if (!crng_ready_maybe_cb()) { + if (!ratelimit_disable && maxwarn <= 0) + ++urandom_warning.missed; + else if (ratelimit_disable || __ratelimit(&urandom_warning)) { +@@ -1471,7 +1903,7 @@ static ssize_t random_read_iter(struct k + { + int ret; + +- if (!crng_ready() && ++ if (!crng_ready_by_cb() && + ((kiocb->ki_flags & (IOCB_NOWAIT | IOCB_NOIO)) || + (kiocb->ki_filp->f_flags & O_NONBLOCK))) + return -EAGAIN; +@@ -1536,6 +1968,14 @@ static long random_ioctl(struct file *f, + case RNDRESEEDCRNG: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* fall through to reseed native crng too. */ ++ if (call_crng_reseed_cb() == 0) { ++ if (crng_ready()) ++ crng_reseed(NULL); ++ return 0; ++ } ++#endif + if (!crng_ready()) + return -ENODATA; + crng_reseed(NULL); +--- 6.12.75/include/linux/random.h.dist 2026-08-21 17:31:47.928709123 -0600 ++++ 6.12.75/include/linux/random.h 2026-08-21 17:31:47.929662178 -0600 +@@ -161,4 +161,42 @@ int random_online_cpu(unsigned int cpu); + extern const struct file_operations random_fops, urandom_fops; + #endif + ++#ifndef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS 1 ++#endif ++ ++typedef int (*_get_random_bytes_cb_t)(void *buf, size_t len); ++struct iov_iter; ++/* kernels >= 5.17.0 use get_random_bytes_user() */ ++typedef ssize_t (*get_random_bytes_user_cb_t)(struct iov_iter *iter); ++/* kernels < 5.17.0 use extract_crng_user(), though some LTS kernels, ++ * e.g. 5.10.236, have the 5.17+ architecture backported. ++ */ ++typedef ssize_t (*extract_crng_user_cb_t)(void __user *buf, size_t nbytes); ++typedef bool (*crng_ready_cb_t)(void); ++typedef int (*mix_pool_bytes_cb_t)(const void *buf, size_t len); ++typedef int (*credit_init_bits_cb_t)(size_t bits); ++typedef int (*crng_reseed_cb_t)(void); ++ ++struct wolfssl_linuxkm_random_bytes_handlers { ++ _get_random_bytes_cb_t _get_random_bytes; ++ get_random_bytes_user_cb_t get_random_bytes_user; ++ extract_crng_user_cb_t extract_crng_user; ++ crng_ready_cb_t crng_ready; ++ mix_pool_bytes_cb_t mix_pool_bytes; ++ credit_init_bits_cb_t credit_init_bits; ++ crng_reseed_cb_t crng_reseed; ++}; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); ++ + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/6.12/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v12.patch b/linuxkm/patches/6.12/WOLFSSL_KERNELv6_12_FIPS.patch similarity index 61% rename from linuxkm/patches/6.12/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v12.patch rename to linuxkm/patches/6.12/WOLFSSL_KERNELv6_12_FIPS.patch index f0908ea4cff..293d48b9dbb 100644 --- a/linuxkm/patches/6.12/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v12.patch +++ b/linuxkm/patches/6.12/WOLFSSL_KERNELv6_12_FIPS.patch @@ -1,6 +1,6 @@ --- 6.12/drivers/char/random.c.dist 2025-01-19 19:03:47.877152701 -0600 +++ 6.12/drivers/char/random.c 2025-07-02 10:40:21.994303997 -0500 -@@ -67,6 +67,260 @@ +@@ -67,6 +67,303 @@ #include #include @@ -145,6 +145,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -261,7 +304,7 @@ /********************************************************************* * * Initialization and readiness waiting. -@@ -87,7 +341,15 @@ static enum { +@@ -87,7 +384,15 @@ static enum { CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ } crng_init __read_mostly = CRNG_EMPTY; static DEFINE_STATIC_KEY_FALSE(crng_is_ready); @@ -277,7 +320,7 @@ /* Various types of waiters for crng_init->CRNG_READY transition. */ static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); static struct fasync_struct *fasync; -@@ -112,7 +374,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis +@@ -112,7 +417,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis */ bool rng_is_initialized(void) { @@ -286,7 +329,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -136,11 +398,11 @@ static void try_to_generate_entropy(void +@@ -136,11 +441,11 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -300,7 +343,7 @@ if (ret) return ret > 0 ? 0 : ret; } -@@ -160,7 +422,7 @@ int __cold execute_with_initialized_rng( +@@ -160,7 +465,7 @@ int __cold execute_with_initialized_rng( int ret = 0; spin_lock_irqsave(&random_ready_notifier.lock, flags); @@ -309,7 +352,7 @@ nb->notifier_call(nb, 0, NULL); else ret = raw_notifier_chain_register((struct raw_notifier_head *)&random_ready_notifier.head, nb); -@@ -169,7 +431,7 @@ int __cold execute_with_initialized_rng( +@@ -169,7 +474,7 @@ int __cold execute_with_initialized_rng( } #define warn_unseeded_randomness() \ @@ -318,22 +361,116 @@ printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ __func__, (void *)_RET_IP_, crng_init) -@@ -402,6 +664,14 @@ static void _get_random_bytes(void *buf, +@@ -402,6 +707,108 @@ static void _get_random_bytes(void *buf, if (!len) return; +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow falls through to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, len) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + first_block_len = min_t(size_t, 32, len); crng_make_state(chacha_state, buf, first_block_len); len -= first_block_len; -@@ -448,6 +718,18 @@ static ssize_t get_random_bytes_user(str +@@ -448,6 +855,18 @@ static ssize_t get_random_bytes_user(str if (unlikely(!iov_iter_count(iter))) return 0; @@ -352,7 +489,7 @@ /* * Immediately overwrite the ChaCha key at index 4 with random * bytes, in case userspace causes copy_to_iter() below to sleep -@@ -524,7 +806,7 @@ type get_random_ ##type(void) \ +@@ -524,7 +943,7 @@ type get_random_ ##type(void) \ \ warn_unseeded_randomness(); \ \ @@ -361,7 +498,7 @@ _get_random_bytes(&ret, sizeof(ret)); \ return ret; \ } \ -@@ -660,6 +942,11 @@ static void mix_pool_bytes(const void *b +@@ -660,6 +1079,11 @@ static void mix_pool_bytes(const void *b { unsigned long flags; @@ -373,7 +510,7 @@ spin_lock_irqsave(&input_pool.lock, flags); _mix_pool_bytes(buf, len); spin_unlock_irqrestore(&input_pool.lock, flags); -@@ -719,7 +1006,11 @@ static void extract_entropy(void *buf, s +@@ -719,7 +1143,11 @@ static void extract_entropy(void *buf, s memzero_explicit(&block, sizeof(block)); } @@ -385,7 +522,7 @@ static void __cold _credit_init_bits(size_t bits) { -@@ -1400,7 +1691,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1400,7 +1828,7 @@ SYSCALL_DEFINE3(getrandom, char __user * if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) return -EINVAL; @@ -394,7 +531,7 @@ if (flags & GRND_NONBLOCK) return -EAGAIN; ret = wait_for_random_bytes(); -@@ -1416,6 +1707,10 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1416,6 +1844,10 @@ SYSCALL_DEFINE3(getrandom, char __user * static __poll_t random_poll(struct file *file, poll_table *wait) { @@ -405,7 +542,7 @@ poll_wait(file, &crng_init_wait, wait); return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; } -@@ -1461,10 +1756,10 @@ static ssize_t urandom_read_iter(struct +@@ -1461,10 +1893,10 @@ static ssize_t urandom_read_iter(struct * Opportunistically attempt to initialize the RNG on platforms that * have fast cycle counters, but don't (for now) require it to succeed. */ @@ -418,7 +555,7 @@ if (!ratelimit_disable && maxwarn <= 0) ++urandom_warning.missed; else if (ratelimit_disable || __ratelimit(&urandom_warning)) { -@@ -1481,7 +1776,7 @@ static ssize_t random_read_iter(struct k +@@ -1481,7 +1913,7 @@ static ssize_t random_read_iter(struct k { int ret; @@ -427,7 +564,7 @@ ((kiocb->ki_flags & (IOCB_NOWAIT | IOCB_NOIO)) || (kiocb->ki_filp->f_flags & O_NONBLOCK))) return -EAGAIN; -@@ -1546,6 +1841,14 @@ static long random_ioctl(struct file *f, +@@ -1546,6 +1978,14 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -444,7 +581,7 @@ crng_reseed(NULL); --- 6.12/include/linux/random.h.dist 2025-01-19 19:03:57.524328914 -0600 +++ 6.12/include/linux/random.h 2025-06-30 12:04:32.801676104 -0500 -@@ -161,4 +161,37 @@ int random_online_cpu(unsigned int cpu); +@@ -161,4 +161,42 @@ int random_online_cpu(unsigned int cpu); extern const struct file_operations random_fops, urandom_fops; #endif @@ -480,5 +617,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/6.15/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v15.patch b/linuxkm/patches/6.15/WOLFSSL_KERNELv6_15_FIPS.patch similarity index 63% rename from linuxkm/patches/6.15/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v15.patch rename to linuxkm/patches/6.15/WOLFSSL_KERNELv6_15_FIPS.patch index 041fc7807cd..c71f263a9f5 100644 --- a/linuxkm/patches/6.15/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-6v15.patch +++ b/linuxkm/patches/6.15/WOLFSSL_KERNELv6_15_FIPS.patch @@ -1,6 +1,6 @@ --- 6.15/drivers/char/random.c.dist 2025-05-27 15:19:59.167827834 -0500 +++ 6.15/drivers/char/random.c 2025-07-11 09:18:33.060365202 -0500 -@@ -67,6 +67,265 @@ +@@ -67,6 +67,308 @@ #include #include @@ -150,6 +150,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -266,7 +309,7 @@ /********************************************************************* * * Initialization and readiness waiting. -@@ -87,7 +346,16 @@ static enum { +@@ -87,7 +389,16 @@ static enum { CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ } crng_init __read_mostly = CRNG_EMPTY; static DEFINE_STATIC_KEY_FALSE(crng_is_ready); @@ -283,7 +326,7 @@ /* Various types of waiters for crng_init->CRNG_READY transition. */ static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); static struct fasync_struct *fasync; -@@ -112,7 +380,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis +@@ -112,7 +423,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis */ bool rng_is_initialized(void) { @@ -292,7 +335,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -136,11 +404,11 @@ static void try_to_generate_entropy(void +@@ -136,11 +447,11 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -306,7 +349,7 @@ if (ret) return ret > 0 ? 0 : ret; } -@@ -160,7 +428,7 @@ int __cold execute_with_initialized_rng( +@@ -160,7 +471,7 @@ int __cold execute_with_initialized_rng( int ret = 0; spin_lock_irqsave(&random_ready_notifier.lock, flags); @@ -315,7 +358,7 @@ nb->notifier_call(nb, 0, NULL); else ret = raw_notifier_chain_register((struct raw_notifier_head *)&random_ready_notifier.head, nb); -@@ -169,7 +437,7 @@ int __cold execute_with_initialized_rng( +@@ -169,7 +480,7 @@ int __cold execute_with_initialized_rng( } #define warn_unseeded_randomness() \ @@ -324,22 +367,116 @@ printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ __func__, (void *)_RET_IP_, crng_init) -@@ -402,6 +670,14 @@ static void _get_random_bytes(void *buf, +@@ -402,6 +713,108 @@ static void _get_random_bytes(void *buf, if (!len) return; +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow continues to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, len) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + first_block_len = min_t(size_t, 32, len); crng_make_state(chacha_state, buf, first_block_len); len -= first_block_len; -@@ -448,6 +724,18 @@ static ssize_t get_random_bytes_user(str +@@ -448,6 +861,18 @@ static ssize_t get_random_bytes_user(str if (unlikely(!iov_iter_count(iter))) return 0; @@ -358,7 +495,7 @@ /* * Immediately overwrite the ChaCha key at index 4 with random * bytes, in case userspace causes copy_to_iter() below to sleep -@@ -524,7 +812,7 @@ type get_random_ ##type(void) \ +@@ -524,7 +949,7 @@ type get_random_ ##type(void) \ \ warn_unseeded_randomness(); \ \ @@ -367,7 +504,7 @@ _get_random_bytes(&ret, sizeof(ret)); \ return ret; \ } \ -@@ -660,6 +948,11 @@ static void mix_pool_bytes(const void *b +@@ -660,6 +1085,11 @@ static void mix_pool_bytes(const void *b { unsigned long flags; @@ -379,7 +516,7 @@ spin_lock_irqsave(&input_pool.lock, flags); _mix_pool_bytes(buf, len); spin_unlock_irqrestore(&input_pool.lock, flags); -@@ -719,7 +1012,13 @@ static void extract_entropy(void *buf, s +@@ -719,7 +1149,13 @@ static void extract_entropy(void *buf, s memzero_explicit(&block, sizeof(block)); } @@ -394,7 +531,7 @@ static void __cold _credit_init_bits(size_t bits) { -@@ -1400,7 +1699,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1400,7 +1836,7 @@ SYSCALL_DEFINE3(getrandom, char __user * if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) return -EINVAL; @@ -403,7 +540,7 @@ if (flags & GRND_NONBLOCK) return -EAGAIN; ret = wait_for_random_bytes(); -@@ -1416,6 +1715,10 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1416,6 +1852,10 @@ SYSCALL_DEFINE3(getrandom, char __user * static __poll_t random_poll(struct file *file, poll_table *wait) { @@ -414,7 +551,7 @@ poll_wait(file, &crng_init_wait, wait); return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; } -@@ -1461,10 +1764,10 @@ static ssize_t urandom_read_iter(struct +@@ -1461,10 +1901,10 @@ static ssize_t urandom_read_iter(struct * Opportunistically attempt to initialize the RNG on platforms that * have fast cycle counters, but don't (for now) require it to succeed. */ @@ -427,7 +564,7 @@ if (!ratelimit_disable && maxwarn <= 0) ++urandom_warning.missed; else if (ratelimit_disable || __ratelimit(&urandom_warning)) { -@@ -1481,7 +1784,7 @@ static ssize_t random_read_iter(struct k +@@ -1481,7 +1921,7 @@ static ssize_t random_read_iter(struct k { int ret; @@ -436,7 +573,7 @@ ((kiocb->ki_flags & (IOCB_NOWAIT | IOCB_NOIO)) || (kiocb->ki_filp->f_flags & O_NONBLOCK))) return -EAGAIN; -@@ -1546,6 +1849,14 @@ static long random_ioctl(struct file *f, +@@ -1546,6 +1986,14 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -453,7 +590,7 @@ crng_reseed(NULL); --- 6.15/include/linux/random.h.dist 2025-05-27 15:20:04.394946820 -0500 +++ 6.15/include/linux/random.h 2025-07-11 07:58:55.505031720 -0500 -@@ -154,4 +154,37 @@ int random_online_cpu(unsigned int cpu); +@@ -154,4 +154,42 @@ int random_online_cpu(unsigned int cpu); extern const struct file_operations random_fops, urandom_fops; #endif @@ -489,5 +626,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/6.16/WOLFSSL_KERNELv6_16_FIPS.patch b/linuxkm/patches/6.16/WOLFSSL_KERNELv6_16_FIPS.patch new file mode 100644 index 00000000000..7896c99086d --- /dev/null +++ b/linuxkm/patches/6.16/WOLFSSL_KERNELv6_16_FIPS.patch @@ -0,0 +1,635 @@ +--- 6.16/drivers/char/random.c.dist 2026-08-21 17:56:07.264344683 -0600 ++++ 6.16/drivers/char/random.c 2026-08-21 17:56:25.256641545 -0600 +@@ -67,6 +67,308 @@ + #include + #include + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ ++#include ++ ++static atomic_long_t random_bytes_cb_owner = ++ ATOMIC_INIT((long)NULL); ++static atomic_t random_bytes_cb_refcnt = ++ ATOMIC_INIT(0); /* 0 if unregistered, 1 if no calls in flight. */ ++static _get_random_bytes_cb_t _get_random_bytes_cb; ++static get_random_bytes_user_cb_t get_random_bytes_user_cb; ++static crng_ready_cb_t crng_ready_cb; ++static mix_pool_bytes_cb_t mix_pool_bytes_cb; ++static credit_init_bits_cb_t credit_init_bits_cb; ++static crng_reseed_cb_t crng_reseed_cb; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers) ++{ ++ if ((new_random_bytes_cb_owner == NULL) || ++ (handlers == NULL) || ++ (handlers->_get_random_bytes == NULL) || ++ (handlers->get_random_bytes_user == NULL)) { ++ return -EINVAL; ++ } ++ ++ /* random_bytes_cb_owner is used to enforce serialization of ++ * wolfssl_register_random_bytes_handlers() and ++ * wolfssl_unregister_random_bytes_handlers(). ++ */ ++ if (atomic_long_cmpxchg(&random_bytes_cb_owner, ++ (long)NULL, ++ (long)new_random_bytes_cb_owner) ++ != (long)NULL) { ++ return -EBUSY; ++ } ++ ++ { ++ int current_random_bytes_cb_refcnt = atomic_read(&random_bytes_cb_refcnt); ++ ++ if (current_random_bytes_cb_refcnt) { ++ pr_err("BUG: random_bytes_cb_refcnt == %d with null random_bytes_cb_owner", ++ current_random_bytes_cb_refcnt); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -EFAULT; ++ } ++ } ++ ++ if (!try_module_get(new_random_bytes_cb_owner)) { ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -ENODEV; ++ } ++ ++ _get_random_bytes_cb = handlers->_get_random_bytes; ++ get_random_bytes_user_cb = handlers->get_random_bytes_user; ++ crng_ready_cb = handlers->crng_ready; ++ mix_pool_bytes_cb = handlers->mix_pool_bytes; ++ credit_init_bits_cb = handlers->credit_init_bits; ++ crng_reseed_cb = handlers->crng_reseed; ++ ++ barrier(); ++ atomic_set_release(&random_bytes_cb_refcnt, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_register_random_bytes_handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void) ++{ ++ int current_random_bytes_cb_refcnt; ++ int n_tries; ++ ++ if (atomic_long_read(&random_bytes_cb_owner) == 0) ++ return -ENODEV; ++ ++ /* we're racing the kernel at large to try to catch random_bytes_cb_refcnt ++ * with no callers in flight -- retry and relax up to 100 times. ++ */ ++ for (n_tries = 0; n_tries < 100; ++n_tries) { ++ current_random_bytes_cb_refcnt = atomic_cmpxchg(&random_bytes_cb_refcnt, 1, 0); ++ if (current_random_bytes_cb_refcnt == 1) ++ break; ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in %s.", ++ current_random_bytes_cb_refcnt, __func__); ++ break; ++ } ++ if (msleep_interruptible(10) != 0) ++ return -EINTR; ++ } ++ if (current_random_bytes_cb_refcnt != 1) { ++ pr_warn("WARNING: %s called with random_bytes_cb_refcnt == %d", __func__, ++ current_random_bytes_cb_refcnt); ++ return -EBUSY; ++ } ++ ++ _get_random_bytes_cb = NULL; ++ get_random_bytes_user_cb = NULL; ++ crng_ready_cb = NULL; ++ mix_pool_bytes_cb = NULL; ++ credit_init_bits_cb = NULL; ++ crng_reseed_cb = NULL; ++ ++ module_put((struct module *)atomic_long_read(&random_bytes_cb_owner)); ++ barrier(); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_unregister_random_bytes_handlers); ++ ++static __always_inline int reserve_random_bytes_cb(void) ++{ ++ int current_random_bytes_cb_refcnt = ++ atomic_read_acquire(&random_bytes_cb_refcnt); ++ ++ if (current_random_bytes_cb_refcnt == 0) ++ return -ENODEV; ++ ++ if (current_random_bytes_cb_refcnt < 0) { ++ pr_err("BUG: random_bytes_cb_refcnt is %d in %s.", ++ current_random_bytes_cb_refcnt, __func__); ++ return -EFAULT; ++ } ++ ++ for (;;) { ++ int orig_random_bytes_cb_refcnt = ++ atomic_cmpxchg( ++ &random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt, ++ current_random_bytes_cb_refcnt + 1); ++ if (orig_random_bytes_cb_refcnt == current_random_bytes_cb_refcnt) ++ return 0; ++ else if (orig_random_bytes_cb_refcnt == 0) ++ return -ENODEV; ++ current_random_bytes_cb_refcnt = orig_random_bytes_cb_refcnt; ++ } ++ ++ __builtin_unreachable(); ++} ++ ++static __always_inline void release_random_bytes_cb(void) ++{ ++ atomic_dec(&random_bytes_cb_refcnt); ++} ++ ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ ++static inline int call__get_random_bytes_cb(void *buf, size_t len) ++{ ++ int ret; ++ ++ if (_get_random_bytes_cb == NULL) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = _get_random_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline ssize_t call_get_random_bytes_user_cb(struct iov_iter *iter) ++{ ++ ssize_t ret; ++ ++ if (get_random_bytes_user_cb == NULL) ++ return -ECANCELED; ++ ++ ret = (ssize_t)reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = get_random_bytes_user_cb(iter); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline bool call_crng_ready_cb(void) ++{ ++ bool ret; ++ ++ /* Null crng_ready_cb signifies that the DRBG is always ready, i.e. that if ++ * called, it will always have or obtain sufficient entropy to fulfill the ++ * call. ++ */ ++ if (crng_ready_cb == NULL) ++ return 1; ++ ++ if (reserve_random_bytes_cb() != 0) ++ return 0; ++ ++ ret = crng_ready_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_mix_pool_bytes_cb(const void *buf, size_t len) ++{ ++ int ret; ++ ++ if (mix_pool_bytes_cb == NULL) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = mix_pool_bytes_cb(buf, len); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_credit_init_bits_cb(size_t bits) ++{ ++ int ret; ++ ++ if (credit_init_bits_cb == NULL) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = credit_init_bits_cb(bits); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++static inline int call_crng_reseed_cb(void) ++{ ++ int ret; ++ ++ if (crng_reseed_cb == NULL) ++ return -ENODEV; ++ ++ ret = reserve_random_bytes_cb(); ++ if (ret) ++ return ret; ++ ++ ret = crng_reseed_cb(); ++ ++ release_random_bytes_cb(); ++ ++ return ret; ++} ++ ++#endif /* WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS */ ++ + /********************************************************************* + * + * Initialization and readiness waiting. +@@ -87,7 +389,16 @@ static enum { + CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ + } crng_init __read_mostly = CRNG_EMPTY; + static DEFINE_STATIC_KEY_FALSE(crng_is_ready); ++ + #define crng_ready() (static_branch_likely(&crng_is_ready) || crng_init >= CRNG_READY) ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (atomic_read(&random_bytes_cb_refcnt) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (atomic_read(&random_bytes_cb_refcnt) ? \ ++ (call_crng_ready_cb() || crng_ready()) : crng_ready()) ++#else ++ #define crng_ready_maybe_cb() crng_ready() ++#endif ++ + /* Various types of waiters for crng_init->CRNG_READY transition. */ + static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); + static struct fasync_struct *fasync; +@@ -112,7 +423,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis + */ + bool rng_is_initialized(void) + { +- return crng_ready(); ++ return crng_ready_maybe_cb(); + } + EXPORT_SYMBOL(rng_is_initialized); + +@@ -136,11 +447,11 @@ static void try_to_generate_entropy(void + */ + int wait_for_random_bytes(void) + { +- while (!crng_ready()) { ++ while (!crng_ready_maybe_cb()) { + int ret; + + try_to_generate_entropy(); +- ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready(), HZ); ++ ret = wait_event_interruptible_timeout(crng_init_wait, crng_ready_maybe_cb(), HZ); + if (ret) + return ret > 0 ? 0 : ret; + } +@@ -160,7 +471,7 @@ int __cold execute_with_initialized_rng( + int ret = 0; + + spin_lock_irqsave(&random_ready_notifier.lock, flags); +- if (crng_ready()) ++ if (crng_ready_maybe_cb()) + nb->notifier_call(nb, 0, NULL); + else + ret = raw_notifier_chain_register((struct raw_notifier_head *)&random_ready_notifier.head, nb); +@@ -169,7 +480,7 @@ int __cold execute_with_initialized_rng( + } + + #define warn_unseeded_randomness() \ +- if (IS_ENABLED(CONFIG_WARN_ALL_UNSEEDED_RANDOM) && !crng_ready()) \ ++ if (IS_ENABLED(CONFIG_WARN_ALL_UNSEEDED_RANDOM) && !crng_ready_maybe_cb()) \ + printk_deferred(KERN_NOTICE "random: %s called from %pS with crng_init=%d\n", \ + __func__, (void *)_RET_IP_, crng_init) + +@@ -402,6 +713,108 @@ static void _get_random_bytes(void *buf, + if (!len) + return; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. ++ */ ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } ++#endif ++ + first_block_len = min_t(size_t, 32, len); + crng_make_state(&chacha_state, buf, first_block_len); + len -= first_block_len; +@@ -448,6 +861,18 @@ static ssize_t get_random_bytes_user(str + if (unlikely(!iov_iter_count(iter))) + return 0; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ { ++ ssize_t cb_ret = call_get_random_bytes_user_cb(iter); ++ /* If the callback returns -ECANCELED, that signals that iter is ++ * still intact, and flow can safely continue to the native ++ * implementation. ++ */ ++ if (cb_ret != -ECANCELED) ++ return cb_ret; ++ } ++#endif ++ + /* + * Immediately overwrite the ChaCha key at index 4 with random + * bytes, in case userspace causes copy_to_iter() below to sleep +@@ -525,7 +950,7 @@ type get_random_ ##type(void) \ + \ + warn_unseeded_randomness(); \ + \ +- if (!crng_ready()) { \ ++ if (!crng_ready_maybe_cb()) { \ + _get_random_bytes(&ret, sizeof(ret)); \ + return ret; \ + } \ +@@ -661,6 +1086,11 @@ static void mix_pool_bytes(const void *b + { + unsigned long flags; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ (void)call_mix_pool_bytes_cb(buf, len); ++ /* continue to mix into native pool too. */ ++#endif ++ + spin_lock_irqsave(&input_pool.lock, flags); + _mix_pool_bytes(buf, len); + spin_unlock_irqrestore(&input_pool.lock, flags); +@@ -720,7 +1150,13 @@ static void extract_entropy(void *buf, s + memzero_explicit(&block, sizeof(block)); + } + +-#define credit_init_bits(bits) if (!crng_ready()) _credit_init_bits(bits) ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define credit_init_bits(bits) do { (void)call_credit_init_bits_cb(bits); \ ++ if (!crng_ready()) \ ++ _credit_init_bits(bits); } while (0) ++#else ++ #define credit_init_bits(bits) do { if (!crng_ready()) _credit_init_bits(bits); } while (0) ++#endif + + static void __cold _credit_init_bits(size_t bits) + { +@@ -1402,7 +1838,7 @@ SYSCALL_DEFINE3(getrandom, char __user * + if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) + return -EINVAL; + +- if (!crng_ready() && !(flags & GRND_INSECURE)) { ++ if (!crng_ready_maybe_cb() && !(flags & GRND_INSECURE)) { + if (flags & GRND_NONBLOCK) + return -EAGAIN; + ret = wait_for_random_bytes(); +@@ -1418,6 +1854,10 @@ SYSCALL_DEFINE3(getrandom, char __user * + + static __poll_t random_poll(struct file *file, poll_table *wait) + { ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ if (crng_ready_by_cb()) ++ return EPOLLIN | EPOLLRDNORM; ++#endif + poll_wait(file, &crng_init_wait, wait); + return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; + } +@@ -1463,10 +1903,10 @@ static ssize_t urandom_read_iter(struct + * Opportunistically attempt to initialize the RNG on platforms that + * have fast cycle counters, but don't (for now) require it to succeed. + */ +- if (!crng_ready()) ++ if (!crng_ready_maybe_cb()) + try_to_generate_entropy(); + +- if (!crng_ready()) { ++ if (!crng_ready_maybe_cb()) { + if (!ratelimit_disable && maxwarn <= 0) + ratelimit_state_inc_miss(&urandom_warning); + else if (ratelimit_disable || __ratelimit(&urandom_warning)) { +@@ -1483,7 +1923,7 @@ static ssize_t random_read_iter(struct k + { + int ret; + +- if (!crng_ready() && ++ if (!crng_ready_by_cb() && + ((kiocb->ki_flags & (IOCB_NOWAIT | IOCB_NOIO)) || + (kiocb->ki_filp->f_flags & O_NONBLOCK))) + return -EAGAIN; +@@ -1548,6 +1988,14 @@ static long random_ioctl(struct file *f, + case RNDRESEEDCRNG: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ /* continue to reseed native crng too. */ ++ if (call_crng_reseed_cb() == 0) { ++ if (crng_ready()) ++ crng_reseed(NULL); ++ return 0; ++ } ++#endif + if (!crng_ready()) + return -ENODATA; + crng_reseed(NULL); +--- 6.16/include/linux/random.h.dist 2026-08-21 17:56:07.265185014 -0600 ++++ 6.16/include/linux/random.h 2026-08-21 17:56:07.270940080 -0600 +@@ -154,4 +154,42 @@ int random_online_cpu(unsigned int cpu); + extern const struct file_operations random_fops, urandom_fops; + #endif + ++#ifndef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS 1 ++#endif ++ ++typedef int (*_get_random_bytes_cb_t)(void *buf, size_t len); ++struct iov_iter; ++/* kernels >= 5.17.0 use get_random_bytes_user() */ ++typedef ssize_t (*get_random_bytes_user_cb_t)(struct iov_iter *iter); ++/* kernels < 5.17.0 use extract_crng_user(), though some LTS kernels, ++ * e.g. 5.10.236, have the 5.17+ architecture backported. ++ */ ++typedef ssize_t (*extract_crng_user_cb_t)(void __user *buf, size_t nbytes); ++typedef bool (*crng_ready_cb_t)(void); ++typedef int (*mix_pool_bytes_cb_t)(const void *buf, size_t len); ++typedef int (*credit_init_bits_cb_t)(size_t bits); ++typedef int (*crng_reseed_cb_t)(void); ++ ++struct wolfssl_linuxkm_random_bytes_handlers { ++ _get_random_bytes_cb_t _get_random_bytes; ++ get_random_bytes_user_cb_t get_random_bytes_user; ++ extract_crng_user_cb_t extract_crng_user; ++ crng_ready_cb_t crng_ready; ++ mix_pool_bytes_cb_t mix_pool_bytes; ++ credit_init_bits_cb_t credit_init_bits; ++ crng_reseed_cb_t crng_reseed; ++}; ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); ++ + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/7.0/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v0.patch b/linuxkm/patches/7.0/WOLFSSL_KERNELv7_0_FIPS.patch similarity index 63% rename from linuxkm/patches/7.0/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v0.patch rename to linuxkm/patches/7.0/WOLFSSL_KERNELv7_0_FIPS.patch index 5c421d68a99..d9417a1fcdd 100644 --- a/linuxkm/patches/7.0/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v0.patch +++ b/linuxkm/patches/7.0/WOLFSSL_KERNELv7_0_FIPS.patch @@ -1,6 +1,6 @@ --- linux-next-9999/drivers/char/random.c.dist 2026-02-27 20:38:28.210284927 +0000 +++ linux-next-9999/drivers/char/random.c 2026-02-27 20:39:23.770059819 +0000 -@@ -67,6 +67,265 @@ +@@ -67,6 +67,308 @@ #include #include @@ -150,6 +150,49 @@ + atomic_dec(&random_bytes_cb_refcnt); +} + ++/* Requests a registered module handler declined WOLFSSL_LINUXKM_GRB_TRIES ++ * times. READING 0 IS CORRECT AND EXPECTED on any machine that is up: the ++ * increment below is immediately followed by panic(), so a live system has ++ * by definition never taken that path. A zero here is not a broken ++ * counter. It is exported so a consumer can assert on it, and so that a ++ * future change which makes this path survivable stays measurable rather ++ * than only fatal. See the note at the hook site in _get_random_bytes(). ++ */ ++#define WOLFSSL_LINUXKM_GRB_TRIES 3 ++static atomic64_t wolfssl_linuxkm_grb_unserved = ATOMIC64_INIT(0); ++ ++u64 wolfssl_linuxkm_random_bytes_unserved(void) ++{ ++ return (u64)atomic64_read(&wolfssl_linuxkm_grb_unserved); ++} ++EXPORT_SYMBOL_GPL(wolfssl_linuxkm_random_bytes_unserved); ++ ++/* in_hardirq() is the 5.13-and-later name for what earlier kernels call ++ * in_irq(); both expand to hardirq_count() in , and both ++ * are macros, so #ifdef selects correctly across every kernel this series ++ * targets. in_nmi(), in_serving_softirq() and in_interrupt() are spelled ++ * the same on all of them. ++ */ ++#ifdef in_hardirq ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_hardirq() ++#else ++#define WOLFSSL_LINUXKM_IN_HARDIRQ() in_irq() ++#endif ++ ++/* Named in the WARN and in the panic: which context lost service is the ++ * first thing anyone reading the crash will want. ++ */ ++static const char *wolfssl_linuxkm_grb_ctx(void) ++{ ++ if (in_nmi()) ++ return "NMI"; ++ if (WOLFSSL_LINUXKM_IN_HARDIRQ()) ++ return "hardirq"; ++ if (in_serving_softirq()) ++ return "softirq"; ++ return "task"; ++} ++ +static inline int call__get_random_bytes_cb(void *buf, size_t len) +{ + int ret; @@ -266,7 +309,7 @@ /********************************************************************* * * Initialization and readiness waiting. -@@ -87,7 +346,16 @@ static enum { +@@ -87,7 +389,16 @@ static enum { CRNG_READY = 2 /* Fully initialized with POOL_READY_BITS collected */ } crng_init __read_mostly = CRNG_EMPTY; static DEFINE_STATIC_KEY_FALSE(crng_is_ready); @@ -283,7 +326,7 @@ /* Various types of waiters for crng_init->CRNG_READY transition. */ static DECLARE_WAIT_QUEUE_HEAD(crng_init_wait); static struct fasync_struct *fasync; -@@ -111,7 +379,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis +@@ -111,7 +422,7 @@ MODULE_PARM_DESC(ratelimit_disable, "Dis */ bool rng_is_initialized(void) { @@ -292,7 +335,7 @@ } EXPORT_SYMBOL(rng_is_initialized); -@@ -135,11 +403,11 @@ static void try_to_generate_entropy(void +@@ -135,11 +446,11 @@ static void try_to_generate_entropy(void */ int wait_for_random_bytes(void) { @@ -306,7 +349,7 @@ if (ret) return ret > 0 ? 0 : ret; } -@@ -159,7 +427,7 @@ int __cold execute_with_initialized_rng( +@@ -159,7 +470,7 @@ int __cold execute_with_initialized_rng( int ret = 0; spin_lock_irqsave(&random_ready_notifier.lock, flags); @@ -315,22 +358,116 @@ nb->notifier_call(nb, 0, NULL); else ret = raw_notifier_chain_register((struct raw_notifier_head *)&random_ready_notifier.head, nb); -@@ -395,6 +663,14 @@ static void _get_random_bytes(void *buf, +@@ -395,6 +706,108 @@ static void _get_random_bytes(void *buf, if (!len) return; +#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS -+ /* If call__get_random_bytes_cb() doesn't succeed, flow continues to -+ * the native implementation. _get_random_bytes() must succeed. ++ /* THE MODULE IS THE SERVICE. While a handler is registered, this does ++ * NOT fall through to the kernel's own generator when the module ++ * declines. Bytes generated outside the module are not validated ++ * output, and the caller cannot tell which generator answered, because ++ * _get_random_bytes() returns void and so has no error channel. ++ * ++ * A decline is normally transient -- a leaf wanting a reseed it cannot ++ * take in the calling context -- so the module is asked again, up to ++ * WOLFSSL_LINUXKM_GRB_TRIES times. Every attempt is non-blocking: ++ * cpu_relax() is a scheduling hint, not a sleep and not a lock, so the ++ * loop is legal with interrupts off and in NMI. Three attempts, not ++ * more: a retry re-runs the whole failure path with interrupts off, ++ * which is most expensive exactly when something is already wrong. ++ * ++ * ON EXHAUSTION THIS PANICS. Three behaviours are available and all ++ * three break something; this one is chosen because it is the only one ++ * that cannot be mistaken for working. ++ * ++ * - Falling through to the kernel's generator takes ++ * spin_lock_irqsave() on the CRNG. From NMI that is a ++ * self-deadlock if the interrupted context on this CPU already ++ * holds that lock. The resulting hang has been observed on this ++ * path, not merely reasoned about. It also silently substitutes ++ * unvalidated bytes for module output. ++ * - Returning without filling buf breaks the contract documented ++ * above these interfaces -- that they "will return the requested ++ * number of random bytes into the given buffer" -- and breaks it ++ * INVISIBLY: the caller proceeds with stack residue and may use it ++ * as key material. Silent is the one property that cannot be ++ * accepted here. ++ * - Panicking is loud, diagnosable, and happens at a point we chose. ++ * ++ * Entering a FIPS error state instead is not a gentler option: this ++ * hook is system-wide, so a module that stops answering takes the ++ * whole machine's randomness with it -- a slower, messier death that ++ * is harder to diagnose than a panic with a message. ++ * ++ * THE KERNEL HAS NO NMI STORY FOR THIS INTERFACE AT ALL. There is no ++ * reference to in_nmi() anywhere in this file, no NMI-safe variant in ++ * , and crypto/drbg.c -- the kernel's own SP 800-90A ++ * DRBG -- takes a mutex, so upstream never serves NMI from a 90A DRBG ++ * either. Where the kernel does have to work in NMI it either fails ++ * fast rather than spinning (kernel/bpf/ringbuf.c, which trylocks and ++ * gives up when in_nmi()) or keeps a separate NMI-dedicated data path ++ * (___pcpu_freelist_push_nmi(), printk's per-CPU printk_count_nmi). ++ * The module's per-CPU NMI leaves are that second idiom, which is why ++ * 1,500,000 measured NMIs produced 0 declines. ++ * ++ * So this path is unreachable BY DESIGN, not merely unlikely, and ++ * reaching it means an invariant has already been violated. That is ++ * precisely when a panic is the correct response rather than a ++ * heuristic. The counter is kept so that the event is measurable if a ++ * future change ever makes it reachable, and WARN_ONCE() runs first so ++ * that a stack trace naming the caller is in the log ahead of the ++ * panic. ++ * ++ * With NO handler registered there is nothing to violate: -ENODEV ends ++ * the loop and flow falls through to the kernel exactly as before. ++ * That is the window before the module is inserted, and it is the only ++ * path out of here that reaches the kernel's generator. + */ -+ if (call__get_random_bytes_cb(buf, len) == 0) -+ return; ++ { ++ int wolfssl_grb_try; ++ int wolfssl_grb_ret = -ENODEV; ++ ++ for (wolfssl_grb_try = 0; ++ wolfssl_grb_try < WOLFSSL_LINUXKM_GRB_TRIES; ++ wolfssl_grb_try++) { ++ wolfssl_grb_ret = ++ call__get_random_bytes_cb(buf, len); ++ if (wolfssl_grb_ret == 0) ++ return; ++ /* No handler registered: not a decline. */ ++ if (wolfssl_grb_ret == -ENODEV) ++ break; ++ cpu_relax(); ++ } ++ ++ if (wolfssl_grb_ret != -ENODEV) { ++ atomic64_inc(&wolfssl_linuxkm_grb_unserved); ++ WARN_ONCE(1, ++ "wolfssl: get_random_bytes() declined %d times" ++ " by the registered module in %s context\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ panic("wolfSSL FIPS: get_random_bytes() could not be" ++ " serviced by the wolfCrypt module after %d" ++ " attempts in %s context. The module refuses both" ++ " to fall through to the kernel's own generator" ++ " (its output is not validated, and reaching it from" ++ " NMI can self-deadlock on the CRNG lock) and to" ++ " return an unfilled buffer (the caller would use" ++ " stack residue as key material). This path is" ++ " unreachable by design; reaching it means a wolfSSL" ++ " RBGC invariant is already broken.\n", ++ WOLFSSL_LINUXKM_GRB_TRIES, ++ wolfssl_linuxkm_grb_ctx()); ++ } ++ } +#endif + first_block_len = min_t(size_t, 32, len); crng_make_state(&chacha_state, buf, first_block_len); len -= first_block_len; -@@ -440,6 +716,18 @@ static ssize_t get_random_bytes_user(str +@@ -440,6 +853,18 @@ static ssize_t get_random_bytes_user(str if (unlikely(!iov_iter_count(iter))) return 0; @@ -349,7 +486,7 @@ /* * Immediately overwrite the ChaCha key at index 4 with random * bytes, in case userspace causes copy_to_iter() below to sleep -@@ -515,7 +803,7 @@ type get_random_ ##type(void) \ +@@ -515,7 +940,7 @@ type get_random_ ##type(void) \ struct batch_ ##type *batch; \ unsigned long next_gen; \ \ @@ -358,7 +495,7 @@ _get_random_bytes(&ret, sizeof(ret)); \ return ret; \ } \ -@@ -651,6 +939,11 @@ static void mix_pool_bytes(const void *b +@@ -651,6 +1076,11 @@ static void mix_pool_bytes(const void *b { unsigned long flags; @@ -370,7 +507,7 @@ spin_lock_irqsave(&input_pool.lock, flags); _mix_pool_bytes(buf, len); spin_unlock_irqrestore(&input_pool.lock, flags); -@@ -710,7 +1003,13 @@ static void extract_entropy(void *buf, s +@@ -710,7 +1140,13 @@ static void extract_entropy(void *buf, s memzero_explicit(&block, sizeof(block)); } @@ -385,7 +522,7 @@ static void __cold _credit_init_bits(size_t bits) { -@@ -1396,7 +1695,7 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1396,7 +1832,7 @@ SYSCALL_DEFINE3(getrandom, char __user * if ((flags & (GRND_INSECURE | GRND_RANDOM)) == (GRND_INSECURE | GRND_RANDOM)) return -EINVAL; @@ -394,7 +531,7 @@ if (flags & GRND_NONBLOCK) return -EAGAIN; ret = wait_for_random_bytes(); -@@ -1412,6 +1711,10 @@ SYSCALL_DEFINE3(getrandom, char __user * +@@ -1412,6 +1848,10 @@ SYSCALL_DEFINE3(getrandom, char __user * static __poll_t random_poll(struct file *file, poll_table *wait) { @@ -405,7 +542,7 @@ poll_wait(file, &crng_init_wait, wait); return crng_ready() ? EPOLLIN | EPOLLRDNORM : EPOLLOUT | EPOLLWRNORM; } -@@ -1457,10 +1760,10 @@ static ssize_t urandom_read_iter(struct +@@ -1457,10 +1897,10 @@ static ssize_t urandom_read_iter(struct * Opportunistically attempt to initialize the RNG on platforms that * have fast cycle counters, but don't (for now) require it to succeed. */ @@ -418,7 +555,7 @@ if (!ratelimit_disable && maxwarn <= 0) ratelimit_state_inc_miss(&urandom_warning); else if (ratelimit_disable || __ratelimit(&urandom_warning)) { -@@ -1477,7 +1780,7 @@ static ssize_t random_read_iter(struct k +@@ -1477,7 +1917,7 @@ static ssize_t random_read_iter(struct k { int ret; @@ -427,7 +564,7 @@ ((kiocb->ki_flags & (IOCB_NOWAIT | IOCB_NOIO)) || (kiocb->ki_filp->f_flags & O_NONBLOCK))) return -EAGAIN; -@@ -1542,6 +1845,14 @@ static long random_ioctl(struct file *f, +@@ -1542,6 +1982,14 @@ static long random_ioctl(struct file *f, case RNDRESEEDCRNG: if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -444,7 +581,7 @@ crng_reseed(NULL); --- linux-next-9999/include/linux/random.h.dist 2026-02-27 20:38:33.018159190 +0000 +++ linux-next-9999/include/linux/random.h 2026-02-27 20:39:23.770406607 +0000 -@@ -139,4 +139,37 @@ int random_online_cpu(unsigned int cpu); +@@ -139,4 +139,42 @@ int random_online_cpu(unsigned int cpu); extern const struct file_operations random_fops, urandom_fops; #endif @@ -480,5 +617,10 @@ + const struct wolfssl_linuxkm_random_bytes_handlers *handlers); + +int wolfssl_linuxkm_unregister_random_bytes_handlers(void); ++ ++/* Count of get_random_bytes() requests NOT served by a registered module ++ * handler. Must be 0 whenever a handler is registered. ++ */ ++u64 wolfssl_linuxkm_random_bytes_unserved(void); + #endif /* _LINUX_RANDOM_H */ diff --git a/linuxkm/patches/README.md b/linuxkm/patches/README.md new file mode 100644 index 00000000000..3a0d060f99f --- /dev/null +++ b/linuxkm/patches/README.md @@ -0,0 +1,343 @@ +# Kernel patches for `WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS` + +Each subdirectory is a kernel version and holds one patch against that version's +`drivers/char/random.c` and `include/linux/random.h`. The patch adds the +callback hooks that let the wolfSSL kernel module service the kernel's +`get_random_bytes()` family, so that randomness the kernel hands out is +generated inside the FIPS module boundary rather than by the kernel's own +generator. + +## An unlisted kernel version is NOT an unsupported one + +There are seventeen directories here and thirty-four supported kernel versions. +The seventeen are **bases**, not the list of what is supported. Most supported +versions are served by a patch derived from a base belonging to a different +version -- often a different series. If your version has no directory of its +own, look it up in the coverage table below; it is almost certainly there. + +The mapping is many-to-one and it is **not** nearest-by-version. See "Why the +numbers do not order the way you expect" before assuming which base serves you. + +## Coverage: every supported version, and the patch that serves it + +Two columns carry the evidence, and they are deliberately separate: + +| column | meaning | +|---|---| +| `source` | Where the patch FILE lives -- provenance only, NOT a confidence level. `SHIPPED` means a directory named for this series serves it directly; `DERIVED` means a base belonging to another series serves it. **A DERIVED row is tested exactly as hard as a SHIPPED one** -- see the `tested` column, which is what says whether it was verified. | +| `tested` | `TESTED` -- on 2026-08-22 this row was measured end to end: the patch applied at `--fuzz=0`, `drivers/char/random.o` was built from the patched tree with no diagnostic the pristine tree did not also emit, the symbols the module binds to were present AND exported, and a consumer translation unit using those declarations the way `linuxkm/module_hooks.c` does compiled. 39 of 39 rows passed all four. Controls: a wrong-side base fails to compile; a deleted `EXPORT_SYMBOL_GPL` still COMPILES CLEAN while reading NOT-EXPORTED, which is why 'it compiles' is not the test. | + +**Every row applies at `--fuzz=0`.** That was re-measured for all thirty-four +supported versions against all seventeen bases -- 578 cells -- so there are no +`NOT COVERED` rows. + +**Every row has now been compiled**, on x86_64 `defconfig`, each against its own +pristine baseline build so that a diagnostic is only counted when the patch is +what introduced it. Compiling matters separately from applying: a base from the +wrong side of the 6.2 `crng_reseed()` boundary applies at `--fuzz=3` and then +fails with `too few arguments to function 'crng_reseed'`, and a fuzzed hunk can +land in a function where its variables do not exist. + +Compiling is not the end of it either, which is why the column also covers the +symbols. A patched `random.c` can compile perfectly and still export a name the +module does not call, in which case `libwolfssl.ko` loads against nothing -- +`insmod` fails with `Unknown symbol wolfssl_linuxkm_register_random_bytes_handlers +(err -2)`. Both halves are checked per row. + +**`--fuzz=0` is the bar here, and it is not the bar the harness uses.** +`make-fips-rbgc-patch.sh` fuzzes at 3 unconditionally. Fuzz drops context lines +until a hunk matches *something*, so a hunk can land in the wrong function +entirely and still be reported as applied. That is not hypothetical: the `5.15` +base on a 5.17.11 tree puts its `mix_pool_bytes()` hunk inside `fast_mix()` at +`--fuzz=3`, and the kernel then will not build. + +## Applying a patch to a kernel + +```sh +./patch-kernel.sh /path/to/linux-6.6.99 # picks the tested base for you +./patch-kernel.sh --dry-run /path/to/linux-7.1.9 # check first, write nothing +./patch-kernel.sh /path/to/linux-6.6.99 6.12 # force a base, if you must +``` + +With no base argument the script reads `VERSION`/`PATCHLEVEL`/`SUBLEVEL` out of +the kernel's own `Makefile` and looks the version up in the table below, so you +get the base that was **tested** for your version rather than the one whose +directory name looks closest. + +**It applies at `--fuzz=0` and refuses if that fails. That refusal is the +feature.** `patch(1)` will place a hunk in the wrong function when context has +drifted, and exit 0 while doing it. That is not hypothetical here: applying the +`5.15` base to a 5.17 tree at `--fuzz=3` put the `mix_pool_bytes()` hunk inside +`fast_mix()`, and the tree then failed to build with +`random.c:915: error: 'in' undeclared`. A marker count cannot detect this -- +the count is identical either way. If the script refuses, your version needs a +base of its own; raising the fuzz is never the answer. + +Doing it by hand is the same two commands: + +```sh +patch -p1 -d --dry-run --fuzz=0 < /WOLFSSL_KERNELv_FIPS.patch +patch -p1 -d --fuzz=0 < /WOLFSSL_KERNELv_FIPS.patch +``` + +If the kernel is left unpatched, nothing silently degrades: the module fails to +load with `Unknown symbol wolfssl_linuxkm_register_random_bytes_handlers` +rather than running without the hook. See `linuxkm/README.md`. + +| version | verified at | patch that serves it | source | tested | +|---|---|---|---|---| +| 5.6 | 5.6.19 | `5.6/WOLFSSL_KERNELv5_6_FIPS.patch` | SHIPPED | TESTED | +| 5.7 | 5.7.19 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | DERIVED | TESTED | +| 5.8 | 5.8.18 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | DERIVED | TESTED | +| 5.9 | 5.9.16 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | DERIVED | TESTED | +| 5.10 | 5.10.17 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | SHIPPED | TESTED | +| 5.10 | 5.10.265 | `5.10.236/WOLFSSL_KERNELv5_10_236_FIPS.patch` | SHIPPED | TESTED | +| 5.11 | 5.11.22 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | DERIVED | TESTED | +| 5.12 | 5.12.19 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | DERIVED | TESTED | +| 5.13 | 5.13.19 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | DERIVED | TESTED | +| 5.15 | 5.15 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | SHIPPED | TESTED | +| 5.15 | 5.15.216 | `5.17-ubuntu-jammy-tegra/WOLFSSL_KERNELv5_17_tegra_FIPS.patch` | DERIVED | TESTED | +| 5.16 | 5.16.20 | `5.16/WOLFSSL_KERNELv5_16_FIPS.patch` | SHIPPED | TESTED | +| 5.17 | 5.17.11 | `5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch` | SHIPPED | TESTED | +| 5.17 | 5.17.13 | `5.17/WOLFSSL_KERNELv5_17_FIPS.patch` | SHIPPED | TESTED | +| 5.17 | 5.17.15 | `5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch` | SHIPPED | TESTED | +| 5.18 | 5.18.19 | `5.18/WOLFSSL_KERNELv5_18_FIPS.patch` | SHIPPED | TESTED | +| 5.19 | 5.19.17 | `6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch` | DERIVED | TESTED | +| 6.0 | 6.0.19 | `6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch` | DERIVED | TESTED | +| 6.1 | 6.1.183 | `6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch` | SHIPPED | TESTED | +| 6.2 | 6.2.16 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | +| 6.4 | 6.4.16 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | +| 6.5 | 6.5.13 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | +| 6.6 | 6.6.99 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | +| 6.6 | 6.6.152 | `6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch` | DERIVED | TESTED | +| 6.7 | 6.7.12 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | +| 6.8 | 6.8.12 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | +| 6.9 | 6.9.12 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | +| 6.10 | 6.10.14 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | +| 6.11 | 6.11.11 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | +| 6.12 | 6.12.104 | `6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch` | SHIPPED | TESTED | +| 6.13 | 6.13.12 | `6.15/WOLFSSL_KERNELv6_15_FIPS.patch` | DERIVED | TESTED | +| 6.14 | 6.14.11 | `6.15/WOLFSSL_KERNELv6_15_FIPS.patch` | DERIVED | TESTED | +| 6.15 | 6.15.11 | `6.15/WOLFSSL_KERNELv6_15_FIPS.patch` | SHIPPED | TESTED | +| 6.16 | 6.16.12 | `6.16/WOLFSSL_KERNELv6_16_FIPS.patch` | SHIPPED | TESTED | +| 6.17 | 6.17.13 | `6.16/WOLFSSL_KERNELv6_16_FIPS.patch` | DERIVED | TESTED | +| 6.18 | 6.18.45 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | DERIVED | TESTED | +| 6.19 | 6.19.14 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | DERIVED | TESTED | +| 7.0 | 7.0.14 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | SHIPPED | TESTED | +| 7.1 | 7.1.9 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | DERIVED | TESTED | + +One of the seventeen directories is never named by a row above, because the +version it was authored against is not in the supported list: + +| directory | serves | +|---|---| +| `5.14.0-570.58.1.el9_6` | the RHEL 9.6 vendor kernel | + +The 5.17 series occupies three rows rather than one, because it changes API +shape twice inside the series. See "A series can change shape mid-series". + +### The row is per patchlevel, not per series + +The table states the patchlevel each row was verified at, because the answer +**changes within a series**. Do not read a row as covering every `.y` of that +series. + +`5.10.17` does not apply to 5.10.265 and `5.15` does not apply to 5.15.216; +both of those series took the 5.18 random.c rewrite as a stable backport +partway through their lifetime. + +### A series can change shape mid-series + +Two series change `random.c` API shape at a known patchlevel, so each needs more +than one patch. Every seam was measured by applying at `--fuzz=0` to every +patchlevel across the boundary, not inferred: + +| series | patchlevels | patch | +|---|---|---| +| 5.10 | 5.10.17 and earlier* | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | +| 5.10 | up to 5.10.265* | `5.10.236/WOLFSSL_KERNELv5_10_236_FIPS.patch` | +| 5.15 | 5.15 and earlier* | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | +| 5.15 | up to 5.15.216* | `5.17-ubuntu-jammy-tegra/WOLFSSL_KERNELv5_17_tegra_FIPS.patch` | +| 5.17 | 5.17.0 - 5.17.11 | `5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch` | +| 5.17 | 5.17.12 - 5.17.13 | `5.17/WOLFSSL_KERNELv5_17_FIPS.patch` | +| 5.17 | 5.17.14 - 5.17.15 | `5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch` | +| 6.6 | 6.6.0 - 6.6.127 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | +| 6.6 | 6.6.128 - 6.6.152+ | `6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch` | +| 6.12 | 6.12.0 - 6.12.74 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | +| 6.12 | 6.12.75 - 6.12.104+ | `6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch` | + +`*` **5.10 and 5.15: the exact patchlevel at which each flips is NOT measured.** +What is measured is that both ends need different bases and each refuses the +other's, at `--fuzz=0`: + +| tree | `5.10.17` | `5.10.236` | +|---|---|---| +| 5.10.17 | applies | refuses | +| 5.10.265 | refuses | applies | + +| tree | `5.15` | `5.17-ubuntu-jammy-tegra` | +|---|---|---| +| 5.15 | applies | refuses | +| 5.15.216 | refuses | applies | + +If your 5.10 or 5.15 patchlevel is between those points, try both and take the +one that applies with **no** fuzz. Do not take the one that applies with fuzz. + +**5.10 and 5.15 flip too**, for the same reason 6.6 does: the 5.18 `random.c` +rewrite was backported into both LTS branches partway through their lifetime. + +**6.12 flips at 6.12.75.** Stable backported "Remove WARN_ALL_UNSEEDED_RANDOM +kernel config option" (Linus Torvalds, 2026-03-04, mainline 6.18), which deletes +the `warn_unseeded_randomness()` macro and its two call sites. That breaks two +hunks of the `6.12` patch: hunk 6 *edits* the macro, and hunk 9 carries one of +its calls in context. Fuzz rescues hunk 9 but never hunk 6, because the lines it +edits no longer exist -- which is why `--fuzz=3` does not help. The `6.12.75` +patch is the `6.12` patch with hunk 6 dropped and hunk 9's context refreshed; +the `crng_ready()` -> `crng_ready_maybe_cb()` substitutions are unchanged and +land at the same nine sites. + +The same backport reached **6.6 at 6.6.128**, which is why `6.12.75` -- not +`7.0` -- is the closest fit for current 6.6 as well. **So 6.6 has the same seam +6.12 has**, and both sides of it are measured: on 6.6.99 the `6.12` patch +applies at `--fuzz=0` and `6.12.75` fails hunk 8; on 6.6.152 it is the other way +round. 6.6 is LTS and most 6.6 deployments are below 6.6.128, so this is the +seam a reader is most likely to land on. + +**5.17 flips twice.** 5.17.0-5.17.11 predate the 5.18 `random.c` rewrite; +5.17.12 took the rewrite *and* the `crng_is_ready` static key as a stable +backport; 5.17.14 then **reverted the static key** while keeping the rewrite. So +5.17 runs pre-rewrite -> static key -> no static key, and the middle window is +two patchlevels wide. All three windows now have a base, and each refuses the +other two windows at `--fuzz=0` rather than half-applying into them. + +### Why the numbers do not order the way you expect + +`drivers/char/random.c` was rewritten in 5.18 (`extract_crng_user` and +`urandom_read` gave way to `get_random_bytes_user` and `urandom_read_iter`). +The rewrite was then **backported into the LTS branches and not into the +others**, so API shape does not follow version order: + +| shape | versions | +|---|---| +| pre-rewrite (`extract_crng_user`) | 5.6, 5.7, 5.8, 5.9, 5.11, 5.12, 5.13, 5.16, 5.17.0 - 5.17.11 | +| post-rewrite (`get_random_bytes_user`) | 5.10, 5.15, 5.17.12+, 5.18 and everything newer | + +5.10 and 5.15 are LTS and took the backport. 5.11, 5.12, 5.13 and 5.16 reached +end of life first and never did. So 5.10 is *newer in API* than 5.13, and 5.15 +is newer in API than 5.16, even though the numbers say otherwise. A +nearest-by-number rule is wrong across that entire span, which is why the table +above exists and why it is derived from measurement rather than arithmetic. + +A second boundary sits inside the 6.x line: `crng_reseed()` took a +`struct work_struct *` argument in **6.2**. A base from either side of that +boundary will often still *apply* to a tree on the other side, and then fail to +compile with `too few arguments to function 'crng_reseed'`. Applying is not +compiling, which is why the coverage table carries a separate `random.o built` +column instead of treating a clean apply as the end of the question. + +### Why each of the newer bases exists + +These five bases were added because the nearest existing base missed by one or +two hunks at `--fuzz=0`. In every case the miss is a real difference in the +tree, not patch rot: + +| base | what the tree has that the nearest base did not expect | +|---|---| +| `5.6` | `include/linux/random.h` ends after `next_pseudo_random32()`. The `arch_get_random_*_early()` inlines that `5.10.17`'s header hunk carries as context arrived in 5.7. Only the header hunk differs; the `random.c` edits are `5.10.17`'s. | +| `5.16` | `static bool crng_need_final_init = false;` sits between `crng_init` and `#define crng_ready()`, which is context for `5.15` hunk 2. One line; everything else is `5.15`. | +| `5.17.0` | pre-rewrite like `5.15`, but 5.17 took the `random.c` cleanup series: `mix_pool_bytes()` lost its `struct entropy_store *`, `_warn_unseeded_randomness()` folded onto one line, `urandom_read()` lost `unsigned long flags`, and `crng_reseed()` became `crng_reseed(&primary_crng, bool)`. That last one matters: under fuzz the `RNDRESEEDCRNG` hunk *applies* and then will not compile, because the code it inserts calls the old two-pool signature. | +| `5.18` | `urandom_read_iter()` gained the opportunistic `try_to_generate_entropy()` block, so it has **two** `crng_ready()` tests where `5.10.236` has one. Both are converted, matching `6.1.73`. This is a third shape, not a near-miss. | +| `6.16` | still has `warn_unseeded_randomness()`, which `7.0` does not (removed in mainline 6.18). The macro is edited to use `crng_ready_maybe_cb()` and the batched-entropy hunk carries the call in context, exactly as `6.15` does. Serves 6.17 as well. | + +Two of these five change the result and three make an already-correct result +reproducible. That was measured, by applying the previously-selected base at +`--fuzz=3` and diffing against the new base at `--fuzz=0`: + +* `5.17.0` -- the fuzzed result **does not compile**. `5.15`'s + `mix_pool_bytes()` hunk lands inside `fast_mix()`, and the build stops with + `'in' undeclared` / `'nbytes' undeclared` at `random.c:915`. This sub-range + had no base at all before. +* `5.18` -- the fuzzed result compiles, but is **short one site**: the + opportunistic `try_to_generate_entropy()` test kept calling `crng_ready()`, + so that path asked the kernel's readiness instead of the module's. +* `5.6`, `5.16`, `6.16` -- the fuzzed result was already byte-identical to the + new one. These three buy reproducibility rather than a behaviour change: the + patched tree no longer depends on fuzz landing where it happened to land. + +## Naming + + WOLFSSL_KERNELv_FIPS.patch + +with `.` replaced by `_`. Two directories carry a distinguishing suffix instead +of their full vendor version string, because the full string is unwieldy: + +| directory | patch | +|---|---| +| `5.6` | `WOLFSSL_KERNELv5_6_FIPS.patch` | +| `5.10.17` | `WOLFSSL_KERNELv5_10_17_FIPS.patch` | +| `5.10.236` | `WOLFSSL_KERNELv5_10_236_FIPS.patch` | +| `5.14.0-570.58.1.el9_6` | `WOLFSSL_KERNELv5_14_el9_6_FIPS.patch` | +| `5.15` | `WOLFSSL_KERNELv5_15_FIPS.patch` | +| `5.16` | `WOLFSSL_KERNELv5_16_FIPS.patch` | +| `5.17` | `WOLFSSL_KERNELv5_17_FIPS.patch` | +| `5.17.0` | `WOLFSSL_KERNELv5_17_0_FIPS.patch` | +| `5.17.14` | `WOLFSSL_KERNELv5_17_14_FIPS.patch` | +| `5.17-ubuntu-jammy-tegra` | `WOLFSSL_KERNELv5_17_tegra_FIPS.patch` | +| `5.18` | `WOLFSSL_KERNELv5_18_FIPS.patch` | +| `6.1.73` | `WOLFSSL_KERNELv6_1_73_FIPS.patch` | +| `6.12` | `WOLFSSL_KERNELv6_12_FIPS.patch` | +| `6.12.75` | `WOLFSSL_KERNELv6_12_75_FIPS.patch` | +| `6.15` | `WOLFSSL_KERNELv6_15_FIPS.patch` | +| `6.16` | `WOLFSSL_KERNELv6_16_FIPS.patch` | +| `7.0` | `WOLFSSL_KERNELv7_0_FIPS.patch` | + +`regen-patches.sh` derives these names from the directory name and asserts the +result against what is checked in, so the table above and the files cannot +drift apart silently. + +## There are two kinds of patch, and they are not interchangeable + +**Shipped (this directory, in git).** The files listed above. They define +`WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS` and use unnamespaced symbols. + +**Derived (generated by the test harness, never in git).** The test harness +takes a shipped patch as a base and derives a version-specific patch whose +symbols are namespaced (`..._FIPS` defines, `wc_grb_*` counters). These are +written to a scratch output directory at test time by +`matrix/arm64/patches/make-fips-rbgc-patch.sh` and its counterpart under +`matrix/is-deferral-required/`. + +Neither of those scripts chooses a base -- both take one as their second +argument. Base selection lives in their callers, and the two callers do not use +the same rule: + +* `matrix/arm64/lib/kernel.sh` orders candidates by (same side of the + `crng_reseed` boundary, then `|major.minor|` distance, then older-first) and + takes the first that applies with `--fuzz=3`. +* `matrix/is-deferral-required/kernels-full/pass2.sh` takes the base with the + fewest offsets among those that applied with **no** fuzz. + +They disagree for some versions, and only the second matches the table above. +Which base a given harness run actually used is recorded in that run's results +(`kernel_patch_base=`), so read that rather than assuming. + +**Adding a base changes what the first caller picks, so re-measure it.** Adding +the five newest bases moved that caller's choice for seven of the thirty-four +supported versions: 5.6, 5.16, 5.18, 6.16 and 6.17 moved onto their new exact +base, and 5.7 and 5.8 moved onto `5.6` because it is numerically nearer than +`5.10.17`. The last two were checked rather than assumed -- `5.6` at `--fuzz=3` +produces a byte-identical `random.c` and `random.h` on both trees, because the +only hunk that differs between the two bases is the `random.h` tail and it +fuzzes onto the same anchor. Nothing else moved. A future base can just as +easily move a pick onto something that is *not* equivalent, so measure the +whole matrix when you add one. + +Consumers locate a shipped patch by directory and glob (`/*.patch`), +not by filename, so the filenames above are not a harness API. + +## Regenerating + +`regen-patches.sh` regenerates every patch from full kernel sources staged under +`src//`, each holding a pristine `random.c.dist` / `random.h.dist` +alongside the modified `random.c` / `random.h`. It is an internal tool: it +requires those staged trees and is not part of the library build. diff --git a/linuxkm/patches/patch-kernel.sh b/linuxkm/patches/patch-kernel.sh new file mode 100755 index 00000000000..5d7daa4d6c3 --- /dev/null +++ b/linuxkm/patches/patch-kernel.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# +# patch-kernel.sh -- apply the wolfSSL FIPS get_random_bytes() patch to a kernel. +# +# ./patch-kernel.sh /path/to/linux-6.6.99 +# ./patch-kernel.sh /path/to/linux-6.6.99 6.12 # force a base +# ./patch-kernel.sh --dry-run /path/to/linux-7.1.9 +# +# With no base given, the version is read from the kernel Makefile and looked up +# in README.md's coverage table, so you get the base that was TESTED for your +# version rather than the nearest-looking one. +# +# IT REFUSES TO FUZZ. patch(1) will happily place a hunk in the wrong function +# when context has drifted; that produces a tree which compiles and is wrong. +# It happened here: applying the 5.15 base to 5.17 at --fuzz=3 put the +# mix_pool_bytes() hunk inside fast_mix(), giving +# "random.c:915: error: 'in' undeclared". A marker count cannot see that -- the +# count is identical either way. So this script applies at --fuzz=0 and stops +# if that fails. If it stops, your version needs its own base; do not reach for +# --fuzz. +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +DRY=0 +[ "${1:-}" = "--dry-run" ] && { DRY=1; shift; } + +KDIR="${1:-}" +BASE="${2:-}" + +die() { printf 'patch-kernel: %s\n' "$*" >&2; exit 1; } + +[ -n "$KDIR" ] || die "usage: $0 [--dry-run] [base]" +[ -d "$KDIR" ] || die "not a directory: $KDIR" +[ -f "$KDIR/Makefile" ] || die "no Makefile in $KDIR -- is that a kernel tree?" +[ -f "$KDIR/drivers/char/random.c" ] || die "no drivers/char/random.c in $KDIR" + +# --- work out the kernel version ------------------------------------------- +V=$(awk '/^VERSION[[:space:]]*=/{v=$3} /^PATCHLEVEL[[:space:]]*=/{p=$3} + /^SUBLEVEL[[:space:]]*=/{s=$3} END{printf "%s.%s.%s", v, p, s}' "$KDIR/Makefile") +SERIES=${V%.*} +printf 'kernel : %s (series %s)\n' "$V" "$SERIES" + +# --- pick the base ---------------------------------------------------------- +if [ -z "$BASE" ]; then + # Rows look like: | 6.6 | 6.6.99 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | ... + # Prefer a row whose "verified at" matches exactly, else the series row. + BASE_PATCH=$(awk -v v="$V" -F'|' ' + $0 ~ /^\|/ && $3 ~ v {gsub(/[` ]/,"",$4); print $4; exit}' "$HERE/README.md" || true) + [ -n "${BASE_PATCH:-}" ] || BASE_PATCH=$(awk -v s="$SERIES" -F'|' ' + $0 ~ /^\|/ {gsub(/ /,"",$2); if ($2==s) {gsub(/[` ]/,"",$4); print $4; exit}}' "$HERE/README.md" || true) + [ -n "${BASE_PATCH:-}" ] || die "no row for $V (series $SERIES) in README.md. +Your version is NOT in the supported set. Do not guess a nearby base -- +see README.md for why that produces a tree that compiles and is wrong." +else + BASE_PATCH=$(ls "$HERE/$BASE"/*_FIPS.patch 2>/dev/null | head -1) \ + || die "no *_FIPS.patch under $HERE/$BASE" + BASE_PATCH=${BASE_PATCH#"$HERE/"} +fi + +PATCH="$HERE/$BASE_PATCH" +[ -f "$PATCH" ] || die "patch file not found: $PATCH" +printf 'patch : %s\n' "$BASE_PATCH" + +# --- apply, at fuzz 0, never more ------------------------------------------- +if ! patch -p1 -d "$KDIR" --dry-run --fuzz=0 --force < "$PATCH" >/tmp/pk.$$ 2>&1; then + sed 's/^/ /' /tmp/pk.$$ >&2; rm -f /tmp/pk.$$ + die "patch does NOT apply cleanly at --fuzz=0. +This is a refusal, not a suggestion to retry with fuzz. Either the tree is not +pristine, or $V needs a base of its own. Raising --fuzz can place a hunk in the +wrong function and still exit 0." +fi +rm -f /tmp/pk.$$ + +if [ "$DRY" = 1 ]; then + printf 'result : would apply cleanly (dry run, nothing written)\n' + exit 0 +fi + +patch -p1 -d "$KDIR" --fuzz=0 --force < "$PATCH" | sed 's/^/ /' +printf 'result : applied\n' +printf '\nnext : build the kernel, then build the module against it:\n' +printf ' ./configure --enable-linuxkm --with-linux-source=%s ...\n' "$KDIR" +printf ' The module binds to symbols this patch exports; if the kernel\n' +printf ' is unpatched, insmod fails with "Unknown symbol\n' +printf ' wolfssl_linuxkm_register_random_bytes_handlers" rather than\n' +printf ' silently running without the hook.\n' diff --git a/linuxkm/patches/regen-patches.sh b/linuxkm/patches/regen-patches.sh index f0af9182d4a..4a5ea3cb7cc 100755 --- a/linuxkm/patches/regen-patches.sh +++ b/linuxkm/patches/regen-patches.sh @@ -9,6 +9,18 @@ if [[ ! -d 6.15 ]]; then exit 1 fi +# Patch file naming. The map is explicit rather than a substitution on $v +# because two directories do not follow the numeric pattern, and the previous +# "${v//./v}" transform silently produced names that did not match the files +# actually checked in for those two. +patch_basename() { + case "$1" in + 5.14.0-570.58.1.el9_6) echo "WOLFSSL_KERNELv5_14_el9_6_FIPS.patch" ;; + 5.17-ubuntu-jammy-tegra) echo "WOLFSSL_KERNELv5_17_tegra_FIPS.patch" ;; + *) echo "WOLFSSL_KERNELv${1//./_}_FIPS.patch" ;; + esac +} + cd src || exit $? for v in *; do @@ -27,7 +39,7 @@ for v in *; do continue fi - out_f="../${v}/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-${v//./v}.patch" + out_f="../${v}/$(patch_basename "$v")" diff --minimal -up "${v}/drivers/char/"{random.c.dist,random.c} >| "$out_f" if [[ $? != "1" ]]; then echo "diff ${v}/src/drivers/char/{random.c.dist,random.c} exited with unexpected status." >&2 diff --git a/src/include.am b/src/include.am index 70224233450..bd62272d0a2 100644 --- a/src/include.am +++ b/src/include.am @@ -1082,6 +1082,13 @@ endif !BUILD_PPC64_ASM_INLINE endif BUILD_PPC64_ASM if BUILD_SHA512 +if BUILD_LINUXKM_RBGC +# RBGC compiles ONE SHA-512: the portable C transform. No Intel or ARM asm, so +# there is no second implementation in the boundary and nothing to select at run +# time. linuxkm_get_entropy.c takes the place of the asm it replaces. +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/sha512.c +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/linuxkm_get_entropy.c +else if BUILD_RISCV_ASM src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/sha512.c src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/port/riscv64/riscv-64-sha512-asm.S @@ -1137,6 +1144,7 @@ endif BUILD_INTELASM endif !BUILD_ARMASM endif !BUILD_ARMASM_NEON endif !BUILD_RISCV_ASM +endif !BUILD_LINUXKM_RBGC if BUILD_PPC64_ASM if BUILD_PPC64_ASM_INLINE diff --git a/wolfcrypt/src/linuxkm_get_entropy.c b/wolfcrypt/src/linuxkm_get_entropy.c new file mode 100644 index 00000000000..1001991d37f --- /dev/null +++ b/wolfcrypt/src/linuxkm_get_entropy.c @@ -0,0 +1,1191 @@ +/* linuxkm_get_entropy.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Serves the Linux kernel's get_random_bytes() from inside the FIPS module + * cryptographic boundary. + * + * SP 800-90C Sec. 7 DRBG tree. A root DRBG reseeds 2*ncpu leaf DRBGs: one + * leaf per CPU for process, softirq and hardirq, and one leaf per CPU for NMI. + * Only the root ever touches the entropy source: leaves are instantiated and + * reseeded from it. Everything here is SHA-512. + * + * The root seeding a leaf is one instantiated DRBG serving a second, + * separately instantiated DRBG, which is what SP 800-90C Sec. 7.2.3.2.1 calls + * for: a leaf reseeds by asking its parent to generate, not by asking the + * entropy source. SP 800-90A Sec. 8.6.9 bars a DRBG from reseeding itself, + * and that is a rule about one instantiation, so it does not reach a parent + * feeding a child. Each leaf reseed takes its own fresh root generate + * (Sec. 7.3.1 req 15), no leaf output ever reaches the root (req 16), and + * prediction resistance is not claimed for the leaves (Sec. 7.2.3.2.1). + * + * One root, not one per leaf: wolfentropy.c has a single noise source, one + * mutex and one shared SP 800-90B RCT/APT state, so extra roots would + * serialize on it and share its health verdict for 3x the cost. + * + * Concurrency: a caller runs with interrupts off, so on one CPU process, + * softirq and hardirq cannot overlap. The maintenance reseed for a general + * leaf runs ON THAT LEAF'S OWN CPU, also with interrupts off, so no caller can + * be inside while it writes and no exclusion is needed at all. + * + * NMI is not masked by that, so an NMI leaf is two instantiations: the reseed + * writes the spare and then publishes it with one atomic index store. An NMI + * reads either the old or the new instance, both fully instantiated, so a torn + * state is not representable. That reseed also runs on the leaf's own CPU, so + * a stalled NMI stalls it too and it cannot come round twice underneath a read. + * CPU hotplug is the one thing that can move it off that CPU; a per-instance + * generation counter backstops that case and is the only path that declines. + * + * A request is served in WC_GRB_CHUNK_SZ pieces, so the interrupts-off window + * is set by the chunk size rather than by the size the caller asked for. + * + * Nothing here waits. + */ + +#include + +#if defined(WOLFSSL_LINUXKM) && defined(LINUXKM_RBGC) + +#include +#include +#include + +/* ForceZero. misc.c is inlined into the including translation unit unless + * NO_INLINE, the same idiom random.c uses. */ +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* No raw includes: linuxkm_wc_port.h already pulls + * inside a _Pragma guard suppressing warnings wolfSSL builds with -Werror. */ + +/* Cap on the leaf reseed threshold, so a build whose WC_RESEED_INTERVAL is the + * SP 800-90A maximum still refreshes at a sane rate. Runtime and not #if: the + * interval may carry a cast the preprocessor cannot evaluate. */ +#ifndef WC_GRB_RESEED_CAP +#define WC_GRB_RESEED_CAP 500000UL +#endif + +/* Root reseed period, in maintenance ticks. */ +#define WC_GRB_ROOT_PERIOD 1200UL + +/* Bytes generated per interrupts-off section. The request length comes from + * the caller and is not bounded: ip_rt_init() asks for 65536 in one call, which + * as a single generate would hold interrupts off for roughly 2048 SHA-512 + * compressions. The kernel's own CRNG takes its lock only to derive a key and + * runs the bulk output unlocked for the same reason. Large requests are + * therefore served in chunks, releasing interrupts between them. + * + * Splitting is safe: each chunk is an independent generate, so a caller that + * slips in between chunks merely advances the leaf and the next chunk + * continues from the new state. Output is validated DRBG output either way. */ +#define WC_GRB_CHUNK_SZ 256 + +/* Reads to attempt before an NMI is turned away. Each retry costs one more + * chunk generate; a reseed lands about 7.6 times a second per CPU, so two in + * succession inside one read is not reachable at any measured rate. */ +#define WC_GRB_NMI_TRIES 3 + +/* Internal to the read loop, never returned: distinguishes "reseeded + * underneath the read, try again" from a generator error. */ +#define WC_GRB_RETRY 1 + +/* Guard against an absurd allocation from a bogus CPU count. */ +#define WC_GRB_MAX_CPU 512 + +enum { + WC_GRB_PROC = 0, + WC_GRB_SOFTIRQ, + WC_GRB_HARDIRQ, + WC_GRB_NMI +}; + +/* 64-bit counter built from two 32-bit atomics. atomic64_t is not inline on + * 32-bit x86: it calls out-of-line cx8 helpers, and an in-boundary file must + * have no unresolved symbols (linuxkm/Kbuild). A 32-bit counter would wrap + * within hours at the rate this path is driven. */ +struct wc_grb_ctr { + atomic_t lo; + atomic_t hi; +}; + +/* Per-context counters, one struct rather than nine parallel arrays: the + * service path then touches one cache line instead of nine. The _boot pair + * keeps start-up demand apart from steady state, which an aggregate hides. */ +struct wc_grb_ctx_stats { + struct wc_grb_ctr calls; + struct wc_grb_ctr served; + struct wc_grb_ctr failed; + struct wc_grb_ctr inhibit; + struct wc_grb_ctr notready; + struct wc_grb_ctr declined; + struct wc_grb_ctr calls_boot; + struct wc_grb_ctr failed_boot; + struct wc_grb_ctr reseeds; +}; + +/* General leaf: process, softirq and hardirq on one CPU. No exclusion flag, + * because the only writer is the maintenance reseed running on this same CPU + * with interrupts off. */ +struct wc_grb_slot { + WC_RNG rng; + atomic_t pending; + struct wc_grb_ctr since; + unsigned long at; +}; + +/* NMI leaf: two instantiations, one in service. live is the index a caller + * reads; the reseed writes the other and then stores the new index. */ +struct wc_grb_nmi_slot { + WC_RNG rng[2]; + atomic_t live; + /* Bumped before an instance is reseeded. An NMI reads it either side of + * its generate and discards the result if it changed, which turns the + * reuse interval from a timing assumption into a checked one: a vCPU + * descheduled inside an NMI handler can outlive two flips, and 926 ms of + * steal has been measured on this hardware. */ + atomic_t gen[2]; + atomic_t pending; + /* One reseed at a time. Three maintenance contexts can reach one leaf and + * none excluded another: the leaf's own pinned worker through + * wc_grb_maintain_cpu(cpu), the unbound sweep in wc_grb_root_tick() which + * passes want_cpu -1, and the service path's self-help + * wc_grb_maintain_cpu(-1). wc_grb_maint_busy guards only + * wc_grb_root_tick() against itself. + * + * Two reseeders in one leaf break both of the things that keep an NMI out + * of an instantiation whose exclusion flag is held. They derive the same + * spare from the same live, so the first to finish stores live = spare + * while the second is still inside wc_RNG_DRBG_Reseed(); and the second's + * own increment of gen[spare] returns that counter to EVEN while the first + * still holds the flag, so the odd-means-busy check below reads even and + * lets the caller in. Measured on 6.12.59/x86_64 over 49 runs with the + * exclusion window widened: 10 runs wedged and every one of them had two + * reseeders in one leaf; of the 36 runs that never had two, none wedged. + * The race itself needs no widening -- at the natural window it fired + * about 4,000 times in a 20-second run. */ + atomic_t reseeding; + struct wc_grb_ctr since; + unsigned long at; +}; + +static struct wc_grb_ctx_stats wc_grb_stat[WC_GRB_CTX_N]; +static atomic_t wc_grb_last_err; +static int wc_grb_boot_done; + +static WC_RNG wc_grb_root; +static struct wc_grb_slot *wc_grb_cpu; +static struct wc_grb_nmi_slot *wc_grb_nmi; +static int wc_grb_ncpu; + +static unsigned long wc_grb_reseed_base; +static struct wc_grb_ctr wc_grb_reseeds; +static struct wc_grb_ctr wc_grb_reseed_failed; + +/* Summary of every leaf's pending flag. wc_grb_reseed_due() is polled by the + * glue and is also consulted on the service path, so it must be one read and + * not a scan of 2*ncpu atomics. */ +static atomic_t wc_grb_any_pending; + + +/* Maintenance reseeds skipped: either CPU hotplug moved the work off the CPU + * whose leaf it was going to write, or another maintenance context was already + * inside that leaf. Neither is a decline: no caller was turned away, and the + * leaf is picked up on the next tick. */ +static struct wc_grb_ctr wc_grb_maint_deferred; + +/* NMI requests answered by the leaf's other instantiation because the live one + * could not answer. Every one of these is a request that would otherwise have + * been handed back to the kernel's own generator. */ +static struct wc_grb_ctr wc_grb_nmi_alt; + +static struct wc_grb_ctr wc_grb_root_reseeds; +static struct wc_grb_ctr wc_grb_root_reseed_failed; +static unsigned long wc_grb_root_ticks; +static const unsigned long wc_grb_root_period = WC_GRB_ROOT_PERIOD; +static unsigned long wc_grb_root_at = WC_GRB_ROOT_PERIOD; +static atomic_t wc_grb_maint_busy; + +static int wc_grb_rng_ready; +static int wc_grb_registered; + +static void wc_grb_ctr_inc(struct wc_grb_ctr *c) +{ + if ((unsigned int) atomic_inc_return(&c->lo) == 0u) { + atomic_inc(&c->hi); + } +} + +static long long wc_grb_ctr_inc_return(struct wc_grb_ctr *c) +{ + unsigned int lo = (unsigned int) atomic_inc_return(&c->lo); + unsigned int hi; + + if (lo == 0u) { + hi = (unsigned int) atomic_inc_return(&c->hi); + } + else { + hi = (unsigned int) atomic_read(&c->hi); + } + + return (long long) (((unsigned long long) hi << 32) | lo); +} + +/* Re-read hi around lo so a carry landing mid-read cannot pair a new hi with + * an old lo. */ +static long long wc_grb_ctr_read(struct wc_grb_ctr *c) +{ + unsigned int hi1, lo, hi2; + + do { + hi1 = (unsigned int) atomic_read(&c->hi); + lo = (unsigned int) atomic_read(&c->lo); + hi2 = (unsigned int) atomic_read(&c->hi); + } while (hi1 != hi2); + + return (long long) (((unsigned long long) hi1 << 32) | lo); +} + +static void wc_grb_ctr_zero(struct wc_grb_ctr *c) +{ + atomic_set(&c->lo, 0); + atomic_set(&c->hi, 0); +} + +#ifdef WC_GRB_MEASURE +/* Interrupts-off duration per chunk, so the bound WC_GRB_CHUNK_SZ is supposed + * to give can be checked rather than asserted. Two clock reads per chunk, so + * it is a harness build only; the shipped path reads no clock. + * + * Bucket k is [2^(k-1), 2^k) units of 1024 ns; bucket 0 is under 1024 ns. + * ns >> 10 rather than ns / 1000 because a 64-bit division calls __udivdi3 on + * i386 and the container must resolve every symbol. */ +static atomic_t wc_grb_irq_max_ns[WC_GRB_CTX_N]; +static struct wc_grb_ctr wc_grb_irq_buckets[WC_GRB_CTX_N][WC_GRB_IRQ_BUCKETS]; +static struct wc_grb_ctr wc_grb_chunks[WC_GRB_CTX_N]; + +static void wc_grb_note_irqoff(int ctx, unsigned long long ns) +{ + unsigned long long u = ns >> 10; + int b = 0; + + while ((u != 0ULL) && (b < (WC_GRB_IRQ_BUCKETS - 1))) { + u >>= 1; + b++; + } + + wc_grb_ctr_inc(&wc_grb_chunks[ctx]); + wc_grb_ctr_inc(&wc_grb_irq_buckets[ctx][b]); + if (ns > 0x7fffffffULL) { + ns = 0x7fffffffULL; + } + if ((int) ns > atomic_read(&wc_grb_irq_max_ns[ctx])) { + atomic_set(&wc_grb_irq_max_ns[ctx], (int) ns); + } +} +#endif /* WC_GRB_MEASURE */ + +static int wc_grb_reseed_due(void); + +/* in_irq() was removed in 6.19 and in_hardirq() only appeared in 5.11, so + * neither spelling covers the supported range on its own. */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) + #define WC_GRB_IN_HARDIRQ() in_hardirq() +#else + #define WC_GRB_IN_HARDIRQ() in_irq() +#endif + +static int wc_grb_ctx(void) +{ + if (in_nmi()) { + return WC_GRB_NMI; + } + + if (WC_GRB_IN_HARDIRQ()) { + return WC_GRB_HARDIRQ; + } + + if (in_softirq()) { + return WC_GRB_SOFTIRQ; + } + + return WC_GRB_PROC; +} + +/* This CPU's index. Safe to use across the critical section: the caller holds + * interrupts off, so it cannot migrate. */ +static int wc_grb_this_cpu(void) +{ + int cpu = wc_linuxkm_cpu_id(); + + return ((cpu < 0) || (cpu >= wc_grb_ncpu)) ? 0 : cpu; +} + +/* Draw the next reseed point from the root. Firing at exactly half the seed + * life would put the refresh on an externally observable schedule. */ +static void wc_grb_set_next_threshold(unsigned long *at) +{ + /* Not an SSP: root output used only to jitter a reseed counter, never as + * seed material, so it is not zeroized. */ + byte b[2]; + unsigned long span = wc_grb_reseed_base / 4; + unsigned long j = 0; + + if ((span != 0) && + (wc_RNG_GenerateBlock(&wc_grb_root, b, (word32) sizeof(b)) == 0)) { + j = (((unsigned long) b[0] << 8) | (unsigned long) b[1]) % (span + 1); + } + + *at = wc_grb_reseed_base - (span / 2) + j; +} + +/* Reseed the root from the entropy source. Process context only: + * wc_GenerateSeed() takes a mutex it cannot take with interrupts disabled. */ +static int wc_grb_reseed_root(void) +{ + byte seed[WC_DRBG_SEED_SZ]; + int ret; + + ret = wc_GenerateSeed(&wc_grb_root.seed, seed, (word32) sizeof(seed)); + if (ret == 0) { + ret = wc_RNG_DRBG_Reseed(&wc_grb_root, seed, (word32) sizeof(seed)); + } + + /* CSP: entropy-source output, and the root's seed material. SP 800-90A + * Sec. 8.6.6 makes the entropy input a CSP, so it dies with the reseed + * rather than living on in this frame. */ + ForceZero(seed, sizeof(seed)); + + if (ret != 0) { + wc_grb_ctr_inc(&wc_grb_root_reseed_failed); + atomic_set(&wc_grb_last_err, ret); + return ret; + } + + wc_grb_ctr_inc(&wc_grb_root_reseeds); + + return 0; +} + +/* Reseed THIS CPU's general leaf from its own fresh root generate, per + * SP 800-90C Sec. 7.2.3.2.1 and Sec. 7.3.1 requirement 15. + * + * The CPU is re-read with interrupts off and checked against the one the glue + * pinned this work to. A worker that CPU hotplug migrated would otherwise + * write a leaf another CPU is reading; here it simply skips and the leaf is + * picked up on the next tick. */ +static int wc_grb_reseed_local(int want_cpu) +{ + byte seed[WC_DRBG_SEED_SZ]; + unsigned long flags; + /* cpu is assigned only inside the ret == 0 branch below, and done is set + * only inside that same branch, so the use under if (done) is always after + * the assignment. gcc cannot see that correlation on 32-bit and fails + * -Werror=maybe-uninitialized there (i386 kernel build, x86_64 does not + * warn), so give it an explicit initial value. */ + int cpu = -1, ret, done = 0; + + ret = wc_RNG_GenerateBlock(&wc_grb_root, seed, (word32) sizeof(seed)); + if (ret == 0) { + flags = wc_linuxkm_irq_save(); + cpu = wc_grb_this_cpu(); + if ((want_cpu >= 0) && (cpu != want_cpu)) { + /* CPU hotplug moved this work off the CPU it was pinned to. The + * leaf belongs to a CPU that may be inside it right now, so leave + * it pending and let the next tick have it. */ + atomic_set(&wc_grb_any_pending, 1); + wc_grb_ctr_inc(&wc_grb_maint_deferred); + } + else if (atomic_read(&wc_grb_cpu[cpu].pending)) { + ret = wc_RNG_DRBG_Reseed(&wc_grb_cpu[cpu].rng, seed, + (word32) sizeof(seed)); + if (ret == 0) { + wc_grb_ctr_zero(&wc_grb_cpu[cpu].since); + atomic_set(&wc_grb_cpu[cpu].pending, 0); + done = 1; + } + } + wc_linuxkm_irq_restore(flags); + } + + /* CSP: this leaf's seed material, consumed by the reseed above, or unused + * because the leaf was not due. SP 800-90C Sec. 7.3.1 req 15 bars reusing + * it either way. */ + ForceZero(seed, sizeof(seed)); + + if (ret != 0) { + wc_grb_ctr_inc(&wc_grb_reseed_failed); + atomic_set(&wc_grb_last_err, ret); + + return ret; + } + + if (done) { + /* Outside the critical section: this draws from the root. A caller + * reading the old threshold meanwhile gets a valid one. */ + wc_grb_set_next_threshold(&wc_grb_cpu[cpu].at); + wc_grb_ctr_inc(&wc_grb_reseeds); + wc_grb_ctr_inc(&wc_grb_stat[WC_GRB_PROC].reseeds); + } + + return 0; +} + +/* Reseeds the spare and flips it live, so an NMI sees one instantiation or the + * other. Runs only on the leaf's own CPU: a stalled NMI stalls that CPU, so + * this worker cannot run twice underneath a reader. Hotplug is the one way it + * lands elsewhere; the generation counter below backstops that. */ +static int wc_grb_reseed_nmi(int cpu, int want_cpu) +{ + byte sl_seed[WC_DRBG_SEED_SZ]; + unsigned long flags; + int spare, ret, here; + + flags = wc_linuxkm_irq_save(); + here = wc_grb_this_cpu(); + wc_linuxkm_irq_restore(flags); + + if ((want_cpu >= 0) && (here != want_cpu)) { + atomic_set(&wc_grb_any_pending, 1); + wc_grb_ctr_inc(&wc_grb_maint_deferred); + + return 0; + } + + ret = wc_RNG_GenerateBlock(&wc_grb_root, sl_seed, + (word32) sizeof(sl_seed)); + if ((ret == 0) && + (atomic_cmpxchg(&wc_grb_nmi[cpu].reseeding, 0, 1) != 0)) + { + /* Another maintenance context is already inside this leaf. Leave it + * pending; the next tick has it. */ + atomic_set(&wc_grb_any_pending, 1); + wc_grb_ctr_inc(&wc_grb_maint_deferred); + ForceZero(sl_seed, sizeof(sl_seed)); + + return 0; + } + if (ret == 0) { + spare = atomic_read(&wc_grb_nmi[cpu].live) ? 0 : 1; + /* Seqlock on this instantiation: odd while it is being rewritten. + * The first increment lets an NMI still inside this instance from + * before a previous flip discard its result. The odd value in + * between lets an NMI that arrives DURING the reseed see that this + * instance is held and stay out of it: wc_RNG_DRBG_Reseed() takes the + * instance's exclusion flag (RngExclEnter() in random.c, a CAS spin + * on rng->excl), and an NMI cannot wait for that flag -- when the + * holder is this CPU's own process context it cannot run again until + * the NMI returns. + * + * The second increment is unconditional, so a failed reseed still + * leaves the counter even and the instance usable. */ + atomic_inc(&wc_grb_nmi[cpu].gen[spare]); + ret = wc_RNG_DRBG_Reseed(&wc_grb_nmi[cpu].rng[spare], sl_seed, + (word32) sizeof(sl_seed)); + atomic_inc(&wc_grb_nmi[cpu].gen[spare]); + if (ret == 0) { + wc_grb_ctr_zero(&wc_grb_nmi[cpu].since); + atomic_set(&wc_grb_nmi[cpu].pending, 0); + atomic_set(&wc_grb_nmi[cpu].live, spare); + } + atomic_set(&wc_grb_nmi[cpu].reseeding, 0); + } + + /* CSP: this leaf's seed material; see wc_grb_reseed_local(). */ + ForceZero(sl_seed, sizeof(sl_seed)); + + if (ret != 0) { + wc_grb_ctr_inc(&wc_grb_reseed_failed); + atomic_set(&wc_grb_last_err, ret); + + return ret; + } + + wc_grb_set_next_threshold(&wc_grb_nmi[cpu].at); + wc_grb_ctr_inc(&wc_grb_reseeds); + wc_grb_ctr_inc(&wc_grb_stat[WC_GRB_NMI].reseeds); + + return 0; +} + +/* The hook. Not static and does not self-register: + * wolfssl_linuxkm_register_random_bytes_handlers() is a kernel symbol outside + * the boundary and the container must resolve every symbol it references, so + * the boundary provides this and linuxkm/module_hooks.c registers it. */ +int wc_grb_service(void *buf, size_t len) +{ + int ctx = wc_grb_ctx(); + int ret = 0; + size_t done = 0; + + wc_grb_ctr_inc(&wc_grb_stat[ctx].calls); + if (!wc_grb_boot_done) { + wc_grb_ctr_inc(&wc_grb_stat[ctx].calls_boot); + } + + if (!wc_grb_rng_ready) { + wc_grb_ctr_inc(&wc_grb_stat[ctx].notready); + wc_grb_ctr_inc(&wc_grb_stat[ctx].failed); + if (!wc_grb_boot_done) { + wc_grb_ctr_inc(&wc_grb_stat[ctx].failed_boot); + } + + return -1; + } + + while (done < len) { + size_t want = len - done; + unsigned long flags; + int cpu; + + if (want > WC_GRB_CHUNK_SZ) { + want = WC_GRB_CHUNK_SZ; + } + + /* Interrupts off for one chunk. This is what makes same-CPU nesting + * impossible, and why nothing below may allocate or sleep. */ +#ifdef WC_GRB_MEASURE + { + unsigned long long t0; +#endif + flags = wc_linuxkm_irq_save(); +#ifdef WC_GRB_MEASURE + t0 = wc_linuxkm_mono_ns(); +#endif + cpu = wc_grb_this_cpu(); + + if (ctx == WC_GRB_NMI) { + struct wc_grb_nmi_slot *sl = &wc_grb_nmi[cpu]; + int live, g, tries; + + /* Straight into the caller's buffer. Nothing is generated ahead + * of demand and nothing is retained: these bytes belong to the + * request being served, and the caller is blocked inside this + * call until it returns. + * + * A changed generation means this instance was reseeded mid-read. + * The other one is live now and fully instantiated, so read that + * one, overwriting what the interrupted read left. Only a return + * of 0 tells the caller the bytes are good; on any other return + * the kernel refills the whole buffer itself. */ + for (tries = 0; tries < WC_GRB_NMI_TRIES; tries++) { + live = atomic_read(&sl->live); + g = atomic_read(&sl->gen[live]); + ret = wc_RNG_GenerateBlock(&sl->rng[live], + (byte *) buf + done, + (word32) want); + + if (ret != 0) { + /* This instantiation could not answer. The reachable + * reason is that it has reached its own reseed interval, + * and a reseed cannot be performed from NMI because the + * noise source takes a mutex. + * + * Ask the leaf's OTHER instantiation before giving up. It + * is separately instantiated with its own state and its own + * reseed counter, so it is not in the same condition, and + * reading it is the same on-demand generate into the same + * caller-owned buffer -- nothing is held, nothing is + * produced ahead of the request. + * + * Giving up here is not a neutral outcome. The kernel + * patch retries this hook WOLFSSL_LINUXKM_GRB_TRIES times + * and then panic()s rather than let its own generator + * answer, because those bytes would not be validated + * output and _get_random_bytes() returns void, so the + * caller could not tell which generator it got. A decline + * that survives the retries takes the machine down. */ + int other = live ? 0 : 1; + int og; + + /* Ask for a reseed NOW. The counters below only advance + * on a SUCCESSFUL generate, so an instance that has + * reached its reseed interval stops advancing "since" and + * would therefore never raise its own pending flag -- the + * leaf goes quiet exactly when it most needs maintenance, + * and the flag is only set later by whichever generate + * happens to succeed. Raising it here bounds the stall to + * one maintenance tick instead of leaving it to chance. + * Both stores are atomic and neither can block, so this is + * legal in NMI. */ + atomic_set(&sl->pending, 1); + atomic_set(&wc_grb_any_pending, 1); + + /* Read AFTER raising the flag above, which is what can + * start a reseed of this very instance. An odd count + * means maintenance is inside it now, holding its + * exclusion flag; wc_RNG_GenerateBlock() would spin in + * RngExclEnter() waiting for a holder this NMI is itself + * blocking. Decline instead -- the hook retries. */ + og = atomic_read(&sl->gen[other]); + if (og & 1) { + break; + } + + ret = wc_RNG_GenerateBlock(&sl->rng[other], + (byte *) buf + done, + (word32) want); + if (ret != 0) { + break; + } + if (atomic_read(&sl->gen[other]) != og) { + ret = WC_GRB_RETRY; + continue; + } + wc_grb_ctr_inc(&wc_grb_nmi_alt); + break; + } + + if (atomic_read(&sl->gen[live]) == g) { + break; + } + + ret = WC_GRB_RETRY; + } + + if (ret == WC_GRB_RETRY) { + wc_linuxkm_irq_restore(flags); + wc_grb_ctr_inc(&wc_grb_stat[ctx].declined); + + return -1; + } + + if (ret == 0) { + if ((unsigned long) wc_grb_ctr_inc_return(&sl->since) >= + sl->at) { + atomic_set(&sl->pending, 1); + atomic_set(&wc_grb_any_pending, 1); + } + } + } + else { + struct wc_grb_slot *sl = &wc_grb_cpu[cpu]; + + ret = wc_RNG_GenerateBlock(&sl->rng, (byte *) buf + done, + (word32) want); + if ((ret == 0) && + ((unsigned long) wc_grb_ctr_inc_return(&sl->since) >= sl->at)) { + atomic_set(&sl->pending, 1); + atomic_set(&wc_grb_any_pending, 1); + } + } + +#ifdef WC_GRB_MEASURE + wc_grb_note_irqoff(ctx, wc_linuxkm_mono_ns() - t0); +#endif + wc_linuxkm_irq_restore(flags); +#ifdef WC_GRB_MEASURE + } +#endif + + if (ret != 0) { + break; + } + + done += want; + } + + if (ret == 0) { + wc_grb_ctr_inc(&wc_grb_stat[ctx].served); + /* Self-help, so worker scheduling cannot starve the reseed. Interrupts + * are restored by now, the maintenance call is a root generate not the + * entropy gather, and can_block() rules out a caller holding a lock. */ + if ((ctx == WC_GRB_PROC) && wc_grb_reseed_due() && + wc_linuxkm_can_block()) { + (void) wc_grb_maintain_cpu(-1); + } + + return 0; + } + + wc_grb_ctr_inc(&wc_grb_stat[ctx].failed); + if (!wc_grb_boot_done) { + wc_grb_ctr_inc(&wc_grb_stat[ctx].failed_boot); + } + + if (ret == WC_NO_ERR_TRACE(WC_ACCEL_INHIBIT_E)) { + wc_grb_ctr_inc(&wc_grb_stat[ctx].inhibit); + } + + atomic_set(&wc_grb_last_err, ret); + + return -1; +} + +int wc_grb_service_active(void) +{ + return wc_grb_registered; +} + +void wc_grb_set_registered(int on) +{ + wc_grb_registered = on ? 1 : 0; +} + +/* One maintenance tick. The root refreshes from the noise source on its own + * jittered schedule, never coupled to leaf demand. */ +int wc_grb_root_tick(void) +{ + int ret = 0; + + if (!wc_grb_rng_ready) { + return 0; + } + + if (atomic_cmpxchg(&wc_grb_maint_busy, 0, 1) != 0) { + return 0; + } + + if (++wc_grb_root_ticks >= wc_grb_root_at) { + byte b[2]; /* not an SSP; see wc_grb_set_next_threshold() */ + unsigned long span = wc_grb_root_period / 4; + unsigned long j = 0; + + ret = wc_grb_reseed_root(); + wc_grb_root_ticks = 0; + if ((span != 0) && + (wc_RNG_GenerateBlock(&wc_grb_root, b, (word32) sizeof(b)) == 0)) { + j = (((unsigned long) b[0] << 8) | (unsigned long) b[1]) % + (span + 1); + } + + wc_grb_root_at = wc_grb_root_period - (span / 2) + j; + } + + /* Sweep the NMI leaves from here, which is unbound work in process + * context, in addition to each leaf's own pinned worker. + * + * The pinned worker alone was not enough, and the gap was not theoretical: + * a CPU taking NMIs back to back never schedules it, so that CPU's NMI + * leaf is never refreshed however overdue it gets. Its live instance then + * reaches WC_RESEED_INTERVAL on its own and every further NMI generate + * lands in PollAndReSeed(), which cannot reseed from NMI. Measured on + * 6.17.0/x86_64: the service answered exactly 499,999 NMI requests and + * then failed 945,000 consecutive ones, never recovering, and the machine + * hung. + * + * This sweep is not pinned, so it reseeds a leaf from a CPU other than the + * leaf's own and concurrently with that leaf's own pinned worker. What + * makes that safe is wc_grb_nmi[].reseeding: one reseeder per leaf, so + * spare cannot go stale and the generation counter cannot be returned to + * even by a second incrementer while the first still holds the flag. + * Without it the generation check reads even on a held instance and the + * caller walks in. */ + { + int i; + + for (i = 0; i < wc_grb_ncpu; i++) { + if (atomic_read(&wc_grb_nmi[i].pending)) { + int nret = wc_grb_reseed_nmi(i, -1); + + if ((ret == 0) && (nret != 0)) { + ret = nret; + } + } + } + } + + atomic_set(&wc_grb_maint_busy, 0); + + return ret; +} + +static int wc_grb_reseed_due(void) +{ + if (!wc_grb_rng_ready) { + return 0; + } + + return atomic_read(&wc_grb_any_pending) ? 1 : 0; +} + +/* Reseed the leaves belonging to one CPU. Process context only: the root's + * own generate may gather entropy, and that has to be able to block. + * + * cpu is the CPU the glue pinned this work to, or negative for "wherever this + * call happens to be", which is what the service path's self-help uses. */ +int wc_grb_maintain_cpu(int cpu) +{ + int ret = 0, target; + + if ((!wc_grb_rng_ready) || (wc_grb_cpu == NULL) || (cpu >= wc_grb_ncpu)) { + return 0; + } + + /* Cleared before the scan: anything already pending is still visible in + * its own flag, and anything raised during the scan re-arms this. */ + atomic_set(&wc_grb_any_pending, 0); + + target = (cpu < 0) ? wc_grb_this_cpu() : cpu; + + if (atomic_read(&wc_grb_cpu[target].pending)) { + ret = wc_grb_reseed_local(cpu); + } + + /* Not conditional on the general leaf's reseed: a failure there is no + * reason to leave the NMI leaf stale, and the NMI leaf is the one whose + * staleness costs every NMI caller. Also unpinned, for the reason given + * in wc_grb_root_tick(). */ + if (atomic_read(&wc_grb_nmi[target].pending)) { + int nret = wc_grb_reseed_nmi(target, -1); + + if ((ret == 0) && (nret != 0)) { + ret = nret; + } + } + + return ret; +} + +void wc_grb_mark_boot_done(void) +{ + wc_grb_boot_done = 1; +} + +int wc_grb_stat_snapshot(long long *out, int n) +{ + long long v[WC_GRB_STAT_N]; + int i; + + wc_static_assert(WC_GRB_NMI + 1 == WC_GRB_CTX_N); + + if ((out == NULL) || (n <= 0) || (wc_grb_cpu == NULL)) { + return 0; + } + + for (i = 0; i < WC_GRB_CTX_N; i++) { + v[WC_GRB_ST_CALLS + i] = wc_grb_ctr_read(&wc_grb_stat[i].calls); + v[WC_GRB_ST_SERVED + i] = wc_grb_ctr_read(&wc_grb_stat[i].served); + v[WC_GRB_ST_FAILED + i] = wc_grb_ctr_read(&wc_grb_stat[i].failed); + v[WC_GRB_ST_DECLINED + i] = wc_grb_ctr_read(&wc_grb_stat[i].declined); + v[WC_GRB_ST_CTX_RESEEDS + i] = wc_grb_ctr_read(&wc_grb_stat[i].reseeds); +#ifdef WC_GRB_MEASURE + v[WC_GRB_ST_IRQ_MAXNS + i] = atomic_read(&wc_grb_irq_max_ns[i]); + v[WC_GRB_ST_CHUNKS + i] = wc_grb_ctr_read(&wc_grb_chunks[i]); +#else + v[WC_GRB_ST_IRQ_MAXNS + i] = -1; + v[WC_GRB_ST_CHUNKS + i] = -1; +#endif + } + v[WC_GRB_ST_MAINT_DEFERRED] = wc_grb_ctr_read(&wc_grb_maint_deferred); + v[WC_GRB_ST_NMI_ALT] = wc_grb_ctr_read(&wc_grb_nmi_alt); + + v[WC_GRB_ST_RESEEDS] = wc_grb_ctr_read(&wc_grb_reseeds); + v[WC_GRB_ST_RESEED_FAILED] = wc_grb_ctr_read(&wc_grb_reseed_failed); + v[WC_GRB_ST_SINCE_RESEED] = wc_grb_ctr_read(&wc_grb_cpu[0].since); + v[WC_GRB_ST_RESEED_AT] = (long long) wc_grb_cpu[0].at; + v[WC_GRB_ST_LAST_ERR] = atomic_read(&wc_grb_last_err); + v[WC_GRB_ST_REGISTERED] = wc_grb_registered; + v[WC_GRB_ST_RNG_READY] = wc_grb_rng_ready; + v[WC_GRB_ST_ROOT_RESEEDS] = wc_grb_ctr_read(&wc_grb_root_reseeds); + v[WC_GRB_ST_ROOT_FAILED] = wc_grb_ctr_read(&wc_grb_root_reseed_failed); + + if (n > WC_GRB_STAT_N) { + n = WC_GRB_STAT_N; + } + + for (i = 0; i < n; i++) { + out[i] = v[i]; + } + + return n; +} + +int wc_grb_irq_hist(int ctx, long long *out, int n) +{ +#ifdef WC_GRB_MEASURE + int i; + + if ((out == NULL) || (n <= 0) || (ctx < 0) || (ctx >= WC_GRB_CTX_N)) { + return 0; + } + + if (n > WC_GRB_IRQ_BUCKETS) { + n = WC_GRB_IRQ_BUCKETS; + } + + for (i = 0; i < n; i++) { + out[i] = wc_grb_ctr_read(&wc_grb_irq_buckets[ctx][i]); + } + + return n; +#else + (void) ctx; (void) out; (void) n; + + return 0; +#endif +} + +static void wc_grb_report(void) +{ + /* 2-D, not an array of pointers: pointers need relocations and land in + * .data, this lands in .rodata with none. */ + static const char ctx_name[WC_GRB_CTX_N][8] = { + "process", "softirq", "hardirq", "nmi" + }; + int i; + + for (i = 0; i < WC_GRB_CTX_N; i++) { + pr_info("WCGRB: RESULT ctx=%s calls=%lld served=%lld failed=%lld" + " inhibit=%lld notready=%lld declined=%lld boot_calls=%lld" + " boot_failed=%lld\n", + ctx_name[i], + wc_grb_ctr_read(&wc_grb_stat[i].calls), + wc_grb_ctr_read(&wc_grb_stat[i].served), + wc_grb_ctr_read(&wc_grb_stat[i].failed), + wc_grb_ctr_read(&wc_grb_stat[i].inhibit), + wc_grb_ctr_read(&wc_grb_stat[i].notready), + wc_grb_ctr_read(&wc_grb_stat[i].declined), + wc_grb_ctr_read(&wc_grb_stat[i].calls_boot), + wc_grb_ctr_read(&wc_grb_stat[i].failed_boot)); + } + + pr_info("WCGRB: root_reseeds=%lld root_failed=%lld period=%lu\n", + wc_grb_ctr_read(&wc_grb_root_reseeds), + wc_grb_ctr_read(&wc_grb_root_reseed_failed), wc_grb_root_period); + pr_info("WCGRB: reseeds=%lld reseed_failed=%lld\n", + wc_grb_ctr_read(&wc_grb_reseeds), + wc_grb_ctr_read(&wc_grb_reseed_failed)); + if ((wc_grb_cpu != NULL) && (wc_grb_nmi != NULL)) { + for (i = 0; i < wc_grb_ncpu; i++) { + pr_info("WCGRB: leaf[cpu%d] since=%lld at=%lu nmi_since=%lld" + " nmi_at=%lu nmi_live=%d\n", + i, wc_grb_ctr_read(&wc_grb_cpu[i].since), wc_grb_cpu[i].at, + wc_grb_ctr_read(&wc_grb_nmi[i].since), wc_grb_nmi[i].at, + atomic_read(&wc_grb_nmi[i].live)); + } + } + + pr_info("WCGRB: last_err=%d registered=%d rng_ready=%d\n", + atomic_read(&wc_grb_last_err), wc_grb_registered, wc_grb_rng_ready); +} + +/* wc_FreeRng() zeroizes each leaf's DRBG internal state (V, C, reseed + * counter), which is the CSP this file holds for the life of the module. */ +/* wc_FreeRng() zeroizes each leaf's DRBG internal state (V, C, reseed + * counter), which is the CSP this file holds for the life of the module. */ +static void wc_grb_free_cpu(int n) +{ + int i; + + if (wc_grb_cpu == NULL) { + return; + } + + for (i = 0; i < n; i++) { + (void) wc_FreeRng(&wc_grb_cpu[i].rng); + } + + XFREE(wc_grb_cpu, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wc_grb_cpu = NULL; +} + +static void wc_grb_free_nmi(int n) +{ + int i; + + if (wc_grb_nmi == NULL) { + return; + } + + for (i = 0; i < n; i++) { + (void) wc_FreeRng(&wc_grb_nmi[i].rng[0]); + (void) wc_FreeRng(&wc_grb_nmi[i].rng[1]); + } + + XFREE(wc_grb_nmi, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wc_grb_nmi = NULL; +} + +static int wc_grb_alloc_cpu(void) +{ + size_t sz = sizeof(struct wc_grb_slot) * (size_t) wc_grb_ncpu; + int ret, i; + + wc_grb_cpu = (struct wc_grb_slot *) XMALLOC(sz, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (wc_grb_cpu == NULL) { + pr_err("WCGRB: no memory for %d cpu leaves\n", wc_grb_ncpu); + + return MEMORY_E; + } + + XMEMSET(wc_grb_cpu, 0, sz); + for (i = 0; i < wc_grb_ncpu; i++) { + ret = wc_InitRngRBGC(&wc_grb_cpu[i].rng, &wc_grb_root); + if (ret != 0) { + pr_err("WCGRB: wc_InitRngRBGC(cpu%d) failed: %d\n", i, ret); + wc_grb_free_cpu(i); + + return ret; + } + + atomic_set(&wc_grb_cpu[i].pending, 0); + wc_grb_ctr_zero(&wc_grb_cpu[i].since); + wc_grb_cpu[i].at = wc_grb_reseed_base; + } + + return 0; +} + +/* Both NMI instantiations are seeded up front, so the spare is a complete + * RBGC leaf from the moment the module starts and the first flip publishes a + * fully instantiated DRBG rather than an empty one. */ +static int wc_grb_alloc_nmi(void) +{ + size_t sz = sizeof(struct wc_grb_nmi_slot) * (size_t) wc_grb_ncpu; + int ret, i, k; + + wc_grb_nmi = (struct wc_grb_nmi_slot *) XMALLOC(sz, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (wc_grb_nmi == NULL) { + pr_err("WCGRB: no memory for %d nmi leaves\n", wc_grb_ncpu); + + return MEMORY_E; + } + + XMEMSET(wc_grb_nmi, 0, sz); + for (i = 0; i < wc_grb_ncpu; i++) { + for (k = 0; k < 2; k++) { + ret = wc_InitRngRBGC(&wc_grb_nmi[i].rng[k], &wc_grb_root); + if (ret != 0) { + pr_err("WCGRB: wc_InitRngRBGC(nmi cpu%d/%d) failed: %d\n", + i, k, ret); + while (--k >= 0) { + (void) wc_FreeRng(&wc_grb_nmi[i].rng[k]); + } + + wc_grb_free_nmi(i); + + return ret; + } + } + + atomic_set(&wc_grb_nmi[i].live, 0); + atomic_set(&wc_grb_nmi[i].gen[0], 0); + atomic_set(&wc_grb_nmi[i].gen[1], 0); + atomic_set(&wc_grb_nmi[i].pending, 0); + atomic_set(&wc_grb_nmi[i].reseeding, 0); + wc_grb_ctr_zero(&wc_grb_nmi[i].since); + wc_grb_nmi[i].at = wc_grb_reseed_base; + } + + return 0; +} + +int wc_grb_init(int ncpus) +{ + int ret, i; + + XMEMSET(wc_grb_stat, 0, sizeof(wc_grb_stat)); + atomic_set(&wc_grb_last_err, 0); + atomic_set(&wc_grb_any_pending, 0); + atomic_set(&wc_grb_maint_busy, 0); + wc_grb_ctr_zero(&wc_grb_reseeds); + wc_grb_ctr_zero(&wc_grb_reseed_failed); + wc_grb_ctr_zero(&wc_grb_root_reseeds); + wc_grb_ctr_zero(&wc_grb_root_reseed_failed); + wc_grb_boot_done = 0; + wc_grb_root_ticks = 0; + wc_grb_root_at = wc_grb_root_period; + + wc_grb_reseed_base = (unsigned long) (WC_RESEED_INTERVAL / 2); + if (wc_grb_reseed_base > WC_GRB_RESEED_CAP) { + wc_grb_reseed_base = WC_GRB_RESEED_CAP; + } + + if (wc_grb_reseed_base < 16UL) { + wc_grb_reseed_base = 16UL; + } + + wc_grb_ncpu = (ncpus > 0) ? ncpus : 1; + if (wc_grb_ncpu > WC_GRB_MAX_CPU) { + wc_grb_ncpu = WC_GRB_MAX_CPU; + } + + ret = wc_InitRng(&wc_grb_root); + if (ret != 0) { + pr_err("WCGRB: wc_InitRng(root) failed: %d\n", ret); + return ret; + } + + /* The construction is SHA-512 throughout. wc_InitRng() falls back to + * SHA-256 when the SHA-512 DRBG is disabled, and a tree half built on the + * other DRBG is not what was validated, so refuse and leave the kernel's + * own CRNG serving. */ + if (wc_grb_root.drbgType != WC_DRBG_SHA512) { + pr_err("WCGRB: root is not SHA-512 (drbgType=%d); not starting\n", + (int) wc_grb_root.drbgType); + (void) wc_FreeRng(&wc_grb_root); + return BAD_STATE_E; + } + + ret = wc_grb_alloc_cpu(); + if (ret != 0) { + (void) wc_FreeRng(&wc_grb_root); + + return ret; + } + + ret = wc_grb_alloc_nmi(); + if (ret != 0) { + wc_grb_free_cpu(wc_grb_ncpu); + (void) wc_FreeRng(&wc_grb_root); + + return ret; + } + + wc_grb_rng_ready = 1; + for (i = 0; i < wc_grb_ncpu; i++) { + wc_grb_set_next_threshold(&wc_grb_cpu[i].at); + wc_grb_set_next_threshold(&wc_grb_nmi[i].at); + } + + /* The effective interval, not the threshold: the threshold is capped and + * would read the same for 1e6 and for the SP 800-90A maximum. */ + pr_info("WCGRB: RBGC up, 1 root + %d cpu leaves + %d x2 nmi leaves," + " SHA-512, WC_RESEED_INTERVAL=%llu\n", + wc_grb_ncpu, wc_grb_ncpu, (unsigned long long) WC_RESEED_INTERVAL); + + return 0; +} + +void wc_grb_cleanup(void) +{ + wc_grb_registered = 0; + wc_grb_report(); + if (wc_grb_rng_ready) { + wc_grb_rng_ready = 0; + wc_grb_free_cpu(wc_grb_ncpu); + wc_grb_free_nmi(wc_grb_ncpu); + (void) wc_FreeRng(&wc_grb_root); + } +} + +#else /* !(WOLFSSL_LINUXKM && LINUXKM_RBGC) */ + +/* ISO C requires a translation unit to contain at least one declaration, and + * several OEs build with -Wpedantic. */ +typedef int wc_linuxkm_get_entropy_not_used; + +#endif /* WOLFSSL_LINUXKM && LINUXKM_RBGC */ diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 2e396516740..76d865ca9e9 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -351,6 +351,16 @@ enum { #define SEED_SZ WC_DRBG_SEED_SZ #define MAX_SEED_SZ WC_DRBG_MAX_SEED_SZ + +/* Selects the branch of _InitRng() that takes its seed material from the + * caller instead of from a randomness source. One caller compiles it in here: + * wc_InitRngRBGC() under LINUXKM_RBGC. Build-time only; nothing chooses + * between the branches at run time -- _InitRng() takes the caller-supplied + * path if and only if the caller passed a seed pointer. */ +#ifdef LINUXKM_RBGC + #define WC_RNG_SEED_FROM_CALLER +#endif + /* Verify max gen block len */ #if RNG_MAX_BLOCK_LEN > MAX_REQUEST_LEN #error RNG_MAX_BLOCK_LEN is larger than NIST DBRG max request length @@ -1899,8 +1909,13 @@ int wc_Sha512Drbg_IsDisabled(void) static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, + const byte* rbgcSeed, word32 rbgcSeedSz, void* heap, int devId) { +#ifndef WC_RNG_SEED_FROM_CALLER + (void)rbgcSeed; + (void)rbgcSeedSz; +#endif int ret = 0; #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) #if !defined(HAVE_FIPS) && defined(WOLFSSL_RNG_USE_FULL_SEED) @@ -2184,6 +2199,20 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, #endif } else { +#ifdef WC_RNG_SEED_FROM_CALLER + if (rbgcSeed != NULL) { + /* The caller already holds the seed material, so no randomness + * source is consulted: neither seedCb nor wc_GenerateSeed() + * below runs. The one caller here is wc_InitRngRBGC(), where + * the material came from the parent construction per + * SP 800-90C Sec. 7.2.1.2, and which bounds rbgcSeedSz by + * MAX_SEED_SZ before calling. */ + XMEMCPY(seed, rbgcSeed, rbgcSeedSz); + seedSz = rbgcSeedSz; + ret = 0; + } + else +#endif #ifdef WC_RNG_SEED_CB if (seedCb == NULL) { ret = DRBG_NO_SEED_CB; @@ -2218,8 +2247,21 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, rng->status = DRBG_FAILED; } - if (ret == 0) - ret = wc_RNG_TestSeed(seed, seedSz); + if (ret == 0) { +#ifdef WC_RNG_SEED_FROM_CALLER + /* wc_RNG_TestSeed() is a repetition check on raw noise. The + * seed material on this path is not raw noise: it is the + * parent's DRBG output, and SP 800-90C Sec. 7.2.1.2 makes the + * parent's generate status the check instead. */ + if (rbgcSeed != NULL) { + ret = DRBG_SUCCESS; + } + else +#endif + { + ret = wc_RNG_TestSeed(seed, seedSz); + } + } #if defined(DEBUG_WOLFSSL) if (ret != 0) { WOLFSSL_MSG_EX("wc_RNG_TestSeed failed... %d", ret); @@ -2233,25 +2275,41 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, #endif if (ret == DRBG_SUCCESS) { + const byte* instSeed; + word32 instSeedSz; + + #if defined(HAVE_FIPS) || !defined(WOLFSSL_RNG_USE_FULL_SEED) + /* The SEED_BLOCK_SZ prefix is the block wc_RNG_TestSeed() + * compares against the rest of a raw seed, so it is not part of + * the seed material. A caller-supplied seed runs no such test + * -- SP 800-90C Sec. 7.2.1.2 wants the whole 3s/2 bits in the + * instantiate -- so that path keeps the whole buffer. */ + #ifdef WC_RNG_SEED_FROM_CALLER + if (rbgcSeed != NULL) { + instSeed = seed; + instSeedSz = seedSz; + } + else + #endif + { + instSeed = seed + SEED_BLOCK_SZ; + instSeedSz = seedSz - SEED_BLOCK_SZ; + } + #else + instSeed = seed; + instSeedSz = seedSz; + #endif #ifndef NO_SHA256 if (rng->drbgType == WC_DRBG_SHA256) ret = Hash_DRBG_Instantiate((DRBG_internal *)rng->drbg, - #if defined(HAVE_FIPS) || !defined(WOLFSSL_RNG_USE_FULL_SEED) - seed + SEED_BLOCK_SZ, seedSz - SEED_BLOCK_SZ, - #else - seed, seedSz, - #endif + instSeed, instSeedSz, nonce, nonceSz, NULL, 0, rng->heap, devId); #endif #ifdef WOLFSSL_DRBG_SHA512 if (rng->drbgType == WC_DRBG_SHA512) ret = Hash512_DRBG_Instantiate( (DRBG_SHA512_internal *)rng->drbg512, - #if defined(HAVE_FIPS) || !defined(WOLFSSL_RNG_USE_FULL_SEED) - seed + SEED_BLOCK_SZ, seedSz - SEED_BLOCK_SZ, - #else - seed, seedSz, - #endif + instSeed, instSeedSz, nonce, nonceSz, NULL, 0, rng->heap, devId); #endif } @@ -2379,7 +2437,7 @@ int wc_rng_new_ex(WC_RNG **rng, byte* nonce, word32 nonceSz, return MEMORY_E; } - ret = _InitRng(*rng, nonce, nonceSz, heap, devId); + ret = _InitRng(*rng, nonce, nonceSz, NULL, 0, heap, devId); if (ret != 0) { XFREE(*rng, heap, DYNAMIC_TYPE_RNG); *rng = NULL; @@ -2402,29 +2460,72 @@ void wc_rng_free(WC_RNG* rng) } } +#ifdef LINUXKM_RBGC +/* Instantiate a non-root RBGC construction from its parent, SP 800-90C + * Sec. 7.2.1.2. Internal to the RBGC: not WOLFSSL_API, so it is not exported. + * + * WC_RBGC_INSTANTIATE_SZ is 3s/2 at s = 256, which is the amount that section + * names for Hash_DRBG and also the larger of the two amounts it gives. The + * s/2 above the security strength is the nonce entropy of SP 800-90A + * Sec. 8.6.7(b), so no separate nonce is passed. + * + * SHA-512 only, matching the construction; a parent that is not SHA-512 means + * that DRBG was disabled, and a leaf must not quietly be built on the other. */ +int wc_InitRngRBGC(WC_RNG* rng, WC_RNG* parent) +{ + byte seed[WC_RBGC_INSTANTIATE_SZ]; + int ret; + + if ((rng == NULL) || (parent == NULL)) { + return BAD_FUNC_ARG; + } + + if (parent->drbgType != WC_DRBG_SHA512) { + return BAD_STATE_E; + } + + ret = wc_RNG_GenerateBlock(parent, seed, (word32) sizeof(seed)); + if (ret == 0) { + ret = _InitRng(rng, NULL, 0, seed, (word32) sizeof(seed), NULL, + INVALID_DEVID); + } + + /* CSP: the parent's output, which is this leaf's seed material. SP + * 800-90C Sec. 7.3.1 req 15 bars reusing it for anything else. */ + ForceZero(seed, sizeof(seed)); + + if ((ret == 0) && (rng->drbgType != WC_DRBG_SHA512)) { + (void) wc_FreeRng(rng); + return BAD_STATE_E; + } + + return ret; +} +#endif /* LINUXKM_RBGC */ + WOLFSSL_ABI int wc_InitRng(WC_RNG* rng) { - return _InitRng(rng, NULL, 0, NULL, INVALID_DEVID); + return _InitRng(rng, NULL, 0, NULL, 0, NULL, INVALID_DEVID); } int wc_InitRng_ex(WC_RNG* rng, void* heap, int devId) { - return _InitRng(rng, NULL, 0, heap, devId); + return _InitRng(rng, NULL, 0, NULL, 0, heap, devId); } int wc_InitRngNonce(WC_RNG* rng, byte* nonce, word32 nonceSz) { - return _InitRng(rng, nonce, nonceSz, NULL, INVALID_DEVID); + return _InitRng(rng, nonce, nonceSz, NULL, 0, NULL, INVALID_DEVID); } int wc_InitRngNonce_ex(WC_RNG* rng, byte* nonce, word32 nonceSz, void* heap, int devId) { - return _InitRng(rng, nonce, nonceSz, heap, devId); + return _InitRng(rng, nonce, nonceSz, NULL, 0, heap, devId); } #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) diff --git a/wolfcrypt/src/sha512.c b/wolfcrypt/src/sha512.c index 108af7ed2f8..91f6d9839ba 100644 --- a/wolfcrypt/src/sha512.c +++ b/wolfcrypt/src/sha512.c @@ -136,16 +136,24 @@ #include #endif -#if defined(WC_C_DYNAMIC_FALLBACK) && \ - defined(WOLFSSL_AESNI) && !defined(USE_INTEL_SPEEDUP) - /* AES-NI can be enabled with WC_C_DYNAMIC_FALLBACK, but without the rest of - * USE_INTEL_SPEEDUP, in which case we need to disable the dynamic - * fallback. - */ - #undef WC_C_DYNAMIC_FALLBACK + +#if defined(WOLFSSL_ARMASM) && defined(LINUXKM_RBGC) + /* src/include.am compiles exactly one SHA-512 transform under + * BUILD_LINUXKM_RBGC -- the portable C one in this file -- and emits no + * ARM assembly object for it. The dispatch below has to agree with what + * is actually compiled: left defined, it installs + * Transform_Sha512_Len_neon, which nothing then defines, and the wolfCrypt + * PIE container fails to link with "U Transform_Sha512_Len_neon". + * This is the ARM counterpart of the !defined(LINUXKM_RBGC) already on the + * Intel dispatch immediately below. Both pin the implementation at build + * time; neither introduces a run-time choice, and the scalar transform is + * what makes the RBGC DRBG callable with interrupts off, where the vector + * registers are not available. */ + #undef WOLFSSL_ARMASM #endif -#if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) +#if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) #if defined(__GNUC__) && ((__GNUC__ < 4) || \ (__GNUC__ == 4 && __GNUC_MINOR__ <= 8)) #undef NO_AVX2_SUPPORT @@ -196,6 +204,7 @@ defined(WOLFSSL_ARMASM) || \ defined(WOLFSSL_RISCV_ASM) || \ (defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2))) #error "WOLF_CRYPTO_CB_ONLY_SHA512 is incompatible with SHA-512 hardware" \ " acceleration backends" @@ -914,9 +923,13 @@ int wc_Sha384GetFlags(wc_Sha384* sha384, word32* flags) #ifdef WOLFSSL_SHA512 +/* A pinned x86 build has no transform to select, so Sha512_SetTransform() is + * a do-nothing macro there rather than a function. Defined here, ahead of + * its first call site below. */ #if (defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ - (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2))) || \ - defined(WOLFSSL_ARMASM) + !defined(LINUXKM_RBGC) && \ + (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2))) || \ + defined(WOLFSSL_ARMASM) static void Sha512_SetTransform(void); #endif @@ -940,6 +953,7 @@ static int InitSha512(wc_Sha512* sha512) sha512->hiLen = 0; #if (defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2))) || \ defined(WOLFSSL_ARMASM) Sha512_SetTransform(); @@ -991,6 +1005,7 @@ static int InitSha512_224(wc_Sha512* sha512) sha512->hiLen = 0; #if (defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2))) || \ defined(WOLFSSL_ARMASM) Sha512_SetTransform(); @@ -1044,6 +1059,7 @@ static int InitSha512_256(wc_Sha512* sha512) sha512->hiLen = 0; #if (defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2))) || \ defined(WOLFSSL_ARMASM) Sha512_SetTransform(); @@ -1073,8 +1089,27 @@ static int InitSha512_256(wc_Sha512* sha512) /* Hardware Acceleration */ #if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2)) + /* SHA-512 transforms compiled here: unpinned that is AVX1 and AVX2, each + * with and without RORX, and the C one, chosen from CPUID at run time. + * IG 10.3.A GeneralNote1 requires each implementation in the module to be + * self-tested separately. A certified build compiles several and CPUID + * selects one per OE; what a save failure must never do is select a + * different one. */ + + /* Suppress the C transform (and K512, which only it uses) when an + * accelerated implementation is pinned: it would be a second SHA-512 in + * the boundary, reachable from the same services. */ + + /* Does the compiled transform want the block as host-endian words? Only + * the C one does; the accelerated ones byte-reverse internally. Unpinned + * this is a CPUID test at every call site, which is the run-time + * selection the pin removes; pinned it is a build-time constant. */ + #define WC_SHA512_REV_BLOCK() \ + (!IS_INTEL_AVX1(intel_flags) && !IS_INTEL_AVX2(intel_flags)) + /***** Intel AVX1/AVX2 Macro Control Structure @@ -2035,8 +2070,9 @@ static WC_INLINE int Sha512Update(wc_Sha512* sha512, const byte* data, word32 le !defined(WOLFSSL_ARMASM) && !defined(WOLFSSL_PPC64_ASM) && \ !defined(WOLFSSL_RISCV_ASM) #if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2)) - if (!IS_INTEL_AVX1(intel_flags) && !IS_INTEL_AVX2(intel_flags)) + if (WC_SHA512_REV_BLOCK()) #endif { #ifdef WOLFSSL_WIDE_BYTE @@ -2091,10 +2127,11 @@ static WC_INLINE int Sha512Update(wc_Sha512* sha512, const byte* data, word32 le len -= blocksLen; } #else -#if (defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ - (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2))) +#if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ + (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2)) - #ifdef WC_NO_INTERNAL_FUNCTION_POINTERS +#if defined(WC_NO_INTERNAL_FUNCTION_POINTERS) if (sha_method != SHA512_C) #else if (Transform_Sha512_Len_p != NULL) @@ -2126,9 +2163,10 @@ static WC_INLINE int Sha512Update(wc_Sha512* sha512, const byte* data, word32 le len -= WC_SHA512_BLOCK_SIZE; #if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2)) && \ !defined(WC_SHA512_RAW_BE_BUFFER) - if (!IS_INTEL_AVX1(intel_flags) && !IS_INTEL_AVX2(intel_flags)) + if (WC_SHA512_REV_BLOCK()) { ByteReverseWords64(sha512->buffer, sha512->buffer, WC_SHA512_BLOCK_SIZE); @@ -2275,8 +2313,9 @@ static WC_INLINE int Sha512Final(wc_Sha512* sha512) sha512->buffLen += WC_SHA512_BLOCK_SIZE - sha512->buffLen; #if defined(LITTLE_ENDIAN_ORDER) && !defined(WC_SHA512_RAW_BE_BUFFER) #if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2)) - if (!IS_INTEL_AVX1(intel_flags) && !IS_INTEL_AVX2(intel_flags)) + if (WC_SHA512_REV_BLOCK()) #endif { @@ -2362,8 +2401,9 @@ static WC_INLINE int Sha512Final(wc_Sha512* sha512) #else #if defined(LITTLE_ENDIAN_ORDER) && !defined(WC_SHA512_RAW_BE_BUFFER) #if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2)) - if (!IS_INTEL_AVX1(intel_flags) && !IS_INTEL_AVX2(intel_flags)) + if (WC_SHA512_REV_BLOCK()) #endif #if (!defined(WOLFSSL_ESP32_CRYPT) || \ defined(NO_WOLFSSL_ESP32_CRYPT_HASH) || \ @@ -2384,12 +2424,13 @@ static WC_INLINE int Sha512Final(wc_Sha512* sha512) #endif /* WOLFSSL_WIDE_BYTE */ #if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2)) #ifdef WC_SHA512_RAW_BE_BUFFER /* raw-buffer convention -- the length words must be big-endian in the * stream regardless of which transform consumes the final block. */ #else - if (IS_INTEL_AVX1(intel_flags) || IS_INTEL_AVX2(intel_flags)) + if (!WC_SHA512_REV_BLOCK()) #endif { ByteReverseWords64(&(sha512->buffer[WC_SHA512_BLOCK_SIZE / sizeof(word64) - 2]), @@ -2677,8 +2718,9 @@ int wc_Sha512Transform(wc_Sha512* sha, const unsigned char* data) #if defined(LITTLE_ENDIAN_ORDER) && !defined(WC_SHA512_RAW_BE_BUFFER) #if defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2)) - if (!IS_INTEL_AVX1(intel_flags) && !IS_INTEL_AVX2(intel_flags)) + if (WC_SHA512_REV_BLOCK()) #endif { ByteReverseWords64((word64*)data, (word64*)data, @@ -2865,6 +2907,7 @@ static int InitSha384(wc_Sha384* sha384) sha384->hiLen = 0; #if (defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ + !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2))) || \ defined(WOLFSSL_ARMASM) Sha512_SetTransform(); diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index 1c159b176ad..e1ba2d9ee49 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -48,6 +48,7 @@ nobase_include_HEADERS+= \ wolfssl/wolfcrypt/chacha.h \ wolfssl/wolfcrypt/chacha20_poly1305.h \ wolfssl/wolfcrypt/random.h \ + wolfssl/wolfcrypt/linuxkm_get_entropy.h \ wolfssl/wolfcrypt/wolfentropy.h \ wolfssl/wolfcrypt/rng_bank.h \ wolfssl/wolfcrypt/ripemd.h \ diff --git a/wolfssl/wolfcrypt/linuxkm_get_entropy.h b/wolfssl/wolfcrypt/linuxkm_get_entropy.h new file mode 100644 index 00000000000..7eca19ed049 --- /dev/null +++ b/wolfssl/wolfcrypt/linuxkm_get_entropy.h @@ -0,0 +1,135 @@ +/* linuxkm_get_entropy.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* In-boundary service for the Linux kernel's get_random_bytes(). The + * SP 800-90C Sec. 7 DRBG tree it implements is described in + * linuxkm/RBGC-design.md and in wolfcrypt/src/linuxkm_get_entropy.c. + * + * WOLFSSL_LOCAL throughout: none of this is module API. Every caller is + * linuxkm/module_hooks.c in the same module, and the kernel reaches + * wc_grb_service() through a registered function pointer, not a symbol. The + * cryptographic service these run is wc_RNG_GenerateBlock(), which is already + * the fips.c wrapper and already carries the FipsAllowed() and AlgoAllowed() + * checks, so nothing here needs wrapping a second time. */ + +#ifndef WOLF_CRYPT_LINUXKM_GET_ENTROPY_H +#define WOLF_CRYPT_LINUXKM_GET_ENTROPY_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +#if defined(WOLFSSL_LINUXKM) && defined(LINUXKM_RBGC) + +/* Contexts the counters are indexed by: process, softirq, hardirq, nmi. */ +#define WC_GRB_CTX_N 4 + +/* Index-addressed snapshot rather than a struct: a consumer links to this out + * of tree, so a mismatched layout would corrupt silently. Appending never + * shifts an existing index, and the call returns how many it wrote. */ +enum wc_grb_stat_idx { + WC_GRB_ST_CALLS = 0, /* + context, 4 slots each */ + WC_GRB_ST_SERVED = 4, + WC_GRB_ST_FAILED = 8, + /* Only the NMI generation check declines, and only if a leaf were + * reseeded underneath a read. Zero on every measured run. */ + WC_GRB_ST_DECLINED = 12, + WC_GRB_ST_RESEEDS = 16, + WC_GRB_ST_RESEED_FAILED = 17, + WC_GRB_ST_SINCE_RESEED = 18, + WC_GRB_ST_RESEED_AT = 19, + WC_GRB_ST_LAST_ERR = 20, + WC_GRB_ST_REGISTERED = 21, + WC_GRB_ST_RNG_READY = 22, + /* The root refreshed from the noise source, as against the leaf reseeds + * above: different events, and very different costs. */ + WC_GRB_ST_ROOT_RESEEDS = 23, + WC_GRB_ST_ROOT_FAILED = 24, + WC_GRB_ST_CTX_RESEEDS = 25, /* + context, 4 slots */ + /* Interrupts-off duration per chunk, + context. -1 when the module was + * not built with WC_GRB_MEASURE, which is the shipping default: the + * measurement costs two clock reads per chunk. */ + WC_GRB_ST_IRQ_MAXNS = 29, + WC_GRB_ST_CHUNKS = 33, + /* Maintenance reseeds skipped because hotplug moved the work off the CPU + * whose leaf it targets. Not a decline: no caller was turned away. */ + WC_GRB_ST_MAINT_DEFERRED = 37, + /* NMI requests answered by the leaf's other instantiation because the live + * one could not. Each one is a request that would otherwise have been + * handed back to the kernel's own generator, so this is the count of + * fall-throughs avoided, not a count of errors. */ + WC_GRB_ST_NMI_ALT = 38, + WC_GRB_STAT_N = 39 +}; + +/* Interrupts-off histogram for one context. Bucket k is [2^(k-1), 2^k) units + * of 1024 ns; bucket 0 is under 1024 ns. Returns how many it wrote, 0 when + * the module was not built with WC_GRB_MEASURE. */ +#define WC_GRB_IRQ_BUCKETS 16 +WOLFSSL_LOCAL int wc_grb_irq_hist(int ctx, long long *out, int n); + +/* Bring the tree up and tear it down. These do not install the kernel hook: + * the container must have no unresolved symbols, so an in-boundary file cannot + * call wolfssl_linuxkm_register_random_bytes_handlers(). The glue does + * that. */ +WOLFSSL_LOCAL int wc_grb_init(int ncpus); +WOLFSSL_LOCAL void wc_grb_cleanup(void); + +/* The service. Signature matches _get_random_bytes_cb_t from + * , i.e. the ._get_random_bytes member of + * struct wolfssl_linuxkm_random_bytes_handlers. */ +WOLFSSL_LOCAL int wc_grb_service(void *buf, size_t len); + +/* Told to us by the glue after a successful registration. */ +WOLFSSL_LOCAL void wc_grb_set_registered(int on); + +/* Nonzero once the hook is installed. A consumer must assert this before + * trusting a clean run: zero failures from an unregistered hook look exactly + * like zero failures from a working one. */ +WOLFSSL_LOCAL int wc_grb_service_active(void); + +/* Reseed the leaves belonging to one CPU, from the root. Process context + * only: the root's own generate may gather entropy, and that has to be able to + * block. The glue pins this to cpu; a negative cpu means "wherever this call + * happens to be", which the service path's self-help uses. */ +WOLFSSL_LOCAL int wc_grb_maintain_cpu(int cpu); + +/* One maintenance tick. The root refreshes from the noise source on its own + * jittered schedule, independent of leaf demand. */ +WOLFSSL_LOCAL int wc_grb_root_tick(void); + +/* Separates boot-time demand from steady state in the counters. */ +WOLFSSL_LOCAL void wc_grb_mark_boot_done(void); + +/* Live snapshot, so a consumer can report while a run is in progress rather + * than only at unload. 64-bit throughout: a 32-bit count wraps within hours + * at the rate this path is driven. */ +WOLFSSL_LOCAL int wc_grb_stat_snapshot(long long *out, int n); + +#endif /* WOLFSSL_LINUXKM && LINUXKM_RBGC */ + +#ifdef __cplusplus + } +#endif + +#endif /* WOLF_CRYPT_LINUXKM_GET_ENTROPY_H */ diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index d9c5a9289b9..ec0ec403913 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -454,6 +454,14 @@ WOLFSSL_ABI WOLFSSL_API void wc_rng_free(WC_RNG* rng); #ifndef WC_NO_RNG +#ifdef LINUXKM_RBGC +/* SP 800-90C Sec. 7.2.1.2 instantiation of a non-root RBGC construction from + * its parent. WOLFSSL_LOCAL, not WOLFSSL_API: internal to the RBGC and never + * exported. 3s/2 bits are drawn from the parent; see the definition. */ +#define WC_RBGC_INSTANTIATE_SZ (RNG_SECURITY_STRENGTH * 3 / 2 / 8) +WOLFSSL_LOCAL int wc_InitRngRBGC(WC_RNG* rng, WC_RNG* parent); +#endif + WOLFSSL_ABI WOLFSSL_API int wc_InitRng(WC_RNG* rng); WOLFSSL_API int wc_InitRng_ex(WC_RNG* rng, void* heap, int devId); WOLFSSL_API int wc_InitRngNonce(WC_RNG* rng, byte* nonce, word32 nonceSz); From 06fb32a5ee377f11b693f003f5d6a34e5e236c61 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 11:32:17 -0600 Subject: [PATCH 02/10] linuxkm/patches: document host gcc per kernel and the IBT objtool flag --- linuxkm/patches/README.md | 83 ++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/linuxkm/patches/README.md b/linuxkm/patches/README.md index 3a0d060f99f..0253df98d5a 100644 --- a/linuxkm/patches/README.md +++ b/linuxkm/patches/README.md @@ -25,6 +25,7 @@ Two columns carry the evidence, and they are deliberately separate: | column | meaning | |---|---| | `source` | Where the patch FILE lives -- provenance only, NOT a confidence level. `SHIPPED` means a directory named for this series serves it directly; `DERIVED` means a base belonging to another series serves it. **A DERIVED row is tested exactly as hard as a SHIPPED one** -- see the `tested` column, which is what says whether it was verified. | +| `gcc` | Host gcc the kernel tree at `verified at` has actually been BUILT with here. Blank means untried, not unsupported. | | `tested` | `TESTED` -- on 2026-08-22 this row was measured end to end: the patch applied at `--fuzz=0`, `drivers/char/random.o` was built from the patched tree with no diagnostic the pristine tree did not also emit, the symbols the module binds to were present AND exported, and a consumer translation unit using those declarations the way `linuxkm/module_hooks.c` does compiled. 39 of 39 rows passed all four. Controls: a wrong-side base fails to compile; a deleted `EXPORT_SYMBOL_GPL` still COMPILES CLEAN while reading NOT-EXPORTED, which is why 'it compiles' is not the test. | **Every row applies at `--fuzz=0`.** That was re-measured for all thirty-four @@ -84,47 +85,47 @@ If the kernel is left unpatched, nothing silently degrades: the module fails to load with `Unknown symbol wolfssl_linuxkm_register_random_bytes_handlers` rather than running without the hook. See `linuxkm/README.md`. -| version | verified at | patch that serves it | source | tested | -|---|---|---|---|---| -| 5.6 | 5.6.19 | `5.6/WOLFSSL_KERNELv5_6_FIPS.patch` | SHIPPED | TESTED | -| 5.7 | 5.7.19 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | DERIVED | TESTED | -| 5.8 | 5.8.18 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | DERIVED | TESTED | -| 5.9 | 5.9.16 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | DERIVED | TESTED | -| 5.10 | 5.10.17 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | SHIPPED | TESTED | -| 5.10 | 5.10.265 | `5.10.236/WOLFSSL_KERNELv5_10_236_FIPS.patch` | SHIPPED | TESTED | -| 5.11 | 5.11.22 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | DERIVED | TESTED | -| 5.12 | 5.12.19 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | DERIVED | TESTED | -| 5.13 | 5.13.19 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | DERIVED | TESTED | -| 5.15 | 5.15 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | SHIPPED | TESTED | -| 5.15 | 5.15.216 | `5.17-ubuntu-jammy-tegra/WOLFSSL_KERNELv5_17_tegra_FIPS.patch` | DERIVED | TESTED | -| 5.16 | 5.16.20 | `5.16/WOLFSSL_KERNELv5_16_FIPS.patch` | SHIPPED | TESTED | -| 5.17 | 5.17.11 | `5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch` | SHIPPED | TESTED | -| 5.17 | 5.17.13 | `5.17/WOLFSSL_KERNELv5_17_FIPS.patch` | SHIPPED | TESTED | -| 5.17 | 5.17.15 | `5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch` | SHIPPED | TESTED | -| 5.18 | 5.18.19 | `5.18/WOLFSSL_KERNELv5_18_FIPS.patch` | SHIPPED | TESTED | -| 5.19 | 5.19.17 | `6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch` | DERIVED | TESTED | -| 6.0 | 6.0.19 | `6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch` | DERIVED | TESTED | -| 6.1 | 6.1.183 | `6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch` | SHIPPED | TESTED | -| 6.2 | 6.2.16 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | -| 6.4 | 6.4.16 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | -| 6.5 | 6.5.13 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | -| 6.6 | 6.6.99 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | -| 6.6 | 6.6.152 | `6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch` | DERIVED | TESTED | -| 6.7 | 6.7.12 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | -| 6.8 | 6.8.12 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | -| 6.9 | 6.9.12 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | -| 6.10 | 6.10.14 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | -| 6.11 | 6.11.11 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | -| 6.12 | 6.12.104 | `6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch` | SHIPPED | TESTED | -| 6.13 | 6.13.12 | `6.15/WOLFSSL_KERNELv6_15_FIPS.patch` | DERIVED | TESTED | -| 6.14 | 6.14.11 | `6.15/WOLFSSL_KERNELv6_15_FIPS.patch` | DERIVED | TESTED | -| 6.15 | 6.15.11 | `6.15/WOLFSSL_KERNELv6_15_FIPS.patch` | SHIPPED | TESTED | -| 6.16 | 6.16.12 | `6.16/WOLFSSL_KERNELv6_16_FIPS.patch` | SHIPPED | TESTED | -| 6.17 | 6.17.13 | `6.16/WOLFSSL_KERNELv6_16_FIPS.patch` | DERIVED | TESTED | -| 6.18 | 6.18.45 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | DERIVED | TESTED | -| 6.19 | 6.19.14 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | DERIVED | TESTED | -| 7.0 | 7.0.14 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | SHIPPED | TESTED | -| 7.1 | 7.1.9 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | DERIVED | TESTED | +| version | verified at | patch that serves it | source | tested | gcc | +|---|---|---|---|---|---| +| 5.6 | 5.6.19 | `5.6/WOLFSSL_KERNELv5_6_FIPS.patch` | SHIPPED | TESTED | | +| 5.7 | 5.7.19 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | DERIVED | TESTED | | +| 5.8 | 5.8.18 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | DERIVED | TESTED | | +| 5.9 | 5.9.16 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | DERIVED | TESTED | | +| 5.10 | 5.10.17 | `5.10.17/WOLFSSL_KERNELv5_10_17_FIPS.patch` | SHIPPED | TESTED | | +| 5.10 | 5.10.265 | `5.10.236/WOLFSSL_KERNELv5_10_236_FIPS.patch` | SHIPPED | TESTED | | +| 5.11 | 5.11.22 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | DERIVED | TESTED | | +| 5.12 | 5.12.19 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | DERIVED | TESTED | | +| 5.13 | 5.13.19 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | DERIVED | TESTED | | +| 5.15 | 5.15 | `5.15/WOLFSSL_KERNELv5_15_FIPS.patch` | SHIPPED | TESTED | | +| 5.15 | 5.15.216 | `5.17-ubuntu-jammy-tegra/WOLFSSL_KERNELv5_17_tegra_FIPS.patch` | DERIVED | TESTED | | +| 5.16 | 5.16.20 | `5.16/WOLFSSL_KERNELv5_16_FIPS.patch` | SHIPPED | TESTED | | +| 5.17 | 5.17.11 | `5.17.0/WOLFSSL_KERNELv5_17_0_FIPS.patch` | SHIPPED | TESTED | | +| 5.17 | 5.17.13 | `5.17/WOLFSSL_KERNELv5_17_FIPS.patch` | SHIPPED | TESTED | | +| 5.17 | 5.17.15 | `5.17.14/WOLFSSL_KERNELv5_17_14_FIPS.patch` | SHIPPED | TESTED | | +| 5.18 | 5.18.19 | `5.18/WOLFSSL_KERNELv5_18_FIPS.patch` | SHIPPED | TESTED | | +| 5.19 | 5.19.17 | `6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch` | DERIVED | TESTED | | +| 6.0 | 6.0.19 | `6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch` | DERIVED | TESTED | | +| 6.1 | 6.1.183 | `6.1.73/WOLFSSL_KERNELv6_1_73_FIPS.patch` | SHIPPED | TESTED | | +| 6.2 | 6.2.16 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | | +| 6.4 | 6.4.16 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | | +| 6.5 | 6.5.13 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | | +| 6.6 | 6.6.99 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | | +| 6.6 | 6.6.152 | `6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch` | DERIVED | TESTED | | +| 6.7 | 6.7.12 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | | +| 6.8 | 6.8.12 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | | +| 6.9 | 6.9.12 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | | +| 6.10 | 6.10.14 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | | +| 6.11 | 6.11.11 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | DERIVED | TESTED | | +| 6.12 | 6.12.104 | `6.12.75/WOLFSSL_KERNELv6_12_75_FIPS.patch` | SHIPPED | TESTED | | +| 6.13 | 6.13.12 | `6.15/WOLFSSL_KERNELv6_15_FIPS.patch` | DERIVED | TESTED | | +| 6.14 | 6.14.11 | `6.15/WOLFSSL_KERNELv6_15_FIPS.patch` | DERIVED | TESTED | | +| 6.15 | 6.15.11 | `6.15/WOLFSSL_KERNELv6_15_FIPS.patch` | SHIPPED | TESTED | | +| 6.16 | 6.16.12 | `6.16/WOLFSSL_KERNELv6_16_FIPS.patch` | SHIPPED | TESTED | 13.3, 14.3 | +| 6.17 | 6.17.13 | `6.16/WOLFSSL_KERNELv6_16_FIPS.patch` | DERIVED | TESTED | | +| 6.18 | 6.18.45 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | DERIVED | TESTED | | +| 6.19 | 6.19.14 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | DERIVED | TESTED | | +| 7.0 | 7.0.14 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | SHIPPED | TESTED | | +| 7.1 | 7.1.9 | `7.0/WOLFSSL_KERNELv7_0_FIPS.patch` | DERIVED | TESTED | 14.3 | One of the seventeen directories is never named by a row above, because the version it was authored against is not in the supported list: From d5cdafa8a9a5cd5b01ea2a6c3a8bc505a0d74899 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 11:32:48 -0600 Subject: [PATCH 03/10] linuxkm/patches: add run-test.sh to build and test patched kernels --- .gitignore | 1 + linuxkm/patches/run-test.sh | 149 ++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100755 linuxkm/patches/run-test.sh diff --git a/.gitignore b/.gitignore index 07b4e415747..c9acaf4b399 100644 --- a/.gitignore +++ b/.gitignore @@ -526,3 +526,4 @@ compile_commands.json # Python cache __pycache__/ +linuxkm/patches/work/ diff --git a/linuxkm/patches/run-test.sh b/linuxkm/patches/run-test.sh new file mode 100755 index 00000000000..d7a1e6ad372 --- /dev/null +++ b/linuxkm/patches/run-test.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# +# run-test.sh -- reproduce the kernel-side test setup for these patches. +# +# ./run-test.sh # 5.15.170, 6.16.12, 7.1.9 +# ./run-test.sh 6.16.12 # just one +# ./run-test.sh --list +# +# For each version: check the toolchain, fetch the tarball, apply the patch the +# coverage table in README.md says serves it, build the kernel far enough to get +# Module.symvers, confirm the hook symbol is EXPORTED, then build the wolfSSL +# module against it and confirm the RBGC symbols are in the .ko. +# +# Work lands in ./work/ beside this script; set WORK= to move it. Each kernel +# tree is roughly 2G, and anything running "git clean -xdff" over this checkout +# will delete the default location mid-build. +# +# Env: WORK, WOLFSSL_SRC, FIPS_FLAVOR (e.g. FIPS_FLAVOR=v7). +set -u -o pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +WORK="${WORK:-$HERE/work}" +WOLFSSL_SRC="${WOLFSSL_SRC:-$(cd "$HERE/../.." && pwd)}" + +# version : gcc : patch base : kernel.org path +KNOWN=" +5.15.170:gcc-10:5.17-ubuntu-jammy-tegra:v5.x +6.16.12:gcc:6.16:v6.x +7.1.9:gcc:7.0:v7.x +" + +say() { printf '%s\n' "$*"; } +head_() { printf '\n=== %s\n' "$*"; } +die() { printf 'run-test: %s\n' "$*" >&2; exit 1; } + +if [ "${1:-}" = "--list" ]; then + printf '%-10s %-8s %s\n' version gcc base + echo "$KNOWN" | while IFS=: read -r v g b u; do + [ -n "${v:-}" ] && printf '%-10s %-8s %s\n' "$v" "$g" "$b" + done + exit 0 +fi + +WANT=("$@"); [ ${#WANT[@]} -eq 0 ] && WANT=(5.15.170 6.16.12 7.1.9) + +missing="" +for t in curl tar make patch nm bc flex bison; do + command -v "$t" >/dev/null 2>&1 || missing="$missing $t" +done +for v in "${WANT[@]}"; do + line=$(echo "$KNOWN" | grep "^$v:") || die "unknown version $v (try --list)" + g=$(echo "$line" | cut -d: -f2) + command -v "$g" >/dev/null 2>&1 || missing="$missing $g" +done +[ -f "$WOLFSSL_SRC/configure.ac" ] || die "no wolfSSL source at $WOLFSSL_SRC (set WOLFSSL_SRC)" +[ -x "$HERE/patch-kernel.sh" ] || die "patch-kernel.sh missing beside this script" + +if [ -n "$missing" ]; then + say "run-test: missing:$missing" + say "" + say " sudo apt install build-essential curl bc flex bison libelf-dev libssl-dev" + say "" + say "Older trees need an older compiler; 5.15 is built here with gcc-10:" + say " sudo apt install gcc-10" + exit 1 +fi + +mkdir -p "$WORK/tarballs" "$WORK/shim" +rc_all=0 + +for v in "${WANT[@]}"; do + line=$(echo "$KNOWN" | grep "^$v:") + GCC=$(echo "$line" | cut -d: -f2) + BASE=$(echo "$line" | cut -d: -f3) + URLD=$(echo "$line" | cut -d: -f4) + DIR="$WORK/linux-$v" + TB="$WORK/tarballs/linux-$v.tar.xz" + LOG="$WORK/linux-$v.log" + + head_ "$v (gcc $GCC, base $BASE)" + + # tools/objtool resolves its own compiler, so CC= and HOSTCC= do not reach + # it. A PATH shim is what actually pins the version. + ln -sf "$(command -v "$GCC")" "$WORK/shim/gcc" + ln -sf "$(command -v "$GCC")" "$WORK/shim/cc" + export PATH="$WORK/shim:$PATH" + + if [ ! -s "$TB" ]; then + say " downloading" + curl -fL --retry 3 --no-progress-meter -o "$TB.part" \ + "https://cdn.kernel.org/pub/linux/kernel/$URLD/linux-$v.tar.xz" \ + || { say " FAIL download"; rm -f "$TB.part"; rc_all=1; continue; } + mv "$TB.part" "$TB" + fi + [ -d "$DIR" ] || { say " extracting" + tar -C "$WORK" -xf "$TB" || { say " FAIL extract"; rc_all=1; continue; }; } + + # The base is forced. patch-kernel.sh takes the first coverage row for a + # series, which for 5.15.170 is the 5.15 base, whose hunks fail at --fuzz=0; + # README.md's sublevel table puts that version on the tegra base. + if grep -qs wolfssl_linuxkm_register_random_bytes_handlers "$DIR/include/linux/random.h"; then + say " already patched" + else + say " applying $BASE" + "$HERE/patch-kernel.sh" "$DIR" "$BASE" >"$LOG" 2>&1 \ + || { say " FAIL patch -- see $LOG"; rc_all=1; continue; } + fi + + # A full build, not modules_prepare: modules_prepare leaves no + # Module.symvers, and without that modpost calls the hook symbol undefined, + # which looks exactly like the patch not having worked. + if [ ! -f "$DIR/Module.symvers" ]; then + say " building kernel with $GCC (several minutes)" + { make -C "$DIR" -j"$(nproc)" defconfig + make -C "$DIR" -j"$(nproc)" + } >>"$LOG" 2>&1 || { say " FAIL kernel build -- see $LOG"; rc_all=1; continue; } + fi + grep -qs wolfssl_linuxkm_register_random_bytes_handlers "$DIR/Module.symvers" \ + || { say " FAIL hook not exported in Module.symvers"; rc_all=1; continue; } + say " kernel ok, hook exported" + + # CONFIG_X86_KERNEL_IBT (defconfig, 6.2+) makes Kbuild run objtool over the + # linked libwolfssl.o, which OBJECT_FILES_NON_STANDARD does not cover, and + # it rejects the return thunks Kbuild inlines on purpose. + OBJTOOL_OFF= + if grep -qs "^CONFIG_X86_KERNEL_IBT=y" "$DIR/.config"; then + OBJTOOL_OFF=KBUILD_EXTRA_FLAGS=FORCE_GLOBAL_OBJTOOL_OFF=1 + say " IBT on -- adding FORCE_GLOBAL_OBJTOOL_OFF=1" + fi + + say " building wolfSSL module" + MLOG="$WORK/linux-$v.module.log" + ( cd "$WOLFSSL_SRC" \ + && ./configure --enable-linuxkm --enable-linuxkm-pie --disable-sp-asm \ + --enable-linuxkm-rbgc "--with-linux-source=$DIR" \ + ${FIPS_FLAVOR:+--enable-fips=$FIPS_FLAVOR} \ + && env KERNEL_EXTRA_CFLAGS_REMOVE=-pg FORCE_NO_MODULE_SIG=1 $OBJTOOL_OFF \ + make -j"$(nproc)" ) >"$MLOG" 2>&1 \ + || { say " FAIL module build -- see $MLOG"; rc_all=1; continue; } + + # The RBGC sources are gated, so a build that compiled none of them still + # links clean. Count the symbols rather than trusting the exit status. + n=$(nm "$WOLFSSL_SRC/linuxkm/libwolfssl.ko" 2>/dev/null | grep -c ' [tT] wc_grb_') + [ "${n:-0}" -ge 1 ] \ + || { say " FAIL module built with 0 wc_grb_ symbols"; rc_all=1; continue; } + say " PASS $n wc_grb_ symbols" +done + +exit $rc_all From 952d24452474642ac38ccb6291edf31a553ef94f Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 11:35:19 -0600 Subject: [PATCH 04/10] linuxkm/patches: run-test.sh defaults to FIPS v7 and checks for fips.c --- linuxkm/patches/run-test.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/linuxkm/patches/run-test.sh b/linuxkm/patches/run-test.sh index d7a1e6ad372..7356f302afd 100755 --- a/linuxkm/patches/run-test.sh +++ b/linuxkm/patches/run-test.sh @@ -15,12 +15,17 @@ # tree is roughly 2G, and anything running "git clean -xdff" over this checkout # will delete the default location mid-build. # -# Env: WORK, WOLFSSL_SRC, FIPS_FLAVOR (e.g. FIPS_FLAVOR=v7). +# Env: WORK, WOLFSSL_SRC, FIPS_FLAVOR (default v7). +# +# RBGC only compiles under FIPS v7+ -- src/include.am puts BUILD_LINUXKM_RBGC +# inside BUILD_FIPS_V7_PLUS -- so the FIPS sources have to be in place first, +# via fips-check.sh or your own symlinks. set -u -o pipefail HERE="$(cd "$(dirname "$0")" && pwd)" WORK="${WORK:-$HERE/work}" WOLFSSL_SRC="${WOLFSSL_SRC:-$(cd "$HERE/../.." && pwd)}" +FIPS_FLAVOR="${FIPS_FLAVOR:-v7}" # version : gcc : patch base : kernel.org path KNOWN=" @@ -53,6 +58,11 @@ for v in "${WANT[@]}"; do command -v "$g" >/dev/null 2>&1 || missing="$missing $g" done [ -f "$WOLFSSL_SRC/configure.ac" ] || die "no wolfSSL source at $WOLFSSL_SRC (set WOLFSSL_SRC)" +if [ -n "$FIPS_FLAVOR" ] && [ ! -s "$WOLFSSL_SRC/wolfcrypt/src/fips.c" ]; then + die "FIPS_FLAVOR=$FIPS_FLAVOR but wolfcrypt/src/fips.c is absent. +Put the FIPS sources in place first (fips-check.sh), or set FIPS_FLAVOR= to +build without FIPS -- but note RBGC compiles no sources at all in that case." +fi [ -x "$HERE/patch-kernel.sh" ] || die "patch-kernel.sh missing beside this script" if [ -n "$missing" ]; then @@ -133,7 +143,7 @@ for v in "${WANT[@]}"; do ( cd "$WOLFSSL_SRC" \ && ./configure --enable-linuxkm --enable-linuxkm-pie --disable-sp-asm \ --enable-linuxkm-rbgc "--with-linux-source=$DIR" \ - ${FIPS_FLAVOR:+--enable-fips=$FIPS_FLAVOR} \ + ${FIPS_FLAVOR:+--enable-fips="$FIPS_FLAVOR"} \ && env KERNEL_EXTRA_CFLAGS_REMOVE=-pg FORCE_NO_MODULE_SIG=1 $OBJTOOL_OFF \ make -j"$(nproc)" ) >"$MLOG" 2>&1 \ || { say " FAIL module build -- see $MLOG"; rc_all=1; continue; } From e7eb2833394e17ccadd9d20689ecf1a103dcb786 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 11:39:37 -0600 Subject: [PATCH 05/10] linuxkm/patches: pick the base by patchlevel range, not first series row --- linuxkm/patches/patch-kernel.sh | 37 +++++++++++++++++++++++++++++++-- linuxkm/patches/run-test.sh | 5 ++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/linuxkm/patches/patch-kernel.sh b/linuxkm/patches/patch-kernel.sh index 5d7daa4d6c3..54120448df5 100755 --- a/linuxkm/patches/patch-kernel.sh +++ b/linuxkm/patches/patch-kernel.sh @@ -42,9 +42,42 @@ printf 'kernel : %s (series %s)\n' "$V" "$SERIES" # --- pick the base ---------------------------------------------------------- if [ -z "$BASE" ]; then + # A series that changes random.c shape mid-series has several rows, and the + # coverage table's "verified at" is one tested point in each range, not the + # range itself. Taking the first coverage row for the series therefore + # picks the wrong base: 5.15.170 got the `5.15` base, whose hunks fail + # outright, when it belongs on the tegra base. So consult the + # "A series can change shape mid-series" table first -- that one states the + # patchlevel ranges. + BASE_PATCH=$(awk -v v="$V" -v s="$SERIES" -F'|' ' + /^\| series \| patchlevels \| patch \|/ {inseam=1; next} + inseam && $0 !~ /^\|/ {inseam=0} + !inseam {next} + { + series=$2; gsub(/ /,"",series) + if (series != s) next + spec=$3 + n=0; delete lim + while (match(spec, /[0-9]+\.[0-9]+(\.[0-9]+)?/)) { + lim[++n]=substr(spec,RSTART,RLENGTH) + spec=substr(spec,RSTART+RLENGTH) + } + if (n==0) next + hi = lim[n] + open = (spec ~ /^\+/) # "6.6.152+" has no upper bound + if (open) { print trim($4); exit } + if (cmp(v,hi) <= 0) { print trim($4); exit } + } + function trim(x) { gsub(/[` ]/,"",x); return x } + function cmp(a,b, x,y,i) { + split(a,x,"."); split(b,y,".") + for (i=1;i<=3;i++) { x[i]+=0; y[i]+=0 + if (x[i]y[i]) return 1 } + return 0 + }' "$HERE/README.md" || true) # Rows look like: | 6.6 | 6.6.99 | `6.12/WOLFSSL_KERNELv6_12_FIPS.patch` | ... - # Prefer a row whose "verified at" matches exactly, else the series row. - BASE_PATCH=$(awk -v v="$V" -F'|' ' + # Then an exact "verified at" match, then the series row. + [ -n "${BASE_PATCH:-}" ] || BASE_PATCH=$(awk -v v="$V" -F'|' ' $0 ~ /^\|/ && $3 ~ v {gsub(/[` ]/,"",$4); print $4; exit}' "$HERE/README.md" || true) [ -n "${BASE_PATCH:-}" ] || BASE_PATCH=$(awk -v s="$SERIES" -F'|' ' $0 ~ /^\|/ {gsub(/ /,"",$2); if ($2==s) {gsub(/[` ]/,"",$4); print $4; exit}}' "$HERE/README.md" || true) diff --git a/linuxkm/patches/run-test.sh b/linuxkm/patches/run-test.sh index 7356f302afd..2e4941dd3e8 100755 --- a/linuxkm/patches/run-test.sh +++ b/linuxkm/patches/run-test.sh @@ -105,9 +105,8 @@ for v in "${WANT[@]}"; do [ -d "$DIR" ] || { say " extracting" tar -C "$WORK" -xf "$TB" || { say " FAIL extract"; rc_all=1; continue; }; } - # The base is forced. patch-kernel.sh takes the first coverage row for a - # series, which for 5.15.170 is the 5.15 base, whose hunks fail at --fuzz=0; - # README.md's sublevel table puts that version on the tegra base. + # The base is stated rather than inferred, so a change to the tables in + # README.md cannot silently move what this reproduces. if grep -qs wolfssl_linuxkm_register_random_bytes_handlers "$DIR/include/linux/random.h"; then say " already patched" else From f4f48da7660ea4a1fa09a31f6b0dc49d37b37578 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 11:52:46 -0600 Subject: [PATCH 06/10] linuxkm: pair thunk flags with the right mitigation, accept old names --- linuxkm/Kbuild | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/linuxkm/Kbuild b/linuxkm/Kbuild index c385ba969a7..052d63d4f21 100644 --- a/linuxkm/Kbuild +++ b/linuxkm/Kbuild @@ -195,13 +195,20 @@ ifeq "$(ENABLED_LINUXKM_PIE)" "yes" # __x86_indirect_thunk_foo implementations. _all_ references must be # eliminated, not just those in PIE objects, otherwise some kernels will # false-positively complain about unpatched thunks. - ifeq "$(CONFIG_MITIGATION_RETPOLINE)" "y" + # RETHUNK is the return-thunk mitigation and gates -mfunction-return; + # RETPOLINE is the indirect-branch one and gates -mindirect-branch. Both + # gained a MITIGATION_ prefix in 6.9, so both spellings are accepted -- + # testing only the new name silently takes the "keep" branch on every + # kernel older than that. + WC_KM_RETHUNK := $(or $(CONFIG_MITIGATION_RETHUNK),$(CONFIG_RETHUNK)) + WC_KM_RETPOLINE := $(or $(CONFIG_MITIGATION_RETPOLINE),$(CONFIG_RETPOLINE)) + ifeq "$(WC_KM_RETHUNK)" "y" PIE_SUPPORT_FLAGS += -mfunction-return=thunk-inline else PIE_SUPPORT_FLAGS += -mfunction-return=keep endif - ifeq "$(CONFIG_MITIGATION_RETHUNK)" "y" - PIE_SUPPORT_FLAGS += -mindirect-branch=thunk-inline + ifeq "$(WC_KM_RETPOLINE)" "y" + PIE_SUPPORT_FLAGS += -mindirect-branch=thunk-inline else PIE_SUPPORT_FLAGS += -mindirect-branch=keep endif From 1d3d50aad6793da67e773dc43153433fbda7c441 Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 12:11:16 -0600 Subject: [PATCH 07/10] random: keep _InitRng declarations ahead of the unused-arg casts --- wolfcrypt/src/random.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 76d865ca9e9..187f22cb482 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -1912,10 +1912,6 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, const byte* rbgcSeed, word32 rbgcSeedSz, void* heap, int devId) { -#ifndef WC_RNG_SEED_FROM_CALLER - (void)rbgcSeed; - (void)rbgcSeedSz; -#endif int ret = 0; #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) #if !defined(HAVE_FIPS) && defined(WOLFSSL_RNG_USE_FULL_SEED) @@ -1931,6 +1927,10 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, (void)nonce; (void)nonceSz; +#ifndef WC_RNG_SEED_FROM_CALLER + (void)rbgcSeed; + (void)rbgcSeedSz; +#endif if (rng == NULL) return BAD_FUNC_ARG; From b36f87eea4454441c199c0c210fa2bf5e4d2e74f Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 13:50:59 -0600 Subject: [PATCH 08/10] linuxkm: drop svr-bracket term whose definition is not in this branch --- linuxkm/module_hooks.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/linuxkm/module_hooks.c b/linuxkm/module_hooks.c index 7554a77ba01..d1155c47987 100644 --- a/linuxkm/module_hooks.c +++ b/linuxkm/module_hooks.c @@ -380,16 +380,12 @@ int wc_linuxkm_can_block(void) { * -- stranding kernel_fpu_begin()'s section on the origin CPU for the life * of the module (1 event in 920,727 saves). */ - return (preempt_count() == 0) && (! irqs_disabled()) -/* Guarded exactly like the declaration at the top of this file and like the - * other caller: wc_linuxkm_in_svr_bracket() only exists when the vector - * register glue is compiled in. Without it there are no brackets to be - * inside, so the term is vacuously true and its absence changes nothing. */ -#if !defined(WOLFSSL_LINUXKM_USE_MUTEXES) && \ - defined(WOLFSSL_USE_SAVE_VECTOR_REGISTERS) - && (! wc_linuxkm_in_svr_bracket()) -#endif - ; + /* An open vector-register bracket is a fourth reason not to sleep here, + * but detecting one needs wc_linuxkm_in_svr_bracket(), which is part of + * the vector-register glue and is not in this branch. It has to be added + * back by whichever change brings that glue in; without the definition the + * term is an implicit declaration and does not build under -Werror. */ + return (preempt_count() == 0) && (! irqs_disabled()); } /* for simplicity, we use a global count to suspend signal processing while any From 716f1430fdefd25147c8903a3a61626138f89c0f Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 16:51:37 -0600 Subject: [PATCH 09/10] add WC_GRB_MEASURE to the known-macro list --- .wolfssl_known_macro_extras | 1 + 1 file changed, 1 insertion(+) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 4d52b845037..1365b0fcdb2 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -765,6 +765,7 @@ WC_FIPS_AESGCM_NO_SHORT_NONCES WC_FIPS_AESGCM_ONE_SHOT_EXT_IV_ALLOWED WC_FLAG_DONT_USE_AESNI WC_FORCE_LINUXKM_FORTIFY_SOURCE +WC_GRB_MEASURE WC_HASH_CUSTOM_MAX_BLOCK_SIZE WC_HASH_CUSTOM_MAX_DIGEST_SIZE WC_HASH_CUSTOM_MIN_DIGEST_SIZE From de84baea96780ac4ead01ac3ca8aaf12b37d1aae Mon Sep 17 00:00:00 2001 From: kaleb-himes Date: Wed, 26 Aug 2026 17:09:17 -0600 Subject: [PATCH 10/10] shorten comments in the RBGC code --- linuxkm/Kbuild | 18 ++--- linuxkm/linuxkm_wc_port.h | 19 ++--- linuxkm/module_hooks.c | 50 ++++--------- wolfcrypt/src/linuxkm_get_entropy.c | 104 ++++++++-------------------- wolfcrypt/src/random.c | 46 ++++-------- wolfcrypt/src/sha512.c | 34 ++------- wolfssl/wolfcrypt/random.h | 5 +- 7 files changed, 75 insertions(+), 201 deletions(-) diff --git a/linuxkm/Kbuild b/linuxkm/Kbuild index 052d63d4f21..28e127ec2e3 100644 --- a/linuxkm/Kbuild +++ b/linuxkm/Kbuild @@ -99,14 +99,9 @@ ifeq "$(KERNEL_ARCH_X86)" "yes" endif endif -# -Wextra (from configure's warning set) turns on -Wunused-parameter, which -# fires on the KERNEL's own headers, not on wolfSSL code: e.g. 6.8's -# include/linux/node.h:88 node_set_perf_attrs() is an empty static inline with -# three named parameters when !CONFIG_HMEM_REPORTING. The kernel never builds -# with that warning on -- scripts/Makefile.extrawarn:90 pairs it as -# "-Wextra -Wunused -Wno-unused-parameter" even at W=1 -- so an out-of-tree -# module that adds -Wextra must pair it the same way. This was clang-only; -# gcc fails identically (measured: gcc 14.3 vs linux-6.8.12 defconfig). +# -Wextra turns on -Wunused-parameter, which fires on the kernel's own headers, +# not on ours. The kernel always pairs it with -Wno-unused-parameter, so do +# the same. WOLFSSL_CFLAGS += -Wno-unused-parameter # this rule is needed to get build to succeed in 4.x (get_thread_size still doesn't get built) @@ -195,11 +190,8 @@ ifeq "$(ENABLED_LINUXKM_PIE)" "yes" # __x86_indirect_thunk_foo implementations. _all_ references must be # eliminated, not just those in PIE objects, otherwise some kernels will # false-positively complain about unpatched thunks. - # RETHUNK is the return-thunk mitigation and gates -mfunction-return; - # RETPOLINE is the indirect-branch one and gates -mindirect-branch. Both - # gained a MITIGATION_ prefix in 6.9, so both spellings are accepted -- - # testing only the new name silently takes the "keep" branch on every - # kernel older than that. + # RETHUNK gates -mfunction-return, RETPOLINE gates -mindirect-branch. + # Both were renamed with a MITIGATION_ prefix in 6.9, so accept either. WC_KM_RETHUNK := $(or $(CONFIG_MITIGATION_RETHUNK),$(CONFIG_RETHUNK)) WC_KM_RETPOLINE := $(or $(CONFIG_MITIGATION_RETPOLINE),$(CONFIG_RETPOLINE)) ifeq "$(WC_KM_RETHUNK)" "y" diff --git a/linuxkm/linuxkm_wc_port.h b/linuxkm/linuxkm_wc_port.h index 13a0ebccbdb..7863857858a 100644 --- a/linuxkm/linuxkm_wc_port.h +++ b/linuxkm/linuxkm_wc_port.h @@ -217,23 +217,18 @@ WOLFSSL_API void wc_linuxkm_relax_long_loop(void); #ifdef LINUXKM_RBGC - /* Interrupt save/restore as calls. local_irq_save() expands, on arm64 - * before 6.6, to an ALTERNATIVE() asm whose "r" operand cannot be - * satisfied from a PIE-compiled in-boundary object ("impossible - * constraint in 'asm'"); keeping the expansion in non-PIE glue builds - * on every arm64 tree. */ + /* Interrupt save/restore as calls: on arm64 before 6.6 the inline asm + * in local_irq_save() will not compile inside a PIE object. */ WOLFSSL_API unsigned long wc_linuxkm_irq_save(void); WOLFSSL_API void wc_linuxkm_irq_restore(unsigned long flags); - /* Current CPU index, for the per-CPU DRBG leaves. - * raw_smp_processor_id() reads a kernel symbol the container may not - * reference, so it goes through the redirect table. */ + /* Current CPU index for the per-CPU leaves. Goes through the + * redirect table because the container cannot name the kernel + * symbol. */ WOLFSSL_API int wc_linuxkm_cpu_id(void); - /* Monotonic nanoseconds for the interrupts-off measurement in - * linuxkm_get_entropy.c. ktime_get_mono_fast_ns() is the NMI-safe - * reader (seqcount latch, no lock), so it is legal with interrupts - * off and in NMI. */ + /* Monotonic nanoseconds. The fast reader takes no lock, so it is + * safe with interrupts off and in NMI. */ WOLFSSL_API unsigned long long wc_linuxkm_mono_ns(void); #endif diff --git a/linuxkm/module_hooks.c b/linuxkm/module_hooks.c index d1155c47987..09c702805d1 100644 --- a/linuxkm/module_hooks.c +++ b/linuxkm/module_hooks.c @@ -361,30 +361,14 @@ unsigned long long wc_linuxkm_mono_ns(void) #endif /* LINUXKM_RBGC */ int wc_linuxkm_can_block(void) { - /* preempt_count() is NOT an accurate "may I sleep here" test on its own. - * In !CONFIG_PREEMPT_COUNT configs (PREEMPT_VOLUNTARY without - * PREEMPT_DYNAMIC -- the default through 5.15) preempt_disable() expands to - * barrier() and never touches __preempt_count - * (include/linux/preempt.h), so a task inside kernel_fpu_begin() still - * reads 0 here. preempt_count() is exactly as blind as preemptible() in - * that configuration; the earlier comment claimed the opposite. - * - * The hardirq/softirq masks ARE still maintained there, so the irqs and - * interrupt-context halves of this test remain sound. Only the - * preempt-disabled half is blind, which is why an open vector-register - * section has to be tested directly. - * - * Measured consequence when it was not: on 5.7.19 a cond_resched() from - * WC_RELAX_LONG_LOOP() inside an open bracket slept, the task migrated, - * and wc_restore_vector_registers_x86() ran on a CPU with no open section - * -- stranding kernel_fpu_begin()'s section on the origin CPU for the life - * of the module (1 event in 920,727 saves). + /* Without CONFIG_PREEMPT_COUNT (the default through 5.15) + * preempt_disable() is just barrier(), so preempt_count() reads 0 even + * inside kernel_fpu_begin() and cannot be trusted alone. The irq and + * interrupt-context halves are still accurate. */ - /* An open vector-register bracket is a fourth reason not to sleep here, - * but detecting one needs wc_linuxkm_in_svr_bracket(), which is part of - * the vector-register glue and is not in this branch. It has to be added - * back by whichever change brings that glue in; without the definition the - * term is an implicit declaration and does not build under -Werror. */ + /* An open vector-register bracket is another reason not to sleep, but the + * check for it lives in the vector-register glue, which is not in this + * branch. Add it back with that glue. */ return (preempt_count() == 0) && (! irqs_disabled()); } @@ -624,15 +608,11 @@ int wc_linuxkm_GenerateSeed_IntelRD(struct OS_Seed* os, byte* output, word32 sz) /* cpus_read_lock()/cpus_read_unlock() around the per-CPU work setup. */ #include -/* The boundary cannot schedule work itself, so the glue drives it. - * - * One delayed work PER CPU, queued with queue_delayed_work_on() so it runs on - * the CPU whose leaf it reseeds. That is what lets the reseed take interrupts - * off and know no caller on that CPU can be inside the leaf, which is what - * removes the need for any exclusion flag in the boundary. - * - * The root's own refresh from the noise source is a separate, unpinned work: - * it is the entropy gather and must not be coupled to leaf demand. */ +/* The boundary cannot schedule work, so the glue does it. One delayed work + * per CPU, pinned with queue_delayed_work_on() so it reseeds that CPU's leaf + * with interrupts off and no caller can be inside it -- which is why the + * boundary needs no exclusion flag. The root's refresh is separate work: it + * gathers entropy and must not follow leaf demand. */ #define WC_GRB_MAINT_POLL_MS 50 struct wc_grb_cpu_work { @@ -646,10 +626,8 @@ static int wc_grb_maint_running; static void wc_grb_cpu_work_fn(struct work_struct *work) { - /* dw is the first member of wc_grb_cpu_work and work is the first member - * of delayed_work, so the work pointer is the wrapper pointer. - * container_of() cannot be used here: it does void* arithmetic and this - * builds with -Werror=pointer-arith. */ + /* Both are first members, so the pointers are the same. container_of() + * would do void* arithmetic and this builds -Werror=pointer-arith. */ struct wc_grb_cpu_work *cw = (struct wc_grb_cpu_work *) work; int ret; diff --git a/wolfcrypt/src/linuxkm_get_entropy.c b/wolfcrypt/src/linuxkm_get_entropy.c index 1001991d37f..8337f4045d7 100644 --- a/wolfcrypt/src/linuxkm_get_entropy.c +++ b/wolfcrypt/src/linuxkm_get_entropy.c @@ -19,44 +19,25 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -/* Serves the Linux kernel's get_random_bytes() from inside the FIPS module - * cryptographic boundary. +/* Serves the kernel's get_random_bytes() from inside the FIPS boundary. * - * SP 800-90C Sec. 7 DRBG tree. A root DRBG reseeds 2*ncpu leaf DRBGs: one - * leaf per CPU for process, softirq and hardirq, and one leaf per CPU for NMI. - * Only the root ever touches the entropy source: leaves are instantiated and - * reseeded from it. Everything here is SHA-512. + * An SP 800-90C Sec. 7 DRBG tree, all SHA-512. One root DRBG owns the entropy + * source and reseeds 2*ncpu leaves: one per CPU for process/softirq/hardirq, + * and one per CPU for NMI. Leaves reseed from the root, never from the + * entropy source. One root, not several, because there is a single noise + * source behind one mutex and one shared health state. * - * The root seeding a leaf is one instantiated DRBG serving a second, - * separately instantiated DRBG, which is what SP 800-90C Sec. 7.2.3.2.1 calls - * for: a leaf reseeds by asking its parent to generate, not by asking the - * entropy source. SP 800-90A Sec. 8.6.9 bars a DRBG from reseeding itself, - * and that is a rule about one instantiation, so it does not reach a parent - * feeding a child. Each leaf reseed takes its own fresh root generate - * (Sec. 7.3.1 req 15), no leaf output ever reaches the root (req 16), and - * prediction resistance is not claimed for the leaves (Sec. 7.2.3.2.1). + * Callers run with interrupts off, and each leaf's reseed runs on that leaf's + * own CPU, so no caller can be inside a leaf while it is rewritten and no lock + * is needed. NMI is not masked by that, so an NMI leaf keeps two instances + * and publishes a reseed with one atomic index store; a reader gets the old or + * the new one, never a torn one. Hotplug can move the reseed off its CPU, and + * a per-instance generation counter covers that. * - * One root, not one per leaf: wolfentropy.c has a single noise source, one - * mutex and one shared SP 800-90B RCT/APT state, so extra roots would - * serialize on it and share its health verdict for 3x the cost. + * Requests are served in WC_GRB_CHUNK_SZ pieces, so the interrupts-off window + * depends on the chunk size, not on how much the caller asked for. * - * Concurrency: a caller runs with interrupts off, so on one CPU process, - * softirq and hardirq cannot overlap. The maintenance reseed for a general - * leaf runs ON THAT LEAF'S OWN CPU, also with interrupts off, so no caller can - * be inside while it writes and no exclusion is needed at all. - * - * NMI is not masked by that, so an NMI leaf is two instantiations: the reseed - * writes the spare and then publishes it with one atomic index store. An NMI - * reads either the old or the new instance, both fully instantiated, so a torn - * state is not representable. That reseed also runs on the leaf's own CPU, so - * a stalled NMI stalls it too and it cannot come round twice underneath a read. - * CPU hotplug is the one thing that can move it off that CPU; a per-instance - * generation counter backstops that case and is the only path that declines. - * - * A request is served in WC_GRB_CHUNK_SZ pieces, so the interrupts-off window - * is set by the chunk size rather than by the size the caller asked for. - * - * Nothing here waits. + * Nothing here waits. See linuxkm/RBGC-design.md for the reasoning. */ #include @@ -159,31 +140,15 @@ struct wc_grb_slot { struct wc_grb_nmi_slot { WC_RNG rng[2]; atomic_t live; - /* Bumped before an instance is reseeded. An NMI reads it either side of - * its generate and discards the result if it changed, which turns the - * reuse interval from a timing assumption into a checked one: a vCPU - * descheduled inside an NMI handler can outlive two flips, and 926 ms of - * steal has been measured on this hardware. */ + /* Bumped before a reseed. An NMI reads it either side of its generate + * and retries if it moved, so a long stall cannot go unnoticed. */ atomic_t gen[2]; atomic_t pending; - /* One reseed at a time. Three maintenance contexts can reach one leaf and - * none excluded another: the leaf's own pinned worker through - * wc_grb_maintain_cpu(cpu), the unbound sweep in wc_grb_root_tick() which - * passes want_cpu -1, and the service path's self-help - * wc_grb_maintain_cpu(-1). wc_grb_maint_busy guards only - * wc_grb_root_tick() against itself. - * - * Two reseeders in one leaf break both of the things that keep an NMI out - * of an instantiation whose exclusion flag is held. They derive the same - * spare from the same live, so the first to finish stores live = spare - * while the second is still inside wc_RNG_DRBG_Reseed(); and the second's - * own increment of gen[spare] returns that counter to EVEN while the first - * still holds the flag, so the odd-means-busy check below reads even and - * lets the caller in. Measured on 6.12.59/x86_64 over 49 runs with the - * exclusion window widened: 10 runs wedged and every one of them had two - * reseeders in one leaf; of the 36 runs that never had two, none wedged. - * The race itself needs no widening -- at the natural window it fired - * about 4,000 times in a 20-second run. */ + /* One reseed at a time. Three maintenance paths can reach one leaf and + * nothing else keeps them apart. Two at once pick the same spare and + * cancel each other's gen[] bump, which lets an NMI into an instance that + * is being rewritten. Measured: 10 of 49 runs wedged, all of them with + * two reseeders in one leaf. */ atomic_t reseeding; struct wc_grb_ctr since; unsigned long at; @@ -596,25 +561,12 @@ int wc_grb_service(void *buf, size_t len) (word32) want); if (ret != 0) { - /* This instantiation could not answer. The reachable - * reason is that it has reached its own reseed interval, - * and a reseed cannot be performed from NMI because the - * noise source takes a mutex. - * - * Ask the leaf's OTHER instantiation before giving up. It - * is separately instantiated with its own state and its own - * reseed counter, so it is not in the same condition, and - * reading it is the same on-demand generate into the same - * caller-owned buffer -- nothing is held, nothing is - * produced ahead of the request. - * - * Giving up here is not a neutral outcome. The kernel - * patch retries this hook WOLFSSL_LINUXKM_GRB_TRIES times - * and then panic()s rather than let its own generator - * answer, because those bytes would not be validated - * output and _get_random_bytes() returns void, so the - * caller could not tell which generator it got. A decline - * that survives the retries takes the machine down. */ + /* Usually means this instance is due a reseed, which NMI + * cannot do (the noise source takes a mutex). Try the + * leaf's other instance first: it has its own state and + * counter. Declining is not free -- the kernel patch + * retries and then panics rather than hand out + * unvalidated bytes. */ int other = live ? 0 : 1; int og; diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 187f22cb482..bbd6c59004d 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -352,11 +352,8 @@ enum { #define MAX_SEED_SZ WC_DRBG_MAX_SEED_SZ -/* Selects the branch of _InitRng() that takes its seed material from the - * caller instead of from a randomness source. One caller compiles it in here: - * wc_InitRngRBGC() under LINUXKM_RBGC. Build-time only; nothing chooses - * between the branches at run time -- _InitRng() takes the caller-supplied - * path if and only if the caller passed a seed pointer. */ +/* Makes _InitRng() take its seed from the caller. Only wc_InitRngRBGC() + * uses it. Build-time only. */ #ifdef LINUXKM_RBGC #define WC_RNG_SEED_FROM_CALLER #endif @@ -2201,12 +2198,8 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, else { #ifdef WC_RNG_SEED_FROM_CALLER if (rbgcSeed != NULL) { - /* The caller already holds the seed material, so no randomness - * source is consulted: neither seedCb nor wc_GenerateSeed() - * below runs. The one caller here is wc_InitRngRBGC(), where - * the material came from the parent construction per - * SP 800-90C Sec. 7.2.1.2, and which bounds rbgcSeedSz by - * MAX_SEED_SZ before calling. */ + /* Caller supplied the seed, so skip seedCb and + * wc_GenerateSeed(). */ XMEMCPY(seed, rbgcSeed, rbgcSeedSz); seedSz = rbgcSeedSz; ret = 0; @@ -2249,10 +2242,8 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, if (ret == 0) { #ifdef WC_RNG_SEED_FROM_CALLER - /* wc_RNG_TestSeed() is a repetition check on raw noise. The - * seed material on this path is not raw noise: it is the - * parent's DRBG output, and SP 800-90C Sec. 7.2.1.2 makes the - * parent's generate status the check instead. */ + /* No repetition check: this is the parent's DRBG output, not + * raw noise (SP 800-90C 7.2.1.2). */ if (rbgcSeed != NULL) { ret = DRBG_SUCCESS; } @@ -2279,11 +2270,8 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, word32 instSeedSz; #if defined(HAVE_FIPS) || !defined(WOLFSSL_RNG_USE_FULL_SEED) - /* The SEED_BLOCK_SZ prefix is the block wc_RNG_TestSeed() - * compares against the rest of a raw seed, so it is not part of - * the seed material. A caller-supplied seed runs no such test - * -- SP 800-90C Sec. 7.2.1.2 wants the whole 3s/2 bits in the - * instantiate -- so that path keeps the whole buffer. */ + /* No repetition check here, so no SEED_BLOCK_SZ prefix to + * skip: use the whole buffer. */ #ifdef WC_RNG_SEED_FROM_CALLER if (rbgcSeed != NULL) { instSeed = seed; @@ -2461,16 +2449,10 @@ void wc_rng_free(WC_RNG* rng) } #ifdef LINUXKM_RBGC -/* Instantiate a non-root RBGC construction from its parent, SP 800-90C - * Sec. 7.2.1.2. Internal to the RBGC: not WOLFSSL_API, so it is not exported. - * - * WC_RBGC_INSTANTIATE_SZ is 3s/2 at s = 256, which is the amount that section - * names for Hash_DRBG and also the larger of the two amounts it gives. The - * s/2 above the security strength is the nonce entropy of SP 800-90A - * Sec. 8.6.7(b), so no separate nonce is passed. - * - * SHA-512 only, matching the construction; a parent that is not SHA-512 means - * that DRBG was disabled, and a leaf must not quietly be built on the other. */ +/* Instantiate a child DRBG from its parent (SP 800-90C 7.2.1.2). Not + * exported. Draws 3s/2 bits, so the extra s/2 covers the nonce and none is + * passed separately. SHA-512 only: a different parent means that DRBG was + * disabled, and we must not silently build on another one. */ int wc_InitRngRBGC(WC_RNG* rng, WC_RNG* parent) { byte seed[WC_RBGC_INSTANTIATE_SZ]; @@ -2490,8 +2472,8 @@ int wc_InitRngRBGC(WC_RNG* rng, WC_RNG* parent) INVALID_DEVID); } - /* CSP: the parent's output, which is this leaf's seed material. SP - * 800-90C Sec. 7.3.1 req 15 bars reusing it for anything else. */ + /* CSP: parent output, this child's seed. Not reusable (SP 800-90C + * 7.3.1 req 15). */ ForceZero(seed, sizeof(seed)); if ((ret == 0) && (rng->drbgType != WC_DRBG_SHA512)) { diff --git a/wolfcrypt/src/sha512.c b/wolfcrypt/src/sha512.c index 91f6d9839ba..7db3a6d0d75 100644 --- a/wolfcrypt/src/sha512.c +++ b/wolfcrypt/src/sha512.c @@ -138,17 +138,8 @@ #if defined(WOLFSSL_ARMASM) && defined(LINUXKM_RBGC) - /* src/include.am compiles exactly one SHA-512 transform under - * BUILD_LINUXKM_RBGC -- the portable C one in this file -- and emits no - * ARM assembly object for it. The dispatch below has to agree with what - * is actually compiled: left defined, it installs - * Transform_Sha512_Len_neon, which nothing then defines, and the wolfCrypt - * PIE container fails to link with "U Transform_Sha512_Len_neon". - * This is the ARM counterpart of the !defined(LINUXKM_RBGC) already on the - * Intel dispatch immediately below. Both pin the implementation at build - * time; neither introduces a run-time choice, and the scalar transform is - * what makes the RBGC DRBG callable with interrupts off, where the vector - * registers are not available. */ + /* RBGC builds only the C transform, so don't let the ARM dispatch pick a + * NEON one that was never compiled. */ #undef WOLFSSL_ARMASM #endif @@ -923,9 +914,7 @@ int wc_Sha384GetFlags(wc_Sha384* sha384, word32* flags) #ifdef WOLFSSL_SHA512 -/* A pinned x86 build has no transform to select, so Sha512_SetTransform() is - * a do-nothing macro there rather than a function. Defined here, ahead of - * its first call site below. */ +/* RBGC has nothing to select, so this is a macro there, not a function. */ #if (defined(WOLFSSL_X86_64_BUILD) && defined(USE_INTEL_SPEEDUP) && \ !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2))) || \ @@ -1092,21 +1081,8 @@ static int InitSha512_256(wc_Sha512* sha512) !defined(LINUXKM_RBGC) && \ (defined(HAVE_INTEL_AVX1) || defined(HAVE_INTEL_AVX2)) - /* SHA-512 transforms compiled here: unpinned that is AVX1 and AVX2, each - * with and without RORX, and the C one, chosen from CPUID at run time. - * IG 10.3.A GeneralNote1 requires each implementation in the module to be - * self-tested separately. A certified build compiles several and CPUID - * selects one per OE; what a save failure must never do is select a - * different one. */ - - /* Suppress the C transform (and K512, which only it uses) when an - * accelerated implementation is pinned: it would be a second SHA-512 in - * the boundary, reachable from the same services. */ - - /* Does the compiled transform want the block as host-endian words? Only - * the C one does; the accelerated ones byte-reverse internally. Unpinned - * this is a CPUID test at every call site, which is the run-time - * selection the pin removes; pinned it is a build-time constant. */ + /* Only the C transform wants host-endian words; the asm ones byte-reverse + * themselves. */ #define WC_SHA512_REV_BLOCK() \ (!IS_INTEL_AVX1(intel_flags) && !IS_INTEL_AVX2(intel_flags)) diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index ec0ec403913..71e76230b17 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -455,9 +455,8 @@ WOLFSSL_ABI WOLFSSL_API void wc_rng_free(WC_RNG* rng); #ifndef WC_NO_RNG #ifdef LINUXKM_RBGC -/* SP 800-90C Sec. 7.2.1.2 instantiation of a non-root RBGC construction from - * its parent. WOLFSSL_LOCAL, not WOLFSSL_API: internal to the RBGC and never - * exported. 3s/2 bits are drawn from the parent; see the definition. */ +/* Instantiate a child DRBG from its parent (SP 800-90C 7.2.1.2). Internal, + * never exported. */ #define WC_RBGC_INSTANTIATE_SZ (RNG_SECURITY_STRENGTH * 3 / 2 / 8) WOLFSSL_LOCAL int wc_InitRngRBGC(WC_RNG* rng, WC_RNG* parent); #endif