diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 7ff31f705ce..82e80658d40 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -27,7 +27,7 @@ jobs: check_filenames: true check_hidden: true # Add comma separated list of words that occur multiple times that should be ignored (sorted alphabetically, case sensitive) - ignore_words_list: adin,ameba,aNULL,brunch,carryIn,chainG,ciph,cLen,cliKs,cna,dout,FPR,fpr,haveA,inCreated,inOut,inout,larg,LEAPYEAR,Merget,optionA,parm,parms,repid,rIn,userA,ser,siz,te,Te,HSI,failT,toLen,vor, + ignore_words_list: adin,ameba,aNULL,brunch,carryIn,chainG,ciph,cLen,cliKs,cna,dout,FPR,fpr,haveA,inCreated,inOut,inout,larg,LEAPYEAR,Merget,optionA,parm,parms,repid,rIn,userA,ser,siz,te,Te,HSI,failT,toLen,vor,checkin # The exclude_file contains lines of code that should be ignored. This is useful for individual lines which have non-words that can safely be ignored. exclude_file: '.codespellexcludelines' # To skip files entirely from being processed, add it to the following list: diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 25e01d94eac..bbc58d08f1e 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -63,6 +63,7 @@ CIOCGSESSINFO CLOCK_MONOTONIC CMSIS_OS2_H_ COMPONENT_WOLFSSL +CONFIG_ACPI CONFIG_ARCH_CHIP_STM32F746ZG CONFIG_ARCH_CHIP_STM32H743ZI CONFIG_ARCH_CHIP_STM32L552ZE @@ -155,6 +156,7 @@ CONFIG_NET_SOCKETS_SOCKOPT_TLS CONFIG_NEWLIB_LIBC CONFIG_NEWLIB_NANO_FORMAT CONFIG_PICOLIBC +CONFIG_PM_SLEEP CONFIG_POSIX_API CONFIG_POSIX_THREADS CONFIG_PREEMPT_COUNT @@ -787,6 +789,7 @@ WC_HASH_CUSTOM_MAX_BLOCK_SIZE WC_HASH_CUSTOM_MAX_DIGEST_SIZE WC_HASH_CUSTOM_MIN_DIGEST_SIZE WC_INIT_ERROR_WHEN_CONTENDED +WC_LINUXKM_NO_ENTROPY_DAEMON WC_LINUXKM_NO_USE_HEAP_WRAPPERS WC_LINUXKM_SVR_NO_BATCHING WC_MLDSA_NO_ASM @@ -801,8 +804,22 @@ WC_PKCS12_PBKDF_USING_MP_API WC_PROTECT_ENCRYPTED_MEM WC_PUF_HELPER_COMPACT WC_PUF_SHA3 +WC_RNG_BANK_NO_DAEMON_SUPPORT WC_RNG_BANK_NO_DEFAULT_SUPPORT WC_RNG_BLOCKING +WC_RNG_NO_FREE_HOOK +WC_RNG_NO_LOCK +WC_RNG_NO_LOCK_FULL_MUTEX +WC_RNG_NO_NEXT_SEED +WC_RNG_NO_POOL +WC_RNG_NO_RBGC +WC_RNG_NO_RBGC_RESEED +WC_RNG_WANT_FREE_HOOK +WC_RNG_WANT_LOCK +WC_RNG_WANT_LOCK_FULL_MUTEX +WC_RNG_WANT_NEXT_SEED +WC_RNG_WANT_POOL +WC_RNG_WANT_RBGC WC_RSA_NONBLOCK_TIME WC_RSA_NO_FERMAT_CHECK WC_RTL8735B_NO_DERIVE_CACHE diff --git a/configure.ac b/configure.ac index 0028f10ba0b..45a7cf82342 100644 --- a/configure.ac +++ b/configure.ac @@ -1700,6 +1700,7 @@ then test "$enable_base16" = "" && enable_base16=yes test "$enable_ssh" = "" && test "$enable_hmac" != "no" && enable_ssh=yes test "$enable_rng_bank" = "" && enable_rng_bank=yes + test "$enable_rng_extras" = "" && enable_rng_extras=yes test "$enable_dh" = "" && enable_dh=yes test "$enable_defaultdhparams" = "" && enable_defaultdhparams=yes @@ -1917,6 +1918,8 @@ then test "$enable_supportedcurves" = "" && enable_supportedcurves=yes fi test "$enable_rng" != "no" && test "$enable_rng_bank" = "" && enable_rng_bank=yes + test "$enable_rng" != "no" && test "$enable_rng_extras" = "" && enable_rng_extras=yes + if test "$ENABLED_FIPS" = "no" || test "$HAVE_FIPS_VERSION" -ge 6 then test "$enable_aes" != "no" && test "$enable_aescfb" = "" && enable_aescfb=yes @@ -2788,6 +2791,20 @@ then AM_CFLAGS="$AM_CFLAGS -DWC_NO_RNG" fi + +# RNG extras +AC_ARG_ENABLE([rng-extras], + [AS_HELP_STRING([--enable-rng-extras],[Enable RNG locks, pools, RBGC, and daemon support (default: disabled)])], + [ ENABLED_RNG_EXTRAS=$enableval ], + [ ENABLED_RNG_EXTRAS=$KERNEL_MODE_DEFAULTS ] + ) + +if test "$ENABLED_RNG_EXTRAS" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DWC_RNG_EXTRAS" +fi + + AC_ARG_ENABLE([rng-bank], [AS_HELP_STRING([--enable-rng-bank],[Enable compiling and using RNG banks (default: disabled)])], [ ENABLED_RNG_BANK=$enableval ], @@ -7167,7 +7184,8 @@ AS_CASE([$FIPS_VERSION], -DECC_USER_CURVES \ -DHAVE_ECC384 \ -DHAVE_ECC521 \ - -DWOLFSSL_VALIDATE_FFC_IMPORT" + -DWOLFSSL_VALIDATE_FFC_IMPORT \ + -DWC_RNG_EXTRAS" # KCAPI API does not support custom k for sign, don't force enable ECC key sizes and don't use seed callback AS_IF([test "x$ENABLED_KCAPI_ECC" = "xno"], diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index b5cac51e509..64f99e8a707 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -135,7 +135,7 @@ int wc_InitRng(WC_RNG* rng); \sa wc_FreeRng \sa wc_RNG_HealthTest */ -int wc_RNG_GenerateBlock(WC_RNG* rng, byte* b, word32 sz); +int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz); /*! \ingroup Random @@ -397,7 +397,7 @@ int wc_InitRng_ex(WC_RNG* rng, void* heap, int devId); \sa wc_InitRng */ -int wc_InitRngNonce(WC_RNG* rng, byte* nonce, word32 nonceSz); +int wc_InitRngNonce(WC_RNG* rng, const byte* nonce, word32 nonceSz); /*! \ingroup Random @@ -424,7 +424,7 @@ int wc_InitRngNonce(WC_RNG* rng, byte* nonce, word32 nonceSz); \sa wc_InitRngNonce */ -int wc_InitRngNonce_ex(WC_RNG* rng, byte* nonce, word32 nonceSz, +int wc_InitRngNonce_ex(WC_RNG* rng, const byte* nonce, word32 nonceSz, void* heap, int devId); /*! @@ -477,6 +477,8 @@ int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz); \return 0 If valid \return BAD_FUNC_ARG If seed is NULL \return ENTROPY_RT_E || ENTROPY_APT_E Validation failed + \return ENTROPY_APT_E The adaptive proportion test failed. + \return MEMORY_E Allocation failed. \param seed Seed to test \param seedSz Seed size @@ -761,3 +763,1032 @@ int wc_Sha512Drbg_Enable(void); \sa wc_Sha512Drbg_Enable */ int wc_Sha512Drbg_IsDisabled(void); + +/*! + \ingroup Random + + \brief Initialize a WC_RNG with instantiation-time security attributes. + Identical to wc_InitRng_ex(), with a flags argument fixing attributes at + birth: WC_RNG_INIT_FLAG_LOCK_REQUIRED latches the sticky lock-required + policy bit, so there is no reachable state in which the instance serves + without its lock policy; WC_RNG_INIT_FLAG_LOCK_INITIALLY constructs into + a held lease, to be released with wc_RNG_lock_put(); + WC_RNG_INIT_FLAG_USE_FULL_MUTEX layers a blocking wolfSSL_Mutex + outermost around the lock latch, for user-mode sharing of one instance + among threads (requires WC_RNG_HAVE_LOCK_FULL_MUTEX). + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + \return NOT_COMPILED_IN A requested flag is not compiled in. + + \param rng The RNG object to initialize. + \param heap Heap hint for dynamic allocation. + \param devId Device id, or INVALID_DEVID. + \param flags Bitwise-or of WC_RNG_INIT_FLAG_* attributes. + + _Example_ + \code + WC_RNG rng; + if (wc_InitRng_ex2(&rng, NULL, INVALID_DEVID, + WC_RNG_INIT_FLAG_LOCK_REQUIRED | + WC_RNG_INIT_FLAG_LOCK_INITIALLY) != 0) { + // error handling + } + // caller holds the lease from birth + \endcode + + \sa wc_InitRng_ex + \sa wc_InitRngNonce_ex2 + \sa wc_RNG_lock_get + \sa wc_RNG_lock_put +*/ +int wc_InitRng_ex2(WC_RNG* rng, void* heap, int devId, word32 flags); + +/*! + \ingroup Random + + \brief Initialize a WC_RNG with a caller-supplied nonce and + instantiation-time security attributes. The nonce semantics are those of + wc_InitRngNonce_ex(); the flags semantics are those of wc_InitRng_ex2(). + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + \return NOT_COMPILED_IN A requested flag is not compiled in. + + \param rng The RNG object to initialize. + \param nonce Optional nonce used as additional instantiation input. + \param nonceSz Length of nonce in bytes. + \param heap Heap hint for dynamic allocation. + \param devId Device id, or INVALID_DEVID. + \param perso Optional personalization string (may be null). + \param persoSz Length of perso in bytes. + \param flags Bitwise-or of WC_RNG_INIT_FLAG_* attributes. + + \sa wc_InitRng_ex2 + \sa wc_InitRngNonce_ex +*/ +int wc_InitRngNonce_ex2(WC_RNG* rng, const byte* nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + void* heap, int devId, word32 flags); + +/*! + \ingroup Random + + \brief Read-only accessor for the RNG health status. Returns the + instance's enum wc_RngHealthState value (WC_DRBG_NOT_INIT, WC_DRBG_OK, + WC_DRBG_FAILED, WC_DRBG_CONT_FAILED). + + \return WC_DRBG_OK The instance is in service. + \return BAD_FUNC_ARG rng is null. + + \param rng The RNG object to interrogate. + + _Example_ + \code + if (wc_RNG_GetStatus(&rng) != WC_DRBG_OK) { + // instance is not serviceable + } + \endcode + + \sa wc_RNG_DRBG_Present + \sa wc_RNG_DRBG_GetReseedCtr +*/ +int wc_RNG_GetStatus(const WC_RNG* rng); + +/*! + \ingroup Random + + \brief Returns 1 if rng has an instantiated DRBG, else 0. An in-service + WC_RNG can lack one: instantiation bypasses the DRBG when the CPU has + RDRAND (HAVE_INTEL_RDRAND). DRBG-specific services (commanded reseed, + banked next seeds, RBG chains) are unavailable on such instances. + + \return 1 rng has a live DRBG. + \return 0 rng is null or has no DRBG. + + \param rng The RNG object to interrogate. + + \sa wc_RNG_GetStatus + \sa wc_RNG_DRBG_ScheduleReseed +*/ +int wc_RNG_DRBG_Present(const WC_RNG* rng); + +/*! + \ingroup Random + + \brief Report the DRBG's current reseed counter -- the number of generate + operations since the last credited (re)seed, starting at 1. + + \return 0 Success + \return BAD_FUNC_ARG rng or reseedCtr is null. + \return WRONG_TYPE_OBJECT_E rng has no DRBG (RDRAND et al.). + + \param rng The RNG object to interrogate. + \param reseedCtr Receives the counter. + + \sa wc_RNG_DRBG_Present + \sa wc_RNG_DRBG_ScheduleReseed +*/ +int wc_RNG_DRBG_GetReseedCtr(const WC_RNG* rng, wc_drbg_reseed_ctr_t* reseedCtr); + +/*! + \ingroup Random + + \brief Mark rng due for reseed: the next generate operation reseeds from + the module's built-in or registered seed source before producing output. + This can only shorten the current seed's remaining lifetime, never extend + it. + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + \return WRONG_TYPE_OBJECT_E rng has no DRBG (RDRAND et al.) -- a + commanded reseed that cannot happen is not a success. + + \param rng The RNG object to schedule. + + \sa wc_RNG_DRBG_Reseed_Now + \sa wc_RNG_DRBG_GetReseedCtr +*/ +int wc_RNG_DRBG_ScheduleReseed(WC_RNG* rng); + +/*! + \ingroup Random + + \brief Immediately reseed rng from the module's built-in or registered + seed source, with an optional nonce as additional input. The credited + reseed resets the reseed counter. + + \return 0 Success + \return BAD_FUNC_ARG rng is null, or nonce is null with nonceSz nonzero. + \return WRONG_TYPE_OBJECT_E rng has no DRBG (RDRAND et al.). + \return DRBG_CONT_FIPS_E The continuous test failed; the DRBG is out of + service. + \return RNG_FAILURE_E The DRBG is out of service or reseeding failed. + + \param rng The RNG object to reseed. + \param nonce Optional additional input. + \param nonceSz Length of nonce in bytes. + + \sa wc_RNG_DRBG_ScheduleReseed + \sa wc_RNG_DRBG_Reseed_Nonce +*/ +int wc_RNG_DRBG_Reseed_Now(WC_RNG* rng, const byte* nonce, word32 nonceSz); + +/*! + \ingroup Random + + \brief Reseed rng's DRBG with caller-supplied seed material and an + optional nonce as additional input. The material is credited as entropy: + the reseed counter resets. + + \return 0 Success + \return RNG_FAILURE_E rng is condemned (status DRBG_FAILED): a + condemned instance does not accept a credited reseed; recover with + wc_FreeRng() then wc_InitRng*(). + \return BAD_FUNC_ARG rng or seed is null. + \return WRONG_TYPE_OBJECT_E rng has no DRBG (RDRAND et al.). + + \param rng The RNG object to reseed. + \param seed Seed material. + \param seedSz Length of seed in bytes. + \param nonce Optional additional input. + \param nonceSz Length of nonce in bytes. + + \sa wc_RNG_DRBG_Reseed + \sa wc_RNG_DRBG_Stir_Nonce +*/ +int wc_RNG_DRBG_Reseed_Nonce(WC_RNG* rng, const byte* seed, word32 seedSz, + const byte *nonce, word32 nonceSz); + +/*! + \ingroup Random + + \brief Similar to wc_RNG_DRBG_Reseed(), except the caller-supplied + material is mixed through the reseed derivation function without being + credited as entropy: the reseed counter is not reset, so only the + module's own seed source ever extends the instance's seed lifetime. + + \return 0 Success + \return BAD_FUNC_ARG rng or seed is null. + \return WRONG_TYPE_OBJECT_E rng has no DRBG (RDRAND et al.). + + \param rng The RNG object to stir. + \param seed Material to mix in. + \param seedSz Length of seed in bytes. + + \sa wc_RNG_DRBG_Reseed + \sa wc_RNG_DRBG_Stir_Nonce +*/ +int wc_RNG_DRBG_Stir(WC_RNG* rng, const byte* seed, word32 seedSz); + +/*! + \ingroup Random + + \brief The nonce-bearing form of wc_RNG_DRBG_Stir(). + + \return 0 Success + \return BAD_FUNC_ARG rng or seed is null. + \return WRONG_TYPE_OBJECT_E rng has no DRBG (RDRAND et al.). + + \param rng The RNG object to stir. + \param seed Material to mix in. + \param seedSz Length of seed in bytes. + \param nonce Optional additional input. + \param nonceSz Length of nonce in bytes. + + \sa wc_RNG_DRBG_Stir + \sa wc_RNG_DRBG_Reseed_Nonce +*/ +int wc_RNG_DRBG_Stir_Nonce(WC_RNG* rng, const byte* seed, + word32 seedSz, const byte *nonce, + word32 nonceSz); + +/*! + \ingroup Random + + \brief Instantiate child as an SP 800-90C RBG-chain member subordinate to + parent, drawing its seed material from parent's generate function in + place of the module's seed source. Every other aspect of instantiation + is wc_InitRng_ex2()'s. The child is tagged with stratum + (parent's stratum + 1), sticky for the instance's lifetime even across + subsequent source reseeds; its claimable security strength is capped by + parent's, and it has no prediction resistance. The caller must hold + exclusive access to parent for the duration of the call; the spawn debits + parent's reseed counter by one generate. + + \return 0 Success + \return BAD_FUNC_ARG child or parent is null, or child equals parent. + \return SEQ_OVERFLOW_E parent's stratum is at the representable maximum. + + \param child The caller-provided WC_RNG to instantiate (uninitialized). + \param parent The chain parent to draw seed material from. + \param flags Bitwise-or of WC_RNG_INIT_FLAG_* attributes for the child. + + _Example_ + \code + WC_RNG root, child; + wc_InitRng(&root); + if (wc_InitRngRBGC(&child, &root, WC_RNG_INIT_FLAG_NONE) == 0) { + // child serves independently; release with wc_FreeRng(&child) + } + \endcode + + \sa wc_InitRngNonceRBGC + \sa wc_InitRngRBGC_New + \sa wc_RNG_DRBG_ReseedRBGC + \sa wc_RNG_DRBG_GetRBGCStratum +*/ +int wc_InitRngRBGC(WC_RNG* child, WC_RNG* parent, word32 flags); + +/*! + \ingroup Random + + \brief The nonce-bearing form of wc_InitRngRBGC(): the nonce is used as + additional instantiation input, as in wc_InitRngNonce_ex2(). + + \return 0 Success + \return BAD_FUNC_ARG child or parent is null, child equals parent, or + nonce is null with nonceSz nonzero. + \return SEQ_OVERFLOW_E parent's stratum is at the representable maximum. + + \param child The caller-provided WC_RNG to instantiate (uninitialized). + \param parent The chain parent to draw seed material from. + \param nonce Additional instantiation input. + \param nonceSz Length of nonce in bytes. + \param flags Bitwise-or of WC_RNG_INIT_FLAG_* attributes for the child. + + \sa wc_InitRngRBGC + \sa wc_InitRngNonceRBGC_New +*/ +int wc_InitRngNonceRBGC(WC_RNG* child, WC_RNG* parent, const byte* nonce, + word32 nonceSz, word32 flags); + +/*! + \ingroup Random + + \brief The allocating form of wc_InitRngRBGC(): the child is allocated + from parent's heap and returned through child. Release with + wc_rng_free(). + + \return 0 Success + \return BAD_FUNC_ARG child or parent is null. + \return MEMORY_E Allocation failed. + \return SEQ_OVERFLOW_E parent's stratum is at the representable maximum. + + \param child Receives the allocated, instantiated WC_RNG. + \param parent The chain parent to draw seed material from. + \param flags Bitwise-or of WC_RNG_INIT_FLAG_* attributes for the child. + + \sa wc_InitRngRBGC + \sa wc_InitRngNonceRBGC_New +*/ +int wc_InitRngRBGC_New(WC_RNG** child, WC_RNG* parent, word32 flags); + +/*! + \ingroup Random + + \brief The allocating, nonce-bearing form of wc_InitRngRBGC(). + + \return 0 Success + \return BAD_FUNC_ARG child or parent is null, or nonce is null with + nonceSz nonzero. + \return MEMORY_E Allocation failed. + \return SEQ_OVERFLOW_E parent's stratum is at the representable maximum. + + \param child Receives the allocated, instantiated WC_RNG. + \param parent The chain parent to draw seed material from. + \param nonce Additional instantiation input. + \param nonceSz Length of nonce in bytes. + \param flags Bitwise-or of WC_RNG_INIT_FLAG_* attributes for the child. + + \sa wc_InitRngRBGC_New + \sa wc_InitRngNonceRBGC +*/ +int wc_InitRngNonceRBGC_New(WC_RNG** child, WC_RNG* parent, const byte* nonce, + word32 nonceSz, word32 flags); + +/*! + \ingroup Random + + \brief Reseed rng from root's generate output -- the SP 800-90C chain + reseed -- with an optional nonce as additional input. The reseed is + credited (the reseed counter resets) and rng acquires root's stratum + plus one. The caller must hold exclusive access to both instances. + + \details Credited chain reseeds obey a no-downgrade rule: a + primary-seeded (stratum-0) root is always accepted, and a chained + (stratum > 0) root is accepted only when its stratum is strictly less + than rng's -- the acquired stratum never increases, so reseed cycles + are impossible by construction, consistent with SP 800-90C 7.1.2.2. + Lateral (equal-stratum) and downgrading reseeds are refused with + BAD_FUNC_ARG. Building WC_RNG_NO_RBGC_RESEED restricts credited chain + reseeds to primary-seeded roots. Uncredited chain stirs + (wc_RNG_DRBG_StirRBGC()) are exempt from all of this: they + are stirs, claim nothing, and leave rng's stratum untouched. + + \return 0 Success + \return BAD_FUNC_ARG rng or root is null, rng equals root, or the + no-downgrade rule refuses root as a chain parent (see \details). + \return WRONG_TYPE_OBJECT_E rng has no DRBG (RDRAND et al.). + \return SEQ_OVERFLOW_E root's stratum is at the representable maximum. + + \param rng The chain member to reseed. + \param root The chain parent to draw seed material from. + \param nonce Optional additional input. + \param nonceSz Length of nonce in bytes. + + \sa wc_InitRngRBGC + \sa wc_RNG_DRBG_StirRBGC + \sa wc_RNG_DRBG_Reseed_Now +*/ +int wc_RNG_DRBG_ReseedRBGC(WC_RNG* rng, WC_RNG* root, const byte* nonce, + word32 nonceSz); + +/*! + \ingroup Random + + \brief The uncredited form of wc_RNG_DRBG_ReseedRBGC(): material from + root is mixed in without resetting rng's reseed counter. + + \return 0 Success + \return BAD_FUNC_ARG rng or root is null, or rng equals root. + \return WRONG_TYPE_OBJECT_E rng has no DRBG (RDRAND et al.). + + \param rng The chain member to stir. + \param root The chain parent to draw material from. + \param nonce Optional additional input. + \param nonceSz Length of nonce in bytes. + + \sa wc_RNG_DRBG_ReseedRBGC + \sa wc_RNG_DRBG_Stir + \details Unrestricted by the credited no-downgrade rule: any source + stratum is accepted, and rng's reseed counter, stratum, and + entropy-invalidated state are all left untouched -- an uncredited + chain reseed is a stir, and a stir must never masquerade as recovery + or promotion. + +*/ +int wc_RNG_DRBG_StirRBGC(WC_RNG* rng, WC_RNG* root, + const byte* nonce, word32 nonceSz); + +/*! + \ingroup Random + + \brief Report rng's RBG-chain stratum: 0 for a root (never chain-seeded), + n for a member seeded from a stratum-(n-1) parent. The stratum is sticky + for the instance's lifetime, even across subsequent source reseeds. + + \return 0 rng is a chain root. + \return n The stratum, positive for a chain member. + \return BAD_FUNC_ARG rng is null. + + \param rng The RNG object to interrogate. + + \sa wc_InitRngRBGC + \sa wc_RNG_DRBG_GetNextSeedRBGCStratum +*/ +int wc_RNG_DRBG_GetRBGCStratum(const WC_RNG* rng); + +/*! + \ingroup Random + + \brief Report the RBG-chain stratum of rng's banked next seed -- + race-free via the aperture protocol -- for provenance-aware consumers. + + \return 0 The banked seed has root (source) provenance. + \return n The banked seed's stratum, positive for chain provenance. + \return BAD_FUNC_ARG rng is null or has no DRBG. + \return NOT_READY_E No banked seed is ready. + + \param rng The RNG object to interrogate. + + \sa wc_RNG_DRBG_GetRBGCStratum + \sa wc_RNG_DRBG_NextSeedGenerate_RBGC +*/ +int wc_RNG_DRBG_GetNextSeedRBGCStratum(const WC_RNG* rng); + +/*! + \ingroup Random + + \brief Bank up to n more bytes of next-seed material from the module's + seed source, health-testing and publishing the bank when it completes. + The fill is incremental and in-boundary; a scheduling daemon may call + this without owning the instance -- the single-writer fill and the + atomic aperture hand-off make it safe alongside a concurrent consumer. + + \return 0 Bytes were banked (bank may or may not yet be complete). + \return ALREADY_E The bank is ready or being consumed. + \return NOT_READY_E The health test could not run; simply retry. + \return BAD_FUNC_ARG rng is null or n is 0. + \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). + + \param rng The RNG object whose bank to fill. + \param n Maximum bytes to bank this call (clamped to space remaining). + + _Example_ + \code + // scheduling daemon: fill incrementally until published + int ret = wc_RNG_DRBG_NextSeedGenerate(rng, 16); + if (ret == ALREADY_E) { + // bank is ready; nothing to do until a consumer claims it + } + \endcode + + \sa wc_RNG_DRBG_NextSeedNow + \sa wc_RNG_DRBG_NextSeedCurrent + \sa wc_RNG_DRBG_NextSeedGenerate_RBGC +*/ +int wc_RNG_DRBG_NextSeedGenerate(WC_RNG* rng, word32 n); + +/*! + \ingroup Random + + \brief The chain-sourced form of wc_RNG_DRBG_NextSeedGenerate(): the + banked material is drawn from root's generate function, and the bank is + tagged with root's stratum plus one for provenance-aware consumption. + + \return 0 Bytes were banked. + \return ALREADY_E The bank is ready or being consumed. + \return NOT_READY_E The health test could not run; simply retry. + \return BAD_FUNC_ARG rng or root is null, or n is 0. + \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). + \return SEQ_OVERFLOW_E root's stratum is at the representable maximum. + + \param rng The RNG object whose bank to fill. + \param root The chain parent to draw material from. + \param n Maximum bytes to bank this call. + + \sa wc_RNG_DRBG_NextSeedGenerate + \sa wc_RNG_DRBG_GetNextSeedRBGCStratum + \details Banking is bound for credited redemption, so the credited + no-downgrade rule applies at bank time: a primary-seeded (stratum-0) + root is always accepted, and a chained root only when its stratum is + strictly less than rng's -- banking whose redemption would raise rng's + stratum is refused with BAD_FUNC_ARG. The banked material records + root's stratum plus one, observable via + wc_RNG_DRBG_GetNextSeedRBGCStratum(), and redemption + (wc_RNG_DRBG_NextSeedNow()) carries it onto rng. + +*/ +int wc_RNG_DRBG_NextSeedGenerate_RBGC(WC_RNG* rng, WC_RNG *root, word32 n); + +/*! + \ingroup Random + + \brief Report the raw next-seed aperture value: a non-negative banked + byte count (filling), WC_DRBG_NEXT_SEED_READY, or + WC_DRBG_NEXT_SEED_CONSUMING. The snapshot is racy by design; use it for + scheduling and diagnostics, not for hand-off decisions. + + \return 0 Success + \return BAD_FUNC_ARG rng or n is null. + \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). + + \param rng The RNG object to interrogate. + \param n Receives the aperture value. + + \sa wc_RNG_DRBG_NextSeedGenerate + \sa wc_RNG_DRBG_NextSeedNow +*/ +int wc_RNG_DRBG_NextSeedCurrent(WC_RNG* rng, WC_ATOMIC_INT_ARG* n); + +/*! + \ingroup Random + + \brief Claim a ready next-seed bank and perform a source-free credited + reseed with it -- safe in atomic context. The bank empties (use-once) + and the reseed counter resets. The caller must own the instance. + + \return 0 Success + \return NOT_READY_E No bank is ready. + \return BAD_FUNC_ARG rng is null. + \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). + + \param rng The RNG object to reseed. + + _Example_ + \code + // atomic-context consumer + if (wc_RNG_DRBG_NextSeedNow(rng) == 0) { + // freshly reseeded without touching the seed source + } + \endcode + + \sa wc_RNG_DRBG_NextSeedGenerate + \sa wc_RNG_DRBG_NextSeedNow_Nonce +*/ +int wc_RNG_DRBG_NextSeedNow(WC_RNG* rng); + +/*! + \ingroup Random + + \brief The nonce-bearing form of wc_RNG_DRBG_NextSeedNow(): the nonce is + mixed in as uncredited additional input alongside the banked seed. + + \return 0 Success + \return NEEDS_RECOVERY_E A purge crossed the consume (an invalidation + epoch boundary): no material is adopted, the entropy-invalidated latch + is re-asserted, and a recovery reseed is scheduled. + \return NOT_READY_E No bank is ready. + \return BAD_FUNC_ARG rng is null, or nonce is null with nonceSz nonzero. + \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). + \return DRBG_CONT_FIPS_E The continuous test failed; the DRBG is out of + service. + \return RNG_FAILURE_E The DRBG is out of service. + + \param rng The RNG object to reseed. + \param nonce Additional input. + \param nonceSz Length of nonce in bytes. + + \sa wc_RNG_DRBG_NextSeedNow +*/ +int wc_RNG_DRBG_NextSeedNow_Nonce(WC_RNG* rng, const byte* nonce, + word32 nonceSz); + +/*! + \ingroup Random + + \brief Bank caller-supplied material (up to + WC_DRBG_NEXT_STIR_LEN bytes) in the uncredited accumulator + beside the banked next seed. Writer-safe without a lease + (read-copy-store); if the accumulator is already full, the material is + absorbed by xor. Harvested entropy deposited here improves the instance + without claiming credit. + + \return 0 Success + \return BAD_FUNC_ARG rng or nonce is null, or nonceSz is 0. + \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). + + \param rng The RNG object whose accumulator to feed. + \param nonce Material to bank. + \param nonceSz Length of nonce in bytes. + + \sa wc_RNG_DRBG_NextStirNow + \sa wc_RNG_DRBG_Stir +*/ +int wc_RNG_DRBG_NextStirStore(WC_RNG* rng, const byte *nonce, + word32 nonceSz); + +/*! + \ingroup Random + + \brief Stir the banked uncredited accumulator into the DRBG as an + uncredited, source-free mix-in -- safe in atomic context; the reseed + counter is not reset. The caller must own the instance. + + \return 0 Success + \return NOT_READY_E The accumulator is empty or still accumulating, or + the stir is refused (reseed interval, entropy-invalidated quarantine). + \return BUSY_E The accumulator was claimed by a racing consumer -- the + stir is happening by another hand. + \return BAD_FUNC_ARG rng is null. + \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). + \return RNG_FAILURE_E The DRBG is out of service, or its hash failed + mid-stir leaving a half-applied update -- the instance is then + condemned (status DRBG_FAILED). + + \param rng The RNG object to stir. + + \sa wc_RNG_DRBG_NextStirStore +*/ +int wc_RNG_DRBG_NextStirNow(WC_RNG* rng); + +/*! + \ingroup Random + + \brief Acquire rng's exclusive-ownership lock latch, spinning on the CAS + until acquired, and or the caller's extra bits into the lock word. On an + instance without the lock-required policy the call is a successful no-op + unless extra bits are supplied. + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + \return BUSY_E The lock is held. + \return NEEDS_RECOVERY_E The instance's entropy is invalidated (see + wc_RNG_invalidate_entropy()); recover with a credited reseed before + use. + \return BAD_MUTEX_E (WC_RNG_HAVE_LOCK_FULL_MUTEX) The outer mutex failed. + \return UNEXPECTED_STATE_E Spurious acquisition failure; retry. + + \param rng The RNG object to lock. + \param extra_bits Caller-defined bits (above WC_RNG_LOCK_EXTRA_SHIFT) to + set atomically with the acquisition, or 0. + + _Example_ + \code + if (wc_RNG_lock_get(rng, 0) == 0) { + ret = wc_RNG_GenerateBlock(rng, out, sizeof(out)); + wc_RNG_lock_put(rng, 0); + } + \endcode + + \sa wc_RNG_lock_put + \sa wc_RNG_lock_get_conditional + \sa wc_RNG_lock_read +*/ +int wc_RNG_lock_get(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits); + +/*! + \ingroup Random + + \brief The conditional form of wc_RNG_lock_get(): acquire only if the + current extra bits equal expected_extra_bits, atomically replacing them + with want_extra_bits on success. Non-blocking with respect to the + condition: a mismatch fails immediately rather than spinning. + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + \return BUSY_E The lock is held, or the extra bits do not match + expected_extra_bits. + \return NEEDS_RECOVERY_E Entropy-invalidated and + WC_RNG_LOCK_ENTROPY_INVALIDATED is not in expected_extra_bits. + \return BAD_MUTEX_E (WC_RNG_HAVE_LOCK_FULL_MUTEX) The outer mutex + failed. + \return UNEXPECTED_STATE_E Spurious acquisition failure; retry. + + \param rng The RNG object to lock. + \param expected_extra_bits The extra bits required for acquisition. + \param want_extra_bits The extra bits to install on acquisition. + + \sa wc_RNG_lock_get + \sa wc_RNG_lock_put_conditional +*/ +int wc_RNG_lock_get_conditional(WC_RNG* rng, + WC_RNG_lock_arg_t expected_extra_bits, + WC_RNG_lock_arg_t want_extra_bits); + +/*! + \ingroup Random + + \brief Release rng's lock latch, clearing the supplied extra bits + atomically with the release. + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + \return OBJECT_NOT_LOCKED_E The latch is not held. + \return NEEDS_RECOVERY_E Released successfully; informational notice that the + instance is entropy-invalidated. + + \param rng The RNG object to unlock. + \param extra_bits Caller-defined bits to clear with the release, or 0. + + \sa wc_RNG_lock_get + \sa wc_RNG_lock_put_conditional +*/ +int wc_RNG_lock_put(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits); + +/*! + \ingroup Random + + \brief The conditional form of wc_RNG_lock_put(): release only if the + current extra bits equal expected_extra_bits, atomically replacing them + with want_extra_bits on success. + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + \return BUSY_E The extra bits did not match expected_extra_bits. + \return OBJECT_NOT_LOCKED_E The latch is not held. + \return NEEDS_RECOVERY_E Released successfully; informational notice + that the instance is entropy-invalidated. + \return UNEXPECTED_STATE_E Spurious release failure. + + \param rng The RNG object to unlock. + \param expected_extra_bits The extra bits required for release. + \param want_extra_bits The extra bits to install on release. + + \sa wc_RNG_lock_put + \sa wc_RNG_lock_get_conditional +*/ +int wc_RNG_lock_put_conditional(WC_RNG* rng, + WC_RNG_lock_arg_t expected_extra_bits, + WC_RNG_lock_arg_t want_extra_bits); + +/*! + \ingroup Random + + \brief Read rng's lock word: the held/required latch bits, the + entropy-invalidated bit, and any caller extra bits. The snapshot is + racy by design. + + \return 0 Success + \return BAD_FUNC_ARG rng or state is null. + + \param rng The RNG object to interrogate. + \param state Receives the lock word. + + \sa wc_RNG_lock_get + \sa wc_RNG_invalidate_entropy +*/ +int wc_RNG_lock_read(WC_RNG* rng, WC_RNG_lock_arg_t* state); + +/*! + \ingroup Random + + \brief Atomically set (or) the supplied caller extra bits in rng's lock + word. The caller should hold the latch. + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + + \param rng The RNG object to modify. + \param extra_bits The bits to set. + + \sa wc_RNG_lock_add_extra + \sa wc_RNG_lock_clear_extra +*/ +int wc_RNG_lock_set_extra(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits); + +/*! + \ingroup Random + + \brief Atomically add the supplied value to the caller extra-bits field + of rng's lock word -- for counters carried above + WC_RNG_LOCK_EXTRA_SHIFT. + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + + \param rng The RNG object to modify. + \param extra_bits The value to add. + + \sa wc_RNG_lock_set_extra + \sa wc_RNG_lock_clear_extra +*/ +int wc_RNG_lock_add_extra(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits); + +/*! + \ingroup Random + + \brief Atomically clear the supplied caller extra bits in rng's lock + word. + + \return 0 Success + \return BAD_FUNC_ARG rng is null. + + \param rng The RNG object to modify. + \param extra_bits The bits to clear. + + \sa wc_RNG_lock_set_extra + \sa wc_RNG_lock_add_extra +*/ +int wc_RNG_lock_clear_extra(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits); + +/*! + \ingroup Random + + \brief Mark rng's seed material untrusted -- for VM fork/resume and + similar duplication events -- opening a new invalidation epoch: banked + and pooled pre-event material is purged and wiped first, then the + entropy-invalidated bit is latched in the lock word. An invalidated + instance refuses service (NEEDS_RECOVERY_E) until recovery-reseeded. + Latch or condemn: on any error return the latch is down, and the + instance is instead condemned (status DRBG_FAILED). A condemned bank + instance is retired and recovered by the entropy daemon; a condemned + leaf gets no daemon rescue -- its owner sees RNG_FAILURE_E from + subsequent operations and recovers it with wc_FreeRng() then + wc_InitRng*(). + + \return 0 Success: purges complete, latch asserted. + \return BAD_FUNC_ARG rng is null. + \return RNG_FAILURE_E (or other nonzero) A purge or the latch failed; + the instance is condemned as above. + + \param rng The RNG object to invalidate. + + _Example_ + \code + // VM-resume handler + (void)wc_RNG_invalidate_entropy(rng); + // subsequent wc_RNG_lock_get() returns NEEDS_RECOVERY_E until recovery + \endcode + + \sa wc_RNG_lock_get + \sa wc_RNG_register_free_hook +*/ +int wc_RNG_invalidate_entropy(WC_RNG* rng); + +/*! + \ingroup Random + + \brief Register a callback fired by wc_FreeRng() at teardown -- for + external registries (e.g. a kernel-module registry that must reach every + live RNG on a VM duplication event) that need to drop their reference + when the object dies. + + \return 0 Success + \return BAD_FUNC_ARG rng or free_hook is null. + + \param rng The RNG object to hook. + \param free_hook The callback. + \param arg Opaque argument passed to the callback. + + \sa wc_RNG_invalidate_entropy + \sa wc_FreeRng +*/ +int wc_RNG_register_free_hook(WC_RNG* rng, wc_RNG_free_hook_cb_t free_hook, + void *arg); + +/*! + \ingroup Random + + \brief Attach a random pool to rng: a buffer of size bytes of + pre-generated output, filled by wc_RNG_Pool_Collect() and drained + atomic-context-safely by wc_RNG_Pool_Extract(). The pool is released + with the instance. + + \return 0 Success + \return BAD_FUNC_ARG rng is null, or size is 0 or out of range. + \return MEMORY_E Allocation failed. + \return ALREADY_E The pool is already allocated. + + \param rng The RNG object to equip. + \param size Pool capacity in bytes. + + _Example_ + \code + wc_RNG_Pool_Alloc(rng, 256); + wc_RNG_Pool_Collect(rng, 256); // sleepable context + word32 n = 16; + if (wc_RNG_Pool_Extract(rng, out, &n) == 0) { + // n bytes delivered, atomic-context-safe + } + \endcode + + \sa wc_RNG_Pool_Collect + \sa wc_RNG_Pool_Extract +*/ +int wc_RNG_Pool_Alloc(WC_RNG* rng, word32 size); + +/*! + \ingroup Random + + \brief Generate up to n bytes into rng's pool from rng itself. The + collect/extract hand-off is arbitrated by an atomic aperture word, so a + single collector is safe alongside concurrent extractors. + + \return 0 Success + \return ALREADY_E The pool is full or being drained. + \return BAD_FUNC_ARG rng is null, has no pool, or n is 0. + + \param rng The RNG object whose pool to fill. + \param n Maximum bytes to collect this call. + + \sa wc_RNG_Pool_Alloc + \sa wc_RNG_Pool_Collect2 + \sa wc_RNG_Pool_Extract +*/ +int wc_RNG_Pool_Collect(WC_RNG* rng, word32 n); + +/*! + \ingroup Random + + \brief The two-instance form of wc_RNG_Pool_Collect(): fill rng_dest's + pool with output drawn from rng_src -- so a service instance's pool can + be topped up by a daemon-owned generator. + + \return 0 Success + \return ALREADY_E The pool is full or being drained. + \return BAD_FUNC_ARG rng_dest or rng_src is null, or rng_dest has no + pool, or n is 0. + \return BAD_STATE_E The pool is not allocated. + \return NOT_READY_E The source could not serve; retry later. + + \param rng_dest The RNG object whose pool to fill. + \param rng_src The RNG object to draw output from. + \param n Maximum bytes to collect this call. + + \sa wc_RNG_Pool_Collect +*/ +int wc_RNG_Pool_Collect2(WC_RNG* rng_dest, WC_RNG* rng_src, word32 n); + +/*! + \ingroup Random + + \brief Drain up to *n bytes from rng's pool into out -- + atomic-context-safe. On success *n reports the bytes actually + delivered; on any error return *n is left unmodified. + + \return 0 Success + \return NOT_READY_E The pool is empty or being filled. + \return BAD_FUNC_ARG rng, out, or n is null, or rng has no pool. + \return BAD_STATE_E The pool is not allocated. + \return RNG_FAILURE_E The instance is out of service. + + \param rng The RNG object whose pool to drain. + \param out Receives the output. + \param n In: bytes requested; out: bytes delivered. + + \sa wc_RNG_Pool_Collect + \sa wc_RNG_Pool_Current +*/ +int wc_RNG_Pool_Extract(WC_RNG* rng, byte* out, word32* n); + +/*! + \ingroup Random + + \brief Report the pool's current fill in bytes. The snapshot is racy by + design. + + \return 0 Success + \return BAD_FUNC_ARG rng or n is null, or rng has no pool. + + \param rng The RNG object to interrogate. + \param n Receives the fill. + + \sa wc_RNG_Pool_Extract +*/ +int wc_RNG_Pool_Current(WC_RNG* rng, word32* n); + +/*! + \ingroup Random + + \brief Snapshot the global RNG debug counters (WC_RNG_DEBUG_STATS) -- + seeds and reseeds by provenance, generates, pool and bank traffic -- + into s, for later delta accounting with wc_rng_debug_stats_sum(). + + \return 0 Success + \return BAD_FUNC_ARG s is null. + + \param s Receives the snapshot. + \param rng Optional instance for per-instance context, or null. + + \sa wc_rng_debug_stats_sum + \sa wc_rng_debug_stats_restore +*/ +int wc_rng_debug_stats_snap(struct wc_rng_debug_stats_snapshot *s, + const WC_RNG *rng); + +/*! + \ingroup Random + + \brief Restore the global RNG debug counters from a snapshot -- so a + test can unwind its own accounting. + + \return 0 Success + \return BAD_FUNC_ARG s is null. + + \param s The snapshot to restore from. + \param rng Optional instance for per-instance context, or null. + + \sa wc_rng_debug_stats_snap +*/ +int wc_rng_debug_stats_restore(const struct wc_rng_debug_stats_snapshot *s, + WC_RNG *rng); + +/*! + \ingroup Random + + \brief Accumulate the current global RNG debug counters into s -- + combined with a prior wc_rng_debug_stats_snap(), a delta accounting of + the interval's RNG activity. + + \return 0 Success + \return BAD_FUNC_ARG s is null. + + \param s The snapshot to accumulate into. + \param rng Optional instance for per-instance context, or null. + + \sa wc_rng_debug_stats_snap +*/ +int wc_rng_debug_stats_sum(struct wc_rng_debug_stats_snapshot *s, + const WC_RNG *rng); diff --git a/doc/dox_comments/header_files/rng_bank.h b/doc/dox_comments/header_files/rng_bank.h new file mode 100644 index 00000000000..ac35bd321a3 --- /dev/null +++ b/doc/dox_comments/header_files/rng_bank.h @@ -0,0 +1,961 @@ +/*! + \ingroup Random + + \brief Allocate and initialize a bank of n_rngs pre-instantiated WC_RNG + instances, checked out and back in by consumers (wc_rng_bank_checkout() + et al.). The bank is allocated from heap; release with + wc_rng_bank_free(). Bank-level flags (WC_RNG_BANK_FLAG_*) fix the + bank's posture at initialization: e.g. _CAN_WAIT admits sleeping, + _QUIET suppresses seeding-degradation warnings, + _NO_CHECKOUT_REFCOUNTING suppresses per-checkout refcount traffic for + container-guaranteed lifetimes, _PREDICTION_RESISTANCE imposes a + bank-wide fresh-reseed posture on every sleepable lease. + + \return 0 Success + \return BAD_FUNC_ARG ctx is null or n_rngs is out of range. + \return MEMORY_E Allocation failed. + \return RNG_FAILURE_E No instance could be seeded within timeout_secs. + + \param ctx Receives the allocated bank. + \param n_rngs Number of instances. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* bank-posture flags. + \param timeout_secs Seeding timeout budget. + \param heap Heap hint for dynamic allocation. + \param devId Device id, or INVALID_DEVID. + + _Example_ + \code + struct wc_rng_bank *bank = NULL; + if (wc_rng_bank_new(&bank, 4, WC_RNG_BANK_FLAG_CAN_WAIT, 10, + NULL, INVALID_DEVID) == 0) { + // ... checkout/checkin traffic ... + wc_rng_bank_free(&bank); + } + \endcode + + \sa wc_rng_bank_init + \sa wc_rng_bank_checkout + \sa wc_rng_bank_free +*/ +int wc_rng_bank_new(struct wc_rng_bank **ctx, int n_rngs, word32 flags, + int timeout_secs, void *heap, int devId); + +/*! + \ingroup Random + + \brief Initialize a caller-provided bank object. Semantics of + wc_rng_bank_new(), without the allocation; release with + wc_rng_bank_fini(). + + \return 0 Success + \return BAD_FUNC_ARG ctx is null or n_rngs is out of range. + \return BAD_LENGTH_E n_rngs exceeds the static capacity + (WC_RNG_BANK_STATIC builds). + \return RNG_FAILURE_E No instance could be seeded within timeout_secs. + + \param ctx The bank object to initialize. + \param n_rngs Number of instances. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* bank-posture flags. + \param timeout_secs Seeding timeout budget. + \param heap Heap hint for dynamic allocation. + \param devId Device id, or INVALID_DEVID. + + \sa wc_rng_bank_new + \sa wc_rng_bank_init_nonce + \sa wc_rng_bank_fini +*/ +int wc_rng_bank_init(struct wc_rng_bank *ctx, int n_rngs, word32 flags, + int timeout_secs, void *heap, int devId); + +/*! + \ingroup Random + + \brief The nonce-bearing form of wc_rng_bank_init(): the nonce is used + as additional instantiation input for each instance. + + \return 0 Success + \return BAD_FUNC_ARG ctx is null, n_rngs is out of range, or nonce is + null with nonceSz nonzero. + \return RNG_FAILURE_E No instance could be seeded within timeout_secs. + \return BAD_LENGTH_E nonceSz exceeds the supported maximum. + \return MEMORY_E Allocation failed. + \return WC_TIMEOUT_E Instance seeding exceeded timeout_secs. + + \param ctx The bank object to initialize. + \param n_rngs Number of instances. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* bank-posture flags. + \param timeout_secs Seeding timeout budget. + \param heap Heap hint for dynamic allocation. + \param devId Device id, or INVALID_DEVID. + \param nonce Additional instantiation input. + \param nonceSz Length of nonce in bytes. + \param perso Optional personalization string (may be null). + \param persoSz Length of perso in bytes. + + \sa wc_rng_bank_init +*/ +int wc_rng_bank_init_nonce(struct wc_rng_bank *ctx, int n_rngs, + word32 flags, int timeout_secs, void *heap, + int devId, const byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz); + +/*! + \ingroup Random + + \brief Designate the first instance of the failover pool: checkouts + without a targeted preference rotate through instances at and above + first_failover_inst, reserving the lower offsets for targeted + (affinity or daemon) use. + + \return 0 Success + \return BAD_FUNC_ARG ctx is null or first_failover_inst is out of range. + + \param ctx The bank to configure. + \param first_failover_inst The first failover-eligible instance offset. + + \sa wc_rng_bank_checkout +*/ +int wc_rng_bank_first_failover_inst_set(struct wc_rng_bank *ctx, + int first_failover_inst); + +/*! + \ingroup Random + + \brief Install affinity handlers: callbacks that pin the caller to an + execution context (e.g. disable preemption or migration), report its id + for instance affinity, and unpin. With handlers installed, + WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST checkouts prefer the instance + matching the caller's affinity id, and WC_RNG_BANK_FLAG_AFFINITY_LOCK + holds the pin across the lease. + + \return 0 Success + \return BAD_FUNC_ARG ctx is null. + \return BUSY_E The bank is in service; handlers must be set before use. + + \param ctx The bank to configure. + \param affinity_lock_cb Pin the caller; may be null. + \param affinity_get_id_cb Report the caller's affinity id. + \param affinity_unlock_cb Unpin the caller; may be null. + \param cb_arg Opaque argument passed to the callbacks. + + \sa wc_rng_bank_checkout +*/ +int wc_rng_bank_set_affinity_handlers(struct wc_rng_bank *ctx, + wc_affinity_lock_fn_t affinity_lock_cb, + wc_affinity_get_id_fn_t affinity_get_id_cb, + wc_affinity_unlock_fn_t affinity_unlock_cb, + void *cb_arg); + +/*! + \ingroup Random + + \brief Tear down a bank initialized with wc_rng_bank_init(): once the + refcount and per-instance lease gates pass, fires any registered free + hook and frees every instance. Never waits: a referenced or leased + bank is refused with BUSY_E, and the caller quiesces its consumers and + retries. + + \return 0 Success + \return BAD_FUNC_ARG ctx is null. + \return BUSY_E The bank is still referenced, or an instance lease is + outstanding. + \return BAD_STATE_E The refcount is below its initialization baseline + (teardown of an uninitialized or corrupted bank). + + \param ctx The bank to tear down. + + \sa wc_rng_bank_init + \sa wc_rng_bank_free + \sa wc_rng_bank_register_free_hook +*/ +int wc_rng_bank_fini(struct wc_rng_bank *ctx); + +/*! + \ingroup Random + + \brief Tear down and release a bank allocated with wc_rng_bank_new(). + + \return 0 Success + \return BAD_FUNC_ARG ctx or *ctx is null. + \return BAD_STATE_E The bank is still referenced. + + \param ctx The bank to release; nulled on success. + + \sa wc_rng_bank_new + \sa wc_rng_bank_fini +*/ +int wc_rng_bank_free(struct wc_rng_bank **ctx); + +/*! + \ingroup Random + + \brief Register bank as the process-default bank, retrievable with + wc_rng_bank_default_checkout(). + + \return 0 Success + \return BAD_FUNC_ARG bank is null. + \return BAD_STATE_E A default bank is already registered. + \return BUSY_E Registration is contended; retry. + + \param bank The bank to register. + + \sa wc_rng_bank_default_checkout + \sa wc_rng_bank_default_clear +*/ +int wc_rng_bank_default_set(struct wc_rng_bank *bank); + +/*! + \ingroup Random + + \brief Take a reference on the process-default bank. + + \return 0 Success + \return BAD_FUNC_ARG bank is null. + \return BAD_STATE_E No default bank is registered. + \return NO_DEFAULT_FOUND_E No default bank is registered. + + \param bank Receives the default bank. + + \sa wc_rng_bank_default_set + \sa wc_rng_bank_default_checkin +*/ +int wc_rng_bank_default_checkout(struct wc_rng_bank **bank); + +/*! + \ingroup Random + + \brief Release a reference taken with wc_rng_bank_default_checkout(). + + \return 0 Success + \return BAD_FUNC_ARG bank or *bank is null. + + \param bank The reference to release; nulled on success. + + \sa wc_rng_bank_default_checkout +*/ +int wc_rng_bank_default_checkin(struct wc_rng_bank **bank); + +/*! + \ingroup Random + + \brief Unregister the process-default bank. + + \return 0 Success + \return BAD_FUNC_ARG bank is null or is not the registered default. + \return BUSY_E Unregistration is contended; retry. + + \param bank The bank to unregister. + + \sa wc_rng_bank_default_set +*/ +int wc_rng_bank_default_clear(struct wc_rng_bank *bank); + +/*! + \ingroup Random + + \brief Lease an in-service instance from the bank: either the preferred + (or affinity-matched) instance, or -- with + WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST -- the first available failover + instance. On success the caller owns the instance's lock; access the + WC_RNG with WC_RNG_BANK_INST_TO_RNG() and return the lease with + wc_rng_bank_inst_checkin(). WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED consumes + a ready banked next seed in an immediate source-free credited reseed + before returning; WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE (per-call, + requires _CAN_WAIT) freshly credited-reseeds the lease before the + caller's first draw. + + \details Out-of-service instances: a targeted (non-failover) checkout + admits them; failover checkouts divert around them. + WC_RNG_BANK_FLAG_FOR_RECOVERY makes the targeted admission explicit and + interaction-safe (recovery machinery and patrols): out-of-service + status is expected, the _CONSUME_NEXT_SEED arm is suppressed (a consume + would fail on exactly the instances recovery targets), and failover and + affinity selection are rejected in combination. + WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED gives the opposite guarantee: + either a lease on an in-service instance, or an error with no lease -- + never a lease on an out-of-service instance; under _CAN_WAIT an + out-of-service instance is retried within the timeout budget (allowing + a patrol to restore it), and the distinguished error for a lap or wait + that found only out-of-service instances is BAD_STATE_E. + + Entropy-invalidated (quarantined) instances refuse ordinary leases with + NEEDS_RECOVERY_E, with two admissions. First, when the quarantined + instance holds a READY banked next seed, any claimant is admitted and + the consume-at-checkout reseed runs unconditionally: the invalidation + purge guarantees READY banked material post-dates the invalidation + event, so admitting the claimant is completing the recovery. Second, + WC_RNG_BANK_FLAG_MAYBE_FOR_RECOVERY admits the caller to a quarantined + instance with no banked material, transferring the recovery obligation: + checkout then returns NEEDS_RECOVERY_E with the checkout otherwise + complete -- *rng_inst set; instance lock, affinity locks, and any + vector-inhibit state held. This is the robust-mutex (EOWNERDEAD) + pattern: an error return with the acquisition complete and persistent, + because "this resource needs consistency recovery" is only safely + reportable to a caller that already holds it. The caller must either + recover the instance (a credited reseed, e.g. wc_RNG_DRBG_Reseed_Now(), + clears the quarantine) or check it back in. Ordinary consumers that + cannot complete a recovery must not pass this flag. + + \return 0 Success; *rng_inst holds the lease. + \return BAD_FUNC_ARG bank or rng_inst is null, or the flags are + contradictory. + \return NEEDS_RECOVERY_E Quarantined: refused without a lease + (ordinary checkout), or lease held with recovery owed + (_MAYBE_FOR_RECOVERY; see \details). + \return BAD_STATE_E (_ERROR_ON_RNG_FAILED) only out-of-service + instances were found. + \return WC_TIMEOUT_E The wait budget expired. + \return RNG_FAILURE_E No serviceable instance. + \return BAD_INDEX_E preferred_inst_offset is out of range. + \return BUSY_E The selected instance is contended (without _CAN_WAIT). + + \param bank The bank to lease from. + \param rng_inst Receives the leased instance. + \param preferred_inst_offset The preferred instance, or 0. + \param timeout_secs Wait budget (with _CAN_WAIT). + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* per-call flags. + + _Example_ + \code + struct wc_rng_bank_inst *inst = NULL; + if (wc_rng_bank_checkout(bank, &inst, 0, 10, + WC_RNG_BANK_FLAG_CAN_WAIT | + WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST) == 0) { + ret = wc_RNG_GenerateBlock(WC_RNG_BANK_INST_TO_RNG(inst), + out, sizeof(out)); + wc_rng_bank_inst_checkin(&inst); + } + \endcode + + \sa wc_rng_bank_inst_checkin + \sa wc_rng_bank_recover_inst + \sa wc_rng_bank_spawn +*/ +int wc_rng_bank_checkout(struct wc_rng_bank *bank, + struct wc_rng_bank_inst **rng_inst, + int preferred_inst_offset, int timeout_secs, + word32 flags); + +/*! + \ingroup Random + + \brief Return a lease through the bank object, validating that rng_inst + belongs to bank. Prefer wc_rng_bank_inst_checkin() when only the + instance pointer is at hand. + + \return 0 Success + \return BAD_FUNC_ARG bank or rng_inst is null, or the instance does not + belong to bank. + \return OBJECT_NOT_LOCKED_E The instance's lease is not held (e.g. a + stale duplicate check-in). + \return NEEDS_RECOVERY_E Checked in successfully; informational + notice that the instance is entropy-invalidated. + + \param bank The bank the instance belongs to. + \param rng_inst The lease to return; nulled on success. + + \sa wc_rng_bank_inst_checkin + \sa wc_rng_bank_checkout +*/ +int wc_rng_bank_checkin(struct wc_rng_bank *bank, + struct wc_rng_bank_inst **rng_inst); + +/*! + \ingroup Random + + \brief Return a lease by instance pointer alone. + + \return 0 Success + \return BAD_FUNC_ARG rng_inst or *rng_inst is null. + \return OBJECT_NOT_LOCKED_E The instance's lease is not held. + \return NEEDS_RECOVERY_E Checked in successfully; informational + notice that the instance is entropy-invalidated. + + \param rng_inst The lease to return; nulled on success. + + \sa wc_rng_bank_checkout + \sa wc_rng_bank_checkin +*/ +int wc_rng_bank_inst_checkin(struct wc_rng_bank_inst **rng_inst); + +/*! + \ingroup Random + + \brief Report the instance's offset within its bank. + + \return n The instance offset, non-negative. + \return BAD_FUNC_ARG rng_inst is null. + + \param rng_inst The instance to interrogate. + + \sa wc_rng_bank_checkout +*/ +int wc_rng_bank_get_inst_id(struct wc_rng_bank_inst *rng_inst); + +/*! + \ingroup Random + + \brief Bank next-seed material for the instance at inst_offset -- + wc_RNG_DRBG_NextSeedGenerate() through the bank, without taking the + instance lock; the daemon-side serialization word arbitrates against + concurrent whole-instance reinitialization. + + \return 0 Bytes were banked. + \return ALREADY_E The instance's bank is ready or being consumed. + \return NOT_READY_E The health test could not run; simply retry. + \return BAD_FUNC_ARG bank is null, inst_offset is out of range, or n + is 0. + + \param bank The bank. + \param inst_offset The instance to bank for. + \param n Maximum bytes to bank this call. + + \sa wc_RNG_DRBG_NextSeedGenerate + \sa wc_rng_bank_next_seed_generate_rbgc + \details The daemon-side serialization word (not the instance lock) + excludes a concurrent wc_rng_bank_inst_reinit() from freeing the DRBG + out from under the gather; lease-holders never consult it, since + instance-lock exclusion already covers every lease-holder interaction. + The caller must hold a bank reference (e.g. per the daemon association) + for the duration of the call. Return taxonomy for a banking rotation: + BUSY_E, the gate is held by a reinit -- skip this turn; ALREADY_E, + sleep until the bank is consumed; MISSING_RNG_E, the instance has no + DRBG (RDRAND et al.) and can be retired from the rotation permanently; + other errors are transient gather or health-test failures -- skip the + turn, and alarm if persistent. + +*/ +int wc_rng_bank_next_seed_generate(struct wc_rng_bank *bank, int inst_offset, + word32 n); + +/*! + \ingroup Random + + \brief The chain-sourced form of wc_rng_bank_next_seed_generate(): the + banked material is drawn from root's generate function and tagged with + its provenance. + + \return 0 Bytes were banked. + \return ALREADY_E The instance's bank is ready or being consumed. + \return NOT_READY_E The health test could not run; simply retry. + \return BAD_FUNC_ARG bank or root is null, inst_offset is out of range, + or n is 0. + + \param bank The bank. + \param inst_offset The instance to bank for. + \param n Maximum bytes to bank this call. + + \sa wc_rng_bank_next_seed_generate + \sa wc_RNG_DRBG_NextSeedGenerate_RBGC +*/ +int wc_rng_bank_next_seed_generate_rbgc(struct wc_rng_bank *bank, + int inst_offset, word32 n); + +/*! + \ingroup Random + + \brief Free and reinstantiate a leased instance in place. The caller + must hold the lease; the daemon-side serialization word excludes + concurrent lockless banking during the cycle. + + \return 0 Success + \return BAD_FUNC_ARG bank or rng_inst is null. + \return RNG_FAILURE_E Reinstantiation could not be seeded within + timeout_secs. + \return BUSY_E The whole-instance-operation gate is held (daemon banking in + progress); retry. + + \param bank The bank the instance belongs to. + \param rng_inst The leased instance to reinitialize. + \param timeout_secs Seeding timeout budget. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* flags. + + \sa wc_rng_bank_recover_inst +*/ +int wc_rng_bank_inst_reinit(struct wc_rng_bank *bank, + struct wc_rng_bank_inst *rng_inst, + int timeout_secs, word32 flags); + +/*! + \ingroup Random + + \brief Patrol helper: check out the instance at inst_offset with + WC_RNG_BANK_FLAG_FOR_RECOVERY, recover it iff it needs recovery, and + check it back in. Two recovery arms: an out-of-service instance + (wc_RNG_GetStatus() != WC_DRBG_OK) is reinitialized in place; an + in-service but entropy-invalidated (quarantined) instance takes one + credited reseed, which clears the quarantine while preserving instance + identity. A healthy instance is a success no-op, so callers can + invoke this unconditionally on state observed locklessly. + + \return 0 Success (recovered, or nothing to recover). + \return BAD_FUNC_ARG bank is null, inst_offset is out of range, or the + flags are contradictory. + \return RNG_FAILURE_E Recovery could not be seeded within timeout_secs. + \return BUSY_E The instance lock or whole-instance-operation gate is + contended; retry on a later patrol turn. + + \param bank The bank. + \param inst_offset The instance to patrol. + \param timeout_secs Seeding timeout budget. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* flags. + + _Example_ + \code + // after a VM duplication event has invalidated the bank: + for (i = 0; i < n_rngs; i++) + (void)wc_rng_bank_recover_inst(bank, i, 10, + WC_RNG_BANK_FLAG_CAN_WAIT); + \endcode + + \sa wc_rng_bank_invalidate_entropy + \sa wc_rng_bank_checkout + \details A stale lockless status observation costs one harmless round + trip. BUSY_E reports contention on the instance lock or the + whole-instance-operation gate: retry on a later patrol turn. flags may + include WC_RNG_BANK_FLAG_CAN_WAIT and WC_RNG_BANK_FLAG_AFFINITY_LOCK, + which are passed through. + +*/ +int wc_rng_bank_recover_inst(struct wc_rng_bank *bank, int inst_offset, + int timeout_secs, word32 flags); + +/*! + \ingroup Random + + \brief Spawn an SP 800-90C chain RNG from a bank instance: check out a + parent instance (honoring the usual selection flags), instantiate + child_rng as its chain child (wc_InitRngNonceRBGC()), and check the + parent back in. The child's lifetime is thereafter decoupled from the + parent and its bank; release it with wc_FreeRng() (or wc_rng_free() for + the heap form, wc_rng_bank_spawn_new()). The child's RBGC stratum is + one plus the parent's stratum at instantiation. + + \details A recommended nonce, when available, is a racy read of a + high-resolution timer (e.g. Linux kernel random_get_entropy()). + WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED composes: a ready banked seed is + redeemed on the parent before the spawn draw. + WC_RNG_BANK_FLAG_STIR and WC_RNG_BANK_FLAG_FOR_RECOVERY are + rejected. WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED is implied: the parent + is guaranteed in-service, or an error is returned with no lease and no + child. + + \return 0 Success + \return BAD_FUNC_ARG bank or child_rng is null, or the flags are + contradictory (uncredited or recovery seeding contradict a spawn). + \return RNG_FAILURE_E No serviceable parent within the timeout budget. + + \param bank The bank to spawn from. + \param child_rng The caller-provided WC_RNG to instantiate. + \param nonce Optional additional instantiation input. + \param nonceSz Length of nonce in bytes. + \param perso Optional personalization string (may be null). + \param persoSz Length of perso in bytes. + \param preferred_inst_offset The preferred parent instance, or 0. + \param timeout_secs Wait budget (with _CAN_WAIT). + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* per-call flags. + + \sa wc_rng_bank_spawn_new + \sa wc_InitRngNonceRBGC +*/ +int wc_rng_bank_spawn(struct wc_rng_bank *bank, WC_RNG *child_rng, + byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + int preferred_inst_offset, + int timeout_secs, word32 flags); + +/*! + \ingroup Random + + \brief The allocating form of wc_rng_bank_spawn(): the child is + allocated from the bank's heap and returned through child_rng; release + with wc_rng_free(). + + \return 0 Success + \return BAD_FUNC_ARG bank or child_rng is null. + \return MEMORY_E Allocation failed. + \return RNG_FAILURE_E No serviceable parent within the timeout budget. + + \param bank The bank to spawn from. + \param child_rng Receives the allocated, instantiated WC_RNG. + \param nonce Optional additional instantiation input. + \param nonceSz Length of nonce in bytes. + \param perso Optional personalization string (may be null). + \param persoSz Length of perso in bytes. + \param preferred_inst_offset The preferred parent instance, or 0. + \param timeout_secs Wait budget (with _CAN_WAIT). + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* per-call flags. + + \sa wc_rng_bank_spawn +*/ +int wc_rng_bank_spawn_new(struct wc_rng_bank *bank, WC_RNG **child_rng, + byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + int preferred_inst_offset, int timeout_secs, + word32 flags); + +/*! + \ingroup Random + + \brief Reseed every instance with caller-supplied seed material. + WC_RNG_BANK_FLAG_STIR mixes the material in without + crediting it. + + \return 0 Success + \return BAD_FUNC_ARG bank or seed is null. + \return RNG_FAILURE_E An instance could not be reseeded within + timeout_secs. + + \param bank The bank to seed. + \param seed Seed material. + \param seedSz Length of seed in bytes. + \param nonce Optional per-instance nonce material (may be null). + \param nonceSz Length of nonce in bytes. + \param timeout_secs Wait budget per instance. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* flags. + + \sa wc_rng_bank_seed_range + \sa wc_rng_bank_reseed +*/ +int wc_rng_bank_seed(struct wc_rng_bank *bank, const byte* seed, + word32 seedSz, const byte *nonce, word32 nonceSz, + int timeout_secs, word32 flags); + +/*! + \ingroup Random + + \brief The range form of wc_rng_bank_seed(): seed instances first_inst + through last_inst inclusive. + + \return 0 Success + \return BAD_FUNC_ARG bank or seed is null, or the range is out of + bounds. + \return RNG_FAILURE_E An instance could not be reseeded within + timeout_secs. + \return BAD_INDEX_E The instance range is invalid. + \return BAD_STATE_E The bank is not initialized. + \return NO_DEFAULT_FOUND_E bank is null and no default bank is registered. + + \param bank The bank to seed. + \param first_inst The first instance offset. + \param last_inst The last instance offset. + \param seed Seed material. + \param seedSz Length of seed in bytes. + \param nonce Optional per-instance nonce material (may be null). + \param nonceSz Length of nonce in bytes. + \param timeout_secs Wait budget per instance. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* flags. + + \sa wc_rng_bank_seed +*/ +int wc_rng_bank_seed_range(struct wc_rng_bank *bank, int first_inst, + int last_inst, const byte* seed, word32 seedSz, + const byte *nonce, word32 nonceSz, + int timeout_secs, word32 flags); + +/*! + \ingroup Random + + \brief Reseed every instance from the module's seed source. + + \return 0 Success + \return BAD_FUNC_ARG bank is null. + \return RNG_FAILURE_E An instance could not be reseeded within + timeout_secs. + + \param bank The bank to reseed. + \param nonce Optional per-instance nonce material (may be null). + \param nonceSz Length of nonce in bytes. + \param timeout_secs Wait budget per instance. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* flags. + + \sa wc_rng_bank_reseed_range + \sa wc_rng_bank_seed +*/ +int wc_rng_bank_reseed(struct wc_rng_bank *bank, + const byte *nonce, word32 nonceSz, + int timeout_secs, word32 flags); + +/*! + \ingroup Random + + \brief The range form of wc_rng_bank_reseed(). + + \return 0 Success + \return BAD_FUNC_ARG bank is null, or the range is out of bounds. + \return RNG_FAILURE_E An instance could not be reseeded within + timeout_secs. + \return BAD_INDEX_E The instance range is invalid. + \return BAD_STATE_E The bank is not initialized. + \return WC_TIMEOUT_E The walk exceeded timeout_secs. + + \param bank The bank to reseed. + \param nonce Optional per-instance nonce material (may be null). + \param nonceSz Length of nonce in bytes. + \param first_inst The first instance offset. + \param last_inst The last instance offset. + \param timeout_secs Wait budget per instance. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* flags. + + \sa wc_rng_bank_reseed +*/ +int wc_rng_bank_reseed_range(struct wc_rng_bank *bank, int first_inst, + int last_inst, + const byte *nonce, word32 nonceSz, + int timeout_secs, word32 flags); + +/*! + \ingroup Random + + \brief Set the entropy-invalidated latch on every instance (see + wc_RNG_invalidate_entropy()): cached entropy products are discarded, + and each instance is forced through a credited reseed before its next + generate serves output. Lock-free and constant-time per instance; safe + from a state-invalidation event context (VM fork/resume). + + \return 0 Success + \return BAD_FUNC_ARG bank is null. + \return BAD_STATE_E The bank is not initialized. + + \param bank The bank to invalidate. + \param flags Bitwise-or of WC_RNG_BANK_FLAG_* flags. + + _Example_ + \code + // VM-resume notifier + (void)wc_rng_bank_invalidate_entropy(bank, WC_RNG_BANK_FLAG_NONE); + // instances recover on next checkout, or by patrol: + // wc_rng_bank_recover_inst() + \endcode + + \sa wc_RNG_invalidate_entropy + \sa wc_rng_bank_recover_inst + \details Also purges each instance's banked next-seed apertures: the + purge is the provenance guarantee that a READY bank observed after the + event holds post-event material (see wc_rng_bank_checkout()'s recovery + admissions). Walks every instance even on error, returning the first + error. flags must be 0. + +*/ +int wc_rng_bank_invalidate_entropy(struct wc_rng_bank *bank, word32 flags); + +/*! + \ingroup Random + + \brief Reserve the bank's daemon slot with a caller-chosen nonzero + magic word, admitting exactly one scheduling daemon per bank. The + lifecycle is strictly ordered: _reserve, then _register, then + _unregister, then _release. + + \return 0 Success + \return BAD_FUNC_ARG bank is null or magic is the free sentinel. + \return BUSY_E The slot is claimed, or claiming is contended. + + \param bank The bank to claim. + \param magic The daemon's magic word. + + \sa wc_rng_bank_daemon_register + \sa wc_rng_bank_daemon_release +*/ +int wc_rng_bank_daemon_reserve(struct wc_rng_bank *bank, + WC_ATOMIC_UINT_ARG magic); + +/*! + \ingroup Random + + \brief Register the daemon object in a slot reserved with the same + magic word. + + \return 0 Success + \return BAD_FUNC_ARG bank or daemon is null. + \return ALREADY_E The slot is already in the requested state. + \return WRONG_TYPE_OBJECT_E magic does not match the claim. + \return BUSY_E The slot transition is contended; retry. + + \param bank The bank. + \param daemon The daemon object to register. + \param magic The daemon's magic word. + + \sa wc_rng_bank_daemon_reserve + \sa wc_rng_bank_daemon_unregister +*/ +int wc_rng_bank_daemon_register(struct wc_rng_bank *bank, void *daemon, + WC_ATOMIC_UINT_ARG magic); + +/*! + \ingroup Random + + \brief Unregister the daemon object, returning it through daemon. + + \return 0 Success + \return BAD_FUNC_ARG bank or daemon is null. + \return ALREADY_E The slot is already in the requested state. + \return WRONG_TYPE_OBJECT_E magic does not match the claim. + \return BUSY_E The slot transition is contended; retry. + + \param bank The bank. + \param daemon Receives the registered daemon object. + \param magic The daemon's magic word. + + \sa wc_rng_bank_daemon_register + \sa wc_rng_bank_daemon_release +*/ +int wc_rng_bank_daemon_unregister(struct wc_rng_bank *bank, void **daemon, + WC_ATOMIC_UINT_ARG magic); + +/*! + \ingroup Random + + \brief Release the daemon slot claimed with magic, returning it to the + free sentinel. + + \return 0 Success + \return BAD_FUNC_ARG bank is null. + \return ALREADY_E The slot is already in the requested state. + \return WRONG_TYPE_OBJECT_E magic does not match the claim. + \return BUSY_E The slot transition is contended; retry. + + \param bank The bank. + \param magic The daemon's magic word. + + \sa wc_rng_bank_daemon_reserve +*/ +int wc_rng_bank_daemon_release(struct wc_rng_bank *bank, + WC_ATOMIC_UINT_ARG magic); + +/*! + \ingroup RNGBank + + \brief Initialize the bank's root RNG -- the primary-sourced parent + that seeds and recovers the bank's instances. + + \return 0 Success + \return BAD_FUNC_ARG bank is null. + + \param bank The bank whose root to initialize. + \param nonce Optional instantiation nonce (may be null). + \param nonceSz Length of nonce in bytes. + \param perso Optional personalization string (may be null). + \param persoSz Length of perso in bytes. + \param flags Bitwise-or of WC_RNG_INIT_FLAG_* attributes. + + \sa wc_rng_bank_root_rng_get +*/ +int wc_rng_bank_root_rng_init(struct wc_rng_bank *bank, + const byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + word32 flags); + +/*! + \ingroup RNGBank + + \brief Report the bank's root RNG. + + \return The root WC_RNG, or null when none is initialized or bank is + null. + + \param bank The bank to interrogate. + + \sa wc_rng_bank_root_rng_init +*/ +WC_RNG *wc_rng_bank_root_rng_get(struct wc_rng_bank *bank); + +/*! + \ingroup Random + + \brief Register a callback fired by wc_rng_bank_fini() once its + refcount and leak gates pass -- i.e. once teardown is committed -- for + external registries that must drop their reference when the bank dies. + One-shot: the hook is cleared before firing. A null free_hook + unregisters. + + \return 0 Success + \return BAD_FUNC_ARG bank is null. + + \param bank The bank to hook. + \param free_hook The callback. + \param arg Opaque argument passed to the callback. + + \sa wc_rng_bank_fini + \sa wc_RNG_register_free_hook +*/ +int wc_rng_bank_register_free_hook(struct wc_rng_bank *bank, + wc_rng_bank_free_hook_cb_t free_hook, + void *arg); + +/*! + \ingroup Random + + \brief Initialize rng as a bank reference: a WC_RNG with no DRBG of its + own, whose wc_RNG_GenerateBlock() transparently checks an instance out + of bank, generates, and checks it back in. Release with wc_FreeRng(). + + \return 0 Success + \return BAD_FUNC_ARG bank or rng is null. + + \param bank The bank to reference. + \param rng The WC_RNG to initialize as a reference. + + _Example_ + \code + WC_RNG rng; + if (wc_InitRng_BankRef(bank, &rng) == 0) { + // rng now serves through the bank + ret = wc_RNG_GenerateBlock(&rng, out, sizeof(out)); + wc_FreeRng(&rng); + } + \endcode + + \sa wc_BankRef_Release + \sa wc_rng_new_bankref +*/ +int wc_InitRng_BankRef(struct wc_rng_bank *bank, WC_RNG *rng); + +/*! + \ingroup Random + + \brief Release a bank reference. wc_FreeRng() calls this + automatically for bank references; direct use is rarely needed. + + \return 0 Success + \return BAD_FUNC_ARG rng is null or is not a bank reference. + + \param rng The bank reference to release. + + \sa wc_InitRng_BankRef +*/ +int wc_BankRef_Release(WC_RNG *rng); + +/*! + \ingroup Random + + \brief The allocating form of wc_InitRng_BankRef(): the reference is + allocated from the bank's heap; release with wc_rng_free(). + + \return 0 Success + \return BAD_FUNC_ARG bank or rng is null. + \return MEMORY_E Allocation failed. + + \param bank The bank to reference. + \param rng Receives the allocated bank reference. + + \sa wc_InitRng_BankRef +*/ +int wc_rng_new_bankref(struct wc_rng_bank *bank, WC_RNG **rng); + +/*! + \ingroup Random + + \brief Snapshot the RNG debug counters (WC_RNG_DEBUG_STATS) with + bank-level context. + + \return 0 Success + \return BAD_FUNC_ARG s is null. + + \param s Receives the snapshot. + \param bank Optional bank for context, or null. + + \sa wc_rng_debug_stats_snap +*/ +int wc_rng_bank_debug_stats_snap(struct wc_rng_debug_stats_snapshot *s, + struct wc_rng_bank *bank); diff --git a/linuxkm/include.am b/linuxkm/include.am index e99954af615..39b28a5ce6a 100644 --- a/linuxkm/include.am +++ b/linuxkm/include.am @@ -35,4 +35,5 @@ EXTRA_DIST += m4/ax_linuxkm.m4 \ 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/7.0/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v0.patch \ + linuxkm/patches/7.3/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v3.patch diff --git a/linuxkm/linuxkm_wc_port.h b/linuxkm/linuxkm_wc_port.h index 051c37ed012..6902c62cdf6 100644 --- a/linuxkm/linuxkm_wc_port.h +++ b/linuxkm/linuxkm_wc_port.h @@ -795,6 +795,10 @@ } #endif #define WC_LKM_REFCOUNT_TO_INT(refcount) wc_lkm_refcount_to_int(&(refcount)) + #include + #ifdef CONFIG_PM_SLEEP + #include + #endif #endif /* !WC_CONTAINERIZE_THIS */ #endif /* LINUXKM_LKCAPI_REGISTER */ diff --git a/linuxkm/lkcapi_glue.c b/linuxkm/lkcapi_glue.c index 685e4736010..42d26a251ad 100644 --- a/linuxkm/lkcapi_glue.c +++ b/linuxkm/lkcapi_glue.c @@ -228,14 +228,14 @@ static int linuxkm_lkcapi_unregister(void); static int enabled_kernel_fips_enabled = 0; #endif -static ssize_t install_algs_handler(struct kobject *kobj, struct kobj_attribute *attr, +static ssize_t install_algs_handler(WC_MODULE_ATTR_CONST struct module_attribute *mattr, struct module_kobject *mk, const char *buf, size_t count) { int arg; int ret; - (void)kobj; - (void)attr; + (void)mattr; + (void)mk; if (kstrtoint(buf, 10, &arg) || arg != 1) return -EINVAL; @@ -249,14 +249,14 @@ static ssize_t install_algs_handler(struct kobject *kobj, struct kobj_attribute return count; } -static ssize_t deinstall_algs_handler(struct kobject *kobj, struct kobj_attribute *attr, +static ssize_t deinstall_algs_handler(WC_MODULE_ATTR_CONST struct module_attribute *mattr, struct module_kobject *mk, const char *buf, size_t count) { int arg; int ret; - (void)kobj; - (void)attr; + (void)mattr; + (void)mk; if (kstrtoint(buf, 10, &arg) || arg != 1) return -EINVAL; @@ -279,22 +279,50 @@ static ssize_t deinstall_algs_handler(struct kobject *kobj, struct kobj_attribut /* create control channels at /sys/module/libwolfssl/{install_algs,deinstall_algs} */ -static struct kobj_attribute install_algs_attr = __ATTR(install_algs, 0220, NULL, install_algs_handler); -static struct kobj_attribute deinstall_algs_attr = __ATTR(deinstall_algs, 0220, NULL, deinstall_algs_handler); +static struct module_attribute install_algs_attr = __ATTR(install_algs, 0220, NULL, install_algs_handler); +static struct module_attribute deinstall_algs_attr = __ATTR(deinstall_algs, 0220, NULL, deinstall_algs_handler); static int installed_sysfs_LKCAPI_files = 0; static int linuxkm_lkcapi_sysfs_install(void) { int ret; if (! installed_sysfs_LKCAPI_files) { - ret = linuxkm_lkcapi_sysfs_install_node(&install_algs_attr, NULL); + ret = linuxkm_sysfs_install_attr(&install_algs_attr.attr, NULL); if (ret) return ret; - ret = linuxkm_lkcapi_sysfs_install_node(&deinstall_algs_attr, NULL); + ret = linuxkm_sysfs_install_attr(&deinstall_algs_attr.attr, NULL); if (ret) { - (void)linuxkm_lkcapi_sysfs_deinstall_node(&install_algs_attr, NULL); + (void)linuxkm_sysfs_deinstall_attr(&install_algs_attr.attr, NULL); return ret; } + +#ifdef WC_LINUXKM_HAVE_RNG_STATE_INVALIDATE_HANDLER + ret = linuxkm_sysfs_install_attr(&wc_linuxkm_rng_state_invalidate_attr.attr, + NULL); + if (ret) { + (void)linuxkm_sysfs_deinstall_attr(&deinstall_algs_attr.attr, + NULL); + (void)linuxkm_sysfs_deinstall_attr(&install_algs_attr.attr, + NULL); + return ret; + } +#endif +#if defined(LINUXKM_LKCAPI_REGISTER_HASH_DRBG_DEFAULT) && \ + defined(WC_RNG_DEBUG_STATS) + ret = linuxkm_sysfs_install_attr(&wc_linuxkm_rng_stats_attr.attr, + NULL); + if (ret) { +#ifdef WC_LINUXKM_HAVE_RNG_STATE_INVALIDATE_HANDLER + (void)linuxkm_sysfs_deinstall_attr(&wc_linuxkm_rng_state_invalidate_attr.attr, + NULL); +#endif + (void)linuxkm_sysfs_deinstall_attr(&deinstall_algs_attr.attr, + NULL); + (void)linuxkm_sysfs_deinstall_attr(&install_algs_attr.attr, + NULL); + return ret; + } +#endif installed_sysfs_LKCAPI_files = 1; } return 0; @@ -302,10 +330,27 @@ static int linuxkm_lkcapi_sysfs_install(void) { static int linuxkm_lkcapi_sysfs_deinstall(void) { if (installed_sysfs_LKCAPI_files) { - int ret = linuxkm_lkcapi_sysfs_deinstall_node(&install_algs_attr, NULL); + int ret; +#if defined(LINUXKM_LKCAPI_REGISTER_HASH_DRBG_DEFAULT) && \ + defined(WC_RNG_DEBUG_STATS) + ret = linuxkm_sysfs_deinstall_attr(&wc_linuxkm_rng_stats_attr.attr, + NULL); + if (ret) + return ret; +#endif +#ifdef WC_LINUXKM_HAVE_RNG_STATE_INVALIDATE_HANDLER + /* removed first (LIFO), and in any case before RNG teardown can + * begin: the store handler walks the registry and reaches the + * daemon root. */ + ret = linuxkm_sysfs_deinstall_attr(&wc_linuxkm_rng_state_invalidate_attr.attr, + NULL); + if (ret) + return ret; +#endif + ret = linuxkm_sysfs_deinstall_attr(&install_algs_attr.attr, NULL); if (ret) return ret; - ret = linuxkm_lkcapi_sysfs_deinstall_node(&deinstall_algs_attr, NULL); + ret = linuxkm_sysfs_deinstall_attr(&deinstall_algs_attr.attr, NULL); if (ret) return ret; installed_sysfs_LKCAPI_files = 0; diff --git a/linuxkm/lkcapi_sha_glue.c b/linuxkm/lkcapi_sha_glue.c index 928eabcd123..99965e84bce 100644 --- a/linuxkm/lkcapi_sha_glue.c +++ b/linuxkm/lkcapi_sha_glue.c @@ -26,7 +26,8 @@ #error lkcapi_sha_glue.c included in non-LINUXKM_LKCAPI_REGISTER project. #endif -#if defined(WC_LINUXKM_C_FALLBACK_IN_SHIMS) && defined(USE_INTEL_SPEEDUP) +#if defined(WC_LINUXKM_C_FALLBACK_IN_SHIMS) && defined(USE_INTEL_SPEEDUP) && \ + !defined(WC_DEBUG_FORCE_KERNEL_SETTINGS) #error SHA* WC_LINUXKM_C_FALLBACK_IN_SHIMS is not currently supported. #endif @@ -49,6 +50,28 @@ #include #include +#ifdef LINUXKM_LKCAPI_REGISTER + _Pragma("GCC diagnostic push"); + _Pragma("GCC diagnostic ignored \"-Wunused-parameter\""); + _Pragma("GCC diagnostic ignored \"-Wpointer-arith\""); + _Pragma("GCC diagnostic ignored \"-Wshadow\""); + _Pragma("GCC diagnostic ignored \"-Wnested-externs\""); + _Pragma("GCC diagnostic ignored \"-Wredundant-decls\""); + _Pragma("GCC diagnostic ignored \"-Wsign-compare\""); + _Pragma("GCC diagnostic ignored \"-Wpointer-sign\""); + _Pragma("GCC diagnostic ignored \"-Wbad-function-cast\""); +#ifndef __clang__ + _Pragma("GCC diagnostic ignored \"-Wdiscarded-qualifiers\""); +#endif +#if defined(__GNUC__) && (__GNUC__ >= 17) + _Pragma("GCC diagnostic ignored \"-Wconstant-logical-operand\""); +#endif + #include + #include + #include + _Pragma("GCC diagnostic pop"); +#endif + #define WOLFKM_SHA1_NAME "sha1" #define WOLFKM_SHA2_224_NAME "sha224" #define WOLFKM_SHA2_256_NAME "sha256" @@ -123,14 +146,20 @@ #define WOLFKM_STDRNG_RDSEED "" #endif +#ifdef WOLFSSL_DRBG_SHA512 + #define WOLFKM_STDRNG_DRIVER_BASE "sha2-512-drbg-nopr" +#else + #define WOLFKM_STDRNG_DRIVER_BASE "sha2-256-drbg-nopr" +#endif + #ifdef LINUXKM_DRBG_GET_RANDOM_BYTES - #define WOLFKM_STDRNG_DRIVER ("sha2-256-drbg-nopr" \ + #define WOLFKM_STDRNG_DRIVER (WOLFKM_STDRNG_DRIVER_BASE \ WOLFKM_STDRNG_WOLFENTROPY \ WOLFKM_STDRNG_RDSEED \ WOLFKM_DRIVER_SUFFIX_BASE \ "-with-global-replace") #else - #define WOLFKM_STDRNG_DRIVER ("sha2-256-drbg-nopr" \ + #define WOLFKM_STDRNG_DRIVER (WOLFKM_STDRNG_DRIVER_BASE \ WOLFKM_STDRNG_WOLFENTROPY \ WOLFKM_STDRNG_RDSEED \ WOLFKM_DRIVER_SUFFIX_BASE) @@ -1364,7 +1393,8 @@ PRAGMA("GCC diagnostic ignored \"-Wnested-externs\""); #include -WC_MAYBE_UNUSED static int linuxkm_hmac_setkey_common(struct crypto_shash *tfm, int type, const byte* key, word32 length) +WC_MAYBE_UNUSED static int linuxkm_hmac_setkey_common(struct crypto_shash *tfm, + int type, const byte* key, word32 length) { struct km_sha_hmac_pstate *p_ctx = (struct km_sha_hmac_pstate *)crypto_shash_ctx(tfm); int ret; @@ -1927,7 +1957,7 @@ WC_MAYBE_UNUSED static int km_hmac_test_export_import( return ret; } -PRAGMA_DIAG_POP +PRAGMA_DIAG_POP /* -Wno-pointer-arith -Wno-nested-externs, for linux/list.h */ WC_MAYBE_UNUSED static int hmac_sha3_test_once(void) { static int once = 0; @@ -2059,7 +2089,16 @@ struct wc_swallow_the_semicolon #error LINUXKM_LKCAPI_REGISTER_HASH_DRBG requires WC_RNG_BANK_DEFAULT_SUPPORT. #endif +#ifdef WC_RNG_DEBUG_STATS + #if defined(SIZEOF_LONG) && (SIZEOF_LONG == 8) + #define WC_RNG_STAT_FMT "%ld" + #else + #define WC_RNG_STAT_FMT "%lld" + #endif +#endif + static volatile int wc_linuxkm_rng_initing_default_bank_flag = 0; +static struct wc_rng_bank *default_bank; #ifndef WC_LINUXKM_INITRNG_TIMEOUT_SEC #define WC_LINUXKM_INITRNG_TIMEOUT_SEC 30 @@ -2093,9 +2132,32 @@ static int linuxkm_affinity_lock(void *arg) { #endif /* !WOLFSSL_USE_SAVE_VECTOR_REGISTERS */ } +/* one per CPU for each of task, softirq, hardirq, and NMI, plus 4 slop */ +#define LINUXKM_RNG_BANK_SIZE (nr_cpu_ids * 4 + 4) +#define LINUXKM_RNG_BANK_LAST_SAFELY_CONTENDABLE (nr_cpu_ids * 2 - 1) +#define LINUXKM_RNG_BANK_FIRST_FAILOVER (nr_cpu_ids * 4) + static int linuxkm_affinity_get_id(void *arg, int *id) { (void)arg; *id = raw_smp_processor_id(); + /* Stratify by execution context class -- one band of nr_cpu_ids + * instances each for task, softirq, hardirq, and NMI -- so that + * same-CPU context nesting never contends for an instance. Note + * in_serving_softirq(), NOT in_softirq(): the latter is also true + * whenever softirqs are merely disabled (local_bh_disable(), + * spin_lock_bh(), including our own affinity-lock callback), which + * would misroute task-context callers into the softirq band. + * Order matters: NMI context also carries hardirq state. + * Misclassification is never unsafe -- the per-instance CAS lease + * is the enforcement -- it only costs the structural-noncontention + * property. + */ + if (in_nmi()) + *id += nr_cpu_ids * 3; + else if (hardirq_count()) + *id += nr_cpu_ids * 2; + else if (in_serving_softirq()) + *id += nr_cpu_ids * 1; return 0; } @@ -2118,10 +2180,936 @@ static int linuxkm_affinity_unlock(void *arg) { #endif /* !WOLFSSL_USE_SAVE_VECTOR_REGISTERS */ } +#define WC_LINUXKM_ENTROPY_DAEMON_MAGIC 0x6f77666c + +#ifdef WC_RNG_HAVE_RBGC + #define WC_LKM_BANK_RBGC_FLAG WC_RNG_BANK_FLAG_RBGC +#else + #define WC_LKM_BANK_RBGC_FLAG WC_RNG_BANK_FLAG_NONE +#endif + +#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \ + defined(WC_RNG_HAVE_FREE_HOOK) && defined(WC_RNG_HAVE_LOCK) + +#define WC_LINUXKM_HAVE_RNG_REGISTRY + +/* Registry of every kernel-module RNG object needing state-invalidation + * coverage: banks (default and tfm-private) and long-lived process-context + * RBGC leaves from LKCAPI_INITRNG(). Atomic-born leaves are deliberately + * excluded (see linuxkm_InitRng_DefaultRBGC()): the mutex is thereby never + * taken from atomic context, so it can sleep, and the daemon's leaf pass + * may gather entropy under it. + * + * Not usable on old FIPS because the mechanism fundamentally depends on + * wc_RNG_register_free_hook(), nor under WC_RNG_NO_FREE_HOOK or + * WC_RNG_NO_LOCK, which remove wc_RNG_register_free_hook() and + * wc_RNG_invalidate_entropy() respectively. + */ +struct linuxkm_rng_object { + struct linuxkm_rng_object *prev, *next; + int is_bank; + union { + WC_RNG *rng; + struct wc_rng_bank *bank; + }; +}; +static DEFINE_MUTEX(wc_linuxkm_rng_registry_mutex); +static struct linuxkm_rng_object *wc_linuxkm_rng_registry_head; +/* Generation counter gating the daemon's registered-leaf recovery sweep: + * incremented by wc_linuxkm_rng_state_invalidate() before it releases the registry + * mutex, snapshotted by the daemon at sweep start, CAS'd from the snapshot + * to 0 at completion. A CAS failure means an invalidation landed since + * the snapshot -- the counter stays hot and the next pass re-sweeps. The + * daemon thereby pays one atomic load per iteration instead of a mutexed + * list walk. */ +static wolfSSL_Atomic_Int wc_linuxkm_rng_registry_needs_recovery = 0; + +static void wc_linuxkm_rng_registry_link(struct linuxkm_rng_object *obj) +{ + mutex_lock(&wc_linuxkm_rng_registry_mutex); + obj->prev = NULL; + obj->next = wc_linuxkm_rng_registry_head; + if (obj->next) + obj->next->prev = obj; + wc_linuxkm_rng_registry_head = obj; + mutex_unlock(&wc_linuxkm_rng_registry_mutex); +} + +static void wc_linuxkm_rng_registry_unlink(struct linuxkm_rng_object *obj) +{ + mutex_lock(&wc_linuxkm_rng_registry_mutex); + if (obj->prev) + obj->prev->next = obj->next; + else + wc_linuxkm_rng_registry_head = obj->next; + if (obj->next) + obj->next->prev = obj->prev; + mutex_unlock(&wc_linuxkm_rng_registry_mutex); +} + +/* wc_FreeRng() free hook for registered leaves: O(1) unlink (arg is the + * registry entry), then free the entry. Process context by the atomic-born + * exclusion rule. */ +static int wc_linuxkm_rng_registry_free_hook(const WC_RNG *rng, void *arg) +{ + struct linuxkm_rng_object *obj = (struct linuxkm_rng_object *)arg; + (void)rng; + wc_linuxkm_rng_registry_unlink(obj); + kfree(obj); + return 0; +} + +static WARN_UNUSED_RESULT int wc_linuxkm_rng_registry_add_rng(WC_RNG *rng) +{ + struct linuxkm_rng_object *obj = kmalloc(sizeof(*obj), GFP_KERNEL); + int ret; + if (obj == NULL) + return MEMORY_E; + obj->is_bank = 0; + obj->rng = rng; + ret = wc_RNG_register_free_hook(rng, wc_linuxkm_rng_registry_free_hook, + obj); + if (ret != 0) { + kfree(obj); + return ret; + } + wc_linuxkm_rng_registry_link(obj); + return 0; +} + +static int wc_linuxkm_rng_registry_bank_free_hook( + const struct wc_rng_bank *bank, void *arg) +{ + struct linuxkm_rng_object *obj = (struct linuxkm_rng_object *)arg; + (void)bank; + wc_linuxkm_rng_registry_unlink(obj); + kfree(obj); + return 0; +} + +static WARN_UNUSED_RESULT int wc_linuxkm_rng_registry_add_bank(struct wc_rng_bank *bank) +{ + struct linuxkm_rng_object *obj = kmalloc(sizeof(*obj), GFP_KERNEL); + int ret; + if (obj == NULL) + return MEMORY_E; + obj->is_bank = 1; + obj->bank = bank; + ret = wc_rng_bank_register_free_hook( + bank, wc_linuxkm_rng_registry_bank_free_hook, obj); + if (ret != 0) { + kfree(obj); + return ret; + } + wc_linuxkm_rng_registry_link(obj); + return 0; +} + +/* platform announcement (VM fork/clone, resume from hibernation) that RNG + * state assumptions no longer hold: invalidate the daemon's local root + * directly (it is unleased by design), invalidate every bank instance, + * and wake the daemon -- its loop-head check recovers the root first, and + * consumers recover per-instance through the NEEDS_RECOVERY_E protocol + * and the daemon's recovery pass. */ +static int wc_linuxkm_rng_state_invalidate(void) { + struct linuxkm_rng_object *obj; + int ret = 0; + + /* Process context (vmfork notifier / pm notifier); the registry mutex + * is sleepable and never taken from atomic context. */ + mutex_lock(&wc_linuxkm_rng_registry_mutex); + for (obj = wc_linuxkm_rng_registry_head; obj != NULL; obj = obj->next) { + if (obj->is_bank) { + int this_ret = wc_rng_bank_invalidate_entropy(obj->bank, 0); + if ((this_ret != 0) && (ret == 0)) + ret = this_ret; +#ifndef WC_LINUXKM_NO_ENTROPY_DAEMON + if (WOLFSSL_ATOMIC_LOAD(obj->bank->daemon_magic) == + WC_LINUXKM_ENTROPY_DAEMON_MAGIC) + { + struct task_struct *t = (struct task_struct *)obj->bank->daemon; + if (t != NULL) + wake_up_process(t); + } + else +#endif + { + /* daemon-less bank: recover synchronously -- the + * FOR_RECOVERY claim path in wc_rng_bank_reseed_range()'s + * checkouts claims the quarantined instances. */ + unsigned long uncredited_nonce = random_get_entropy(); + this_ret = wc_rng_bank_reseed_range( + obj->bank, 0, -1, + (byte *)&uncredited_nonce, (word32)sizeof uncredited_nonce, + WC_LINUXKM_INITRNG_TIMEOUT_SEC, + WC_RNG_BANK_FLAG_CAN_WAIT | WC_LKM_BANK_RBGC_FLAG); + ForceZero(&uncredited_nonce, (word32)sizeof uncredited_nonce); + if ((this_ret != 0) && (ret == 0)) + ret = this_ret; + } + } + else { + (void)wc_RNG_invalidate_entropy(obj->rng); + } + } + (void)wolfSSL_Atomic_Int_FetchAdd(&wc_linuxkm_rng_registry_needs_recovery, + 1); + mutex_unlock(&wc_linuxkm_rng_registry_mutex); + + if (ret != 0) { + pr_err("ERROR: wc_linuxkm_rng_state_invalidate() walk returned err %d.\n", ret); + return -EINVAL; + } + pr_notice("wolfssl: RNG state invalidated; all instances will recover by credited reseed\n"); + return 0; +} + +/* Stock-kernel event coverage for the invalidation machinery: the kernel + * already broadcasts the two state-duplication events publicly -- VM fork + * (vmgenid, via the random_vmfork notifier chain, kernels >= 5.18) and + * resume from hibernation (pm notifier) -- so no kernel patch is needed to + * receive them. Both chains are blocking (process context), so the + * handler's registry mutex is legal, and both unregister calls return only + * after in-flight callbacks complete, so uninstall-before-teardown is + * race-free. */ + +#if IS_ENABLED(CONFIG_VMGENID) +static int wc_linuxkm_rng_vmfork_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + int ret; + (void)nb; + (void)action; + (void)data; /* the vmfork chain carries no payload; on kernels with the + * callback patch, the fork id itself reaches the module as + * harvest via the mix_pool_bytes hook. */ + ret = wc_linuxkm_rng_state_invalidate(); + if (ret != 0) + pr_err("libwolfssl: wc_linuxkm_rng_vmfork_notify: " + "wc_linuxkm_rng_state_invalidate failed with code %d.\n", ret); + return NOTIFY_OK; +} +static struct notifier_block wc_linuxkm_rng_vmfork_nb = { + .notifier_call = wc_linuxkm_rng_vmfork_notify +}; +#endif /* CONFIG_VMGENID */ + +#ifdef CONFIG_PM_SLEEP +static int wc_linuxkm_rng_pm_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + (void)nb; + (void)data; + /* mirror the native crng's policy: hibernation writes RNG state to + * disk (duplication-class); suspend-to-RAM does not. */ + if ((action == PM_POST_HIBERNATION) || (action == PM_POST_RESTORE)) { + int ret = wc_linuxkm_rng_state_invalidate(); + if (ret != 0) + pr_err("libwolfssl: wc_linuxkm_rng_pm_notify for action 0x%lx: " + "wc_linuxkm_rng_state_invalidate failed with code %d.\n", action, ret); + } + return NOTIFY_OK; +} +static struct notifier_block wc_linuxkm_rng_pm_nb = { + .notifier_call = wc_linuxkm_rng_pm_notify +}; +#endif /* CONFIG_PM_SLEEP */ + +static int wc_linuxkm_rng_notifiers_installed = 0; + +static void wc_linuxkm_rng_notifiers_install(void) +{ + if (wc_linuxkm_rng_notifiers_installed) + return; +#if IS_ENABLED(CONFIG_VMGENID) + if (register_random_vmfork_notifier(&wc_linuxkm_rng_vmfork_nb) != 0) + pr_warn("libwolfssl: register_random_vmfork_notifier failed -- " + "no VM-fork RNG invalidation coverage.\n"); +#endif +#ifdef CONFIG_PM_SLEEP + if (register_pm_notifier(&wc_linuxkm_rng_pm_nb) != 0) + pr_warn("libwolfssl: register_pm_notifier failed -- " + "no hibernation RNG invalidation coverage.\n"); +#endif + wc_linuxkm_rng_notifiers_installed = 1; +} + +static void wc_linuxkm_rng_notifiers_uninstall(void) +{ + if (! wc_linuxkm_rng_notifiers_installed) + return; +#ifdef CONFIG_PM_SLEEP + (void)unregister_pm_notifier(&wc_linuxkm_rng_pm_nb); +#endif +#if IS_ENABLED(CONFIG_VMGENID) + (void)unregister_random_vmfork_notifier(&wc_linuxkm_rng_vmfork_nb); +#endif + wc_linuxkm_rng_notifiers_installed = 0; +} + +static ssize_t wc_linuxkm_rng_state_invalidate_handler( + WC_MODULE_ATTR_CONST struct module_attribute *mattr, + struct module_kobject *mk, + const char *buf, size_t count) +{ + int mode = 0; + int ret; + + (void)mattr; + (void)mk; + + if (kstrtoint(buf, 10, &mode) < 0) + return -EINVAL; + if (mode == 1) { + /* direct local exercise */ + ret = wc_linuxkm_rng_state_invalidate(); +#ifdef WOLFSSL_LINUXKM_VERBOSE_DEBUG + pr_info("wc_linuxkm_rng_state_invalidate_handler: called " + "wc_linuxkm_rng_state_invalidate, retval %d.\n", ret); +#endif + return ret ? -EIO : (ssize_t)count; + } +#if IS_ENABLED(CONFIG_VMGENID) + if (mode == 2) { + u8 fake_id[16]; +#if !IS_MODULE(CONFIG_VMGENID) && defined(WC_LINUXKM_HAVE_MY_KALLSYMS_LOOKUP_NAME) + static typeof(add_vmfork_randomness) *my_add_vmfork_randomness = NULL; +#endif + + get_random_bytes(fake_id, sizeof fake_id); /* any unique blob */ + +#if IS_MODULE(CONFIG_VMGENID) + add_vmfork_randomness(fake_id, sizeof fake_id); /* full wire */ +#elif defined(WC_LINUXKM_HAVE_MY_KALLSYMS_LOOKUP_NAME) + /* add_vmfork_randomness() is exported only if vmgenid is a module -- + * work around it. */ + if (my_add_vmfork_randomness == NULL) + my_add_vmfork_randomness = my_kallsyms_lookup_name("add_vmfork_randomness"); + if (my_add_vmfork_randomness == NULL) + return -ENOSYS; + my_add_vmfork_randomness(fake_id, sizeof fake_id); /* full wire */ +#else + return -ENOSYS; +#endif + +#ifdef WOLFSSL_LINUXKM_VERBOSE_DEBUG + pr_info("wc_linuxkm_rng_state_invalidate_handler: called add_vmfork_randomness.\n"); +#endif + return (ssize_t)count; + } +#endif /* CONFIG_VMGENID */ +#if IS_ENABLED(CONFIG_PM_SLEEP) + if (mode == 3) { + /* synthetic PM_POST_HIBERNATION delivered directly to our own pm + * callback: exercises the wake-from-hibernation leg from the + * notifier boundary inward. (Injecting into the kernel's pm chain + * itself would deliver a fake hibernation event to every + * registered subsystem -- not a test, an incident.) */ + ret = wc_linuxkm_rng_pm_notify(&wc_linuxkm_rng_pm_nb, + PM_POST_HIBERNATION, NULL); +#ifdef WOLFSSL_LINUXKM_VERBOSE_DEBUG + pr_info("wc_linuxkm_rng_state_invalidate_handler: called " + "wc_linuxkm_rng_pm_notify(PM_POST_HIBERNATION), retval %d.\n", ret); +#endif + return (ret == NOTIFY_OK) ? (ssize_t)count : -EIO; + } +#endif /* CONFIG_PM_SLEEP */ + + return -EINVAL; +} + +static struct module_attribute wc_linuxkm_rng_state_invalidate_attr = + __ATTR(rng_state_invalidate, 0220, NULL, wc_linuxkm_rng_state_invalidate_handler); + +#define WC_LINUXKM_HAVE_RNG_STATE_INVALIDATE_HANDLER + +#endif /* !HAVE_FIPS || FIPS_VERSION3_GE(7,0,0) */ + +#ifndef WC_LINUXKM_NO_ENTROPY_DAEMON + +#ifdef WC_LINUXKM_HAVE_RNG_REGISTRY + +#if defined(WC_LINUXKM_VMGENID_POLL) || \ + (defined(CONFIG_ACPI) && !IS_ENABLED(CONFIG_VMGENID)) +/* Without CONFIG_VMGENID, we can only detect VM fork events by polling. + * Mainline gained vmgenid and the random_vmfork notifier chain together in + * kernel 5.18, so on older kernels and kernels with CONFIG_VMGENID configured + * off, there is no event to subscribe to -- but the ACPI VM Generation ID + * device (Microsoft spec; exposed by QEMU, Hyper-V, VMware) is still present, + * and its 16-byte counter changes exactly when the hypervisor + * forks/clones/restores the VM. The daemon polls it each iteration (a 16-byte + * compare of a memremap'd page -- effectively free) and, on change, invalidates + * all module RNG state and recovers its own root immediately, folding the new + * generation id into the credited recovery reseed as nonce. Detection latency + * is bounded by the daemon nap. + * + * All state is per-daemon, on the daemon's stack: wc_linuxkm_entropy_daemon() + * is threadsafe, and concurrent daemons discover, map, and poll + * independently. Redundant detections by multiple daemons are benign: + * wc_linuxkm_rng_state_invalidate() is idempotent, and the sweep generation + * counter dedups the recovery work. + */ + +#ifndef WC_LINUXKM_VMGENID_POLL + #define WC_LINUXKM_VMGENID_POLL +#endif + +struct wc_linuxkm_vmgenid_poll_state { + void *map; + int state; /* 0 untried, 1 mapped, -1 absent */ + u8 last[16]; +}; + +static acpi_status wc_linuxkm_vmgenid_acpi_cb(acpi_handle handle, u32 depth, + void *context, void **ret) +{ + struct wc_linuxkm_vmgenid_poll_state *st = + (struct wc_linuxkm_vmgenid_poll_state *)context; + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + u64 gpa; + + (void)depth; + + if (ACPI_FAILURE(acpi_evaluate_object(handle, (acpi_string)"ADDR", NULL, &buf))) + return AE_OK; /* not it -- keep walking */ + obj = (union acpi_object *)buf.pointer; + if ((obj != NULL) && (obj->type == ACPI_TYPE_PACKAGE) && + (obj->package.count == 2) && + (obj->package.elements[0].type == ACPI_TYPE_INTEGER) && + (obj->package.elements[1].type == ACPI_TYPE_INTEGER)) + { + gpa = (obj->package.elements[0].integer.value & 0xffffffffULL) | + (obj->package.elements[1].integer.value << 32); + if (gpa != 0) { + st->map = memremap(gpa, 16, MEMREMAP_WB); + if (st->map != NULL) { + kfree(buf.pointer); + *ret = st->map; + return AE_CTRL_TERMINATE; + } + } + } + kfree(buf.pointer); + return AE_OK; +} + +static void wc_linuxkm_vmgenid_poll(struct wc_linuxkm_vmgenid_poll_state *st, + WC_RNG *local_root) +{ + if (st->state == 0) { + /* one-time discovery, in daemon task context. ACPICA uppercases + * _HID/_CID strings when building the namespace, and + * acpi_get_devices() matches by strcmp, so the IDs here mirror + * the kernel vmgenid driver's own table verbatim: "VMGENCTR" + * (Microsoft spec _HID) and "VM_GEN_COUNTER" (the _CID as + * stored -- QEMU, Hyper-V, VMware all present it). */ + void *found = NULL; + (void)acpi_get_devices("VMGENCTR", wc_linuxkm_vmgenid_acpi_cb, + st, &found); + if (found == NULL) + (void)acpi_get_devices("VM_GEN_COUNTER", + wc_linuxkm_vmgenid_acpi_cb, + st, &found); + if (found != NULL) { + memcpy(st->last, st->map, 16); + st->state = 1; + pr_info("libwolfssl: vmgenid ACPI poller active (VM-fork " + "RNG invalidation coverage).\n"); + } + else { + st->state = -1; /* bare metal or no device */ + } + return; + } + if (st->state != 1) + return; + + if (memcmp(st->map, st->last, 16) != 0) { + memcpy(st->last, st->map, 16); + pr_notice("libwolfssl: VM generation change detected by poller.\n"); + (void)wc_linuxkm_rng_state_invalidate(); + /* recover our root immediately, folding the new generation id in + * as the credited reseed's nonce; the loop-head recovery check + * then finds the flag already clear. (Invalidate-then-reseed + * ordering keeps the recovery-entry scrub ahead of the fold.) */ + if (local_root != NULL) + (void)wc_RNG_DRBG_Reseed_Now(local_root, (const byte *)st->last, + 16); + } +} + +static void wc_linuxkm_vmgenid_poll_teardown( + struct wc_linuxkm_vmgenid_poll_state *st) +{ + if (st->map != NULL) { + memunmap(st->map); + st->map = NULL; + } + st->state = 0; +} +#endif /* CONFIG_ACPI && !CONFIG_VMGENID */ + +#endif /* WC_LINUXKM_HAVE_RNG_REGISTRY */ + +/* Entropy-banking daemon for the default rng bank: cycles the bank's + * instances, keeping each DRBG's nextSeed aperture full so that + * WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED checkouts can perform credited + * reseeds as pure computation, patrolling for out-of-service + * instances (taking their lease and reinitializing them, per + * wc_rng_bank_recover_inst()), and topping off each instance's output + * pool (wc_RNG_Pool_Collect2()) from a daemon-local source DRBG -- + * serviced by the module's normal inline reseed machinery, in task + * context -- so that lease-holding extractors in atomic contexts find + * pre-generated output. The daemon is a control plane only for seed + * material -- entropy moves from the module's seed source to the + * in-boundary aperture without ever crossing into daemon-visible + * storage -- and the pool top-off path likewise never holds a lease on + * the destination: publication is by CAS against the pool aperture, + * with lost races abandoned in place per the pool protocol. + * + * Policy: the daemon sleeps only when it runs out of work, or makes no + * progress on any instance -- it must keep pace with a consumer + * draining nextSeeds as fast as it can. Per-turn classification of + * wc_rng_bank_next_seed_generate() returns: + * 0 gathered/published -- progress; + * NOT_READY_E transient (incl. a burned bank, which is refill-eligible + * now, and an environmental TestSeed miss with the aperture + * preserved) -- progress, so a forced burn can never induce + * a nap; + * ALREADY_E ready or consuming -- no work on this instance; + * BUSY_E instance-op gate held by a reinit -- no progress here, + * but the gate holder is making it; + * MISSING_RNG_E no DRBG behind the instance -- nothing to bank; + * ENTROPY_RT_E / ENTROPY_APT_E and anything else: entropy source + * suspect or code defect; shout (unconditionally -- the + * library layer deliberately doesn't), and treat as + * no-progress so retry is nap-paced, not spin-paced. + * + * Lifecycle: spawned when the default bank is installed, reaped + * (kthread_stop(), which joins) in wc_linuxkm_drbg_exit_tfm() before + * wc_rng_bank_default_clear()/wc_rng_bank_fini() -- the join is what + * makes the daemon's use of the bank safe without a separate refcount + * hold. + */ + +#ifndef WC_LINUXKM_ENTROPY_DAEMON_NAP_MS + #define WC_LINUXKM_ENTROPY_DAEMON_NAP_MS 100 +#endif +#ifndef WC_LINUXKM_ENTROPY_DAEMON_GRANULE + /* wc_Entropy_Get() gathers and hashes in 32 byte blocks -- smaller + * requests cost the same. */ + #define WC_LINUXKM_ENTROPY_DAEMON_GRANULE 32 +#endif +#if !defined(WC_LINUXKM_BONUS_RESEED_INTERVAL) + /* Opportunistic supplementary reseed interval, for daemon seeding and + * wc_RNG_DRBG_NextSeedNow(). Pass rate is load-variable (e.g. nap-paced + * when idle, cond_resched()-paced when busy), so a busy RNG reseeds more + * often -- the conservative direction. wolfcrypt's internal + * WC_RESEED_INTERVAL auto-reseed remains the backstop if this schedule is + * somehow starved. */ + #define WC_LINUXKM_BONUS_RESEED_INTERVAL 1000 + #if WC_LINUXKM_BONUS_RESEED_INTERVAL >= WC_RESEED_INTERVAL / 2 + #undef WC_LINUXKM_BONUS_RESEED_INTERVAL + #define WC_LINUXKM_BONUS_RESEED_INTERVAL (WC_RESEED_INTERVAL / 2) + #endif +#endif + +wc_static_assert(WC_LINUXKM_BONUS_RESEED_INTERVAL >= 0 && + WC_LINUXKM_BONUS_RESEED_INTERVAL < WC_RESEED_INTERVAL / 2); + +#if defined(WC_RNG_HAVE_POOL) && !defined(WC_LINUXKM_RNG_POOL_SIZE) + /* per-instance output pool ring size (2..65535). 256 = 8 native + * get_random_u32-batch-sized draws between top-offs. */ + #define WC_LINUXKM_RNG_POOL_SIZE 256 +#endif + +static int wc_linuxkm_entropy_daemon(void *arg) +{ + struct wc_rng_bank *bank = (struct wc_rng_bank *)arg; + int i; + int ret; +#ifdef WC_LINUXKM_VMGENID_POLL + struct wc_linuxkm_vmgenid_poll_state vmgenid_poll_state = {}; +#endif + + if (WOLFSSL_ATOMIC_LOAD(bank->daemon_magic) != WC_LINUXKM_ENTROPY_DAEMON_MAGIC) + return -EINVAL; + + struct WC_RNG *root_rng = wc_rng_bank_root_rng_get(bank); + int root_rng_reseed_countdown = 0; + +#ifdef WC_RNG_HAVE_POOL + if (root_rng != NULL) { + /* One-time pool allocation for every instance, before any + * extractor can hold a lease against a nonempty ring. A + * failure leaves that instance poolless: extract-side callers + * fall through to direct generates, and Collect2() skips it + * (BAD_STATE_E) each turn. */ + for (i = 0; i < bank->n_rngs; i++) { + ret = wc_RNG_Pool_Alloc(WC_RNG_BANK_INST_TO_RNG(&bank->rngs[i]), + WC_LINUXKM_RNG_POOL_SIZE); + if ((ret != 0) && (ret != WC_NO_ERR_TRACE(ALREADY_E))) { + /* ALREADY_E: already allocated (daemon restart) */ + pr_err("wc_entropyd: pool alloc for DRBG inst %d " + "failed: %d\n", i, ret); + } + } + } +#endif /* WC_RNG_HAVE_POOL */ + + while (! kthread_should_stop()) { + int progress = 0; + int congested_progress = 0; + +#ifdef WC_LINUXKM_VMGENID_POLL + wc_linuxkm_vmgenid_poll(&vmgenid_poll_state, root_rng); +#endif + +#ifdef WC_RNG_HAVE_LOCK + /* deterministic root_rng recovery after a state-invalidation + * event: the saturated reseedCtr from wc_RNG_invalidate_entropy() + * also forces this, but that write races our own generates (the + * root is unleased by design), so the flag is the authoritative + * signal and this the authoritative response. */ + if (root_rng != NULL) { + WC_RNG_lock_arg_t root_lock_state; + if ((wc_RNG_lock_read(root_rng, &root_lock_state) == 0) && + (root_lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + { + unsigned long uncredited_nonce = random_get_entropy(); + int inv_ret; + +#ifdef WC_VERBOSE_RNG + pr_info("wc_linuxkm_entropy_daemon: starting root recovery reseed.\n"); +#endif + inv_ret = wc_RNG_DRBG_Reseed_Now( + root_rng, + (byte *)&uncredited_nonce, (word32)sizeof uncredited_nonce); + ForceZero(&uncredited_nonce, (word32)sizeof uncredited_nonce); + if (inv_ret == 0) { +#ifdef WC_VERBOSE_RNG + pr_info("wc_linuxkm_entropy_daemon: finished root recovery reseed.\n"); +#endif + progress = 1; + } + else + pr_err_ratelimited("wc_entropyd: post-invalidation " + "root_rng reseed failed: %d\n", inv_ret); + } + } +#endif + +#if defined(WC_RNG_HAVE_NEXT_SEED) && defined(WC_RNG_HAVE_RBGC) + /* recovery pass: push RBGC seeds to all WC_RNG_LOCK_ENTROPY_INVALIDATED + * instances that need them. */ + if (root_rng != NULL) { +#ifdef WC_VERBOSE_RNG + int n_rbgc_recovered = 0; +#endif + for (i = 0; i < bank->n_rngs; i++) { + WC_RNG *rng = WC_RNG_BANK_INST_TO_RNG(&bank->rngs[i]); + WC_RNG_lock_arg_t rng_lock_state; + WC_ATOMIC_INT_ARG nextSeedLen; + + if (wc_RNG_GetStatus(rng) != WC_DRBG_OK) + continue; + if (wc_RNG_lock_read(rng, &rng_lock_state) != 0) + continue; + if (! (rng_lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + continue; + if (wc_RNG_DRBG_NextSeedCurrent(rng, &nextSeedLen) != 0) + continue; + if ((nextSeedLen == WC_DRBG_NEXT_SEED_READY) || + (nextSeedLen == WC_DRBG_NEXT_SEED_CONSUMING)) + { + continue; + } + if (wc_rng_bank_next_seed_generate_rbgc(bank, i, WC_DRBG_NEXT_SEED_LEN) == 0) { +#ifdef WC_VERBOSE_RNG + ++n_rbgc_recovered; +#endif + congested_progress = 1; + progress = 1; + } + } +#ifdef WC_VERBOSE_RNG + if (n_rbgc_recovered > 0) { + pr_info("wc_linuxkm_entropy_daemon: RBGC recovery of %d/%d insts.\n", + n_rbgc_recovered, bank->n_rngs); + } +#endif + } +#endif /* WC_RNG_HAVE_NEXT_SEED && WC_RNG_HAVE_RBGC */ + + /* recovery pass: fix out-of-service instances. The status + * peek is lockless and possibly stale -- worst case it sends a + * recover_inst() at a healthy instance (no-op) or misses one + * cycle; the instance-op gate arbitrates any race with an + * inline recovery (BUSY_E). */ + for (i = 0; i < bank->n_rngs; i++) { + if (wc_RNG_GetStatus(WC_RNG_BANK_INST_TO_RNG(&bank->rngs[i])) + == WC_DRBG_OK) + { + continue; + } + ret = wc_rng_bank_recover_inst(bank, i, 0 /* timeout_secs */, + WC_LKM_BANK_RBGC_FLAG | + WC_RNG_BANK_FLAG_AUTO_RECOVER_AND_PROMOTE); + if (ret == 0) { + (void)wc_rng_bank_inst_flags_down( + &bank->rngs[i], WC_RNG_BANK_INST_FLAG_ALREADY_WARNED); + progress = 1; + } + else if (ret != WC_NO_ERR_TRACE(BUSY_E)) { + if (wc_rng_bank_inst_flags_up( + &bank->rngs[i], WC_RNG_BANK_INST_FLAG_ALREADY_WARNED)) + { + pr_err_ratelimited( + "ERROR: wc_entropyd: recovery of DRBG inst %d failed: %d\n", + i, ret); + } + } + } + +#ifdef WC_RNG_HAVE_NEXT_SEED + if (root_rng != NULL) { + /* congestion-triggered RBGC seed pass. */ + for (i = 0; i < bank->n_rngs; i++) { + wc_drbg_reseed_ctr_t this_reseedCtr; + ret = wc_RNG_DRBG_GetReseedCtr(WC_RNG_BANK_INST_TO_RNG(&bank->rngs[i]), + &this_reseedCtr); + if ((ret == 0) && (this_reseedCtr > WC_RESEED_INTERVAL / 2)) { + WC_ATOMIC_INT_ARG this_NextSeedCurrent; + ret = wc_RNG_DRBG_NextSeedCurrent( + WC_RNG_BANK_INST_TO_RNG(&bank->rngs[i]), &this_NextSeedCurrent); + if ((ret == 0) && + (this_NextSeedCurrent != WC_DRBG_NEXT_SEED_READY) && + (this_NextSeedCurrent != WC_DRBG_NEXT_SEED_CONSUMING)) + { + ret = wc_rng_bank_next_seed_generate_rbgc( + bank, i, WC_DRBG_NEXT_SEED_LEN); + congested_progress = 1; + if (ret == 0) + progress = 1; + } + } + } + } +#endif /* WC_RNG_HAVE_NEXT_SEED */ + + /* Periodic explicit reseed of the root_rng, with a fresh cycle-counter + * nonce -- scheduled fresh entropy in task context, rather than waiting + * for the counter-forced internal reseed. */ + if ((root_rng != NULL) && (--root_rng_reseed_countdown < 0) && (! congested_progress)) { +#if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) + ret = wc_RNG_DRBG_Reseed_Now(root_rng, NULL, 0); +#else + unsigned long uncredited_nonce = random_get_entropy(); + ret = wc_RNG_DRBG_Reseed_Now(root_rng, + (byte *)&uncredited_nonce, + sizeof uncredited_nonce); + ForceZero(&uncredited_nonce, sizeof uncredited_nonce); +#endif + if (ret == 0) { + root_rng_reseed_countdown = + WC_LINUXKM_BONUS_RESEED_INTERVAL; + } + else { + pr_err_ratelimited( + "wc_entropyd: pool source reseed failed: %d\n", ret); + } + } + +#ifdef WC_RNG_HAVE_POOL + /* pooling pass -- run this pass even if there was high-load seed + * generation, as it is good defense against high load scenarios. + */ + if (root_rng != NULL) { + for (i = 0; i < bank->n_rngs; i++) { + /* pool top-off: fill whatever free span the ring reports. + * The fullness peek is a lockless aperture load; Collect2() + * re-clamps against a fresh snapshot and publishes by CAS, + * so staleness costs at most a wasted attempt. Progress + * accounting keys on the peek, not the call: a full ring is + * not work, and NOT_READY_E means a racing consumer is making + * the progress. */ + word32 pool_n = 0; + WC_RNG *inst_rng = WC_RNG_BANK_INST_TO_RNG(&bank->rngs[i]); + + if ((wc_RNG_Pool_Current(inst_rng, &pool_n) == 0) && + (inst_rng->pool != NULL) && + (pool_n < (word32)inst_rng->poolSize)) + { + unsigned long uncredited_nonce = random_get_entropy(); + (void)wc_RNG_DRBG_Stir(root_rng, + (byte *)&uncredited_nonce, + (word32)sizeof uncredited_nonce); + ForceZero(&uncredited_nonce, (word32)sizeof uncredited_nonce); + ret = wc_RNG_Pool_Collect2(inst_rng, root_rng, + (word32)inst_rng->poolSize + - pool_n); + if (ret == 0) { + progress = 1; + } +#ifdef WC_VERBOSE_RNG + else if ((ret == WC_NO_ERR_TRACE(NOT_READY_E)) || + (ret == WC_NO_ERR_TRACE(BAD_STATE_E)) || + (ret == WC_NO_ERR_TRACE(BUSY_E))) + { + /* contention (other writer active), a purge landed + * while the collector was generating, or no pool -- + * nothing to do here this turn. */ + } + else { + pr_err_ratelimited( + "wc_entropyd: pool top-off on DRBG inst %d " + "returned %d\n", i, ret); + } +#endif + } + } + } +#endif /* WC_RNG_HAVE_POOL */ + +#if defined(WC_RNG_HAVE_NEXT_SEED) && defined(WC_RNG_HAVE_RBGC) + /* registered-leaf pass: bank RBGC seeds from root_rng into + * long-lived leaves that are invalidated or chain-backed, so their + * next generate recovers/promotes in place + * (WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED). + * Sleepable-mutex context; entropy gathers are legal under it by + * the atomic-born exclusion rule. */ + if (root_rng != NULL) { + WC_ATOMIC_INT_ARG needs_recovery_snapshot = + WOLFSSL_ATOMIC_LOAD(wc_linuxkm_rng_registry_needs_recovery); + if (needs_recovery_snapshot != 0) { + struct linuxkm_rng_object *obj; + mutex_lock(&wc_linuxkm_rng_registry_mutex); + for (obj = wc_linuxkm_rng_registry_head; obj != NULL; + obj = obj->next) + { + WC_RNG_lock_arg_t leaf_lock_state; + if (obj->is_bank) + continue; + if (wc_RNG_lock_read(obj->rng, &leaf_lock_state) != 0) + continue; + if ((leaf_lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED) || + (wc_RNG_DRBG_GetRBGCStratum(obj->rng) > 0)) + { + if (wc_RNG_DRBG_NextSeedGenerate_RBGC(obj->rng, + root_rng, WC_DRBG_NEXT_SEED_LEN) == 0) + progress = 1; + } + } + mutex_unlock(&wc_linuxkm_rng_registry_mutex); + /* on failure, an invalidation landed since the snapshot: + * leave the counter hot and re-sweep next pass. */ + (void)wolfSSL_Atomic_Int_CompareExchange( + &wc_linuxkm_rng_registry_needs_recovery, + &needs_recovery_snapshot, 0); + } + } +#endif /* WC_RNG_HAVE_NEXT_SEED && WC_RNG_HAVE_RBGC */ + + /* if we're coping with congestion hits, continue here, don't bog down + * in primary seed ops. */ + if (congested_progress) + goto next_pass; + +#ifdef WC_RNG_HAVE_NEXT_SEED + /* seed banking pass: one gather granule per instance per turn. */ + for (i = 0; i < bank->n_rngs; i++) { + + ret = wc_rng_bank_next_seed_generate( + bank, i, WC_LINUXKM_ENTROPY_DAEMON_GRANULE); + if ((ret == 0) || (ret == WC_NO_ERR_TRACE(NOT_READY_E))) { + progress = 1; + } + else if ((ret == WC_NO_ERR_TRACE(ALREADY_E)) || + (ret == WC_NO_ERR_TRACE(BUSY_E)) || + (ret == WC_NO_ERR_TRACE(MISSING_RNG_E))) + { + /* nothing to do here this turn. */ + } + else if ((ret == WC_NO_ERR_TRACE(ENTROPY_RT_E)) || + (ret == WC_NO_ERR_TRACE(ENTROPY_APT_E))) + { +#ifdef WC_VERBOSE_RNG + pr_err_ratelimited( + "WARNING: wc_entropyd: seed health test failed on DRBG inst " + "%d: %d -- entropy source suspect\n", i, ret); +#endif + } + else { + pr_err_ratelimited( + "ERROR: wc_entropyd: next_seed_generate on DRBG inst %d " + "returned %d\n", i, ret); + } + } +#endif /* WC_RNG_HAVE_NEXT_SEED */ + + next_pass: + + if (progress) { + cond_resched(); + } + else { + if (! kthread_should_stop()) + schedule_timeout_interruptible(msecs_to_jiffies(WC_LINUXKM_ENTROPY_DAEMON_NAP_MS)); + } + } + + if (root_rng != NULL) { +#ifdef WC_RNG_DEBUG_STATS + struct wc_rng_debug_stats_snapshot s; + if (wc_rng_debug_stats_snap(&s, root_rng) == 0) { + pr_info("RNG INFO: wc_entropyd root total_bytes_requested=" WC_RNG_STAT_FMT "\n" + " total_bytes_produced=" WC_RNG_STAT_FMT + " total_requests=" WC_RNG_STAT_FMT "\n" + " reseeds=" WC_RNG_STAT_FMT + " stirs=" WC_RNG_STAT_FMT + " seed_failures=" WC_RNG_STAT_FMT "\n" +#ifdef WC_RNG_HAVE_NEXT_SEED + " nextstirs_banked=" WC_RNG_STAT_FMT + " nextstirs_redeemed=" WC_RNG_STAT_FMT "\n" +#endif + , + s._stats_total_bytes_requested, + s._stats_total_bytes_produced, + s._stats_total_requests, + s._stats_reseeds, + s._stats_stirs, + s._stats_seed_failures +#ifdef WC_RNG_HAVE_NEXT_SEED + , + s._stats_nextstirs_banked, + s._stats_nextstirs_redeemed +#endif + ); + } +#endif /* WC_RNG_DEBUG_STATS */ +#ifdef WC_LINUXKM_VMGENID_POLL + wc_linuxkm_vmgenid_poll_teardown(&vmgenid_poll_state); +#endif + } + + return 0; +} + +#endif /* !WC_LINUXKM_NO_ENTROPY_DAEMON */ + static int wc_linuxkm_rng_bank_init(struct wc_rng_bank *ctx) { int ret; - word32 flags = WC_RNG_BANK_FLAG_CAN_WAIT; + word32 flags = WC_RNG_BANK_FLAG_CAN_WAIT | WC_RNG_BANK_FLAG_AUTO_RECOVER_AND_PROMOTE | + WC_RNG_BANK_FLAG_NO_CHECKOUT_REFCOUNTING | WC_LKM_BANK_RBGC_FLAG; + unsigned long uncredited_nonce = random_get_entropy(); + + if (wc_linuxkm_rng_initing_default_bank_flag && (default_bank != NULL)) { + pr_err("BUG: wc_linuxkm_rng_bank_init() called with " + "wc_linuxkm_rng_initing_default_bank_flag asserted and default_bank != NULL.\n"); + return -EINVAL; + } #if defined(WOLFSSL_USE_SAVE_VECTOR_REGISTERS) && \ defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) @@ -2143,11 +3131,31 @@ static int wc_linuxkm_rng_bank_init(struct wc_rng_bank *ctx) } #endif - ret = wc_rng_bank_init( - ctx, nr_cpu_ids + 4, flags, WC_LINUXKM_INITRNG_TIMEOUT_SEC, - NULL /* heap */, INVALID_DEVID); + /* The bank is embedded in the tfm context: its lifetime encloses all + * checkouts by kernel crypto API teardown ordering, so per-checkout + * refcounting buys nothing here and is the one bank-global RMW pair + * on the readout hot path. */ + ret = wc_rng_bank_init_nonce( + ctx, LINUXKM_RNG_BANK_SIZE, + flags, + WC_LINUXKM_INITRNG_TIMEOUT_SEC, + NULL /* heap */, INVALID_DEVID, + (byte *)&uncredited_nonce, (word32)sizeof uncredited_nonce, NULL, 0); + + #ifdef WC_LINUXKM_HAVE_RNG_REGISTRY + if (ret == 0) { + ret = wc_linuxkm_rng_registry_add_bank(ctx); + if (ret != 0) { + (void)wc_rng_bank_fini(ctx); + pr_err("ERROR: wc_linuxkm_rng_registry_add_bank() in " + "wc_linuxkm_rng_bank_init() returned err %d\n", ret); + return ret; + } + } + #endif if (ret == 0) { + (void)wc_rng_bank_first_failover_inst_set(ctx, LINUXKM_RNG_BANK_FIRST_FAILOVER); ret = wc_rng_bank_set_affinity_handlers( ctx, linuxkm_affinity_lock, @@ -2159,19 +3167,62 @@ static int wc_linuxkm_rng_bank_init(struct wc_rng_bank *ctx) ret = wc_rng_bank_default_set(ctx); if (ret != 0) { (void)wc_rng_bank_fini(ctx); - pr_err("ERROR: wc_rng_bank_default_set() in wc_linuxkm_rng_bank_init() returned err %d\n", ret); + pr_err("ERROR: wc_rng_bank_default_set() in " + "wc_linuxkm_rng_bank_init() returned err %d\n", ret); WC_DUMP_BACKTRACE_NONDEBUG; } + else { + default_bank = ctx; +#ifndef WC_LINUXKM_NO_ENTROPY_DAEMON + /* Try to launch the entropy daemon. Failure is nonfatal: + * the inline reseed and recovery paths serve daemonless + * operation. */ + ret = wc_rng_bank_daemon_reserve( + ctx, WC_LINUXKM_ENTROPY_DAEMON_MAGIC); + if (ret != 0) { + pr_err("ERROR: wc_rng_bank_daemon_reserve() in " + "wc_linuxkm_rng_bank_init() returned err %d\n", + ret); + ret = 0; + } + else { + struct task_struct *t = kthread_run( + wc_linuxkm_entropy_daemon, ctx, "wc_entropyd"); + if (IS_ERR(t)) { + (void)wc_rng_bank_daemon_release( + ctx, WC_LINUXKM_ENTROPY_DAEMON_MAGIC); + pr_err("WARNING: wc_entropyd spawn failed: %d " + "(falling back to synchronous entropy strategy)\n", + (int)PTR_ERR(t)); + } + else { + ret = wc_rng_bank_daemon_register( + ctx, t, WC_LINUXKM_ENTROPY_DAEMON_MAGIC); + if (ret != 0) { + pr_err("ERROR: wc_rng_bank_daemon_register() " + "in wc_linuxkm_rng_bank_init() returned err %d\n", + ret); + (void)kthread_stop(t); + (void)wc_rng_bank_daemon_release( + ctx, WC_LINUXKM_ENTROPY_DAEMON_MAGIC); + ret = 0; + } + } + } +#endif /* !WC_LINUXKM_NO_ENTROPY_DAEMON */ + } } } else { (void)wc_rng_bank_fini(ctx); - pr_err("ERROR: wc_rng_bank_set_affinity_handlers() in wc_linuxkm_rng_bank_init() returned err %d\n", ret); + pr_err("ERROR: wc_rng_bank_set_affinity_handlers() in " + "wc_linuxkm_rng_bank_init() returned err %d\n", ret); WC_DUMP_BACKTRACE_NONDEBUG; } } else { - pr_err("ERROR: wc_rng_bank_init() in wc_linuxkm_rng_bank_init() returned err %d\n", ret); + pr_err("ERROR: wc_rng_bank_init() in wc_linuxkm_rng_bank_init() " + "returned err %d\n", ret); if (ret == WC_NO_ERR_TRACE(MEMORY_E)) ret = -ENOMEM; else if (ret == WC_NO_ERR_TRACE(WC_TIMEOUT_E)) @@ -2185,26 +3236,160 @@ static int wc_linuxkm_rng_bank_init(struct wc_rng_bank *ctx) return ret; } -static int wc_linuxkm_drbg_init_tfm(struct crypto_tfm *tfm) +#ifdef WC_RNG_DEBUG_STATS +/* Dump the default bank's aggregate stats, and (when reachable via the + * daemon-root slot) the entropy daemon's root stats, to the kernel log. + * Snapshots are racy by design (debug stats doctrine); callable any time + * from task context. */ +static void wc_linuxkm_rng_dump_stats(struct wc_rng_bank *ctx) { - return wc_linuxkm_rng_bank_init((struct wc_rng_bank *)crypto_tfm_ctx(tfm)); + struct wc_rng_debug_stats_snapshot s; + + { + WC_RNG *root_rng = wc_rng_bank_root_rng_get(ctx); + if ((root_rng != NULL) && + (wc_rng_debug_stats_snap(&s, root_rng) == 0)) + { + pr_info("RNG INFO: wc_entropyd root total_bytes_requested=" WC_RNG_STAT_FMT "\n" + " total_bytes_produced=" WC_RNG_STAT_FMT + " total_requests=" WC_RNG_STAT_FMT "\n" + " reseeds=" WC_RNG_STAT_FMT + " stirs=" WC_RNG_STAT_FMT + " seed_failures=" WC_RNG_STAT_FMT "\n" +#ifdef WC_RNG_HAVE_NEXT_SEED + " stirs_banked=" WC_RNG_STAT_FMT + " stirs_redeemed=" WC_RNG_STAT_FMT "\n" +#endif + , + s._stats_total_bytes_requested, + s._stats_total_bytes_produced, + s._stats_total_requests, + s._stats_reseeds, + s._stats_stirs, + s._stats_seed_failures +#ifdef WC_RNG_HAVE_NEXT_SEED + , + s._stats_nextstirs_banked, + s._stats_nextstirs_redeemed +#endif + ); + } + } + + if (wc_rng_bank_debug_stats_snap(&s, ctx) == 0) { + pr_info("RNG INFO: default bank size=%d total_bytes_requested=" WC_RNG_STAT_FMT "\n" + " total_bytes_produced=" WC_RNG_STAT_FMT + " total_requests=" WC_RNG_STAT_FMT "\n" + " reseeds=" WC_RNG_STAT_FMT + " stirs=" WC_RNG_STAT_FMT + " seed_failures=" WC_RNG_STAT_FMT "\n" + " locks_taken=" WC_RNG_STAT_FMT + " locks_released=" WC_RNG_STAT_FMT + " locks_refused=" WC_RNG_STAT_FMT "\n" +#ifdef WC_RNG_HAVE_RBGC + " RBGC_bytes_produced=" WC_RNG_STAT_FMT + " RBGC_reseeds=" WC_RNG_STAT_FMT "\n" +#endif +#ifdef WC_RNG_HAVE_POOL + " pool_bytes_produced=" WC_RNG_STAT_FMT + " pool_bytes_missed=" WC_RNG_STAT_FMT "\n" +#endif +#ifdef WC_RNG_HAVE_NEXT_SEED + " nextseedsprimary_redeemed=" WC_RNG_STAT_FMT + " nextseedsRBGC_redeemed=" WC_RNG_STAT_FMT "\n" + " nextseedsbanked=" WC_RNG_STAT_FMT + " nextstirs_banked=" WC_RNG_STAT_FMT + " nextstirs_redeemed=" WC_RNG_STAT_FMT "\n" +#endif + , + ctx->n_rngs, + s._stats_total_bytes_requested, + s._stats_total_bytes_produced, + s._stats_total_requests, + s._stats_reseeds, + s._stats_stirs, + s._stats_seed_failures, + s._stats_locks_taken, + s._stats_locks_released, + s._stats_locks_refused +#ifdef WC_RNG_HAVE_RBGC + ,s._stats_RBGC_bytes_produced + ,s._stats_RBGC_reseeds +#endif +#ifdef WC_RNG_HAVE_POOL + ,s._stats_pool_bytes_produced + ,s._stats_pool_bytes_missed +#endif +#ifdef WC_RNG_HAVE_NEXT_SEED + ,s._stats_nextseedsprimary_redeemed + ,s._stats_nextseedsRBGC_redeemed + ,s._stats_nextseedsbanked + ,s._stats_nextstirs_banked + ,s._stats_nextstirs_redeemed +#endif + ); + } } +#endif /* WC_RNG_DEBUG_STATS */ -static void wc_linuxkm_drbg_exit_tfm(struct crypto_tfm *tfm) -{ - struct wc_rng_bank *ctx = (struct wc_rng_bank *)crypto_tfm_ctx(tfm); +static int wc_linuxkm_rng_bank_fini(struct wc_rng_bank *ctx) { int ret; - ret = wc_rng_bank_default_clear(ctx); - if (ret && (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG))) - pr_err("ERROR: wc_rng_bank_default_clear() in wc_linuxkm_drbg_exit_tfm() returned unexpected code %d\n", ret); +#ifndef WC_LINUXKM_NO_ENTROPY_DAEMON + if (WOLFSSL_ATOMIC_LOAD(ctx->daemon_magic) == WC_LINUXKM_ENTROPY_DAEMON_MAGIC) { + struct task_struct *t; + ret = wc_rng_bank_daemon_unregister( + ctx, (void **)&t, WC_LINUXKM_ENTROPY_DAEMON_MAGIC); + if ((ret == 0) || (ret == WC_NO_ERR_TRACE(ALREADY_E))) { + if (ret == 0) + (void)kthread_stop(t); + ret = wc_rng_bank_daemon_release( + ctx, WC_LINUXKM_ENTROPY_DAEMON_MAGIC); + if (ret != 0) + pr_err("ERROR: wc_rng_bank_daemon_release() in " + "wc_linuxkm_rng_bank_fini() returned code %d\n", ret); + } + else + pr_err("ERROR: wc_rng_bank_daemon_unregister() in " + "wc_linuxkm_rng_bank_fini() returned code %d\n", ret); + } +#endif /* !WC_LINUXKM_NO_ENTROPY_DAEMON */ + + if (ctx->flags & WC_RNG_BANK_FLAG_DEFAULT_BANK) { + /* clear the _inited flag unconditionally -- if either + * wc_rng_bank_default_clear() or wc_rng_bank_fini() fails, then the ctx + * is in an indeterminate state and should not be accessed. */ + default_bank = NULL; + + ret = wc_rng_bank_default_clear(ctx); + if (ret != 0) + pr_err("ERROR: wc_rng_bank_default_clear() in " + "wc_linuxkm_rng_bank_fini() returned code %d\n", ret); + +#ifdef WC_RNG_DEBUG_STATS + wc_linuxkm_rng_dump_stats(ctx); +#endif + } ret = wc_rng_bank_fini(ctx); if (ret != 0) - pr_err("ERROR: wc_rng_bank_fini() in wc_linuxkm_drbg_exit_tfm() returned err %d\n", ret); + pr_err("ERROR: wc_rng_bank_fini() in wc_linuxkm_rng_bank_fini() " + "returned err %d\n", ret); + + return ret; +} - return; +static int wc_linuxkm_drbg_init_tfm(struct crypto_tfm *tfm) +{ + return wc_linuxkm_rng_bank_init((struct wc_rng_bank *)crypto_tfm_ctx(tfm)); +} + +static void wc_linuxkm_drbg_exit_tfm(struct crypto_tfm *tfm) +{ + struct wc_rng_bank *ctx = (struct wc_rng_bank *)crypto_tfm_ctx(tfm); + + (void)wc_linuxkm_rng_bank_fini(ctx); } static int wc_linuxkm_drbg_default_instance_registered = 0; @@ -2224,13 +3409,40 @@ static struct wc_rng_bank_inst *linuxkm_get_drbg(struct wc_rng_bank *ctx) { #endif if (wc_linuxkm_can_block()) flags |= WC_RNG_BANK_FLAG_AFFINITY_LOCK; +#if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) else flags |= WC_RNG_BANK_FLAG_NO_VECTOR_OPS; +#endif + + if (! wc_linuxkm_can_block()) { + /* atomic-context callers can't wait out a quarantine: accept + * admission to an invalidated instance and recover it inline + * (below) with a synchronous credited primary reseed. The + * entropy gather rides wc_LockMutex()'s atomic-context arm. */ + flags |= WC_RNG_BANK_FLAG_MAYBE_FOR_RECOVERY; + } err = wc_rng_bank_checkout(ctx, &ret, 0, WC_LINUXKM_INITRNG_TIMEOUT_SEC, flags); + if ((err == WC_NO_ERR_TRACE(NEEDS_RECOVERY_E)) && (ret != NULL)) { + /* leased-but-quarantined per WC_RNG_BANK_FLAG_MAYBE_FOR_RECOVERY: + * we own the recovery obligation. */ + unsigned long uncredited_nonce = random_get_entropy(); + err = wc_RNG_DRBG_Reseed_Now( + WC_RNG_BANK_INST_TO_RNG(ret), + (byte *)&uncredited_nonce, (word32)sizeof uncredited_nonce); + ForceZero(&uncredited_nonce, (word32)sizeof uncredited_nonce); + if (err == 0) + return ret; + pr_err_ratelimited("ERROR: inline recovery reseed in " + "linuxkm_get_drbg() returned err %d.\n", err); + (void)wc_rng_bank_inst_checkin(&ret); + return NULL; + } + if (err != 0) { - pr_err("ERROR: wc_rng_bank_checkout() in linuxkm_get_drbg() returned err %d.\n", err); + pr_err("ERROR: wc_rng_bank_checkout() in linuxkm_get_drbg() returned " + "err %d.\n", err); WC_DUMP_BACKTRACE_NONDEBUG; return NULL; } @@ -2238,10 +3450,17 @@ static struct wc_rng_bank_inst *linuxkm_get_drbg(struct wc_rng_bank *ctx) { return ret; } -static void linuxkm_put_drbg(struct wc_rng_bank *ctx, struct wc_rng_bank_inst **drbg) { - int ret = wc_rng_bank_checkin(ctx, drbg); - if (ret != 0) { - pr_err("ERROR: wc_rng_bank_checkin() in linuxkm_put_drbg() returned err %d.\n", ret); +static void linuxkm_put_drbg(struct wc_rng_bank_inst **drbg) { + int ret = wc_rng_bank_inst_checkin(drbg); + if (ret == WC_NO_ERR_TRACE(NEEDS_RECOVERY_E)) { + /* informational: checked in successfully; the instance is + * entropy-invalidated (e.g. a state-invalidation event landed + * mid-lease) and recovers via the checkout admissions or the + * patrol. */ + } + else if (ret != 0) { + pr_err("ERROR: wc_rng_bank_inst_checkin() in linuxkm_put_drbg() " + "returned err %d.\n", ret); WC_DUMP_BACKTRACE_NONDEBUG; } } @@ -2260,9 +3479,43 @@ int wc_linux_kernel_rng_is_wolfcrypt(struct crypto_rng *rng) { } } -#ifndef WC_HAVE_RNG_BANKREF - #error LINUXKM_LKCAPI_REGISTER_HASH_DRBG_DEFAULT requires WC_HAVE_RNG_BANKREF. +#ifdef WC_RNG_HAVE_RBGC + +WC_MAYBE_UNUSED static int linuxkm_InitRng_DefaultRBGC(WC_RNG* rng) { + unsigned long uncredited_nonce = random_get_entropy(); + int ret = wc_rng_bank_spawn(NULL /* bank */, rng, (byte *)&uncredited_nonce, + sizeof uncredited_nonce, + NULL, 0, + 0 /* preferred_inst_offset */, + 0 /* timeout_secs */, + WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST | + WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST | + WC_RNG_BANK_FLAG_AUTO_RECOVER_AND_PROMOTE); + ForceZero(&uncredited_nonce, (word32)sizeof uncredited_nonce); + if (ret != 0) { + pr_warn_ratelimited("WARNING: linuxkm_InitRng_DefaultRBGC() failed " + "with code %d; falling through to wc_InitRng().\n", + ret); + ret = wc_InitRng(rng); + } +#ifdef WC_LINUXKM_HAVE_RNG_REGISTRY + if (ret == 0) { + /* Long-lived process-context leaves join the invalidation registry; + * atomic-born leaves are excluded by rule (and are transient by + * nature). */ + if (wc_linuxkm_can_block()) { + ret = wc_linuxkm_rng_registry_add_rng(rng); + if (ret != 0) + (void)wc_FreeRng(rng); + } + } #endif + return ret; +} + +#define LKCAPI_INITRNG(rng) linuxkm_InitRng_DefaultRBGC(rng) + +#elif defined(WC_HAVE_RNG_BANKREF) WC_MAYBE_UNUSED static int linuxkm_InitRng_DefaultRef(WC_RNG* rng) { struct wc_rng_bank *ctx; @@ -2274,7 +3527,8 @@ WC_MAYBE_UNUSED static int linuxkm_InitRng_DefaultRef(WC_RNG* rng) { return ret; } else { - pr_warn_once("WARNING: linuxkm_InitRng_DefaultRef() called with null default_wc_rng_bank; falling through to wc_InitRng().\n"); + pr_warn_once("WARNING: linuxkm_InitRng_DefaultRef() called with null " + "default_wc_rng_bank; falling through to wc_InitRng().\n"); return wc_InitRng(rng); } @@ -2282,33 +3536,217 @@ WC_MAYBE_UNUSED static int linuxkm_InitRng_DefaultRef(WC_RNG* rng) { } #define LKCAPI_INITRNG(rng) linuxkm_InitRng_DefaultRef(rng) +#else /* !WC_RNG_HAVE_RBGC && !WC_HAVE_RNG_BANKREF */ + + #error LINUXKM_LKCAPI_REGISTER_HASH_DRBG_DEFAULT requires WC_RNG_HAVE_RBGC or WC_HAVE_RNG_BANKREF. + +#endif /* !WC_RNG_HAVE_RBGC && !WC_HAVE_RNG_BANKREF */ + #endif /* LINUXKM_LKCAPI_REGISTER_HASH_DRBG_DEFAULT && HAVE_HASHDRBG */ +#ifndef WC_LINUXKM_DRBG_SMALL_LIMIT + #define WC_LINUXKM_DRBG_SMALL_LIMIT 8 +#endif + +#ifdef WC_RNG_HAVE_POOL +wc_static_assert(WC_LINUXKM_DRBG_SMALL_LIMIT <= WC_LINUXKM_RNG_POOL_SIZE); +#endif + static int wc_linuxkm_drbg_generate(struct wc_rng_bank *ctx, const u8 *src, unsigned int slen, - u8 *dst, unsigned int dlen) + u8 *dst, unsigned int dlen, int pr) { int ret, retried = 0; - struct wc_rng_bank_inst *drbg = linuxkm_get_drbg(ctx); + /* can_block() is false whenever the affinity lock is held -- blockability + * must be sampled before checkout. */ + int can_wait = wc_linuxkm_can_block(); + wc_drbg_reseed_ctr_t cur_counter = 0; + struct wc_rng_bank_inst *drbg; + + if (pr && !can_wait) + return -EAGAIN; + + drbg = linuxkm_get_drbg(ctx); if (! drbg) { pr_err_once("BUG: linuxkm_get_drbg() failed.\n"); return -EFAULT; } +#ifdef WC_RNG_HAVE_POOL + if ((! pr) && (slen == 0) && (dlen <= WC_LINUXKM_DRBG_SMALL_LIMIT)) { + for (retried = 0; retried < 2; ++retried) { + word32 dlen_before_pool_extraction = dlen; + if (wc_RNG_Pool_Extract(WC_RNG_BANK_INST_TO_RNG(drbg), dst, &dlen) == 0) { + dst += dlen; + dlen = dlen_before_pool_extraction - dlen; + if (dlen == 0) { + ret = 0; + goto out; + } + } + else { + if (wc_RNG_Pool_Collect(WC_RNG_BANK_INST_TO_RNG(drbg), + can_wait ? WC_LINUXKM_RNG_POOL_SIZE : + WC_SHA256_BLOCK_SIZE) + != 0) + { + break; + } + } + } + } + retried = 0; +#endif + if (slen > 0) { - ret = wc_RNG_DRBG_Reseed(WC_RNG_BANK_INST_TO_RNG(drbg), src, slen); + /* The kernel crypto API's generate-op src is additional data (cf. + * crypto/drbg.c, which passes it as SP 800-90A additional input). + * Mix it in without entropy credit -- the reseed counter is + * unmodified, so only the module's own seed source resets the + * reseed schedule. */ + ret = wc_RNG_DRBG_Stir(WC_RNG_BANK_INST_TO_RNG(drbg), + src, slen); if (ret != 0) { - pr_warn_once("WARNING: wc_RNG_DRBG_Reseed returned %d\n",ret); + pr_warn_once("WARNING: wc_RNG_DRBG_Stir returned %d\n",ret); ret = -EINVAL; goto out; } } + if (pr || (wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(drbg), &cur_counter) == 0)) + { +#ifdef WC_RNG_HAVE_NEXT_SEED + WC_ATOMIC_INT_ARG NextSeedCurrent; + ret = wc_RNG_DRBG_NextSeedCurrent( + WC_RNG_BANK_INST_TO_RNG(drbg), &NextSeedCurrent); + if ((! pr) && + (ret == 0) && + (NextSeedCurrent == WC_DRBG_NEXT_SEED_READY) && + ((cur_counter >= WC_LINUXKM_BONUS_RESEED_INTERVAL) +#ifdef WC_RNG_HAVE_RBGC + || + ((wc_RNG_DRBG_GetRBGCStratum(WC_RNG_BANK_INST_TO_RNG(drbg)) > 0) && + (wc_RNG_DRBG_GetNextSeedRBGCStratum(WC_RNG_BANK_INST_TO_RNG(drbg)) == 0))) +#endif + ) + { + unsigned long uncredited_nonce = random_get_entropy(); + ret = wc_RNG_DRBG_NextSeedNow_Nonce(WC_RNG_BANK_INST_TO_RNG(drbg), + (byte *)&uncredited_nonce, + (word32)sizeof uncredited_nonce); + ForceZero(&uncredited_nonce, (word32)sizeof uncredited_nonce); + if (ret == 0) + { + /* Consumed a daemon-banked seed: full reseed, counter reset, no + * entropy gathering, safe in any context -- nothing more to do. + * On any nonzero return (typically nothing banked), fall through + * to the direct-reseed leg below. + */ + cur_counter = 0; + } + } +#endif + if (pr || (can_wait && (cur_counter > WC_RESEED_INTERVAL / 2))) { +#ifdef WOLFSSL_USE_SAVE_VECTOR_REGISTERS + /* carefully restore preemptibility for the reseed operation. */ + + #if defined(CONFIG_SMP) && (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)) + migrate_disable(); + #endif + + /* wc_RNG_lock_read()/wc_RNG_lock_clear_extra() suffice here without + * stronger synchronization: WC_RNG_LOCK_HELD is held invariantly + * across the span, so this holder is the latch's sole writer -- the + * exact owner-only contract those accessors encode. */ + + /* both levels can be held (an affinity-locked check-out with + * WC_RNG_BANK_FLAG_NO_VECTOR_OPS, whether from the caller's flags + * or bank-wide bank->flags, also takes the vector-ops inhibit) -- + * release each held level separately, innermost first, mirroring + * wc_rng_bank_inst_checkin(). */ + { + WC_RNG_lock_arg_t lock_state = 0; + (void)wc_rng_bank_inst_lock_read(drbg, &lock_state); + if (lock_state & WC_RNG_BANK_INST_LOCK_VEC_OPS_INH) + REENABLE_VECTOR_REGISTERS(); + if (lock_state & WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED) + RESTORE_VECTOR_REGISTERS_MAYBE_INHIBITED(); + } +#endif + +#ifndef WOLFSSL_USE_SAVE_VECTOR_REGISTERS + /* The non-vector checkout hold is migrate_disable() + + * local_bh_disable(). Only BH-off blocks sleeping; drop it for + * the blockable reseed and retake it after. The checkout's + * migrate_disable() legally persists across the sleep, + * preserving the CPU pinning preemptibly -- the same property + * the vector arm's migrate_disable() bracket provides. */ + local_bh_enable(); +#endif + + /* Reseed synchronously. wc_RNG_DRBG_Reseed_Now() resets the reseed + * counter iff the reseed succeeds; on failure it leaves the counter + * unmodified (the WC_RESEED_INTERVAL backstop still governs) and + * marks the instance out of service, exactly as an interval-forced + * reseed failure would. */ +#if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) + ret = wc_RNG_DRBG_Reseed_Now(WC_RNG_BANK_INST_TO_RNG(drbg), NULL, 0); +#else + { + unsigned long uncredited_nonce = random_get_entropy(); + ret = wc_RNG_DRBG_Reseed_Now(WC_RNG_BANK_INST_TO_RNG(drbg), + (byte *)&uncredited_nonce, + (word32)sizeof uncredited_nonce); + + ForceZero(&uncredited_nonce, (word32)sizeof uncredited_nonce); + } +#endif + if (ret != 0) { + pr_warn_ratelimited("WARNING: wc_RNG_DRBG_Reseed_Now() failed " + "for RNG #%d: %d\n", + wc_rng_bank_get_inst_id(drbg), ret); + } + +#ifdef WOLFSSL_USE_SAVE_VECTOR_REGISTERS + /* re-establish each level separately, in acquisition order (the + * affinity save first, then the vector-ops inhibit), mirroring + * wc_rng_bank_checkout(); a failed re-acquisition clears only its + * own lock bit, so check-in unwinds exactly the levels actually + * held. */ + { + WC_RNG_lock_arg_t lock_state = 0; + (void)wc_rng_bank_inst_lock_read(drbg, &lock_state); + if (lock_state & WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED) { + int ret2 = SAVE_VECTOR_REGISTERS2(); + if (ret2 != 0) + (void)wc_rng_bank_inst_lock_clear_extra(drbg, + WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED); + } + if (lock_state & WC_RNG_BANK_INST_LOCK_VEC_OPS_INH) { + int ret2 = DISABLE_VECTOR_REGISTERS(); + if (ret2 != 0) + (void)wc_rng_bank_inst_lock_clear_extra(drbg, + WC_RNG_BANK_INST_LOCK_VEC_OPS_INH); + } + } + + #if defined(CONFIG_SMP) && (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)) + migrate_enable(); + #endif +#else /* !WOLFSSL_USE_SAVE_VECTOR_REGISTERS */ + /* retake the checkout's BH-off hold. */ + local_bh_disable(); +#endif + } + } + for (;;) { #define RNG_MAX_BLOCK_LEN_ROUNDED (RNG_MAX_BLOCK_LEN & ~0xfU) if (dlen > RNG_MAX_BLOCK_LEN_ROUNDED) { - ret = wc_RNG_GenerateBlock(WC_RNG_BANK_INST_TO_RNG(drbg), dst, RNG_MAX_BLOCK_LEN_ROUNDED); + ret = wc_RNG_GenerateBlock( + WC_RNG_BANK_INST_TO_RNG(drbg), dst, RNG_MAX_BLOCK_LEN_ROUNDED); if (ret == 0) { dlen -= RNG_MAX_BLOCK_LEN_ROUNDED; dst += RNG_MAX_BLOCK_LEN_ROUNDED; @@ -2335,17 +3773,85 @@ static int wc_linuxkm_drbg_generate(struct wc_rng_bank *ctx, break; retried = 1; - ret = wc_rng_bank_inst_reinit(ctx, - drbg, + if (! can_wait) + break; + +#ifdef WOLFSSL_USE_SAVE_VECTOR_REGISTERS + /* carefully restore preemptibility for the reinit operation. */ + + #if defined(CONFIG_SMP) && (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)) + migrate_disable(); + #endif + + /* both levels can be held (an affinity-locked checkout with + * WC_RNG_BANK_FLAG_NO_VECTOR_OPS, whether from the caller's flags + * or bank-wide bank->flags, also takes the vector-ops inhibit) -- + * release each held level separately, innermost first, mirroring + * wc_rng_bank_inst_checkin(). */ + { + WC_RNG_lock_arg_t lock_state = 0; + (void)wc_rng_bank_inst_lock_read(drbg, &lock_state); + if (lock_state & WC_RNG_BANK_INST_LOCK_VEC_OPS_INH) + REENABLE_VECTOR_REGISTERS(); + if (lock_state & WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED) + RESTORE_VECTOR_REGISTERS_MAYBE_INHIBITED(); + } +#else /* !WOLFSSL_USE_SAVE_VECTOR_REGISTERS */ + /* The non-vector checkout hold is migrate_disable() + + * local_bh_disable(). Only BH-off blocks sleeping; drop it for + * the blockable reinit and retake it after (see the reseed leg + * above). */ + local_bh_enable(); +#endif + + ret = wc_rng_bank_inst_reinit(NULL, drbg, WC_LINUXKM_INITRNG_TIMEOUT_SEC, WC_RNG_BANK_FLAG_CAN_WAIT); +#ifdef WOLFSSL_USE_SAVE_VECTOR_REGISTERS + /* re-establish each level separately, in acquisition order (the + * affinity save first, then the vector-ops inhibit), mirroring + * wc_rng_bank_checkout(); a failed re-acquisition clears only its + * own lock bit, so check-in unwinds exactly the levels actually + * held. */ + { + /* the latch (annotation bits included) is preserved + * across wc_rng_bank_inst_reinit()'s _InitRng() by the + * module, so this post-reinit read is authoritative. */ + WC_RNG_lock_arg_t lock_state = 0; + (void)wc_rng_bank_inst_lock_read(drbg, &lock_state); + if (lock_state & WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED) { + int ret2 = SAVE_VECTOR_REGISTERS2(); + if (ret2 != 0) + (void)wc_rng_bank_inst_lock_clear_extra(drbg, + WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED); + } + if (lock_state & WC_RNG_BANK_INST_LOCK_VEC_OPS_INH) { + int ret2 = DISABLE_VECTOR_REGISTERS(); + if (ret2 != 0) + (void)wc_rng_bank_inst_lock_clear_extra(drbg, + WC_RNG_BANK_INST_LOCK_VEC_OPS_INH); + } + } + + #if defined(CONFIG_SMP) && (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)) + migrate_enable(); + #endif +#else /* !WOLFSSL_USE_SAVE_VECTOR_REGISTERS */ + /* retake the checkout's BH-off hold. */ + local_bh_disable(); +#endif + if (ret == 0) { - pr_warn_ratelimited("WARNING: reinitialized DRBG #%d after RNG_FAILURE_E from wc_RNG_GenerateBlock().\n", raw_smp_processor_id()); + pr_warn_ratelimited("WARNING: reinitialized DRBG #%d after " + "RNG_FAILURE_E from wc_RNG_GenerateBlock().\n", + wc_rng_bank_get_inst_id(drbg)); continue; } else { - pr_err_ratelimited("ERROR: reinitialization of DRBG #%d after RNG_FAILURE_E failed with ret %d.\n", raw_smp_processor_id(), ret); + pr_err_ratelimited("ERROR: reinitialization of DRBG #%d after " + "RNG_FAILURE_E failed with ret %d.\n", + wc_rng_bank_get_inst_id(drbg), ret); break; } } @@ -2354,13 +3860,14 @@ static int wc_linuxkm_drbg_generate(struct wc_rng_bank *ctx, } if (ret != 0) { - pr_err_ratelimited("ERROR: wc_linuxkm_drbg_generate() failing on wolfCrypt code %d.\n",ret); - ret = -EINVAL; + pr_err_ratelimited("ERROR: wc_linuxkm_drbg_generate() failing on " + "wolfCrypt code %d.\n",ret); + ret = -EIO; } out: - linuxkm_put_drbg(ctx, &drbg); + linuxkm_put_drbg(&drbg); return ret; } @@ -2376,7 +3883,7 @@ static int wc_linuxkm_drbg_generate_tfm(struct crypto_rng *tfm, } return wc_linuxkm_drbg_generate((struct wc_rng_bank *)crypto_rng_ctx(tfm), - src, slen, dst, dlen); + src, slen, dst, dlen, 0 /* pr */); } static int wc_linuxkm_drbg_seed(struct wc_rng_bank *ctx, @@ -2387,7 +3894,17 @@ static int wc_linuxkm_drbg_seed(struct wc_rng_bank *ctx, if (slen == 0) return 0; - ret = wc_rng_bank_seed(ctx, seed, slen, WC_LINUXKM_INITRNG_TIMEOUT_SEC, WC_RNG_BANK_FLAG_CAN_WAIT); + /* The kernel crypto API's seed op carries caller-supplied material (cf. + * crypto/drbg.c, which maps it to an SP 800-90A personalization string / + * additional input, never crediting it as entropy). Mix it into every + * instance without credit; the reseed schedule stays governed solely by + * the module's own seed source. */ + ret = wc_rng_bank_seed_range(ctx, 0, + LINUXKM_RNG_BANK_LAST_SAFELY_CONTENDABLE, + seed, slen, NULL, 0, + WC_LINUXKM_INITRNG_TIMEOUT_SEC, + WC_RNG_BANK_FLAG_CAN_WAIT | + WC_RNG_BANK_FLAG_STIR); if (ret != 0) { pr_err("wc_rng_bank_seed() in wc_linuxkm_drbg_seed() returned err %d.\n", ret); ret = -EINVAL; @@ -2446,6 +3963,10 @@ static int wc_linuxkm_drbg_loaded = 0; #ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS +#if defined(HAVE_FIPS) && !defined(WOLFSSL_LINUXKM_GET_RANDOM_NO_FALLTHROUGH) + #define WOLFSSL_LINUXKM_GET_RANDOM_NO_FALLTHROUGH +#endif + static int wc__get_random_bytes(void *buf, size_t len) { struct wc_rng_bank *current_default_wc_rng_bank; @@ -2456,17 +3977,33 @@ static int wc__get_random_bytes(void *buf, size_t len) ret = wc_rng_bank_default_checkout(¤t_default_wc_rng_bank); if (ret) { -#ifdef WC_VERBOSE_RNG - pr_err_ratelimited("ERROR: wc_rng_bank_default_checkout() in wc__get_random_bytes() returned %d.\n", ret); +#ifdef WOLFSSL_LINUXKM_GET_RANDOM_NO_FALLTHROUGH + pr_emerg_ratelimited("ERROR: FIPS RNG source failed in " + "wc__get_random_bytes(): wc_rng_bank_default_checkout() " + "returned %d.\n", ret); +#else + pr_err_ratelimited("ERROR: FIPS RNG source failed in wc__get_random_bytes(): " + "wc_rng_bank_default_checkout() returned %d.\n", ret); #endif - return -EFAULT; + /* kernel must-succeed call used from hard IRQ contexts etc. -- the + * callback dispatch point will always fall through to native DRBG, but + * we log the condition loudly from here. */ + return -ECANCELED; } else { ret = wc_linuxkm_drbg_generate(current_default_wc_rng_bank, - NULL, 0, buf, (unsigned int)len); + NULL, 0, buf, (unsigned int)len, 0 /* pr */); (void)wc_rng_bank_default_checkin(¤t_default_wc_rng_bank); if (ret) { - pr_warn("BUG: wc__get_random_bytes falling through to native get_random_bytes with wc_linuxkm_drbg_default_instance_registered, ret=%d.\n", ret); +#ifdef WOLFSSL_LINUXKM_GET_RANDOM_NO_FALLTHROUGH + pr_emerg_ratelimited("ERROR: FIPS RNG source failed: " + "wc__get_random_bytes(): wc_linuxkm_drbg_generate() " + "failed with code %d.\n", ret); +#else + pr_err_ratelimited("ERROR: FIPS RNG source failed: " + "wc__get_random_bytes(): wc_linuxkm_drbg_generate() " + "failed with code %d.\n", ret); +#endif } return ret; } @@ -2477,25 +4014,40 @@ static int wc__get_random_bytes(void *buf, size_t len) static ssize_t wc_get_random_bytes_user(struct iov_iter *iter) { struct wc_rng_bank *current_default_wc_rng_bank; ssize_t ret; + if (unlikely(!iov_iter_count(iter))) return 0; ret = wc_rng_bank_default_checkout(¤t_default_wc_rng_bank); if (ret) { -#ifdef WC_VERBOSE_RNG - pr_err_ratelimited("ERROR: wc_rng_bank_default_checkout() in wc_get_random_bytes_user() returned %ld.\n", ret); -#endif - return -ECANCELED; + pr_emerg_ratelimited("ERROR: wc_rng_bank_default_checkout() in " + "wc_get_random_bytes_user() returned %ld.\n", ret); + return -EIO; /* no fallthrough to native randomness */ } else { size_t this_copied, total_copied = 0; - byte block[WC_SHA256_BLOCK_SIZE]; + byte *block; + byte block_small[WC_SHA256_BLOCK_SIZE]; + size_t block_size; + + if (iov_iter_count(iter) <= sizeof block_small) + block = NULL; + else + block = (byte *)malloc(PAGE_SIZE); + if (block == NULL) { + block = block_small; + block_size = sizeof block_small; + } + else + block_size = PAGE_SIZE; for (;;) { + size_t n = min_t(size_t, iov_iter_count(iter), block_size); ret = wc_linuxkm_drbg_generate(current_default_wc_rng_bank, - NULL, 0, block, sizeof block); + NULL, 0, block, n, 0 /* pr */); if (unlikely(ret != 0)) { - pr_err("ERROR: wc_get_random_bytes_user() wc_linuxkm_drbg_generate() returned %ld.\n", ret); + pr_emerg_ratelimited("ERROR: wc_get_random_bytes_user() " + "wc_linuxkm_drbg_generate() returned %ld.\n", ret); break; } @@ -2503,32 +4055,33 @@ static ssize_t wc_get_random_bytes_user(struct iov_iter *iter) { * DISABLE_VECTOR_REGISTERS() or kprobes status, i.e. * irq_count() must be zero here. */ - this_copied = copy_to_iter(block, sizeof(block), iter); + this_copied = copy_to_iter(block, n, iter); total_copied += this_copied; - if (!iov_iter_count(iter) || this_copied != sizeof(block)) + if (!iov_iter_count(iter) || this_copied != n) break; - wc_static_assert(PAGE_SIZE % sizeof(block) == 0); - if (total_copied % PAGE_SIZE == 0) { - if (signal_pending(current)) - break; - cond_resched(); - } + if (signal_pending(current)) + break; + cond_resched(); } (void)wc_rng_bank_default_checkin(¤t_default_wc_rng_bank); - ForceZero(block, sizeof(block)); + ForceZero(block, block_size); + + if (block != block_small) + free(block); if (total_copied == 0) { if (ret == 0) ret = -EFAULT; - else - ret = -ECANCELED; + else { + ret = -EIO; + } } - if (ret == 0) - ret = (ssize_t)total_copied; + if (total_copied != 0) + ret = (ssize_t)total_copied; /* partial success wins */ return ret; } @@ -2539,120 +4092,201 @@ static ssize_t wc_get_random_bytes_user(struct iov_iter *iter) { static ssize_t wc_extract_crng_user(void __user *buf, size_t nbytes) { ssize_t ret; struct wc_rng_bank *current_default_wc_rng_bank; + if (unlikely(!nbytes)) return 0; ret = wc_rng_bank_default_checkout(¤t_default_wc_rng_bank); if (ret) { -#ifdef WC_VERBOSE_RNG - pr_err_ratelimited("ERROR: wc_rng_bank_default_checkout() in wc_extract_crng_user() returned %ld.\n", ret); -#endif - return -ECANCELED; + pr_emerg_ratelimited("ERROR: wc_rng_bank_default_checkout() in " + "wc_extract_crng_user() returned %ld.\n", ret); + return -EIO; /* no fallthrough to native randomness */ } else { size_t this_copied, total_copied = 0; - byte block[WC_SHA256_BLOCK_SIZE]; + byte *block; + byte block_small[WC_SHA256_BLOCK_SIZE]; + size_t block_size; + + if (nbytes <= sizeof block_small) + block = NULL; + else + block = (byte *)malloc(PAGE_SIZE); + if (block == NULL) { + block = block_small; + block_size = sizeof block_small; + } + else + block_size = PAGE_SIZE; for (;;) { + size_t n = min_t(size_t, nbytes - total_copied, block_size); ret = wc_linuxkm_drbg_generate(current_default_wc_rng_bank, - NULL, 0, block, sizeof block); + NULL, 0, block, n, 0 /* pr */); if (unlikely(ret != 0)) { - pr_err("ERROR: wc_extract_crng_user() wc_linuxkm_drbg_generate() returned %ld.\n", ret); + pr_emerg_ratelimited("ERROR: wc_extract_crng_user() " + "wc_linuxkm_drbg_generate() returned %ld.\n", ret); break; } - this_copied = nbytes - total_copied; - if (this_copied > sizeof(block)) - this_copied = sizeof(block); - if (copy_to_user((byte *)buf + total_copied, block, this_copied)) { - ret = -EFAULT; - break; - } + /* note copy_to_user() cannot be safely executed with + * DISABLE_VECTOR_REGISTERS() or kprobes status, i.e. + * irq_count() must be zero here. + */ + this_copied = n - copy_to_user((byte *)buf + total_copied, + block, n); total_copied += this_copied; - if (this_copied != sizeof(block)) + if ((total_copied == nbytes) || (this_copied != n)) break; - wc_static_assert(PAGE_SIZE % sizeof(block) == 0); - if (total_copied % PAGE_SIZE == 0) { - if (signal_pending(current)) - break; - cond_resched(); - } + if (signal_pending(current)) + break; + cond_resched(); } (void)wc_rng_bank_default_checkin(¤t_default_wc_rng_bank); - ForceZero(block, sizeof(block)); + ForceZero(block, block_size); + + if (block != block_small) + free(block); - if ((total_copied == 0) && (ret == 0)) { - ret = -ECANCELED; + if (total_copied == 0) { + if (ret == 0) + ret = -EFAULT; + else + ret = -EIO; } - if (ret == 0) - ret = (ssize_t)total_copied; + if (total_copied != 0) + ret = (ssize_t)total_copied; /* partial success wins */ return ret; } __builtin_unreachable(); } +/* Note, wc_mix_pool_bytes() only injects the supplied entropy into one RNG, + * selection dependent on WC_RNG_HAVE_NEXT_SEED and the size of the input. This + * routine can be pegged by unprivileged users, so with large input, it tries to + * keep its impact as CPU-local as possible. */ static int wc_mix_pool_bytes(const void *buf, size_t len) { int ret; - struct wc_rng_bank *ctx; - size_t i; - int n; + struct wc_rng_bank *ctx = NULL; + unsigned long uncredited_nonce; int can_sleep = wc_linuxkm_can_block(); - if (len == 0) - return 0; + if (len > WC_MAX_UINT_OF(word32)) + return -EFBIG; + + if (len == 0) { + uncredited_nonce = random_get_entropy(); + buf = &uncredited_nonce; + len = sizeof uncredited_nonce; + } + + if (! can_sleep) { +#ifdef WC_RNG_HAVE_NEXT_SEED + if (len > WC_DRBG_NEXT_STIR_LEN) + len = WC_DRBG_NEXT_STIR_LEN; +#else + if (len > 64) + len = 64; +#endif + } ret = wc_rng_bank_default_checkout(&ctx); if (ret) { #ifdef WC_VERBOSE_RNG - pr_err_ratelimited("ERROR: wc_rng_bank_default_checkout() in wc_mix_pool_bytes() returned %d.\n", ret); + pr_err_ratelimited("ERROR: wc_rng_bank_default_checkout() in " + "wc_mix_pool_bytes() returned %d.\n", ret); #endif return -EFAULT; } - ret = 0; - - for (n = ctx->n_rngs - 1; n >= 0; --n) { - struct wc_rng_bank_inst *drbg; - - int V_offset; - - if (wc_rng_bank_checkout(ctx, &drbg, n, 0, WC_RNG_BANK_FLAG_NONE) != 0) - continue; - -#ifdef WOLFSSL_DRBG_SHA512 - if (WC_RNG_BANK_INST_TO_RNG(drbg)->drbgType == WC_DRBG_SHA512) { - for (i = 0, V_offset = 0; i < len; ++i) { - ((struct DRBG_SHA512_internal *)WC_RNG_BANK_INST_TO_RNG(drbg)->drbg512)->V[V_offset++] += ((byte *)buf)[i]; - if (V_offset == (int)sizeof ((struct DRBG_SHA512_internal *)WC_RNG_BANK_INST_TO_RNG(drbg)->drbg512)->V) - V_offset = 0; +#ifdef WC_RNG_HAVE_NEXT_SEED + { + WC_RNG *stir_root = wc_rng_bank_root_rng_get(ctx); + + /* Small input, fast path: lock-free XMEMCPY/xorbuf. */ + if (len <= WC_DRBG_NEXT_STIR_LEN) { + static DEFINE_PER_CPU(int, stir_index) = -2; + int this_index = this_cpu_inc_return(stir_index); + /* at startup, stagger them across the bank, to get wider spread and + * less contentious coverage. */ + if (this_index < 0) { + int stride = ctx->n_rngs / nr_cpu_ids; + if (stride < 1) + stride = 1; + this_index = raw_smp_processor_id() * stride; + this_cpu_write(stir_index, this_index); + } + /* Note, this_index can be >= ctx->n_rngs here even if this_index + * was < 0 on entry. */ + if (this_index >= ctx->n_rngs) { + this_index %= ctx->n_rngs; + /* we may have been migrated since this_cpu_inc_return() -- + * tolerate the harmless reset of a different counter. */ + this_cpu_write(stir_index, this_index); } + (void)wc_RNG_DRBG_NextStirStore( + WC_RNG_BANK_OFFSET_TO_RNG(ctx, this_index), (const byte *)buf, + (word32)len); + } + + if (stir_root != NULL) { + /* note that input beyond WC_DRBG_NEXT_STIR_LEN is discarded. */ + (void)wc_RNG_DRBG_NextStirStore(stir_root, (const byte *)buf, + (word32)len); } + } + + if (len > WC_DRBG_NEXT_STIR_LEN) +#endif /* WC_RNG_HAVE_NEXT_SEED */ + { + word32 flags = + WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST | + WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST; + struct wc_rng_bank_inst *drbg = NULL; + + if (can_sleep) + flags |= WC_RNG_BANK_FLAG_AFFINITY_LOCK; +#if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) else -#endif /* WOLFSSL_DRBG_SHA512 */ - { - for (i = 0, V_offset = 0; i < len; ++i) { - ((struct DRBG_internal *)WC_RNG_BANK_INST_TO_RNG(drbg)->drbg)->V[V_offset++] += ((byte *)buf)[i]; - if (V_offset == (int)sizeof ((struct DRBG_internal *)WC_RNG_BANK_INST_TO_RNG(drbg)->drbg)->V) - V_offset = 0; - } + flags |= WC_RNG_BANK_FLAG_NO_VECTOR_OPS; +#endif + + ret = wc_rng_bank_checkout(ctx, &drbg, 0, 0, flags); + if (ret != 0) { + ret = -EINVAL; + goto out; } - wc_rng_bank_checkin(ctx, &drbg); - if (can_sleep) { - if (signal_pending(current)) { - ret = -EINTR; - break; - } - cond_resched(); + if (! wc_RNG_DRBG_Present(WC_RNG_BANK_INST_TO_RNG(drbg))) { + ret = 0; /* consistent with wc_RNG_DRBG_Reseed() behavior in RDRAND configs. */ + goto out; } + + /* Mix without crediting the contributed entropy -- + * wc_RNG_DRBG_Stir() leaves the reseed counter unmodified, + * so only the module's own seed source resets the reseed schedule. */ + ret = wc_RNG_DRBG_Stir(WC_RNG_BANK_INST_TO_RNG(drbg), buf, + (word32)len); + + out: + + if (drbg) + (void)wc_rng_bank_inst_checkin(&drbg); } - (void)wc_rng_bank_default_checkin(&ctx); + if (buf == &uncredited_nonce) + ForceZero(&uncredited_nonce, (word32)sizeof uncredited_nonce); + + if (ret != 0) + ret = -EINVAL; + + if (ctx) + (void)wc_rng_bank_default_checkin(&ctx); return ret; } @@ -2661,25 +4295,33 @@ static int wc_crng_reseed(void) { struct wc_rng_bank *ctx; int can_sleep = wc_linuxkm_can_block(); int ret = wc_rng_bank_default_checkout(&ctx); + unsigned long uncredited_nonce; if (ret) { #ifdef WC_VERBOSE_RNG - pr_err_ratelimited("ERROR: wc_rng_bank_default_checkout() in wc_crng_reseed() returned %d.\n", ret); + pr_err_ratelimited("ERROR: wc_rng_bank_default_checkout() in " + "wc_crng_reseed() returned %d.\n", ret); #endif return -EFAULT; } - ret = wc_rng_bank_reseed(ctx, WC_LINUXKM_INITRNG_TIMEOUT_SEC, - can_sleep - ? - WC_RNG_BANK_FLAG_CAN_WAIT - : - WC_RNG_BANK_FLAG_NONE); + uncredited_nonce = random_get_entropy(); + ret = wc_rng_bank_reseed_range(ctx, 0, + LINUXKM_RNG_BANK_LAST_SAFELY_CONTENDABLE, + (byte *)&uncredited_nonce, + (word32)sizeof uncredited_nonce, + WC_LINUXKM_INITRNG_TIMEOUT_SEC, + can_sleep + ? + WC_RNG_BANK_FLAG_CAN_WAIT + : + WC_RNG_BANK_FLAG_NONE); + ForceZero(&uncredited_nonce, (word32)sizeof uncredited_nonce); (void)wc_rng_bank_default_checkin(&ctx); if (ret != 0) { - pr_err("ERROR: wc_rng_bank_reseed() returned err %d.\n", ret); + pr_err("ERROR: wc_rng_bank_reseed_range() returned err %d.\n", ret); return -EINVAL; } else { @@ -2698,7 +4340,7 @@ struct wolfssl_linuxkm_random_bytes_handlers random_bytes_handlers = { .mix_pool_bytes = wc_mix_pool_bytes, /* .credit_init_bits not implemented */ - .crng_reseed = wc_crng_reseed + .crng_reseed = wc_crng_reseed, }; static int wc_get_random_bytes_callbacks_installed = 0; @@ -2728,10 +4370,20 @@ static int wc_get_random_bytes_by_kprobe(struct kprobe *p, struct pt_regs *regs) regs->ip = (unsigned long)p->addr + p->ainsn.size; return 1; /* Handled. */ } - pr_warn("BUG: wc_get_random_bytes_by_kprobe falling through to native get_random_bytes with wc_linuxkm_drbg_default_instance_registered, ret=%d.\n", ret); +#ifdef HAVE_FIPS + pr_emerg_ratelimited("ERROR: wc_get_random_bytes_by_kprobe falling " + "through to native get_random_bytes with " + "wc_linuxkm_drbg_default_instance_registered, ret=%d.\n", ret); +#else + pr_warn_ratelimited("ERROR: wc_get_random_bytes_by_kprobe falling " + "through to native get_random_bytes with " + "wc_linuxkm_drbg_default_instance_registered, ret=%d.\n", ret); +#endif + } + else { + pr_warn("BUG: wc_get_random_bytes_by_kprobe called without " + "wc_linuxkm_drbg_default_instance_registered.\n"); } - else - pr_warn("BUG: wc_get_random_bytes_by_kprobe called without wc_linuxkm_drbg_default_instance_registered.\n"); /* Not handled. Fall through to native implementation, given * that the alternative is an immediate kernel panic. @@ -2884,11 +4536,29 @@ static int wc_get_random_bytes_user_kretprobe_installed = 0; #endif /* LINUXKM_DRBG_GET_RANDOM_BYTES */ -#if defined(LINUXKM_LKCAPI_REGISTER_HASH_DRBG_DEFAULT) && \ - (LINUX_VERSION_CODE >= KERNEL_VERSION(7, 1, 0)) -static struct wc_rng_bank default_bank; -static int default_bank_inited; -#endif +#ifdef WC_RNG_DEBUG_STATS +/* control channel at /sys/module/libwolfssl/rng_stats: echo 1 to dump the + * current RNG stats to the kernel log on demand (they otherwise appear + * only at teardown). */ +static ssize_t wc_linuxkm_rng_stats_handler(WC_MODULE_ATTR_CONST struct module_attribute *mattr, + struct module_kobject *mk, + const char *buf, size_t count) +{ + int arg; + + (void)mattr; + (void)mk; + + if (kstrtoint(buf, 10, &arg) || (arg != 1)) + return -EINVAL; + if (! default_bank) + return -ENODEV; + wc_linuxkm_rng_dump_stats(default_bank); + return (ssize_t)count; +} +static struct module_attribute wc_linuxkm_rng_stats_attr = + __ATTR(rng_stats, 0220, NULL, wc_linuxkm_rng_stats_handler); +#endif /* WC_RNG_DEBUG_STATS */ static int wc_linuxkm_drbg_startup(void) { @@ -3037,7 +4707,8 @@ static int wc_linuxkm_drbg_startup(void) } if (crypto_default_rng->base.__crt_alg->cra_init != wc_linuxkm_drbg_init_tfm) { - pr_err("ERROR: %s NOT registered as systemwide default stdrng -- found \"%s\".\n", wc_linuxkm_drbg.base.cra_driver_name, crypto_tfm_alg_driver_name(&crypto_default_rng->base)); + pr_err("ERROR: %s NOT registered as systemwide default stdrng -- found \"%s\".\n", + wc_linuxkm_drbg.base.cra_driver_name, crypto_tfm_alg_driver_name(&crypto_default_rng->base)); crypto_put_default_rng(); return -EINVAL; } @@ -3069,13 +4740,13 @@ static int wc_linuxkm_drbg_startup(void) else #endif /* CONFIG_CRYPTO_FIPS */ { - ret = wc_linuxkm_rng_bank_init(&default_bank); + static struct wc_rng_bank local_default_bank; + ret = wc_linuxkm_rng_bank_init(&local_default_bank); wc_linuxkm_rng_initing_default_bank_flag = 0; if (ret) { pr_err("ERROR: wc_linuxkm_rng_bank_init returned %d\n", ret); return ret; } - default_bank_inited = 1; } #endif /* >= 7.1.0 */ @@ -3093,10 +4764,8 @@ static int wc_linuxkm_drbg_startup(void) if (ret != 0) { #if defined(LINUXKM_LKCAPI_REGISTER_HASH_DRBG_DEFAULT) && \ (LINUX_VERSION_CODE >= KERNEL_VERSION(7, 1, 0)) - if (default_bank_inited) { - (void)wc_rng_bank_default_clear(&default_bank); - (void)wc_rng_bank_fini(&default_bank); - default_bank_inited = 0; + if (default_bank != NULL) { + (void)wc_linuxkm_rng_bank_fini(default_bank); } #endif return -ECANCELED; @@ -3107,6 +4776,12 @@ static int wc_linuxkm_drbg_startup(void) pr_info("%s registered as systemwide default stdrng.\n", wc_linuxkm_drbg.base.cra_driver_name); pr_info("libwolfssl: to unload module, first echo 1 > /sys/module/libwolfssl/deinstall_algs\n"); +#ifdef WC_LINUXKM_HAVE_RNG_REGISTRY + /* stock-notifier invalidation coverage rides with the registered + * DRBGs, patched and unpatched kernels alike. */ + wc_linuxkm_rng_notifiers_install(); +#endif + #ifdef LINUXKM_DRBG_GET_RANDOM_BYTES #ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS @@ -3200,6 +4875,13 @@ static int wc_linuxkm_drbg_cleanup(void) { */ int ret; +#ifdef WC_LINUXKM_HAVE_RNG_REGISTRY + /* the notifier callbacks walk the RNG registry: uninstall them + * before any of what they reference is dismantled. unregister + * returns only after in-flight callbacks complete. */ + wc_linuxkm_rng_notifiers_uninstall(); +#endif + #ifdef LINUXKM_DRBG_GET_RANDOM_BYTES /* we need to unregister the get_random_bytes handlers first to remove @@ -3260,16 +4942,10 @@ static int wc_linuxkm_drbg_cleanup(void) { } else #endif /* CONFIG_CRYPTO_FIPS */ - if (default_bank_inited) { - ret = wc_rng_bank_default_clear(&default_bank); + if (default_bank) { + ret = wc_linuxkm_rng_bank_fini(default_bank); if (ret) - pr_err("ERROR: wc_rng_bank_default_clear in wc_linuxkm_drbg_cleanup failed: %d\n", ret); - else { - ret = wc_rng_bank_fini(&default_bank); - if (ret) - pr_err("ERROR: wc_rng_bank_fini in wc_linuxkm_drbg_cleanup failed: %d\n", ret); - } - default_bank_inited = 0; + pr_err("ERROR: wc_linuxkm_rng_bank_fini in wc_linuxkm_drbg_cleanup failed: %d\n", ret); } #endif /* >= 7.1.0 */ diff --git a/linuxkm/patches/7.3/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v3.patch b/linuxkm/patches/7.3/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v3.patch new file mode 100644 index 00000000000..be16a5ab725 --- /dev/null +++ b/linuxkm/patches/7.3/WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS-7v3.patch @@ -0,0 +1,548 @@ +--- 7.3/drivers/char/random.c.dist 2026-09-07 15:16:52.000000000 -0500 ++++ 7.3/drivers/char/random.c 2026-09-08 14:52:24.223216472 -0500 +@@ -84,6 +84,310 @@ static enum { + } 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 ++ ++#include ++ ++static atomic_long_t random_bytes_cb_owner = ++ ATOMIC_LONG_INIT((long)NULL); ++static struct percpu_ref random_bytes_cb_ref; ++static bool random_bytes_cb_live; /* READ_ONCE/WRITE_ONCE only */ ++static DECLARE_COMPLETION(random_bytes_cb_drained); ++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; ++ ++static void random_bytes_cb_ref_release(struct percpu_ref *ref) ++{ ++ complete(&random_bytes_cb_drained); ++} ++ ++/* distinct non-module value marking teardown in progress; guarantees ++ * non-collision with any real module pointer. */ ++static char random_bytes_cb_teardown_sentinel; ++#define RANDOM_BYTES_CB_TEARDOWN ((long)&random_bytes_cb_teardown_sentinel) ++ ++int wolfssl_linuxkm_register_random_bytes_handlers( ++ struct module *new_random_bytes_cb_owner, ++ const struct wolfssl_linuxkm_random_bytes_handlers *handlers) ++{ ++ int ret; ++ ++ 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_linuxkm_register_random_bytes_handlers() and ++ * wolfssl_linuxkm_unregister_random_bytes_handlers(): NULL means ++ * unowned, a module pointer means registered (or registration in ++ * flight, while random_bytes_cb_live is still false), and ++ * RANDOM_BYTES_CB_TEARDOWN means unregistration in flight. ++ */ ++ if (atomic_long_cmpxchg(&random_bytes_cb_owner, ++ (long)NULL, ++ (long)new_random_bytes_cb_owner) ++ != (long)NULL) ++ { ++ return -EBUSY; ++ } ++ ++ if (!try_module_get(new_random_bytes_cb_owner)) { ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ return -ENODEV; ++ } ++ ++ /* handlers must be published before the ref goes live. */ ++ _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; ++ ++ reinit_completion(&random_bytes_cb_drained); ++ ret = percpu_ref_init(&random_bytes_cb_ref, ++ random_bytes_cb_ref_release, 0, GFP_KERNEL); ++ if (ret) { ++ _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(new_random_bytes_cb_owner); ++ /* an unregister call racing this in-flight registration may ++ * hold the owner word at RANDOM_BYTES_CB_TEARDOWN for the ++ * brief interval before it observes !random_bytes_cb_live ++ * and restores our pointer; wait it out rather than ++ * clobbering its claim. */ ++ while (atomic_long_cmpxchg(&random_bytes_cb_owner, ++ (long)new_random_bytes_cb_owner, ++ (long)NULL) ++ != (long)new_random_bytes_cb_owner) ++ { ++ cpu_relax(); ++ } ++ return ret; ++ } ++ ++ if (IS_ENABLED(CONFIG_VDSO_GETRANDOM)) { ++ /* route vDSO getrandom() users to the syscall, hence to the ++ * callbacks. */ ++ WRITE_ONCE(vdso_k_rng_data->is_ready, false); ++ } ++ ++ WRITE_ONCE(random_bytes_cb_live, true); ++ ++ return 0; ++} ++EXPORT_SYMBOL(wolfssl_linuxkm_register_random_bytes_handlers); ++ ++int wolfssl_linuxkm_unregister_random_bytes_handlers(void) ++{ ++ long prev = atomic_long_read(&random_bytes_cb_owner); ++ struct module *owner; ++ ++ /* claim teardown ownership: exactly one caller transitions the owner ++ * word from a module pointer to RANDOM_BYTES_CB_TEARDOWN; any ++ * concurrent or repeated caller is refused. ++ */ ++ for (;;) { ++ if ((prev == (long)NULL) || (prev == RANDOM_BYTES_CB_TEARDOWN)) ++ return -ENODEV; ++ if (atomic_long_try_cmpxchg(&random_bytes_cb_owner, &prev, ++ RANDOM_BYTES_CB_TEARDOWN)) ++ break; ++ /* prev was refreshed by the failed cmpxchg; loop. */ ++ } ++ owner = (struct module *)prev; ++ ++ if (!READ_ONCE(random_bytes_cb_live)) { ++ /* registration still in flight (or failing): hand the owner ++ * word back and refuse. */ ++ atomic_long_set(&random_bytes_cb_owner, prev); ++ return -EBUSY; ++ } ++ ++ WRITE_ONCE(random_bytes_cb_live, false); ++ /* after this, no resolver can be between its liveness check and its ++ * tryget. */ ++ synchronize_rcu(); ++ percpu_ref_kill(&random_bytes_cb_ref); ++ wait_for_completion(&random_bytes_cb_drained); ++ percpu_ref_exit(&random_bytes_cb_ref); ++ ++ _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; ++ ++ if (IS_ENABLED(CONFIG_VDSO_GETRANDOM)) ++ WRITE_ONCE(vdso_k_rng_data->is_ready, crng_ready()); ++ ++ module_put(owner); ++ atomic_long_set(&random_bytes_cb_owner, (long)NULL); ++ ++ return 0; ++} ++EXPORT_SYMBOL(wolfssl_linuxkm_unregister_random_bytes_handlers); ++ ++/* Output emitters covered while a generate callback is live: ++ * _get_random_bytes (and the batched get_random_uXX fills, which route ++ * through it), get_random_bytes_user, and vDSO getrandom (readiness ++ * withheld, forcing the syscall path). New crng_make_state callers or ++ * vDSO datapage exports need corresponding treatment. */ ++static __always_inline int reserve_random_bytes_cb(void) ++{ ++ int ret = -ENODEV; ++ ++ rcu_read_lock(); ++ if (READ_ONCE(random_bytes_cb_live) && ++ percpu_ref_tryget_live(&random_bytes_cb_ref)) ++ { ++ ret = 0; ++ } ++ rcu_read_unlock(); ++ ++ return ret; ++} ++ ++static __always_inline void release_random_bytes_cb(void) ++{ ++ percpu_ref_put(&random_bytes_cb_ref); ++} ++ ++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 */ ++ ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ #define crng_ready_by_cb() (READ_ONCE(random_bytes_cb_live) && call_crng_ready_cb()) ++ #define crng_ready_maybe_cb() (READ_ONCE(random_bytes_cb_live) ? \ ++ (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; +@@ -107,7 +411,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); + +@@ -131,11 +435,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; + } +@@ -155,7 +459,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); +@@ -392,6 +696,24 @@ static void _get_random_bytes(void *buf, + if (!len) + return; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ { ++ int cb_ret = call__get_random_bytes_cb(buf, len); ++ if (cb_ret == 0) ++ return; ++ /* Nonzero cb_ret: no handlers installed (-ENODEV), a lost ++ * race with deinstallation (also -ENODEV), or a genuine ++ * callback failure. Check the sentinel at warn time to ++ * distinguish: only a failure with the sentinel still live is ++ * a contract violation. Continue regardless; native ++ * fallthrough is the only available mechanism to preserve ++ * the void contract. */ ++ WARN_ONCE(READ_ONCE(random_bytes_cb_live), ++ "_get_random_bytes callback failed with code %d; " ++ "native fallthrough\n", cb_ret); ++ } ++#endif ++ + first_block_len = min_t(size_t, 32, len); + crng_make_state(&chacha_state, buf, first_block_len); + len -= first_block_len; +@@ -437,6 +759,20 @@ static ssize_t get_random_bytes_user(str + if (unlikely(!iov_iter_count(iter))) + return 0; + ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ if (READ_ONCE(random_bytes_cb_live)) { ++ ssize_t cb_ret = call_get_random_bytes_user_cb(iter); ++ /* -ECANCELED: no iter callback registered; iter is intact. ++ * While a generate callback is live, no output bytes come ++ * from the native crng: a live handler set must include the ++ * iter callback. */ ++ if (cb_ret != -ECANCELED) ++ return cb_ret; ++ WARN_ONCE(1, "live random_bytes handler set lacks get_random_bytes_user callback"); ++ return -EIO; ++ } ++#endif ++ + /* + * Immediately overwrite the ChaCha key at index 4 with random + * bytes, in case userspace causes copy_to_iter() below to sleep +@@ -512,7 +848,7 @@ type get_random_ ##type(void) \ + struct batch_ ##type *batch; \ + unsigned long next_gen; \ + \ +- if (!crng_ready()) { \ ++ if (READ_ONCE(random_bytes_cb_live) || !crng_ready()) { \ + _get_random_bytes(&ret, sizeof(ret)); \ + return ret; \ + } \ +@@ -648,6 +984,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); +@@ -707,7 +1048,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) + { +@@ -731,7 +1078,11 @@ static void __cold _credit_init_bits(siz + if (system_dfl_wq) + queue_work(system_dfl_wq, &set_ready); + atomic_notifier_call_chain(&random_ready_notifier, 0, NULL); +- if (IS_ENABLED(CONFIG_VDSO_GETRANDOM)) ++ if (IS_ENABLED(CONFIG_VDSO_GETRANDOM) ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ && !READ_ONCE(random_bytes_cb_live) ++#endif ++ ) + WRITE_ONCE(vdso_k_rng_data->is_ready, true); + wake_up_interruptible(&crng_init_wait); + kill_fasync(&fasync, SIGIO, POLL_IN); +@@ -934,6 +1285,10 @@ void add_device_randomness(const void *b + _mix_pool_bytes(&entropy, sizeof(entropy)); + _mix_pool_bytes(buf, len); + spin_unlock_irqrestore(&input_pool.lock, flags); ++#ifdef WOLFSSL_LINUXKM_HAVE_GET_RANDOM_CALLBACKS ++ if (READ_ONCE(random_bytes_cb_live)) ++ (void)call_mix_pool_bytes_cb(buf, len); ++#endif + } + EXPORT_SYMBOL(add_device_randomness); + +@@ -1392,7 +1747,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(); +@@ -1408,6 +1763,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; + } +@@ -1453,10 +1812,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)) { +@@ -1473,7 +1832,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; +@@ -1538,6 +1897,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); +--- 7.3/include/linux/random.h.dist 2026-09-07 15:16:52.000000000 -0500 ++++ 7.3/include/linux/random.h 2026-09-08 14:52:45.063663933 -0500 +@@ -139,4 +139,37 @@ 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); ++ + #endif /* _LINUX_RANDOM_H */ diff --git a/tests/unit-mcdc/test_random_fault_whitebox.c b/tests/unit-mcdc/test_random_fault_whitebox.c index a40e0f6ff89..af7268c827f 100644 --- a/tests/unit-mcdc/test_random_fault_whitebox.c +++ b/tests/unit-mcdc/test_random_fault_whitebox.c @@ -204,16 +204,24 @@ static void wb_hash_drbg_init_df_chain(void) /* Vector (F, -): the first Hash_df fails on its very first primitive * call, so the && short-circuits and never calls the second one. */ mcdc_fh_arm(1); - (void)Hash_DRBG_Instantiate(&drbg, seed, (word32)sizeof(seed), + ret = Hash_DRBG_Instantiate(&drbg, seed, (word32)sizeof(seed), nonce, (word32)sizeof(nonce), NULL, 0, NULL, INVALID_DEVID); + if (ret == DRBG_SUCCESS) { + WB_NOTE("armed Hash_DRBG_Instantiate unexpectedly succeeded"); + wb_fail = 1; + } mcdc_fh_disarm(); (void)Hash_DRBG_Uninstantiate(&drbg); /* Vector (T, F): the first Hash_df's n1 primitive calls all succeed; * every call from n1+1 on -- the whole second Hash_df -- fails. */ mcdc_fh_arm(n1 + 1); - (void)Hash_DRBG_Instantiate(&drbg, seed, (word32)sizeof(seed), + ret = Hash_DRBG_Instantiate(&drbg, seed, (word32)sizeof(seed), nonce, (word32)sizeof(nonce), NULL, 0, NULL, INVALID_DEVID); + if (ret == DRBG_SUCCESS) { + WB_NOTE("armed Hash_DRBG_Instantiate unexpectedly succeeded"); + wb_fail = 1; + } mcdc_fh_disarm(); (void)Hash_DRBG_Uninstantiate(&drbg); @@ -270,15 +278,23 @@ static void wb_hash512_drbg_init_df_chain(void) /* Vector (F, -). */ mcdc_fh_arm(1); - (void)Hash512_DRBG_Instantiate(&drbg, seed, (word32)sizeof(seed), + ret = Hash512_DRBG_Instantiate(&drbg, seed, (word32)sizeof(seed), nonce, (word32)sizeof(nonce), NULL, 0, NULL, INVALID_DEVID); + if (ret == DRBG_SUCCESS) { + WB_NOTE("armed Hash512_DRBG_Instantiate unexpectedly succeeded"); + wb_fail = 1; + } mcdc_fh_disarm(); (void)Hash512_DRBG_Uninstantiate(&drbg); /* Vector (T, F). */ mcdc_fh_arm(n1 + 1); - (void)Hash512_DRBG_Instantiate(&drbg, seed, (word32)sizeof(seed), + ret = Hash512_DRBG_Instantiate(&drbg, seed, (word32)sizeof(seed), nonce, (word32)sizeof(nonce), NULL, 0, NULL, INVALID_DEVID); + if (ret == DRBG_SUCCESS) { + WB_NOTE("armed Hash512_DRBG_Instantiate unexpectedly succeeded"); + wb_fail = 1; + } mcdc_fh_disarm(); (void)Hash512_DRBG_Uninstantiate(&drbg); diff --git a/tests/unit-mcdc/test_random_whitebox.c b/tests/unit-mcdc/test_random_whitebox.c index 3cf934c904c..eb729b8e206 100644 --- a/tests/unit-mcdc/test_random_whitebox.c +++ b/tests/unit-mcdc/test_random_whitebox.c @@ -300,7 +300,7 @@ static void wb_hash_drbg_generate_reseed(void) /* False side: reseedCtr below the interval -> generate proceeds. */ drbg.reseedCtr = 1; - ret = Hash_DRBG_Generate(&drbg, out, (word32)sizeof(out), NULL, 0); + ret = Hash_DRBG_Generate(&drbg, out, (word32)sizeof(out), NULL, 0, NULL); if (ret != DRBG_SUCCESS) { WB_NOTE("Hash_DRBG_Generate (below interval) failed"); wb_fail = 1; @@ -308,7 +308,7 @@ static void wb_hash_drbg_generate_reseed(void) /* True side: reseedCtr at the interval -> early DRBG_NEED_RESEED. */ drbg.reseedCtr = WC_RESEED_INTERVAL; - ret = Hash_DRBG_Generate(&drbg, out, (word32)sizeof(out), NULL, 0); + ret = Hash_DRBG_Generate(&drbg, out, (word32)sizeof(out), NULL, 0, NULL); if (ret != DRBG_NEED_RESEED) { WB_NOTE("Hash_DRBG_Generate did not signal DRBG_NEED_RESEED"); wb_fail = 1; @@ -448,14 +448,14 @@ static void wb_hash512_drbg_generate_reseed(void) } drbg.reseedCtr = 1; /* false side */ - ret = Hash512_DRBG_Generate(&drbg, out, (word32)sizeof(out), NULL, 0); + ret = Hash512_DRBG_Generate(&drbg, out, (word32)sizeof(out), NULL, 0, NULL); if (ret != DRBG_SUCCESS) { WB_NOTE("Hash512_DRBG_Generate (below interval) failed"); wb_fail = 1; } drbg.reseedCtr = WC_RESEED_INTERVAL; /* true side */ - ret = Hash512_DRBG_Generate(&drbg, out, (word32)sizeof(out), NULL, 0); + ret = Hash512_DRBG_Generate(&drbg, out, (word32)sizeof(out), NULL, 0, NULL); if (ret != DRBG_NEED_RESEED) { WB_NOTE("Hash512_DRBG_Generate did not signal DRBG_NEED_RESEED"); wb_fail = 1; diff --git a/wolfcrypt/src/curve25519.c b/wolfcrypt/src/curve25519.c index 9cf5cb08d72..13a7a98f234 100644 --- a/wolfcrypt/src/curve25519.c +++ b/wolfcrypt/src/curve25519.c @@ -1436,6 +1436,17 @@ int wc_curve25519_set_rng(curve25519_key* key, WC_RNG* rng) key->rng = rng; return 0; } + +/* Companion to wc_curve25519_set_rng(): detach the key's RNG association. + * Subsequent blinded operations then fail in the RNG service (BAD_FUNC_ARG + * on the NULL WC_RNG) until a new one is set. */ +int wc_curve25519_clear_rng(curve25519_key* key) +{ + if (key == NULL) + return BAD_FUNC_ARG; + key->rng = NULL; + return 0; +} #endif /* get key size */ diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 4609def586c..078b288e167 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -15101,6 +15101,28 @@ int wc_ecc_set_rng(ecc_key* key, WC_RNG* rng) return err; } +/* Companion to wc_ecc_set_rng(): detach the key's RNG association. + * Subsequent operations that require the key's RNG then fail with + * MISSING_RNG_E until a new one is set. */ +int wc_ecc_clear_rng(ecc_key* key) +{ + int err = 0; + +#ifdef ECC_TIMING_RESISTANT + if (key == NULL) { + err = BAD_FUNC_ARG; + } + else { + key->rng = NULL; + } +#else + (void)key; + /* report success, not an error if ECC_TIMING_RESISTANT is not defined */ +#endif + + return err; +} + #ifdef HAVE_ECC_ENCRYPT @@ -15760,6 +15782,9 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, /* Device for the ECIES callback and the KDF/AES/HMAC steps. It comes * only from the context; unset means software, or the CB_FIND finder. */ int eciesDevId = INVALID_DEVID; +#ifdef ECC_TIMING_RESISTANT + int lentRng = 0; /* ctx->rng lent to privKey for this op */ +#endif if (privKey == NULL || pubKey == NULL || msg == NULL || out == NULL || outSz == NULL) @@ -15848,8 +15873,14 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, return BUFFER_E; #ifdef ECC_TIMING_RESISTANT - if (ctx->rng != NULL && privKey->rng == NULL) + if (ctx->rng != NULL && privKey->rng == NULL) { + /* Lend the ctx's RNG to the key for the duration of this operation + * only. Restored to NULL before every subsequent return, so no + * borrowed pointer survives on the caller's key object after the + * call. */ privKey->rng = ctx->rng; + lentRng = 1; + } #endif #ifndef WOLFSSL_ECIES_OLD @@ -15859,23 +15890,42 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #else ret = wc_ecc_make_pub_ex(privKey, NULL, NULL); #endif - if (ret != 0) + if (ret != 0) { + #ifdef ECC_TIMING_RESISTANT + if (lentRng) + privKey->rng = NULL; + #endif return ret; + } } ret = wc_ecc_export_x963_ex(privKey, out, &pubKeySz, compressed); - if (ret != 0) + if (ret != 0) { + #ifdef ECC_TIMING_RESISTANT + if (lentRng) + privKey->rng = NULL; + #endif return ret; + } out += pubKeySz; #endif #ifdef WOLFSSL_SMALL_STACK sharedSecret = (byte*)XMALLOC(sharedSz, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); - if (sharedSecret == NULL) + if (sharedSecret == NULL) { + #ifdef ECC_TIMING_RESISTANT + if (lentRng) + privKey->rng = NULL; + #endif return MEMORY_E; + } keys = (byte*)XMALLOC(ECC_BUFSIZE, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); if (keys == NULL) { XFREE(sharedSecret, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); + #ifdef ECC_TIMING_RESISTANT + if (lentRng) + privKey->rng = NULL; + #endif return MEMORY_E; } #endif @@ -16160,6 +16210,11 @@ int wc_ecc_encrypt_ex(ecc_key* privKey, ecc_key* pubKey, const byte* msg, WC_FREE_VAR_EX(sharedSecret, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); WC_FREE_VAR_EX(keys, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); +#ifdef ECC_TIMING_RESISTANT + if (lentRng) + privKey->rng = NULL; +#endif + return ret; } @@ -16218,6 +16273,9 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, /* Device for the ECIES callback and the KDF/AES/HMAC steps. It comes * only from the context; unset means software, or the CB_FIND finder. */ int eciesDevId = INVALID_DEVID; +#ifdef ECC_TIMING_RESISTANT + int lentRng = 0; /* ctx->rng lent to privKey for this op */ +#endif if (privKey == NULL || msg == NULL || out == NULL || outSz == NULL) @@ -16347,8 +16405,14 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, } #ifdef ECC_TIMING_RESISTANT - if (ctx->rng != NULL && privKey->rng == NULL) + if (ctx->rng != NULL && privKey->rng == NULL) { + /* Lend the ctx's RNG to the key for the duration of this operation + * only. Restored to NULL before every subsequent return, so no + * borrowed pointer survives on the caller's key object after the + * call. */ privKey->rng = ctx->rng; + lentRng = 1; + } #endif #ifdef WOLFSSL_SMALL_STACK @@ -16357,6 +16421,10 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #ifndef WOLFSSL_ECIES_OLD if (pubKey == peerKey) wc_ecc_free(peerKey); + #endif + #ifdef ECC_TIMING_RESISTANT + if (lentRng) + privKey->rng = NULL; #endif return MEMORY_E; } @@ -16367,6 +16435,10 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, #ifndef WOLFSSL_ECIES_OLD if (pubKey == peerKey) wc_ecc_free(peerKey); + #endif + #ifdef ECC_TIMING_RESISTANT + if (lentRng) + privKey->rng = NULL; #endif return MEMORY_E; } @@ -16675,6 +16747,11 @@ int wc_ecc_decrypt(ecc_key* privKey, ecc_key* pubKey, const byte* msg, XFREE(keys, ctx->heap, DYNAMIC_TYPE_ECC_BUFFER); #endif +#ifdef ECC_TIMING_RESISTANT + if (lentRng) + privKey->rng = NULL; +#endif + return ret; } diff --git a/wolfcrypt/src/error.c b/wolfcrypt/src/error.c index 7a6e39e27d6..82261393a4a 100644 --- a/wolfcrypt/src/error.c +++ b/wolfcrypt/src/error.c @@ -760,6 +760,12 @@ const char* wc_GetErrorString(int error) case WRONG_TYPE_OBJECT_E: return "Object is wrong type for requested operation"; + case NEEDS_RECOVERY_E: + return "Object needs recovery before use"; + + case UNEXPECTED_STATE_E: + return "Object has unexpected state"; + case MAX_CODE_E: case WC_SPAN1_MIN_CODE_E: case MIN_CODE_E: diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index d7ae5ff465f..b41e35ce562 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -35,12 +35,12 @@ This library contains implementation for the random number generator. * WC_RNG_BLOCKING: Make RNG operations blocking default: off * WC_VERBOSE_RNG: Enable verbose RNG debug output default: off * WC_RNG_SEED_CB: Use custom seed callback function default: off - * WC_RNG_BANK_SUPPORT: Enable RNG bank (pre-generated) default: off - * random data support - * WOLFSSL_RNG_USE_FULL_SEED: Use full-length seed for DRBG default: off + * WC_HAVE_RNG_BANKREF: Enable RNG bank indirect RNG default: off + * support + * WOLFSSL_RNG_USE_FULL_SEED: Use full-length seed for DRBG default: off * WOLFSSL_GENSEED_FORTEST: Use deterministic seed for testing default: off * WARNING: not for production use - * WOLFSSL_KEEP_RNG_SEED_FD_OPEN: Keep /dev/random fd open default: off + * WOLFSSL_KEEP_RNG_SEED_FD_OPEN: Keep /dev/random fd open default: off * between seed operations * * Custom RNG Sources: @@ -135,7 +135,10 @@ This library contains implementation for the random number generator. #include -#ifdef WC_RNG_BANK_SUPPORT +#ifdef WC_HAVE_RNG_BANKREF + #if defined(HAVE_FIPS) && !defined(WOLFSSL_EXPERIMENTAL_SETTINGS) + #error WC_HAVE_RNG_BANKREF is unsupported in FIPS configurations. + #endif #include #endif #include @@ -297,6 +300,75 @@ This library contains implementation for the random number generator. #endif /* USE_WINDOWS_API */ #endif +/* RNG health states */ +#define DRBG_NOT_INIT WC_DRBG_NOT_INIT +#define DRBG_OK WC_DRBG_OK +#define DRBG_FAILED WC_DRBG_FAILED +#define DRBG_CONT_FAILED WC_DRBG_CONT_FAILED + +/* enforcement helper for WC_RNG_LOCK_REQUIRED and + * WC_RNG_LOCK_ENTROPY_INVALIDATED: instance-consuming public APIs call this on + * entry. */ +static WC_MAYBE_UNUSED WC_INLINE int rng_lock_required_check(WC_RNG* rng) +{ + if (rng == NULL) + return BAD_FUNC_ARG; +#ifndef WC_RNG_HAVE_LOCK + return 0; +#else /* WC_RNG_HAVE_LOCK */ + else { + #ifdef WOLFSSL_NO_ATOMICS + WC_RNG_lock_t lock_state = rng->lock; + #else + WC_RNG_lock_arg_t lock_state = WOLFSSL_ATOMIC_LOAD(rng->lock); + #endif + if ((lock_state & WC_RNG_LOCK_REQUIRED) && + (! (lock_state & WC_RNG_LOCK_HELD))) + { + return OBJECT_NOT_LOCKED_E; + } + /* WC_RNG_LOCK_ENTROPY_INVALIDATED deliberately does not gate entry + * here: on lock-required instances the lock API refuses new leases, + * and on unlocked instances the saturated reseedCtr (see + * wc_RNG_invalidate_entropy()) forces a credited reseed -- which + * clears the flag -- before the next generate. Refusing here would + * brick unlocked instances, with no path to recovery. */ + return 0; + } +#endif /* WC_RNG_HAVE_LOCK */ +} + +/* Read-only accessor for the RNG health status (enum wc_RngHealthState). + * Returns the status, or BAD_FUNC_ARG for a NULL rng. */ +int wc_RNG_GetStatus(const WC_RNG* rng) +{ + if (rng == NULL) + return BAD_FUNC_ARG; + return (int)rng->status; +} + +/* Returns 1 if rng has an instantiated DRBG, else 0. An in-service WC_RNG can + * lack one: _InitRng() in HAVE_INTEL_RDRAND configurations bypasses DRBG + * instantiation when the CPU has RDRAND (). */ +int wc_RNG_DRBG_Present(const WC_RNG* rng) +{ + if (rng == NULL) + return 0; +#if defined(HAVE_HASHDRBG) && !defined(NO_SHA256) + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL)) + return 1; +#endif +#if defined(HAVE_HASHDRBG) && defined(WOLFSSL_DRBG_SHA512) + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL)) + return 1; +#endif + return 0; +} + +#ifdef WC_RNG_HAVE_POOL +static WARN_UNUSED_RESULT int PoolPurge(WC_RNG* rng); +#endif + /* Start NIST DRBG code */ #ifdef HAVE_HASHDRBG @@ -336,20 +408,17 @@ enum { CONST_NUM_ERR_DRBG_CONT_FAILURE = DRBG_CONT_FAILURE, CONST_NUM_ERR_DRBG_NO_SEED_CB = DRBG_NO_SEED_CB }; - #define DRBG_FAILURE WC_ERR_TRACE(DRBG_FAILURE) - #define DRBG_NEED_RESEED WC_ERR_TRACE(DRBG_NEED_RESEED) - #define DRBG_CONT_FAILURE WC_ERR_TRACE(DRBG_CONT_FAILURE) - #define DRBG_NO_SEED_CB WC_ERR_TRACE(DRBG_NO_SEED_CB) - #define WC_DRBG_FAILED WC_ERR_TRACE(WC_DRBG_FAILED) - #define WC_DRBG_CONT_FAILED WC_ERR_TRACE(WC_DRBG_CONT_FAILED) + /* DRBG_SUCCESS needs to be macroized to avoid "enumerated and + * non-enumerated type in conditional expression" in C++. */ + #define DRBG_SUCCESS (byte)DRBG_SUCCESS + #define DRBG_FAILURE (byte)WC_ERR_TRACE(DRBG_FAILURE) + #define DRBG_NEED_RESEED (byte)WC_ERR_TRACE(DRBG_NEED_RESEED) + #define DRBG_CONT_FAILURE (byte)WC_ERR_TRACE(DRBG_CONT_FAILURE) + #define DRBG_NO_SEED_CB (byte)WC_ERR_TRACE(DRBG_NO_SEED_CB) + #define WC_DRBG_FAILED (byte)WC_ERR_TRACE(WC_DRBG_FAILED) + #define WC_DRBG_CONT_FAILED (byte)WC_ERR_TRACE(WC_DRBG_CONT_FAILED) #endif -/* RNG health states */ -#define DRBG_NOT_INIT WC_DRBG_NOT_INIT -#define DRBG_OK WC_DRBG_OK -#define DRBG_FAILED WC_DRBG_FAILED -#define DRBG_CONT_FAILED WC_DRBG_CONT_FAILED - #define SEED_SZ WC_DRBG_SEED_SZ #define MAX_SEED_SZ WC_DRBG_MAX_SEED_SZ @@ -378,13 +447,15 @@ typedef struct DRBG_internal DRBG_internal; #ifdef WOLFSSL_DRBG_SHA512 typedef struct DRBG_SHA512_internal DRBG_SHA512_internal; -static int Hash512_DRBG_Reseed(DRBG_SHA512_internal* drbg, const byte* seed, - word32 seedSz, +static WARN_UNUSED_RESULT int Hash512_DRBG_Reseed(DRBG_SHA512_internal* drbg, + const byte* seed, word32 seedSz, const byte* additional, word32 additionalSz); -static int Hash512_DRBG_Generate(DRBG_SHA512_internal* drbg, byte* out, - word32 outSz, - const byte* additional, word32 additionalSz); -static int Hash512_DRBG_Instantiate(DRBG_SHA512_internal* drbg, +static WARN_UNUSED_RESULT int Hash512_DRBG_Generate(DRBG_SHA512_internal* drbg, + byte* out, word32 outSz, + const byte* additional, word32 additionalSz, + WC_RNG* rng); +static WARN_UNUSED_RESULT int Hash512_DRBG_Instantiate( + DRBG_SHA512_internal* drbg, const byte* seed, word32 seedSz, const byte* nonce, word32 nonceSz, const byte* perso, word32 persoSz, @@ -467,7 +538,7 @@ int wc_DrbgState_MutexFree(void) return 0; } -static int LockDrbgState(void) +static WARN_UNUSED_RESULT int LockDrbgState(void) { #ifndef SINGLE_THREADED return wc_LockMutex(&drbgStateMutex); @@ -487,11 +558,12 @@ static int UnlockDrbgState(void) #endif /* !HAVE_SELFTEST && (!HAVE_FIPS || FIPS v7+) */ -static int wc_RNG_HealthTestLocal(WC_RNG* rng, int reseed, void* heap, - int devId); +static WARN_UNUSED_RESULT int wc_RNG_HealthTestLocal(WC_RNG* rng, int reseed, + void* heap, int devId); #ifdef WOLFSSL_DRBG_SHA512 -static int wc_RNG_HealthTest_SHA512_ex_internal(DRBG_SHA512_internal* drbg, +static WARN_UNUSED_RESULT int wc_RNG_HealthTest_SHA512_ex_internal( + DRBG_SHA512_internal* drbg, int reseed, const byte* nonce, word32 nonceSz, const byte* perso, word32 persoSz, const byte* seedA, word32 seedASz, @@ -511,7 +583,8 @@ static int wc_RNG_HealthTest_SHA512_ex_internal(DRBG_SHA512_internal* drbg, /* Hash Derivation Function */ /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash_df(DRBG_internal* drbg, byte* out, word32 outSz, byte type, +static WARN_UNUSED_RESULT int Hash_df(DRBG_internal* drbg, byte* out, + word32 outSz, byte type, const byte* inA, word32 inASz, const byte* inB, word32 inBSz, const byte* inC, word32 inCSz) @@ -640,8 +713,10 @@ static int Hash_df(DRBG_internal* drbg, byte* out, word32 outSz, byte type, } /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash_DRBG_Reseed(DRBG_internal* drbg, const byte* seed, word32 seedSz, - const byte* additional, word32 additionalSz) +static WARN_UNUSED_RESULT int Hash256_DRBG_Reseed(DRBG_internal* drbg, + const byte* seed, + word32 seedSz, const byte* additional, + word32 additionalSz) { int ret; WC_DECLARE_VAR(newV, byte, DRBG_SEED_LEN, 0); @@ -658,7 +733,7 @@ static int Hash_DRBG_Reseed(DRBG_internal* drbg, const byte* seed, word32 seedSz #endif XMEMSET(newV, 0, DRBG_SEED_LEN); #ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Add("Hash_DRBG_Reseed newV", newV, DRBG_SEED_LEN); + wc_MemZero_Add("Hash256_DRBG_Reseed newV", newV, DRBG_SEED_LEN); #endif ret = Hash_df(drbg, newV, DRBG_SEED_LEN, drbgReseed, @@ -686,68 +761,528 @@ static int Hash_DRBG_Reseed(DRBG_internal* drbg, const byte* seed, word32 seedSz #ifdef WC_VERBOSE_RNG if (ret != 0) - WOLFSSL_DEBUG_PRINTF("ERROR: Hash_DRBG_Reseed failed with err %d.", + WOLFSSL_DEBUG_PRINTF("ERROR: Hash256_DRBG_Reseed failed with err %d.", ret); #endif return ret; } -#endif /* !NO_SHA256 - close before wc_RNG_DRBG_Reseed (dual-DRBG-aware) - * and array_add_one (shared utility) which both must - * remain available to SHA-512-only builds */ +#endif /* !NO_SHA256 */ -/* Returns: DRBG_SUCCESS and DRBG_FAILURE or BAD_FUNC_ARG on fail */ -int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz) +#ifdef WC_RNG_HAVE_NEXT_SEED +/* Purge the credited next-seed aperture. A plain store would race an in-flight + * producer: the producer's publish must lose against a purge, never the + * reverse, or material generated before a state-invalidation event could + * surface READY after it -- exactly the resurrection the provenance guarantee + * forbids. A producer mid-fill (PRODUCING) owns the buffer, so the purge only + * repaints the sentinel (PRODUCING -> PURGED); the producer's failed + * publish-CAS observes the repaint and reopens the aperture EMPTY (see + * NextSeedProducerRelease()) -- the sentinel alone suppresses the pre-event + * material; abandoned seed-aperture buffers are zeroized as explained below. + * All other states purge directly to EMPTY. (The uncredited stir aperture + * keeps its plain-store purge: stirs carry no claims, so resurrection there is + * benign by the three-no-ops doctrine.) + * + * Zeroization doctrine for purges: abandonment here is event-driven (fork, + * VM clone/resume, credited reseed), and the event that abandons bytes in + * this lineage is the same event that created a sibling lineage that may + * consume its identical copy of them. Abandoned-here can be consumed-there, + * so purged material is treated as CSP and wiped -- under ownership only: + * READY or parked-fill words are claimed _CONSUMING first (the same CAS a + * consumer uses; producers claim only non-negative words, so the claim + * cannot collide), then wiped, then reopened EMPTY. A _CONSUMING holder's + * material is left to that consumer's own burn-before-release, and a + * _PRODUCING holder's to its unwind (see NextSeedProducerRelease()). + * Contrast the health-test burn arm, which stays sentinel-only: RCT/APT + * are deterministic on the bytes, so every sibling rejects the same + * material identically and no lineage can have consumed it. + */ +static WARN_UNUSED_RESULT int NextSeedPurge(wolfSSL_Atomic_Int *lenp, + byte *seed_buf, + word32 seed_buf_sz) { - if (rng == NULL || seed == NULL) { + int ret; + WC_ATOMIC_INT_ARG cur_len, want_len; + + WC_CAS_WITH_RETRY_BEGIN_INIT_CUR(lenp, cur_len, ret) { + if (cur_len == WC_DRBG_NEXT_SEED_PURGED) + return ALREADY_E; /* already handed off to a producer's unwind. */ + if ((cur_len == WC_DRBG_NEXT_SEED_READY) || (cur_len > 0)) { + /* Published or parked bytes with no owner: claim, wipe as + * owner, reopen. */ + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Int_CompareExchange, + lenp, cur_len, + WC_DRBG_NEXT_SEED_CONSUMING, ret); + ForceZero(seed_buf, seed_buf_sz); + WOLFSSL_ATOMIC_STORE(*lenp, WC_DRBG_NEXT_SEED_EMPTY); + } + else { + want_len = (cur_len == WC_DRBG_NEXT_SEED_PRODUCING) ? + WC_DRBG_NEXT_SEED_PURGED : WC_DRBG_NEXT_SEED_EMPTY; + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Int_CompareExchange, + lenp, cur_len, want_len, ret); + } + } WC_CAS_WITH_RETRY_END; + + /* percolate the CAS result -- if the loop was aborted by user logic, the + * caller needs to know that the purge failed. */ + return ret; +} + +/* Release a producer claim (PRODUCING) on the credited aperture, + * installing val (a fill offset, the full length, READY, or EMPTY). + * Returns 0 on release, else BUSY_E: the release CAS can fail for + * exactly one reason -- a concurrent NextSeedPurge() repainted the + * claim PURGED (producers cannot claim a PRODUCING word, consumers + * only exchange from READY, and the purge is the sole writer against + * a claim). The producer's material then predates the invalidation + * event and must not surface: the aperture reopens EMPTY, which is + * the whole suppression -- an EMPTY aperture is never consumed, and + * the next fill overwrites from offset zero. The bytes themselves are + * wiped before the reopen, while this producer still owns the buffer: + * the purge that repainted the claim marks an event that created a + * sibling lineage, and the sibling's copy of a completed fill may be + * consumed there (see the doctrine at NextSeedPurge()). */ +static int NextSeedProducerRelease(wolfSSL_Atomic_Int *lenp, + byte *seed_buf, word32 seed_buf_sz, + WC_ATOMIC_INT_ARG val) +{ + WC_ATOMIC_INT_ARG expected = WC_DRBG_NEXT_SEED_PRODUCING; + if (wolfSSL_Atomic_Int_CompareExchange(lenp, &expected, val)) + return 0; + ForceZero(seed_buf, seed_buf_sz); + WOLFSSL_ATOMIC_STORE(*lenp, WC_DRBG_NEXT_SEED_EMPTY); + return BUSY_E; +} +#endif /* WC_RNG_HAVE_NEXT_SEED */ + +/* in_bracketed_consume: nonzero when the caller feeds this reseed from its own + * _CONSUMING claim on the credited aperture (wc_RNG_DRBG_NextSeedNow_Nonce()). + * The invalidated-entry purges below must then exempt that aperture: purging + * it would steal the caller's claim and defeat its release CAS, converting + * the caller's own recovery into a false epoch violation. An external + * (event) purge still repaints the claim, which is exactly what the + * caller's release CAS exists to detect. */ +static WARN_UNUSED_RESULT int Hash_DRBG_Reseed(WC_RNG* rng, const byte* seed, + word32 seedSz, + const byte* additional, word32 additionalSz, + int in_bracketed_consume) +{ + int ret; +#ifdef WC_RNG_HAVE_LOCK + WC_RNG_lock_arg_t cur_lock; +#endif + + if (rng == NULL) return BAD_FUNC_ARG; + +#ifdef WC_RNG_HAVE_LOCK + cur_lock = WOLFSSL_ATOMIC_LOAD(rng->lock); +#endif + +#ifdef WC_RNG_HAVE_LOCK + /* Never allow an undersized seed to clear an invalidated state. */ + if ((cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) && + (seedSz < WC_DRBG_SEED_SZ)) + { + /* Short-circuit return in case the _RECOVERING mutex below would have + * failed. */ + return NEEDS_RECOVERY_E; + } + + /* Iff _ENTROPY_INVALIDATED, assert the _ENTROPY_RECOVERING bit now -- if we + * are re-invalidated in the meantime, it will have been cleared by the + * invalidation at exit time, signaling that we are still invalidated. */ + + WC_CAS_WITH_RETRY_BEGIN(&rng->lock, cur_lock, ret) { + if (! (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED)) { + /* Not invalidated -- no recovery mutex needed. Explicit + * success: BEGIN initializes ret to a failure code. */ + ret = 0; + break; + } + if (cur_lock & WC_RNG_LOCK_ENTROPY_RECOVERING) { + /* Must short-circuit return here, so that we don't improperly clear + * the _RECOVERING bit. */ + return BUSY_E; + } + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &rng->lock, cur_lock, + cur_lock | WC_RNG_LOCK_ENTROPY_RECOVERING, + ret); + /* We now have the _RECOVERING mutex -- record that fact. */ + cur_lock |= WC_RNG_LOCK_ENTROPY_RECOVERING; + } WC_CAS_WITH_RETRY_END; + if (ret != 0) { + /* Aborted acquire (a port's retry clause): the lock word is + * untouched and no state has moved -- bail before the purges, + * which must not run unserialized against a concurrent + * recovery. */ + return ret; + } +#endif /* WC_RNG_HAVE_LOCK */ + +#ifdef WC_RNG_HAVE_POOL + /* Purge the pool when recovering from invalidation, or if reseeding without + * locks (i.e. without an internal mechanism for tracking invalidation). + * + * The reseed counter tracks generates since the last reseed, and every + * pooled byte is itself a generate that incremented it. So the pool's + * contents are within the budget the counter enforces -- indeed they were + * authorized by the same accounting that later demanded the + * reseed. Discarding them treats output as retroactively over-budget when + * it was under-budget when produced. + */ + #ifdef WC_RNG_HAVE_LOCK + if (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) + #endif + { + ret = PoolPurge(rng); + if ((ret != 0) && + (ret != WC_NO_ERR_TRACE(ALREADY_E))) + { + goto out; + } } +#endif /* WC_RNG_HAVE_POOL */ #ifndef NO_SHA256 if (rng->drbgType == WC_DRBG_SHA256) { - if (rng->drbg == NULL) { + DRBG_internal* drbg = (DRBG_internal *)rng->drbg; + + if (drbg == NULL) { #if defined(HAVE_INTEL_RDSEED) || defined(HAVE_INTEL_RDRAND) if (IS_INTEL_RDRAND(intel_flags)) { /* using RDRAND not DRBG, so return success */ - return 0; + ret = 0; + goto out; } #endif - return BAD_FUNC_ARG; + ret = BAD_FUNC_ARG; + goto out; + } + +#if defined(WC_RNG_HAVE_LOCK) && defined(WC_RNG_HAVE_NEXT_SEED) + if ((cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) && + (! in_bracketed_consume)) + { + ret = NextSeedPurge(&drbg->nextSeedLen, drbg->nextSeed, + (word32)sizeof(drbg->nextSeed)); + if ((ret != 0) && + (ret != WC_NO_ERR_TRACE(ALREADY_E))) + { + goto out; + } + /* the uncredited stir aperture is purged too, for provenance + * uniformity; best-effort (an in-flight depositor may + * resurrect a partial fill -- benign, stirs carry no + * divergence burden), and never zeroized (racy, and + * interleaved entropy of compatible provenance is harmless). + */ + WOLFSSL_ATOMIC_STORE(drbg->nextStirLen, + WC_DRBG_NEXT_SEED_EMPTY); + } +#else + (void)in_bracketed_consume; +#endif + + ret = Hash256_DRBG_Reseed(drbg, seed, seedSz, additional, additionalSz); +#ifdef WC_RNG_DEBUG_STATS + if (ret == 0) { + ++rng->_stats_reseeds; } - return Hash_DRBG_Reseed((DRBG_internal *)rng->drbg, seed, seedSz, - NULL, 0); +#endif + goto out; } #endif + #ifdef WOLFSSL_DRBG_SHA512 if (rng->drbgType == WC_DRBG_SHA512) { - if (rng->drbg512 == NULL) { + DRBG_SHA512_internal* drbg512 = + (DRBG_SHA512_internal *)rng->drbg512; + + if (drbg512 == NULL) { #if defined(HAVE_INTEL_RDSEED) || defined(HAVE_INTEL_RDRAND) if (IS_INTEL_RDRAND(intel_flags)) { /* using RDRAND not DRBG, so return success */ - return 0; + ret = 0; + goto out; } #endif - return BAD_FUNC_ARG; + ret = BAD_FUNC_ARG; + goto out; + } + +#if defined(WC_RNG_HAVE_LOCK) && defined(WC_RNG_HAVE_NEXT_SEED) + if ((cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) && + (! in_bracketed_consume)) + { + ret = NextSeedPurge(&drbg512->nextSeedLen, drbg512->nextSeed, + (word32)sizeof(drbg512->nextSeed)); + if ((ret != 0) && + (ret != WC_NO_ERR_TRACE(ALREADY_E))) + { + goto out; + } + /* see the SHA-256 arm re best-effort and no-zeroize. */ + WOLFSSL_ATOMIC_STORE(drbg512->nextStirLen, + WC_DRBG_NEXT_SEED_EMPTY); + } +#endif + + ret = Hash512_DRBG_Reseed(drbg512, seed, seedSz, + additional, additionalSz); +#ifdef WC_RNG_DEBUG_STATS + if (ret == 0) { + ++rng->_stats_reseeds; } - return Hash512_DRBG_Reseed((DRBG_SHA512_internal *)rng->drbg512, - seed, seedSz, NULL, 0); +#endif + goto out; } #endif /* No DRBG type matched; if using RDRAND, that's OK */ -#if defined(HAVE_INTEL_RDSEED) || defined(HAVE_INTEL_RDRAND) + #if defined(HAVE_INTEL_RDSEED) || defined(HAVE_INTEL_RDRAND) if (IS_INTEL_RDRAND(intel_flags)) { - return 0; + /* using RDRAND not DRBG, so return success */ + ret = 0; + goto out; + } + #endif + + ret = WRONG_TYPE_OBJECT_E; + + out: + +#ifdef WC_RNG_HAVE_LOCK + /* The _RECOVERING bit functions as a mutex -- if it's set here, *we* + * set it, and must clear it. + * + * If we were invalidated on entry, and reseed succeeded, and provided we + * weren't re-invalidated in the meantime, clear the _INVALIDATED bit + * alongside the _RECOVERING bit. + * + * Otherwise, just clear the _RECOVERING bit (releasing the recovery mutex), + * and return with the success or failure code from above. + */ + if ((ret == 0) && (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED)) { + WC_CAS_WITH_RETRY_BEGIN(&rng->lock, cur_lock, ret) { + if (! (cur_lock & WC_RNG_LOCK_ENTROPY_RECOVERING)) { + ret = NEEDS_RECOVERY_E; + break; + } + WC_CAS_WITH_RETRY_LOOP_FOREVER( + wolfSSL_Atomic_Uint_CompareExchange, &rng->lock, cur_lock, + cur_lock & ~(WC_RNG_LOCK_ENTROPY_INVALIDATED | + WC_RNG_LOCK_ENTROPY_RECOVERING), + ret); + } WC_CAS_WITH_RETRY_END; + if ((ret != 0) && (ret != WC_NO_ERR_TRACE(NEEDS_RECOVERY_E))) { + /* Aborted clear (a port's retry clause) strands _RECOVERING with no + * in-band path back: every future recovery would refuse BUSY_E. + * Condemn -- in kernel, DRBG_FAILED has an automated exit (the + * entropy daemon's recovery pass), and the owner can always reinit. + * A wedged mutex bit would have no visible indication that reinit + * is required. + */ + rng->status = DRBG_FAILED; + } + } + else if (cur_lock & WC_RNG_LOCK_ENTROPY_RECOVERING) { + /* ret carries the reseed's status here and must survive, so the + * release gets its own result variable. */ + int release_ret; + WC_CAS_WITH_RETRY_BEGIN(&rng->lock, cur_lock, release_ret) { + WC_CAS_WITH_RETRY_LOOP_FOREVER( + wolfSSL_Atomic_Uint_CompareExchange, &rng->lock, cur_lock, + cur_lock & ~WC_RNG_LOCK_ENTROPY_RECOVERING, release_ret); + } WC_CAS_WITH_RETRY_END; + if (release_ret != 0) { + /* Same stranded-_RECOVERING condemnation as the clear arm + * above; the reseed's own status still wins the return. */ + rng->status = DRBG_FAILED; + if (ret == 0) + ret = release_ret; + } + } +#endif /* WC_RNG_HAVE_LOCK */ + + return ret; +} + +int wc_RNG_DRBG_Reseed_Nonce(WC_RNG* rng, const byte* seed, word32 seedSz, + const byte *nonce, word32 nonceSz) +{ + int ret; + + if (rng == NULL || seed == NULL) { + return BAD_FUNC_ARG; + } + + /* Hash_df() skips a NULL input regardless of its stated length, so + * without this the caller's additional input would be dropped and + * success reported. Matches _InitRng() and ReseedRBGC(). */ + if ((nonce == NULL) && (nonceSz > 0)) + return BAD_FUNC_ARG; + + ret = rng_lock_required_check(rng); + if (ret != 0) + return ret; + + /* A condemned instance does not accept a credited reseed: DRBG_FAILED's + * designed exit is wc_FreeRng()/wc_InitRng() (or the daemon's recovery + * pass), not in-place resurrection that would reset the counter, purge + * the pool, and clear quarantine on unvetted authority. */ + if (rng->status != DRBG_OK) + return RNG_FAILURE_E; + +#ifdef WC_RNG_HAVE_LOCK + /* Never allow an undersized seed to clear an invalidated state, and if + * invalidated, always assume potentially primary seed data -- test it with + * wc_RNG_TestSeed(). */ + if (WOLFSSL_ATOMIC_LOAD(rng->lock) & WC_RNG_LOCK_ENTROPY_INVALIDATED) { + if (seedSz < WC_DRBG_SEED_SZ) + return NEEDS_RECOVERY_E; + ret = wc_RNG_TestSeed(seed, seedSz); + if (ret != 0) + return ret; + } +#endif /* WC_RNG_HAVE_LOCK */ + + ret = Hash_DRBG_Reseed(rng, seed, seedSz, nonce, nonceSz, + 0 /* in_bracketed_consume */); +#ifdef WC_RNG_HAVE_RBGC + if (ret == 0) { + /* User-supplied entropy is of unknown provenance. In RBGC builds, + * represent that fact using WC_RNG_RBGC_USER_SEED_STRATUM, preventing confusion with RNGs seeded by the ESV . */ + rng->RBGCStratum = WC_RNG_RBGC_USER_SEED_STRATUM; + } +#endif + + return ret; +} + +int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz) { + return wc_RNG_DRBG_Reseed_Nonce(rng, seed, seedSz, NULL, 0); +} + +#ifdef WC_RNG_HAVE_RBGC + +int wc_RNG_DRBG_GetRBGCStratum(const WC_RNG* rng) +{ + if (rng) + return rng->RBGCStratum; + else + return BAD_FUNC_ARG; +} + +#ifdef WC_RNG_HAVE_NEXT_SEED +int wc_RNG_DRBG_GetNextSeedRBGCStratum(const WC_RNG* rng) +{ + if (rng == NULL) + return BAD_FUNC_ARG; + + /* Race-free via the NextSeed aperture protocol. If called with rng locked, + * and ->nextSeedLen == WC_DRBG_NEXT_SEED_READY, then competing producers + * and consumers are all excluded, unambiguously marking + * ->nextSeedRBGCStratum as strictly reliable and stable. nextSeedLen + * functions as the synchronizer -- the producer writes + * ->nextSeedRBGCStratum before publishing WC_DRBG_NEXT_SEED_READY to + * nextSeedLen with release semantics. + */ + +#ifndef NO_SHA256 + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL)) { + if (WOLFSSL_ATOMIC_LOAD(((const DRBG_internal*)rng->drbg)->nextSeedLen) + != WC_DRBG_NEXT_SEED_READY) + { + return NOT_READY_E; + } + else + return ((const DRBG_internal *)rng->drbg)->nextSeedRBGCStratum; } #endif +#ifdef WOLFSSL_DRBG_SHA512 + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL)) { + if (WOLFSSL_ATOMIC_LOAD(((const DRBG_SHA512_internal*)rng->drbg512)->nextSeedLen) + != WC_DRBG_NEXT_SEED_READY) + { + return NOT_READY_E; + } + else + return ((const DRBG_SHA512_internal *)rng->drbg512)->nextSeedRBGCStratum; + } +#endif + + return BAD_FUNC_ARG; +} +#endif /* WC_RNG_HAVE_NEXT_SEED */ +#endif /* WC_RNG_HAVE_RBGC */ + +/* Read-only accessor for the DRBG reseed counter. When no DRBG is + * instantiated (see wc_RNG_DRBG_Present()) there is no counter; *reseedCtr + * is set to 0 -- never due for reseed -- and 0 is returned. */ +int wc_RNG_DRBG_GetReseedCtr(const WC_RNG* rng, + wc_drbg_reseed_ctr_t* reseedCtr) +{ + if ((rng == NULL) || (reseedCtr == NULL)) + return BAD_FUNC_ARG; + if (! wc_RNG_DRBG_Present(rng)) + return WRONG_TYPE_OBJECT_E; + if (rng->status != DRBG_OK) + return BAD_STATE_E; +#ifndef NO_SHA256 + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL)) { + *reseedCtr = ((const DRBG_internal *)rng->drbg)->reseedCtr; + return 0; + } +#endif +#ifdef WOLFSSL_DRBG_SHA512 + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL)) { + *reseedCtr = (wc_drbg_reseed_ctr_t) + ((const DRBG_SHA512_internal *)rng->drbg512)->reseedCtr; + return 0; + } +#endif return BAD_FUNC_ARG; } +#if defined(WC_RESEED_INTERVAL) && !defined(WORD64_AVAILABLE) + wc_static_assert((WC_RESEED_INTERVAL) <= 0xFFFFFFFFUL); +#endif + +/* Mark rng due for reseed: the next generate operation reseeds from the + * module's built-in or registered seed source before producing output (see also + * wc_RNG_DRBG_Reseed_Now()). This can only shorten the current seed's + * remaining lifetime, never extend it. When no DRBG is instantiated (RDRAND et + * al.) commanded reseed is not supported and the call returns + * WRONG_TYPE_OBJECT_E. */ +int wc_RNG_DRBG_ScheduleReseed(WC_RNG* rng) +{ + if (rng == NULL) + return BAD_FUNC_ARG; +#ifndef NO_SHA256 + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL)) { + ((DRBG_internal *)rng->drbg)->reseedCtr = + (wc_drbg_reseed_ctr_t)WC_RESEED_INTERVAL; + return 0; + } +#endif +#ifdef WOLFSSL_DRBG_SHA512 + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL)) { + ((DRBG_SHA512_internal *)rng->drbg512)->reseedCtr = + (word64)WC_RESEED_INTERVAL; + return 0; + } +#endif + return WRONG_TYPE_OBJECT_E; +} + /* Generic byte-array helper -- shared by both SHA-256 and SHA-512 DRBG * cores. Lives outside the NO_SHA256 guard so SHA-512-only builds - * still link. */ + * still build. */ static WC_INLINE void array_add_one(byte* data, word32 dataSz) { int i; @@ -760,7 +1295,8 @@ static WC_INLINE void array_add_one(byte* data, word32 dataSz) #ifndef NO_SHA256 /* re-open SHA-256 Hash_DRBG core */ /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash_gen(DRBG_internal* drbg, byte* out, word32 outSz, const byte* V) +static WARN_UNUSED_RESULT int Hash_gen(DRBG_internal* drbg, byte* out, + word32 outSz, const byte* V) { int ret = WC_NO_ERR_TRACE(DRBG_FAILURE); word32 i; @@ -898,9 +1434,12 @@ static WC_INLINE void array_add(byte* d, word32 dLen, const byte* s, word32 sLen #ifndef NO_SHA256 /* re-open SHA-256 Hash_DRBG core */ /* Returns: DRBG_SUCCESS, DRBG_NEED_RESEED, or DRBG_FAILURE */ -static int Hash_DRBG_Generate(DRBG_internal* drbg, byte* out, word32 outSz, - const byte* additional, word32 additionalSz) +static WARN_UNUSED_RESULT int Hash_DRBG_Generate(DRBG_internal* drbg, + byte* out, word32 outSz, + const byte* additional, word32 additionalSz, + WC_RNG* rng) { + int state_mutated = 0; int ret; #ifdef WOLFSSL_SMALL_STACK_CACHE wc_Sha256* sha = &drbg->sha256; @@ -992,6 +1531,7 @@ static int Hash_DRBG_Generate(DRBG_internal* drbg, byte* out, word32 outSz, if (ret == 0) { array_add(drbg->V, sizeof(drbg->V), digest, WC_SHA256_DIGEST_SIZE); + state_mutated = 1; } else { ForceZero(digest, WC_SHA256_DIGEST_SIZE); @@ -1074,11 +1614,21 @@ static int Hash_DRBG_Generate(DRBG_internal* drbg, byte* out, word32 outSz, } #endif + /* Condemn iff the failure landed after the first state write (the + * step-2 V add): V then carries a half-applied transition that no + * in-band operation re-validates -- latch-or-condemn. Failures + * before the seam leave V/C/reseedCtr intact and stay retryable. + * (A bare reseedCtr++ on a pre-seam failure is benign: + * self-consistent state, one interval slot burned.) */ + if ((ret != 0) && state_mutated && (rng != NULL)) + rng->status = DRBG_FAILED; + return (ret == 0) ? DRBG_SUCCESS : DRBG_FAILURE; } /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash_DRBG_Init(DRBG_internal* drbg, const byte* seed, word32 seedSz, +static WARN_UNUSED_RESULT int Hash_DRBG_Init(DRBG_internal* drbg, + const byte* seed, word32 seedSz, const byte* nonce, word32 nonceSz, const byte* perso, word32 persoSz) { @@ -1101,14 +1651,20 @@ static int Hash_DRBG_Init(DRBG_internal* drbg, const byte* seed, word32 seedSz, } /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash_DRBG_Instantiate(DRBG_internal* drbg, const byte* seed, - word32 seedSz, const byte* nonce, - word32 nonceSz, const byte* perso, - word32 persoSz, void* heap, int devId) +static WARN_UNUSED_RESULT int Hash_DRBG_Instantiate(DRBG_internal* drbg, + const byte* seed, word32 seedSz, + const byte* nonce, word32 nonceSz, + const byte* perso, word32 persoSz, + void* heap, int devId) { int ret = WC_NO_ERR_TRACE(DRBG_FAILURE); XMEMSET(drbg, 0, sizeof(DRBG_internal)); +#ifdef WC_RNG_HAVE_NEXT_SEED + wolfSSL_Atomic_Int_Init(&drbg->nextSeedLen, WC_DRBG_NEXT_SEED_EMPTY); + wolfSSL_Atomic_Int_Init(&drbg->nextStirLen, + WC_DRBG_NEXT_SEED_EMPTY); +#endif drbg->heap = heap; #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) drbg->devId = devId; @@ -1156,14 +1712,14 @@ static int Hash_DRBG_Uninstantiate(DRBG_internal* drbg) /* ====================================================================== */ /* SHA-512 Hash_DRBG (SP 800-90A Rev 1, Table 2) */ -/* */ -/* Internal state (V, C): seedlen = 888 bits = 111 bytes each */ -/* Output block length: 512 bits = 64 bytes (WC_SHA512_DIGEST_SIZE) */ -/* Security strength: 256 bits */ -/* */ -/* NOTE: The raw entropy seed gathered at instantiation / reseed is */ +/* */ +/* Internal state (V, C): seedlen = 888 bits = 111 bytes each */ +/* Output block length: 512 bits = 64 bytes (WC_SHA512_DIGEST_SIZE) */ +/* Security strength: 256 bits */ +/* */ +/* NOTE: The raw entropy seed gathered at instantiation / reseed is */ /* WC_DRBG_SEED_SZ (1024 bits in FIPS builds), NOT seedlen. We overseed */ -/* to tolerate weak entropy sources. Hash_df then compresses the seed */ +/* to tolerate weak entropy sources. Hash_df then compresses the seed */ /* material down to the 888-bit V and derives C from V. See random.h. */ /* ====================================================================== */ #ifdef WOLFSSL_DRBG_SHA512 @@ -1172,7 +1728,8 @@ static int Hash_DRBG_Uninstantiate(DRBG_internal* drbg) /* Hash Derivation Function using SHA-512 */ /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash512_df(DRBG_SHA512_internal* drbg, byte* out, word32 outSz, +static WARN_UNUSED_RESULT int Hash512_df(DRBG_SHA512_internal* drbg, byte* out, + word32 outSz, byte type, const byte* inA, word32 inASz, const byte* inB, word32 inBSz, @@ -1308,8 +1865,8 @@ static int Hash512_df(DRBG_SHA512_internal* drbg, byte* out, word32 outSz, } /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash512_DRBG_Reseed(DRBG_SHA512_internal* drbg, const byte* seed, - word32 seedSz, +static WARN_UNUSED_RESULT int Hash512_DRBG_Reseed(DRBG_SHA512_internal* drbg, + const byte* seed, word32 seedSz, const byte* additional, word32 additionalSz) { int ret; @@ -1358,8 +1915,9 @@ static int Hash512_DRBG_Reseed(DRBG_SHA512_internal* drbg, const byte* seed, } /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash512_gen(DRBG_SHA512_internal* drbg, byte* out, word32 outSz, - const byte* V) +static WARN_UNUSED_RESULT int Hash512_gen(DRBG_SHA512_internal* drbg, + byte* out, word32 outSz, + const byte* V) { int ret = WC_NO_ERR_TRACE(DRBG_FAILURE); word32 i; @@ -1456,10 +2014,12 @@ static int Hash512_gen(DRBG_SHA512_internal* drbg, byte* out, word32 outSz, } /* Returns: DRBG_SUCCESS, DRBG_NEED_RESEED, or DRBG_FAILURE */ -static int Hash512_DRBG_Generate(DRBG_SHA512_internal* drbg, byte* out, - word32 outSz, - const byte* additional, word32 additionalSz) +static WARN_UNUSED_RESULT int Hash512_DRBG_Generate(DRBG_SHA512_internal* drbg, + byte* out, word32 outSz, + const byte* additional, word32 additionalSz, + WC_RNG* rng) { + int state_mutated = 0; int ret; #ifdef WOLFSSL_SMALL_STACK_CACHE wc_Sha512* sha = &drbg->sha512; @@ -1526,9 +2086,11 @@ static int Hash512_DRBG_Generate(DRBG_SHA512_internal* drbg, byte* out, #ifndef WOLFSSL_SMALL_STACK_CACHE wc_Sha512Free(sha); #endif - if (ret == 0) + if (ret == 0) { array_add(drbg->V, sizeof(drbg->V), digest, WC_SHA512_DIGEST_SIZE); + state_mutated = 1; + } } if (ret == 0) @@ -1582,12 +2144,22 @@ static int Hash512_DRBG_Generate(DRBG_SHA512_internal* drbg, byte* out, #endif } + /* Condemn iff the failure landed after the first state write (the + * step-2 V add): V then carries a half-applied transition that no + * in-band operation re-validates -- latch-or-condemn. Failures + * before the seam leave V/C/reseedCtr intact and stay retryable. + * (A bare reseedCtr++ on a pre-seam failure is benign: + * self-consistent state, one interval slot burned.) */ + if ((ret != 0) && state_mutated && (rng != NULL)) + rng->status = DRBG_FAILED; + return (ret == 0) ? DRBG_SUCCESS : DRBG_FAILURE; } /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash512_DRBG_Init(DRBG_SHA512_internal* drbg, const byte* seed, - word32 seedSz, const byte* nonce, word32 nonceSz, +static WARN_UNUSED_RESULT int Hash512_DRBG_Init(DRBG_SHA512_internal* drbg, + const byte* seed, word32 seedSz, + const byte* nonce, word32 nonceSz, const byte* perso, word32 persoSz) { if (seed == NULL) @@ -1609,7 +2181,8 @@ static int Hash512_DRBG_Init(DRBG_SHA512_internal* drbg, const byte* seed, } /* Returns: DRBG_SUCCESS or DRBG_FAILURE */ -static int Hash512_DRBG_Instantiate(DRBG_SHA512_internal* drbg, +static WARN_UNUSED_RESULT int Hash512_DRBG_Instantiate( + DRBG_SHA512_internal* drbg, const byte* seed, word32 seedSz, const byte* nonce, word32 nonceSz, const byte* perso, word32 persoSz, @@ -1618,6 +2191,11 @@ static int Hash512_DRBG_Instantiate(DRBG_SHA512_internal* drbg, int ret = WC_NO_ERR_TRACE(DRBG_FAILURE); XMEMSET(drbg, 0, sizeof(DRBG_SHA512_internal)); +#ifdef WC_RNG_HAVE_NEXT_SEED + wolfSSL_Atomic_Int_Init(&drbg->nextSeedLen, WC_DRBG_NEXT_SEED_EMPTY); + wolfSSL_Atomic_Int_Init(&drbg->nextStirLen, + WC_DRBG_NEXT_SEED_EMPTY); +#endif drbg->heap = heap; #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) drbg->devId = devId; @@ -1663,23 +2241,115 @@ static int Hash512_DRBG_Uninstantiate(DRBG_SHA512_internal* drbg) #endif /* WOLFSSL_DRBG_SHA512 */ +/* Uncredited stirring, per SP 800-90A 10.1.1.4 generate with additional_input + * (step 2: V += Hash(0x02 || V || additional_input)). The generate is + * zero-length: Hash_gen()'s (and Hash512_gen()'s) outSz==0 mode banks the + * generated block for the continuous-test hook (its consumer is + * configuration-dependent; no comparison state lives in this file), and + * out is never dereferenced. The reseed counter is incremented as for any + * generate, and quarantine/stratum are untouched, so the no-claims doctrine + * holds as a theorem of the standard rather than a property of a custom + * transition. Refused with NOT_READY_E when the instance is quarantined or due + * for a credited reseed: a generate must not run past the reseed interval. */ + +static WARN_UNUSED_RESULT int Hash_DRBG_StirGenerate(WC_RNG* rng, + const byte* add, + word32 addSz) +{ + wc_drbg_reseed_ctr_t ctr = 0; + int ret; -/* FIPS 140-3 IG 10.3.A / SP800-90B Health Tests for Seed Data - * - * These tests replace the older FIPS 140-2 Continuous Random Number Generator - * Test (CRNGT) with more mathematically robust statistical tests per - * ISO 19790 / SP800-90B requirements. - * - * When HAVE_ENTROPY_MEMUSE is defined, the wolfentropy.c jitter-based TRNG - * performs another set of these health tests, but those are on the noise not - * the conditioned output so we still need to retest here even in that case - * to evaluate the conditioned output for the same behavior. These tests ensure - * the seed data meets basic entropy requirements regardless of the source. - */ +#ifdef WC_RNG_HAVE_LOCK + /* The lock word is the atomic source of truth for quarantine: the + * invalidator's counter saturation can be lost to a racing + * lease-holder's plain reseedCtr++, but the latch cannot. Checked + * before the counter for exactly that reason. */ + if (WOLFSSL_ATOMIC_LOAD(rng->lock) & WC_RNG_LOCK_ENTROPY_INVALIDATED) + return NOT_READY_E; +#endif + ret = wc_RNG_DRBG_GetReseedCtr(rng, &ctr); + if (ret != 0) + return ret; + if (ctr >= WC_RESEED_INTERVAL) + return NOT_READY_E; -/* SP800-90B 4.4.1 - Repetition Count Test - * Detects if the noise source becomes "stuck" producing repeated output. - * + ret = RNG_FAILURE_E; +#ifndef NO_SHA256 + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL)) { + ret = Hash_DRBG_Generate((DRBG_internal *)rng->drbg, NULL, 0, + add, addSz, rng); + if ((ret != 0) && (rng->status == DRBG_FAILED)) + ret = RNG_FAILURE_E; + } +#endif +#ifdef WOLFSSL_DRBG_SHA512 + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL)) { + ret = Hash512_DRBG_Generate((DRBG_SHA512_internal *)rng->drbg512, + NULL, 0, add, addSz, rng); + if ((ret != 0) && (rng->status == DRBG_FAILED)) + ret = RNG_FAILURE_E; + } +#endif +#ifdef WC_RNG_DEBUG_STATS + if (ret == 0) + ++rng->_stats_stirs; /* counts uncredited + * stir-generates. */ +#endif + return ret; +} + +int wc_RNG_DRBG_Stir_Nonce(WC_RNG* rng, + const byte* seed, word32 seedSz, + const byte *nonce, word32 nonceSz) +{ + if (rng == NULL || seed == NULL) + return BAD_FUNC_ARG; + if ((nonce == NULL) && (nonceSz != 0)) + return BAD_FUNC_ARG; + + if (rng->status != WC_DRBG_OK) + return RNG_FAILURE_E; + + { + int lock_ret = rng_lock_required_check(rng); + if (lock_ret != 0) + return lock_ret; + } + + { + int ret = Hash_DRBG_StirGenerate(rng, seed, seedSz); + if ((ret == 0) && (nonce != NULL) && (nonceSz > 0)) { + /* Second chunk as its own specified generate: additional + * input is per-call, and chunking beats concatenation + * scratch. */ + ret = Hash_DRBG_StirGenerate(rng, nonce, nonceSz); + } + return ret; + } +} + +int wc_RNG_DRBG_Stir(WC_RNG* rng, const byte* seed, word32 seedSz) +{ + return wc_RNG_DRBG_Stir_Nonce(rng, seed, seedSz, NULL, + 0); +} + +/* FIPS 140-3 IG 10.3.A / SP800-90B Health Tests for Seed Data + * + * These tests replace the older FIPS 140-2 Continuous Random Number Generator + * Test (CRNGT) with more mathematically robust statistical tests per + * ISO 19790 / SP800-90B requirements. + * + * When HAVE_ENTROPY_MEMUSE is defined, the wolfentropy.c jitter-based TRNG + * performs another set of these health tests, but those are on the noise not + * the conditioned output so we still need to retest here even in that case + * to evaluate the conditioned output for the same behavior. These tests ensure + * the seed data meets basic entropy requirements regardless of the source. + */ + +/* SP800-90B 4.4.1 - Repetition Count Test + * Detects if the noise source becomes "stuck" producing repeated output. + * * C = 1 + ceil(-log2(alpha) / H) * For alpha = 2^-30 (false positive probability) and H = 1 (min entropy): * C = 1 + ceil(30 / 1) = 31 @@ -1902,9 +2572,28 @@ int wc_Sha512Drbg_IsDisabled(void) #endif /* HAVE_HASHDRBG */ /* End NIST DRBG Code */ - -static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, - void* heap, int devId) +/* Semantics of "flags": + * + * Security attributes are fixed at instantiation and caller-declared -- the + * constructor never reads the target object. _LOCK_REQUIRED sets the sticky + * WC_RNG_LOCK_REQUIRED latch bit at birth, so there is no reachable state in + * which the instance serves without its lock policy. _LOCK_INITIALLY sets + * WC_RNG_LOCK_HELD at birth: the caller is the lease holder from the first + * instruction -- the flag for constructing into a held lease (a lease-holding + * reinitialization keeps the instance invariantly locked across the reinit), to + * be released by wc_RNG_lock_put() as usual. _USE_FULL_MUTEX layers a blocking + * wolfSSL_Mutex outermost around wc_RNG_lock_get() and wc_RNG_lock_put*() -- + * for user-mode sharing of one instance among threads, where spinning on the + * CAS would be wrong; contending getters sleep in wc_LockMutex(). The inner + * latch (and every other wc_RNG_lock_*() operation) is unchanged. + * NOT_COMPILED_IN unless WC_RNG_HAVE_LOCK_FULL_MUTEX; composes with + * _LOCK_INITIALLY (born held at both layers). wc_FreeRng() releases (if the + * latch is held) and frees the mutex. + */ +static WARN_UNUSED_RESULT int _InitRng(WC_RNG* rng, + const byte* nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + void* heap, int devId, WC_RNG* seedRng, word32 flags) { int ret = 0; #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) @@ -1914,6 +2603,7 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, word32 seedSz = SEED_SZ + SEED_BLOCK_SZ; #endif WC_DECLARE_VAR(seed, byte, MAX_SEED_SZ, rng->heap); + int drbg_instantiated = 0; #ifdef WOLFSSL_SMALL_STACK_CACHE int drbg_scratch_instantiated = 0; #endif @@ -1921,13 +2611,68 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, (void)nonce; (void)nonceSz; + (void)perso; + (void)persoSz; + /* seedRng is consumed only in the seed-acquisition arm; cast for + * configurations that compile that arm out. */ + (void)seedRng; if (rng == NULL) return BAD_FUNC_ARG; if (nonce == NULL && nonceSz != 0) return BAD_FUNC_ARG; + if (perso == NULL && persoSz != 0) + return BAD_FUNC_ARG; + +#ifndef WC_RNG_HAVE_NEXT_SEED + if (flags & WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED) + return NOT_COMPILED_IN; +#endif +#ifndef WC_RNG_HAVE_LOCK_FULL_MUTEX + if (flags & WC_RNG_INIT_FLAG_USE_FULL_MUTEX) + return NOT_COMPILED_IN; +#endif + +#ifdef WC_RNG_HAVE_LOCK + if (flags & (WC_RNG_INIT_FLAG_LOCK_REQUIRED | + WC_RNG_INIT_FLAG_LOCK_INITIALLY)) + { + word32 initial_flags = + ((flags & WC_RNG_INIT_FLAG_LOCK_REQUIRED) ? + WC_RNG_LOCK_REQUIRED : 0) | + ((flags & WC_RNG_INIT_FLAG_LOCK_INITIALLY) ? + WC_RNG_LOCK_HELD : 0); + XMEMSET(rng, 0, WC_OFFSETOF(WC_RNG, lock)); + XMEMSET((byte *)rng + WC_OFFSETOF(WC_RNG, lock) + sizeof(rng->lock), 0, + sizeof(*rng) - + (WC_OFFSETOF(WC_RNG, lock) + sizeof(rng->lock))); + #ifdef WC_RNG_DEBUG_STATS + if (flags & WC_RNG_INIT_FLAG_LOCK_INITIALLY) + rng->_stats_locks_taken = 1; + #endif +#ifdef WOLFSSL_NO_ATOMICS + rng->lock = initial_flags; +#else + wolfSSL_Atomic_Uint_Init(&rng->lock, initial_flags); +#endif + } + else +#endif /* WC_RNG_HAVE_LOCK */ + { + XMEMSET(rng, 0, sizeof(*rng)); + } - XMEMSET(rng, 0, sizeof(*rng)); +#ifdef WC_RNG_HAVE_RBGC + if (seedRng == NULL) + rng->RBGCStratum = 0; + else { + if (seedRng->RBGCStratum >= WC_MAX_SINT_OF(int)) + return SEQ_OVERFLOW_E; + else if (seedRng->RBGCStratum == WC_RNG_RBGC_USER_SEED_STRATUM - 1) + return SEQ_OVERFLOW_E; + rng->RBGCStratum = seedRng->RBGCStratum + 1; + } +#endif #ifdef WOLFSSL_HEAP_TEST rng->heap = (void*)WOLFSSL_HEAP_TEST; @@ -2015,11 +2760,16 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, #endif #ifdef HAVE_INTEL_RDRAND - /* if CPU supports RDRAND, use it directly and by-pass DRBG init */ + /* if CPU supports RDRAND, use it directly and bypass DRBG init */ if (IS_INTEL_RDRAND(intel_flags)) { #ifdef HAVE_HASHDRBG rng->status = DRBG_OK; #endif +#ifdef WC_RNG_HAVE_RBGC + /* undo stratum increment */ + if (seedRng != NULL) + rng->RBGCStratum = 0; +#endif return 0; } #endif @@ -2175,7 +2925,8 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, #ifdef WOLFSSL_SMALL_STACK if (ret == 0) { - WC_ALLOC_VAR_EX(seed, byte, MAX_SEED_SZ, rng->heap, DYNAMIC_TYPE_SEED, WC_DO_NOTHING); + WC_ALLOC_VAR_EX(seed, byte, MAX_SEED_SZ, rng->heap, DYNAMIC_TYPE_SEED, + WC_DO_NOTHING); if (seed == NULL) { ret = MEMORY_E; rng->status = DRBG_FAILED; @@ -2189,6 +2940,16 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, #endif } else { + if (seedRng != NULL) { + /* RBGC spawn: draw the seed material from the parent DRBG's + * generate function in place of the module's seed source -- the SP + * 800-90C RBG chain construction. The root DRBG is implicitly + * healthy, so the seed Health test is omitted; all subsequent + * handling (seed byte accounting, instantiate, failure disposition) + * is then identical to the primary seed path. */ + ret = wc_RNG_GenerateBlock(seedRng, seed, seedSz); + } + else { #ifdef WC_RNG_SEED_CB if (seedCb == NULL) { ret = DRBG_NO_SEED_CB; @@ -2207,61 +2968,73 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, #else ret = wc_GenerateSeed(&rng->seed, seed, seedSz); #endif /* WC_RNG_SEED_CB */ + } + #ifdef WOLFSSL_CHECK_MEM_ZERO - /* seed now holds entropy; register across DRBG instantiation */ - wc_MemZero_Add("_InitRng seed", seed, seedSz); + /* seed now holds entropy; register across DRBG instantiation */ + wc_MemZero_Add("_InitRng seed", seed, seedSz); #endif - if (ret != 0) { + + if (ret != 0) { #if defined(DEBUG_WOLFSSL) - WOLFSSL_MSG_EX("Seed generation failed... %d", ret); + WOLFSSL_MSG_EX("Seed generation failed... %d", ret); #elif defined(WC_VERBOSE_RNG) - WOLFSSL_DEBUG_PRINTF( - "ERROR: wc_GenerateSeed() in _InitRng() failed with err %d", - ret); + WOLFSSL_DEBUG_PRINTF( + "ERROR: seed acquisition in _InitRng() failed with err %d", + ret); #endif - ret = DRBG_FAILURE; - rng->status = DRBG_FAILED; - } + ret = DRBG_FAILURE; + rng->status = DRBG_FAILED; + } - if (ret == 0) - ret = wc_RNG_TestSeed(seed, seedSz); - #if defined(DEBUG_WOLFSSL) + /* Health-check the primary seed -- RBGC seed is implicitly healthy. */ + + if ((ret == 0) && (seedRng == NULL)) { + ret = wc_RNG_TestSeed(seed, seedSz); + #if defined(DEBUG_WOLFSSL) if (ret != 0) { WOLFSSL_MSG_EX("wc_RNG_TestSeed failed... %d", ret); } - #elif defined(WC_VERBOSE_RNG) + #elif defined(WC_VERBOSE_RNG) if (ret != DRBG_SUCCESS) { WOLFSSL_DEBUG_PRINTF( "ERROR: wc_RNG_TestSeed() in _InitRng() returned err %d.", ret); } - #endif + #endif + } - if (ret == DRBG_SUCCESS) { + /* Instantiate the DRBG */ + + if (ret == DRBG_SUCCESS) { #ifndef NO_SHA256 - if (rng->drbgType == WC_DRBG_SHA256) - ret = Hash_DRBG_Instantiate((DRBG_internal *)rng->drbg, + 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, + seed + SEED_BLOCK_SZ, seedSz - SEED_BLOCK_SZ, #else - seed, seedSz, + seed, seedSz, #endif - nonce, nonceSz, NULL, 0, rng->heap, devId); + nonce, nonceSz, perso, persoSz, rng->heap, devId); #endif #ifdef WOLFSSL_DRBG_SHA512 - if (rng->drbgType == WC_DRBG_SHA512) - ret = Hash512_DRBG_Instantiate( - (DRBG_SHA512_internal *)rng->drbg512, + 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, + seed + SEED_BLOCK_SZ, seedSz - SEED_BLOCK_SZ, #else - seed, seedSz, + seed, seedSz, #endif - nonce, nonceSz, NULL, 0, rng->heap, devId); + nonce, nonceSz, perso, persoSz, rng->heap, devId); #endif - } + if (ret == 0) + drbg_instantiated = 1; + } } /* ret == 0 */ + /* Unconditionally burn the seed data. */ + #ifdef WOLFSSL_SMALL_STACK if (seed) #endif @@ -2274,14 +3047,77 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, } WC_FREE_VAR_EX(seed, rng->heap, DYNAMIC_TYPE_SEED); - if (ret != DRBG_SUCCESS) { + if (ret == DRBG_SUCCESS) { +#ifdef WOLFSSL_CHECK_MEM_ZERO #ifndef NO_SHA256 if (rng->drbgType == WC_DRBG_SHA256) { - #if !defined(WOLFSSL_NO_MALLOC) || defined(WOLFSSL_STATIC_MEMORY) + struct DRBG_internal* drbg = (struct DRBG_internal*)rng->drbg; + wc_MemZero_Add("DRBG V", &drbg->V, sizeof(drbg->V)); + wc_MemZero_Add("DRBG C", &drbg->C, sizeof(drbg->C)); + } + #endif +#endif + + rng->status = DRBG_OK; + ret = 0; + } + else if (ret == WC_NO_ERR_TRACE(DRBG_CONT_FAILURE)) { + rng->status = DRBG_CONT_FAILED; + ret = DRBG_CONT_FIPS_E; + } + else if (ret == WC_NO_ERR_TRACE(DRBG_FAILURE)) { + rng->status = DRBG_FAILED; + ret = RNG_FAILURE_E; + } + else { + rng->status = DRBG_FAILED; + } +#endif /* HAVE_HASHDRBG */ +#endif /* CUSTOM_RAND_GENERATE_BLOCK */ + +#ifdef WC_RNG_HAVE_NEXT_SEED + if ((ret == 0) && + (flags & WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED)) + { + rng->flags |= WC_RNG_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED; + } +#endif + if ((ret == 0) && (flags & WC_RNG_INIT_FLAG_USE_FULL_MUTEX)) { +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + /* deliberately the last init step: no failure path can strand an + * initialized mutex. */ + ret = wc_InitMutex(&rng->mutex); + if (ret == 0) { + rng->flags |= WC_RNG_FLAG_FULL_MUTEX; + if (flags & WC_RNG_INIT_FLAG_LOCK_INITIALLY) { + /* born held at both layers: the constructor's caller holds + * the whole latch, mutex included. */ + ret = wc_LockMutex(&rng->mutex); + } + } +#endif + } + + if (ret != 0) { + #ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) { + /* covers wc_LockMutex() failure after successful + * wc_InitMutex() (WC_RNG_INIT_FLAG_LOCK_INITIALLY). */ + (void)wc_FreeMutex(&rng->mutex); + rng->flags &= ~WC_RNG_FLAG_FULL_MUTEX; + } + #endif + #if defined(HAVE_HASHDRBG) && !defined(NO_SHA256) + if (rng->drbgType == WC_DRBG_SHA256) { + if (drbg_instantiated) { + (void)Hash_DRBG_Uninstantiate( + (DRBG_internal *)rng->drbg); + } + #if !defined(WOLFSSL_NO_MALLOC) || defined(WOLFSSL_STATIC_MEMORY) XFREE(rng->drbg, rng->heap, DYNAMIC_TYPE_RNG); - #endif + #endif rng->drbg = NULL; - #ifdef WOLFSSL_SMALL_STACK_CACHE + #ifdef WOLFSSL_SMALL_STACK_CACHE XFREE(rng->health_check_scratch, rng->heap, DYNAMIC_TYPE_TMP_BUFFER); rng->health_check_scratch = NULL; @@ -2290,16 +3126,20 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, (DRBG_internal *)rng->drbg_scratch); XFREE(rng->drbg_scratch, rng->heap, DYNAMIC_TYPE_RNG); rng->drbg_scratch = NULL; - #endif + #endif /* WOLFSSL_SMALL_STACK_CACHE */ } - #endif /* !NO_SHA256 */ - #ifdef WOLFSSL_DRBG_SHA512 + #endif /* HAVE_HASHDRBG && !NO_SHA256 */ + #if defined(HAVE_HASHDRBG) && defined(WOLFSSL_DRBG_SHA512) if (rng->drbgType == WC_DRBG_SHA512) { - #if !defined(WOLFSSL_NO_MALLOC) || defined(WOLFSSL_STATIC_MEMORY) + if (drbg_instantiated) { + (void)Hash512_DRBG_Uninstantiate( + (DRBG_SHA512_internal *)rng->drbg512); + } + #if !defined(WOLFSSL_NO_MALLOC) || defined(WOLFSSL_STATIC_MEMORY) XFREE(rng->drbg512, rng->heap, DYNAMIC_TYPE_RNG); - #endif + #endif rng->drbg512 = NULL; - #ifdef WOLFSSL_SMALL_STACK_CACHE + #ifdef WOLFSSL_SMALL_STACK_CACHE XFREE(rng->health_check_scratch_512, rng->heap, DYNAMIC_TYPE_TMP_BUFFER); rng->health_check_scratch_512 = NULL; @@ -2307,232 +3147,2229 @@ static int _InitRng(WC_RNG* rng, byte* nonce, word32 nonceSz, (void)Hash512_DRBG_Uninstantiate(rng->drbg512_scratch); XFREE(rng->drbg512_scratch, rng->heap, DYNAMIC_TYPE_RNG); rng->drbg512_scratch = NULL; - #endif + #endif /* WOLFSSL_SMALL_STACK_CACHE */ } - #endif + #endif /* HAVE_HASHDRBG && WOLFSSL_DRBG_SHA512 */ #ifdef WOLFSSL_SMALL_STACK_CACHE XFREE(rng->newSeed_buf, rng->heap, DYNAMIC_TYPE_SEED); rng->newSeed_buf = NULL; #endif } - /* else wc_RNG_HealthTestLocal was successful */ - if (ret == DRBG_SUCCESS) { -#ifdef WOLFSSL_CHECK_MEM_ZERO - #ifndef NO_SHA256 - if (rng->drbgType == WC_DRBG_SHA256) { - struct DRBG_internal* drbg = (struct DRBG_internal*)rng->drbg; - wc_MemZero_Add("DRBG V", &drbg->V, sizeof(drbg->V)); - wc_MemZero_Add("DRBG C", &drbg->C, sizeof(drbg->C)); - } - #endif + return ret; +} + + +WOLFSSL_ABI +WC_RNG* wc_rng_new(byte* nonce, word32 nonceSz, void* heap) +{ + int ret = 0; + WC_RNG* rng = NULL; + + /* Assume if WC_USE_DEVID it is intended for default usage */ +#ifdef WC_USE_DEVID + ret = wc_rng_new_ex(&rng, nonce, nonceSz, heap, WC_USE_DEVID); +#else + ret = wc_rng_new_ex(&rng, nonce, nonceSz, heap, INVALID_DEVID); #endif - rng->status = DRBG_OK; - ret = 0; + if (ret != 0) { + return NULL; } - else if (ret == WC_NO_ERR_TRACE(DRBG_CONT_FAILURE)) { - rng->status = DRBG_CONT_FAILED; - ret = DRBG_CONT_FIPS_E; + + return rng; +} + + +int wc_rng_new_ex(WC_RNG **rng, byte* nonce, word32 nonceSz, + void* heap, int devId) +{ + int ret; + + if (rng == NULL) { + return BAD_FUNC_ARG; } - else if (ret == WC_NO_ERR_TRACE(DRBG_FAILURE)) { - rng->status = DRBG_FAILED; - ret = RNG_FAILURE_E; + + *rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), heap, DYNAMIC_TYPE_RNG); + if (*rng == NULL) { + return MEMORY_E; } - else { - rng->status = DRBG_FAILED; + + ret = _InitRng(*rng, nonce, nonceSz, NULL, 0, heap, devId, NULL, + WC_RNG_INIT_FLAG_NONE); + if (ret != 0) { + XFREE(*rng, heap, DYNAMIC_TYPE_RNG); + *rng = NULL; } -#endif /* HAVE_HASHDRBG */ -#endif /* CUSTOM_RAND_GENERATE_BLOCK */ return ret; } WOLFSSL_ABI -WC_RNG* wc_rng_new(byte* nonce, word32 nonceSz, void* heap) +void wc_rng_free(WC_RNG* rng) +{ + if (rng) { + void* heap = rng->heap; + + wc_FreeRng(rng); + ForceZero(rng, sizeof(WC_RNG)); + XFREE(rng, heap, DYNAMIC_TYPE_RNG); + (void)heap; + } +} + +WOLFSSL_ABI +int wc_InitRng(WC_RNG* rng) +{ + return _InitRng(rng, NULL, 0, NULL, 0, NULL, INVALID_DEVID, NULL, + WC_RNG_INIT_FLAG_NONE); +} + + +int wc_InitRng_ex(WC_RNG* rng, void* heap, int devId) +{ + return _InitRng(rng, NULL, 0, NULL, 0, heap, devId, NULL, + WC_RNG_INIT_FLAG_NONE); +} + + +int wc_InitRngNonce(WC_RNG* rng, const byte* nonce, word32 nonceSz) +{ + return _InitRng(rng, nonce, nonceSz, NULL, 0, NULL, INVALID_DEVID, NULL, + WC_RNG_INIT_FLAG_NONE); +} + + +int wc_InitRngNonce_ex(WC_RNG* rng, const byte* nonce, word32 nonceSz, + void* heap, int devId) { + return _InitRng(rng, nonce, nonceSz, NULL, 0, heap, devId, NULL, + WC_RNG_INIT_FLAG_NONE); +} + +int wc_InitRng_ex2(WC_RNG* rng, void* heap, int devId, word32 flags) +{ + return _InitRng(rng, NULL, 0, NULL, 0, heap, devId, NULL, flags); +} + +int wc_InitRngNonce_ex2(WC_RNG* rng, const byte* nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + void* heap, int devId, word32 flags) +{ + return _InitRng(rng, nonce, nonceSz, perso, persoSz, + heap, devId, NULL, flags); +} + +#if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) + +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) +static WARN_UNUSED_RESULT int PollAndReSeed(WC_RNG* rng, const byte* additional, + word32 additionalSz); +#endif + +/* rng_pid_change_check() is used by wc_RNG_Pool_Extract(), + * wc_RNG_DRBG_NextSeedNow_Nonce(), and wc_RNG_GenerateBlock(), to assure that + * the RNG is freshly seeded after a fork(), to avoid seeding or generating from + * duplicated internal state. + */ +static WARN_UNUSED_RESULT WC_MAYBE_UNUSED int rng_pid_change_check(WC_RNG* rng) { int ret = 0; - WC_RNG* rng = NULL; + int my_pid = getpid(); + + if (rng->pid == my_pid) + return 0; + + rng->pid = my_pid; + +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) + ret = PollAndReSeed(rng, NULL, 0); + if (ret != DRBG_SUCCESS) { + rng->status = DRBG_FAILED; + ret = RNG_FAILURE_E; + } +#endif + +#ifdef WC_RNG_HAVE_POOL + { + int ret2 = PoolPurge(rng); + if ((ret == 0) && + (ret2 != 0) && + (ret2 != WC_NO_ERR_TRACE(ALREADY_E))) + { + ret = ret2; + } + } +#endif + +#ifdef WC_RNG_HAVE_NEXT_SEED + #ifndef NO_SHA256 + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL)) { + int ret2 = NextSeedPurge(&((DRBG_internal *)rng->drbg)->nextSeedLen, + ((DRBG_internal *)rng->drbg)->nextSeed, + (word32)sizeof(((DRBG_internal *)rng->drbg)->nextSeed)); + if ((ret == 0) && + (ret2 != 0) && + (ret2 != WC_NO_ERR_TRACE(ALREADY_E))) + { + ret = ret2; + } + WOLFSSL_ATOMIC_STORE(((DRBG_internal *)rng->drbg)->nextStirLen, + WC_DRBG_NEXT_SEED_EMPTY); + } + #endif + #ifdef WOLFSSL_DRBG_SHA512 + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL)) { + int ret2 = + NextSeedPurge(&((DRBG_SHA512_internal *)rng->drbg512)->nextSeedLen, + ((DRBG_SHA512_internal *)rng->drbg512)->nextSeed, + (word32)sizeof(((DRBG_SHA512_internal *)rng->drbg512)->nextSeed)); + if ((ret == 0) && + (ret2 != 0) && + (ret2 != WC_NO_ERR_TRACE(ALREADY_E))) + { + ret = ret2; + } + WOLFSSL_ATOMIC_STORE(((DRBG_SHA512_internal *)rng->drbg512)->nextStirLen, + WC_DRBG_NEXT_SEED_EMPTY); + } + #endif +#endif /* WC_RNG_HAVE_NEXT_SEED */ + + return ret; +} +#endif /* HAVE_GETPID && !WOLFSSL_NO_GETPID */ + +#ifdef WC_RNG_HAVE_LOCK + +/* Note, in CAS updates here, the stored value derives only from expected and + * the caller's arguments, never from a prior load. This assures no race with + * unlocked changes to any bits. + */ + +int wc_RNG_lock_get(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits) +{ + WC_RNG_lock_arg_t cur_lock; + + if (rng == NULL) + return BAD_FUNC_ARG; + +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + /* outermost blocking layer, when constructed with _USE_FULL_MUTEX: + * contending getters sleep here rather than seeing BUSY_E. */ + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) { + if (wc_LockMutex(&rng->mutex) != 0) { + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_refused; /* racy */ + #endif + return BAD_MUTEX_E; + } + } +#endif + + /* extra_bits is allowed to assert WC_RNG_LOCK_REQUIRED, which is in the + * reserved section. */ + extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + + cur_lock = WOLFSSL_ATOMIC_LOAD(rng->lock); + + if (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) { +#ifdef WC_RNG_HAVE_NEXT_SEED + WC_ATOMIC_INT_ARG NextSeedCurrent; + if ((rng->flags & WC_RNG_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED) && + (wc_RNG_DRBG_NextSeedCurrent(rng, &NextSeedCurrent) == 0) && + (NextSeedCurrent == WC_DRBG_NEXT_SEED_READY)) + { + /* cheap inline recovery available. */ + } + else +#endif + { + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_refused; /* racy */ + #endif +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) + (void)wc_UnLockMutex(&rng->mutex); +#endif + return NEEDS_RECOVERY_E; + } + } + + if ((! (cur_lock & WC_RNG_LOCK_HELD)) && + (wolfSSL_Atomic_Uint_CompareExchange( + &rng->lock, &cur_lock, + cur_lock | WC_RNG_LOCK_HELD | extra_bits))) + { + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_taken; + #endif + + /* If we arrived here via _RECOVER_AND_PROMOTE_FROM_NEXT_SEED with a + * pending NextSeed, there is a finite though minuscule chance that a + * second invalidation left the RNG without a banked seed to consume. + * In that case, the holder's generate falls through to the regular + * inline forced-reseed machinery. This is the same outcome as an + * invalidation landing immediately after a successful acquire. + */ + + return 0; + } + + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_refused; + #endif + +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + /* CAS failure with the mutex held means a non-mutex claimant holds + * the latch (mixed-discipline use); back out the mutex. */ + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) + (void)wc_UnLockMutex(&rng->mutex); +#endif + + if (cur_lock & WC_RNG_LOCK_HELD) + return BUSY_E; + else if (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) + return NEEDS_RECOVERY_E; + else /* not reachable */ + return UNEXPECTED_STATE_E; +} + +int wc_RNG_lock_get_conditional(WC_RNG* rng, + WC_RNG_lock_arg_t expected_extra_bits, + WC_RNG_lock_arg_t want_extra_bits) +{ + WC_RNG_lock_arg_t cur_lock, expected; + + if (rng == NULL) + return BAD_FUNC_ARG; + +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + /* outermost blocking layer, when constructed with _USE_FULL_MUTEX: + * contending getters sleep here rather than seeing BUSY_E. */ + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) { + if (wc_LockMutex(&rng->mutex) != 0) { + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_refused; /* racy */ + #endif + return BAD_MUTEX_E; + } + } +#endif + + /* *_extra_bits are allowed to assert WC_RNG_LOCK_REQUIRED, which is in the + * reserved section. Additionally, expected_extra_bits is allowed to + * include WC_RNG_LOCK_ENTROPY_INVALIDATED, for purposes of recovery. */ + expected_extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED | WC_RNG_LOCK_ENTROPY_INVALIDATED; + want_extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + + cur_lock = WOLFSSL_ATOMIC_LOAD(rng->lock); + + if ((cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) && + (! (expected_extra_bits & WC_RNG_LOCK_ENTROPY_INVALIDATED))) + { + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_refused; /* racy */ + #endif +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) + (void)wc_UnLockMutex(&rng->mutex); +#endif + return NEEDS_RECOVERY_E; + } + + expected = (cur_lock & (((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) & + ~(WC_RNG_LOCK_HELD | + WC_RNG_LOCK_ENTROPY_INVALIDATED))) | + expected_extra_bits; + + if ((! (cur_lock & WC_RNG_LOCK_HELD)) && + (wolfSSL_Atomic_Uint_CompareExchange( + &rng->lock, &expected, + expected | WC_RNG_LOCK_HELD | want_extra_bits))) + { + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_taken; + #endif + return 0; + } + + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_refused; /* racy */ + #endif + +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + /* CAS failure with the mutex held means a non-mutex claimant holds + * the latch (mixed-discipline use); back out the mutex. */ + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) + (void)wc_UnLockMutex(&rng->mutex); +#endif + + if ((cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) != + (expected & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + { + return NEEDS_RECOVERY_E; + } + else if (expected & WC_RNG_LOCK_HELD) + return BUSY_E; + else + return UNEXPECTED_STATE_E; +} + +int wc_RNG_lock_put(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits) +{ + WC_RNG_lock_arg_t cur_lock, new_lock; + int cas_ret; + if (rng == NULL) + return BAD_FUNC_ARG; + cur_lock = WOLFSSL_ATOMIC_LOAD(rng->lock); + if (! (cur_lock & WC_RNG_LOCK_HELD)) + return OBJECT_NOT_LOCKED_E; + + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_released; + #endif + + WC_CAS_WITH_RETRY_BEGIN(&rng->lock, cur_lock, cas_ret) { + new_lock = cur_lock & (((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) & + ~WC_RNG_LOCK_HELD); + /* extra_bits is allowed to assert WC_RNG_LOCK_REQUIRED, which is in the + * reserved section. */ + extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + new_lock |= extra_bits; + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &rng->lock, cur_lock, new_lock, cas_ret); + } WC_CAS_WITH_RETRY_END; + + if (cas_ret != 0) { + /* Aborted release (a port's retry clause): the latch is still ours + * and new_lock was never installed. Keep ownership consistent -- + * mutex included -- and percolate so the caller can retry the + * put. */ + return cas_ret; + } + +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) + (void)wc_UnLockMutex(&rng->mutex); +#endif + + if (new_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) + return NEEDS_RECOVERY_E; + else + return 0; +} + +int wc_RNG_lock_put_conditional(WC_RNG* rng, + WC_RNG_lock_arg_t expected_extra_bits, + WC_RNG_lock_arg_t want_extra_bits) +{ + int cas_ret; + WC_CAS_WITH_RETRY_EXTRA_DECLS; + WC_RNG_lock_arg_t cur_lock, expected, new_lock; + + if (rng == NULL) + return BAD_FUNC_ARG; + + cur_lock = WOLFSSL_ATOMIC_LOAD(rng->lock); + if (! (cur_lock & WC_RNG_LOCK_HELD)) + return OBJECT_NOT_LOCKED_E; + + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_locks_released; + #endif + + /* Note this CAS loop doesn't use WC_CAS_WITH_RETRY_*() (non-conformant code + * pattern), so the WC_CAS_WITH_RETRY_* hook macros are invoked directly. */ + for (;;) { + new_lock = cur_lock & (((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) & + ~WC_RNG_LOCK_HELD); + /* want_extra_bits is allowed to assert WC_RNG_LOCK_REQUIRED, which is + * in the reserved section. */ + want_extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + new_lock |= want_extra_bits; + + expected = WC_RNG_LOCK_HELD | expected_extra_bits | + (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED); + + new_lock |= (expected_extra_bits & WC_RNG_LOCK_REQUIRED) | + (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED); + + /* release preserves the sticky bit if the caller reports it held */ + if (wolfSSL_Atomic_Uint_CompareExchange( + &rng->lock, &expected, + new_lock)) + { +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) + (void)wc_UnLockMutex(&rng->mutex); +#endif + if (new_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) + return NEEDS_RECOVERY_E; + else + return 0; + } + if ((expected & ((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U)) != + (expected_extra_bits & ((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U))) + { + break; + } + + cur_lock = expected; + + cas_ret = WC_CAS_WITH_RETRY_FOREVER_CLAUSE; + if (cas_ret != 0) + return cas_ret; + WC_CAS_WITH_RETRY_ITER_CLAUSE(&rng->lock, cur_lock, new_lock, cas_ret); + } + + /* conditional release failed: the caller is still the holder, at both + * layers -- the mutex stays held. */ + + #ifdef WC_RNG_DEBUG_STATS + --rng->_stats_locks_released; + #endif + + return UNEXPECTED_STATE_E; +} + +int wc_RNG_lock_read(WC_RNG* rng, WC_RNG_lock_arg_t* state) +{ + if ((rng == NULL) || (state == NULL)) + return BAD_FUNC_ARG; + *state = WOLFSSL_ATOMIC_LOAD(rng->lock); + return 0; +} + +int wc_RNG_lock_set_extra(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits) +{ + WC_RNG_lock_arg_t cur_lock, new_lock; + int cas_ret; + if (rng == NULL) + return BAD_FUNC_ARG; + cur_lock = WOLFSSL_ATOMIC_LOAD(rng->lock); + + WC_CAS_WITH_RETRY_BEGIN(&rng->lock, cur_lock, cas_ret) { + new_lock = cur_lock & ((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U); + /* extra_bits is allowed to assert WC_RNG_LOCK_REQUIRED, which is in the + * reserved section. */ + extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + new_lock |= extra_bits; + + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &rng->lock, cur_lock, new_lock, cas_ret); + } WC_CAS_WITH_RETRY_END; + /* 0 unless a port's retry clause aborted; the lock word is then + * untouched, so percolation is the whole handling. */ + return cas_ret; +} + +int wc_RNG_lock_add_extra(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits) +{ + WC_RNG_lock_arg_t cur_lock; + int cas_ret; + if (rng == NULL) + return BAD_FUNC_ARG; + cur_lock = WOLFSSL_ATOMIC_LOAD(rng->lock); + + /* extra_bits is allowed to assert WC_RNG_LOCK_REQUIRED, which is in the + * reserved section. */ + extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + + WC_CAS_WITH_RETRY_BEGIN(&rng->lock, cur_lock, cas_ret) { + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &rng->lock, cur_lock, + cur_lock | extra_bits, cas_ret); + } WC_CAS_WITH_RETRY_END; + /* see wc_RNG_lock_set_extra() re nonzero cas_ret. */ + return cas_ret; +} + +int wc_RNG_lock_clear_extra(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits) +{ + WC_RNG_lock_arg_t cur_lock; + int cas_ret; + if (rng == NULL) + return BAD_FUNC_ARG; + if (extra_bits & WC_RNG_LOCK_REQUIRED) { + /* WC_RNG_LOCK_REQUIRED is sticky by contract */ + return BAD_FUNC_ARG; + } + cur_lock = WOLFSSL_ATOMIC_LOAD(rng->lock); + + extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U); + + WC_CAS_WITH_RETRY_BEGIN(&rng->lock, cur_lock, cas_ret) { + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &rng->lock, cur_lock, + cur_lock & ~extra_bits, cas_ret); + } WC_CAS_WITH_RETRY_END; + /* see wc_RNG_lock_set_extra() re nonzero cas_ret. */ + return cas_ret; +} + +#ifdef HAVE_HASHDRBG +WOLFSSL_API int wc_RNG_invalidate_entropy(WC_RNG* rng) { + WC_RNG_lock_arg_t cur_lock; + int ret; + + if (rng == NULL) + return BAD_FUNC_ARG; + + /* If no lock is held, either the RNG is in use without a lock, in which + * case the reseedCtr is the only way to force invalidation semantics on the + * user, or it is not in use at all and scheduling a reseed is harmless. + * + * If a lock is held, the holder will learn of the invalidation at unlock + * time, and will implement its own mitigation strategy. We do not force it + * into a synchronous reseed. + * + * In either case, the state purges here are best effort. With + * WC_RNG_HAVE_LOCK, these purges are repeated, strictly serialized against + * concurrent recovery attempts, in Hash_DRBG_Reseed() (the sole recovery + * path from _ENTROPY_INVALIDATED). + */ + ret = wc_RNG_DRBG_ScheduleReseed(rng); + if (ret == WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)) { + /* No DRBG (direct-RDRAND et al.): nothing to schedule, and nothing + * whose staleness the latch would mark -- not a condemnable + * failure. Any applicable auxiliary-state purges below still + * run. */ + ret = 0; + } + +#ifdef WC_RNG_HAVE_POOL + { + int ret2 = PoolPurge(rng); + if ((ret2 != 0) && (ret == 0)) + ret = ret2; + } +#endif +#ifdef WC_RNG_HAVE_NEXT_SEED +#ifndef NO_SHA256 + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL)) { + int ret2 = NextSeedPurge(&((DRBG_internal *)rng->drbg)->nextSeedLen, + ((DRBG_internal *)rng->drbg)->nextSeed, + (word32)sizeof(((DRBG_internal *)rng->drbg)->nextSeed)); + if ((ret2 != 0) && + (ret2 != WC_NO_ERR_TRACE(ALREADY_E)) && + (ret == 0)) + { + ret = ret2; + } + WOLFSSL_ATOMIC_STORE(((DRBG_internal *)rng->drbg)->nextStirLen, + WC_DRBG_NEXT_SEED_EMPTY); + } +#endif +#ifdef WOLFSSL_DRBG_SHA512 + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL)) { + int ret2 = + NextSeedPurge(&((DRBG_SHA512_internal *)rng->drbg512)->nextSeedLen, + ((DRBG_SHA512_internal *)rng->drbg512)->nextSeed, + (word32)sizeof(((DRBG_SHA512_internal *)rng->drbg512)->nextSeed)); + if ((ret2 != 0) && + (ret2 != WC_NO_ERR_TRACE(ALREADY_E)) && + (ret == 0)) + { + ret = ret2; + } + WOLFSSL_ATOMIC_STORE(((DRBG_SHA512_internal *)rng->drbg512)->nextStirLen, + WC_DRBG_NEXT_SEED_EMPTY); + } +#endif +#endif /* WC_RNG_HAVE_NEXT_SEED */ + + /* Assert _ENTROPY_INVALIDATED last: latch-after-purge makes the latch a + * provenance marker. Any consumer that observes the latch observes + * post-purge apertures, so a READY it then claims necessarily postdates + * this event; a consumer that raced ahead of the latch loses its claim + * to the purge above and is refused at its release CAS (see + * wc_RNG_DRBG_NextSeedNow_Nonce()); and an event landing mid-reseed + * strips _ENTROPY_RECOVERING here, so the recovery's exit CAS refuses + * the clear. No interleaving recovers from pre-event material. */ + + { + int cas_ret; + WC_CAS_WITH_RETRY_BEGIN_INIT_CUR(&rng->lock, cur_lock, cas_ret) { + WC_CAS_WITH_RETRY_LOOP_FOREVER( + wolfSSL_Atomic_Uint_CompareExchange, &rng->lock, cur_lock, + (cur_lock & ~WC_RNG_LOCK_ENTROPY_RECOVERING) | + WC_RNG_LOCK_ENTROPY_INVALIDATED, + cas_ret); + } WC_CAS_WITH_RETRY_END; + if ((cas_ret != 0) && (ret == 0)) + ret = cas_ret; + } + + /* Postcondition: latch or condemn. Zero return means _INVALIDATED is + * asserted; any nonzero return leaves the latch down (it is asserted last, + * above), so the instance is quarantined by counter saturation alone -- and + * the reseedCtr is subject to lost-update races. DRBG_FAILED is the + * remaining stop no interleaving can lift: sticky until a full recovery. + * + * This is the one leaseless outsider write of DRBG_FAILED; the race against + * a lease-holder is benign both ways (a completing reseed's OK overwrite + * means the state was genuinely re-derived from post-event material; a + * FAILED overwrite of OK costs one spurious reinit, never unsound output). + * In the kernel build, bank instances are retired and recovered by the + * entropy daemon's out-of-service pass; a leaf's owner sees hard + * RNG_FAILURE_E and reinitializes. + */ + if (ret != 0) + rng->status = DRBG_FAILED; + + return ret; +} + +#endif /* HAVE_HASHDRBG */ +#endif /* WC_RNG_HAVE_LOCK */ + +#ifdef WC_RNG_HAVE_FREE_HOOK +/* This routine is used for mitigation of RNG cloning events, particularly by + * hypervisors. */ +WOLFSSL_API int wc_RNG_register_free_hook(WC_RNG* rng, + wc_RNG_free_hook_cb_t free_hook, + void *arg) +{ + if (rng == NULL) + return BAD_FUNC_ARG; + rng->free_hook = free_hook; + rng->free_hook_arg = arg; + return 0; +} +#endif /* WC_RNG_HAVE_FREE_HOOK */ + +#ifdef WC_RNG_HAVE_POOL + + /* In-boundary asynchronous DRBG output pool (wc_RNG_Pool_*()): a circular + * buffer of pre-generated output, held and zeroized under the module's CSP + * discipline and consumed destructively (each delivered or discarded byte + * is burned). + * + * Writer state coherence is enforced with a CAS; reader exclusivity is + * enforced by the umbrella WC_RNG.lock, or absent that, by caller contract. + * + * The reader is lock-free -- two plain loads of {head, epoch} bracketing + * the copy, then a plain store of {tail, epoch} exclusively written by the + * reader. The writer CASes its publication, carrying the epoch it read; a + * purge during its generate results in an epoch mismatch, whereupon BUSY_E + * is returned to the caller. + * + * _Alloc() sizes the ring (2..32767 bytes), with positions running in [0, + * 2*size) and packing into 16 bits. _Collect() tops up the pool from rng's + * own DRBG; _Collect2() tops dest's ring up from an independent src + * instance, generating directly into the free span and publishing with a + * single CAS -- callable WITHOUT any lease on dest (contending writers + * return BUSY_E on CAS failure; the final write of every published byte is + * certified DRBG output). _Extract() (lease-holder only) delivers up to *n + * bytes destructively, burning each byte on the way out, and fails closed + * on an out-of-service DRBG; *n = 0 on empty, for fall-through to a direct + * generate. _Current() reports the published count (racy snapshot). No + * special free API: the ring lives until wc_FreeRng(), eliminating + * deallocation races by construction. + * + * Two words track FIFO state: + * + * poolHead = {head, epoch} written by the writer (publish, CAS) and by + * PoolPurge() (epoch bump, CAS) + * poolTail = {tail, epoch} written by the reader alone, plain store + * + * Position and epoch share one word, so the pairs are always mutually + * consistent -- there is no torn snapshot to reason about. + * + * head and tail are free-running positions in [0, 2*poolSize), advanced by + * conditional subtraction -- no division, and no modulus constraint on + * poolSize. The writer publishes only into free space, so head can never + * pass tail + poolSize and the two can never lap. + * + * wc_RNG_lock_put{,_conditional}() return NEEDS_RECOVERY_E to the reader if + * an invalidation occurred after lock but before release (contingent on + * WC_RNG_HAVE_LOCK). State coherence for the lock-free writers and purgers + * hinges on the CAS and epoch counter protocol in PoolPurge() and + * wc_RNG_Pool_Collect2(). + * + * The reader never writes poolHead and never CASes anything, leveraging + * exclusivity enforced by WC_RNG.lock or arranged by caller contract. It + * brackets its copy with leading and trailing loads of poolHead and + * compares the epoch: a purge that landed anywhere in between is caught, + * providing for early, pre-unlock failure upon invalidation. The epoch is + * 16 bits, so defeating this early failure requires exactly k * 65536 + * purges (k a positive integer) inside one copy-and-burn of at most + * poolSize bytes (implausible). + * + * PoolPurge() bumps epoch and touches nothing else. It does not reset the + * counters: leaving them monotonic keeps the reader's burn span [tail, + * tail+m) and the writer's generate span [head, head+m') disjoint across + * the event, so a purge can never cause one to erase the other's bytes. + * Stale pre-purge material is discarded by the reader instead, which + * resynchronizes tail to head on any epoch change -- whether it observed + * the purge mid-serve or merely arrives afterwards. + * + * The writer still CASes, and its publication carries the epoch it read. A + * purge during its generate makes that CAS fail, and it abandons rather + * than publishing material that predates the event. This protocol is + * airtight in the same sense as the credited next-seed aperture + * (NextSeedPurge()): no invalidation can go unobserved by either side. + * + * If multiple writers simultaneously write to the pool, their inputs are + * unpredictably but benignly interspersed, with one of the writers + * successfully finalizing its write with a CAS, while the rest fail their + * CAS and return BUSY_E. Because all writers are tested for provenance + * compatible with that of the destination RNG (particularly, by the stratum + * test in wc_RNG_Pool_Collect2()), this interspersal is intrinsically + * benign. It can be trivially avoided by single-writer caller contract; + * the fundamental benefit of this arrangement is the avoidance of an + * initial frivolous CAS at entry to _Collect2(). + */ + +#define WC_RNG_POOL_POS(w) ((word32)((word32)(w) & 0xFFFFU)) +#define WC_RNG_POOL_EPOCH(w) ((word32)(((word32)(w) >> 16) & 0xFFFFU)) +#define WC_RNG_POOL_PACK(pos, epoch) \ + ((WC_ATOMIC_UINT_ARG)((((word32)(pos)) & 0xFFFFU) | \ + ((((word32)(epoch)) & 0xFFFFU) << 16))) + +wc_static_assert(sizeof(WC_ATOMIC_UINT_ARG) >= 4); + +/* position -> ring index. Positions run in [0, 2*poolSize). */ +static WC_INLINE word32 PoolAt(word32 pos, word32 poolSize) +{ + return (pos >= poolSize) ? (pos - poolSize) : pos; +} + +/* advance a position, wrapping at 2*poolSize. */ +static WC_INLINE word32 PoolAdvance(word32 pos, word32 by, word32 poolSize) +{ + word32 lim = poolSize * 2U; + pos += by; + return (pos >= lim) ? (pos - lim) : pos; +} + +static WC_INLINE word32 PoolUsed(word32 head, word32 tail, word32 poolSize) +{ + /* Note, the modular subtraction is unambiguous because the only publisher + * (wc_RNG_Pool_Collect2()) carefully bounds itself to the free span, so + * head never passes tail + poolSize. */ + return (head >= tail) ? (head - tail) : (head + (poolSize * 2U) - tail); +} + +/* Wipe the retired span [tail, head): pooled bytes are finished DRBG output + * at rest, and a retiring event (invalidation, fork, credited reseed) has a + * sibling lineage that may serve its identical copy -- CSP by the doctrine + * at NextSeedPurge(). Race-free by ownership: the single reader is the + * only mover of tail, and writers write only past head. */ +static WC_INLINE void PoolWipeRetired(WC_RNG *rng, word32 tail, word32 head) +{ + word32 used = PoolUsed(head, tail, (word32)rng->poolSize); + word32 t = (tail >= (word32)rng->poolSize) ? + (tail - (word32)rng->poolSize) : tail; + word32 seg = (word32)rng->poolSize - t; + if (seg > used) + seg = used; + if (seg > 0) + ForceZero(rng->pool + t, seg); + if (used > seg) + ForceZero(rng->pool, used - seg); +} + +/* Retire pooled output: any event after which pre-event bytes must not be + * served -- state invalidation, fork, a credited reseed recovering from + * invalidation, the reader's + * fail-closed path. Bumping epoch is the whole operation; see above for why + * the counters are deliberately left alone. */ +static WARN_UNUSED_RESULT int PoolPurge(WC_RNG* rng) +{ + WC_ATOMIC_UINT_ARG cur, want; + int ret; + + WC_CAS_WITH_RETRY_BEGIN_INIT_CUR(&rng->poolHead, cur, ret) { + want = WC_RNG_POOL_PACK(WC_RNG_POOL_POS(cur), + WC_RNG_POOL_EPOCH(cur) + 1U); + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &rng->poolHead, cur, want, ret); + } WC_CAS_WITH_RETRY_END; + + return ret; +} + +int wc_RNG_Pool_Alloc(WC_RNG* rng, word32 size) +{ + if ((rng == NULL) || (size < 2) || (size > 32767U)) + return BAD_FUNC_ARG; /* halves are word16; current in [0, size] */ + if (rng->pool != NULL) + return ALREADY_E; + + rng->pool = (byte*)XMALLOC(size, rng->heap, DYNAMIC_TYPE_RNG); + if (rng->pool == NULL) + return MEMORY_E; + rng->poolSize = (word16)size; + wolfSSL_Atomic_Uint_Init(&rng->poolHead, 0); + wolfSSL_Atomic_Uint_Init(&rng->poolTail, 0); + + return 0; +} + +int wc_RNG_Pool_Collect2(WC_RNG* rng_dest, WC_RNG* rng_src, word32 n) +{ + if ((rng_dest == NULL) || (rng_src == NULL)) + return BAD_FUNC_ARG; + if (rng_dest->pool == NULL) + return BAD_STATE_E; + +#ifdef WC_RNG_HAVE_RBGC + /* Pool data is served directly as DRBG output, via wc_RNG_Pool_Extract(). + * To preserve the destination's provenance guarantee (SP 800-90C + * sect. 7.3.1 item 16, no output to a predecessor), the generator stratum + * must not be deeper than the destination stratum. Contrast with stir data + * (wc_RNG_DRBG_ReseedRBGC_local() uncredited path), which has and imparts + * no provenance. */ + if (rng_src->RBGCStratum > rng_dest->RBGCStratum) + return BAD_FUNC_ARG; +#endif + + if (n == 0) + return 0; + + /* Note, a second writer can read the same head, generate into the same + * span, then lose the publication CAS, having already overwritten part of + * the winner's published bytes. This is benign -- every byte in the span + * is output of compatible provenance from one generate or the other, and + * the loser just returns BUSY_E, while no invalidation is lost either way. + * + * If interspersal of bytes from multiple producers is undesirable, the + * caller can simply arrange not to have multiple concurrent producxers -- + * this is the arrangement in the wolfSSL kernel module, for example, which + * has a single daemon (wc_linuxkm_entropy_daemon()) that is the sole pool + * collector. + */ + { + WC_ATOMIC_UINT_ARG snap; + word32 head, epoch, tail, free_sz, m, done = 0; + int ret; + + WC_ATOMIC_UINT_ARG tw; + + snap = WOLFSSL_ATOMIC_LOAD(rng_dest->poolHead); + head = WC_RNG_POOL_POS(snap); + epoch = WC_RNG_POOL_EPOCH(snap); + tw = WOLFSSL_ATOMIC_LOAD(rng_dest->poolTail); + + if (WC_RNG_POOL_EPOCH(tw) != epoch) { + /* A purge landed and the reader has not yet acknowledged it. Its + * resync discards everything published before the purge -- which + * would include anything we published now -- so there is no useful + * work here until a read happens. Reporting NOT_READY_E rather + * than success also keeps a collector that polls fullness from + * spinning: wc_RNG_Pool_Current() reports empty across this + * window, while tail still describes the retired span, so a + * free-span computation from it would say full. Those two + * disagree only here, and only until the reader resyncs. */ + return NOT_READY_E; + } + + tail = WC_RNG_POOL_POS(tw); + + free_sz = (word32)rng_dest->poolSize + - PoolUsed(head, tail, (word32)rng_dest->poolSize); + if (free_sz == 0) + return 0; /* full: success no-op */ + m = (free_sz > n) ? n : free_sz; + + /* generate directly into the unpublished span (up to two contiguous + * segments), then publish the whole of it with one CAS. */ + while (done < m) { + word32 at = + PoolAt(PoolAdvance(head, done, (word32)rng_dest->poolSize), + (word32)rng_dest->poolSize); + word32 chunk = (word32)rng_dest->poolSize - at; + if (chunk > m - done) + chunk = m - done; + ret = wc_RNG_GenerateBlock(rng_src, rng_dest->pool + at, chunk); + if (ret != 0) { + /* Abandon in place. The written bytes lie beyond head and + * are therefore unpublished -- benign in-boundary content + * awaiting overwrite. Not burned: the reader's burn span and + * ours are disjoint, and zeroing here would be + * indistinguishable from published zeros to the next writer. */ + return ret; + } + done += chunk; + } + + if (wolfSSL_Atomic_Uint_CompareExchange( + &rng_dest->poolHead, &snap, + WC_RNG_POOL_PACK( + PoolAdvance(head, m, (word32)rng_dest->poolSize), epoch))) + { + return 0; + } + else { + /* Either we're competing with another writer, or the epoch changed + * (invalidation). In either case, we return BUSY_E. + */ + return BUSY_E; + } + } +} + +int wc_RNG_Pool_Collect(WC_RNG* rng, word32 n) +{ + return wc_RNG_Pool_Collect2(rng, rng, n); +} + +int wc_RNG_Pool_Extract(WC_RNG* rng, byte* out, word32* n) +{ + WC_ATOMIC_UINT_ARG w1, w2, tw; + word32 head, epoch, tail, avail, m, done = 0; + + if ((rng == NULL) || (out == NULL) || (n == NULL)) + return BAD_FUNC_ARG; + { + int lock_ret = rng_lock_required_check(rng); + if (lock_ret != 0) + return lock_ret; + } + if (rng->pool == NULL) + return BAD_STATE_E; + +#if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) + { + int ret = rng_pid_change_check(rng); + if (ret != 0) + return ret; + } +#endif + + /* Fail closed: no serving output on behalf of an out-of-service DRBG, and + * its pooled output is unusable material at rest. A writer mid-fill sees + * the epoch move and abandons rather than publishing. */ + if (wc_RNG_DRBG_Present(rng) && (rng->status != DRBG_OK)) { + int ret = PoolPurge(rng); + if (ret != 0) + return ret; + /* Reader-owned fail-closed wipe and resync of whatever was + * pending. */ + tw = WOLFSSL_ATOMIC_LOAD(rng->poolTail); + w1 = WOLFSSL_ATOMIC_LOAD(rng->poolHead); + PoolWipeRetired(rng, WC_RNG_POOL_POS(tw), WC_RNG_POOL_POS(w1)); + WOLFSSL_ATOMIC_STORE(rng->poolTail, + WC_RNG_POOL_PACK(WC_RNG_POOL_POS(w1), + WC_RNG_POOL_EPOCH(w1))); + return RNG_FAILURE_E; + } + + tw = WOLFSSL_ATOMIC_LOAD(rng->poolTail); + w1 = WOLFSSL_ATOMIC_LOAD(rng->poolHead); + head = WC_RNG_POOL_POS(w1); + epoch = WC_RNG_POOL_EPOCH(w1); + + if (WC_RNG_POOL_EPOCH(tw) != epoch) { + /* A purge landed since our last visit. Everything published before + * it is retired: resynchronize to head and report empty. Anything + * the writer publishes after this point is post-event and stands. */ + PoolWipeRetired(rng, WC_RNG_POOL_POS(tw), head); + WOLFSSL_ATOMIC_STORE(rng->poolTail, WC_RNG_POOL_PACK(head, epoch)); +#ifdef WC_RNG_DEBUG_STATS + rng->_stats_pool_bytes_missed += *n; +#endif + return NOT_READY_E; + } + + tail = WC_RNG_POOL_POS(tw); + avail = PoolUsed(head, tail, (word32)rng->poolSize); + if (avail == 0) { +#ifdef WC_RNG_DEBUG_STATS + rng->_stats_pool_bytes_missed += *n; +#endif + return NOT_READY_E; + } + m = (avail > *n) ? *n : avail; + + while (done < m) { + word32 at = PoolAt(PoolAdvance(tail, done, (word32)rng->poolSize), + (word32)rng->poolSize); + word32 chunk = (word32)rng->poolSize - at; + if (chunk > m - done) + chunk = m - done; + XMEMCPY(out + done, rng->pool + at, chunk); + /* burn on the way out the door, before the span is republished. + * [tail, tail+m) and the writer's [head, head+m') are disjoint by + * construction, so this can never erase published bytes. */ + ForceZero(rng->pool + at, chunk); + done += chunk; + } + + /* The linearization point. Both loads read head and epoch as one word, + * so a purge anywhere in our window is caught here -- epoch moves and + * never moves back. Our bytes then predate the event and must not be + * served, so discard the whole pre-event span rather than advancing. */ + w2 = WOLFSSL_ATOMIC_LOAD(rng->poolHead); + if (WC_RNG_POOL_EPOCH(w2) != epoch) { + /* We own [tail, old head) exclusively; the purged span is wiped + * before it is skipped. */ + PoolWipeRetired(rng, tail, head); + WOLFSSL_ATOMIC_STORE(rng->poolTail, + WC_RNG_POOL_PACK(WC_RNG_POOL_POS(w2), + WC_RNG_POOL_EPOCH(w2))); +#ifdef WC_RNG_DEBUG_STATS + rng->_stats_pool_bytes_missed += *n; +#endif + return BUSY_E; + } + + /* Sole writer of poolTail: a plain store, no CAS on the reader path. */ + WOLFSSL_ATOMIC_STORE(rng->poolTail, + WC_RNG_POOL_PACK( + PoolAdvance(tail, m, (word32)rng->poolSize), + epoch)); + +#ifdef WC_RNG_DEBUG_STATS + rng->_stats_pool_bytes_produced += m; + rng->_stats_pool_bytes_missed += *n - m; /* shortfall on partial serve */ +#endif + *n = m; + + return 0; +} + +int wc_RNG_Pool_Current(WC_RNG* rng, word32* n) +{ + if ((rng == NULL) || (n == NULL)) + return BAD_FUNC_ARG; + if (rng->pool != NULL) { + WC_ATOMIC_UINT_ARG w = WOLFSSL_ATOMIC_LOAD(rng->poolHead); + WC_ATOMIC_UINT_ARG tw = WOLFSSL_ATOMIC_LOAD(rng->poolTail); + /* a purge not yet observed by the reader retires everything + * published before it: report empty. */ + *n = (WC_RNG_POOL_EPOCH(tw) != WC_RNG_POOL_EPOCH(w)) ? 0 : + PoolUsed(WC_RNG_POOL_POS(w), WC_RNG_POOL_POS(tw), + (word32)rng->poolSize); + } + else { + *n = 0; + } + return 0; +} +#endif /* WC_RNG_HAVE_POOL */ + +#ifdef WC_RNG_HAVE_RBGC + +/* Unified mechanics for the four wc_InitRng*RBGC() APIs: instantiate a child + * DRBG subordinate to parent in an SP 800-90C RBG chain, drawing its seed + * material from parent's generate function in place of the module's seed + * source; every other aspect of instantiation -- seed byte accounting, nonce + * handling, failure disposition -- is _InitRng()'s, identically to + * wc_InitRngNonce_ex() with the omission of health testing, which is + * superfluous when a healthy DRBG generates the seed data. + * + * SP 800-90C accounting: the child's claimable security strength is capped by + * the parent's, and the child has no formal prediction resistance. The child's + * own reseeds default to the module's seed source (the reseed-interval + * backstop, wc_RNG_DRBG_Reseed_Now()); wc_RNG_DRBG_ReseedRBGC() reseeds it from + * a supplied root instead. In configurations with no DRBG (RDRAND et al.), the + * child comes up as _InitRng() dictates for such configurations and the parent + * is not consulted. + * + * Exactly one of new_child_stack (caller-provided WC_RNG, uninitialized) and + * new_child_heap (callee-allocated from parent's heap, to be released with + * wc_rng_free()) must be non-NULL. The caller must hold exclusive access + * to parent for the duration of the call, as for all WC_RNG operations; the + * spawn debits parent's reseed counter by one generate. + */ +static WARN_UNUSED_RESULT int SpawnRngRBGC( + WC_RNG* new_child_stack, WC_RNG** new_child_heap, + WC_RNG* parent, const byte* nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + word32 flags) +{ + WC_RNG* child = new_child_stack; + int ret; + + if (parent == NULL) + return BAD_FUNC_ARG; + + if ((new_child_stack == NULL) == (new_child_heap == NULL)) + return BAD_FUNC_ARG; + + if (new_child_stack == parent) + return BAD_FUNC_ARG; + + if ((nonce == NULL) && (nonceSz > 0)) + return BAD_FUNC_ARG; + + if (new_child_heap != NULL) { + *new_child_heap = (WC_RNG*)XMALLOC(sizeof(WC_RNG), parent->heap, + DYNAMIC_TYPE_RNG); + if (*new_child_heap == NULL) + return MEMORY_E; + child = *new_child_heap; + } + + ret = _InitRng(child, nonce, nonceSz, perso, persoSz, parent->heap, + #if defined(WOLF_CRYPTO_CB) + parent->devId, + #else + INVALID_DEVID, + #endif + parent, flags); + + if (new_child_heap != NULL) { + if (ret != 0) { + XFREE(child, parent->heap, DYNAMIC_TYPE_RNG); + *new_child_heap = child = NULL; + } + } + + return ret; +} + +int wc_InitRngRBGC(WC_RNG* child, WC_RNG* parent, word32 flags) +{ + return SpawnRngRBGC(child, NULL, parent, NULL, 0, NULL, 0, flags); +} + +int wc_InitRngNonceRBGC(WC_RNG* child, WC_RNG* parent, const byte* nonce, + word32 nonceSz, const byte *perso, word32 persoSz, + word32 flags) +{ + return SpawnRngRBGC(child, NULL, parent, nonce, nonceSz, perso, persoSz, + flags); +} + +#ifndef WC_NO_CONSTRUCTORS +int wc_InitRngRBGC_New(WC_RNG** child, WC_RNG* parent, word32 flags) +{ + return SpawnRngRBGC(NULL, child, parent, NULL, 0, NULL, 0, flags); +} + +int wc_InitRngNonceRBGC_New(WC_RNG** child, WC_RNG* parent, const byte* nonce, + word32 nonceSz, const byte *perso, word32 persoSz, + word32 flags) +{ + return SpawnRngRBGC(NULL, child, parent, nonce, nonceSz, perso, persoSz, + flags); +} +#endif /* !WC_NO_CONSTRUCTORS */ + +/* Immediately reseed rng from root's generate output -- the reseed counterpart + * of the wc_InitRng*RBGC() spawn. The reseed counter is reset iff the reseed + * succeeds and credited. The nonce, if any, rides the same reseed derivation + * as (uncredited) additional input. The caller must hold exclusive access to + * BOTH rng and root. On credited success, rng is (or remains) a chain RNG: + * its current seed period is chain-backed, so RBGCStratum is set, and it is not + * usable as a reseed root. + * + * By default, consistent with SP 800-90C 7.1.2.2, reseed by an RBGC root is + * allowed, provided its stratum is less than the child's stratum (no stratum + * downgrade allowed, no cycles possible); build-time option + * WC_RNG_NO_RBGC_RESEED restricts credited reseeds to primary-seeded roots. + * + * Uncredited reseeds by wc_RNG_DRBG_ReseedRBGC_local() are unconditionally + * permitted (these are just stirs). + */ +static WARN_UNUSED_RESULT int wc_RNG_DRBG_ReseedRBGC_local( + WC_RNG* rng, WC_RNG* root, + const byte* nonce, word32 nonceSz, + int credited) +{ +#ifdef WOLFSSL_SMALL_STACK_CACHE + byte *seed; +#else + byte seed[SEED_SZ]; +#endif + int ret; + + if ((rng == NULL) || (root == NULL) || (rng == root) || + ((nonce == NULL) && (nonceSz > 0))) + { + return BAD_FUNC_ARG; + } + + ret = rng_lock_required_check(rng); + if (ret != 0) + return ret; + + ret = rng_lock_required_check(root); + if (ret != 0) + return ret; + + if (credited) { + if (root->RBGCStratum >= WC_MAX_SINT_OF(int)) + return SEQ_OVERFLOW_E; + else if (root->RBGCStratum == WC_RNG_RBGC_USER_SEED_STRATUM - 1) + return SEQ_OVERFLOW_E; + } + /* else the RBGC strata are irrelevant -- stir data has no implication of + * provenance, and can legitimately be wall clock time or even strings of + * zeros. */ + +#ifdef WOLFSSL_SMALL_STACK_CACHE + seed = rng->newSeed_buf; +#endif + + if (credited && (root->RBGCStratum > 0) +#ifndef WC_RNG_NO_RBGC_RESEED + && (root->RBGCStratum >= rng->RBGCStratum) +#else + /* Credited reseed from root only, by policy. */ +#endif + ) + { + return BAD_FUNC_ARG; + } + + if (rng->status != DRBG_OK) + return RNG_FAILURE_E; + + if (! wc_RNG_DRBG_Present(rng)) { + return 0; + } + + ret = wc_RNG_GenerateBlock(root, seed, SEED_SZ); + if (ret == 0) { + if (credited) { + ret = Hash_DRBG_Reseed(rng, seed, SEED_SZ, nonce, nonceSz, + 0 /* in_bracketed_consume */); + if (ret == 0) { + rng->RBGCStratum = root->RBGCStratum + 1; + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_RBGC_reseeds; + #endif + } + } + else { + ret = wc_RNG_DRBG_Stir_Nonce(rng, seed, SEED_SZ, nonce, + nonceSz); + } + } + ForceZero(seed, SEED_SZ); + + return ret; +} + +int wc_RNG_DRBG_ReseedRBGC(WC_RNG* rng, WC_RNG* root, const byte* nonce, + word32 nonceSz) +{ + return wc_RNG_DRBG_ReseedRBGC_local(rng, root, nonce, nonceSz, 1); +} + +int wc_RNG_DRBG_StirRBGC(WC_RNG* rng, WC_RNG* root, + const byte* nonce, word32 nonceSz) +{ + return wc_RNG_DRBG_ReseedRBGC_local(rng, root, nonce, nonceSz, 0); +} + +#endif /* WC_RNG_HAVE_RBGC */ + +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) + +static WARN_UNUSED_RESULT int PollAndReSeed(WC_RNG* rng, const byte* additional, + word32 additionalSz) +{ + int ret = WC_NO_ERR_TRACE(DRBG_NEED_RESEED); + int devId = INVALID_DEVID; +#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) + devId = rng->devId; +#endif + if (wc_RNG_HealthTestLocal(rng, 1, rng->heap, devId) == 0) { + #if defined(WOLFSSL_SMALL_STACK_CACHE) + byte* newSeed = rng->newSeed_buf; + ret = DRBG_SUCCESS; + #elif defined(WOLFSSL_SMALL_STACK) + byte* newSeed = (byte*)XMALLOC(SEED_SZ + SEED_BLOCK_SZ, rng->heap, + DYNAMIC_TYPE_SEED); + ret = (newSeed == NULL) ? MEMORY_E : DRBG_SUCCESS; + #else + byte newSeed[SEED_SZ + SEED_BLOCK_SZ]; + ret = DRBG_SUCCESS; + #endif + if (ret == DRBG_SUCCESS) { + #ifdef WC_RNG_SEED_CB + if (seedCb == NULL) { + ret = DRBG_NO_SEED_CB; + } + else { + ret = seedCb(&rng->seed, newSeed, SEED_SZ + SEED_BLOCK_SZ); + if (ret != 0) { + #ifdef WC_VERBOSE_RNG + WOLFSSL_DEBUG_PRINTF("ERROR: seedCb() in PollAndReSeed() " + "failed with err %d", ret); + #endif + ret = DRBG_FAILURE; + } + } + #else + ret = wc_GenerateSeed(&rng->seed, newSeed, + SEED_SZ + SEED_BLOCK_SZ); + if (ret != 0) { + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_seed_failures; + #endif + #ifdef WC_VERBOSE_RNG + WOLFSSL_DEBUG_PRINTF( + "ERROR: wc_GenerateSeed() in PollAndReSeed() failed with " + "err %d", ret); + #endif + ret = DRBG_FAILURE; + } + #endif + } + if (ret == DRBG_SUCCESS) { + ret = wc_RNG_TestSeed(newSeed, SEED_SZ + SEED_BLOCK_SZ); + #ifdef WC_RNG_DEBUG_STATS + if (ret != DRBG_SUCCESS) + ++rng->_stats_seed_failures; + #endif + #ifdef WC_VERBOSE_RNG + if (ret != DRBG_SUCCESS) + WOLFSSL_DEBUG_PRINTF( + "ERROR: wc_RNG_TestSeed() in PollAndReSeed() returned " + "err %d.", ret); + #endif + } + if (ret == DRBG_SUCCESS) { + ret = Hash_DRBG_Reseed(rng, newSeed + SEED_BLOCK_SZ, SEED_SZ, + additional, additionalSz, + 0 /* in_bracketed_consume */); + + #ifdef WC_RNG_HAVE_RBGC + if (ret == 0) + rng->RBGCStratum = 0; + #endif + } + #if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_SMALL_STACK_CACHE) + if (newSeed != NULL) { + ForceZero(newSeed, SEED_SZ + SEED_BLOCK_SZ); + } + XFREE(newSeed, rng->heap, DYNAMIC_TYPE_SEED); + #else + /* newSeed is a byte[] in the plain build but a byte* in the + * SMALL_STACK_CACHE build, so use the explicit buffer length (not + * sizeof) to zero the whole reseed entropy buffer in both cases. */ + #ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("PollAndReSeed newSeed", newSeed, + SEED_SZ + SEED_BLOCK_SZ); + #endif + ForceZero(newSeed, SEED_SZ + SEED_BLOCK_SZ); + #ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(newSeed, SEED_SZ + SEED_BLOCK_SZ); + #endif + #endif + } + else { + ret = DRBG_CONT_FAILURE; + } + + return ret; +} + +/* Immediately reseed rng from the module's built-in or previously registered + * seed source, exactly as the WC_RESEED_INTERVAL backstop does during a + * generate operation: the gathered seed is health-tested and applied by the + * module's own reseed function, and the reseed counter is reset iff the + * reseed succeeds. If nonceSz > 0, nonce is incorporated into the same + * reseed derivation as (uncredited) additional input, with the semantics of + * wc_RNG_DRBG_Stir(), in a single state transition. On failure + * the reseed counter is not reset and rng->status reflects the failure + * exactly as a generate-time reseed failure would. The caller must hold + * exclusive access to rng, as for all WC_RNG operations. */ +int wc_RNG_DRBG_Reseed_Now(WC_RNG* rng, const byte* nonce, word32 nonceSz) +{ + int ret; + + if (rng == NULL) + return BAD_FUNC_ARG; + if ((nonce == NULL) && (nonceSz > 0)) + return BAD_FUNC_ARG; + ret = rng_lock_required_check(rng); + if (ret != 0) + return ret; + + /* Mirror wc_RNG_GenerateBlock(): only an in-service DRBG may reseed. */ + if (rng->status != DRBG_OK) + return RNG_FAILURE_E; + + if (! wc_RNG_DRBG_Present(rng)) { + /* No DRBG instantiated -- nothing to reseed (RDRAND et al.). */ + return 0; + } + + ret = PollAndReSeed(rng, nonce, nonceSz); + + /* Identical outcome mapping to the generate-path reseed. */ + if (ret == DRBG_SUCCESS) { + ret = 0; + } + else if (ret == WC_NO_ERR_TRACE(DRBG_CONT_FAILURE)) { + ret = DRBG_CONT_FIPS_E; + rng->status = DRBG_CONT_FAILED; + } + else { + ret = RNG_FAILURE_E; + rng->status = DRBG_FAILED; + } + + return ret; +} + +#ifdef WC_RNG_HAVE_NEXT_SEED + + /* Banked-next-seed services. _NextSeedGenerate() banks up to n more + * bytes from the module's seed source (clamped to the space remaining; + * ALREADY_E when the bank is ready or being consumed), health-testing + * and publishing the bank when it completes (NOT_READY_E when the health + * test could not run and the call should simply be retried); a + * scheduling daemon may call it without owning the instance. + * _NextSeedCurrent() reports the raw aperture value (racy snapshot). + * _NextSeedNow() claims a ready bank and performs a source-free + * credited reseed with it -- safe in atomic context -- or returns + * NOT_READY_E when no bank is ready; _NextSeedNow_Nonce() is the same + * with a nonce as uncredited additional input. All report + * MISSING_RNG_E for an instance with no DRBG (RDRAND et al.). The + * caller must own the instance for _NextSeedNow[_Nonce](). */ + +/* Banked-next-seed protocol. + * + * Entropy is gathered incrementally, in-boundary, from the + * module's seed source by wc_RNG_DRBG_NextSeedGenerate(), and consumed + * (source-free, atomic-context-safe) by wc_RNG_DRBG_NextSeedNow(). + * nextSeedLen is the hand-off aperture: values in [0, bank length) + * count banked bytes (filling); WC_DRBG_NEXT_SEED_READY marks a complete, + * health-tested bank; WC_DRBG_NEXT_SEED_CONSUMING marks exclusive ownership by a + * consumer. + * + * DRBG_internal.nextSeedLen (and the DRBG_SHA512_internal analog) is a + * wolfSSL_Atomic_Int with C-native atomic semantics (release stores, acquire + * loads, sequentially consistent RMWs): + * + * The single scheduling daemon (one writer per instance, by contract) advances + * the fill with the AddFetch in wc_RNG_DRBG_NextSeedGenerate() and publishes by + * storing _READY; a consumer claims with a CAS from _READY to _CONSUMING, + * consumes, zeroizes, and releases to _EMPTY. Ownership-taking transitions are + * atomic RMWs and releases are atomic stores with release semantics, so the + * hand-off is ordered on all supported targets, and the daemon never touches + * any other DRBG state. Gathering draws from the configured / installed + * entropy source directly, never from rng->seed, so the daemon also does not + * race an owner's own source reseed. */ + +/* Locate the aperture members for rng's live DRBG. Returns nonzero when no + * DRBG is instantiated (RDRAND et al.). */ +static WARN_UNUSED_RESULT WC_INLINE int NextSeedPtrs(WC_RNG* rng, + byte** seed, word32 *nextSeedSz, + wolfSSL_Atomic_Int** len, + int **nextSeedRBGCStratum) +{ +#ifndef NO_SHA256 + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL)) { + *seed = ((DRBG_internal*)rng->drbg)->nextSeed; + *nextSeedSz = (word32)sizeof(((DRBG_internal*)rng->drbg)->nextSeed); + *len = &((DRBG_internal*)rng->drbg)->nextSeedLen; + if (nextSeedRBGCStratum) { + #ifdef WC_RNG_HAVE_RBGC + *nextSeedRBGCStratum = + &((DRBG_internal*)rng->drbg)->nextSeedRBGCStratum; + #else + *nextSeedRBGCStratum = NULL; + #endif + } + return 0; + } +#endif +#ifdef WOLFSSL_DRBG_SHA512 + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL)) { + *seed = ((DRBG_SHA512_internal*)rng->drbg512)->nextSeed; + *nextSeedSz = + (word32)sizeof(((DRBG_SHA512_internal*)rng->drbg512)->nextSeed); + *len = &((DRBG_SHA512_internal*)rng->drbg512)->nextSeedLen; + if (nextSeedRBGCStratum) { + #ifdef WC_RNG_HAVE_RBGC + *nextSeedRBGCStratum = + &((DRBG_SHA512_internal*)rng->drbg512)->nextSeedRBGCStratum; + #else + *nextSeedRBGCStratum = NULL; + #endif + } + return 0; + } +#endif + return MISSING_RNG_E; +} + +static WARN_UNUSED_RESULT WC_INLINE int NextStirPtrs(WC_RNG* rng, byte** seed, + word32 *nextSeedSz, + wolfSSL_Atomic_Int** len) +{ +#ifndef NO_SHA256 + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL)) { + *seed = ((DRBG_internal*)rng->drbg)->nextStir; + *nextSeedSz = + (word32)sizeof(((DRBG_internal*)rng->drbg)->nextStir); + *len = &((DRBG_internal*)rng->drbg)->nextStirLen; + return 0; + } +#endif +#ifdef WOLFSSL_DRBG_SHA512 + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL)) { + *seed = ((DRBG_SHA512_internal*)rng->drbg512)->nextStir; + *nextSeedSz = + (word32)sizeof(((DRBG_SHA512_internal*)rng->drbg512)->nextStir); + *len = &((DRBG_SHA512_internal*)rng->drbg512)->nextStirLen; + return 0; + } +#endif + return MISSING_RNG_E; +} + +/* Bank up to n more bytes of seed material from a supplied root RNG into + * rng's next-seed bank. Callable without owning the instance (the scheduling + * daemon's entry point); deliberately independent of rng->status so that + * banking can proceed for any instantiated DRBG. n is clamped to the space + * remaining; a ready or consuming bank is signaled with ALREADY_E. On + * completing the bank, the material is health-tested (wc_RNG_TestSeed()) and + * published; a failed test consumes the material (use-once) and returns the + * test's error, leaving an empty bank for the next cycle. A gather failure + * leaves the partial bank intact for retry. */ +static WARN_UNUSED_RESULT int wc_RNG_DRBG_NextSeedGenerate_local( + WC_RNG* rng, WC_RNG* root, + const byte* nonce, word32 n) +{ + int claim_ret; + byte* seed = NULL; + wolfSSL_Atomic_Int* lenp = NULL; + int* nextSeedRBGCStratum_p = NULL; + WC_ATOMIC_INT_ARG cur; + word32 nextSeedSz = 0; + int ret; + + if ((rng == NULL) || (n == 0) || (rng == root)) + return BAD_FUNC_ARG; + + if ((root != NULL) && (nonce != NULL)) + return BAD_FUNC_ARG; + + /* Note, rng need not be locked -- that's the whole point of the + * banked-next-seed aperture protocol. + */ + + if (root) { + ret = rng_lock_required_check(root); + if (ret != 0) + return ret; +#ifdef WC_RNG_HAVE_RBGC + if ((root->RBGCStratum > 0) + #ifndef WC_RNG_NO_RBGC_RESEED + && (root->RBGCStratum >= rng->RBGCStratum) + #else + /* Credited reseed from root only, by policy. */ + #endif + ) + { + return BAD_FUNC_ARG; + } + if (root->RBGCStratum >= WC_MAX_SINT_OF(int)) + return SEQ_OVERFLOW_E; + else if (root->RBGCStratum == WC_RNG_RBGC_USER_SEED_STRATUM - 1) + return SEQ_OVERFLOW_E; +#else + return NOT_COMPILED_IN; +#endif + } + + if (nonce) + ret = NextStirPtrs(rng, &seed, &nextSeedSz, &lenp); + else + ret = NextSeedPtrs(rng, &seed, &nextSeedSz, &lenp, + &nextSeedRBGCStratum_p); + if (ret != 0) { + /* No DRBG instantiated -- nothing to bank (RDRAND et al.). */ + return ret; + } + + cur = WOLFSSL_ATOMIC_LOAD(*lenp); + if (nonce) { + if ((cur < 0) || (cur >= (WC_ATOMIC_INT_ARG)nextSeedSz)) { + /* The accumulator is full (READY) or being consumed: fold the + * arriving entropy in rather than discarding it. The sentinel + * check is deliberately advisory in this lane -- a torn read by a + * racing consumer, or an XOR lost to a racing depositor, yields + * interleaved stir material of compatible provenance, which is + * always harmless. */ + if (n > nextSeedSz) + n = nextSeedSz; + xorbuf(seed, nonce, n); + return 0; + } + } + else { + /* Producer claim: exactly one banker may fill or publish at a + * time. Concurrent fills would be tolerable as BYTES (a torn + * mix is still entropy) but poisonous as CLAIMS: interleaved + * primary/RBGC production could publish material under the + * wrong stratum or health-test disposition. The claim makes + * produce-side exclusivity a CAS, matching the consume side's + * READY -> CONSUMING claim. This lane only: the uncredited + * accumulator above stays multi-writer by design. */ + WC_CAS_WITH_RETRY_BEGIN(lenp, cur, claim_ret) { + if ((cur == WC_DRBG_NEXT_SEED_PRODUCING) || + (cur == WC_DRBG_NEXT_SEED_PURGED)) + { + /* A producer is in flight (or unwinding a purge): + * retryable on a later banking cycle. Traced: the + * competing producer -- typically the entropy daemon + * -- is the diagnosis a surprised caller needs. */ + return BUSY_E; + } + if (cur < 0) { + /* Ready or consuming -- nothing to do. */ + return WC_NO_ERR_TRACE(ALREADY_E); /* not an error */ + } + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Int_CompareExchange, + lenp, cur, + WC_DRBG_NEXT_SEED_PRODUCING, + claim_ret); + } WC_CAS_WITH_RETRY_END; + if (claim_ret != 0) { + /* Aborted claim (a port's retry clause): nothing claimed, + * nothing moved. Proceeding would fill and adjudicate an + * unclaimed aperture -- the torn-claims disease the + * producer mutex exists to prevent. */ + return claim_ret; + } + if (cur == (WC_ATOMIC_INT_ARG)nextSeedSz) { + /* Complete but unpublished (interrupted between fill + * completion and publication): retry the health test and + * publication below. */ + n = 0; + } + /* From here to release/publication the aperture word is + * PRODUCING; fill progress lives only in the local cur, and + * every exit passes through NextSeedProducerRelease(). */ + } + + if (n > 0) { + if (nonce != NULL) { + if (n > nextSeedSz - (word32)cur) + n = nextSeedSz - (word32)cur; + XMEMCPY(seed + cur, nonce, n); + } + else +#ifdef WC_RNG_HAVE_RBGC + if (root) { + /* If primary seed bytes were carried forward, reset now to avoid + * wc_RNG_TestSeed() at completion. Local only: the word is + * held at PRODUCING. */ + if ((cur > 0) && (*nextSeedRBGCStratum_p == 0)) + cur = 0; + + if (n > nextSeedSz - (word32)cur) + n = nextSeedSz - (word32)cur; + + ret = wc_RNG_GenerateBlock(root, seed + cur, n); + if (ret != 0) { + /* Partial bank preserved -- retry on a later cycle. + * (If a purge landed meanwhile, the release discards + * instead; the draw failure is the more informative + * code and wins over the release's BUSY_E.) + * + * Note, a failed release cannot brick the RNG and needs no + * disposition at the several (void) sites below: its one cause + * is a purge's _PURGED repaint (producers cannot claim a + * PRODUCING word, consumers exchange only from READY, and the + * purge is the sole other writer), and the BUSY arm is its own + * compensation -- it wipes and reopens the aperture EMPTY + * before returning. Afterward the world is fully consistent: + * aperture empty and healthy, rng->status untouched, recovery + * proceeding through the event's normal channels. BUSY_E is + * information, not a condition awaiting action; the caller's + * own error outranks it wherever one is in flight. Cost of + * swallowing it: at worst one silent refill from offset + * zero. + */ + (void)NextSeedProducerRelease(lenp, seed, nextSeedSz, cur); + return ret; + } + + /* If RBGC seed bytes were carried forward, make sure we're + * pessimistic about the RBGC stratum. */ + if ((cur == 0) || (*nextSeedRBGCStratum_p < root->RBGCStratum + 1)) + *nextSeedRBGCStratum_p = root->RBGCStratum + 1; + } + else +#endif /* WC_RNG_HAVE_RBGC */ + { + /* wc_GenerateSeed() must be called completely independent of rng, + * aside from the memory aperture itself. For safety, we pass a + * dummy OS_Seed, which will be ignored by the wc_GenerateSeed() + * typically used in conjunction with WC_RNG_HAVE_NEXT_SEED. + */ + struct OS_Seed os; + + /* Named-member init: layout-proof against OS_Seed growing or + * reordering members under its several config axes. */ + XMEMSET(&os, 0, sizeof(os)); +#ifndef USE_WINDOWS_API + os.fd = -1; +#endif +#ifdef WOLF_CRYPTO_CB + os.devId = INVALID_DEVID; +#endif + +#ifdef WC_RNG_HAVE_RBGC + /* If RBGC seed bytes were carried forward, reset now to avoid + * intermixture and force wc_RNG_TestSeed() at completion. + * Local only: the word is held at PRODUCING. */ + if ((cur > 0) && (*nextSeedRBGCStratum_p > 0)) + cur = 0; +#endif + + if (n > nextSeedSz - (word32)cur) + n = nextSeedSz - (word32)cur; + + ret = wc_GenerateSeed(&os, seed + cur, n); + if (ret != 0) { + /* Partial bank preserved -- retry on a later cycle. + * (If a purge landed meanwhile, the release discards + * instead; the seed-gather failure is the more + * informative code and wins over the release's + * BUSY_E.) */ + (void)NextSeedProducerRelease(lenp, seed, nextSeedSz, cur); + return ret; + } + +#ifdef WC_RNG_HAVE_RBGC + *nextSeedRBGCStratum_p = 0; +#endif + } + + cur += (int)n; + if (nonce != NULL) + WOLFSSL_ATOMIC_STORE(*lenp, cur); + } + + if (nonce == NULL && cur < (WC_ATOMIC_INT_ARG)nextSeedSz) { + /* Partial credited fill this call: release the claim back to + * the fill offset for a later cycle to resume. A purge + * meanwhile discards instead, and the release's BUSY_E + * percolates -- returning 0 would claim banked progress the + * purge just evaporated. */ + return NextSeedProducerRelease(lenp, seed, nextSeedSz, cur); + } + + if (cur == (WC_ATOMIC_INT_ARG)nextSeedSz) { + if (nonce != NULL) { + WOLFSSL_ATOMIC_STORE(*lenp, WC_DRBG_NEXT_SEED_READY); + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_nextstirs_banked; + #endif + return 0; + } +#ifdef WC_RNG_HAVE_RBGC + /* If RBGC bytes were used for the reseed, then we can skip + * wc_RNG_TestSeed(). */ + if (*nextSeedRBGCStratum_p > 0) { + ret = NextSeedProducerRelease(lenp, seed, nextSeedSz, + WC_DRBG_NEXT_SEED_READY); + if (ret != 0) { + /* Purged while producing (BUSY_E): nothing banked; + * post-event material wanted. Retryable. */ + return ret; + } + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_nextseedsbanked; + #endif + return 0; + } +#endif + /* Bank complete: health-test now, in advance of consumption, so + * that wc_RNG_DRBG_NextSeedNow() is pure computation. */ + ret = wc_RNG_TestSeed(seed, nextSeedSz); + if (ret == 0) { + ret = NextSeedProducerRelease(lenp, seed, nextSeedSz, + WC_DRBG_NEXT_SEED_READY); + if (ret != 0) { + /* Purged while producing (BUSY_E): nothing banked; + * post-event material wanted. Retryable. */ + return ret; + } + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_nextseedsbanked; + #endif + return 0; + } + else if (ret == WC_NO_ERR_TRACE(MEMORY_E)) { + /* wc_RNG_TestSeed() did nothing with the data -- not + * dispositive. Release complete-but-unpublished for a + * later retry; a purge-discard's BUSY_E percolates (the + * retry cause is then the purge, not the test). */ + ret = NextSeedProducerRelease(lenp, seed, nextSeedSz, + (WC_ATOMIC_INT_ARG)nextSeedSz); + if (ret != 0) + return ret; + return NOT_READY_E; + } + else if ((ret == WC_NO_ERR_TRACE(ENTROPY_RT_E)) || + (ret == WC_NO_ERR_TRACE(ENTROPY_APT_E))) + { + /* Use-once: a failed test consumes the material. */ + #ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_seed_failures; + #endif + /* Use-once on a failed test is enforced by the sentinel + * alone: an EMPTY aperture is never consumed, and the next + * fill overwrites from offset zero. The buffer is never + * zeroized (house rule for the seed apertures). Burn and + * purge-discard converge on EMPTY; the release handles + * both, and the health-test failure is the more + * informative code and wins over the release's BUSY_E. */ + /* Sentinel-only by doctrine: rejection here is deterministic + * on the bytes (RCT/APT), so every sibling lineage rejects the + * identical material -- no copy is ever consumed anywhere. */ + (void)NextSeedProducerRelease(lenp, seed, nextSeedSz, + WC_DRBG_NEXT_SEED_EMPTY); + + /* The health-test failure belongs to the depositor's seed + * collection, not to the destination RNG. The depositor collects + * via wc_GenerateSeed(), banks into rng's aperture, and tests + * before publishing; on failure the aperture is reset to _EMPTY, + * so nothing untested is ever visible to rng. rng is a passive + * destination here -- it did not consume the material, and its + * state, status and reseed schedule are untouched. Do not mark it + * failed. + * + * PollAndReSeed() looks similar and is not: there, rng is reseeding + * itself from its own seed source, the tested material is on the + * path into its own state, and a failure indeed means that that + * instance's source has failed. DRBG_FAILED is correct there and + * wrong here. Per-instance attribution is meaningful, not + * arbitrary: seed sources are frequently core-local (RDSEED among + * them), so one instance's source can fail while its siblings' are + * healthy. + * + * Bigger picture: An RCT/APT failure is an entropy-source event (SP + * 800-90B 4.4), and wc_RNG_TestSeed's cutoffs carry a designed + * false-positive rate. A terminal response from a thread that is + * not the instance's owner would effectively be a remote kill + * primitive on a tuned statistical alarm. + */ + + return ret; + } + else { + /* Buggy or brokey. Release complete-but-unpublished; the + * material is untested but intact, and a later cycle + * re-adjudicates. The primary error is the more + * informative code and wins over a purge-discard's + * BUSY_E. */ + (void)NextSeedProducerRelease(lenp, seed, nextSeedSz, + (WC_ATOMIC_INT_ARG)nextSeedSz); + return ret; + } + } + + return 0; +} + +#ifdef WC_RNG_HAVE_RBGC +int wc_RNG_DRBG_NextSeedGenerate_RBGC(WC_RNG* rng, WC_RNG *root, word32 n) { + if (root == NULL) + return BAD_FUNC_ARG; + return wc_RNG_DRBG_NextSeedGenerate_local(rng, root, NULL, n); +} +#endif + +int wc_RNG_DRBG_NextSeedGenerate(WC_RNG* rng, word32 n) { + return wc_RNG_DRBG_NextSeedGenerate_local(rng, NULL, NULL, n); +} + +/* Report the raw aperture value: a racy snapshot by design. Values in [0, bank + * length) count banked bytes; WC_DRBG_NEXT_SEED_READY and + * WC_DRBG_NEXT_SEED_CONSUMING indicate a ready or in-consumption bank, + * respectively. With no DRBG instantiated, reports WC_DRBG_NEXT_SEED_EMPTY. */ +int wc_RNG_DRBG_NextSeedCurrent(WC_RNG* rng, WC_ATOMIC_INT_ARG* n) +{ + byte* seed; + wolfSSL_Atomic_Int* lenp; + word32 nextSeedSz; + + if ((rng == NULL) || (n == NULL)) + return BAD_FUNC_ARG; + + if (NextSeedPtrs(rng, &seed, &nextSeedSz, &lenp, NULL) != 0) { + *n = WC_DRBG_NEXT_SEED_EMPTY; + return 0; + } + + *n = *lenp; + return 0; +} + +/* Consume a ready next-seed bank in an immediate credited reseed. The caller + * must own the instance. Source-free by construction -- the material was + * gathered from the module's seed source and health-tested at bank time -- so + * consumption is pure computation and safe in atomic context: the one credited + * primary reseed shape with that property. Distinct protocol results: + * NOT_READY_E when no bank is ready (nothing consumed), MISSING_RNG_E when the + * instance has no DRBG (RDRAND et al.) -- both deliberately loud, so a direct + * caller must demonstrate it understands the instance it holds. (The + * WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED checkout arm is return-agnostic by + * construction and needs neither.) Use-once: the bank is consumed by the + * attempt, success or failure. Note that a banked reseed can never provide SP + * 800-90 prediction resistance (the material predates the request by + * construction); wc_RNG_DRBG_Reseed_Now() remains the live-gather shape. */ +int wc_RNG_DRBG_NextSeedNow_Nonce(WC_RNG* rng, const byte* nonce, + word32 nonceSz) +{ + byte* seed; + wolfSSL_Atomic_Int* lenp; + word32 nextSeedSz; + int *nextSeedRBGCStratum_p; + WC_ATOMIC_INT_ARG expected = WC_DRBG_NEXT_SEED_READY; + int ret; + + if (rng == NULL) + return BAD_FUNC_ARG; + + if ((nonce == NULL) && (nonceSz != 0)) + return BAD_FUNC_ARG; + + ret = rng_lock_required_check(rng); + if (ret != 0) + return ret; + + /* Mirror wc_RNG_GenerateBlock(): only an in-service DRBG may reseed. */ + if (rng->status != DRBG_OK) + return RNG_FAILURE_E; + + ret = NextSeedPtrs(rng, &seed, &nextSeedSz, &lenp, &nextSeedRBGCStratum_p); + if (ret != 0) { + /* No DRBG instantiated -- nothing to reseed (RDRAND et al.). */ + return ret; + } + + if (! wolfSSL_Atomic_Int_CompareExchange(lenp, &expected, + WC_DRBG_NEXT_SEED_CONSUMING)) + { + /* No ready bank -- nothing consumed; reported distinctly. */ + return NOT_READY_E; + } + + /* Identical byte accounting to PollAndReSeed(): the SEED_BLOCK_SZ + * prefix was consumed by the bank-time health testing. */ + ret = Hash_DRBG_Reseed(rng, seed + SEED_BLOCK_SZ, SEED_SZ, nonce, nonceSz, + 1 /* in_bracketed_consume */); + + /* Use-once: consumed by the attempt, success or not. The scrub must be + * visible before the aperture reopens. */ + ForceZero(seed, WC_DRBG_NEXT_SEED_LEN); + + /* Release by CAS -- one-shot, and its failure is information, not + * contention: only NextSeedPurge() writes over a _CONSUMING claim, so a + * failed release proves an invalidation event landed after this consume + * claimed the material, i.e. the seed just fed to the reseed was banked + * pre-event. The reseed's own latch handling could not see that (an epoch + * crossing shows only at the aperture's claim word, never in the lock + * word), and if it entered invalidated and ran undisturbed it has already + * cleared the latch -- so compensate: re-latch, and re-saturate the counter + * (the stale credited reseed reset it, leaving the latch as sole + * enforcement; wc_RNG_DRBG_ScheduleReseed() restores the second layer -- + * race-free here, under the exclusive lease). Claims (stats, stratum + * adoption) are made only behind a successful release. Do NOT re-run + * wc_RNG_invalidate_entropy() here: the event's purges already ran, and + * re-purging would discard post-event material the banker may have + * re-banked meanwhile. */ + { + int cas_ret; + WC_ATOMIC_INT_ARG expected_out = WC_DRBG_NEXT_SEED_CONSUMING; + WC_CAS_WITH_RETRY_BEGIN(lenp, expected_out, cas_ret) { + WC_CAS_WITH_RETRY_LOOP_UNTIL(wolfSSL_Atomic_Int_CompareExchange, + lenp, expected_out, + WC_DRBG_NEXT_SEED_EMPTY, cas_ret, + NEEDS_RECOVERY_E); + } WC_CAS_WITH_RETRY_END; + if (cas_ret != 0) { + #ifdef WC_RNG_HAVE_LOCK + { + WC_RNG_lock_arg_t cur_lock; + int relatch_ret; + WC_CAS_WITH_RETRY_BEGIN_INIT_CUR(&rng->lock, cur_lock, + relatch_ret) { + WC_CAS_WITH_RETRY_LOOP_FOREVER( + wolfSSL_Atomic_Uint_CompareExchange, &rng->lock, + cur_lock, + cur_lock | WC_RNG_LOCK_ENTROPY_INVALIDATED, + relatch_ret); + } WC_CAS_WITH_RETRY_END; + if (relatch_ret != 0) { + /* Aborted re-latch: latch down, counter scheduled. + * Latch-or-condemn (see wc_RNG_invalidate_entropy()): + * condemn. */ + rng->status = DRBG_FAILED; + } + } + #endif + { + int sched_ret = wc_RNG_DRBG_ScheduleReseed(rng); + if ((ret == DRBG_SUCCESS) && (sched_ret != 0)) + return sched_ret; + } + if (ret == DRBG_SUCCESS) { + /* The discarded recovery is the whole story. */ + return cas_ret; + } + /* Else the reseed's own failure is the more informative code: + * fall through to the standard outcome mapping. */ + } + else { + #ifdef WC_RNG_DEBUG_STATS + if (ret == 0) { + #ifdef WC_RNG_HAVE_RBGC + if (*nextSeedRBGCStratum_p > 0) + ++rng->_stats_nextseedsRBGC_redeemed; + else + #endif + ++rng->_stats_nextseedsprimary_redeemed; + } + #endif + #ifdef WC_RNG_HAVE_RBGC + if (ret == 0) { + rng->RBGCStratum = *nextSeedRBGCStratum_p; + *nextSeedRBGCStratum_p = 0; + } + #endif + } + } - /* Assume if WC_USE_DEVID it is intended for default usage */ -#ifdef WC_USE_DEVID - ret = wc_rng_new_ex(&rng, nonce, nonceSz, heap, WC_USE_DEVID); + /* Identical outcome mapping to the generate-path reseed. */ + if (ret == DRBG_SUCCESS) { +#if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) + /* Check for PID change after consuming the banked seed. */ + ret = rng_pid_change_check(rng); #else - ret = wc_rng_new_ex(&rng, nonce, nonceSz, heap, INVALID_DEVID); + ret = 0; #endif - - if (ret != 0) { - return NULL; - } - - return rng; -} - - -int wc_rng_new_ex(WC_RNG **rng, byte* nonce, word32 nonceSz, - void* heap, int devId) -{ - int ret; - - if (rng == NULL) { - return BAD_FUNC_ARG; } - - *rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), heap, DYNAMIC_TYPE_RNG); - if (*rng == NULL) { - return MEMORY_E; + else if (ret == WC_NO_ERR_TRACE(DRBG_CONT_FAILURE)) { + ret = DRBG_CONT_FIPS_E; + rng->status = DRBG_CONT_FAILED; } - - ret = _InitRng(*rng, nonce, nonceSz, heap, devId); - if (ret != 0) { - XFREE(*rng, heap, DYNAMIC_TYPE_RNG); - *rng = NULL; + else { + ret = RNG_FAILURE_E; + rng->status = DRBG_FAILED; } return ret; } - -WOLFSSL_ABI -void wc_rng_free(WC_RNG* rng) -{ - if (rng) { - void* heap = rng->heap; - - wc_FreeRng(rng); - ForceZero(rng, sizeof(WC_RNG)); - XFREE(rng, heap, DYNAMIC_TYPE_RNG); - (void)heap; - } +int wc_RNG_DRBG_NextSeedNow(WC_RNG* rng) { + return wc_RNG_DRBG_NextSeedNow_Nonce(rng, NULL, 0); } -WOLFSSL_ABI -int wc_InitRng(WC_RNG* rng) +/* Deposit raw uncredited stir material into rng's accumulator. Callable + * from any context and without owning the instance: the deposit protocol + * (read-copy-store) is multi-writer-tolerant -- every published span was + * written by its publisher, lost updates merely drop entropy, and + * interleaved fragments of compatible provenance are harmless. A full + * accumulator publishes WC_DRBG_NEXT_SEED_READY (no health test -- no + * claim is being made) and blocks further deposits until consumed. */ +int wc_RNG_DRBG_NextStirStore(WC_RNG* rng, + const byte *nonce, word32 nonceSz) { - return _InitRng(rng, NULL, 0, NULL, INVALID_DEVID); + if ((nonce == NULL) || (nonceSz == 0)) + return BAD_FUNC_ARG; + /* _local's nonce arm only reads the buffer; the parameter is non-const + * for the benefit of the other arms. */ + return wc_RNG_DRBG_NextSeedGenerate_local(rng, NULL, nonce, nonceSz); } - -int wc_InitRng_ex(WC_RNG* rng, void* heap, int devId) +/* Consume a ready uncredited accumulator in an immediate uncredited + * (stirring) reseed. The caller must own the instance. The credited=0 + * path holds the three no-ops by construction: the reseed counter is not + * reset, WC_RNG_LOCK_ENTROPY_INVALIDATED is not cleared, and RBGCStratum + * is unchanged -- a stir must never masquerade as recovery or promotion. + * Use-once: the material is consumed (accumulation reopens) whether or not + * the reseed succeeds. The buffer is never zeroized (racy against + * depositors, and zeroing is always a net entropy loss). */ +int wc_RNG_DRBG_NextStirNow(WC_RNG* rng) { - return _InitRng(rng, NULL, 0, heap, devId); -} + byte* seed; + wolfSSL_Atomic_Int* lenp; + word32 nextSeedSz; + WC_ATOMIC_INT_ARG expected = WC_DRBG_NEXT_SEED_READY; + wc_drbg_reseed_ctr_t reseedCtr; + int ret; + if (rng == NULL) + return BAD_FUNC_ARG; -int wc_InitRngNonce(WC_RNG* rng, byte* nonce, word32 nonceSz) -{ - return _InitRng(rng, nonce, nonceSz, NULL, INVALID_DEVID); -} + ret = rng_lock_required_check(rng); + if (ret != 0) + return ret; + /* Mirror wc_RNG_GenerateBlock(): only an in-service DRBG may reseed. */ + if (rng->status != DRBG_OK) + return RNG_FAILURE_E; -int wc_InitRngNonce_ex(WC_RNG* rng, byte* nonce, word32 nonceSz, - void* heap, int devId) -{ - return _InitRng(rng, nonce, nonceSz, heap, devId); -} + /* If a reseed is due, the RNG is not ready for a stir. */ + ret = wc_RNG_DRBG_GetReseedCtr(rng, &reseedCtr); + if (ret < 0) + return ret; + if (reseedCtr >= WC_RESEED_INTERVAL) + return NOT_READY_E; -#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) -static int PollAndReSeed(WC_RNG* rng) -{ - int ret = WC_NO_ERR_TRACE(DRBG_NEED_RESEED); - int devId = INVALID_DEVID; -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) - devId = rng->devId; -#endif - if (wc_RNG_HealthTestLocal(rng, 1, rng->heap, devId) == 0) { - #if defined(WOLFSSL_SMALL_STACK_CACHE) - byte* newSeed = rng->newSeed_buf; - ret = DRBG_SUCCESS; - #elif defined(WOLFSSL_SMALL_STACK) - byte* newSeed = (byte*)XMALLOC(SEED_SZ + SEED_BLOCK_SZ, rng->heap, - DYNAMIC_TYPE_SEED); - ret = (newSeed == NULL) ? MEMORY_E : DRBG_SUCCESS; - #else - byte newSeed[SEED_SZ + SEED_BLOCK_SZ]; - ret = DRBG_SUCCESS; - #endif - if (ret == DRBG_SUCCESS) { - #ifdef WC_RNG_SEED_CB - if (seedCb == NULL) { - ret = DRBG_NO_SEED_CB; - } - else { - ret = seedCb(&rng->seed, newSeed, SEED_SZ + SEED_BLOCK_SZ); - if (ret != 0) { - #ifdef WC_VERBOSE_RNG - WOLFSSL_DEBUG_PRINTF("ERROR: seedCb() in PollAndReSeed() " - "failed with err %d", ret); - #endif - ret = DRBG_FAILURE; - } - } - #else - ret = wc_GenerateSeed(&rng->seed, newSeed, - SEED_SZ + SEED_BLOCK_SZ); - if (ret != 0) { - #ifdef WC_VERBOSE_RNG - WOLFSSL_DEBUG_PRINTF( - "ERROR: wc_GenerateSeed() in PollAndReSeed() failed with " - "err %d", ret); - #endif - ret = DRBG_FAILURE; - } - #endif - } - if (ret == DRBG_SUCCESS) { - ret = wc_RNG_TestSeed(newSeed, SEED_SZ + SEED_BLOCK_SZ); - #ifdef WC_VERBOSE_RNG - if (ret != DRBG_SUCCESS) - WOLFSSL_DEBUG_PRINTF( - "ERROR: wc_RNG_TestSeed() in PollAndReSeed() returned " - "err %d.", ret); - #endif - } - if (ret == DRBG_SUCCESS) { -#ifndef NO_SHA256 - if (rng->drbgType == WC_DRBG_SHA256) - ret = Hash_DRBG_Reseed((DRBG_internal *)rng->drbg, - newSeed + SEED_BLOCK_SZ, SEED_SZ, - NULL, 0); -#endif -#ifdef WOLFSSL_DRBG_SHA512 - if (rng->drbgType == WC_DRBG_SHA512) - ret = Hash512_DRBG_Reseed( - (DRBG_SHA512_internal *)rng->drbg512, - newSeed + SEED_BLOCK_SZ, SEED_SZ, NULL, 0); -#endif + ret = NextStirPtrs(rng, &seed, &nextSeedSz, &lenp); + if (ret != 0) { + /* No DRBG instantiated -- nothing to stir (RDRAND et al.). */ + return ret; + } + + if (! wolfSSL_Atomic_Int_CompareExchange(lenp, &expected, + WC_DRBG_NEXT_SEED_CONSUMING)) + { + /* Accumulator not READY. */ + if (expected < 0) { + /* claimed by a racing consumer. */ + return BUSY_E; } - #if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_SMALL_STACK_CACHE) - if (newSeed != NULL) { - ForceZero(newSeed, SEED_SZ + SEED_BLOCK_SZ); + else { + /* empty or still accumulating -- nothing consumable yet. */ + return NOT_READY_E; } - XFREE(newSeed, rng->heap, DYNAMIC_TYPE_SEED); - #else - /* newSeed is a byte[] in the plain build but a byte* in the - * SMALL_STACK_CACHE build, so use the explicit buffer length (not - * sizeof) to zero the whole reseed entropy buffer in both cases. */ - #ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Add("PollAndReSeed newSeed", newSeed, - SEED_SZ + SEED_BLOCK_SZ); - #endif - ForceZero(newSeed, SEED_SZ + SEED_BLOCK_SZ); - #ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(newSeed, SEED_SZ + SEED_BLOCK_SZ); - #endif - #endif - } - else { - ret = DRBG_CONT_FAILURE; } + ret = Hash_DRBG_StirGenerate(rng, seed, nextSeedSz); + +#ifdef WC_RNG_DEBUG_STATS + if (ret == 0) + ++rng->_stats_nextstirs_redeemed; +#endif + + /* Always burn consumed data before releasing it, even if it's uncredited + * noise. Unlike the seed aperture, the stir aperture has no producer + * claim: lease-free depositors xorbuf() into it at any time during + * _CONSUMING, so the burn here can only discard an incoming overflow + * fragment, after the full aperture has already been absorbed by + * the StirGenerate() above. */ + ForceZero(seed, nextSeedSz); + WOLFSSL_ATOMIC_STORE(*lenp, WC_DRBG_NEXT_SEED_EMPTY); + return ret; } -#endif + +#endif /* WC_RNG_HAVE_NEXT_SEED */ + +#endif /* HAVE_HASHDRBG */ /* place a generated block in output */ -#ifdef WC_RNG_BANK_SUPPORT -static int wc_local_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) +#ifdef WC_HAVE_RNG_BANKREF +static WARN_UNUSED_RESULT int wc_local_RNG_GenerateBlock(WC_RNG* rng, + byte* output, + word32 sz) #else WOLFSSL_ABI int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) @@ -2543,6 +5380,10 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) if (rng == NULL || output == NULL) return BAD_FUNC_ARG; + ret = rng_lock_required_check(rng); + if (ret != 0) + return ret; + if (sz == 0) return 0; @@ -2597,11 +5438,80 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) if (rng->status != DRBG_OK) return RNG_FAILURE_E; +#ifdef WC_RNG_DEBUG_STATS + ++rng->_stats_total_requests; + rng->_stats_total_bytes_requested += sz; +#endif + #if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) - if (rng->pid != getpid()) { - rng->pid = getpid(); - ret = PollAndReSeed(rng); - if (ret != DRBG_SUCCESS) { + ret = rng_pid_change_check(rng); + if (ret != 0) + return ret; +#endif + +#if defined(WC_RNG_HAVE_NEXT_SEED) && defined(WC_RNG_HAVE_LOCK) && \ + defined(WC_RNG_HAVE_RBGC) + if (rng->flags & WC_RNG_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED) { + /* externally-refreshed instance: consume a READY banked seed to + * recover from entropy invalidation (any provenance -- the purge + * in wc_RNG_invalidate_entropy() guarantees a READY seed is + * post-event), or to promote a chain-backed instance to primary. + * Consumption is a credited reseed, clearing the flag and + * resetting the schedule; failure falls through to the ordinary + * forced-reseed machinery. */ + int banked_stratum = wc_RNG_DRBG_GetNextSeedRBGCStratum(rng); + if (banked_stratum >= 0) { + if (((WOLFSSL_ATOMIC_LOAD(rng->lock) & + WC_RNG_LOCK_ENTROPY_INVALIDATED)) + || + ((rng->RBGCStratum > 0) && (banked_stratum == 0))) + { + if (wc_RNG_DRBG_NextSeedNow(rng) != 0) { + rng->status = DRBG_FAILED; + return RNG_FAILURE_E; + } + } + } + } +#endif + +#ifdef WC_RNG_HAVE_NEXT_SEED + /* Universal opportunistic stir: a READY uncredited accumulator is + * consumed by any generate, unconditionally -- stirs are always + * harmless, and are invisible to the credited legs above (no counter + * reset, no flag clear, no stratum change). One relaxed load when + * empty. */ + { + int stir_ready = 0; +#ifndef NO_SHA256 + if ((rng->drbgType == WC_DRBG_SHA256) && (rng->drbg != NULL) && + (WOLFSSL_ATOMIC_LOAD(((DRBG_internal *)rng->drbg)->nextStirLen) + == WC_DRBG_NEXT_SEED_READY)) + { + stir_ready = 1; + } +#endif +#ifdef WOLFSSL_DRBG_SHA512 + if ((rng->drbgType == WC_DRBG_SHA512) && (rng->drbg512 != NULL) && + (WOLFSSL_ATOMIC_LOAD(((DRBG_SHA512_internal *)rng->drbg512)->nextStirLen) + == WC_DRBG_NEXT_SEED_READY)) + { + stir_ready = 1; + } +#endif + if (stir_ready) { + int stir_ret = wc_RNG_DRBG_NextStirNow(rng); + if (stir_ret == WC_NO_ERR_TRACE(RNG_FAILURE_E)) { + /* The DRBG broke while we were stirring it. */ + return stir_ret; + } + } + } +#endif /* WC_RNG_HAVE_NEXT_SEED */ + +#ifdef WC_RNG_HAVE_LOCK + if (WOLFSSL_ATOMIC_LOAD(rng->lock) & WC_RNG_LOCK_ENTROPY_INVALIDATED) { + if (PollAndReSeed(rng, NULL, 0) != DRBG_SUCCESS) { rng->status = DRBG_FAILED; return RNG_FAILURE_E; } @@ -2611,12 +5521,12 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) #ifndef NO_SHA256 if (rng->drbgType == WC_DRBG_SHA256) { ret = Hash_DRBG_Generate((DRBG_internal *)rng->drbg, output, sz, - NULL, 0); + NULL, 0, rng); if (ret == WC_NO_ERR_TRACE(DRBG_NEED_RESEED)) { - ret = PollAndReSeed(rng); + ret = PollAndReSeed(rng, NULL, 0); if (ret == DRBG_SUCCESS) ret = Hash_DRBG_Generate((DRBG_internal *)rng->drbg, output, - sz, NULL, 0); + sz, NULL, 0, rng); } } else @@ -2624,13 +5534,13 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) #ifdef WOLFSSL_DRBG_SHA512 if (rng->drbgType == WC_DRBG_SHA512) { ret = Hash512_DRBG_Generate((DRBG_SHA512_internal *)rng->drbg512, - output, sz, NULL, 0); + output, sz, NULL, 0, rng); if (ret == WC_NO_ERR_TRACE(DRBG_NEED_RESEED)) { - ret = PollAndReSeed(rng); + ret = PollAndReSeed(rng, NULL, 0); if (ret == DRBG_SUCCESS) ret = Hash512_DRBG_Generate( (DRBG_SHA512_internal *)rng->drbg512, output, sz, - NULL, 0); + NULL, 0, rng); } } else @@ -2641,6 +5551,14 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) if (ret == DRBG_SUCCESS) { ret = 0; +#ifdef WC_RNG_DEBUG_STATS + rng->_stats_total_bytes_produced += sz; + #ifdef WC_RNG_HAVE_RBGC + /* chain-provenance output: generated while chain-backed */ + if (rng->RBGCStratum > 0) + rng->_stats_RBGC_bytes_produced += sz; + #endif +#endif } else if (ret == WC_NO_ERR_TRACE(DRBG_CONT_FAILURE)) { ret = DRBG_CONT_FIPS_E; @@ -2661,14 +5579,20 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) return ret; } -#ifdef WC_RNG_BANK_SUPPORT +#ifdef WC_HAVE_RNG_BANKREF WOLFSSL_ABI int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) { if (rng == NULL) return BAD_FUNC_ARG; - if (rng->status == WC_DRBG_BANKREF) { + { + int lock_ret = rng_lock_required_check(rng); + if (lock_ret != 0) + return lock_ret; + } + + if (rng->flags & WC_RNG_FLAG_BANKREF) { int ret; struct wc_rng_bank_inst *bank_inst = NULL; @@ -2680,11 +5604,11 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) ret = wc_local_RNG_GenerateBlock(WC_RNG_BANK_INST_TO_RNG(bank_inst), output, sz); { - int checkin_ret = wc_rng_bank_checkin(rng->bankref, &bank_inst); + int checkin_ret = wc_rng_bank_inst_checkin(&bank_inst); if (checkin_ret != 0) { #ifdef WC_VERBOSE_RNG WOLFSSL_DEBUG_PRINTF( - "ERROR: wc_RNG_GenerateBlock() wc_rng_bank_checkin() " + "ERROR: wc_RNG_GenerateBlock() wc_rng_bank_inst_checkin() " "failed with err %d.", checkin_ret); #endif if (ret == 0) @@ -2711,10 +5635,38 @@ int wc_FreeRng(WC_RNG* rng) if (rng == NULL) return BAD_FUNC_ARG; -#ifdef WC_RNG_BANK_SUPPORT - if (rng->status == WC_DRBG_BANKREF) + /* Note, deallocation proceeds regardless of RNG lock status. Lifecycle + * management is the caller's responsibility, and a lock inside the object + * cannot arbitrate deallocation. + */ + +#ifdef WC_HAVE_RNG_BANKREF + if (rng->flags & WC_RNG_FLAG_BANKREF) return wc_BankRef_Release(rng); -#endif /* WC_RNG_BANK_SUPPORT */ +#endif /* WC_HAVE_RNG_BANKREF */ + +#ifdef WC_RNG_HAVE_FREE_HOOK + if (rng->free_hook != NULL) { + /* one-shot, cleared before firing: re-entrant frees from the hook + * (not that they would be a good idea) can't loop. */ + wc_RNG_free_hook_cb_t free_hook = rng->free_hook; + rng->free_hook = NULL; + ret = free_hook(rng, rng->free_hook_arg); + rng->free_hook_arg = NULL; + } +#endif + +#ifdef WC_RNG_HAVE_POOL + /* single-owner teardown; the only pool deallocation site */ + if (rng->pool != NULL) { + ForceZero(rng->pool, rng->poolSize); + XFREE(rng->pool, rng->heap, DYNAMIC_TYPE_RNG); + rng->pool = NULL; + rng->poolSize = 0; + WOLFSSL_ATOMIC_STORE(rng->poolHead, 0); + WOLFSSL_ATOMIC_STORE(rng->poolTail, 0); + } +#endif #if defined(WOLFSSL_ASYNC_CRYPT) wolfAsync_DevCtxFree(&rng->asyncDev, WOLFSSL_ASYNC_MARKER_RNG); @@ -2805,6 +5757,22 @@ int wc_FreeRng(WC_RNG* rng) } #endif +#ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + if (rng->flags & WC_RNG_FLAG_FULL_MUTEX) { + /* If the latch is held, the caller is the holder (enforced when + * _LOCK_REQUIRED) and owns the mutex: release it before + * destruction. A free latch means the mutex is unowned. */ + if (WOLFSSL_ATOMIC_LOAD(rng->lock) & WC_RNG_LOCK_HELD) + (void)wc_UnLockMutex(&rng->mutex); + (void)wc_FreeMutex(&rng->mutex); + rng->flags &= ~WC_RNG_FLAG_FULL_MUTEX; + } +#endif + + /* Note, rng->lock must *not* be cleared -- it may still be arbitrating + * access even after wc_FreeRng(). + */ + return ret; } @@ -2824,7 +5792,7 @@ int wc_RNG_HealthTest(int reseed, const byte* seedA, word32 seedASz, } -static int wc_RNG_HealthTest_ex_internal(DRBG_internal* drbg, +static WARN_UNUSED_RESULT int wc_RNG_HealthTest_ex_internal(DRBG_internal* drbg, int reseed, const byte* nonce, word32 nonceSz, const byte* seedA, word32 seedASz, const byte* seedB, word32 seedBSz, @@ -2861,7 +5829,7 @@ static int wc_RNG_HealthTest_ex_internal(DRBG_internal* drbg, #endif if (reseed) { - if (Hash_DRBG_Reseed(drbg, seedB, seedBSz, NULL, 0) != 0) { + if (Hash256_DRBG_Reseed(drbg, seedB, seedBSz, NULL, 0) != 0) { goto exit_rng_ht; } } @@ -2871,11 +5839,11 @@ static int wc_RNG_HealthTest_ex_internal(DRBG_internal* drbg, * answer test checks the second block of DRBG out of * the generator to ensure the internal state is updated * as expected. */ - if (Hash_DRBG_Generate(drbg, output, outputSz, NULL, 0) != 0) { + if (Hash_DRBG_Generate(drbg, output, outputSz, NULL, 0, NULL) != 0) { goto exit_rng_ht; } - if (Hash_DRBG_Generate(drbg, output, outputSz, NULL, 0) != 0) { + if (Hash_DRBG_Generate(drbg, output, outputSz, NULL, 0, NULL) != 0) { goto exit_rng_ht; } @@ -3350,20 +6318,21 @@ static int wc_RNG_HealthTest_SHA512_ex_internal(DRBG_SHA512_internal* drbg, #endif if (reseed) { - if (Hash512_DRBG_Reseed(drbg, seedB, seedBSz, NULL, 0) != 0) { + if (Hash512_DRBG_Reseed(drbg, seedB, seedBSz, NULL, 0) != 0) + { goto exit_rng_ht512; } } /* First generate: output discarded per NIST DRBGVS procedure */ if (Hash512_DRBG_Generate(drbg, output, outputSz, - additionalA, additionalASz) != 0) { + additionalA, additionalASz, NULL) != 0) { goto exit_rng_ht512; } /* Second generate: this is the actual test output */ if (Hash512_DRBG_Generate(drbg, output, outputSz, - additionalB, additionalBSz) != 0) { + additionalB, additionalBSz, NULL) != 0) { goto exit_rng_ht512; } @@ -3434,12 +6403,12 @@ int wc_RNG_HealthTest_SHA512_ex(int reseed, /* First generate (output discarded per NIST procedure) */ ret = Hash512_DRBG_Generate(drbg, output, outputSz, - additionalA, additionalASz); + additionalA, additionalASz, NULL); if (ret != 0) goto exit_sha512_ex; /* Second generate (this is the actual output) */ ret = Hash512_DRBG_Generate(drbg, output, outputSz, - additionalB, additionalBSz); + additionalB, additionalBSz, NULL); exit_sha512_ex: (void)Hash512_DRBG_Uninstantiate(drbg); @@ -3554,37 +6523,37 @@ int wc_RNG_HealthTest_SHA256_ex( /* Reseed 1 with additionalA, then Generate 1 with NULL (discard) */ if (entropyB != NULL && entropyBSz > 0) { - ret = Hash_DRBG_Reseed(drbg, entropyB, entropyBSz, - additionalA, additionalASz); + ret = Hash256_DRBG_Reseed(drbg, entropyB, entropyBSz, + additionalA, additionalASz); if (ret != 0) goto exit_sha256_ex; } - ret = Hash_DRBG_Generate(drbg, output, outputSz, NULL, 0); + ret = Hash_DRBG_Generate(drbg, output, outputSz, NULL, 0, NULL); if (ret != 0) goto exit_sha256_ex; /* Reseed 2 with additionalB, then Generate 2 with NULL (keep) */ if (entropyC != NULL && entropyCsz > 0) { - ret = Hash_DRBG_Reseed(drbg, entropyC, entropyCsz, - additionalB, additionalBSz); + ret = Hash256_DRBG_Reseed(drbg, entropyC, entropyCsz, + additionalB, additionalBSz); if (ret != 0) goto exit_sha256_ex; } - ret = Hash_DRBG_Generate(drbg, output, outputSz, NULL, 0); + ret = Hash_DRBG_Generate(drbg, output, outputSz, NULL, 0, NULL); } else { /* Standard mode: explicit reseed, then two generates */ if (entropyB != NULL && entropyBSz > 0) { - ret = Hash_DRBG_Reseed(drbg, entropyB, entropyBSz, - additionalReseed, additionalReseedSz); + ret = Hash256_DRBG_Reseed(drbg, entropyB, entropyBSz, + additionalReseed, additionalReseedSz); if (ret != 0) goto exit_sha256_ex; } /* Generate 1 (output discarded per NIST DRBGVS procedure) */ ret = Hash_DRBG_Generate(drbg, output, outputSz, - additionalA, additionalASz); + additionalA, additionalASz, NULL); if (ret != 0) goto exit_sha256_ex; /* Generate 2 (this is the actual test output) */ ret = Hash_DRBG_Generate(drbg, output, outputSz, - additionalB, additionalBSz); + additionalB, additionalBSz, NULL); } exit_sha256_ex: @@ -3662,7 +6631,7 @@ int wc_RNG_HealthTest_SHA512_ex2( additionalA, additionalASz); if (ret != 0) goto exit_sha512_ex2; } - ret = Hash512_DRBG_Generate(drbg, output, outputSz, NULL, 0); + ret = Hash512_DRBG_Generate(drbg, output, outputSz, NULL, 0, NULL); if (ret != 0) goto exit_sha512_ex2; /* Reseed 2 with additionalB, then Generate 2 with NULL (keep) */ @@ -3671,7 +6640,7 @@ int wc_RNG_HealthTest_SHA512_ex2( additionalB, additionalBSz); if (ret != 0) goto exit_sha512_ex2; } - ret = Hash512_DRBG_Generate(drbg, output, outputSz, NULL, 0); + ret = Hash512_DRBG_Generate(drbg, output, outputSz, NULL, 0, NULL); } else { /* Standard mode: explicit reseed, then two generates */ @@ -3683,12 +6652,12 @@ int wc_RNG_HealthTest_SHA512_ex2( /* Generate 1 (output discarded per NIST DRBGVS procedure) */ ret = Hash512_DRBG_Generate(drbg, output, outputSz, - additionalA, additionalASz); + additionalA, additionalASz, NULL); if (ret != 0) goto exit_sha512_ex2; /* Generate 2 (this is the actual test output) */ ret = Hash512_DRBG_Generate(drbg, output, outputSz, - additionalB, additionalBSz); + additionalB, additionalBSz, NULL); } exit_sha512_ex2: @@ -3868,7 +6837,7 @@ static WC_INLINE int IntelRDseed64_r(word64* rnd) if (IntelRDseed64(rnd) == 0) return 0; } - return -1; + return NOT_READY_E; } /* return 0 on success */ @@ -3882,7 +6851,7 @@ static int wc_GenerateSeed_IntelRD(OS_Seed* os, byte* output, word32 sz) (void)os; if (!IS_INTEL_RDSEED(intel_flags)) - return -1; + return WC_HW_E; /* Note, access to rdseed_sanity_status is benignly racey on multithreaded * targets. @@ -3910,14 +6879,14 @@ static int wc_GenerateSeed_IntelRD(OS_Seed* os, byte* output, word32 sz) "check CPU microcode version.", sanity_word2); #endif rdseed_sanity_status = -1; - return -1; + return WC_HW_E; } } rdseed_sanity_status = 1; } else if (rdseed_sanity_status < 0) { - return -1; + return WC_HW_E; } for (; (sz / sizeof(word64)) > 0; sz -= sizeof(word64), @@ -6733,4 +9702,141 @@ int wc_hwrng_generate_block(byte *output, word32 sz) } #endif +#ifdef WC_RNG_DEBUG_STATS + +/* Note on WC_RNG_DEBUG_STATS collection points: + * + * Placement doctrine: each counter is maintained at the single funnel that + * owns the distinction it records -- + * + * - reseed counts in Hash_DRBG_Reseed() (every reseed flavor routes + * through it: interval backstop, Reseed_Now, RBGC, banked redemption); + * - request/byte counts in the DRBG arm of wc_RNG_GenerateBlock() + * (hardware-offload arms -- RDRAND, Silabs, async, cryptocb, custom -- + * are deliberately uncounted: these are DRBG-facility statistics); + * - banked-seed redemption provenance in wc_RNG_DRBG_NextSeedNow_Nonce(); + * - seed health failures at the two sites that observe them per-instance + * (PollAndReSeed(), NextSeedGenerate); + * - chain-provenance bytes (RBGC_bytes_produced: output generated while + * the instance's own RBGCStratum > 0) in the same generate funnel; + * - pool byte accounting in wc_RNG_Pool_Extract(), under the consumer's + * instance lock: bytes produced by reading from the pool, and bytes + * requested but not fulfilled (empty-pool and partial-serve shortfall), + * so requested == produced + missed on the capacity paths. The + * failed-DRBG burn path deliberately counts nothing: it is a failure + * event (visible via rng->status), not a capacity signal. + * + * Counters are plain (non-atomic) adds/increments, and update under the owner's + * exclusive access (the lock contract shared by all WC_RNG operations) except + * where labeled racy: those are unreliable under concurrency, by design. Every + * site carries its own #ifdef WC_RNG_DEBUG_STATS gate so the facility is + * removable outright with unifdef. + */ + +WOLFSSL_API int wc_rng_debug_stats_snap(struct wc_rng_debug_stats_snapshot *s, + const WC_RNG *rng) +{ + if ((s == NULL) || (rng == NULL)) + return BAD_FUNC_ARG; + + s->_stats_total_bytes_requested = rng->_stats_total_bytes_requested; + s->_stats_total_bytes_produced = rng->_stats_total_bytes_produced; + s->_stats_total_requests = rng->_stats_total_requests; + s->_stats_reseeds = rng->_stats_reseeds; + s->_stats_stirs = rng->_stats_stirs; + s->_stats_seed_failures = rng->_stats_seed_failures; + s->_stats_locks_taken = rng->_stats_locks_taken; + s->_stats_locks_released = rng->_stats_locks_released; + s->_stats_locks_refused = rng->_stats_locks_refused; +#ifdef WC_RNG_HAVE_RBGC + s->_stats_RBGC_bytes_produced = rng->_stats_RBGC_bytes_produced; + s->_stats_RBGC_reseeds = rng->_stats_RBGC_reseeds; +#endif +#ifdef WC_RNG_HAVE_POOL + s->_stats_pool_bytes_produced = rng->_stats_pool_bytes_produced; + s->_stats_pool_bytes_missed = rng->_stats_pool_bytes_missed; +#endif +#ifdef WC_RNG_HAVE_NEXT_SEED + s->_stats_nextseedsprimary_redeemed = rng->_stats_nextseedsprimary_redeemed; + s->_stats_nextseedsRBGC_redeemed = rng->_stats_nextseedsRBGC_redeemed; + s->_stats_nextstirs_redeemed = rng->_stats_nextstirs_redeemed; + s->_stats_nextseedsbanked = rng->_stats_nextseedsbanked; + s->_stats_nextstirs_banked = rng->_stats_nextstirs_banked; +#endif + + return 0; +} + +WOLFSSL_API int wc_rng_debug_stats_restore( + const struct wc_rng_debug_stats_snapshot *s, + WC_RNG *rng) +{ + if ((s == NULL) || (rng == NULL)) + return BAD_FUNC_ARG; + + rng->_stats_total_bytes_requested = s->_stats_total_bytes_requested; + rng->_stats_total_bytes_produced = s->_stats_total_bytes_produced; + rng->_stats_total_requests = s->_stats_total_requests; + rng->_stats_reseeds = s->_stats_reseeds; + rng->_stats_stirs = s->_stats_stirs; + rng->_stats_seed_failures = s->_stats_seed_failures; + rng->_stats_locks_taken = s->_stats_locks_taken; + rng->_stats_locks_released = s->_stats_locks_released; + rng->_stats_locks_refused = s->_stats_locks_refused; +#ifdef WC_RNG_HAVE_RBGC + rng->_stats_RBGC_bytes_produced = s->_stats_RBGC_bytes_produced; + rng->_stats_RBGC_reseeds = s->_stats_RBGC_reseeds; +#endif +#ifdef WC_RNG_HAVE_POOL + rng->_stats_pool_bytes_produced = s->_stats_pool_bytes_produced; + rng->_stats_pool_bytes_missed = s->_stats_pool_bytes_missed; +#endif +#ifdef WC_RNG_HAVE_NEXT_SEED + rng->_stats_nextseedsprimary_redeemed = s->_stats_nextseedsprimary_redeemed; + rng->_stats_nextseedsRBGC_redeemed = s->_stats_nextseedsRBGC_redeemed; + rng->_stats_nextstirs_redeemed = s->_stats_nextstirs_redeemed; + rng->_stats_nextseedsbanked = s->_stats_nextseedsbanked; + rng->_stats_nextstirs_banked = s->_stats_nextstirs_banked; +#endif + + return 0; +} + +WOLFSSL_API int wc_rng_debug_stats_sum(struct wc_rng_debug_stats_snapshot *s, + const WC_RNG *rng) +{ + if ((s == NULL) || (rng == NULL)) + return BAD_FUNC_ARG; + + s->_stats_total_bytes_requested += rng->_stats_total_bytes_requested; + s->_stats_total_bytes_produced += rng->_stats_total_bytes_produced; + s->_stats_total_requests += rng->_stats_total_requests; + s->_stats_reseeds += rng->_stats_reseeds; + s->_stats_stirs += rng->_stats_stirs; + s->_stats_seed_failures += rng->_stats_seed_failures; + s->_stats_locks_taken += rng->_stats_locks_taken; + s->_stats_locks_released += rng->_stats_locks_released; + s->_stats_locks_refused += rng->_stats_locks_refused; +#ifdef WC_RNG_HAVE_RBGC + s->_stats_RBGC_bytes_produced += rng->_stats_RBGC_bytes_produced; + s->_stats_RBGC_reseeds += rng->_stats_RBGC_reseeds; +#endif +#ifdef WC_RNG_HAVE_POOL + s->_stats_pool_bytes_produced += rng->_stats_pool_bytes_produced; + s->_stats_pool_bytes_missed += rng->_stats_pool_bytes_missed; +#endif +#ifdef WC_RNG_HAVE_NEXT_SEED + s->_stats_nextseedsprimary_redeemed += rng->_stats_nextseedsprimary_redeemed; + s->_stats_nextseedsRBGC_redeemed += rng->_stats_nextseedsRBGC_redeemed; + s->_stats_nextstirs_redeemed += rng->_stats_nextstirs_redeemed; + s->_stats_nextseedsbanked += rng->_stats_nextseedsbanked; + s->_stats_nextstirs_banked += rng->_stats_nextstirs_banked; +#endif + + return 0; +} + +#endif /* WC_RNG_DEBUG_STATS */ + + #endif /* WC_NO_RNG */ diff --git a/wolfcrypt/src/rng_bank.c b/wolfcrypt/src/rng_bank.c index c17e30eda11..e43dc7d38cb 100644 --- a/wolfcrypt/src/rng_bank.c +++ b/wolfcrypt/src/rng_bank.c @@ -26,78 +26,105 @@ #include #include -/* Helpers to access reseedCtr / null-check the active DRBG. The shape of - * struct WC_RNG and the DRBG_*_internal types varies by which DRBGs are - * compiled in; random.h gates the SHA-256 side on !NO_SHA256 and the SHA-512 - * side on WOLFSSL_DRBG_SHA512, so all three live combinations are handled - * separately here. */ -#if defined(WOLFSSL_DRBG_SHA512) && !defined(NO_SHA256) - /* Both DRBGs compiled in: dispatch on the runtime drbgType. */ - #define WC_RNG_BANK_RESEED_CTR(rng_ptr) \ - (((rng_ptr)->drbgType == WC_DRBG_SHA512) \ - ? ((struct DRBG_SHA512_internal *)(rng_ptr)->drbg512)->reseedCtr \ - : ((struct DRBG_internal *)(rng_ptr)->drbg)->reseedCtr) - #define WC_RNG_BANK_SET_RESEED_CTR(rng_ptr, val) \ - do { \ - if ((rng_ptr)->drbgType == WC_DRBG_SHA512) \ - ((struct DRBG_SHA512_internal *)(rng_ptr)->drbg512)->reseedCtr \ - = (val); \ - else \ - ((struct DRBG_internal *)(rng_ptr)->drbg)->reseedCtr = (val); \ - } while (0) - #define WC_RNG_BANK_DRBG_NULL(rng_ptr) \ - ((rng_ptr)->drbg == NULL && (rng_ptr)->drbg512 == NULL) -#elif defined(WOLFSSL_DRBG_SHA512) - /* SHA-512 DRBG only (NO_SHA256 defined); the SHA-256 struct and - * rng->drbg field do not exist in this build. */ - #define WC_RNG_BANK_RESEED_CTR(rng_ptr) \ - (((struct DRBG_SHA512_internal *)(rng_ptr)->drbg512)->reseedCtr) - #define WC_RNG_BANK_SET_RESEED_CTR(rng_ptr, val) \ - do { \ - ((struct DRBG_SHA512_internal *)(rng_ptr)->drbg512)->reseedCtr \ - = (val); \ - } while (0) - #define WC_RNG_BANK_DRBG_NULL(rng_ptr) \ - ((rng_ptr)->drbg512 == NULL) -#else - /* SHA-256 DRBG only (the historical default). */ - #define WC_RNG_BANK_RESEED_CTR(rng_ptr) \ - (((struct DRBG_internal *)(rng_ptr)->drbg)->reseedCtr) - #define WC_RNG_BANK_SET_RESEED_CTR(rng_ptr, val) \ - do { \ - ((struct DRBG_internal *)(rng_ptr)->drbg)->reseedCtr = (val); \ - } while (0) - #define WC_RNG_BANK_DRBG_NULL(rng_ptr) \ - ((rng_ptr)->drbg == NULL) +#ifndef WC_RNG_HAVE_LOCK + /* feature-gap helper declarations */ + static int wc_rng_bank_inst_recovery_enter( + struct wc_rng_bank_inst *inst, int *recovering); + static int wc_rng_bank_inst_recovery_exit( + struct wc_rng_bank_inst *inst, int recovering, int ret); #endif +#if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) + #ifndef WC_RNG_INIT_FLAG_NONE + #define WC_RNG_INIT_FLAG_NONE 0 + #endif + /* backward-compat shim and helper declarations */ + static int wc_RNG_DRBG_Reseed_Nonce(WC_RNG* rng, const byte* seed, + word32 seedSz, const byte *nonce, + word32 nonceSz); + #if FIPS_VERSION3_NE(5,2,4) + static int wc_RNG_DRBG_Reseed_Now( + WC_RNG* rng, const byte* nonce, word32 nonceSz); + static int wc_RNG_DRBG_GetReseedCtr( + const WC_RNG* rng, wc_drbg_reseed_ctr_t* reseedCtr); + #endif + static int wc_RNG_DRBG_Stir_Nonce( + WC_RNG* rng, const byte* seed, word32 seedSz, const byte *nonce, + word32 nonceSz); + + /* WC_DRBG_* predate some old FIPS editions, but all of them share the same + * values -- force consistency using macros. */ + #undef WC_DRBG_NOT_INIT + #define WC_DRBG_NOT_INIT 0 + #undef WC_DRBG_OK + #define WC_DRBG_OK 1 +#endif /* HAVE_FIPS && FIPS_VERSION3_LT(7,0,0) */ + +/* DRBG status and reseed-counter access, and reseed forcing, are via the + * wc_RNG_GetStatus() / wc_RNG_DRBG_*() services in wolfcrypt/src/random.c + * (FIPS v7+ and non-FIPS builds). For pre-v7 FIPS boundaries, which lack + * those services, rng_bank.h supplies source-compatible static fallbacks. + */ + /* To disable retry looping in wc_rng_bank_init(), pass timeout_secs=0, and to * retry indefinitely, pass negative timeout_secs -- the flags arg here is only * used to initialize the flags in the new bank. */ -WOLFSSL_API int wc_rng_bank_init( +WOLFSSL_API int wc_rng_bank_init_nonce( struct wc_rng_bank *ctx, int n_rngs, word32 flags, int timeout_secs, void *heap, - int devId) + int devId, + const byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz) { int i; int ret; int need_reenable_vec = 0; + wc_static_assert(WC_DRBG_NOT_INIT == 0); /* make sure assumptions are met */ +#ifdef WC_RNG_INIT_FLAG_LOCK_REQUIRED + word32 rng_flags = WC_RNG_INIT_FLAG_LOCK_REQUIRED; +#else + WC_MAYBE_UNUSED word32 rng_flags = WC_RNG_INIT_FLAG_NONE; +#endif if ((ctx == NULL) || (n_rngs <= 0)) return BAD_FUNC_ARG; +#ifndef WC_RNG_HAVE_RBGC + if (flags & WC_RNG_BANK_FLAG_RBGC) + return NOT_COMPILED_IN; + (void)nonce; + (void)nonceSz; + (void)perso; + (void)persoSz; +#endif + + /* the allocation below is sizeof(*ctx->rngs) * n_rngs; on targets where + * size_t is narrow enough for that product to wrap, the initialization + * loop would then run off the end of an undersized array. */ + if ((size_t)n_rngs > ((size_t)(-1) / sizeof(*ctx->rngs))) + return BAD_LENGTH_E; + XMEMSET(ctx, 0, sizeof(*ctx)); wolfSSL_RefInit(&ctx->refcount, &ret); if (ret != 0) return ret; +#ifdef WC_RNG_HAVE_NEXT_SEED + wolfSSL_Atomic_Int_Init(&ctx->inst_op_gate, 0); +#endif ctx->flags = flags | WC_RNG_BANK_FLAG_INITED; +#ifdef WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED + if (flags & WC_RNG_BANK_FLAG_AUTO_RECOVER_AND_PROMOTE) + rng_flags |= WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED; +#endif ctx->heap = heap; + ctx->devId = devId; + ctx->first_failover_inst = -1; #ifdef WC_RNG_BANK_STATIC if (n_rngs > WC_RNG_BANK_STATIC_SIZE) @@ -110,6 +137,11 @@ WOLFSSL_API int wc_rng_bank_init( ret = MEMORY_E; #endif +#ifdef WC_RNG_HAVE_RBGC + if ((ret == 0) && (flags & WC_RNG_BANK_FLAG_RBGC)) + ret = wc_rng_bank_root_rng_init(ctx, nonce, nonceSz, perso, persoSz, 0); +#endif + if (ret == 0) { XMEMSET(ctx->rngs, 0, sizeof(*ctx->rngs) * (size_t)n_rngs); ctx->n_rngs = n_rngs; @@ -122,15 +154,39 @@ WOLFSSL_API int wc_rng_bank_init( int nretries = 0; #endif time_t ts1 = XTIME(0); + rng_inst->bank = ctx; for (;;) { time_t ts2; - if (flags & WC_RNG_BANK_FLAG_NO_VECTOR_OPS) need_reenable_vec = (DISABLE_VECTOR_REGISTERS() == 0); - ret = wc_InitRngNonce_ex( + +#ifdef WC_RNG_HAVE_RBGC + if (flags & WC_RNG_BANK_FLAG_RBGC) { + ret = wc_InitRngNonceRBGC( + WC_RNG_BANK_INST_TO_RNG(rng_inst), + &ctx->root_rng, + (byte *)&rng_inst, sizeof(byte *), +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) + NULL, 0, +#endif + rng_flags + ); + } + else +#endif + { +#ifdef WC_RNG_INIT_FLAG_LOCK_REQUIRED + ret = wc_InitRngNonce_ex2( + WC_RNG_BANK_INST_TO_RNG(rng_inst), + (byte *)&rng_inst, sizeof(byte *), + NULL, 0, heap, devId, + rng_flags); +#else + ret = wc_InitRngNonce_ex( WC_RNG_BANK_INST_TO_RNG(rng_inst), (byte *)&rng_inst, sizeof(byte *), heap, devId); - +#endif + } if (need_reenable_vec) REENABLE_VECTOR_REGISTERS(); /* if we're allowed to sleep, relax the loop between each inner @@ -157,6 +213,12 @@ WOLFSSL_API int wc_rng_bank_init( case WC_NO_ERR_TRACE(DRBG_CONT_FIPS_E): goto out; } + + if (timeout_secs == 0) { + break; /* Retry disabled -- return the real error, not + * WC_TIMEOUT_E. */ + } + /* Allow interrupt only if we're stuck spinning retries -- i.e., * don't allow an untimely user signal to derail an * initialization that is proceeding expeditiously. @@ -165,7 +227,7 @@ WOLFSSL_API int wc_rng_bank_init( if (ret == WC_NO_ERR_TRACE(INTERRUPTED_E)) break; ts2 = XTIME(0); - if ((timeout_secs >= 0) && (ts2 - ts1 > timeout_secs)) { + if ((timeout_secs > 0) && (ts2 - ts1 > timeout_secs)) { ret = WC_TIMEOUT_E; break; } @@ -192,6 +254,33 @@ WOLFSSL_API int wc_rng_bank_init( return ret; } +WOLFSSL_API int wc_rng_bank_init( + struct wc_rng_bank *ctx, + int n_rngs, + word32 flags, + int timeout_secs, + void *heap, + int devId) +{ + + return wc_rng_bank_init_nonce(ctx, n_rngs, flags, timeout_secs, heap, devId, + NULL, 0, NULL, 0); +} + +WOLFSSL_API int wc_rng_bank_first_failover_inst_set( + struct wc_rng_bank *ctx, + int first_failover_inst) +{ + if ((ctx == NULL) || + (first_failover_inst < 0) || + (first_failover_inst >= ctx->n_rngs)) + { + return BAD_FUNC_ARG; + } + ctx->first_failover_inst = first_failover_inst; + return 0; +} + #ifndef WC_RNG_BANK_STATIC WOLFSSL_API int wc_rng_bank_new( struct wc_rng_bank **ctx, @@ -206,7 +295,8 @@ WOLFSSL_API int wc_rng_bank_new( if ((ctx == NULL) || (n_rngs <= 0)) return BAD_FUNC_ARG; - *ctx = (struct wc_rng_bank *)XMALLOC(sizeof(struct wc_rng_bank), heap, DYNAMIC_TYPE_RNG); + *ctx = (struct wc_rng_bank *)XMALLOC(sizeof(struct wc_rng_bank), heap, + DYNAMIC_TYPE_RNG); if (*ctx == NULL) return MEMORY_E; @@ -248,6 +338,7 @@ WOLFSSL_API int wc_rng_bank_fini(struct wc_rng_bank *ctx) { int i; int ret; WC_ATOMIC_INT_ARG new_refcount; + int rng_free_failed = 0; if (ctx == NULL) return BAD_FUNC_ARG; @@ -263,11 +354,27 @@ WOLFSSL_API int wc_rng_bank_fini(struct wc_rng_bank *ctx) { else if (wolfSSL_RefCur(ctx->refcount) < 1) return BAD_STATE_E; +#ifndef WC_RNG_BANK_STATIC + if (ctx->rngs) +#endif + { + /* A held instance aborts finalization with the bank fully intact -- + * refcount untouched, free hook unfired, root alive. BUSY_E: another + * actor holds a lease, definitively retryable. */ + for (i = 0; i < ctx->n_rngs; ++i) { + WC_RNG_lock_arg_t pre_lock_state = 0; + (void)wc_rng_bank_inst_lock_read(&ctx->rngs[i], &pre_lock_state); + if (pre_lock_state & WC_RNG_LOCK_HELD) + return BUSY_E; + } + } + wolfSSL_RefDec_IfEquals(&ctx->refcount, 1, &new_refcount, &ret); if (ret != 0) { #ifdef WC_VERBOSE_RNG WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_fini() called with refcount %d.", new_refcount); + "WARNING: wc_rng_bank_fini() called with refcount %d.", + new_refcount); #endif if (new_refcount > 1) return BUSY_E; @@ -275,13 +382,34 @@ WOLFSSL_API int wc_rng_bank_fini(struct wc_rng_bank *ctx) { return ret; } +#if defined(WC_RNG_HAVE_RBGC) || defined(WC_RNG_HAVE_NEXT_SEED) + if (wc_RNG_GetStatus(&ctx->root_rng) != WC_DRBG_NOT_INIT) { + int free_ret = wc_FreeRng(&ctx->root_rng); + if (free_ret != 0) { +#ifdef WC_VERBOSE_RNG + WOLFSSL_DEBUG_PRINTF( + "wc_rng_bank_fini(): wc_FreeRng() on root_rng returned " + "error %d.\n", free_ret); +#endif + ++rng_free_failed; + } + } +#endif + #ifndef WC_RNG_BANK_STATIC if (ctx->rngs) #endif { for (i = 0; i < ctx->n_rngs; ++i) { - if (ctx->rngs[i].lock != 0) { - /* better to leak than to crash. */ + WC_RNG_lock_arg_t fini_lock_state = 0; + (void)wc_rng_bank_inst_lock_read(&ctx->rngs[i], + &fini_lock_state); + if (fini_lock_state & WC_RNG_LOCK_HELD) { + /* Held is the disqualifier; a bare sticky + * WC_RNG_LOCK_REQUIRED is the at-rest state of a marked + * free instance and is expected here. + * + * better to leak than to crash. */ #ifdef WC_VERBOSE_RNG WOLFSSL_DEBUG_PRINTF( "BUG: wc_rng_bank_fini() called with RNG #%d still " @@ -297,9 +425,46 @@ WOLFSSL_API int wc_rng_bank_fini(struct wc_rng_bank *ctx) { } } + if (ctx->free_hook != NULL) { + /* teardown is committed: fire the free hook (one-shot). */ + wc_rng_bank_free_hook_cb_t free_hook = ctx->free_hook; + ctx->free_hook = NULL; + (void)free_hook(ctx, ctx->free_hook_arg); + ctx->free_hook_arg = NULL; + } + for (i = 0; i < ctx->n_rngs; ++i) { - wc_FreeRng(&ctx->rngs[i].rng); + /* Lease-taking teardown, for internal consistency checking -- + * structurally uncontended at refcount zero with the held-check + * above passed. The latch dies held in dying memory, per the + * lock-uncleared-on-free contract of wc_FreeRng(). */ + if ((wc_rng_bank_inst_lock_get(&ctx->rngs[i], 0) != 0) && + (wc_rng_bank_inst_lock_get_conditional(&ctx->rngs[i], + WC_RNG_LOCK_ENTROPY_INVALIDATED, 0) != 0)) + { + /* can't happen absent corruption; leak, don't crash. */ +#ifdef WC_VERBOSE_RNG + WOLFSSL_DEBUG_PRINTF( + "BUG: wc_rng_bank_fini() couldn't take the teardown " + "lease on RNG #%d.\n", i); +#endif + ret = BAD_STATE_E; + continue; + } + { + int free_ret = wc_FreeRng(&ctx->rngs[i].rng); + if (free_ret != 0) { +#ifdef WC_VERBOSE_RNG + WOLFSSL_DEBUG_PRINTF( + "wc_rng_bank_fini(): wc_FreeRng() on RNG #%d returned " + "error %d.\n", i, free_ret); +#endif + ++rng_free_failed; + } + } } + if (ret == WC_NO_ERR_TRACE(BAD_STATE_E)) + return ret; #ifndef WC_RNG_BANK_STATIC XFREE(ctx->rngs, ctx->heap, DYNAMIC_TYPE_RNG); @@ -313,7 +478,10 @@ WOLFSSL_API int wc_rng_bank_fini(struct wc_rng_bank *ctx) { ctx->flags = WC_RNG_BANK_FLAG_NONE; ctx->cb_arg = NULL; - return 0; + if (rng_free_failed > 0) + return RNG_FAILURE_E; + else + return 0; } #ifndef WC_RNG_BANK_STATIC @@ -354,7 +522,7 @@ static struct wc_rng_bank * volatile default_rng_bank; WOLFSSL_API int wc_rng_bank_default_set(struct wc_rng_bank *bank) { int ret; struct wc_rng_bank *cur_default_rng_bank = NULL; - int new_refcount; + WC_ATOMIC_INT_ARG new_refcount; if (bank == NULL) return BAD_FUNC_ARG; @@ -372,14 +540,20 @@ WOLFSSL_API int wc_rng_bank_default_set(struct wc_rng_bank *bank) { #endif return ret; } - if (wolfSSL_Atomic_Ptr_CompareExchange((void * volatile *)&default_rng_bank, (void **)&cur_default_rng_bank, bank)) + if (wolfSSL_Atomic_Ptr_CompareExchange((void * volatile *)&default_rng_bank, + (void **)&cur_default_rng_bank, + bank)) + { + bank->flags |= WC_RNG_BANK_FLAG_DEFAULT_BANK; return 0; + } else { wolfSSL_RefDec2(&bank->refcount, &new_refcount, &ret); #ifdef WC_VERBOSE_RNG if (new_refcount <= 0) WOLFSSL_DEBUG_PRINTF( - "BUG: wc_rng_bank_default_set() cleanup popped refcount to %d.\n", new_refcount); + "BUG: wc_rng_bank_default_set() cleanup popped refcount to %d.\n", + new_refcount); #else (void)new_refcount; #endif @@ -399,11 +573,24 @@ WOLFSSL_API int wc_rng_bank_default_checkout(struct wc_rng_bank **bank) { if (bank == NULL) return BAD_FUNC_ARG; if (cur_default_rng_bank == NULL) + return NO_DEFAULT_FOUND_E; + else if (! (cur_default_rng_bank->flags & WC_RNG_BANK_FLAG_INITED)) return BAD_STATE_E; - wolfSSL_RefInc_IfAtLeast(&cur_default_rng_bank->refcount, 2, &new_refcount, &ret); - if (ret != 0) - return ret; + if (cur_default_rng_bank->flags & WC_RNG_BANK_FLAG_NO_CHECKOUT_REFCOUNTING) + { + /* read-only validity test: >= 2 means inited and still registered + * as the default (wc_rng_bank_default_set()'s standing ref). */ + if (wolfSSL_RefCur(cur_default_rng_bank->refcount) < 2) + return BAD_STATE_E; + ret = 0; + } + else { + wolfSSL_RefInc_IfAtLeast(&cur_default_rng_bank->refcount, 2, + &new_refcount, &ret); + if (ret != 0) + return ret; + } *bank = cur_default_rng_bank; @@ -412,14 +599,19 @@ WOLFSSL_API int wc_rng_bank_default_checkout(struct wc_rng_bank **bank) { WOLFSSL_API int wc_rng_bank_default_checkin(struct wc_rng_bank **bank) { int ret; - int new_refcount; + WC_ATOMIC_INT_ARG new_refcount; if ((bank == NULL) || (*bank == NULL)) return BAD_FUNC_ARG; + if ((*bank)->flags & WC_RNG_BANK_FLAG_NO_CHECKOUT_REFCOUNTING) { + *bank = NULL; + return 0; + } wolfSSL_RefDec2(&(*bank)->refcount, &new_refcount, &ret); #ifdef WC_VERBOSE_RNG if (new_refcount <= 0) WOLFSSL_DEBUG_PRINTF( - "BUG: wc_rng_bank_default_checkin() popped refcount to %d.\n", new_refcount); + "BUG: wc_rng_bank_default_checkin() popped refcount to %d.\n", + new_refcount); #else (void)new_refcount; #endif @@ -433,11 +625,16 @@ WOLFSSL_API int wc_rng_bank_default_checkin(struct wc_rng_bank **bank) { * wc_rng_bank_default_set(). */ WOLFSSL_API int wc_rng_bank_default_clear(struct wc_rng_bank *bank) { - if ((bank != default_rng_bank) || (bank == NULL)) + if (bank == NULL) return BAD_FUNC_ARG; - if (wolfSSL_Atomic_Ptr_CompareExchange((void * volatile *)&default_rng_bank, (void **)&bank, NULL)) { + if (bank != default_rng_bank) + return BAD_FUNC_ARG; + if (wolfSSL_Atomic_Ptr_CompareExchange((void * volatile *)&default_rng_bank, + (void **)&bank, NULL)) + { int ret; - int new_refcount; + WC_ATOMIC_INT_ARG new_refcount; + bank->flags &= ~WC_RNG_BANK_FLAG_DEFAULT_BANK; wolfSSL_RefDec2(&bank->refcount, &new_refcount, &ret); #ifdef WC_VERBOSE_RNG /* wc_rng_bank_fini() is the sole responsibility of the context that @@ -445,7 +642,8 @@ WOLFSSL_API int wc_rng_bank_default_clear(struct wc_rng_bank *bank) { */ if (new_refcount < 1) WOLFSSL_DEBUG_PRINTF( - "BUG: wc_rng_bank_default_clear() popped refcount to %d.\n", new_refcount); + "BUG: wc_rng_bank_default_clear() popped refcount to %d.\n", + new_refcount); if (! (bank->flags & WC_RNG_BANK_FLAG_INITED)) WOLFSSL_DEBUG_PRINTF( "BUG: wc_rng_bank_default_clear() bank is already uninited.\n"); @@ -476,29 +674,88 @@ WOLFSSL_API int wc_rng_bank_checkout( int timeout_secs, word32 flags) { - int new_lock_value = WC_RNG_BANK_INST_LOCK_HELD; + WC_RNG_lock_arg_t lock_extra_bits = 0; int ret = 0; time_t ts1, ts2; int n_rngs_tried = 0; + int diverted_unusable = 0; WC_ATOMIC_INT_ARG new_refcount; +#ifdef WC_RNG_HAVE_NEXT_SEED + WC_ATOMIC_INT_ARG NextSeedCurrent = 0; + int recovered_claim = 0; +#endif + int maybe_recovery_claim = 0; + + if (rng_inst == NULL) + return BAD_FUNC_ARG; + if (bank == NULL) { #ifdef WC_RNG_BANK_DEFAULT_SUPPORT - if (bank == NULL) - bank = default_rng_bank; + ret = wc_rng_bank_default_checkout(&bank); + if (ret != 0) + return ret; + /* wc_rng_bank_default_checkout() increments bank->refcount, which we + * carry through below (no matching wc_rng_bank_default_checkin()). + */ +#else + return BAD_FUNC_ARG; #endif + } + else { + if ((! (bank->flags & WC_RNG_BANK_FLAG_INITED)) || + (wolfSSL_RefCur(bank->refcount) < 1)) + { + return BAD_STATE_E; + } - if ((bank == NULL) || - (rng_inst == NULL)) - { - return BAD_FUNC_ARG; + /* Increment bank->refcount here speculatively to mitigate races with + * bank deallocation. With _NO_CHECKOUT_REFCOUNTING the container + * guarantees liveness and the RefCur test above suffices. + */ + if (! (bank->flags & WC_RNG_BANK_FLAG_NO_CHECKOUT_REFCOUNTING)) { + wolfSSL_RefInc_IfAtLeast(&bank->refcount, 1, &new_refcount, &ret); + if (ret != 0) { +#ifdef WC_VERBOSE_RNG + WOLFSSL_DEBUG_PRINTF( + "wc_rng_bank_checkout() called with refcount %d.\n", + new_refcount); +#endif + return ret; + } + } + } + + if (flags & WC_RNG_BANK_FLAG_FOR_RECOVERY) { + if (flags & WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE) { + ret = BAD_FUNC_ARG; + goto out; + } + } + else { + if (((flags | bank->flags) & WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE) && + (((! (flags & WC_RNG_BANK_FLAG_CAN_WAIT))) || + (flags & WC_RNG_BANK_FLAG_STIR))) + { + ret = BAD_FUNC_ARG; + goto out; + } } - if ((! (bank->flags & WC_RNG_BANK_FLAG_INITED)) || - (wolfSSL_RefCur(bank->refcount) < 1)) + if ((flags & WC_RNG_BANK_FLAG_FOR_RECOVERY) && + (flags & (WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST | + WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST | + WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED))) { - return BAD_STATE_E; + /* Recovery targets one explicit instance -- selection-altering flags + * contradict it. */ + ret = BAD_FUNC_ARG; + goto out; } + if (! (flags & WC_RNG_BANK_FLAG_FOR_RECOVERY)) + flags |= bank->flags & (WC_RNG_BANK_FLAG_AFFINITY_LOCK | + WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST); + if ((flags & WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST) && (bank->affinity_get_id_cb == NULL)) { @@ -507,19 +764,8 @@ WOLFSSL_API int wc_rng_bank_checkout( "BUG: wc_rng_bank_checkout() called with _PREFER_AFFINITY_INST but " "no _get_id_cb.\n"); #endif - return BAD_FUNC_ARG; - } - - /* Increment bank->refcount here speculatively, and assert on the resulting - * refcount, to mitigate races with bank deallocation. - */ - wolfSSL_RefInc_IfAtLeast(&bank->refcount, 1, &new_refcount, &ret); - if (ret != 0) { -#ifdef WC_VERBOSE_RNG - WOLFSSL_DEBUG_PRINTF( - "wc_rng_bank_checkout() called with refcount %d.\n", new_refcount); -#endif - return ret; + ret = BAD_FUNC_ARG; + goto out; } if ((timeout_secs > 0) && (flags & WC_RNG_BANK_FLAG_CAN_WAIT)) @@ -528,7 +774,6 @@ WOLFSSL_API int wc_rng_bank_checkout( ts1 = 0; /* mollify -Wmaybe-uninitialized... */ for (; ret == 0;) { - int expected = 0; if (flags & WC_RNG_BANK_FLAG_AFFINITY_LOCK) { if ((bank->affinity_lock_cb == NULL) || @@ -536,25 +781,28 @@ WOLFSSL_API int wc_rng_bank_checkout( { #ifdef WC_VERBOSE_RNG WOLFSSL_DEBUG_PRINTF( - "BUG: wc_rng_bank_checkout() called with _AFFINITY_LOCK but " - "missing _lock_cb.\n"); + "BUG: wc_rng_bank_checkout() called with _AFFINITY_LOCK but" + " missing _lock_cb.\n"); #endif ret = BAD_FUNC_ARG; break; } ret = bank->affinity_lock_cb(bank->cb_arg); if (ret == 0) - new_lock_value |= WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED; - else if ((ret == WC_NO_ERR_TRACE(ALREADY_E)) || - (ret == WC_NO_ERR_TRACE(WC_ACCEL_INHIBIT_E))) - ret = 0; - else + lock_extra_bits |= WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED; + else if (ret == WC_NO_ERR_TRACE(INTERRUPTED_E)) break; + else { + /* need to, and can, continue regardless of the error code from + * bank->affinity_lock_cb. */ + ret = 0; + } } if (flags & WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST) { preferred_inst_offset = -1; - ret = bank->affinity_get_id_cb(bank->cb_arg, &preferred_inst_offset); + ret = bank->affinity_get_id_cb(bank->cb_arg, + &preferred_inst_offset); if (ret != 0) { #ifdef WC_VERBOSE_RNG WOLFSSL_DEBUG_PRINTF( @@ -564,35 +812,221 @@ WOLFSSL_API int wc_rng_bank_checkout( } } - if ((preferred_inst_offset < 0) || - (preferred_inst_offset >= bank->n_rngs)) - { + if (preferred_inst_offset < 0) { ret = BAD_INDEX_E; break; } - if (wolfSSL_Atomic_Int_CompareExchange( - &bank->rngs[preferred_inst_offset].lock, - &expected, - new_lock_value)) + if (preferred_inst_offset >= bank->n_rngs) { + /* An affinity id can legitimately exceed n_rngs, there may be + * more CPUs than instances. Wrap into range when the caller + * allows failover; otherwise the index is unusable. + */ + if (flags & WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST) { + preferred_inst_offset %= bank->n_rngs; + } + else { + ret = BAD_INDEX_E; + break; + } + } + +#ifdef WC_RNG_HAVE_NEXT_SEED + recovered_claim = 0; +#endif + maybe_recovery_claim = 0; + if ((wc_rng_bank_inst_lock_get(&bank->rngs[preferred_inst_offset], + lock_extra_bits) == 0) + || + /* recovery-intent checkouts claim quarantined + * (_ENTROPY_INVALIDATED) instances too -- the claimant is about + * to recover them; ordinary consumers stay refused... */ + ((flags & WC_RNG_BANK_FLAG_FOR_RECOVERY) && + (wc_rng_bank_inst_lock_get_conditional( + &bank->rngs[preferred_inst_offset], + WC_RNG_LOCK_ENTROPY_INVALIDATED, + lock_extra_bits) == 0)) +#ifdef WC_RNG_HAVE_NEXT_SEED + || + /* ...UNLESS recovery is pure computation: when the quarantined + * instance's banked next seed reads READY, any claimant + * completes the recovery -- the invalidation purge guarantees + * a READY bank is post-event, and a recovered_claim forces the + * consume-at-checkout leg below (independent of bank consume + * policy), performing the credited reseed from banked material + * before the instance is handed out. Without this admission, + * the quarantine stands in front of the only machinery that + * can lift it, and no bank instance ever recovers (observed as + * system-wide checkout timeouts after a live + * state-invalidation event). */ + ((wc_RNG_DRBG_NextSeedCurrent( + WC_RNG_BANK_INST_TO_RNG(&bank->rngs[preferred_inst_offset]), + &NextSeedCurrent) == 0) && + (NextSeedCurrent == WC_DRBG_NEXT_SEED_READY) && + (wc_rng_bank_inst_lock_get_conditional( + &bank->rngs[preferred_inst_offset], + WC_RNG_LOCK_ENTROPY_INVALIDATED, + lock_extra_bits) == 0) && + ((recovered_claim = 1) != 0)) +#endif /* WC_RNG_HAVE_NEXT_SEED */ + || + /* last resort, by caller declaration: admit to the quarantined + * instance anyway, transferring the recovery obligation to the + * caller (checkout will return NEEDS_RECOVERY_E with the lease + * held -- see WC_RNG_BANK_FLAG_MAYBE_FOR_RECOVERY). */ + ((flags & WC_RNG_BANK_FLAG_MAYBE_FOR_RECOVERY) && + (wc_rng_bank_inst_lock_get_conditional( + &bank->rngs[preferred_inst_offset], + WC_RNG_LOCK_ENTROPY_INVALIDATED, + lock_extra_bits) == 0) && + ((maybe_recovery_claim = 1) != 0)) + ) { + int inst_unusable; +#ifdef HAVE_HASHDRBG + wc_drbg_reseed_ctr_t cur_reseed_ctr = 0; +#endif + *rng_inst = &bank->rngs[preferred_inst_offset]; - if ((! (flags & WC_RNG_BANK_FLAG_CAN_WAIT)) && - (WC_RNG_BANK_RESEED_CTR(&(*rng_inst)->rng) >= - WC_RESEED_INTERVAL) && - (flags & WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST) && - (n_rngs_tried < bank->n_rngs)) +#ifdef WC_RNG_HAVE_NEXT_SEED + if ((recovered_claim != 0) || + (((flags | bank->flags) & WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED) && + (! (flags & WC_RNG_BANK_FLAG_FOR_RECOVERY)) && + (! ((flags | bank->flags) & + WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE)))) + { + /* Consume a ready banked next seed, if any, BEFORE the + * usability evaluation below, so that evaluation judges the + * post-consume state: a reseed-due instance with a ready + * bank is cured here rather than diverted from or warned + * about. The return value is deliberately ignored -- + * every outcome is fully represented in instance state and + * is handled uniformly below: + * + * consumed: reseed counter reset; no longer due; + * not ready / no DRBG: nothing changed; + * status-gated (instance already out of service): the + * bank is left intact and the instance diverts or + * errors below per flags; + * hard reseed failure: the instance is now out of + * service, and diverts (failover finds another + * instance), errors (_ERROR_ON_RNG_FAILED -> + * BAD_STATE_E via the out: mapping), or is handed + * out under incumbent bare-targeted semantics -- + * identically to any other out-of-service instance. + */ +#ifndef WC_RNG_HAVE_LOCK + /* consumption is a credited reseed; mirror the in-boundary + * _RECOVERING protocol (see wc_rng_bank_reseed_range()). A + * BUSY_E here just means another claimant is recovering the + * instance -- nothing to do, and handled uniformly below. */ + { + int recovering; + if (wc_rng_bank_inst_recovery_enter(*rng_inst, + &recovering) == 0) + { + (void)wc_rng_bank_inst_recovery_exit( + *rng_inst, recovering, + wc_RNG_DRBG_NextSeedNow( + WC_RNG_BANK_INST_TO_RNG(*rng_inst))); + } + } +#else + (void)wc_RNG_DRBG_NextSeedNow( + WC_RNG_BANK_INST_TO_RNG(*rng_inst)); +#endif + } +#endif /* WC_RNG_HAVE_NEXT_SEED */ + + /* Two scenarios where we put an instance back and move on, both of + * them only when the caller allows failover and instances remain: + * + * (1) It's not in service (a module-side failure marked it + * WC_DRBG_FAILED, or an earlier wc_rng_bank_inst_reinit() failed + * and left it WC_DRBG_NOT_INIT), or + * + * (2) It's due for reseed and the caller can't wait. + * + * rng.status, not a missing DRBG, is the out-of-service test. + * With HAVE_INTEL_RDRAND on an RDRAND-capable CPU, _InitRng() + * bypasses DRBG instantiation entirely and returns a usable + * instance with no DRBG and status WC_DRBG_OK; treating that as + * out of service would divert away from every instance in the + * bank. wc_RNG_DRBG_GetReseedCtr() reports a counter of 0 for + * such instances -- never due for reseed -- so no separate + * DRBG-presence test is needed here. + */ +#ifdef HAVE_HASHDRBG + inst_unusable = + (wc_RNG_GetStatus(WC_RNG_BANK_INST_TO_RNG(*rng_inst)) != + WC_DRBG_OK); +#else + inst_unusable = 0; +#endif + + /* Divert (release and move on / retry) when: + * + * (a) the instance is out of service and the caller demanded + * the WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED guarantee -- + * unconditionally, in both targeted and failover modes, + * with the wait/timeout machinery below bounding the + * retries and the out: mapping converting the resulting + * BUSY_E/WC_TIMEOUT_E to BAD_STATE_E; or + * + * (b) the incumbent best-effort failover divert: instances + * remain untried this lap, and the instance is out of + * service or is due for reseed for a caller that can't + * wait. (The lap disarm is the anti-livelock provision; + * with (a) in force, the guarantee supersedes it.) + * + * Without HAVE_HASHDRBG neither divert cause can exist -- + * inst_unusable is constant 0 and there is no reseed + * schedule -- so the failover disjunct is compiled out + * entirely: nothing to divert for, never divert. + */ + if ((inst_unusable && + (flags & WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED)) +#ifdef HAVE_HASHDRBG + || + ((flags & WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST) && + (n_rngs_tried < bank->n_rngs) && + (inst_unusable || + ((! (flags & WC_RNG_BANK_FLAG_CAN_WAIT)) && + (wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(*rng_inst), + &cur_reseed_ctr) == 0) && + (cur_reseed_ctr >= WC_RESEED_INTERVAL) + #ifdef WC_RNG_HAVE_NEXT_SEED + && (wc_RNG_DRBG_NextSeedCurrent( + WC_RNG_BANK_INST_TO_RNG(*rng_inst), + &NextSeedCurrent) == 0) + && (NextSeedCurrent != WC_DRBG_NEXT_SEED_READY) + #endif + ))) +#endif /* HAVE_HASHDRBG */ + ) { - WOLFSSL_ATOMIC_STORE((*rng_inst)->lock, WC_RNG_BANK_INST_LOCK_FREE); + if (inst_unusable) + diverted_unusable = 1; + (void)wc_rng_bank_inst_lock_put(*rng_inst); *rng_inst = NULL; } else { +#ifdef HAVE_HASHDRBG #ifdef WC_VERBOSE_RNG - if ((! (flags & WC_RNG_BANK_FLAG_CAN_WAIT)) && - (WC_RNG_BANK_RESEED_CTR(&(*rng_inst)->rng) >= - WC_RESEED_INTERVAL)) + if ((! (bank->flags & WC_RNG_BANK_FLAG_QUIET)) && + (! (flags & (WC_RNG_BANK_FLAG_CAN_WAIT | + WC_RNG_BANK_FLAG_FOR_RECOVERY | + WC_RNG_BANK_FLAG_MAYBE_FOR_RECOVERY))) && + (wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(*rng_inst), + &cur_reseed_ctr) == 0) && + (cur_reseed_ctr >= WC_RESEED_INTERVAL)) { + /* With WC_RNG_HAVE_NEXT_SEED, this reports only a + * genuinely-due instance: a consumable banked seed + * would already have cured it above. */ WOLFSSL_DEBUG_PRINTF( "WARNING: wc_rng_bank_checkout() returning RNG ID %d, " "currently marked for reseed, to !_CAN_WAIT caller.\n", @@ -607,22 +1041,74 @@ WOLFSSL_API int wc_rng_bank_checkout( */ #endif + if (((flags | bank->flags) & + WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE) && + (! (flags & WC_RNG_BANK_FLAG_FOR_RECOVERY))) + { + ret = wc_RNG_DRBG_Reseed_Now( + WC_RNG_BANK_INST_TO_RNG(*rng_inst), NULL, 0); + if (ret != 0) { + (void)wc_rng_bank_inst_lock_put(*rng_inst); + *rng_inst = NULL; + goto out; + } + } +#endif /* HAVE_HASHDRBG */ + #ifdef WOLFSSL_USE_SAVE_VECTOR_REGISTERS if ((flags | bank->flags) & WC_RNG_BANK_FLAG_NO_VECTOR_OPS) { ret = DISABLE_VECTOR_REGISTERS(); if (ret == 0) - WOLFSSL_ATOMIC_STORE((*rng_inst)->lock, new_lock_value | - WC_RNG_BANK_INST_LOCK_VEC_OPS_INH); + ret = wc_rng_bank_inst_lock_add_extra(*rng_inst, + WC_RNG_BANK_INST_LOCK_VEC_OPS_INH); else if (ret == WC_NO_ERR_TRACE(WC_ACCEL_INHIBIT_E)) ret = 0; else { - WOLFSSL_ATOMIC_STORE((*rng_inst)->lock, WC_RNG_BANK_INST_LOCK_FREE); + (void)wc_rng_bank_inst_lock_put(*rng_inst); *rng_inst = NULL; break; } } #endif /* WOLFSSL_USE_SAVE_VECTOR_REGISTERS */ + if (maybe_recovery_claim) { + WC_RNG_lock_arg_t claimed_lock_state = 0; + if ((wc_rng_bank_inst_lock_read(*rng_inst, + &claimed_lock_state) == 0) + && (claimed_lock_state & + WC_RNG_LOCK_ENTROPY_INVALIDATED)) + { +#ifdef WC_RNG_HAVE_NEXT_SEED + /* material may have raced in since the admission + * scan: a successful banked consume cures the + * instance and downgrades this to an ordinary + * checkout. */ + if (wc_RNG_DRBG_NextSeedNow( + WC_RNG_BANK_INST_TO_RNG(*rng_inst)) != 0) +#endif + { + /* The robust-mutex (EOWNERDEAD) pattern: an + * error return WITH the acquisition complete + * and persistent. As with pthread robust + * mutexes, "this resource needs consistency + * recovery" is only safely reportable to a + * caller that already holds it -- reporting + * without the lease races the diagnosis + * against concurrent state changes, and + * leasing without the report invites blind + * use of unrecovered state. The caller owns + * the lease: recover (credited reseed clears + * the quarantine) or check in. + * + * NOT via out: -- that's the failure path, + * which unwinds refcount and affinity state. + * This return holds everything the success + * return below holds. */ + return NEEDS_RECOVERY_E; + } + } + } + return 0; /* Short-circuit return, holding onto bank refcount, * RNG lock, affinity locks, and (if applicable) * vector register inhibition. @@ -644,9 +1130,13 @@ WOLFSSL_API int wc_rng_bank_checkout( flags &= ~(word32)WC_RNG_BANK_FLAG_AFFINITY_LOCK; flags &= ~(word32)WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST; - ++preferred_inst_offset; - if (preferred_inst_offset >= bank->n_rngs) - preferred_inst_offset = 0; + if ((n_rngs_tried == 0) && (bank->first_failover_inst >= 0)) + preferred_inst_offset = bank->first_failover_inst; + else { + ++preferred_inst_offset; + if (preferred_inst_offset >= bank->n_rngs) + preferred_inst_offset = 0; + } ++n_rngs_tried; } else { @@ -658,9 +1148,9 @@ WOLFSSL_API int wc_rng_bank_checkout( } } - if (new_lock_value & WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED) { + if (lock_extra_bits & WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED) { (void)bank->affinity_unlock_cb(bank->cb_arg); - new_lock_value &= ~WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED; + lock_extra_bits &= ~WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED; } if ((flags & WC_RNG_BANK_FLAG_CAN_WAIT) && (timeout_secs != 0)) { @@ -680,23 +1170,43 @@ WOLFSSL_API int wc_rng_bank_checkout( } } +out: + if (ret == 0) ret = RNG_FAILURE_E; - if (new_lock_value & WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED) + /* Under the WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED guarantee, a lap or + * wait that diverted from an out-of-service instance reports + * BAD_STATE_E -- distinguishing bank degradation from mere contention + * (BUSY_E) or slow contention (WC_TIMEOUT_E). */ + if (diverted_unusable && + (flags & WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED) && + ((ret == WC_NO_ERR_TRACE(BUSY_E)) || + (ret == WC_NO_ERR_TRACE(WC_TIMEOUT_E)))) + { + ret = BAD_STATE_E; + } + + if (lock_extra_bits & WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED) (void)bank->affinity_unlock_cb(bank->cb_arg); - /* Decrement the speculative refcount increment. */ - { + /* Decrement the speculative refcount increment. This also covers the + * refcount increment in wc_rng_bank_default_checkout() if that's how it was + * incremented. With _NO_CHECKOUT_REFCOUNTING neither increment + * happened. + */ + if (! (bank->flags & WC_RNG_BANK_FLAG_NO_CHECKOUT_REFCOUNTING)) { int refdec_err; wolfSSL_RefDec2(&bank->refcount, &new_refcount, &refdec_err); #ifdef WC_VERBOSE_RNG if (refdec_err != 0) WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_checkout() cleanup wolfSSL_RefDec2 returned %d.", refdec_err); + "WARNING: wc_rng_bank_checkout() cleanup wolfSSL_RefDec2 " + "returned %d.", refdec_err); else if (new_refcount <= 0) WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_checkout() bank refcount after wolfSSL_RefDec2() is %d.", new_refcount); + "WARNING: wc_rng_bank_checkout() bank refcount after " + "wolfSSL_RefDec2() is %d.", new_refcount); #else (void)new_refcount; (void)refdec_err; @@ -706,63 +1216,334 @@ WOLFSSL_API int wc_rng_bank_checkout( return ret; } -#ifdef WC_HAVE_RNG_BANKREF -WOLFSSL_LOCAL int wc_local_rng_bank_checkout_for_bankref( - struct wc_rng_bank *bank, - struct wc_rng_bank_inst **rng_inst) +WOLFSSL_API int wc_rng_bank_register_free_hook(struct wc_rng_bank *bank, + wc_rng_bank_free_hook_cb_t free_hook, void *arg) { - return wc_rng_bank_checkout( - bank, rng_inst, 0, 0, - WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST | - WC_RNG_BANK_FLAG_CAN_WAIT | - ((bank->affinity_get_id_cb != NULL) ? WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST : 0) | - ((bank->affinity_lock_cb != NULL) ? WC_RNG_BANK_FLAG_AFFINITY_LOCK : 0)); + if (bank == NULL) + return BAD_FUNC_ARG; + bank->free_hook = free_hook; + bank->free_hook_arg = arg; + return 0; } -#endif /* WC_HAVE_RNG_BANKREF */ -static WC_INLINE int rng_inst_matches_bank( - struct wc_rng_bank *bank, - struct wc_rng_bank_inst *rng_inst) +#ifdef WC_RNG_BANK_HAVE_DAEMON_SUPPORT + +WOLFSSL_API int wc_rng_bank_daemon_reserve(struct wc_rng_bank *bank, + WC_ATOMIC_UINT_ARG magic) { - if ((bank == NULL) || (rng_inst == NULL)) - return BAD_FUNC_ARG; -#ifdef WC_RNG_BANK_STATIC - if ((rng_inst >= &bank->rngs[0]) && - (rng_inst <= &bank->rngs[WC_RNG_BANK_STATIC_SIZE - 1])) - return 1; - else + int ret; + WC_ATOMIC_INT_ARG new_refcount; + + if ((bank == NULL) || (magic == WC_RNG_BANK_DAEMON_MAGIC_FREE)) return BAD_FUNC_ARG; -#else - if ((rng_inst >= bank->rngs) && - (rng_inst <= bank->rngs + bank->n_rngs - 1)) - return 1; - else + + if (bank->daemon != NULL) + return BUSY_E; + + { + WC_ATOMIC_UINT_ARG expected = WC_RNG_BANK_DAEMON_MAGIC_FREE; + if (! wolfSSL_Atomic_Uint_CompareExchange(&bank->daemon_magic, + &expected, + magic)) + return BUSY_E; + } + + wolfSSL_RefInc_IfAtLeast(&bank->refcount, 1, &new_refcount, &ret); + if (ret != 0) { +#ifdef WC_VERBOSE_RNG + WOLFSSL_DEBUG_PRINTF( + "wc_rng_bank_daemon_reserve() called with refcount %d.\n", + new_refcount); +#endif + WOLFSSL_ATOMIC_STORE(bank->daemon_magic, WC_RNG_BANK_DAEMON_MAGIC_FREE); + return ret; + } + + return 0; +} + +WOLFSSL_API int wc_rng_bank_daemon_register(struct wc_rng_bank *bank, + void *daemon, + WC_ATOMIC_UINT_ARG magic) +{ + if ((bank == NULL) || (daemon == NULL) || + (magic == WC_RNG_BANK_DAEMON_MAGIC_FREE)) + { + return BAD_FUNC_ARG; + } + + if (bank->daemon != NULL) + return ALREADY_E; + + if (WOLFSSL_ATOMIC_LOAD(bank->daemon_magic) != magic) + return WRONG_TYPE_OBJECT_E; + + bank->daemon = daemon; + + return 0; +} + +WOLFSSL_API int wc_rng_bank_daemon_unregister(struct wc_rng_bank *bank, + void **daemon, + WC_ATOMIC_UINT_ARG magic) +{ + if ((bank == NULL) || (daemon == NULL) || + (magic == WC_RNG_BANK_DAEMON_MAGIC_FREE)) + { + return BAD_FUNC_ARG; + } + + if (WOLFSSL_ATOMIC_LOAD(bank->daemon_magic) != magic) + return WRONG_TYPE_OBJECT_E; + + if (bank->daemon == NULL) + return ALREADY_E; + + *daemon = bank->daemon; + bank->daemon = NULL; + + return 0; +} + +WOLFSSL_API int wc_rng_bank_daemon_release(struct wc_rng_bank *bank, + WC_ATOMIC_UINT_ARG magic) +{ + int ret; + WC_ATOMIC_INT_ARG new_refcount; + + if ((bank == NULL) || (magic == WC_RNG_BANK_DAEMON_MAGIC_FREE)) return BAD_FUNC_ARG; + + if (WOLFSSL_ATOMIC_LOAD(bank->daemon_magic) != magic) + return WRONG_TYPE_OBJECT_E; + + if (bank->daemon != NULL) + return BUSY_E; + + wolfSSL_RefDec2(&bank->refcount, &new_refcount, &ret); +#ifdef WC_VERBOSE_RNG + /* wc_rng_bank_fini() is the sole responsibility of the context that + * called wc_rng_bank_daemon_reserve() for this wc_rng_bank. + */ + if (new_refcount < 1) + WOLFSSL_DEBUG_PRINTF( + "wc_rng_bank_daemon_release() popped refcount to %d.\n", new_refcount); + if (! (bank->flags & WC_RNG_BANK_FLAG_INITED)) + WOLFSSL_DEBUG_PRINTF( + "BUG: wc_rng_bank_daemon_release() bank is already uninited.\n"); +#else + (void)new_refcount; #endif + + WOLFSSL_ATOMIC_STORE(bank->daemon_magic, WC_RNG_BANK_DAEMON_MAGIC_FREE); + + return 0; } -WOLFSSL_API int wc_rng_bank_checkin( +#endif /* WC_RNG_BANK_HAVE_DAEMON_SUPPORT */ + +#if defined(WC_RNG_HAVE_RBGC) || defined(WC_RNG_HAVE_NEXT_SEED) + +WOLFSSL_API int wc_rng_bank_root_rng_init(struct wc_rng_bank *bank, + const byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + word32 flags) +{ + if (bank == NULL) + return BAD_FUNC_ARG; + + if (wc_RNG_GetStatus(&bank->root_rng) != WC_DRBG_NOT_INIT) + return ALREADY_E; + +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) + return wc_InitRngNonce_ex2(&bank->root_rng, + nonce, nonceSz, + perso, persoSz, + bank->heap, bank->devId, flags); +#else + (void)perso; + (void)persoSz; + if (flags != 0) + return NOT_COMPILED_IN; + return wc_InitRngNonce_ex(&bank->root_rng, nonce, nonceSz, + bank->heap, bank->devId); +#endif +} + +WOLFSSL_API WC_RNG *wc_rng_bank_root_rng_get(struct wc_rng_bank *bank) +{ + if (bank == NULL) + return NULL; + if (wc_RNG_GetStatus(&bank->root_rng) == WC_DRBG_NOT_INIT) + return NULL; + return &bank->root_rng; +} + +#endif /* WC_RNG_HAVE_RBGC || WC_RNG_HAVE_NEXT_SEED */ + +#ifdef WC_HAVE_RNG_BANKREF +/* wc_local_rng_bank_checkout_for_bankref() is the shim to the real WC_RNG when + * wc_RNG_GenerateBlock() is called on a bankref WC_RNG. It's called from + * kernel atomic contexts, where waiting for a busy instance is the hazard, not + * the fix. Thus we pass timeout_secs = 0. + * + * _CAN_WAIT is not in contradiction with that. _CAN_WAIT allows selection of + * instances that would otherwise be skipped because due for reseed, so the + * generate absorbs the reseed inline instead of skipping instances, while + * timeout_secs = 0 inhibits waiting when no instances are available. + * + * _CAN_FAIL_OVER_INST tells wc_rng_bank_checkout() to sweep every instance -- + * BUSY_E is reachable only when all of them are held at once, which is + * impossible by construction when the bank has at least as many instances as + * there can be concurrent callers. That sizing is the caller's contract: the + * linuxkm module allocates nr_cpu_ids + 4. An undersized bank does not fail + * unsafely, but it does make this return BUSY_E to callers of the public API + * that have no reason to expect it, so when WC_VERBOSE_RNG, we print a warning + * if it occurs. + */ +WOLFSSL_LOCAL int wc_local_rng_bank_checkout_for_bankref( struct wc_rng_bank *bank, struct wc_rng_bank_inst **rng_inst) { - int lockval; int ret; + if (bank == NULL) + return BAD_FUNC_ARG; + + ret = wc_rng_bank_checkout( + bank, rng_inst, 0, 0, + WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST | + WC_RNG_BANK_FLAG_CAN_WAIT | + ((bank->affinity_get_id_cb != NULL) ? + WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST : 0) | + ((bank->affinity_lock_cb != NULL) ? + WC_RNG_BANK_FLAG_AFFINITY_LOCK : 0)); + +#ifdef WC_VERBOSE_RNG + if ((ret == WC_NO_ERR_TRACE(BUSY_E)) && + (! (bank->flags & WC_RNG_BANK_FLAG_QUIET))) + { + WOLFSSL_DEBUG_PRINTF( + "WARNING: all %d rng_bank instances busy; size the bank to at " + "least the peak number of concurrent callers.\n", bank->n_rngs); + } +#endif + + return ret; +} +#endif /* WC_HAVE_RNG_BANKREF */ + +/* rng_inst_matches_bank() returns 1 if rng_inst is one of this bank's live + * instances, else an error. The INITED and refcount gates catch calls on a + * torn-down bank (wc_rng_bank_fini() clears the flags and zeroes n_rngs); the + * n_rngs and NULL checks are additional checks for the same case, to + * catch data corruption opportunistically. The range check's upper bound is + * n_rngs - 1, not WC_RNG_BANK_STATIC_SIZE - 1: on a live bank sized below + * WC_RNG_BANK_STATIC_SIZE, that bound is the only thing rejecting a pointer + * to a trailing slot that was never instantiated. + */ +static WC_INLINE int rng_inst_matches_bank( + struct wc_rng_bank *bank, + struct wc_rng_bank_inst *rng_inst) +{ + if ((bank == NULL) || (rng_inst == NULL)) + return BAD_FUNC_ARG; + if (! (bank->flags & WC_RNG_BANK_FLAG_INITED)) + return BAD_STATE_E; + /* a live lease implies a per-checkout ref -- unless the bank runs + * _NO_CHECKOUT_REFCOUNTING, where only the standing baseline holds. */ + if (wolfSSL_RefCur(bank->refcount) < + ((bank->flags & WC_RNG_BANK_FLAG_NO_CHECKOUT_REFCOUNTING) ? 1 : 2)) + return BAD_STATE_E; + + if (bank->n_rngs <= 0) + return BAD_FUNC_ARG; + +#ifndef WC_RNG_BANK_STATIC + /* Not testable in the static build, rngs is an array, never NULL. */ + if (bank->rngs == NULL) + return BAD_FUNC_ARG; +#endif + + /* Compare integer addresses: the negative tests deliberately supply + * fabricated pointers, for which pointer relationals and subtraction + * are undefined (C11 6.5.8p5 / 6.5.6p9). Integer comparisons are + * defined for any value. */ + if (((wc_ptr_t)rng_inst < (wc_ptr_t)&bank->rngs[0]) || + ((wc_ptr_t)rng_inst > (wc_ptr_t)&bank->rngs[bank->n_rngs - 1])) + { + return BAD_FUNC_ARG; + } + + /* Reject a pointer into the middle of an instance. */ + if ((((wc_ptr_t)rng_inst - (wc_ptr_t)&bank->rngs[0]) % + sizeof(*rng_inst)) != 0) + { + return BAD_FUNC_ARG; + } + + return 1; +} + +WOLFSSL_API int wc_rng_bank_get_inst_id(struct wc_rng_bank_inst *rng_inst) { + int ret; if (rng_inst == NULL) return BAD_FUNC_ARG; + ret = rng_inst_matches_bank(rng_inst->bank, rng_inst); + if (ret < 0) + return ret; + return (int)(((wc_ptr_t)rng_inst - (wc_ptr_t)&rng_inst->bank->rngs[0]) / + sizeof(*rng_inst)); +} + +WOLFSSL_API int wc_rng_bank_checkin( + struct wc_rng_bank *bank, + struct wc_rng_bank_inst **rng_inst) +{ + WC_RNG_lock_arg_t lockval; + int ret; + + if ((rng_inst == NULL) || (*rng_inst == NULL)) + return BAD_FUNC_ARG; #ifdef WC_RNG_BANK_DEFAULT_SUPPORT if (bank == NULL) - bank = default_rng_bank; + bank = (*rng_inst)->bank; #endif ret = rng_inst_matches_bank(bank, *rng_inst); - if (ret < 0) + if (ret < 0) { + /* Nothing can be released here: the instance the caller actually holds + * can't be identified from a pointer that isn't in this bank, so its + * lock and the bank refcount stay held and wc_rng_bank_fini() will + * report BUSY_E/BAD_STATE_E until the caller checks in correctly. + * + * We can't warn for this misuse because random_bank_test() exercises + * the functionality. + */ +#ifdef WC_RNG_BANK_LOCK_DEBUG + WOLFSSL_DEBUG_PRINTF( + "BUG: wc_rng_bank_checkin() with an instance that is not in this " + "bank; caller's lock and bank refcount (if any) remain held.\n"); +#endif return ret; + } - lockval = (int)WOLFSSL_ATOMIC_LOAD((*rng_inst)->lock); + ret = wc_rng_bank_inst_lock_read(*rng_inst, &lockval); + if (ret < 0) + return ret; - WOLFSSL_ATOMIC_STORE((*rng_inst)->lock, WC_RNG_BANK_INST_LOCK_FREE); + ret = wc_rng_bank_inst_lock_put(*rng_inst); + if (ret != 0) { +#ifdef WC_RNG_BANK_LOCK_DEBUG + WOLFSSL_DEBUG_PRINTF( + "wc_rng_bank_checkin(): wc_rng_bank_inst_lock_put() returned " + "code %d (lock state 0x%x).\n", ret, lockval); +#endif + if (ret == WC_NO_ERR_TRACE(OBJECT_NOT_LOCKED_E)) + return ret; + /* else NEEDS_RECOVERY_E -- proceed with check-in. */ + } *rng_inst = NULL; @@ -770,21 +1551,21 @@ WOLFSSL_API int wc_rng_bank_checkin( REENABLE_VECTOR_REGISTERS(); if (lockval & WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED) - ret = bank->affinity_unlock_cb(bank->cb_arg); - else - ret = 0; + (void)bank->affinity_unlock_cb(bank->cb_arg); - { + if (! (bank->flags & WC_RNG_BANK_FLAG_NO_CHECKOUT_REFCOUNTING)) { WC_ATOMIC_INT_ARG new_refcount; int refdec_err; wolfSSL_RefDec2(&bank->refcount, &new_refcount, &refdec_err); #ifdef WC_VERBOSE_RNG if (refdec_err != 0) WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_checkin() wolfSSL_RefDec2 returned %d.", refdec_err); + "WARNING: wc_rng_bank_checkin() wolfSSL_RefDec2 returned %d.", + refdec_err); else if (new_refcount <= 0) WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_checkin() bank refcount after wolfSSL_RefDec2() is %d.", new_refcount); + "WARNING: wc_rng_bank_checkin() bank refcount after " + "wolfSSL_RefDec2() is %d.", new_refcount); #else (void)new_refcount; (void)refdec_err; @@ -794,6 +1575,93 @@ WOLFSSL_API int wc_rng_bank_checkin( return ret; } +WOLFSSL_API int wc_rng_bank_inst_checkin( + struct wc_rng_bank_inst **rng_inst) +{ + if ((rng_inst == NULL) || (*rng_inst == NULL)) + return BAD_FUNC_ARG; + return wc_rng_bank_checkin((*rng_inst)->bank, rng_inst); +} + +#ifdef WC_RNG_HAVE_NEXT_SEED + +#define WC_RNG_BANK_INST_OP_DAEMON ((WC_ATOMIC_INT_ARG)1) +#define WC_RNG_BANK_INST_OP_REINIT ((WC_ATOMIC_INT_ARG)2) + +static int wc_rng_bank_next_seed_generate_local( + struct wc_rng_bank *bank, + int inst_offset, + word32 n, + WC_RNG *root) +{ + int ret; + WC_ATOMIC_INT_ARG expected = 0; + + if (bank == NULL) + return BAD_FUNC_ARG; + if (! (bank->flags & WC_RNG_BANK_FLAG_INITED)) + return BAD_FUNC_ARG; + if (inst_offset < 0) + return BAD_FUNC_ARG; + if (inst_offset >= bank->n_rngs) + return BAD_FUNC_ARG; + +#ifndef WC_RNG_HAVE_RBGC + if (root != NULL) + return NOT_COMPILED_IN; +#endif + + if (! wolfSSL_Atomic_Int_CompareExchange(&bank->inst_op_gate, &expected, + WC_RNG_BANK_INST_OP_DAEMON)) + { + /* A whole-instance operation (reinit) is in progress somewhere in + * the bank -- skip this turn. */ + return BUSY_E; + } + +#ifdef WC_RNG_HAVE_RBGC + if (root != NULL) { + ret = wc_RNG_DRBG_NextSeedGenerate_RBGC( + WC_RNG_BANK_INST_TO_RNG(&bank->rngs[inst_offset]), root, n); + } + else +#endif + { + ret = wc_RNG_DRBG_NextSeedGenerate( + WC_RNG_BANK_INST_TO_RNG(&bank->rngs[inst_offset]), n); + } + + WOLFSSL_ATOMIC_STORE(bank->inst_op_gate, 0); + + return ret; +} + +#ifdef WC_RNG_HAVE_RBGC +WOLFSSL_API int wc_rng_bank_next_seed_generate_rbgc( + struct wc_rng_bank *bank, + int inst_offset, + word32 n) +{ + if (bank == NULL) + return BAD_FUNC_ARG; + + if (wc_RNG_GetStatus(&bank->root_rng) != WC_DRBG_OK) + return NOT_READY_E; + + return wc_rng_bank_next_seed_generate_local(bank, inst_offset, n, &bank->root_rng); +} +#endif /* WC_RNG_HAVE_RBGC */ + +WOLFSSL_API int wc_rng_bank_next_seed_generate( + struct wc_rng_bank *bank, + int inst_offset, + word32 n) +{ + return wc_rng_bank_next_seed_generate_local(bank, inst_offset, n, NULL); +} + +#endif /* WC_RNG_HAVE_NEXT_SEED */ + /* note the rng_inst passed to wc_rng_bank_inst_reinit() must have been obtained * via wc_rng_bank_checkout() to assure that the caller holds the proper locks. */ @@ -806,22 +1674,39 @@ WOLFSSL_API int wc_rng_bank_inst_reinit( int ret; time_t ts1 = 0; int devId; + WC_RNG_lock_arg_t cur_lock = 0; +#ifdef WC_RNG_DEBUG_STATS + struct wc_rng_debug_stats_snapshot s; + int stats_snap_ret; +#endif +#ifdef WC_RNG_INIT_FLAG_LOCK_REQUIRED + word32 rng_flags = WC_RNG_INIT_FLAG_LOCK_REQUIRED | + WC_RNG_INIT_FLAG_LOCK_INITIALLY; +#else + WC_MAYBE_UNUSED word32 rng_flags = WC_RNG_INIT_FLAG_NONE; +#endif + + if (rng_inst == NULL) + return BAD_FUNC_ARG; -#ifdef WC_RNG_BANK_DEFAULT_SUPPORT if (bank == NULL) - bank = default_rng_bank; -#endif + bank = rng_inst->bank; - /* rng_inst NULL check handled by rng_inst_matches_bank() */ ret = rng_inst_matches_bank(bank, rng_inst); if (ret < 0) - return BAD_FUNC_ARG; + return ret; - if (WC_RNG_BANK_DRBG_NULL(&rng_inst->rng)) - { - return BAD_FUNC_ARG; - } +#ifdef WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED + if (flags & WC_RNG_BANK_FLAG_AUTO_RECOVER_AND_PROMOTE) + rng_flags |= WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED; +#endif + /* No DRBG-NULL rejection here. wc_FreeRng() below nulls the DRBG, so an + * instance left that way by an earlier failed reinit needs another attempt. + * Note that with HAVE_INTEL_RDRAND on an RDRAND-capable CPU, a NULL DRBG is + * the normal in-service state. wc_FreeRng() null-checks each member, so + * it is a safe no-op when called on an already-freed instance. + */ if ((timeout_secs > 0) && (flags & WC_RNG_BANK_FLAG_CAN_WAIT)) ts1 = XTIME(0); @@ -831,143 +1716,718 @@ WOLFSSL_API int wc_rng_bank_inst_reinit( devId = INVALID_DEVID; #endif +#ifdef WC_RNG_HAVE_NEXT_SEED + /* Exclude the entropy daemon's lockless banking for the duration of the + * free/reinstantiate cycle. Non-blocking on both sides: if the daemon + * holds the gate, skip this reinit attempt (the instance stays out of + * service and a later checkout retries); if reinit holds it, the daemon + * skips its turn. */ + { + WC_ATOMIC_INT_ARG expected = 0; + if (! wolfSSL_Atomic_Int_CompareExchange(&bank->inst_op_gate, + &expected, + WC_RNG_BANK_INST_OP_REINIT)) + { + return BUSY_E; + } + } +#endif + + /* Pre-read the held latch's annotation bits -- an owner-context read + * of a live object (the constructor itself never reads its target). + * The reinit is declared _LOCK_INITIALLY, so the instance is + * invariantly locked across the free/reinstantiate cycle, and the + * annotations (including a sticky WC_RNG_LOCK_REQUIRED, when the + * instance carries one) are re-asserted below on success. */ + ret = wc_rng_bank_inst_lock_read(rng_inst, &cur_lock); + if (ret < 0) + return ret; + +#ifdef WC_RNG_DEBUG_STATS + stats_snap_ret = + wc_rng_debug_stats_snap(&s, WC_RNG_BANK_INST_TO_RNG(rng_inst)); +#endif + wc_FreeRng(&rng_inst->rng); for (;;) { +#ifdef WC_RNG_INIT_FLAG_LOCK_REQUIRED + ret = wc_InitRngNonce_ex2(WC_RNG_BANK_INST_TO_RNG(rng_inst), + (byte *)&rng_inst, sizeof(byte *), + NULL, 0, bank->heap, devId, rng_flags); +#else ret = wc_InitRngNonce_ex(WC_RNG_BANK_INST_TO_RNG(rng_inst), - (byte *)&rng_inst, sizeof(byte *), - bank->heap, devId); - if (ret == 0) + (byte *)&rng_inst, sizeof(byte *), + bank->heap, devId); +#endif + + if (ret == 0) { + if (cur_lock != 0) { + ret = wc_rng_bank_inst_lock_set_extra(rng_inst, cur_lock); + } +#ifdef WC_RNG_DEBUG_STATS + if (stats_snap_ret == 0) + wc_rng_debug_stats_restore( + &s, WC_RNG_BANK_INST_TO_RNG(rng_inst)); +#endif break; + } + + /* Relax between iterations exactly as wc_rng_bank_init() does. The + * caller may hold the affinity lock taken by wc_rng_bank_checkout(), + * so this must not sleep in atomic context; WC_RELAX_LONG_LOOP() + * degrades to a cpu_relax() there. + */ + WC_RELAX_LONG_LOOP(); + + /* Several plausible error codes are non-retryable -- fail early for + * these rather than reattempting until the timeout. Same list as + * wc_rng_bank_init(). + */ + switch (ret) { + case WC_NO_ERR_TRACE(BAD_MUTEX_E): + case WC_NO_ERR_TRACE(BAD_FUNC_ARG): + case WC_NO_ERR_TRACE(MEMORY_E): + case WC_NO_ERR_TRACE(NOT_COMPILED_IN): + case WC_NO_ERR_TRACE(MISSING_RNG_E): + case WC_NO_ERR_TRACE(BUFFER_E): + case WC_NO_ERR_TRACE(OPEN_RAN_E): + case WC_NO_ERR_TRACE(FIPS_NOT_ALLOWED_E): + case WC_NO_ERR_TRACE(DRBG_KAT_FIPS_E): + case WC_NO_ERR_TRACE(DRBG_CONT_FIPS_E): +#ifdef WC_VERBOSE_RNG + if (! (bank->flags & WC_RNG_BANK_FLAG_QUIET)) + WOLFSSL_DEBUG_PRINTF( + "WARNING: wc_rng_bank_inst_reinit() non-retryable err " + "%d.\n", ret); +#endif + goto out; + } + if ((! (flags & WC_RNG_BANK_FLAG_CAN_WAIT)) || (timeout_secs == 0)) { #ifdef WC_VERBOSE_RNG - WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_inst_reinit() returning err %d.\n", ret); + if (! (bank->flags & WC_RNG_BANK_FLAG_QUIET)) + WOLFSSL_DEBUG_PRINTF( + "WARNING: wc_rng_bank_inst_reinit() returning err %d.\n", + ret); #endif break; } + /* Allow interrupt only once we are stuck spinning retries. Without + * this, a negative timeout_secs (retry indefinitely) has no break at + * all other than success. + */ + { + int intr = WC_CHECK_FOR_INTR_SIGNALS(); + if (intr == WC_NO_ERR_TRACE(INTERRUPTED_E)) { + ret = intr; + break; + } + } + if (timeout_secs > 0) { time_t ts2 = XTIME(0); if (ts2 - ts1 >= timeout_secs) { #ifdef WC_VERBOSE_RNG - WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_inst_reinit() timed out, err %d.\n", - ret); + if (! (bank->flags & WC_RNG_BANK_FLAG_QUIET)) + WOLFSSL_DEBUG_PRINTF( + "WARNING: wc_rng_bank_inst_reinit() timed out, " + "err %d.\n", ret); #endif break; } } } - return ret; -} +out: -WOLFSSL_API int wc_rng_bank_seed(struct wc_rng_bank *bank, - const byte* seed, word32 seedSz, - int timeout_secs, - word32 flags) + /* Leave a failed instance explicitly out of service rather than relying + * on whichever status _InitRng() happened to leave behind (some of its + * platform-specific failure paths return with status WC_DRBG_OK). + * wc_FreeRng() deterministically leaves status WC_DRBG_NOT_INIT -- out of + * service under every status-gate in this facility -- and is idempotent + * on the failed-init carcass, so the marking happens entirely through + * the module's own service interface. wc_rng_bank_checkout() diverts + * away from such an instance when the caller allows failover, and the + * seed/reseed walks refuse it. + */ + if (ret != 0) + (void)wc_FreeRng(WC_RNG_BANK_INST_TO_RNG(rng_inst)); + +#ifdef WC_RNG_HAVE_NEXT_SEED + WOLFSSL_ATOMIC_STORE(bank->inst_op_gate, 0); +#endif + + return ret; +} + +WOLFSSL_API int wc_rng_bank_recover_inst( + struct wc_rng_bank *bank, + int inst_offset, + int timeout_secs, + word32 flags) +{ + struct wc_rng_bank_inst *rng_inst = NULL; + int ret; + int checkin_ret; + + if ((bank == NULL) || + (flags & ~(word32)(WC_RNG_BANK_FLAG_CAN_WAIT | + WC_RNG_BANK_FLAG_AFFINITY_LOCK | + WC_RNG_BANK_FLAG_RBGC | + WC_RNG_BANK_FLAG_AUTO_RECOVER_AND_PROMOTE))) + { + return BAD_FUNC_ARG; + } + + ret = wc_rng_bank_checkout(bank, &rng_inst, inst_offset, timeout_secs, + flags | WC_RNG_BANK_FLAG_FOR_RECOVERY); + if (ret != 0) + return ret; + + if (wc_RNG_GetStatus(WC_RNG_BANK_INST_TO_RNG(rng_inst)) != WC_DRBG_OK) { + /* Out of service -- recover it. A BUSY_E from the whole-instance- + * operation gate is retryable on a later patrol turn. */ + ret = wc_rng_bank_inst_reinit(bank, rng_inst, timeout_secs, flags); + } +#if defined(WC_RNG_HAVE_LOCK) && defined(HAVE_HASHDRBG) + else { + WC_RNG_lock_arg_t lock_state = 0; + if ((wc_rng_bank_inst_lock_read(rng_inst, &lock_state) == 0) && + (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + { + /* In service but quarantined: one credited reseed clears the + * quarantine -- lighter than reinit, preserving instance + * identity. */ +#ifdef WC_RNG_HAVE_RBGC + WC_RNG_lock_arg_t root_lock_state = 0; + if ((flags & WC_RNG_BANK_FLAG_RBGC) && + (wc_RNG_GetStatus(&bank->root_rng) == WC_DRBG_OK) && + (wc_RNG_lock_read(&bank->root_rng, + &root_lock_state) == 0) && + (! (root_lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED)) && + (wc_RNG_DRBG_ReseedRBGC(WC_RNG_BANK_INST_TO_RNG(rng_inst), + &bank->root_rng, NULL, 0) == 0)) + { + ret = 0; + } + else +#endif + { + ret = wc_RNG_DRBG_Reseed_Now(WC_RNG_BANK_INST_TO_RNG(rng_inst), + NULL, 0); + } + } + /* else: healthy -- a stale lockless status observation; no-op. */ + } +#else + /* else: healthy -- a stale lockless status observation; no-op. */ +#endif + + checkin_ret = wc_rng_bank_checkin(bank, &rng_inst); + if ((checkin_ret != 0) && (ret == 0)) + ret = checkin_ret; + + return ret; +} + +#ifdef WC_RNG_HAVE_RBGC +/* Unified mechanics for wc_rng_bank_spawn() and wc_rng_bank_spawn_new(): + * check out -> wc_InitRngNonceRBGC[_New]() -> check in, following the + * exactly-one-destination convention of random.c's SpawnRngRBGC(). All + * RBGC semantics (depth-one enforcement, leaf tagging, strength + * accounting, root reseed-counter debit) are the spawn APIs' own; the + * bank contributes instance selection, the lease, and (optionally, via + * WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED) a banked reseed of the root before + * the spawn draw. */ +static int rng_bank_spawn( + struct wc_rng_bank *bank, + WC_RNG *leaf_stack, + WC_RNG **leaf_heap, + byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + int preferred_inst_offset, + int timeout_secs, + word32 flags) +{ + struct wc_rng_bank_inst *rng_inst = NULL; + int ret; + int checkin_ret; + +#if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) + (void)perso; + (void)persoSz; +#endif + + if ((leaf_stack == NULL) == (leaf_heap == NULL)) + return BAD_FUNC_ARG; + + if (flags & (WC_RNG_BANK_FLAG_STIR | + WC_RNG_BANK_FLAG_FOR_RECOVERY)) + return BAD_FUNC_ARG; + + /* bank == NULL resolves to the default bank inside + * wc_rng_bank_checkout(), which carries the default-bank refcount + * through the lease; the one-arg wc_rng_bank_inst_checkin() releases + * the whole arrangement without requiring a bank pointer here. + * WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED makes the checkout itself + * guarantee an in-service instance (or an error with no lease), so no + * status gate is needed here. */ + ret = wc_rng_bank_checkout(bank, &rng_inst, preferred_inst_offset, + timeout_secs, + flags | WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED); + if (ret != 0) + return ret; + + { + word32 child_init_flags = WC_RNG_INIT_FLAG_NONE; +#ifdef WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED + if (flags & WC_RNG_BANK_FLAG_AUTO_RECOVER_AND_PROMOTE) + child_init_flags |= + WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED; +#endif + if (leaf_stack != NULL) { + ret = wc_InitRngNonceRBGC(leaf_stack, + WC_RNG_BANK_INST_TO_RNG(rng_inst), + nonce, nonceSz, +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) + perso, persoSz, +#endif + child_init_flags + ); + } + else { +#ifndef WC_NO_CONSTRUCTORS + ret = wc_InitRngNonceRBGC_New(leaf_heap, + WC_RNG_BANK_INST_TO_RNG(rng_inst), + nonce, nonceSz, + perso, persoSz, + child_init_flags); +#else + /* Unreachable: wc_rng_bank_spawn_new() is absent under + * WC_NO_CONSTRUCTORS, so leaf_heap is always null here. */ + ret = BAD_FUNC_ARG; +#endif + } + } + + checkin_ret = wc_rng_bank_inst_checkin(&rng_inst); + if ((checkin_ret != 0) && (ret == 0)) { + /* The leaf came up but the lease release failed: surface the + * check-in error and don't hand back a leaf the caller would + * reasonably pair with a healthy bank. */ + if (leaf_stack != NULL) { + (void)wc_FreeRng(leaf_stack); + } +#ifndef WC_NO_CONSTRUCTORS + else { + wc_rng_free(*leaf_heap); + *leaf_heap = NULL; + } +#endif + ret = checkin_ret; + } + + return ret; +} + +WOLFSSL_API int wc_rng_bank_spawn( + struct wc_rng_bank *bank, + WC_RNG *leaf_rng, + byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + int preferred_inst_offset, + int timeout_secs, + word32 flags) +{ + return rng_bank_spawn(bank, leaf_rng, NULL, nonce, nonceSz, + perso, persoSz, + preferred_inst_offset, timeout_secs, flags); +} + +#ifndef WC_NO_CONSTRUCTORS +WOLFSSL_API int wc_rng_bank_spawn_new( + struct wc_rng_bank *bank, + WC_RNG **leaf_rng, + byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + int preferred_inst_offset, + int timeout_secs, + word32 flags) +{ + return rng_bank_spawn(bank, NULL, leaf_rng, nonce, nonceSz, + perso, persoSz, + preferred_inst_offset, timeout_secs, flags); +} +#endif /* !WC_NO_CONSTRUCTORS */ +#endif /* WC_RNG_HAVE_RBGC */ + +#ifdef HAVE_HASHDRBG + +WOLFSSL_API int wc_rng_bank_seed_range(struct wc_rng_bank *bank, + int first_inst, int last_inst, + const byte* seed, word32 seedSz, + const byte *nonce, word32 nonceSz, + int timeout_secs, + word32 flags) { int ret = 0; int n; - #ifdef WC_RNG_BANK_DEFAULT_SUPPORT - if (bank == NULL) - bank = default_rng_bank; + int bank_is_default = 0; #endif - if ((bank == NULL) || - (! (bank->flags & WC_RNG_BANK_FLAG_INITED))) + /* wc_rng_bank_seed_range() must walk every instance in range by explicit + * index -- forbid flags that would let wc_rng_bank_checkout() pick a + * different instance than requested. Same restriction applies in + * wc_rng_bank_reseed_range(). + */ + if (flags & (WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST | + WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST | + WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED | + WC_RNG_BANK_FLAG_FOR_RECOVERY)) + return BAD_FUNC_ARG; + + if (first_inst < 0) + return BAD_INDEX_E; + + if (((seedSz > 0) && (seed == NULL)) || + ((nonceSz > 0) && (nonce == NULL))) { return BAD_FUNC_ARG; } - if (seedSz == 0) - return 0; + if ((seedSz == 0) && (nonceSz > 0)) + return BAD_FUNC_ARG; + + if (bank == NULL) { +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + if (seedSz == 0) { + if (default_rng_bank == NULL) + return NO_DEFAULT_FOUND_E; + else + return 0; + } + ret = wc_rng_bank_default_checkout(&bank); + if (ret != 0) + return ret; + bank_is_default = 1; +#else + return BAD_FUNC_ARG; +#endif + } + else { + if (! (bank->flags & WC_RNG_BANK_FLAG_INITED)) + return BAD_STATE_E; + if (seedSz == 0) + return 0; + } + + if (first_inst >= bank->n_rngs) { + ret = BAD_INDEX_E; + goto out; + } + if (last_inst < 0) + last_inst = bank->n_rngs - 1; + else if (last_inst >= bank->n_rngs) { + ret = BAD_INDEX_E; + goto out; + } + else if (last_inst < first_inst) { + ret = BAD_INDEX_E; + goto out; + } - /* this iteration counts down, whereas the iteration in get_drbg() counts + /* This iteration counts down, whereas the iteration in get_drbg() counts * up, to assure they can't possibly phase-lock to each other. */ - for (n = bank->n_rngs - 1; n >= 0; --n) { + for (n = last_inst; n >= first_inst; --n) { struct wc_rng_bank_inst *drbg; - ret = wc_rng_bank_checkout(bank, &drbg, n, timeout_secs, flags); + ret = wc_rng_bank_checkout(bank, &drbg, n, timeout_secs, + flags & ~(word32)WC_RNG_BANK_FLAG_STIR); if (ret != 0) { #ifdef WC_VERBOSE_RNG - WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_seed(): wc_rng_bank_checkout() for " - "inst#%d returned err %d.\n", n, ret); + if (! (bank->flags & WC_RNG_BANK_FLAG_QUIET)) + WOLFSSL_DEBUG_PRINTF( + "WARNING: wc_rng_bank_seed_range(): " + "wc_rng_bank_checkout() for " + "inst#%d returned err %d.\n", n, ret); #endif break; } - else if (WC_RNG_BANK_DRBG_NULL(&drbg->rng)) { + else if (wc_RNG_GetStatus(WC_RNG_BANK_INST_TO_RNG(drbg)) != + WC_DRBG_OK) + { #ifdef WC_VERBOSE_RNG - WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_seed(): inst#%d has null .drbg.\n", n); + if (! (bank->flags & WC_RNG_BANK_FLAG_QUIET)) + WOLFSSL_DEBUG_PRINTF( + "WARNING: wc_rng_bank_seed_range(): inst#%d is out of service " + "(status %d).\n", n, + wc_RNG_GetStatus(WC_RNG_BANK_INST_TO_RNG(drbg))); #endif ret = BAD_STATE_E; } - else if ((ret = wc_RNG_DRBG_Reseed(WC_RNG_BANK_INST_TO_RNG(drbg), seed, - seedSz)) != 0) - { + else if (! wc_RNG_DRBG_Present(WC_RNG_BANK_INST_TO_RNG(drbg))) { + /* Note that a NULL DRBG doesn't necessarily indicate a degraded + * RNG: _InitRng() bypasses DRBG instantiation for + * HAVE_INTEL_RDRAND. We just have no way to seed it, so don't + * pretend we can. + */ + ret = WRONG_TYPE_OBJECT_E; + } + else { + if (flags & WC_RNG_BANK_FLAG_STIR) { + ret = wc_RNG_DRBG_Stir_Nonce( + WC_RNG_BANK_INST_TO_RNG(drbg), seed, seedSz, + nonce, nonceSz); #ifdef WC_VERBOSE_RNG - WOLFSSL_DEBUG_PRINTF( - "WARNING: wc_rng_bank_seed(): Hash_DRBG_Reseed() for inst#%d " - "returned %d\n", n, ret); + if ((ret != 0) && (! (bank->flags & WC_RNG_BANK_FLAG_QUIET))) { + WOLFSSL_DEBUG_PRINTF( + "WARNING: wc_rng_bank_seed_range(): " + "wc_RNG_DRBG_Stir_Nonce() for inst#%d " + "returned %d\n", n, ret); + } +#endif + } + else { + ret = wc_RNG_DRBG_Reseed_Nonce( + WC_RNG_BANK_INST_TO_RNG(drbg), seed, seedSz, + nonce, nonceSz); +#ifdef WC_VERBOSE_RNG + if ((ret != 0) && (! (bank->flags & WC_RNG_BANK_FLAG_QUIET))) { + WOLFSSL_DEBUG_PRINTF( + "WARNING: wc_rng_bank_seed_range(): " + "wc_RNG_DRBG_Reseed_Nonce() for inst#%d " + "returned %d\n", n, ret); + } #endif + } } - (void)wc_rng_bank_checkin(bank, &drbg); + { + int checkin_ret = wc_rng_bank_checkin(bank, &drbg); + if (checkin_ret != 0) { + if (ret == 0) + ret = checkin_ret; +#ifdef WC_VERBOSE_RNG + if (! (bank->flags & WC_RNG_BANK_FLAG_QUIET)) { + WOLFSSL_DEBUG_PRINTF( + "WARNING: wc_rng_bank_seed_range(): " + "wc_rng_bank_checkin() for " + "inst#%d returned err %d.\n", n, checkin_ret); + } +#endif + } + } if (ret != 0) break; } +out: + +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + if (bank_is_default) + (void)wc_rng_bank_default_checkin(&bank); +#endif + return ret; } -WOLFSSL_API int wc_rng_bank_reseed(struct wc_rng_bank *bank, - int timeout_secs, - word32 flags) +WOLFSSL_API int wc_rng_bank_seed(struct wc_rng_bank *bank, + const byte* seed, word32 seedSz, + const byte *nonce, word32 nonceSz, + int timeout_secs, + word32 flags) +{ + return wc_rng_bank_seed_range(bank, 0, -1, seed, seedSz, nonce, nonceSz, + timeout_secs, flags); +} + +WOLFSSL_API int wc_rng_bank_reseed_range(struct wc_rng_bank *bank, + int first_inst, int last_inst, + const byte *nonce, word32 nonceSz, + int timeout_secs, + word32 flags) { int n; int ret; time_t ts1 = 0; - #ifdef WC_RNG_BANK_DEFAULT_SUPPORT - if (bank == NULL) - bank = default_rng_bank; + int bank_is_default = 0; #endif - if ((bank == NULL) || - (! (bank->flags & WC_RNG_BANK_FLAG_INITED))) - { + /* wc_rng_bank_reseed() must walk every instance by explicit index -- forbid + * flags that would let wc_rng_bank_checkout() pick a different instance + * than requested. Same restriction applies in wc_rng_bank_seed(). + * WC_RNG_BANK_FLAG_STIR applies only to wc_rng_bank_seed() -- + * a bank reseed is always from the module's own seed source, and always + * credited. + */ + if (flags & (WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST | + WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST | + WC_RNG_BANK_FLAG_STIR | + WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED | + WC_RNG_BANK_FLAG_FOR_RECOVERY)) return BAD_FUNC_ARG; - } - if (flags & (WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST | - WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST)) + if (first_inst < 0) + return BAD_INDEX_E; + + if (bank == NULL) { +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + ret = wc_rng_bank_default_checkout(&bank); + if (ret != 0) + return ret; + bank_is_default = 1; +#else return BAD_FUNC_ARG; +#endif + } + else { + if (! (bank->flags & WC_RNG_BANK_FLAG_INITED)) + return BAD_STATE_E; + } + + if (first_inst >= bank->n_rngs) { + ret = BAD_INDEX_E; + goto out; + } + if (last_inst < 0) + last_inst = bank->n_rngs - 1; + else if (last_inst >= bank->n_rngs) { + ret = BAD_INDEX_E; + goto out; + } + else if (last_inst < first_inst) { + ret = BAD_INDEX_E; + goto out; + } + +#ifdef WC_RNG_HAVE_RBGC + if ((flags & WC_RNG_BANK_FLAG_RBGC) && + (wc_RNG_GetStatus(&bank->root_rng) != WC_DRBG_OK)) + { + ret = NOT_READY_E; + goto out; + } +#else + if (flags & WC_RNG_BANK_FLAG_RBGC) + return NOT_COMPILED_IN; +#endif if ((timeout_secs > 0) && (flags & WC_RNG_BANK_FLAG_CAN_WAIT)) ts1 = XTIME(0); - for (n = bank->n_rngs - 1; n >= 0; --n) { + for (n = last_inst; n >= first_inst; --n) { struct wc_rng_bank_inst *drbg; - ret = wc_rng_bank_checkout(bank, &drbg, n, timeout_secs, flags); + ret = wc_rng_bank_checkout(bank, &drbg, n, timeout_secs, + flags | WC_RNG_BANK_FLAG_FOR_RECOVERY); if (ret != 0) - return ret; + goto out; - WC_RNG_BANK_SET_RESEED_CTR(&drbg->rng, WC_RESEED_INTERVAL); + if (wc_RNG_GetStatus(WC_RNG_BANK_INST_TO_RNG(drbg)) != WC_DRBG_OK) { +#ifdef WC_VERBOSE_RNG + WOLFSSL_DEBUG_PRINTF( + "WARNING: wc_rng_bank_reseed(): inst#%d is out of service " + "(status %d).\n", n, + wc_RNG_GetStatus(WC_RNG_BANK_INST_TO_RNG(drbg))); +#endif + (void)wc_rng_bank_checkin(bank, &drbg); + ret = BAD_STATE_E; + goto out; + } + + /* An in-service instance can still have no DRBG: _InitRng() bypasses + * DRBG instantiation when the CPU has RDRAND (HAVE_INTEL_RDRAND). + * There is nothing to reseed in that case; skip, do not fail. + */ + if (! wc_RNG_DRBG_Present(WC_RNG_BANK_INST_TO_RNG(drbg))) { + (void)wc_rng_bank_checkin(bank, &drbg); + continue; + } if (flags & WC_RNG_BANK_FLAG_CAN_WAIT) { - byte scratch[4]; for (;;) { time_t ts2; - ret = wc_RNG_GenerateBlock(WC_RNG_BANK_INST_TO_RNG(drbg), scratch, - (word32)sizeof(scratch)); - if (ret == 0) + +#ifndef WC_RNG_HAVE_LOCK + /* the pre-lock boundary can't see the inst-side latch: + * mirror the in-boundary _RECOVERING protocol around the + * reseed, so that a concurrent invalidation isn't swallowed + * by the clear. */ + { + int recovering; + ret = wc_rng_bank_inst_recovery_enter(drbg, &recovering); + if (ret == 0) { + #if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) + (void)nonce; + (void)nonceSz; + #ifdef WC_RNG_HAVE_RBGC + if (flags & WC_RNG_BANK_FLAG_RBGC) { + ret = wc_RNG_DRBG_ReseedRBGC( + WC_RNG_BANK_INST_TO_RNG(drbg), &bank->root_rng); + } + else + #endif + { + ret = wc_RNG_DRBG_Reseed_Now( + WC_RNG_BANK_INST_TO_RNG(drbg), NULL, 0); + } + #else + #ifdef WC_RNG_HAVE_RBGC + if (flags & WC_RNG_BANK_FLAG_RBGC) { + ret = wc_RNG_DRBG_ReseedRBGC( + WC_RNG_BANK_INST_TO_RNG(drbg), &bank->root_rng, + NULL, 0); + } + else + #endif + { + ret = wc_RNG_DRBG_Reseed_Now( + WC_RNG_BANK_INST_TO_RNG(drbg), nonce, nonceSz); + } + #endif + ret = wc_rng_bank_inst_recovery_exit(drbg, recovering, + ret); + } + } +#else /* WC_RNG_HAVE_LOCK */ + #if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) + (void)nonce; + (void)nonceSz; + #ifdef WC_RNG_HAVE_RBGC + if (flags & WC_RNG_BANK_FLAG_RBGC) { + ret = wc_RNG_DRBG_ReseedRBGC( + WC_RNG_BANK_INST_TO_RNG(drbg), &bank->root_rng); + } + else + #endif + { + ret = wc_RNG_DRBG_Reseed_Now(WC_RNG_BANK_INST_TO_RNG(drbg), + NULL, 0); + } + #else + #ifdef WC_RNG_HAVE_RBGC + if (flags & WC_RNG_BANK_FLAG_RBGC) { + ret = wc_RNG_DRBG_ReseedRBGC( + WC_RNG_BANK_INST_TO_RNG(drbg), &bank->root_rng, + NULL, 0); + } + else + #endif + { + ret = wc_RNG_DRBG_Reseed_Now(WC_RNG_BANK_INST_TO_RNG(drbg), + nonce, nonceSz); + } + #endif +#endif /* WC_RNG_HAVE_LOCK */ + + if (ret == 0) { break; + } if ((timeout_secs == 0) || (! (flags & WC_RNG_BANK_FLAG_CAN_WAIT))) { @@ -978,8 +2438,8 @@ WOLFSSL_API int wc_rng_bank_reseed(struct wc_rng_bank *bank, if (ts2 - ts1 > timeout_secs) { #ifdef WC_VERBOSE_RNG WOLFSSL_DEBUG_PRINTF( - "ERROR: timeout after attempted reseed by " - "wc_RNG_GenerateBlock() for DRBG #%d, err %d.", n, ret); + "ERROR: timeout trying wc_RNG_DRBG_Reseed_Now() " + "for DRBG #%d, err %d.", n, ret); #endif ret = WC_TIMEOUT_E; break; @@ -990,70 +2450,189 @@ WOLFSSL_API int wc_rng_bank_reseed(struct wc_rng_bank *bank, break; WC_RELAX_LONG_LOOP(); } + if (ret != 0) { + /* Preserve the pre-existing contract: a failed forced reseed + * leaves the instance due for reseed, so the next generate + * operation retries it in-boundary. */ + (void)wc_RNG_DRBG_ScheduleReseed(WC_RNG_BANK_INST_TO_RNG(drbg)); + } #ifdef WC_VERBOSE_RNG if ((ret != 0) && (ret != WC_NO_ERR_TRACE(WC_TIMEOUT_E))) WOLFSSL_DEBUG_PRINTF( - "ERROR: wc_crng_reseed() wc_RNG_GenerateBlock() " + "ERROR: wc_rng_bank_reseed() wc_RNG_DRBG_Reseed_Now() " "for DRBG #%d returned %d.", n, ret); #endif (void)wc_rng_bank_checkin(bank, &drbg); if ((ret == WC_NO_ERR_TRACE(WC_TIMEOUT_E)) || (ret == WC_NO_ERR_TRACE(INTERRUPTED_E))) { - return ret; + goto out; } ret = WC_CHECK_FOR_INTR_SIGNALS(); if (ret == WC_NO_ERR_TRACE(INTERRUPTED_E)) - return ret; + goto out; WC_RELAX_LONG_LOOP(); } else { + /* Cannot gather entropy without waiting -- mark the instance due + * for reseed and let the next entropy-capable generate operation + * perform it in-boundary. */ + (void)wc_RNG_DRBG_ScheduleReseed(WC_RNG_BANK_INST_TO_RNG(drbg)); (void)wc_rng_bank_checkin(bank, &drbg); } } - return 0; -} - -#ifdef WC_HAVE_RNG_BANKREF + ret = 0; -WOLFSSL_API int wc_InitRng_BankRef(struct wc_rng_bank *bank, WC_RNG *rng) -{ - int ret; - WC_ATOMIC_INT_ARG new_refcount; +out: #ifdef WC_RNG_BANK_DEFAULT_SUPPORT - if (bank == NULL) - bank = default_rng_bank; + if (bank_is_default) + (void)wc_rng_bank_default_checkin(&bank); #endif - if ((bank == NULL) || - (rng == NULL)) - { - return BAD_FUNC_ARG; - } - - if (! (bank->flags & WC_RNG_BANK_FLAG_INITED)) - return BAD_STATE_E; - - wolfSSL_RefInc_IfAtLeast(&bank->refcount, 1, &new_refcount, &ret); - (void)new_refcount; - if (ret != 0) - return ret; - - XMEMSET(rng, 0, sizeof(*rng)); - rng->heap = bank->heap; - rng->status = WC_DRBG_BANKREF; - rng->bankref = bank; + return ret; +} - return 0; +WOLFSSL_API int wc_rng_bank_reseed(struct wc_rng_bank *bank, + const byte *nonce, word32 nonceSz, + int timeout_secs, + word32 flags) +{ + return wc_rng_bank_reseed_range(bank, 0, -1, nonce, nonceSz, + timeout_secs, flags); } -WOLFSSL_API int wc_BankRef_Release(WC_RNG *rng) +WOLFSSL_API int wc_rng_bank_invalidate_entropy(struct wc_rng_bank *bank, + word32 flags) { - int isZero = 0; + int n; int ret = 0; - if (rng == NULL) +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + int bank_is_default = 0; +#endif + + if (flags != 0) + return BAD_FUNC_ARG; + + if (bank == NULL) { +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + ret = wc_rng_bank_default_checkout(&bank); + if (ret != 0) + return ret; + bank_is_default = 1; +#else + return BAD_FUNC_ARG; +#endif + } + else { + if (! (bank->flags & WC_RNG_BANK_FLAG_INITED)) + return BAD_STATE_E; + } + +#if defined(WC_RNG_HAVE_RBGC) || defined(WC_RNG_HAVE_NEXT_SEED) + { + if (wc_RNG_GetStatus(&bank->root_rng) != WC_DRBG_NOT_INIT) { + #if !defined(WC_RNG_HAVE_LOCK) + ret = wc_RNG_DRBG_ScheduleReseed(&bank->root_rng); + #else + ret = wc_RNG_invalidate_entropy(&bank->root_rng); + #endif + } + } +#endif + + /* Best-effort-complete: an error on one instance must not leave the + * rest un-flagged. First error wins the return. */ + for (n = 0; n < bank->n_rngs; n++) { + int this_ret = wc_rng_bank_inst_invalidate_entropy(&bank->rngs[n]); + if ((this_ret != 0) && (ret == 0)) + ret = this_ret; + } + +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + if (bank_is_default) + (void)wc_rng_bank_default_checkin(&bank); +#endif + + return ret; +} + +#endif /* HAVE_HASHDRBG */ + +#ifdef WC_HAVE_RNG_BANKREF + +static int wc_InitRng_BankRef_local(struct wc_rng_bank *bank, WC_RNG **rng) { + int ret; + WC_ATOMIC_INT_ARG new_refcount; + + if (rng == NULL) + return BAD_FUNC_ARG; + + if (bank == NULL) { +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + ret = wc_rng_bank_default_checkout(&bank); + if (ret != 0) + return ret; + /* wc_rng_bank_default_checkout() increments bank->refcount, which we + * carry through below (no matching wc_rng_bank_default_checkin()). + */ +#else + return BAD_FUNC_ARG; +#endif + } + else { + if (! (bank->flags & WC_RNG_BANK_FLAG_INITED)) + return BAD_STATE_E; + wolfSSL_RefInc_IfAtLeast(&bank->refcount, 1, &new_refcount, &ret); + (void)new_refcount; + if (ret != 0) + return ret; + } + +#if !defined(WC_RNG_BANK_STATIC) && !defined(WC_NO_CONSTRUCTORS) + if (*rng == NULL) { + *rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), bank->heap, DYNAMIC_TYPE_RNG); + if (*rng == NULL) { + ret = MEMORY_E; + goto out; + } + } +#endif + + XMEMSET(*rng, 0, sizeof(**rng)); + (*rng)->heap = bank->heap; + (*rng)->status = WC_DRBG_BANKREF; + (*rng)->bankref = bank; + + ret = 0; + +#if !defined(WC_RNG_BANK_STATIC) && !defined(WC_NO_CONSTRUCTORS) +out: +#endif + + if (ret != 0) { + int refdec_err; + wolfSSL_RefDec2(&bank->refcount, &new_refcount, &refdec_err); + (void)new_refcount; + (void)refdec_err; + } + + return ret; +} + +WOLFSSL_API int wc_InitRng_BankRef(struct wc_rng_bank *bank, WC_RNG *rng) +{ + if (rng == NULL) + return BAD_FUNC_ARG; + return wc_InitRng_BankRef_local(bank, &rng); +} + +WOLFSSL_API int wc_BankRef_Release(WC_RNG *rng) +{ + int isZero = 0; + int ret = 0; + if (rng == NULL) return BAD_FUNC_ARG; if (rng->bankref == NULL) return BAD_FUNC_ARG; @@ -1073,40 +2652,703 @@ WOLFSSL_API int wc_BankRef_Release(WC_RNG *rng) #if !defined(WC_RNG_BANK_STATIC) && !defined(WC_NO_CONSTRUCTORS) WOLFSSL_API int wc_rng_new_bankref(struct wc_rng_bank *bank, WC_RNG **rng) { - int ret; + if (rng == NULL) + return BAD_FUNC_ARG; + *rng = NULL; + return wc_InitRng_BankRef_local(bank, rng); +} +#endif /* !WC_RNG_BANK_STATIC && !WC_NO_CONSTRUCTORS */ -#ifdef WC_RNG_BANK_DEFAULT_SUPPORT - if (bank == NULL) - bank = default_rng_bank; -#endif +#endif /* WC_HAVE_RNG_BANKREF */ - if ((bank == NULL) || - (rng == NULL)) +#ifdef WC_RNG_DEBUG_STATS + +WOLFSSL_API int wc_rng_bank_debug_stats_snap( + struct wc_rng_debug_stats_snapshot *s, + struct wc_rng_bank *bank) +{ + int i; + int ret = 0; + + if ((s == NULL) || (bank == NULL)) + return BAD_FUNC_ARG; + + XMEMSET(s, 0, sizeof(*s)); + + for (i = 0; i < bank->n_rngs; ++i) { + WC_RNG *rng = WC_RNG_BANK_INST_TO_RNG(&bank->rngs[i]); + ret = wc_rng_debug_stats_sum(s, rng); + if (ret != 0) + break; + } + + return ret; +} + +#endif /* WC_RNG_DEBUG_STATS */ + +#ifndef WC_RNG_HAVE_LOCK + +/* Backward compat: with a pre-v7 FIPS boundary (or WC_RNG_NO_LOCK), the + * latch lives in the bank instance rather than in the (frozen) WC_RNG. + * These are ports of the wc_RNG_lock_*() state machine, including + * WC_RNG_LOCK_ENTROPY_INVALIDATED quarantine/claim/report semantics. + * In every CAS below, the stored value derives only from the CAS-verified + * value and the caller's arguments -- never from a prior load. */ + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_get(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits) +{ + WC_RNG_lock_arg_t cur_lock; + + if (inst == NULL) + return BAD_FUNC_ARG; + + extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + + cur_lock = WOLFSSL_ATOMIC_LOAD(inst->lock); + + if (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) + return NEEDS_RECOVERY_E; + + if ((! (cur_lock & WC_RNG_LOCK_HELD)) && + (wolfSSL_Atomic_Uint_CompareExchange( + &inst->lock, &cur_lock, + cur_lock | WC_RNG_LOCK_HELD | extra_bits))) { + return 0; + } + + if (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) + return NEEDS_RECOVERY_E; + else if (cur_lock & WC_RNG_LOCK_HELD) + return BUSY_E; + else + return UNEXPECTED_STATE_E; +} + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_get_conditional( + struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t expected_extra_bits, + WC_RNG_lock_arg_t want_extra_bits) +{ + WC_RNG_lock_arg_t cur_lock, expected; + + if (inst == NULL) return BAD_FUNC_ARG; + + expected_extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED | WC_RNG_LOCK_ENTROPY_INVALIDATED; + want_extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + + cur_lock = WOLFSSL_ATOMIC_LOAD(inst->lock); + + if ((cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) && + (! (expected_extra_bits & WC_RNG_LOCK_ENTROPY_INVALIDATED))) + { + return NEEDS_RECOVERY_E; } - if ((! (bank->flags & WC_RNG_BANK_FLAG_INITED)) || - (wolfSSL_RefCur(bank->refcount) < 1)) + expected = (cur_lock & (((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) & + ~(WC_RNG_LOCK_HELD | WC_RNG_LOCK_ENTROPY_INVALIDATED))) | + expected_extra_bits; + + if ((! (cur_lock & WC_RNG_LOCK_HELD)) && + (wolfSSL_Atomic_Uint_CompareExchange( + &inst->lock, &expected, + expected | WC_RNG_LOCK_HELD | want_extra_bits))) { - return BAD_STATE_E; + return 0; } - *rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), bank->heap, DYNAMIC_TYPE_RNG); - if (*rng == NULL) { + if ((cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) != + (expected & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + { + return NEEDS_RECOVERY_E; + } + else if (expected & WC_RNG_LOCK_HELD) + return BUSY_E; + else + return UNEXPECTED_STATE_E; +} + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_put(struct wc_rng_bank_inst *inst) +{ + WC_RNG_lock_arg_t cur_lock, new_lock; + int cas_ret; + if (inst == NULL) + return BAD_FUNC_ARG; + cur_lock = WOLFSSL_ATOMIC_LOAD(inst->lock); + if (! (cur_lock & WC_RNG_LOCK_HELD)) + return OBJECT_NOT_LOCKED_E; + + WC_CAS_WITH_RETRY_BEGIN(&inst->lock, cur_lock, cas_ret) { + new_lock = cur_lock & + ((((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) & ~WC_RNG_LOCK_HELD)); + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &inst->lock, cur_lock, new_lock, + cas_ret); + } WC_CAS_WITH_RETRY_END; + + if (cas_ret != 0) { + /* Aborted release: the latch is still ours and new_lock was never + * installed; percolate so the caller can retry (see + * wc_RNG_lock_put()). */ + return cas_ret; + } + + if (new_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) + return NEEDS_RECOVERY_E; + else + return 0; +} + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_put_conditional(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits) +{ + int cas_ret; + WC_CAS_WITH_RETRY_EXTRA_DECLS; + WC_RNG_lock_arg_t cur_lock, expected, new_lock; + + if (inst == NULL) + return BAD_FUNC_ARG; + + cur_lock = WOLFSSL_ATOMIC_LOAD(inst->lock); + if (! (cur_lock & WC_RNG_LOCK_HELD)) + return OBJECT_NOT_LOCKED_E; + + /* Note this CAS loop doesn't use WC_CAS_WITH_RETRY_*() (non-conformant code + * pattern), so the WC_CAS_WITH_RETRY_* hook macros are invoked directly. */ + for (;;) { + new_lock = (cur_lock & + ((((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) & ~WC_RNG_LOCK_HELD))) | + (extra_bits & WC_RNG_LOCK_REQUIRED); + + expected = WC_RNG_LOCK_HELD | extra_bits | + (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED); + + if (wolfSSL_Atomic_Uint_CompareExchange( + &inst->lock, &expected, new_lock)) + { + if (new_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED) + return NEEDS_RECOVERY_E; + else + return 0; + } + if ((expected & ((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U)) != + (extra_bits & ((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U))) + { + break; + } + /* the CAS's failure feedback flows through expected; reseed the + * next reconstruction from it, else a concurrent invalidation + * loops forever. */ + cur_lock = expected; + + cas_ret = WC_CAS_WITH_RETRY_FOREVER_CLAUSE; + if (cas_ret != 0) + return cas_ret; + WC_CAS_WITH_RETRY_ITER_CLAUSE(&inst->lock, cur_lock, new_lock, cas_ret); + } + /* conditional release failed: the caller is still the holder. */ + return UNEXPECTED_STATE_E; +} + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_read(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t* state) +{ + if ((inst == NULL) || (state == NULL)) + return BAD_FUNC_ARG; + *state = WOLFSSL_ATOMIC_LOAD(inst->lock); + return 0; +} + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_set_extra(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits) +{ + WC_RNG_lock_arg_t cur_lock, new_lock; + int cas_ret; + if (inst == NULL) + return BAD_FUNC_ARG; + cur_lock = WOLFSSL_ATOMIC_LOAD(inst->lock); + + WC_CAS_WITH_RETRY_BEGIN(&inst->lock, cur_lock, cas_ret) { + new_lock = cur_lock & ((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U); + extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + new_lock |= extra_bits; + + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &inst->lock, cur_lock, new_lock, + cas_ret); + } WC_CAS_WITH_RETRY_END; + /* 0 unless a port's retry clause aborted; the lock word is then + * untouched, so percolation is the whole handling. */ + return cas_ret; +} + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_add_extra(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits) +{ + WC_RNG_lock_arg_t cur_lock; + int cas_ret; + if (inst == NULL) + return BAD_FUNC_ARG; + cur_lock = WOLFSSL_ATOMIC_LOAD(inst->lock); + + extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U) | + WC_RNG_LOCK_REQUIRED; + + WC_CAS_WITH_RETRY_BEGIN(&inst->lock, cur_lock, cas_ret) { + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &inst->lock, cur_lock, + cur_lock | extra_bits, cas_ret); + } WC_CAS_WITH_RETRY_END; + /* see wc_rng_bank_inst_lock_set_extra() re nonzero cas_ret. */ + return cas_ret; +} + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_clear_extra(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits) +{ + WC_RNG_lock_arg_t cur_lock; + int cas_ret; + if (inst == NULL) + return BAD_FUNC_ARG; + if (extra_bits & WC_RNG_LOCK_REQUIRED) { + /* WC_RNG_LOCK_REQUIRED is sticky by contract */ + return BAD_FUNC_ARG; + } + cur_lock = WOLFSSL_ATOMIC_LOAD(inst->lock); + + extra_bits &= ~((1U << WC_RNG_LOCK_EXTRA_SHIFT) - 1U); + + WC_CAS_WITH_RETRY_BEGIN(&inst->lock, cur_lock, cas_ret) { + WC_CAS_WITH_RETRY_LOOP_FOREVER(wolfSSL_Atomic_Uint_CompareExchange, + &inst->lock, cur_lock, + cur_lock & ~extra_bits, cas_ret); + } WC_CAS_WITH_RETRY_END; + /* see wc_rng_bank_inst_lock_set_extra() re nonzero cas_ret. */ + return cas_ret; +} + +#ifdef HAVE_HASHDRBG + +/* Portable invalidation-recovery helpers. With the in-boundary latch + * (WC_RNG_HAVE_LOCK), invalidation and clear-on-credited-reseed are + * module-enforced and these merely forward; with the bank-side latch, + * the bit is set and cleared out here, clearing only on credited + * reseeds, under the lease, mirroring the in-boundary semantics. */ + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_invalidate_entropy( + struct wc_rng_bank_inst *inst) +{ + WC_RNG_lock_arg_t cur_lock; + int cas_ret; + + if (inst == NULL) + return BAD_FUNC_ARG; + + /* Clearing _RECOVERING here is what makes it an epoch witness: an + * in-flight recovery discovers at exit that its seed predates this + * event, and leaves _INVALIDATED asserted. Mirrors + * wc_RNG_invalidate_entropy(). */ + WC_CAS_WITH_RETRY_BEGIN_INIT_CUR(&inst->lock, cur_lock, cas_ret) { + WC_CAS_WITH_RETRY_LOOP_FOREVER( + wolfSSL_Atomic_Uint_CompareExchange, &inst->lock, cur_lock, + (cur_lock & ~WC_RNG_LOCK_ENTROPY_RECOVERING) | + WC_RNG_LOCK_ENTROPY_INVALIDATED, + cas_ret); + } WC_CAS_WITH_RETRY_END; + + if (cas_ret != 0) { + /* Latch or condemn, mirroring wc_RNG_invalidate_entropy(): an + * aborted latch leaves only the lost-update-racy counter guarding + * duplicated state. */ + WC_RNG_BANK_INST_TO_RNG(inst)->status = WC_DRBG_FAILED; + return cas_ret; + } + + /* If no lock is held, the saturated reseedCtr is the only way to force + * invalidation semantics on a lock-free consumer; if a lock is held, + * the holder learns at unlock time. */ + if (! (cur_lock & WC_RNG_LOCK_HELD)) + (void)wc_RNG_DRBG_ScheduleReseed(WC_RNG_BANK_INST_TO_RNG(inst)); + + return 0; +} + +/* Acquire the recovery mutex for a reseed that may clear _INVALIDATED. The + * pre-lock boundary's Hash_DRBG_Reseed() can't see the inst-side latch, so the + * _RECOVERING protocol it implements in-boundary is reproduced here. + * + * Sets *recovering iff we asserted _RECOVERING and must therefore release it. + * Returns BUSY_E if another claimant is already recovering -- reachable + * whenever callers are not serialized, so never treat it as unreachable. */ +static int wc_rng_bank_inst_recovery_enter( + struct wc_rng_bank_inst *inst, int *recovering) +{ + WC_RNG_lock_arg_t cur_lock; + int cas_ret; + + if ((inst == NULL) || (recovering == NULL)) + return BAD_FUNC_ARG; + + *recovering = 0; + WC_CAS_WITH_RETRY_BEGIN_INIT_CUR(&inst->lock, cur_lock, cas_ret) { + if (! (cur_lock & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + return 0; + if (cur_lock & WC_RNG_LOCK_ENTROPY_RECOVERING) + return BUSY_E; + WC_CAS_WITH_RETRY_LOOP_FOREVER( + wolfSSL_Atomic_Uint_CompareExchange, &inst->lock, cur_lock, + cur_lock | WC_RNG_LOCK_ENTROPY_RECOVERING, cas_ret); + /* success arm: we now have the _RECOVERING mutex -- record that. */ + *recovering = 1; + } WC_CAS_WITH_RETRY_END; + + return cas_ret; +} + +/* Release the recovery mutex, and report. Must be called on every path out of + * a recovery_enter() that returned 0, or the instance is stuck at BUSY_E. + * + * On a successful reseed, clears _INVALIDATED alongside _RECOVERING -- unless + * _RECOVERING is already gone, which means wc_rng_bank_inst_invalidate_entropy() + * ran during the reseed: a new event our seed predates, so _INVALIDATED stands + * and we report NEEDS_RECOVERY_E. On a failed reseed, releases the mutex and + * leaves _INVALIDATED asserted, preserving the incoming error. */ +static int wc_rng_bank_inst_recovery_exit( + struct wc_rng_bank_inst *inst, int recovering, int ret) +{ + WC_RNG_lock_arg_t cur_lock; + int cas_ret; + + if (! recovering) + return ret; + + /* ret is the caller's incoming status and must survive, so the release + * uses its own result variable. */ + WC_CAS_WITH_RETRY_BEGIN_INIT_CUR(&inst->lock, cur_lock, cas_ret) { + if (! (cur_lock & WC_RNG_LOCK_ENTROPY_RECOVERING)) { + if (ret == 0) + ret = NEEDS_RECOVERY_E; + break; + } + WC_CAS_WITH_RETRY_LOOP_FOREVER( + wolfSSL_Atomic_Uint_CompareExchange, &inst->lock, cur_lock, + (ret == 0) + ? (cur_lock & ~(WC_RNG_LOCK_ENTROPY_INVALIDATED | + WC_RNG_LOCK_ENTROPY_RECOVERING)) + : (cur_lock & ~WC_RNG_LOCK_ENTROPY_RECOVERING), + cas_ret); + } WC_CAS_WITH_RETRY_END; + + return ret; +} + +WOLFSSL_TEST_VIS int wc_rng_bank_inst_reseed_now( + struct wc_rng_bank_inst *inst, const byte* nonce, word32 nonceSz) +{ + int ret, recovering; + if (inst == NULL) + return BAD_FUNC_ARG; + ret = wc_rng_bank_inst_recovery_enter(inst, &recovering); + if (ret != 0) + return ret; + ret = wc_RNG_DRBG_Reseed_Now(WC_RNG_BANK_INST_TO_RNG(inst), + nonce, nonceSz); + return wc_rng_bank_inst_recovery_exit(inst, recovering, ret); +} + +#ifdef WC_RNG_HAVE_RBGC +WOLFSSL_TEST_VIS int wc_rng_bank_inst_reseed_rbgc( + struct wc_rng_bank_inst *inst, WC_RNG* root, const byte* nonce, + word32 nonceSz) +{ + int ret, recovering; + if (inst == NULL) + return BAD_FUNC_ARG; + + ret = wc_rng_bank_inst_recovery_enter(inst, &recovering); + if (ret != 0) + return ret; + +#if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) + (void)nonce; + (void)nonceSz; + ret = wc_RNG_DRBG_ReseedRBGC(WC_RNG_BANK_INST_TO_RNG(inst), root); +#else + ret = wc_RNG_DRBG_ReseedRBGC(WC_RNG_BANK_INST_TO_RNG(inst), root, + nonce, nonceSz); +#endif + + return wc_rng_bank_inst_recovery_exit(inst, recovering, ret); +} +#endif /* WC_RNG_HAVE_RBGC */ + +#endif /* HAVE_HASHDRBG */ + +#endif + +#if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) + +/* ---- Legacy FIPS boundary compatibility -------------------------------- + * + * Pre-v7 FIPS boundaries do not export the DRBG accessor and reseed + * scheduling services that wolfcrypt/src/random.c supplies as of FIPS v7 + * (wc_RNG_GetStatus(), wc_RNG_DRBG_Present(), wc_RNG_DRBG_GetReseedCtr(), + * wc_RNG_DRBG_ScheduleReseed(), wc_RNG_DRBG_Stir(), and + * wc_RNG_DRBG_Reseed_Now()). Supply source-compatible static fallbacks + * here, implemented via the public DRBG struct definitions in the legacy + * random.h. These fallbacks are the historic direct-access mechanism, now + * confined to frozen pre-v7 boundaries, which cannot gain new services; + * wherever the in-boundary services exist, they are used instead. + */ + +/* WC_DRBG_OK predates some old FIPS editions, but is 1 in all of them -- force + * consistency. */ +#undef WC_DRBG_OK +#define WC_DRBG_OK 1 + +/* Helpers to access reseedCtr / null-check the active DRBG. The shape of + * struct WC_RNG and the DRBG_*_internal types varies by which DRBGs are + * compiled in; random.h gates the SHA-256 side on !NO_SHA256 and the SHA-512 + * side on WOLFSSL_DRBG_SHA512, so all three live combinations are handled + * separately here. */ +#if defined(WOLFSSL_DRBG_SHA512) && !defined(NO_SHA256) + /* Both DRBGs compiled in: dispatch on the runtime drbgType. */ + #define WC_RNG_BANK_RESEED_CTR(rng_ptr) \ + (((rng_ptr)->drbgType == WC_DRBG_SHA512) \ + ? ((struct DRBG_SHA512_internal *)(rng_ptr)->drbg512)->reseedCtr \ + : ((struct DRBG_internal *)(rng_ptr)->drbg)->reseedCtr) + #define WC_RNG_BANK_SET_RESEED_CTR(rng_ptr, val) \ + do { \ + if ((rng_ptr)->drbgType == WC_DRBG_SHA512) \ + ((struct DRBG_SHA512_internal *)(rng_ptr)->drbg512)->reseedCtr \ + = (val); \ + else \ + ((struct DRBG_internal *)(rng_ptr)->drbg)->reseedCtr = (val); \ + } while (0) + #define WC_RNG_BANK_DRBG_NULL(rng_ptr) \ + ((rng_ptr)->drbg == NULL && (rng_ptr)->drbg512 == NULL) +#elif defined(WOLFSSL_DRBG_SHA512) + /* SHA-512 DRBG only (NO_SHA256 defined); the SHA-256 struct and + * rng->drbg field do not exist in this build. */ + #define WC_RNG_BANK_RESEED_CTR(rng_ptr) \ + (((struct DRBG_SHA512_internal *)(rng_ptr)->drbg512)->reseedCtr) + #define WC_RNG_BANK_SET_RESEED_CTR(rng_ptr, val) \ + do { \ + ((struct DRBG_SHA512_internal *)(rng_ptr)->drbg512)->reseedCtr \ + = (val); \ + } while (0) + #define WC_RNG_BANK_DRBG_NULL(rng_ptr) \ + ((rng_ptr)->drbg512 == NULL) +#else + /* SHA-256 DRBG only (the historical default). */ + #define WC_RNG_BANK_RESEED_CTR(rng_ptr) \ + (((struct DRBG_internal *)(rng_ptr)->drbg)->reseedCtr) + #define WC_RNG_BANK_SET_RESEED_CTR(rng_ptr, val) \ + do { \ + ((struct DRBG_internal *)(rng_ptr)->drbg)->reseedCtr = (val); \ + } while (0) + #define WC_RNG_BANK_DRBG_NULL(rng_ptr) \ + ((rng_ptr)->drbg == NULL) +#endif + +/* WC_RNG_BANK_SET_RESEED_CTR drives reseedCtr up to WC_RESEED_INTERVAL to + * force a reseed. The SHA-256 DRBG's reseedCtr is 32-bit when + * WORD64_AVAILABLE is undefined (random.h), so a reseed interval above 2^32 + * would truncate to 0 and silently defeat the forced reseed (SP 800-90A Rev1 + * sec 9.3). Fail the build rather than mis-reseed. This is a compile-time + * assert rather than a preprocessor #if because WC_RESEED_INTERVAL may be + * defined with a (word64) cast (settings.h kernel path) that the preprocessor + * cannot evaluate; the outer #if uses only defined() so the 64-bit path skips + * it without expanding that cast. */ +#if defined(WC_RESEED_INTERVAL) && !defined(WORD64_AVAILABLE) + wc_static_assert((WC_RESEED_INTERVAL) <= 0xFFFFFFFFUL); +#endif + +#ifdef WC_RNG_HAVE_RBGC + +/* Note, no perso/persoSz in backported wc_InitRngNonceRBGC() */ +#define wc_InitRngRBGC(leaf, root, flags) \ + wc_InitRngNonceRBGC(leaf, root, NULL, 0, flags) + +WOLFSSL_TEST_VIS int wc_InitRngRBGC_New(WC_RNG** leaf, WC_RNG* root, word32 flags) { + int ret; + if ((leaf == NULL) || (root == NULL)) + return BAD_FUNC_ARG; + *leaf = (WC_RNG*)XMALLOC(sizeof(WC_RNG), root->heap, DYNAMIC_TYPE_RNG); + if (*leaf == NULL) return MEMORY_E; + ret = wc_InitRngNonceRBGC(*leaf, root, NULL, 0, flags); + if (ret != 0) { + XFREE(*leaf, root->heap, DYNAMIC_TYPE_RNG); + *leaf = NULL; } + return ret; +} - ret = wc_InitRng_BankRef(bank, *rng); +WOLFSSL_TEST_VIS int wc_InitRngNonceRBGC_New(WC_RNG** leaf, WC_RNG* root, + const byte* nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + word32 flags) +{ + int ret; + if ((leaf == NULL) || (root == NULL)) + return BAD_FUNC_ARG; + (void)perso; + (void)persoSz; + *leaf = (WC_RNG*)XMALLOC(sizeof(WC_RNG), root->heap, DYNAMIC_TYPE_RNG); + if (*leaf == NULL) + return MEMORY_E; + ret = wc_InitRngNonceRBGC(*leaf, root, nonce, nonceSz, flags); if (ret != 0) { - XFREE(*rng, bank->heap, DYNAMIC_TYPE_RNG); - *rng = NULL; + XFREE(*leaf, root->heap, DYNAMIC_TYPE_RNG); + *leaf = NULL; } + return ret; +} + +#endif /* WC_RNG_HAVE_RBGC */ +WOLFSSL_TEST_VIS int wc_RNG_GetStatus(const WC_RNG* rng) +{ + if (rng == NULL) + return BAD_FUNC_ARG; + return (int)rng->status; +} + +WOLFSSL_TEST_VIS int wc_RNG_DRBG_Present(const WC_RNG* rng) +{ + return (rng != NULL) && (! WC_RNG_BANK_DRBG_NULL(rng)); +} + +static int wc_RNG_DRBG_Reseed_Nonce( + WC_RNG* rng, const byte* seed, + word32 seedSz, const byte *nonce, + word32 nonceSz) +{ + (void)nonce; + (void)nonceSz; + return wc_RNG_DRBG_Reseed(rng, seed, seedSz); +} + +#if FIPS_VERSION3_NE(5,2,4) +static int wc_RNG_DRBG_GetReseedCtr( + const WC_RNG* rng, wc_drbg_reseed_ctr_t* reseedCtr) +{ + if ((rng == NULL) || (reseedCtr == NULL)) + return BAD_FUNC_ARG; + if (WC_RNG_BANK_DRBG_NULL(rng)) + *reseedCtr = 0; + else + *reseedCtr = (wc_drbg_reseed_ctr_t)WC_RNG_BANK_RESEED_CTR(rng); + return 0; +} +#endif + +WOLFSSL_TEST_VIS int wc_RNG_DRBG_ScheduleReseed(WC_RNG* rng) +{ + if (rng == NULL) + return BAD_FUNC_ARG; + if (! WC_RNG_BANK_DRBG_NULL(rng)) + WC_RNG_BANK_SET_RESEED_CTR(rng, WC_RESEED_INTERVAL); + return 0; +} + +#if FIPS_VERSION3_EQ(5,2,4) + +static int wc_RNG_DRBG_Stir_Nonce( + WC_RNG* rng, const byte* seed, word32 seedSz, const byte *nonce, word32 nonceSz) +{ + (void)nonce; + (void)nonceSz; + return wc_RNG_DRBG_Reseed_Uncredited(rng, seed, seedSz); +} + +WOLFSSL_TEST_VIS int wc_RNG_DRBG_Stir( + WC_RNG* rng, const byte* seed, word32 seedSz) +{ + return wc_RNG_DRBG_Reseed_Uncredited(rng, seed, seedSz); +} + +#else /* FIPS_VERSION3_NE(5,2,4) */ + +static int wc_RNG_DRBG_Stir_Nonce( + WC_RNG* rng, const byte* seed, word32 seedSz, const byte *nonce, word32 nonceSz) +{ + wc_drbg_reseed_ctr_t saved_ctr; + int ret; + + (void)nonce; + (void)nonceSz; + + if ((rng == NULL) || (seed == NULL)) + return BAD_FUNC_ARG; + if (WC_RNG_BANK_DRBG_NULL(rng)) { + /* defer to wc_RNG_DRBG_Reseed()'s RDRAND-config handling. */ + return wc_RNG_DRBG_Reseed(rng, seed, seedSz); + } + saved_ctr = (wc_drbg_reseed_ctr_t)WC_RNG_BANK_RESEED_CTR(rng); + ret = wc_RNG_DRBG_Reseed(rng, seed, seedSz); + /* wc_RNG_DRBG_Reseed() only resets the counter on success, so the + * unconditional restore is exact either way. */ + WC_RNG_BANK_SET_RESEED_CTR(rng, saved_ctr); return ret; } -#endif /* !WC_RNG_BANK_STATIC && !WC_NO_CONSTRUCTORS */ -#endif /* WC_HAVE_RNG_BANKREF */ +WOLFSSL_TEST_VIS int wc_RNG_DRBG_Stir( + WC_RNG* rng, const byte* seed, word32 seedSz) +{ + return wc_RNG_DRBG_Stir_Nonce(rng, seed, seedSz, NULL, 0); +} + +static int wc_RNG_DRBG_Reseed_Now( + WC_RNG* rng, const byte* nonce, word32 nonceSz) +{ + wc_drbg_reseed_ctr_t saved_ctr; + int ret; + byte scratch[4]; + + if (rng == NULL) + return BAD_FUNC_ARG; + if ((nonce == NULL) && (nonceSz > 0)) + return BAD_FUNC_ARG; + if (wc_RNG_GetStatus(rng) != WC_DRBG_OK) + return RNG_FAILURE_E; + if (WC_RNG_BANK_DRBG_NULL(rng)) { + /* No DRBG instantiated -- nothing to reseed (RDRAND et al.). */ + return 0; + } + + saved_ctr = (wc_drbg_reseed_ctr_t)WC_RNG_BANK_RESEED_CTR(rng); + WC_RNG_BANK_SET_RESEED_CTR(rng, WC_RESEED_INTERVAL); + + /* The legacy boundary has no direct reseed-from-source service; a + * minimal generate at the forced counter performs the module's own + * PollAndReSeed() in-boundary. This consumes 4 bytes of output, so on + * success the fresh reseed counter is 2 rather than 1. scratch holds + * only discarded output bytes; XMEMSET suffices for it here. */ + ret = wc_RNG_GenerateBlock(rng, scratch, (word32)sizeof(scratch)); + XMEMSET(scratch, 0, sizeof(scratch)); + + if ((ret == 0) && (nonce != NULL) && (nonceSz > 0)) { + /* On the legacy boundary, nonce incorporation is a separate + * (uncredited) transition following the reseed, rather than part of + * the same reseed derivation. */ + ret = wc_RNG_DRBG_Stir(rng, nonce, nonceSz); + } + + if ((ret != 0) && + ((wc_drbg_reseed_ctr_t)WC_RNG_BANK_RESEED_CTR(rng) >= + (wc_drbg_reseed_ctr_t)WC_RESEED_INTERVAL)) + { + /* The reseed did not occur -- restore the counter, leaving it + * unmodified as the contract requires. */ + WC_RNG_BANK_SET_RESEED_CTR(rng, saved_ctr); + } + + return ret; +} +#endif /* FIPS_VERSION3_NE(5,2,4) */ + +#endif /* HAVE_FIPS && FIPS_VERSION3_LT(7,0,0) */ #endif /* WC_RNG_BANK_SUPPORT */ diff --git a/wolfcrypt/src/rsa.c b/wolfcrypt/src/rsa.c index 69d292aa2a8..f498bc7914b 100644 --- a/wolfcrypt/src/rsa.c +++ b/wolfcrypt/src/rsa.c @@ -6177,6 +6177,20 @@ int wc_RsaSetRNG(RsaKey* key, WC_RNG* rng) return 0; } + +/* Companion to wc_RsaSetRNG(): detach the key's RNG association. + * Subsequent operations that require the key's RNG (blinding, pairwise + * consistency) then either fail with MISSING_RNG_E or fall back to a + * locally instantiated RNG, per operation, until a new RNG is set. */ +int wc_RsaClearRNG(RsaKey* key) +{ + if (key == NULL) + return BAD_FUNC_ARG; + + key->rng = NULL; + + return 0; +} #endif /* !WC_NO_RNG */ #ifdef WC_RSA_NONBLOCK diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 61201f426e7..c4f8826caa1 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -138,7 +138,7 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; esp_start_heap = esp_this_heap; \ } \ ESP_LOGI(ESPIDF_TAG, "%s #%d; Heap free: %d", \ - ((b) ? (b) : ""), /* breadcrumb string */ \ + ((b) ? (b) : ""), /* breadcrumb string */ \ ((i) ? (i) : 0), /* index */ \ esp_this_heap); @@ -166,7 +166,6 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #endif #endif /* WOLFSSL_ESPIDF */ - #ifdef USE_FLAT_TEST_H #ifdef HAVE_CONFIG_H #include "test_paths.h" @@ -941,6 +940,26 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void); #ifdef WOLFSSL_NOISE_SRC WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void); #endif +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && !defined(HAVE_SELFTEST) +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_svc_test(void); +#endif +#if defined(WC_RNG_BANK_SUPPORT) && defined(HAVE_HASHDRBG) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(5,2,4)) && \ + !defined(HAVE_INTEL_RDRAND) +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_entropy_invalidate_test(void); +#endif +#if defined(WC_RNG_HAVE_RBGC) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) || \ + defined(WC_RNG_BANK_SUPPORT)) +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_rbgc_test(void); +#endif +#ifdef WC_RNG_HAVE_NEXT_SEED +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_nextseedstest(void); +#endif +#ifdef WC_RNG_HAVE_POOL +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_pool_test(void); +#endif #endif /* WC_NO_RNG */ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pwdbased_test(void); #if defined(USE_CERT_BUFFERS_2048) && \ @@ -971,7 +990,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pbkdf2_test(void); #if !defined(NO_PWDBASED) && defined(HAVE_SCRYPT) WOLFSSL_TEST_SUBROUTINE wc_test_ret_t scrypt_test(void); #endif -#ifdef HAVE_ARGON2 +#if defined(HAVE_ARGON2) && !defined(WOLFSSL_NO_MALLOC) WOLFSSL_TEST_SUBROUTINE wc_test_ret_t argon2_test(void); #endif #ifdef HAVE_ECC @@ -2243,7 +2262,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t nist_sp80056c_twostep_cmac(void) /* optional macro to add sleep between tests */ #ifndef TEST_SLEEP -#define TEST_SLEEP() WC_DO_NOTHING +#define TEST_SLEEP() WC_RELAX_LONG_LOOP() #else #define TEST_PASS test_pass #include /* for var args */ @@ -2604,6 +2623,41 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ else TEST_PASS("NOISESRC test passed!\n"); #endif +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && !defined(HAVE_SELFTEST) + if ((ret = rng_drbg_svc_test()) != 0) + TEST_FAIL("RNGSVC test failed!\n", ret); + else + TEST_PASS("RNGSVC test passed!\n"); +#endif +#if defined(WC_RNG_BANK_SUPPORT) && defined(HAVE_HASHDRBG) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(5,2,4)) && \ + !defined(HAVE_INTEL_RDRAND) + if ((ret = rng_entropy_invalidate_test()) != 0) + TEST_FAIL("RNGINVAL test failed!\n", ret); + else + TEST_PASS("RNGINVAL test passed!\n"); +#endif +#if defined(WC_RNG_HAVE_RBGC) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) || \ + defined(WC_RNG_BANK_SUPPORT)) + if ((ret = rng_drbg_rbgc_test()) != 0) + TEST_FAIL("RNGRBGC test failed!\n", ret); + else + TEST_PASS("RNGRBGC test passed!\n"); +#endif +#ifdef WC_RNG_HAVE_NEXT_SEED + if ((ret = rng_drbg_nextseedstest()) != 0) + TEST_FAIL("RNGNXTS test failed!\n", ret); + else + TEST_PASS("RNGNXTS test passed!\n"); +#endif +#ifdef WC_RNG_HAVE_POOL + if ((ret = rng_pool_test()) != 0) + TEST_FAIL("RNGPOOL test failed!\n", ret); + else + TEST_PASS("RNGPOOL test passed!\n"); +#endif #endif /* WC_NO_RNG */ #ifdef WOLFSSL_SHAKE128 @@ -3124,7 +3178,7 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ TEST_PASS("PWDBASED test passed!\n"); #endif -#ifdef HAVE_ARGON2 +#if defined(HAVE_ARGON2) && !defined(WOLFSSL_NO_MALLOC) if ( (ret = argon2_test()) != 0) TEST_FAIL("ARGON2 test failed!\n", ret); else @@ -23835,7 +23889,8 @@ typedef struct keywrapVector { word32 verifyLen; } keywrapVector; -#if !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) +#if !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) && \ + !defined(WOLFSSL_NO_MALLOC) /* struct Aes cannot be a local here: with --enable-aesgcm=table its GCM tables * alone are 4096 bytes, past the frame limit CI enforces. It also asks for 16 * byte alignment through its ALIGN16 members, which XMALLOC does not guarantee @@ -24104,6 +24159,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aeskeywrap_test(void) } #if !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + +#ifndef WOLFSSL_NO_MALLOC /* Drive wc_AesKeyWrap_ex/wc_AesKeyUnWrap_ex directly with a caller Aes; the * KAT loop above already covers every vector via the key-based wrappers. */ { @@ -24113,6 +24170,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aeskeywrap_test(void) if (exRet != 0) return exRet; } +#endif /* WOLFSSL_NO_MALLOC */ /* In-place round-trip (in == out): wrap then unwrap a single buffer. * Exercises the XMEMMOVE staging in wc_AesKeyWrap_ex / AesKeyUnWrapRaw. */ @@ -26808,6 +26866,68 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t XChaCha20Poly1305_test(void) #endif /* defined(HAVE_XCHACHA) && defined(HAVE_POLY1305) */ #ifndef WC_NO_RNG +#ifdef WC_RNG_DEBUG_STATS +/* Snapshot-and-delta assertion kit for the WC_RNG_DEBUG_STATS counters. + * + * Usage: RNG_STATS_DECLS; as the LAST declaration in the block (it + * declares two snapshot cells, so two instances can be tracked across one + * API call, e.g. a chain reseed's source and target). RNG_STATS_SNAP[2]() + * snapshots an instance; RNG_STATS_EXPECT[2]() asserts an exact + * counter delta since the matching snapshot, RNG_STATS_EXPECT_GE[2]() a + * minimum delta, and RNG_STATS_EXPECT_SAME_DELTA() that two counters of + * one instance moved together. fail_action is executed on mismatch + * (e.g. ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)), so the + * encoded line number pinpoints the failing assertion; the observed + * delta (for _SAME_DELTA, the difference of the two deltas) is available + * to fail_action as rng_stats_d_ for encoding via WC_TEST_RET_ENC_I(). Without + * WC_RNG_DEBUG_STATS everything expands to nothing. + */ + +#define RNG_STATS_DECLS \ + struct wc_rng_debug_stats_snapshot rng_stats_s WC_MAYBE_UNUSED, \ + rng_stats_s2 WC_MAYBE_UNUSED +#define RNG_STATS_SNAP(rng) wc_rng_debug_stats_snap(&rng_stats_s, (rng)) +#define RNG_STATS_SNAP2(rng) wc_rng_debug_stats_snap(&rng_stats_s2, (rng)) +#define RNG_STATS_D_(snap, rng, field) ((rng)->field - (snap).field) +#define RNG_STATS_EXPECT_(snap, rng, field, delta, fail_action) \ + do { \ + wc_rng_debug_counter_t rng_stats_d_ = \ + RNG_STATS_D_(snap, rng, field); \ + if (rng_stats_d_ != (wc_rng_debug_counter_t)(delta)) { \ + fail_action; \ + } \ + } while (0) +#define RNG_STATS_EXPECT(rng, field, delta, fail_action) \ + RNG_STATS_EXPECT_(rng_stats_s, rng, field, delta, fail_action) +#define RNG_STATS_EXPECT2(rng, field, delta, fail_action) \ + RNG_STATS_EXPECT_(rng_stats_s2, rng, field, delta, fail_action) +#define RNG_STATS_EXPECT_GE(rng, field, delta, fail_action) \ + do { \ + wc_rng_debug_counter_t rng_stats_d_ = \ + RNG_STATS_D_(rng_stats_s, rng, field); \ + if (rng_stats_d_ < (wc_rng_debug_counter_t)(delta)) { \ + fail_action; \ + } \ + } while (0) +#define RNG_STATS_EXPECT_SAME_DELTA(rng, f1, f2, fail_action) \ + do { \ + wc_rng_debug_counter_t rng_stats_d_ = \ + RNG_STATS_D_(rng_stats_s, rng, f1) - \ + RNG_STATS_D_(rng_stats_s, rng, f2); \ + if (rng_stats_d_ != 0) { \ + fail_action; \ + } \ + } while (0) +#else /* !WC_RNG_DEBUG_STATS */ +#define RNG_STATS_DECLS +#define RNG_STATS_SNAP(rng) WC_DO_NOTHING +#define RNG_STATS_SNAP2(rng) WC_DO_NOTHING +#define RNG_STATS_EXPECT(rng, field, delta, fail_action) WC_DO_NOTHING +#define RNG_STATS_EXPECT2(rng, field, delta, fail_action) WC_DO_NOTHING +#define RNG_STATS_EXPECT_GE(rng, field, delta, fail_action) WC_DO_NOTHING +#define RNG_STATS_EXPECT_SAME_DELTA(rng, f1, f2, fail_action) WC_DO_NOTHING +#endif /* WC_RNG_DEBUG_STATS */ + static wc_test_ret_t _rng_test(WC_RNG* rng) { byte block[32]; @@ -26873,9 +26993,25 @@ static wc_test_ret_t _rng_test(WC_RNG* rng) } #endif + { + RNG_STATS_DECLS; + RNG_STATS_SNAP(rng); ret = wc_RNG_GenerateBlock(rng, block, sizeof(block)); if (ret != 0) return WC_TEST_RET_ENC_EC(ret); + /* the forced interval reseed is credited, and the request is + * fully served */ + RNG_STATS_EXPECT(rng, _stats_reseeds, 1, + return WC_TEST_RET_ENC_I((int)rng_stats_d_)); + RNG_STATS_EXPECT(rng, _stats_stirs, 0, + return WC_TEST_RET_ENC_I((int)rng_stats_d_)); + RNG_STATS_EXPECT(rng, _stats_total_requests, 1, + return WC_TEST_RET_ENC_I((int)rng_stats_d_)); + RNG_STATS_EXPECT(rng, _stats_total_bytes_requested, sizeof(block), + return WC_TEST_RET_ENC_I((int)rng_stats_d_)); + RNG_STATS_EXPECT(rng, _stats_total_bytes_produced, sizeof(block), + return WC_TEST_RET_ENC_I((int)rng_stats_d_)); + } #if defined(WOLFSSL_DRBG_SHA512) && !defined(HAVE_SELFTEST) && \ (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) @@ -27876,6 +28012,7 @@ static int rng_bank_affinity_unlock(void *arg) { WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) { + struct wc_rng_bank_inst *held_inst = NULL; int ret; WC_DECLARE_VAR(bank, struct wc_rng_bank, 1, HEAP_HINT); struct wc_rng_bank_inst *rng_inst = NULL; @@ -27885,13 +28022,28 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) #ifndef WC_RNG_BANK_STATIC struct wc_rng_bank *bank2 = NULL; struct wc_rng_bank_inst *rng_inst2 = NULL; -#ifdef WC_HAVE_RNG_BANKREF +#if defined(WC_HAVE_RNG_BANKREF) && !defined(WC_NO_CONSTRUCTORS) WC_RNG *rng2 = NULL; #endif #endif /* !WC_RNG_BANK_STATIC */ static const char bank_arg[] = "hi"; byte outbuf1[16], outbuf2[16]; +#ifdef HAVE_HASHDRBG int i; +#endif +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) + int svc_present = 0; +#endif +#ifdef WC_RNG_HAVE_RBGC +#ifndef WC_NO_CONSTRUCTORS + WC_RNG *spawned_rng = NULL; +#endif + int leaf_rng_inited = 0; + WC_DECLARE_VAR(leaf_rng, WC_RNG, 1, HEAP_HINT); +#endif +#ifdef WC_RNG_BANK_HAVE_DAEMON_SUPPORT + void *daemon_out = NULL; +#endif WC_CALLOC_VAR_EX(bank, struct wc_rng_bank, 1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER, @@ -27903,6 +28055,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out)); XMEMSET(rng, 0, sizeof(*rng)); #endif +#ifdef WC_RNG_HAVE_RBGC + WC_ALLOC_VAR_EX(leaf_rng, WC_RNG, 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER, + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out)); +#endif ret = wc_rng_bank_init(NULL, WC_RNG_BANK_STATIC_SIZE, WC_RNG_BANK_FLAG_CAN_WAIT, 10, HEAP_HINT, INVALID_DEVID); if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) @@ -27918,6 +28075,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) #ifndef DEBUG_VECTOR_REGISTER_ACCESS_ALWAYS_ON WC_RNG_BANK_FLAG_NO_VECTOR_OPS | #endif + WC_RNG_BANK_FLAG_QUIET | WC_RNG_BANK_FLAG_CAN_WAIT, 10, HEAP_HINT, INVALID_DEVID); if (ret != 0) @@ -27983,6 +28141,30 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); } + { + struct wc_rng_bank_inst *neg_inst = NULL; + + ret = wc_rng_bank_inst_checkin(NULL); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + ret = wc_rng_bank_inst_checkin(&neg_inst); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* A misaligned pointer within the instance array is memory-safe to + * probe through wc_rng_bank_checkin() -- the caller-supplied bank is + * validated before any instance dereference -- and exercises + * rng_inst_matches_bank()'s mid-instance alignment rejection. + * (The same probe through wc_rng_bank_inst_checkin() would be + * undefined behavior: that API must read (*rng_inst)->bank before + * any validation can run.) */ + neg_inst = (struct wc_rng_bank_inst *)((wc_ptr_t)bank->rngs + 1); + ret = wc_rng_bank_checkin(bank, &neg_inst); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + } + ret = wc_rng_bank_checkin(bank, &rng_inst); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); @@ -27996,10 +28178,41 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); if (rng_inst != bank->rngs + 3) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - ret = wc_rng_bank_checkin(bank, &rng_inst); + ret = wc_rng_bank_inst_checkin(&rng_inst); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + /* A duplicate (stale-copy) check-in must be rejected with BAD_STATE_E or + * OBJECT_NOT_LOCKED_E -- the instance's HELD flag is already clear -- + * without mutating the bank, through both entry points. Hold a second + * instance across the stale check-ins: with the bank refcount at 1, + * rng_inst_matches_bank() rejects with BAD_STATE_E/OBJECT_NOT_LOCKED_E + * before the HELD-flag guard in wc_rng_bank_checkin() -- the guard under + * test here -- is reached. */ + ret = wc_rng_bank_checkout(bank, &rng_inst, 3, 10, WC_RNG_BANK_FLAG_NONE); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + { + struct wc_rng_bank_inst *stale_inst = rng_inst; + /* held_inst hoisted to function scope (teardown checks in). */ + ret = wc_rng_bank_checkout(bank, &held_inst, 2, 10, + WC_RNG_BANK_FLAG_NONE); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_checkin(bank, &stale_inst); + if (ret != WC_NO_ERR_TRACE(OBJECT_NOT_LOCKED_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_inst_checkin(&stale_inst); + if (ret != WC_NO_ERR_TRACE(OBJECT_NOT_LOCKED_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_inst_checkin(&held_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + } + rng_bank_affinity_get_id_id = 3; ret = wc_rng_bank_checkout(bank, &rng_inst, -1, 10, WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST | WC_RNG_BANK_FLAG_AFFINITY_LOCK); if (ret != 0) @@ -28011,7 +28224,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - ret = wc_rng_bank_checkin(bank, &rng_inst); + ret = wc_rng_bank_inst_checkin(&rng_inst); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); if (rng_inst != NULL) @@ -28022,8 +28235,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) #ifdef WC_HAVE_RNG_BANKREF ret = wc_InitRng_BankRef(NULL, rng); +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + if (ret != WC_NO_ERR_TRACE(NO_DEFAULT_FOUND_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#else if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#endif ret = wc_InitRng_BankRef(bank, NULL); if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) @@ -28042,33 +28260,34 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) ERROR_OUT(WC_TEST_RET_ENC_NC, out); #endif - ret = wc_rng_bank_reseed(NULL, 10, WC_RNG_BANK_FLAG_NONE); +#if defined(HAVE_HASHDRBG) && !defined(HAVE_INTEL_RDRAND) + ret = wc_rng_bank_reseed(NULL, NULL, 0, 10, WC_RNG_BANK_FLAG_NONE); +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + if (ret != WC_NO_ERR_TRACE(NO_DEFAULT_FOUND_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#else if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#endif - ret = wc_rng_bank_reseed(bank, 10, WC_RNG_BANK_FLAG_NONE); + ret = wc_rng_bank_reseed(bank, NULL, 0, 10, WC_RNG_BANK_FLAG_NONE); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - for (i = 0; i < bank->n_rngs; ++i) { - #if defined(WOLFSSL_DRBG_SHA512) && !defined(HAVE_SELFTEST) && \ + #if !defined(HAVE_SELFTEST) && \ (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) - word64 bankReseedCtr; - if (bank->rngs[i].rng.drbgType == WC_DRBG_SHA512) - bankReseedCtr = ((struct DRBG_SHA512_internal *) - bank->rngs[i].rng.drbg512)->reseedCtr; - else - bankReseedCtr = ((struct DRBG_internal *) - bank->rngs[i].rng.drbg)->reseedCtr; + for (i = 0; i < bank->n_rngs; ++i) { + wc_drbg_reseed_ctr_t bankReseedCtr; + ret = wc_RNG_DRBG_GetReseedCtr(&bank->rngs[i].rng, &bankReseedCtr); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); if (bankReseedCtr != WC_RESEED_INTERVAL) + ERROR_OUT(WC_TEST_RET_ENC_I(bankReseedCtr), out); + } #else - if (((struct DRBG_internal *)bank->rngs[i].rng.drbg) - ->reseedCtr != WC_RESEED_INTERVAL) + (void)i; #endif - { - ERROR_OUT(WC_TEST_RET_ENC_I(i), out); - } - } +#endif /* HAVE_HASHDRBG && !HAVE_INTEL_RDRAND */ rng_bank_affinity_get_id_id = 0; /* WC_RNG_BANK_FLAG_CAN_WAIT needed to avoiding warning message that the @@ -28091,14 +28310,15 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) ret = wc_RNG_GenerateBlock(WC_RNG_BANK_INST_TO_RNG(rng_inst), outbuf2, sizeof(outbuf2)); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - ret = wc_rng_bank_checkin(bank, &rng_inst); + ret = wc_rng_bank_inst_checkin(&rng_inst); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); if (XMEMCMP(outbuf1, outbuf2, sizeof(outbuf1)) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - ret = wc_rng_bank_seed(bank, (byte *)bank_arg, (word32)sizeof(bank_arg), 10, WC_RNG_BANK_FLAG_CAN_WAIT); +#if defined(HAVE_HASHDRBG) && !defined(HAVE_INTEL_RDRAND) + ret = wc_rng_bank_seed(bank, (byte *)bank_arg, (word32)sizeof(bank_arg), NULL, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); @@ -28117,15 +28337,27 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) /* can't wc_rng_bank_seed() while holding an inst (deadlock/timeout) -- * check in then check back out. */ - ret = wc_rng_bank_checkin(bank, &rng_inst); + ret = wc_rng_bank_inst_checkin(&rng_inst); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - ret = wc_rng_bank_seed(NULL, (byte *)bank_arg, (word32)sizeof(bank_arg), 10, WC_RNG_BANK_FLAG_CAN_WAIT); + ret = wc_rng_bank_seed(NULL, (byte *)bank_arg, (word32)sizeof(bank_arg), NULL, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + if (ret != WC_NO_ERR_TRACE(NO_DEFAULT_FOUND_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#else if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#endif + + ret = wc_rng_bank_seed(bank, (byte *)bank_arg, (word32)sizeof(bank_arg), NULL, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - ret = wc_rng_bank_seed(bank, (byte *)bank_arg, (word32)sizeof(bank_arg), 10, WC_RNG_BANK_FLAG_CAN_WAIT); + /* seedSz == 0 short-circuits: no-op success for an explicit inited + * bank, and for the default form while a default is set. (The + * seed pointer is never read on these paths.) */ + ret = wc_rng_bank_seed(bank, NULL, 0, NULL, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); @@ -28144,8 +28376,10 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) if (XMEMCMP(outbuf1, outbuf2, sizeof(outbuf1)) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* NULL bank to wc_rng_bank_inst_reinit() tells it to use the bank with + * which rng_inst is associated. */ ret = wc_rng_bank_inst_reinit(NULL, rng_inst, 10, WC_RNG_BANK_FLAG_CAN_WAIT); - if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); /* bogus pointer test */ @@ -28164,21 +28398,25 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) if (XMEMCMP(outbuf1, outbuf2, sizeof(outbuf1)) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - ret = wc_rng_bank_checkin(NULL, &rng_inst); - if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) - ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - ret = wc_rng_bank_checkin(bank, &rng_inst); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#endif /* HAVE_HASHDRBG && !HAVE_INTEL_RDRAND */ + ret = wc_rng_bank_checkout(NULL, &rng_inst, -1, 10, WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST | WC_RNG_BANK_FLAG_AFFINITY_LOCK); +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + if (ret != WC_NO_ERR_TRACE(NO_DEFAULT_FOUND_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#else if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#endif #ifdef WC_HAVE_RNG_BANKREF - if (wolfSSL_RefCur(bank->refcount) != 2) - ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wolfSSL_RefCur(bank->refcount); + if (ret != 2) + ERROR_OUT(WC_TEST_RET_ENC_I(ret), out); ret = wc_rng_bank_fini(bank); if (ret != WC_NO_ERR_TRACE(BUSY_E)) @@ -28186,8 +28424,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) wc_FreeRng(rng); - if (wolfSSL_RefCur(bank->refcount) != 1) - ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wolfSSL_RefCur(bank->refcount); + if (ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(ret), out); #endif #ifdef WC_RNG_BANK_DEFAULT_SUPPORT @@ -28225,7 +28464,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); wc_FreeRng(rng); -#ifndef WC_RNG_BANK_STATIC +#if !defined(WC_RNG_BANK_STATIC) && !defined(WC_NO_CONSTRUCTORS) ret = wc_rng_new_bankref(NULL, &rng2); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); @@ -28240,18 +28479,35 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - ret = wc_rng_bank_seed(NULL, (byte *)bank_arg, (word32)sizeof(bank_arg), 10, WC_RNG_BANK_FLAG_CAN_WAIT); +#if defined(HAVE_HASHDRBG) && !defined(HAVE_INTEL_RDRAND) + + ret = wc_rng_bank_seed(NULL, (byte *)bank_arg, (word32)sizeof(bank_arg), NULL, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + ret = wc_rng_bank_reseed(NULL, NULL, 0, 10, WC_RNG_BANK_FLAG_NONE); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - ret = wc_rng_bank_reseed(NULL, 10, WC_RNG_BANK_FLAG_NONE); + ret = wc_rng_bank_seed(NULL, NULL, 0, NULL, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#endif /* HAVE_HASHDRBG && !HAVE_INTEL_RDRAND */ + ret = wc_rng_bank_default_clear(bank); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#if defined(HAVE_HASHDRBG) && !defined(HAVE_INTEL_RDRAND) + + /* seedSz == 0 probe with no default bank set: NO_DEFAULT_FOUND_E. */ + ret = wc_rng_bank_seed(NULL, NULL, 0, NULL, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); + if (ret != WC_NO_ERR_TRACE(NO_DEFAULT_FOUND_E)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + +#endif /* HAVE_HASHDRBG && !HAVE_INTEL_RDRAND */ + #endif /* WC_RNG_BANK_DEFAULT_SUPPORT */ #ifdef WC_RNG_BANK_STATIC @@ -28260,7 +28516,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) #else /* !WC_RNG_BANK_STATIC */ - ret = wc_rng_bank_new(&bank2, WC_RNG_BANK_STATIC_SIZE + 1, WC_RNG_BANK_FLAG_NONE, 10, HEAP_HINT, INVALID_DEVID); + ret = wc_rng_bank_new(&bank2, WC_RNG_BANK_STATIC_SIZE + 1, WC_RNG_BANK_FLAG_QUIET, 10, HEAP_HINT, INVALID_DEVID); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); @@ -28291,7 +28547,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - ret = wc_rng_bank_seed(bank2, (byte *)bank_arg, (word32)sizeof(bank_arg), 10, WC_RNG_BANK_FLAG_CAN_WAIT); +#if defined(HAVE_HASHDRBG) && !defined(HAVE_INTEL_RDRAND) + + ret = wc_rng_bank_seed(bank2, (byte *)bank_arg, (word32)sizeof(bank_arg), NULL, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); @@ -28310,7 +28568,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); - ret = wc_rng_bank_seed(bank2, (byte *)bank_arg, (word32)sizeof(bank_arg), 10, WC_RNG_BANK_FLAG_CAN_WAIT); + ret = wc_rng_bank_seed(bank2, (byte *)bank_arg, (word32)sizeof(bank_arg), NULL, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); @@ -28344,10 +28602,17 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) if (ret != 0) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); -#ifdef WC_HAVE_RNG_BANKREF +#endif /* HAVE_HASHDRBG && !HAVE_INTEL_RDRAND */ + +#if defined(WC_HAVE_RNG_BANKREF) && !defined(WC_NO_CONSTRUCTORS) ret = wc_rng_new_bankref(NULL, &rng2); +#ifdef WC_RNG_BANK_DEFAULT_SUPPORT + if (ret != WC_NO_ERR_TRACE(NO_DEFAULT_FOUND_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#else if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#endif ret = wc_rng_new_bankref(bank2, NULL); if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) @@ -28367,8 +28632,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) if (XMEMCMP(outbuf1, outbuf2, sizeof(outbuf1)) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - if (wolfSSL_RefCur(bank2->refcount) != 2) - ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wolfSSL_RefCur(bank2->refcount); + if (ret != 2) + ERROR_OUT(WC_TEST_RET_ENC_I(ret), out); ret = wc_rng_bank_free(&bank2); if (ret != WC_NO_ERR_TRACE(BUSY_E)) @@ -28377,8 +28643,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) wc_rng_free(rng2); rng2 = NULL; - if (wolfSSL_RefCur(bank2->refcount) != 1) - ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wolfSSL_RefCur(bank2->refcount); + if (ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(ret), out); #endif /* WC_HAVE_RNG_BANKREF */ @@ -28390,8 +28657,479 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) #endif /* !WC_RNG_BANK_STATIC */ +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) + /* ---- rng_bank service extensions: recovery-patrol and in-service- + * guarantee checkout flags, wc_rng_bank_inst_checkin(), the daemon + * banking entry point with consume-at-checkout, and the RBGC spawn + * APIs. DRBG-internal probes are runtime-gated on + * wc_RNG_DRBG_Present(). ---- */ + + (void)svc_present; + + /* plain check-out roundtrip via the one-arg check-in */ + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 0, 0); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + svc_present = wc_RNG_DRBG_Present(WC_RNG_BANK_INST_TO_RNG(rng_inst)); + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (rng_inst != NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + +#endif /* !HAVE_FIPS || FIPS_VERSION3_GE(7,0,0) */ + + /* the in-service guarantee on a healthy instance is transparent */ + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 0, + WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* contradictory flag combinations */ + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 0, WC_RNG_BANK_FLAG_FOR_RECOVERY | WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 0, WC_RNG_BANK_FLAG_FOR_RECOVERY | WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* recovery patrol: argument contracts; a healthy instance is a + * success no-op */ + ret = wc_rng_bank_recover_inst(NULL, 0, 0, 0); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_recover_inst(bank, 0, 0, WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (wc_rng_bank_recover_inst(bank, WC_RNG_BANK_STATIC_SIZE, 0, 0) == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wc_rng_bank_recover_inst(bank, 0, 0, 0); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + +#ifdef WC_RNG_HAVE_NEXT_SEED + /* daemon banking entry point: argument contracts */ + ret = wc_rng_bank_next_seed_generate(NULL, 0, 32); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_next_seed_generate(bank, -1, 32); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_next_seed_generate(bank, WC_RNG_BANK_STATIC_SIZE, 32); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_next_seed_generate(bank, 0, 0); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + if (svc_present) { + wc_drbg_reseed_ctr_t ns_ctr; + /* bank instance 0's next seed to publication, then consume it at + * checkout: an atomic-context-safe credited reseed (counter + * lands at 1) */ + for (i = 0; i < 64; i++) { + ret = wc_rng_bank_next_seed_generate(bank, 0, (word32)(WC_DRBG_NEXT_SEED_LEN / 7)); + if (ret == WC_NO_ERR_TRACE(ALREADY_E)) + break; + if ((ret != 0) && (ret != WC_NO_ERR_TRACE(NOT_READY_E)) && + (ret != WC_NO_ERR_TRACE(ENTROPY_RT_E)) && + (ret != WC_NO_ERR_TRACE(ENTROPY_APT_E))) + { + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + } + } + if (i >= 64) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 0, + WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(rng_inst), &ns_ctr); + if ((ret != 0) || (ns_ctr != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wc_RNG_GenerateBlock(WC_RNG_BANK_INST_TO_RNG(rng_inst), + outbuf1, sizeof(outbuf1)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* consume with no bank ready is transparent: counter undisturbed */ + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 0, + WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(rng_inst), &ns_ctr); + if ((ret != 0) || (ns_ctr != 2)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + } +#endif /* WC_RNG_HAVE_NEXT_SEED */ + +#ifdef WC_RNG_HAVE_RBGC + /* RBGC spawn: argument and flag contracts */ + ret = wc_rng_bank_spawn(bank, NULL, NULL, 0, NULL, 0, 0, 0, 0); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#ifndef WC_NO_CONSTRUCTORS + ret = wc_rng_bank_spawn_new(bank, NULL, NULL, 0, NULL, 0, 0, 0, 0); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#endif + ret = wc_rng_bank_spawn(bank, leaf_rng, NULL, 0, NULL, 0, 0, 0, WC_RNG_BANK_FLAG_STIR); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_spawn(bank, leaf_rng, NULL, 0, NULL, 0, 0, 0, WC_RNG_BANK_FLAG_FOR_RECOVERY); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* nonce-bearing stack spawn: the leaf is a tagged chain leaf, + * generates, and is torn down independently of the bank */ + ret = wc_rng_bank_spawn(bank, leaf_rng, outbuf2, sizeof(outbuf2), + NULL, 0, 0, 0, 0); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + leaf_rng_inited = 1; +#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && !defined(HAVE_INTEL_RDRAND) + ret = wc_RNG_DRBG_GetRBGCStratum(leaf_rng); + /* the _NEXT_SEED section above reseeds the bank root -- otherwise it's a + * user seed. */ +#ifdef WC_RNG_HAVE_NEXT_SEED + if (ret != 1) +#else + if (ret != WC_RNG_RBGC_USER_SEED_STRATUM + 1) +#endif + { + ERROR_OUT(WC_TEST_RET_ENC_I(ret), out); + } +#endif + ret = wc_RNG_GenerateBlock(leaf_rng, outbuf1, sizeof(outbuf1)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_FreeRng(leaf_rng); + leaf_rng_inited = 0; + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + +#ifndef WC_NO_CONSTRUCTORS + /* heap spawn from the second instance */ + ret = wc_rng_bank_spawn_new(bank, &spawned_rng, NULL, 0, NULL, 0, 1, 0, 0); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (spawned_rng == NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && !defined(HAVE_INTEL_RDRAND) + ret = wc_RNG_DRBG_GetRBGCStratum(spawned_rng); + if (ret != WC_RNG_RBGC_USER_SEED_STRATUM + 1) + ERROR_OUT(WC_TEST_RET_ENC_I(ret), out); +#endif + ret = wc_RNG_GenerateBlock(spawned_rng, outbuf1, sizeof(outbuf1)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + wc_rng_free(spawned_rng); + spawned_rng = NULL; +#endif /* !WC_NO_CONSTRUCTORS */ + + /* PR spawn: a fresh credited primary reseed of the parent instance + * immediately before the child's seed draw (the SP 800-90C Sec. 4.1.1 + * pattern). The child is stratum 1; the parent instance's counter + * shows reseed-then-one-draw. */ + ret = wc_rng_bank_spawn(bank, leaf_rng, NULL, 0, NULL, 0, 0, 10, + WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE | + WC_RNG_BANK_FLAG_CAN_WAIT); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + leaf_rng_inited = 1; +#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && !defined(HAVE_INTEL_RDRAND) + ret = wc_RNG_DRBG_GetRBGCStratum(leaf_rng); + if (ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(ret), out); +#endif + ret = wc_RNG_GenerateBlock(leaf_rng, outbuf1, sizeof(outbuf1)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_FreeRng(leaf_rng); + leaf_rng_inited = 0; + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) + if (svc_present) { + wc_drbg_reseed_ctr_t ns_ctr; + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, + WC_RNG_BANK_FLAG_NONE); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(rng_inst), &ns_ctr); + if ((ret != 0) || (ns_ctr != 2)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + } +#endif /* !HAVE_FIPS || FIPS_VERSION3_GE(7,0,0) */ +#endif /* WC_RNG_HAVE_RBGC */ + + /* WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE checkout contracts. + * Per-call PR demands CAN_WAIT (the fresh gather may block) and + * contradicts uncredited and recovery seeding. */ + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE | WC_RNG_BANK_FLAG_CAN_WAIT | WC_RNG_BANK_FLAG_STIR); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE | WC_RNG_BANK_FLAG_CAN_WAIT | WC_RNG_BANK_FLAG_FOR_RECOVERY); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + +#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && defined(HAVE_HASHDRBG) + if (svc_present) { + wc_drbg_reseed_ctr_t ns_ctr; + /* effective PR: the leased instance is freshly credited-reseeded + * (counter exactly 1) before any caller draw */ + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, + WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE | + WC_RNG_BANK_FLAG_CAN_WAIT); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(rng_inst), &ns_ctr); + if ((ret != 0) || (ns_ctr != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wc_RNG_GenerateBlock(WC_RNG_BANK_INST_TO_RNG(rng_inst), + outbuf1, sizeof(outbuf1)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* bank-wide PR posture: same freshness on every sleepable lease; + * atomic callers are refused outright; recovery is exempt. */ + bank->flags |= WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE; + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, WC_RNG_BANK_FLAG_NONE); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, + WC_RNG_BANK_FLAG_CAN_WAIT); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(rng_inst), &ns_ctr); + if ((ret != 0) || (ns_ctr != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (WC_RNG_BANK_INST_TO_RNG(rng_inst) == NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* take the instance out of service while holding it */ + WC_RNG_BANK_INST_TO_RNG(rng_inst)->status = WC_DRBG_FAILED; + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + /* a bare-targeted PR checkout refuses to serve -- or heal -- a + * failed instance (recovery is the sole restoration path), and + * unwinds completely */ + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, WC_RNG_BANK_FLAG_CAN_WAIT); + if (ret != WC_NO_ERR_TRACE(RNG_FAILURE_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (rng_inst != NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* recovery is exempt from the bank-wide posture */ + ret = wc_rng_bank_recover_inst(bank, 0, 10, + WC_RNG_BANK_FLAG_CAN_WAIT); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + /* restored: PR-served again, proving no leaked lock or refcount */ + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, + WC_RNG_BANK_FLAG_CAN_WAIT); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(rng_inst), &ns_ctr); + if ((ret != 0) || (ns_ctr != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + bank->flags &= ~(word32)WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE; + +#ifdef WC_RNG_HAVE_NEXT_SEED + /* PR supersedes CONSUME_NEXT_SEED: the fresh reseed is performed, + * and the banked seed is left intact for a later consumer */ + for (i = 0; i < 64; i++) { + ret = wc_rng_bank_next_seed_generate(bank, 0, + (word32)(WC_DRBG_NEXT_SEED_LEN / 7)); + if (ret == WC_NO_ERR_TRACE(ALREADY_E)) + break; + if ((ret != 0) && (ret != WC_NO_ERR_TRACE(NOT_READY_E)) && + (ret != WC_NO_ERR_TRACE(ENTROPY_RT_E)) && + (ret != WC_NO_ERR_TRACE(ENTROPY_APT_E))) + { + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + } + } + if (i >= 64) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wc_rng_bank_checkout(bank, &rng_inst, 0, 10, + WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE | + WC_RNG_BANK_FLAG_CAN_WAIT | + WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + { + WC_ATOMIC_INT_ARG pr_ns_cur = 0; + ret = wc_RNG_DRBG_GetReseedCtr( + WC_RNG_BANK_INST_TO_RNG(rng_inst), &ns_ctr); + if ((ret != 0) || (ns_ctr != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + ret = wc_RNG_DRBG_NextSeedCurrent( WC_RNG_BANK_INST_TO_RNG(rng_inst), &pr_ns_cur); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (pr_ns_cur != WC_DRBG_NEXT_SEED_READY) + ERROR_OUT(WC_TEST_RET_ENC_I((int)pr_ns_cur), out); + /* the surviving banked seed remains redeemable */ + ret = wc_RNG_DRBG_NextSeedNow( + WC_RNG_BANK_INST_TO_RNG(rng_inst)); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_RNG_DRBG_NextSeedCurrent( WC_RNG_BANK_INST_TO_RNG(rng_inst), &pr_ns_cur); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (pr_ns_cur != WC_DRBG_NEXT_SEED_EMPTY) + ERROR_OUT(WC_TEST_RET_ENC_I((int)pr_ns_cur), out); + } + ret = wc_rng_bank_inst_checkin(&rng_inst); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); +#endif /* WC_RNG_HAVE_NEXT_SEED */ + } +#endif /* (!HAVE_FIPS || FIPS_VERSION3_GE(7,0,0)) && HAVE_HASHDRBG */ + +#ifdef WC_RNG_BANK_HAVE_DAEMON_SUPPORT + #define RBT_MAGIC ((WC_ATOMIC_UINT_ARG)0x746e6164) /* arbitrary nonzero */ + #define RBT_MAGIC_2 ((WC_ATOMIC_UINT_ARG)0x746e6145) + + /* arg validation: NULL bank, FREE magic */ + ret = wc_rng_bank_daemon_reserve(NULL, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_daemon_reserve(bank, WC_RNG_BANK_DAEMON_MAGIC_FREE); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* register/unregister/release before any reserve: slot magic is FREE, + * so the caller's magic can never match. */ + ret = wc_rng_bank_daemon_register(bank, (void *)&ret, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_daemon_unregister(bank, &daemon_out, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_daemon_release(bank, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* reserve claims the slot */ + ret = wc_rng_bank_daemon_reserve(bank, RBT_MAGIC); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* the reservation's bank ref makes fini refuse: the documented + * leak-to-BUSY_E demotion, probed directly. */ + ret = wc_rng_bank_fini(bank); + if (ret != WC_NO_ERR_TRACE(BUSY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* double-reserve, same and different magic: slot occupied. */ + ret = wc_rng_bank_daemon_reserve(bank, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(BUSY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_daemon_reserve(bank, RBT_MAGIC_2); + if (ret != WC_NO_ERR_TRACE(BUSY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* register: NULL daemon rejected; wrong magic rejected; then accepted. */ + ret = wc_rng_bank_daemon_register(bank, NULL, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_daemon_register(bank, (void *)&ret, RBT_MAGIC_2); + if (ret != WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_daemon_register(bank, (void *)&ret, RBT_MAGIC); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* double-register: occupied. */ + ret = wc_rng_bank_daemon_register(bank, (void *)&outbuf1, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(ALREADY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* release while registered: refused, registration intact. */ + ret = wc_rng_bank_daemon_release(bank, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(BUSY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* unregister: NULL out and wrong magic rejected; then hands back the + * registered pointer, exactly once. */ + ret = wc_rng_bank_daemon_unregister(bank, NULL, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_daemon_unregister(bank, &daemon_out, RBT_MAGIC_2); + if (ret != WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_daemon_unregister(bank, &daemon_out, RBT_MAGIC); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (daemon_out != (void *)&ret) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* at-most-once: second unregister finds the slot empty. */ + daemon_out = NULL; + ret = wc_rng_bank_daemon_unregister(bank, &daemon_out, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(ALREADY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (daemon_out != NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* release frees the slot and drops the reservation ref. */ + ret = wc_rng_bank_daemon_release(bank, RBT_MAGIC); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* released slot: stale-magic ops can't match FREE. */ + ret = wc_rng_bank_daemon_release(bank, RBT_MAGIC); + if (ret != WC_NO_ERR_TRACE(WRONG_TYPE_OBJECT_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* slot is reusable, under a different magic, for the + * reserve -> spawn-failed -> release unwind shape (no register). */ + ret = wc_rng_bank_daemon_reserve(bank, RBT_MAGIC_2); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + ret = wc_rng_bank_daemon_release(bank, RBT_MAGIC_2); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* refcount balance is implicitly audited by the closing + * wc_rng_bank_fini(bank) succeeding below. */ + + #undef RBT_MAGIC + #undef RBT_MAGIC_2 +#endif /* WC_RNG_BANK_HAVE_DAEMON_SUPPORT */ + out: + if (held_inst != NULL) + (void)wc_rng_bank_inst_checkin(&held_inst); + { int cleanup_ret; @@ -28414,7 +29152,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) WC_FREE_VAR_EX(bank, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); #ifndef WC_RNG_BANK_STATIC -#ifdef WC_HAVE_RNG_BANKREF +#if defined(WC_HAVE_RNG_BANKREF) && !defined(WC_NO_CONSTRUCTORS) if (rng2) wc_rng_free(rng2); #endif @@ -28431,6 +29169,19 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) if ((bank2 != NULL) && (ret == 0)) ret = WC_TEST_RET_ENC_NC; #endif /* !WC_RNG_BANK_STATIC */ + +#ifdef WC_RNG_HAVE_RBGC +#ifndef WC_NO_CONSTRUCTORS + if (spawned_rng != NULL) + wc_rng_free(spawned_rng); +#endif + if (leaf_rng_inited) { + cleanup_ret = wc_FreeRng(leaf_rng); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + WC_FREE_VAR_EX(leaf_rng, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); +#endif /* WC_RNG_HAVE_RBGC */ } return ret; @@ -28438,6 +29189,1926 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void) #endif /* WC_RNG_BANK_SUPPORT */ +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && !defined(HAVE_SELFTEST) +/* Coverage for the DRBG state accessor / reseed scheduling services and the + * per-key RNG clear APIs. Probes that observe DRBG internals via the + * accessors are gated at runtime on wc_RNG_DRBG_Present(), so the test also + * passes on RDRAND-shaped instantiations. (The clear-API probes ride this + * test's gate for economy; a CUSTOM_RAND_GENERATE_BLOCK config loses only + * that sliver of coverage.) The SP 800-90C RBGC APIs are covered + * separately, in rng_drbg_rbgc_test(). */ +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_svc_test(void) +{ + wc_test_ret_t ret = 0; + int api_ret; + int present; + int root_inited = 0; + WC_DECLARE_VAR(root, WC_RNG, 1, HEAP_HINT); + wc_drbg_reseed_ctr_t c1 = 0; + wc_drbg_reseed_ctr_t c2 = 0; + byte buf[32]; + byte matter[32]; + RNG_STATS_DECLS; + + WOLFSSL_ENTER("rng_drbg_svc_test"); + + WC_ALLOC_VAR_EX(root, WC_RNG, 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER, + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out)); + + XMEMSET(matter, 0x5a, sizeof(matter)); + + /* accessor argument contracts, pre-init */ + if (wc_RNG_GetStatus(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_RNG_DRBG_Present(NULL) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifdef WC_RNG_HAVE_RBGC + if (wc_RNG_DRBG_GetRBGCStratum(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif + if (wc_RNG_DRBG_GetReseedCtr(NULL, &c1) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + api_ret = wc_InitRng(root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + root_inited = 1; + + if (wc_RNG_GetStatus(root) != WC_DRBG_OK) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_RNG_DRBG_GetReseedCtr(root, NULL) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifdef WC_RNG_HAVE_RBGC + if (wc_RNG_DRBG_GetRBGCStratum(root) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif + + present = wc_RNG_DRBG_Present(root); + + /* generate advances the reseed counter */ + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c1); + if ((api_ret != 0) || (c1 < 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_RNG_GenerateBlock(root, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c2); + if ((api_ret != 0) || (c2 <= c1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + + /* uncredited mixing never resets the counter -- it increments it by + * exactly one: a stir is a specified generate (additional_input, + * zero-length output), and a generate counts. */ + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c1); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_RNG_DRBG_Stir(root, matter, sizeof(matter)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c2); + if ((api_ret != 0) || (c2 != c1 + 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* credited reseed resets the counter */ + api_ret = wc_RNG_DRBG_Reseed(root, matter, sizeof(matter)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c1); + if ((api_ret != 0) || (c1 != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* schedule-then-generate performs a source reseed */ + api_ret = wc_RNG_DRBG_ScheduleReseed(root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c1); + if ((api_ret != 0) || + (c1 != (wc_drbg_reseed_ctr_t)WC_RESEED_INTERVAL)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + + RNG_STATS_SNAP(root); + api_ret = wc_RNG_GenerateBlock(root, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c1); + if ((api_ret != 0) || (c1 > 2)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* the scheduled reseed rides the generate, credited */ + RNG_STATS_EXPECT(root, _stats_reseeds, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT(root, _stats_total_requests, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT(root, _stats_total_bytes_produced, sizeof(buf), + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + } + + /* immediate source reseed, without and with a nonce */ + RNG_STATS_SNAP(root); + api_ret = wc_RNG_DRBG_Reseed_Now(root, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_Reseed_Now(root, matter, 16); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c1); + if ((api_ret != 0) || (c1 != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* both credited; the nonce is additional input, not an + * uncredited reseed */ + RNG_STATS_EXPECT(root, _stats_reseeds, 2, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT(root, _stats_stirs, 0, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + } + if (wc_RNG_DRBG_Reseed_Now(NULL, NULL, 0) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_RNG_DRBG_Reseed_Now(root, NULL, 5) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* per-key RNG clear APIs */ +#if !defined(NO_RSA) + { + WC_DECLARE_VAR(rsaKey, RsaKey, 1, HEAP_HINT); + int rsaKey_inited = 0; + WC_ALLOC_VAR_EX(rsaKey, RsaKey, 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER, + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out)); + api_ret = wc_InitRsaKey(rsaKey, HEAP_HINT); + if (api_ret != 0) + api_ret = WC_TEST_RET_ENC_EC(api_ret); + else { + rsaKey_inited = 1; + api_ret = wc_RsaSetRNG(rsaKey, root); + if (api_ret != 0) { + api_ret = WC_TEST_RET_ENC_EC(api_ret); + } + } + if (api_ret == 0) { + /* the setter's NULL rejection is contractual and preserved */ + if ((wc_RsaSetRNG(rsaKey, NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) || + (wc_RsaClearRNG(rsaKey) != 0) || + (wc_RsaClearRNG(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG))) + { + api_ret = WC_TEST_RET_ENC_NC; + } + } + if (rsaKey_inited) + (void)wc_FreeRsaKey(rsaKey); + WC_FREE_VAR(rsaKey, HEAP_HINT); + if (api_ret != 0) + ERROR_OUT(api_ret, out); + } +#endif /* !NO_RSA */ +#if defined(HAVE_ECC) && defined(ECC_TIMING_RESISTANT) + { + WC_DECLARE_VAR(eccKey, ecc_key, 1, HEAP_HINT); + int eccKey_inited = 0; + WC_ALLOC_VAR_EX(eccKey, ecc_key, 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER, + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out)); + api_ret = wc_ecc_init(eccKey); + if (api_ret != 0) + api_ret = WC_TEST_RET_ENC_EC(api_ret); + else { + eccKey_inited = 1; + if ((wc_ecc_set_rng(eccKey, root) != 0) || + (wc_ecc_clear_rng(eccKey) != 0) + || (wc_ecc_clear_rng(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ) + { + api_ret = WC_TEST_RET_ENC_NC; + } + } + if (eccKey_inited) + (void)wc_ecc_free(eccKey); + WC_FREE_VAR(eccKey, HEAP_HINT); + if (api_ret != 0) + ERROR_OUT(api_ret, out); + } +#endif /* HAVE_ECC && ECC_TIMING_RESISTANT */ +#if defined(HAVE_CURVE25519) && defined(WOLFSSL_CURVE25519_BLINDING) + { + WC_DECLARE_VAR(cvKey, curve25519_key, 1, HEAP_HINT); + int cvKey_inited = 0; + WC_ALLOC_VAR_EX(cvKey, curve25519_key, 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER, + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out)); + api_ret = wc_curve25519_init(cvKey); + if (api_ret != 0) + api_ret = WC_TEST_RET_ENC_EC(api_ret); + else { + cvKey_inited = 1; + if ((wc_curve25519_set_rng(cvKey, root) != 0) || + (wc_curve25519_clear_rng(cvKey) != 0) || + (wc_curve25519_clear_rng(NULL) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG))) + { + api_ret = WC_TEST_RET_ENC_NC; + } + } + if (cvKey_inited) + wc_curve25519_free(cvKey); + WC_FREE_VAR(cvKey, HEAP_HINT); + if (api_ret != 0) + ERROR_OUT(api_ret, out); + } +#endif /* HAVE_CURVE25519 && WOLFSSL_CURVE25519_BLINDING */ + +out: + + if (root_inited) { + int cleanup_ret = wc_FreeRng(root); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + + WC_FREE_VAR(root, HEAP_HINT); + + return ret; +} + +#endif /* HAVE_HASHDRBG && !CUSTOM_RAND_GENERATE_BLOCK && */ + /* (!HAVE_FIPS || FIPS_VERSION3_GE(7,0,0)) */ + +#if defined(WC_RNG_BANK_SUPPORT) && defined(HAVE_HASHDRBG) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(5,2,4)) && \ + !defined(HAVE_INTEL_RDRAND) + +/* Unit coverage for WC_RNG_LOCK_ENTROPY_INVALIDATED and the + * invalidation-recovery protocol (VM fork / resume), exercised through the + * regime-portable bank-instance latch interface so it runs identically + * against the in-boundary (v7+) lock and the pre-v7 rng_bank.h shims. + * Covers: flag set/read, lock refusal and conditional-claim recovery, + * credited-vs-uncredited clearing, put-side reporting with the flag riding + * through release, put_conditional convergence under a mid-hold + * invalidation, clear_extra immunity, and (v7+) unlocked recovery through + * the forced credited reseed, banked-next-seed purge, and RBGC chain + * recovery. */ + +#ifdef WC_RNG_HAVE_FREE_HOOK +static int rng_inval_test_bank_hook_fired = 0; +static int rng_inval_test_bank_hook_cb(const struct wc_rng_bank *bank, + void *arg) +{ + (void)bank; + if (arg == (void *)&rng_inval_test_bank_hook_fired) + rng_inval_test_bank_hook_fired++; + return 0; +} +static int rng_inval_test_hook_fired = 0; +static int rng_inval_test_hook_cb(const WC_RNG *rng, void *arg) +{ + (void)rng; + if (arg == (void *)&rng_inval_test_hook_fired) + rng_inval_test_hook_fired++; + return 0; +} +#endif + +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_entropy_invalidate_test(void) +{ + wc_test_ret_t ret = 0; + int api_ret; + WC_DECLARE_VAR(bank, struct wc_rng_bank, 1, HEAP_HINT); + struct wc_rng_bank_inst *inst = NULL; + struct wc_rng_bank_inst *held = NULL; + WC_RNG_lock_arg_t lock_state; + byte block[32]; + int bank_inited = 0; +#ifdef WC_RNG_HAVE_RBGC + WC_RNG root; + int root_inited = 0; +#endif + const WC_RNG_lock_arg_t annot_a = (1U << WC_RNG_LOCK_EXTRA_SHIFT); + + WOLFSSL_ENTER("rng_entropy_invalidate_test"); + + WC_CALLOC_VAR_EX(bank, struct wc_rng_bank, 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER, + return WC_TEST_RET_ENC_EC(MEMORY_E)); + + api_ret = wc_rng_bank_init(bank, WC_RNG_BANK_STATIC_SIZE, + WC_RNG_BANK_FLAG_CAN_WAIT, 10, HEAP_HINT, + INVALID_DEVID); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + bank_inited = 1; +#ifdef WC_RNG_HAVE_FREE_HOOK + rng_inval_test_bank_hook_fired = 0; + api_ret = wc_rng_bank_register_free_hook(bank, rng_inval_test_bank_hook_cb, + (void *)&rng_inval_test_bank_hook_fired); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#endif + + if (wc_rng_bank_inst_invalidate_entropy(NULL) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + + /* take a working instance the front way; keep the pointer past the + * check-in (the storage is the bank's). */ + api_ret = wc_rng_bank_checkout(bank, &inst, 0, 10, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + held = inst; + api_ret = wc_rng_bank_checkin(bank, &inst); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + /* fresh instance: flag clear. */ + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* invalidate while unheld: flag set, reseed scheduled. */ + api_ret = wc_rng_bank_inst_invalidate_entropy(held); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (! (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (lock_state & WC_RNG_LOCK_HELD) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#if (!defined(HAVE_INTEL_RDSEED) && !defined(HAVE_INTEL_RDRAND)) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_EQ(5,2,4) || FIPS_VERSION3_GE(7,0,0)) + { + wc_drbg_reseed_ctr_t reseed_ctr = 0; + api_ret = wc_RNG_DRBG_GetReseedCtr(WC_RNG_BANK_INST_TO_RNG(held), + &reseed_ctr); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (reseed_ctr < (wc_drbg_reseed_ctr_t)WC_RESEED_INTERVAL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } +#endif + + /* lock refusal on both get flavors; conditional claim carries the + * flag through acquisition. */ + api_ret = wc_rng_bank_inst_lock_get(held, 0); + if (api_ret != WC_NO_ERR_TRACE(NEEDS_RECOVERY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_get_conditional(held, 0, 0); + if (api_ret != WC_NO_ERR_TRACE(NEEDS_RECOVERY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_get_conditional( + held, WC_RNG_LOCK_ENTROPY_INVALIDATED, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if ((! (lock_state & WC_RNG_LOCK_HELD)) || + (! (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED))) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + + /* an uncredited stir cannot run on a quarantined instance at all -- + * the latch (checked atomically, ahead of the racy counter) refuses + * it: NOT_READY_E, with counter and flag untouched by + * construction. */ + XMEMSET(block, 0x5a, sizeof(block)); +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) + api_ret = wc_RNG_DRBG_Stir(WC_RNG_BANK_INST_TO_RNG(held), + block, sizeof(block)); + if (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#endif + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (! (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_rng_bank_inst_reseed_now(held, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_rng_bank_inst_lock_put(held); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + /* mid-hold invalidation: put reports NEEDS_RECOVERY_E, the flag rides + * through the release. */ + api_ret = wc_rng_bank_inst_lock_get(held, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_invalidate_entropy(held); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_put(held); + if (api_ret != WC_NO_ERR_TRACE(NEEDS_RECOVERY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if ((lock_state & WC_RNG_LOCK_HELD) || + (! (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED))) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + + /* the flag is not clearable through the annotation interface. */ + api_ret = wc_rng_bank_inst_lock_get_conditional( + held, WC_RNG_LOCK_ENTROPY_INVALIDATED, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_clear_extra( + held, WC_RNG_LOCK_ENTROPY_INVALIDATED); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (! (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_rng_bank_inst_lock_clear_extra(held, WC_RNG_LOCK_REQUIRED) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + + /* put_conditional under the flag: converges promptly to + * NEEDS_RECOVERY_E (regression probe for the expected-reconstruction + * refresh), releases, and preserves the flag. */ + api_ret = wc_rng_bank_inst_lock_set_extra(held, annot_a); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* bank instances are born REQUIRED; the conditional release must + * report the sticky bit among the expected extras, as check-in does. */ + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_put_conditional(held, + annot_a | (lock_state & WC_RNG_LOCK_REQUIRED)); + if (api_ret != WC_NO_ERR_TRACE(NEEDS_RECOVERY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if ((lock_state & WC_RNG_LOCK_HELD) || + (lock_state & annot_a) || + (! (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED))) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + +#ifdef WC_RNG_HAVE_RBGC + /* credited-chain recovery: an RBGC reseed from a healthy root clears + * the flag, exactly as a primary reseed does. */ + api_ret = wc_InitRng(&root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + root_inited = 1; + api_ret = wc_rng_bank_inst_lock_get_conditional( + held, WC_RNG_LOCK_ENTROPY_INVALIDATED, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_reseed_rbgc(held, &root, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_rng_bank_inst_lock_put(held); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#else + /* recover for a clean teardown. */ + api_ret = wc_rng_bank_inst_lock_get_conditional( + held, WC_RNG_LOCK_ENTROPY_INVALIDATED, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_reseed_now(held, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_put(held); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#endif /* WC_RNG_HAVE_RBGC */ + + /* bank-wide invalidation, and recovery via credited bank reseed. */ + api_ret = wc_rng_bank_invalidate_entropy(bank, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (! (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_rng_bank_invalidate_entropy(bank, WC_RNG_BANK_FLAG_QUIET) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + api_ret = wc_rng_bank_reseed_range(bank, 0, -1, NULL, 0, 10, + WC_RNG_BANK_FLAG_CAN_WAIT); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + +#if defined(WC_RNG_HAVE_LOCK) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) + /* v7+ in-boundary extras: unlocked recovery through the generate + * path's forced credited reseed, and the banked-next-seed purge. */ + { + WC_RNG* held_rng = WC_RNG_BANK_INST_TO_RNG(held); + + api_ret = wc_RNG_invalidate_entropy(held_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_get_conditional( + held, WC_RNG_LOCK_ENTROPY_INVALIDATED, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_GenerateBlock(held_rng, block, sizeof(block)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(held, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifdef WC_RNG_HAVE_NEXT_SEED + api_ret = wc_RNG_DRBG_NextSeedGenerate(held_rng, WC_DRBG_NEXT_SEED_LEN); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#ifdef WC_RNG_HAVE_RBGC + api_ret = wc_RNG_DRBG_GetNextSeedRBGCStratum(held_rng); + if (api_ret < 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#endif + api_ret = wc_RNG_invalidate_entropy(held_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#ifdef WC_RNG_HAVE_RBGC + api_ret = wc_RNG_DRBG_GetNextSeedRBGCStratum(held_rng); + if (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#endif + api_ret = wc_rng_bank_inst_reseed_now(held, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#endif /* WC_RNG_HAVE_NEXT_SEED */ + api_ret = wc_rng_bank_inst_lock_put(held); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + } + +#ifdef WC_RNG_HAVE_FREE_HOOK + /* free-hook lifecycle: register, fire-on-free (one-shot, arg intact), + * NULL-unregister. */ + { + WC_RNG hook_rng; + api_ret = wc_InitRng(&hook_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (wc_RNG_register_free_hook(NULL, rng_inval_test_hook_cb, NULL) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + api_ret = wc_RNG_register_free_hook(&hook_rng, rng_inval_test_hook_cb, + (void *)&rng_inval_test_hook_fired); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + rng_inval_test_hook_fired = 0; + api_ret = wc_FreeRng(&hook_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (rng_inval_test_hook_fired != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(rng_inval_test_hook_fired), out); + /* NULL-unregister: no fire on free. */ + api_ret = wc_InitRng(&hook_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_register_free_hook(&hook_rng, rng_inval_test_hook_cb, + (void *)&rng_inval_test_hook_fired); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_register_free_hook(&hook_rng, NULL, NULL); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_FreeRng(&hook_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (rng_inval_test_hook_fired != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(rng_inval_test_hook_fired), out); + } +#endif /* WC_RNG_HAVE_FREE_HOOK */ + +#if defined(WC_RNG_HAVE_NEXT_SEED) && defined(WC_RNG_HAVE_RBGC) && \ + !defined(HAVE_INTEL_RDSEED) && !defined(HAVE_INTEL_RDRAND) + /* WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED: + * recovery-consumption and chain-promotion at generate. */ + { + WC_RNG flag_rng; + api_ret = wc_InitRng_ex2(&flag_rng, HEAP_HINT, INVALID_DEVID, + WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* recovery: invalidate (purges the aperture), bank a fresh primary + * seed post-event, and generate -- consumption clears the flag. */ + api_ret = wc_RNG_invalidate_entropy(&flag_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedGenerate(&flag_rng, + WC_DRBG_NEXT_SEED_LEN); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_GenerateBlock(&flag_rng, block, sizeof(block)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_lock_read(&flag_rng, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&flag_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + api_ret = wc_FreeRng(&flag_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#ifdef WC_RNG_HAVE_RBGC + /* promotion: chain-backed leaf with a banked primary seed + * upgrades to stratum 0 at generate; without the flag it must + * not. */ + { + WC_RNG proot; + api_ret = wc_InitRng(&proot); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_InitRngNonceRBGC(&flag_rng, &proot, NULL, 0, NULL, 0, + WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&flag_rng); + if (api_ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedGenerate(&flag_rng, + WC_DRBG_NEXT_SEED_LEN); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_GenerateBlock(&flag_rng, block, sizeof(block)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&flag_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + api_ret = wc_FreeRng(&flag_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* negative: unflagged leaf keeps its stratum. */ + api_ret = wc_InitRngNonceRBGC(&flag_rng, &proot, NULL, 0, + NULL, 0, WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedGenerate(&flag_rng, + WC_DRBG_NEXT_SEED_LEN); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_GenerateBlock(&flag_rng, block, sizeof(block)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&flag_rng); + if (api_ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + api_ret = wc_FreeRng(&flag_rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_FreeRng(&proot); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + } +#endif /* WC_RNG_HAVE_RBGC */ + } +#endif /* NEXT_SEED && RBGC && !RDSEED && !RDRAND */ +#endif /* WC_RNG_HAVE_LOCK && HAVE_HASHDRBG && (!HAVE_FIPS || >= 7.0.0) */ + +#if defined(WC_RNG_BANK_DEFAULT_SUPPORT) && defined(WC_RNG_HAVE_LOCK) && \ + !defined(WC_NO_CONSTRUCTORS) + /* WC_RNG_BANK_FLAG_MAYBE_FOR_RECOVERY contract: checkout of a + * quarantined instance returns NEEDS_RECOVERY_E with the lease held; + * a credited reseed by the lease-holder recovers it. */ + { + struct wc_rng_bank *mb = NULL; + struct wc_rng_bank_inst *minst = NULL; + WC_RNG_lock_arg_t mlock = 0; + + api_ret = wc_rng_bank_new(&mb, 2, WC_RNG_BANK_FLAG_NONE, 0, HEAP_HINT, + INVALID_DEVID); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_invalidate_entropy(mb, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* without the flag: refused (retry disabled: timeout_secs 0). */ + api_ret = wc_rng_bank_checkout(mb, &minst, 0, 0, + WC_RNG_BANK_FLAG_NONE); + if (api_ret == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (minst != NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* with the flag: leased-but-quarantined. */ + api_ret = wc_rng_bank_checkout(mb, &minst, 0, 0, + WC_RNG_BANK_FLAG_MAYBE_FOR_RECOVERY); + if (api_ret != WC_NO_ERR_TRACE(NEEDS_RECOVERY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (minst == NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_rng_bank_inst_lock_read(minst, &mlock); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (! (mlock & WC_RNG_LOCK_HELD)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (! (mlock & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* the lease-holder recovers: credited reseed clears quarantine. */ + api_ret = wc_RNG_DRBG_Reseed_Now(WC_RNG_BANK_INST_TO_RNG(minst), + NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_lock_read(minst, &mlock); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (mlock & WC_RNG_LOCK_ENTROPY_INVALIDATED) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_RNG_GenerateBlock(WC_RNG_BANK_INST_TO_RNG(minst), block, + sizeof(block)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_checkin(&minst); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* patrol recovery: instance 1 is still quarantined; a single + * wc_rng_bank_recover_inst() clears it, after which an ordinary + * checkout succeeds. */ + api_ret = wc_rng_bank_recover_inst(mb, 1, 0, WC_RNG_BANK_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_checkout(mb, &minst, 1, 0, + WC_RNG_BANK_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_rng_bank_inst_checkin(&minst); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + api_ret = wc_rng_bank_free(&mb); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + } +#endif /* WC_RNG_BANK_DEFAULT_SUPPORT && WC_RNG_HAVE_LOCK && */ + /* !WC_NO_CONSTRUCTORS */ + + out: + + if (held != NULL) + (void)wc_rng_bank_inst_lock_put(held); + +#ifdef WC_RNG_HAVE_RBGC + if (root_inited) { + api_ret = wc_FreeRng(&root); + if ((ret == 0) && (api_ret != 0)) + ret = WC_TEST_RET_ENC_EC(api_ret); + } +#endif + if (bank_inited) { + api_ret = wc_rng_bank_fini(bank); + if ((ret == 0) && (api_ret != 0)) + ret = WC_TEST_RET_ENC_EC(api_ret); +#ifdef WC_RNG_HAVE_FREE_HOOK + if ((ret == 0) && (api_ret == 0) && + (rng_inval_test_bank_hook_fired != 1)) + { + ret = WC_TEST_RET_ENC_I(rng_inval_test_bank_hook_fired); + } +#endif + } + WC_FREE_VAR_EX(bank, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* WC_RNG_BANK_SUPPORT && (!HAVE_FIPS || FIPS_VERSION3_GE(5,2,4)) && */ + /* !HAVE_INTEL_RDRAND */ + +#ifdef WC_RNG_HAVE_RBGC + +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) + +/* Coverage for the SP 800-90C RBGC (RBG chain) APIs: spawn, reseed-from- + * root, the leaf tag and accessor, and the sticky stratum-one enforcement. + * DRBG-internal observations are gated at runtime on wc_RNG_DRBG_Present(), + * so the test also passes on RDRAND-shaped instantiations, where the RBGC + * APIs are exercised in their degenerate arms. */ +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_rbgc_test(void) +{ + wc_test_ret_t ret = 0; + int api_ret; + int present; + int root_inited = 0; + int leaf_inited = 0; + int extra_inited = 0; + WC_RNG root; + WC_RNG leaf; + WC_RNG extra; + WC_RNG* pleaf = NULL; + wc_drbg_reseed_ctr_t c1 = 0; + wc_drbg_reseed_ctr_t c2 = 0; + byte buf[32]; + byte matter[32]; + + RNG_STATS_DECLS; + + WOLFSSL_ENTER("rng_drbg_rbgc_test"); + + XMEMSET(matter, 0xa5, sizeof(matter)); + + api_ret = wc_InitRng(&root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + root_inited = 1; + + present = wc_RNG_DRBG_Present(&root); + + /* spawn argument contracts */ + api_ret = wc_InitRngRBGC(NULL, &root, WC_RNG_INIT_FLAG_NONE); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_InitRngRBGC(&leaf, NULL, WC_RNG_INIT_FLAG_NONE); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_InitRngRBGC(&root, &root, WC_RNG_INIT_FLAG_NONE); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#ifndef WC_NO_CONSTRUCTORS + api_ret = wc_InitRngRBGC_New(NULL, &root, WC_RNG_INIT_FLAG_NONE); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#endif + + /* spawn a leaf; the spawn debits root's counter; the leaf is tagged */ + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(&root, &c1); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + RNG_STATS_SNAP(&root); + api_ret = wc_InitRngRBGC(&leaf, &root, WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + leaf_inited = 1; + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(&root, &c2); + if ((api_ret != 0) || (c2 <= c1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* the spawn draw is one fully-served generate on the parent */ + RNG_STATS_EXPECT(&root, _stats_total_requests, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT_GE(&root, _stats_total_bytes_produced, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT_SAME_DELTA(&root, _stats_total_bytes_requested, + _stats_total_bytes_produced, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + } +#ifndef HAVE_INTEL_RDRAND + api_ret = wc_RNG_DRBG_GetRBGCStratum(&leaf); + if (api_ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); +#endif + RNG_STATS_SNAP2(&leaf); + api_ret = wc_RNG_GenerateBlock(&leaf, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (present) { + /* chain-provenance accounting: bytes generated at stratum 1 count + * in both the total and the RBGC ledgers */ + RNG_STATS_EXPECT2(&leaf, _stats_total_bytes_produced, sizeof(buf), + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT2(&leaf, _stats_RBGC_bytes_produced, sizeof(buf), + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + } + +#ifndef HAVE_INTEL_RDRAND + /* root(0) from leaf(1): refused -- no stratum downgrade. */ + api_ret = wc_RNG_DRBG_ReseedRBGC(&root, &leaf, NULL, 0); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + /* reseed-from-root, without and with a nonce; counter resets */ + RNG_STATS_SNAP(&root); + RNG_STATS_SNAP2(&leaf); + api_ret = wc_RNG_DRBG_ReseedRBGC(&leaf, &root, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_ReseedRBGC(&leaf, &root, matter, 16); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(&leaf, &c1); + if ((api_ret != 0) || (c1 != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* target: two credited chain reseeds; source: two fully-served + * seed draws */ + RNG_STATS_EXPECT2(&leaf, _stats_reseeds, 2, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT2(&leaf, _stats_RBGC_reseeds, 2, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT2(&leaf, _stats_stirs, 0, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT(&root, _stats_total_requests, 2, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT_SAME_DELTA(&root, _stats_total_bytes_requested, + _stats_total_bytes_produced, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + } + api_ret = wc_RNG_DRBG_ReseedRBGC(&leaf, &leaf, NULL, 0); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_ReseedRBGC(NULL, &root, NULL, 0); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_ReseedRBGC(&leaf, &root, NULL, 7); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + /* The RBGC stratum is reset to zero by a primary source reseed. */ + api_ret = wc_RNG_DRBG_ScheduleReseed(&leaf); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + RNG_STATS_SNAP2(&leaf); + api_ret = wc_RNG_GenerateBlock(&leaf, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + ret = wc_RNG_DRBG_GetRBGCStratum(&leaf); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_I(ret), out); + if (present) { + /* the primary reseed precedes the byte production, so the served + * bytes are not chain-provenance */ + RNG_STATS_EXPECT2(&leaf, _stats_reseeds, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT2(&leaf, _stats_total_bytes_produced, sizeof(buf), + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT2(&leaf, _stats_RBGC_bytes_produced, 0, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + } +#endif /* !HAVE_INTEL_RDRAND */ + +#if !defined(WC_NO_CONSTRUCTORS) && !defined(HAVE_INTEL_RDRAND) + /* chain-reseeding a source-born instance demotes it, one-way */ + api_ret = wc_InitRng(&extra); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + extra_inited = 1; + api_ret = wc_RNG_DRBG_GetRBGCStratum(&extra); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + if (present) { + api_ret = wc_RNG_DRBG_ReseedRBGC(&extra, &root, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&extra); + if (api_ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + /* long-chained init is allowed; chained reseeds are governed by the + * no-downgrade rule probed below. */ + ret = wc_InitRngRBGC_New(&pleaf, &extra, WC_RNG_INIT_FLAG_NONE); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if ((pleaf == NULL) || (wc_RNG_DRBG_GetRBGCStratum(pleaf) != 2)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* force leaf back to primary class (stratum 0) for the source-class + * probes below. */ + api_ret = wc_RNG_DRBG_ScheduleReseed(&leaf); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_GenerateBlock(&leaf, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&leaf); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + + /* chained credited reseeds: permitted iff the source's stratum + * strictly improves on (is less than) the target's. */ + api_ret = wc_RNG_DRBG_ReseedRBGC(pleaf, &extra, NULL, 0); + if (api_ret != 0) /* 1 < 2: allowed */ + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(pleaf); + if (api_ret != 2) /* acquires extra+1 */ + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + api_ret = wc_RNG_DRBG_ReseedRBGC(&extra, pleaf, NULL, 0); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) /* 2 >= 1: refused */ + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_ReseedRBGC(&leaf, &extra, NULL, 0); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) /* 1 >= 0: refused -- + * primary-born instances never downgrade + * by chained reseed. */ + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + /* primary-class (stratum-0) sources are always welcome, root or + * not. */ + api_ret = wc_RNG_DRBG_ReseedRBGC(&extra, &leaf, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&extra); + if (api_ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + + /* lateral (equal-stratum) chained reseeds are refused: the strict + * inequality is what makes cycles impossible. */ + api_ret = wc_RNG_DRBG_ReseedRBGC(&leaf, &root, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&leaf); + if (api_ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + api_ret = wc_RNG_DRBG_ReseedRBGC(&extra, &leaf, NULL, 0); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) /* 1 >= 1: refused */ + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + /* uncredited chained reseeds are unrestricted (stirs claim + * nothing): any source stratum, target stratum untouched. */ + api_ret = wc_RNG_DRBG_StirRBGC(&extra, pleaf, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(&extra); + if (api_ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + +#ifdef WC_RNG_HAVE_NEXT_SEED + /* the banked twin obeys the same rule: refuse banking whose + * redemption would violate no-downgrade... */ + api_ret = wc_RNG_DRBG_NextSeedGenerate_RBGC(&extra, pleaf, 1); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) /* 2 >= 1: refused */ + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* ...and permit improving banked material, whose redemption + * carries the recorded stratum. */ + api_ret = wc_RNG_DRBG_NextSeedGenerate_RBGC(pleaf, &extra, + 0xffffffffU); + if (api_ret != 0) /* 1 < 2: allowed; oversize fill clamps */ + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetNextSeedRBGCStratum(pleaf); + if (api_ret != 2) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedNow(pleaf); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetRBGCStratum(pleaf); + if (api_ret != 2) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); +#endif /* WC_RNG_HAVE_NEXT_SEED */ + + wc_rng_free(pleaf); + pleaf = NULL; + } + + /* heap-allocated leaves, without and with a nonce */ + api_ret = wc_InitRngRBGC_New(&pleaf, &root, WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if ((pleaf == NULL) || (wc_RNG_DRBG_GetRBGCStratum(pleaf) != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_RNG_GenerateBlock(pleaf, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + wc_rng_free(pleaf); + pleaf = NULL; + api_ret = wc_InitRngNonceRBGC_New(&pleaf, &root, matter, 16, + NULL, 0, WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if ((pleaf == NULL) || (wc_RNG_DRBG_GetRBGCStratum(pleaf) != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + wc_rng_free(pleaf); + pleaf = NULL; +#endif /* !WC_NO_CONSTRUCTORS && !HAVE_INTEL_RDRAND */ + + /* nonce-bearing stack spawn */ + api_ret = wc_FreeRng(&leaf); + leaf_inited = 0; + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_InitRngNonceRBGC(&leaf, &root, matter, 16, + NULL, 0, WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + leaf_inited = 1; +#ifndef HAVE_INTEL_RDRAND + api_ret = wc_RNG_DRBG_GetRBGCStratum(&leaf); + if (api_ret != 1) + ERROR_OUT(WC_TEST_RET_ENC_I(api_ret), out); +#endif + +out: + + { + int cleanup_ret; + if (pleaf != NULL) + wc_rng_free(pleaf); + if (leaf_inited) { + cleanup_ret = wc_FreeRng(&leaf); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + if (extra_inited) { + cleanup_ret = wc_FreeRng(&extra); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + if (root_inited) { + cleanup_ret = wc_FreeRng(&root); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + } + + return ret; +} + +#elif defined(WC_RNG_BANK_SUPPORT) + +/* On old FIPS, WC_RNG_BANK_SUPPORT is needed for RNG-level compat shims. */ + +#ifndef WC_RNG_INIT_FLAG_NONE + #define WC_RNG_INIT_FLAG_NONE 0 +#endif + +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_rbgc_test(void) +{ + wc_test_ret_t ret = 0; + int api_ret; + int present; + int root_inited = 0; + int leaf_inited = 0; + int extra_inited = 0; + WC_RNG root; + WC_RNG leaf; + WC_RNG extra; + WC_RNG* pleaf = NULL; + wc_drbg_reseed_ctr_t c1 = 0; + wc_drbg_reseed_ctr_t c2 = 0; + byte buf[32]; + byte matter[32]; + + WOLFSSL_ENTER("rng_drbg_rbgc_test"); + + XMEMSET(matter, 0xa5, sizeof(matter)); + + api_ret = wc_InitRng(&root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + root_inited = 1; + + present = wc_RNG_DRBG_Present(&root); + + /* spawn argument contracts */ + if (wc_InitRngRBGC(NULL, &root, WC_RNG_INIT_FLAG_NONE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_InitRngRBGC(&leaf, NULL, WC_RNG_INIT_FLAG_NONE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_InitRngRBGC(&root, &root, WC_RNG_INIT_FLAG_NONE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#ifndef WC_NO_CONSTRUCTORS + if (wc_InitRngRBGC_New(NULL, &root, WC_RNG_INIT_FLAG_NONE) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); +#endif + + /* spawn a leaf; the spawn debits root's counter; the leaf is tagged */ + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(&root, &c1); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + api_ret = wc_InitRngRBGC(&leaf, &root, WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + leaf_inited = 1; + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(&root, &c2); + if ((api_ret != 0) || (c2 <= c1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + api_ret = wc_RNG_GenerateBlock(&leaf, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + #if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) + if (wc_RNG_DRBG_ReseedRBGC(&root, &leaf) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + #endif + + /* reseed-from-root, without and with a nonce; counter resets */ + api_ret = wc_RNG_DRBG_ReseedRBGC(&leaf, &root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_ReseedRBGC(&leaf, &root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (present) { + api_ret = wc_RNG_DRBG_GetReseedCtr(&leaf, &c1); + if ((api_ret != 0) || (c1 != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + if (wc_RNG_DRBG_ReseedRBGC(&leaf, &leaf) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_RNG_DRBG_ReseedRBGC(NULL, &root) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* The RGBC stratum is reset to zero by a primary source reseed. */ + api_ret = wc_RNG_DRBG_ScheduleReseed(&leaf); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_GenerateBlock(&leaf, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + +#if !defined(WC_NO_CONSTRUCTORS) + /* chain-reseeding a source-born instance demotes it, one-way */ + api_ret = wc_InitRng(&extra); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + extra_inited = 1; + if (present) { + api_ret = wc_RNG_DRBG_ReseedRBGC(&extra, &root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* long-chained init is allowed, only chained reseed is forbidden. */ + ret = wc_InitRngRBGC_New(&pleaf, &extra, WC_RNG_INIT_FLAG_NONE); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (pleaf == NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + wc_rng_free(pleaf); + pleaf = NULL; + } + + /* heap-allocated leaves, without and with a nonce */ + api_ret = wc_InitRngRBGC_New(&pleaf, &root, WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (pleaf == NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + api_ret = wc_RNG_GenerateBlock(pleaf, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + wc_rng_free(pleaf); + pleaf = NULL; + api_ret = wc_InitRngNonceRBGC_New(&pleaf, &root, matter, 16, + NULL, 0, WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (pleaf == NULL) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + wc_rng_free(pleaf); + pleaf = NULL; +#endif /* !WC_NO_CONSTRUCTORS */ + + /* nonce-bearing stack spawn */ + api_ret = wc_FreeRng(&leaf); + leaf_inited = 0; + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_InitRngNonceRBGC(&leaf, &root, matter, 16, + WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + leaf_inited = 1; + +out: + + { + int cleanup_ret; + if (pleaf != NULL) + wc_rng_free(pleaf); + if (leaf_inited) { + cleanup_ret = wc_FreeRng(&leaf); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + if (extra_inited) { + cleanup_ret = wc_FreeRng(&extra); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + if (root_inited) { + cleanup_ret = wc_FreeRng(&root); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + } + + return ret; +} + +#endif /* HAVE_FIPS && FIPS_VERSION3_LT(7,0,0) && WC_RNG_BANK_SUPPORT */ + +#endif /* WC_RNG_HAVE_RBGC */ + +#ifdef WC_RNG_HAVE_NEXT_SEED +/* Coverage for the banked-next-seed facility: the aperture protocol + * (fill / publish / claim / release) as observed through + * wc_RNG_DRBG_NextSeedCurrent(), the distinct protocol results + * (NOT_READY_E, ALREADY_E), advance health testing at publish, + * source-free consumption resetting the reseed counter, use-once + * emptying, and the argument contracts. DRBG-internal probes are gated + * at runtime on wc_RNG_DRBG_Present(). */ +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_nextseedstest(void) +{ + wc_test_ret_t ret = 0; + int api_ret; + int present; + int root_inited = 0; + int i; +#if defined(WC_RNG_HAVE_LOCK) && defined(WC_RNG_HAVE_RBGC) + WC_RNG leaf; + int leaf_inited = 0; +#endif + WC_DECLARE_VAR(root, WC_RNG, 1, HEAP_HINT); + WC_ATOMIC_INT_ARG cur = 0; + WC_ATOMIC_INT_ARG prev = 0; + wc_drbg_reseed_ctr_t c1 = 0; + byte buf[32]; + byte matter[16]; + + RNG_STATS_DECLS; + + WOLFSSL_ENTER("rng_drbg_nextseedstest"); + + WC_ALLOC_VAR_EX(root, WC_RNG, 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER, + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out)); + + XMEMSET(matter, 0x3c, sizeof(matter)); + + /* argument contracts, pre-init */ + api_ret = wc_RNG_DRBG_NextSeedGenerate(NULL, 1); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedCurrent(NULL, &cur); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedNow(NULL); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedNow_Nonce(NULL, NULL, 0); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + api_ret = wc_InitRng(root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + root_inited = 1; + + present = wc_RNG_DRBG_Present(root); + + api_ret = wc_RNG_DRBG_NextSeedGenerate(root, 0); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedCurrent(root, NULL); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedNow_Nonce(root, NULL, 5); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + if (present) { + /* empty bank: nothing consumable */ + api_ret = wc_RNG_DRBG_NextSeedCurrent(root, &cur); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (cur != WC_DRBG_NEXT_SEED_EMPTY) + ERROR_OUT(WC_TEST_RET_ENC_I((int)cur), out); + api_ret = wc_RNG_DRBG_NextSeedNow(root); + if (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + /* partial bank: counted, still not consumable, preserved across + * the consume attempt */ + api_ret = wc_RNG_DRBG_NextSeedGenerate(root, 7); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedCurrent(root, &cur); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (cur != 7) + ERROR_OUT(WC_TEST_RET_ENC_I((int)cur), out); + api_ret = wc_RNG_DRBG_NextSeedNow(root); + if (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedCurrent(root, &cur); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (cur != 7) + ERROR_OUT(WC_TEST_RET_ENC_I((int)cur), out); + + /* fill in granules to publication: the count grows monotonically, + * then the ready sentinel appears */ + prev = cur; + for (i = 0; i < 64; i++) { + int gen_ret = wc_RNG_DRBG_NextSeedGenerate( + root, (word32)(WC_DRBG_NEXT_SEED_LEN / 7)); + if ((gen_ret != 0) && + (gen_ret != WC_NO_ERR_TRACE(ALREADY_E)) && + (gen_ret != WC_NO_ERR_TRACE(NOT_READY_E)) && + (gen_ret != WC_NO_ERR_TRACE(ENTROPY_RT_E)) && + (gen_ret != WC_NO_ERR_TRACE(ENTROPY_APT_E))) + { + ERROR_OUT(WC_TEST_RET_ENC_EC(gen_ret), out); + } + api_ret = wc_RNG_DRBG_NextSeedCurrent(root, &cur); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if ((gen_ret == WC_NO_ERR_TRACE(ALREADY_E)) || + (cur == WC_DRBG_NEXT_SEED_READY)) + { + break; + } + if (gen_ret == 0) { + if (cur <= prev) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + prev = cur; + } + } + if (cur != WC_DRBG_NEXT_SEED_READY) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* ready bank: further banking is ALREADY_E and changes nothing */ + api_ret = wc_RNG_DRBG_NextSeedGenerate(root, 32); + if (api_ret != WC_NO_ERR_TRACE(ALREADY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextSeedCurrent(root, &cur); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (cur != WC_DRBG_NEXT_SEED_READY) + ERROR_OUT(WC_TEST_RET_ENC_I((int)cur), out); + + /* consume: source-free credited reseed; counter resets to 1; + * bank empties (use-once) */ + RNG_STATS_SNAP(root); + api_ret = wc_RNG_DRBG_NextSeedNow(root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c1); + if ((api_ret != 0) || (c1 != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* redemption of a primary-provenance bank: credited, counted as a + * primary redemption */ + RNG_STATS_EXPECT(root, _stats_reseeds, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT(root, _stats_nextseedsprimary_redeemed, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); +#ifdef WC_RNG_HAVE_RBGC + RNG_STATS_EXPECT(root, _stats_nextseedsRBGC_redeemed, 0, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); +#endif + api_ret = wc_RNG_DRBG_NextSeedCurrent(root, &cur); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (cur != WC_DRBG_NEXT_SEED_EMPTY) + ERROR_OUT(WC_TEST_RET_ENC_I((int)cur), out); + + /* advance the counter, refill, and consume with a nonce */ + api_ret = wc_RNG_GenerateBlock(root, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + for (i = 0; i < 64; i++) { + api_ret = wc_RNG_DRBG_NextSeedGenerate( + root, (word32)(WC_DRBG_NEXT_SEED_LEN / 7)); + if (api_ret == WC_NO_ERR_TRACE(ALREADY_E)) + break; + if ((api_ret != 0) && (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) && + (api_ret != WC_NO_ERR_TRACE(ENTROPY_RT_E)) && + (api_ret != WC_NO_ERR_TRACE(ENTROPY_APT_E))) + { + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + } + } + if (i >= 64) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + RNG_STATS_SNAP(root); + api_ret = wc_RNG_DRBG_NextSeedNow_Nonce(root, matter, + sizeof(matter)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &c1); + if ((api_ret != 0) || (c1 != 1)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* the nonce rides as additional input: the redemption is still one + * credited, primary-provenance reseed */ + RNG_STATS_EXPECT(root, _stats_reseeds, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT(root, _stats_stirs, 0, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + RNG_STATS_EXPECT(root, _stats_nextseedsprimary_redeemed, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out)); + api_ret = wc_RNG_DRBG_NextSeedCurrent(root, &cur); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (cur != WC_DRBG_NEXT_SEED_EMPTY) + ERROR_OUT(WC_TEST_RET_ENC_I((int)cur), out); + } + + /* --- uncredited stir aperture (NextStir) lifecycle --- */ + if (present) { + byte frag[16]; + XMEMSET(frag, 0x71, sizeof(frag)); + + if (wc_RNG_DRBG_NextStirStore(NULL, frag, sizeof(frag)) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + if (wc_RNG_DRBG_NextStirStore(root, NULL, 1) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + if (wc_RNG_DRBG_NextStirStore(root, frag, 0) != + WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + { + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + + /* partial accumulation is not consumable. */ + api_ret = wc_RNG_DRBG_NextStirStore(root, frag, + sizeof(frag)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextStirNow(root); + if (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + /* fill to the top: READY; excess deposits absorbed by xorbuf(); + * oversize deposits clamp. */ + for (i = 0; i < (int)(WC_DRBG_NEXT_STIR_LEN / + sizeof(frag)); i++) + { + api_ret = wc_RNG_DRBG_NextStirStore(root, frag, + sizeof(frag)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + } + /* deposits on a READY accumulator fold in place using xorbuf() + * (advisory sentinel): absorbed, never refused. */ + api_ret = wc_RNG_DRBG_NextStirStore(root, frag, sizeof(frag)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + +#if !defined(HAVE_INTEL_RDSEED) && !defined(HAVE_INTEL_RDRAND) + /* consumption is a stir, not an epoch: the reseed counter advances + * by the stir's one generate, and is not reset. */ + { + wc_drbg_reseed_ctr_t ctr_before = 0, ctr_after = 0; + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &ctr_before); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextStirNow(root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_GetReseedCtr(root, &ctr_after); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* A stir is one SP 800-90A 10.1.1.4 generate and no reseed, so + * the counter advances by exactly one: a reset to 1 would mean + * the stir had masqueraded as a credited reseed, and no change + * at all would mean it never ran. */ + if (ctr_after != ctr_before + 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } +#else + api_ret = wc_RNG_DRBG_NextStirNow(root); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); +#endif + + /* use-once: accumulation reopened. */ + api_ret = wc_RNG_DRBG_NextStirNow(root); + if (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextStirStore(root, frag, + sizeof(frag)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + + /* top up and verify the universal opportunistic consume at + * generate: post-generate, the accumulator is spent. */ + for (i = 0; i < (int)(WC_DRBG_NEXT_STIR_LEN / + sizeof(frag)); i++) + { + api_ret = wc_RNG_DRBG_NextStirStore(root, frag, + sizeof(frag)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + } + api_ret = wc_RNG_GenerateBlock(root, buf, sizeof(buf)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_DRBG_NextStirNow(root); + if (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + } + +#if defined(WC_RNG_HAVE_LOCK) && defined(WC_RNG_HAVE_RBGC) + /* a stir must never masquerade as recovery or promotion: consumption + * preserves WC_RNG_LOCK_ENTROPY_INVALIDATED and RBGCStratum. */ + if (present) { + WC_RNG_lock_arg_t lock_state; + byte frag64[WC_DRBG_NEXT_STIR_LEN]; + XMEMSET(frag64, 0x5e, sizeof(frag64)); + + api_ret = wc_InitRngNonceRBGC(&leaf, root, NULL, 0, + NULL, 0, WC_RNG_INIT_FLAG_NONE); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + leaf_inited = 1; + api_ret = wc_RNG_invalidate_entropy(&leaf); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* deposit post-event (the event purge emptied the accumulator). */ + api_ret = wc_RNG_DRBG_NextStirStore(&leaf, frag64, + sizeof(frag64)); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + /* consumption refuses on the quarantined instance (latch checked + * atomically ahead of the racy counter); the use-once aperture + * reopens EMPTY regardless -- stirs are best-effort, and + * accumulation simply resumes. */ + api_ret = wc_RNG_DRBG_NextStirNow(&leaf); + if (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + api_ret = wc_RNG_lock_read(&leaf, &lock_state); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + if (! (lock_state & WC_RNG_LOCK_ENTROPY_INVALIDATED)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (wc_RNG_DRBG_GetRBGCStratum(&leaf) != 1) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + /* recover for a clean teardown. */ + api_ret = wc_RNG_DRBG_Reseed_Now(&leaf, NULL, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + leaf_inited = 0; + api_ret = wc_FreeRng(&leaf); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out); + } +#endif /* WC_RNG_HAVE_LOCK && WC_RNG_HAVE_RBGC */ + +out: + +#if defined(WC_RNG_HAVE_LOCK) && defined(WC_RNG_HAVE_RBGC) + if (leaf_inited) { + int cleanup_ret = wc_FreeRng(&leaf); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } +#endif + + if (root_inited) { + int cleanup_ret = wc_FreeRng(root); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + WC_FREE_VAR(root, HEAP_HINT); + + return ret; +} +#endif /* WC_RNG_HAVE_NEXT_SEED */ + +#ifdef WC_RNG_HAVE_POOL +/* Coverage for the asynchronous DRBG output pool: allocation contracts + * (incl. the word16 size bound and double-alloc rejection), self- and + * cross-instance collection, published-count tracking, destructive + * extraction with partial delivery and the empty-pool NOT_READY_E, and ring + * wraparound on both the collect and extract sides. Single-threaded, so + * reader/writer interleavings are exercised elsewhere; this pins the + * sequential contracts. */ +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_pool_test(void) +{ + wc_test_ret_t ret = 0; + int api_ret; + int rng_inited = 0; + int src_inited = 0; + WC_DECLARE_VAR(rng, WC_RNG, 1, HEAP_HINT); + WC_DECLARE_VAR(src, WC_RNG, 1, HEAP_HINT); + word32 n = 0; + byte out[48]; + + RNG_STATS_DECLS; + + WOLFSSL_ENTER("rng_pool_test"); + + WC_ALLOC_VAR_EX(rng, WC_RNG, 1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER, + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_l)); + WC_ALLOC_VAR_EX(src, WC_RNG, 1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER, + ERROR_OUT(WC_TEST_RET_ENC_EC(MEMORY_E), out_l)); + + /* argument contracts, pre-init */ + api_ret = wc_RNG_Pool_Alloc(NULL, 48); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Collect(NULL, 1); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Extract(NULL, out, &n); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Current(NULL, &n); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + + api_ret = wc_InitRng(rng); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + rng_inited = 1; + api_ret = wc_InitRng(src); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + src_inited = 1; + + /* size bounds; operations on a pool-less instance */ + api_ret = wc_RNG_Pool_Alloc(rng, 1); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Alloc(rng, 65536); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Collect(rng, 8); + if (api_ret != WC_NO_ERR_TRACE(BAD_STATE_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + n = sizeof(out); + api_ret = wc_RNG_Pool_Extract(rng, out, &n); + if (api_ret != WC_NO_ERR_TRACE(BAD_STATE_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Current(rng, &n); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + if (n != 0) + ERROR_OUT(WC_TEST_RET_ENC_I((int)n), out_l); + +#ifndef WOLFSSL_NO_MALLOC + api_ret = wc_RNG_Pool_Alloc(rng, 48); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Alloc(rng, 48); + if (api_ret != WC_NO_ERR_TRACE(ALREADY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + + /* empty pool: a distinct protocol code, nothing delivered */ + n = sizeof(out); + RNG_STATS_SNAP(rng); + api_ret = wc_RNG_Pool_Extract(rng, out, &n); + if (api_ret != WC_NO_ERR_TRACE(NOT_READY_E)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + /* the whole request is missed bytes; nothing served */ + RNG_STATS_EXPECT(rng, _stats_pool_bytes_missed, sizeof(out), + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out_l)); + RNG_STATS_EXPECT(rng, _stats_pool_bytes_produced, 0, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out_l)); + + /* self-collect to full (clamped), verify count, over-collect no-op */ + api_ret = wc_RNG_Pool_Collect(rng, 100); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Current(rng, &n); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + if (n != 48) + ERROR_OUT(WC_TEST_RET_ENC_I((int)n), out_l); + api_ret = wc_RNG_Pool_Collect(rng, 1); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + + /* partial extract, then cross-instance top-off wrapping the ring, + * then full drain crossing the wrap on the read side */ + n = 32; + RNG_STATS_SNAP(rng); + api_ret = wc_RNG_Pool_Extract(rng, out, &n); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + if (n != 32) + ERROR_OUT(WC_TEST_RET_ENC_I((int)n), out_l); + api_ret = wc_RNG_Pool_Current(rng, &n); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + if (n != 16) + ERROR_OUT(WC_TEST_RET_ENC_I((int)n), out_l); + /* a fully-fulfillable partial drain is all produced, no shortfall */ + RNG_STATS_EXPECT(rng, _stats_pool_bytes_produced, 32, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out_l)); + RNG_STATS_EXPECT(rng, _stats_pool_bytes_missed, 0, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out_l)); + RNG_STATS_SNAP2(src); + api_ret = wc_RNG_Pool_Collect2(rng, src, 32); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + if (wc_RNG_DRBG_Present(src)) { + /* the top-off span starts exactly at the ring origin + * ((offset + current) % size == 0): one contiguous generate */ + RNG_STATS_EXPECT2(src, _stats_total_requests, 1, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out_l)); + RNG_STATS_EXPECT2(src, _stats_total_bytes_produced, 32, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out_l)); + } + api_ret = wc_RNG_Pool_Current(rng, &n); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + if (n != 48) + ERROR_OUT(WC_TEST_RET_ENC_I((int)n), out_l); + n = sizeof(out); + RNG_STATS_SNAP(rng); + api_ret = wc_RNG_Pool_Extract(rng, out, &n); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + if (n != 48) + ERROR_OUT(WC_TEST_RET_ENC_I((int)n), out_l); + api_ret = wc_RNG_Pool_Current(rng, &n); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + if (n != 0) + ERROR_OUT(WC_TEST_RET_ENC_I((int)n), out_l); + /* full serve across the ring wrap: all produced, no shortfall */ + RNG_STATS_EXPECT(rng, _stats_pool_bytes_produced, 48, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out_l)); + RNG_STATS_EXPECT(rng, _stats_pool_bytes_missed, 0, + ERROR_OUT(WC_TEST_RET_ENC_I((int)rng_stats_d_), out_l)); + + /* Collect2 contracts */ + api_ret = wc_RNG_Pool_Collect2(rng, NULL, 8); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Collect2(NULL, src, 8); + if (api_ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); + api_ret = wc_RNG_Pool_Collect2(rng, src, 0); + if (api_ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(api_ret), out_l); +#endif /* WOLFSSL_NO_MALLOC */ + +out_l: + + { + int cleanup_ret; + if (rng_inited) { + cleanup_ret = wc_FreeRng(rng); /* sole pool teardown site */ + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + if (src_inited) { + cleanup_ret = wc_FreeRng(src); + if ((cleanup_ret != 0) && (ret == 0)) + ret = WC_TEST_RET_ENC_EC(cleanup_ret); + } + } + WC_FREE_VAR(rng, HEAP_HINT); + WC_FREE_VAR(src, HEAP_HINT); + + return ret; +} +#endif /* WC_RNG_HAVE_POOL */ + #endif /* !WC_NO_RNG */ #ifndef MEM_TEST_SZ @@ -38718,7 +41389,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t openssl_evpSig_test(void) #endif /* OPENSSL_EXTRA */ -#ifdef HAVE_ARGON2 +#if defined(HAVE_ARGON2) && !defined(WOLFSSL_NO_MALLOC) /* Test vectors from RFC 9106 section 5, which uses the same inputs for all * three variants: p=4, T=32, m=32, t=3, v=0x13, with a secret and associated * data supplied. */ @@ -38852,7 +41523,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t argon2_test(void) return 0; } -#endif /* HAVE_ARGON2 */ +#endif /* HAVE_ARGON2 && !WOLFSSL_NO_MALLOC */ #ifndef NO_PWDBASED #ifdef HAVE_SCRYPT @@ -85765,7 +88436,7 @@ static wc_test_ret_t shake_cb_copy_free_test(myCryptoDevCtx* myCtx, #if defined(HAVE_HKDF) && !defined(NO_HMAC) && \ !defined(NO_SHA256) && !defined(HAVE_SELFTEST) && \ - (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) && \ + !defined(HAVE_FIPS) && \ !defined(WC_TEST_NO_CRYPTOCB_SW_TEST) /* Bound retries so a broken contract fails instead of spinning. */ @@ -86653,7 +89324,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) if (ret == 0) ret = hkdf_test(); #if !defined(NO_SHA256) && !defined(HAVE_SELFTEST) && \ - (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) + !defined(HAVE_FIPS) if (ret == 0) ret = hkdf_cryptocb_async_test(&myCtx); #endif @@ -86676,9 +89347,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) #endif /* Driver coverage for the new CryptoCb hooks: confirm each op is routed - * through myCryptoDevCb (counter bumped) and the round-trip is correct. */ + * through myCryptoDevCb (counter bumped) and the round-trip is correct. + * + * The FIPS wrappers force the devId to FIPS_INVALID_DEVID, so we skip + * the check for FIPS. */ #if defined(HAVE_ED448) && defined(HAVE_ED448_SIGN) && \ - defined(HAVE_ED448_VERIFY) && !defined(WC_NO_RNG) + defined(HAVE_ED448_VERIFY) && !defined(WC_NO_RNG) && \ + !defined(HAVE_FIPS) if (ret == 0) { WC_RNG ed448Rng; int ed448RngInit = 0; @@ -86742,7 +89417,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) #endif /* HAVE_ED448 */ #if defined(WOLFSSL_CMAC) && defined(WOLF_CRYPTO_CB_FREE) && \ - !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT) + !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT) && \ + !defined(HAVE_FIPS) if (ret == 0) { byte cmacKey[WC_AES_BLOCK_SIZE] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, @@ -86802,7 +89478,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) #if defined(WC_RSA_PSS) && defined(WOLF_CRYPTO_CB_RSA_PAD) && \ !defined(NO_RSA) && !defined(WC_NO_RNG) && defined(WOLFSSL_KEY_GEN) && \ - !defined(NO_SHA256) + !defined(NO_SHA256) && !defined(HAVE_FIPS) if (ret == 0) { WC_RNG rsaRng; int rsaRngInit = 0; diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index 0f83600be61..800499d5de4 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -75,7 +75,7 @@ wc_static_assert(-(long)MIN_CODE_E < 0x7ffL); #define WC_TEST_RET_ENC_NC WC_TEST_RET_ENC(WC_TEST_RET_LN, 0, WC_TEST_RET_TAG_NC) /* encode positive integer */ -#define WC_TEST_RET_ENC_I(i) WC_TEST_RET_ENC(WC_TEST_RET_LN, i, WC_TEST_RET_TAG_I) +#define WC_TEST_RET_ENC_I(i) WC_TEST_RET_ENC(WC_TEST_RET_LN, ((i) > 0x7ff) ? 0x7ff : (i), WC_TEST_RET_TAG_I) /* encode error code (negative integer) */ #define WC_TEST_RET_ENC_EC(ec) WC_TEST_RET_ENC(WC_TEST_RET_LN, -(ec), WC_TEST_RET_TAG_EC) @@ -114,6 +114,10 @@ wc_static_assert(-(long)MIN_CODE_E < 0x7ffL); #endif #endif +/* Note, all macro gates used below must be available with just + * wolfcrypt/types.h included, i.e. no macros in alg-specific headers can be + * used here. + */ extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t macro_test(void); extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t error_test(void); extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t octets_test(void); @@ -255,6 +259,14 @@ extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void); #ifdef WC_RNG_BANK_SUPPORT extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void); #endif +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_svc_test(void); +#endif +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_rbgc_test(void); +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_entropy_invalidate_test(void); +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_drbg_nextseedstest(void); +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rng_pool_test(void); #endif /* WC_NO_RNG */ extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pwdbased_test(void); #if defined(USE_CERT_BUFFERS_2048) && \ diff --git a/wolfssl/wolfcrypt/curve25519.h b/wolfssl/wolfcrypt/curve25519.h index 64978403411..ec1db0b69e8 100644 --- a/wolfssl/wolfcrypt/curve25519.h +++ b/wolfssl/wolfcrypt/curve25519.h @@ -222,6 +222,8 @@ void wc_curve25519_free(curve25519_key* key); #ifdef WOLFSSL_CURVE25519_BLINDING WOLFSSL_API int wc_curve25519_set_rng(curve25519_key* key, WC_RNG* rng); +WOLFSSL_API +int wc_curve25519_clear_rng(curve25519_key* key); #endif #ifndef WC_NO_CONSTRUCTORS diff --git a/wolfssl/wolfcrypt/ecc.h b/wolfssl/wolfcrypt/ecc.h index efa57e4290f..072910a574c 100644 --- a/wolfssl/wolfcrypt/ecc.h +++ b/wolfssl/wolfcrypt/ecc.h @@ -898,6 +898,8 @@ WOLFSSL_API void wc_ecc_fp_init(void); WOLFSSL_API int wc_ecc_set_rng(ecc_key* key, WC_RNG* rng); +WOLFSSL_API +int wc_ecc_clear_rng(ecc_key* key); WOLFSSL_API int wc_ecc_set_curve(ecc_key* key, int keysize, int curve_id); diff --git a/wolfssl/wolfcrypt/error-crypt.h b/wolfssl/wolfcrypt/error-crypt.h index fad6f926c7c..f0f57c492ff 100644 --- a/wolfssl/wolfcrypt/error-crypt.h +++ b/wolfssl/wolfcrypt/error-crypt.h @@ -354,9 +354,11 @@ enum wolfCrypt_ErrorCodes { OBJECT_NOT_LOCKED_E = -1031, /* Required lock on object is not held */ WRONG_TYPE_OBJECT_E = -1032, /* Object is wrong type for requested */ /* operation */ + NEEDS_RECOVERY_E = -1033, /* Object needs recovery before use */ + UNEXPECTED_STATE_E = -1034, /* Object has unexpected state */ - WC_SPAN2_LAST_E = -1032, /* Update to indicate last used error code */ - WC_LAST_E = -1032, /* the last code used either here or in + WC_SPAN2_LAST_E = -1034, /* Update to indicate last used error code */ + WC_LAST_E = -1034, /* the last code used either here or in * error-ssl.h */ WC_SPAN2_MIN_CODE_E = -1999, /* Last usable code in span 2 */ diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 0b416da560d..b8999f8736e 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -43,6 +43,87 @@ WOLFSSL_LOCAL int wolfCrypt_FIPS_DRBG_sanity(void); #endif +/***** Setup for RNG extra features *****/ + +#if (defined(WC_RNG_EXTRAS) || defined(WC_RNG_WANT_LOCK)) && \ + !defined(WC_RNG_NO_LOCK) + #define WC_RNG_HAVE_LOCK + #ifdef WOLFSSL_NO_ATOMICS + typedef word32 WC_RNG_lock_t; + typedef word32 WC_RNG_lock_arg_t; + #else + typedef wolfSSL_Atomic_Uint WC_RNG_lock_t; + typedef WC_ATOMIC_UINT_ARG WC_RNG_lock_arg_t; + #endif +#else + #undef WC_RNG_HAVE_LOCK +#endif + +#ifdef WC_RNG_WANT_LOCK_FULL_MUTEX + #ifndef WC_RNG_HAVE_LOCK + #error FULL_MUTEX depends on WC_RNG_HAVE_LOCK. + #endif + #define WC_RNG_HAVE_LOCK_FULL_MUTEX +#else + #undef WC_RNG_HAVE_LOCK_FULL_MUTEX +#endif + +#if (defined(WC_RNG_EXTRAS) || defined(WC_RNG_WANT_RBGC)) && \ + !defined(WC_RNG_NO_RBGC) && defined(HAVE_HASHDRBG) && \ + !defined(CUSTOM_RAND_GENERATE_BLOCK) + #define WC_RNG_HAVE_RBGC +#else + #undef WC_RNG_HAVE_RBGC +#endif + +#if (defined(WC_RNG_EXTRAS) || defined(WC_RNG_WANT_NEXT_SEED)) && \ + !defined(WC_RNG_NO_NEXT_SEED) && defined(HAVE_HASHDRBG) && \ + !defined(CUSTOM_RAND_GENERATE_BLOCK) + #define WC_RNG_HAVE_NEXT_SEED + #ifdef WOLFSSL_NO_ATOMICS + typedef sword32 WC_DRBG_nextSeedLen_t; + #else + typedef wolfSSL_Atomic_Int WC_DRBG_nextSeedLen_t; + #endif +#else + #undef WC_RNG_HAVE_NEXT_SEED +#endif +#if defined(WC_RNG_HAVE_NEXT_SEED) && !defined(WC_RNG_HAVE_LOCK) && \ + defined(HAVE_FIPS) + /* FIPS builds: pregenerated output requires the full invalidation/purge + * protocol, which rides the lock facility. */ + #error WC_RNG_WANT_NEXT_SEED requires WC_RNG_WANT_LOCK in FIPS builds. +#endif + +#if (defined(WC_RNG_EXTRAS) || defined(WC_RNG_WANT_POOL)) && \ + !defined(WC_RNG_NO_POOL) + #define WC_RNG_HAVE_POOL + #ifdef WOLFSSL_NO_ATOMICS + typedef word32 WC_RNG_pool_pos_t; + #else + typedef wolfSSL_Atomic_Uint WC_RNG_pool_pos_t; + #endif +#else + #undef WC_RNG_HAVE_POOL +#endif +#if defined(WC_RNG_HAVE_POOL) && !defined(WC_RNG_HAVE_LOCK) && \ + defined(HAVE_FIPS) + /* FIPS builds: pregenerated output requires the full invalidation/purge + * protocol, which rides the lock facility. */ + #error WC_RNG_WANT_POOL requires WC_RNG_WANT_LOCK in FIPS builds. +#endif + +#if (defined(WC_RNG_EXTRAS) || defined(WC_RNG_WANT_FREE_HOOK)) && \ + !defined(WC_RNG_NO_FREE_HOOK) + #define WC_RNG_HAVE_FREE_HOOK + struct WC_RNG; + typedef int (*wc_RNG_free_hook_cb_t)(const struct WC_RNG *rng, void *arg); +#else + #undef WC_RNG_HAVE_FREE_HOOK +#endif + +/***** End setup for RNG extra features *****/ + /* Maximum generate block length */ #ifndef RNG_MAX_BLOCK_LEN #ifdef HAVE_INTEL_QA @@ -73,7 +154,7 @@ #undef HAVE_HASHDRBG #define HAVE_HASHDRBG #ifndef WC_RESEED_INTERVAL - #define WC_RESEED_INTERVAL (1000000) + #define WC_RESEED_INTERVAL 1000000 #endif #endif @@ -295,15 +376,37 @@ struct OS_Seed { #define RNG_HEALTH_TEST_CHECK_SIZE_SHA512 (WC_SHA512_DIGEST_SIZE * 4) #endif -#ifndef NO_SHA256 -struct DRBG_internal { +#ifdef WC_RNG_HAVE_NEXT_SEED + /* Length of the banked next seed: identical byte accounting to other + * source-fed (re)seeds in the module (gather SEED_SZ + SEED_BLOCK_SZ, apply + * the block-offset remainder). */ + #define WC_DRBG_NEXT_SEED_LEN (WC_DRBG_SEED_SZ + WC_DRBG_SEED_BLOCK_SZ) + #define WC_DRBG_NEXT_STIR_LEN 64 +#endif + +#ifndef WC_DRBG_RESEED_CTR_TYPE_DEFINED +#define WC_DRBG_RESEED_CTR_TYPE_DEFINED #ifdef WORD64_AVAILABLE - word64 reseedCtr; + typedef word64 wc_drbg_reseed_ctr_t; #else - word32 reseedCtr; + typedef word32 wc_drbg_reseed_ctr_t; #endif +#endif + +#ifndef NO_SHA256 +struct DRBG_internal { + wc_drbg_reseed_ctr_t reseedCtr; byte V[DRBG_SEED_LEN]; byte C[DRBG_SEED_LEN]; +#ifdef WC_RNG_HAVE_NEXT_SEED + byte nextSeed[WC_DRBG_NEXT_SEED_LEN]; + WC_DRBG_nextSeedLen_t nextSeedLen; + #ifdef WC_RNG_HAVE_RBGC + int nextSeedRBGCStratum; + #endif + byte nextStir[WC_DRBG_NEXT_STIR_LEN]; + WC_DRBG_nextSeedLen_t nextStirLen; +#endif void* heap; #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) int devId; @@ -318,9 +421,18 @@ struct DRBG_internal { #ifdef WOLFSSL_DRBG_SHA512 struct DRBG_SHA512_internal { - word64 reseedCtr; + wc_drbg_reseed_ctr_t reseedCtr; byte V[DRBG_SHA512_SEED_LEN]; byte C[DRBG_SHA512_SEED_LEN]; +#ifdef WC_RNG_HAVE_NEXT_SEED + byte nextSeed[WC_DRBG_NEXT_SEED_LEN]; + WC_DRBG_nextSeedLen_t nextSeedLen; + #ifdef WC_RNG_HAVE_RBGC + int nextSeedRBGCStratum; + #endif + byte nextStir[WC_DRBG_NEXT_STIR_LEN]; + WC_DRBG_nextSeedLen_t nextStirLen; +#endif void* heap; #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLF_CRYPTO_CB) int devId; @@ -347,29 +459,100 @@ enum wc_RngHealthState { WC_DRBG_NOT_INIT = 0, WC_DRBG_OK = 1, WC_DRBG_FAILED = 2, - WC_DRBG_CONT_FAILED = 3, -#ifdef WC_RNG_BANK_SUPPORT - WC_DRBG_BANKREF = 4, /* Marks the WC_RNG as a ref to a wc_rng_bank, - * with no usable DRBG of its own. - */ - #define WC_HAVE_RNG_BANKREF -#endif - WOLF_ENUM_DUMMY_LAST_ELEMENT(wc_RngHealthState) + WC_DRBG_CONT_FAILED = 3 }; +#define WC_RNG_FLAG_NONE 0 +#define WC_RNG_FLAG_RBGC_NEXT_SEED (1U << 0) +#define WC_RNG_FLAG_FULL_MUTEX (1U << 1) +#define WC_RNG_FLAG_BANKREF (1U << 2) +#define WC_RNG_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED (1U << 3) + +#ifndef WC_RNG_RBGC_USER_SEED_STRATUM + #define WC_RNG_RBGC_USER_SEED_STRATUM 65536 +#endif +wc_static_assert(WC_RNG_RBGC_USER_SEED_STRATUM >= 256); + +#ifdef WC_RNG_WANT_DEBUG_STATS + #define WC_RNG_DEBUG_STATS +#endif + +#if defined(WC_RNG_DEBUG_STATS) && !defined(WC_RNG_HAVE_LOCK) + #error WC_RNG_DEBUG_STATS requires the RNG lock facility (WC_RNG_WANT_LOCK). +#endif + +#ifdef WC_RNG_DEBUG_STATS + #ifdef WORD64_AVAILABLE + typedef word64 wc_rng_debug_counter_t; + #else + typedef word32 wc_rng_debug_counter_t; + #endif +#endif + /* RNG context */ struct WC_RNG { struct OS_Seed seed; void* heap; byte status; + word32 flags; + #ifdef WC_RNG_DEBUG_STATS + wc_rng_debug_counter_t _stats_total_bytes_requested; + wc_rng_debug_counter_t _stats_total_bytes_produced; + wc_rng_debug_counter_t _stats_total_requests; + wc_rng_debug_counter_t _stats_reseeds; + wc_rng_debug_counter_t _stats_stirs; + wc_rng_debug_counter_t _stats_seed_failures; + #endif +#ifdef WC_RNG_HAVE_RBGC + int RBGCStratum; + #ifdef WC_RNG_DEBUG_STATS + wc_rng_debug_counter_t _stats_RBGC_bytes_produced; + wc_rng_debug_counter_t _stats_RBGC_reseeds; + #endif +#endif +#ifdef WC_RNG_HAVE_LOCK + WC_RNG_lock_t lock; + #ifdef WC_RNG_HAVE_LOCK_FULL_MUTEX + wolfSSL_Mutex mutex; + #endif + #ifdef WC_RNG_DEBUG_STATS + wc_rng_debug_counter_t _stats_locks_taken; + wc_rng_debug_counter_t _stats_locks_released; + wc_rng_debug_counter_t _stats_locks_refused; /* racy */ + #endif +#endif +#ifdef WC_RNG_HAVE_FREE_HOOK + wc_RNG_free_hook_cb_t free_hook; + void *free_hook_arg; +#endif +#ifdef WC_RNG_HAVE_POOL + byte* pool; + word16 poolSize; + WC_RNG_pool_pos_t poolHead; /* written by writer/purger only, CAS */ + WC_RNG_pool_pos_t poolTail; /* written by reader only, plain. */ + #ifdef WC_RNG_DEBUG_STATS + wc_rng_debug_counter_t _stats_pool_bytes_produced; + wc_rng_debug_counter_t _stats_pool_bytes_missed; + #endif +#endif -#if defined(WC_RNG_BANK_SUPPORT) || defined(HAVE_HASHDRBG) +#ifdef WC_RNG_DEBUG_STATS + #ifdef WC_RNG_HAVE_NEXT_SEED + wc_rng_debug_counter_t _stats_nextseedsprimary_redeemed; + wc_rng_debug_counter_t _stats_nextseedsRBGC_redeemed; + wc_rng_debug_counter_t _stats_nextstirs_redeemed; + wc_rng_debug_counter_t _stats_nextseedsbanked; + wc_rng_debug_counter_t _stats_nextstirs_banked; + #endif +#endif + +#if defined(HAVE_HASHDRBG) || defined(WC_HAVE_RNG_BANKREF) #ifdef HAVE_ANONYMOUS_INLINE_AGGREGATES union { #endif - #ifdef WC_RNG_BANK_SUPPORT + #ifdef WC_HAVE_RNG_BANKREF struct wc_rng_bank *bankref; #endif @@ -415,7 +598,7 @@ struct WC_RNG { }; #endif -#endif /* WC_RNG_BANK_SUPPORT || HAVE_HASHDRBG */ +#endif /* HAVE_HASHDRBG || WC_HAVE_RNG_BANKREF */ #if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) pid_t pid; @@ -554,8 +737,25 @@ WOLFSSL_ABI WOLFSSL_API void wc_rng_free(WC_RNG* rng); #ifndef WC_NO_RNG 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); -WOLFSSL_API int wc_InitRngNonce_ex(WC_RNG* rng, byte* nonce, word32 nonceSz, +WOLFSSL_API int wc_InitRngNonce(WC_RNG* rng, const byte* nonce, word32 nonceSz); + +#define WC_RNG_INIT_FLAG_NONE 0 +#define WC_RNG_INIT_FLAG_LOCK_REQUIRED (1U << 0) +#define WC_RNG_INIT_FLAG_LOCK_INITIALLY (1U << 1) +#define WC_RNG_INIT_FLAG_USE_FULL_MUTEX (1U << 2) +/* At each generate, if a banked next seed is READY, consume it when the + * instance is flagged _ENTROPY_INVALIDATED (recovery; any provenance), or + * when the instance is chain-backed and the banked seed is primary + * (promotion). For externally-refreshed long-lived RNGs, e.g. the kernel + * module's registered RBGC leaves. */ +#define WC_RNG_INIT_FLAG_RECOVER_AND_PROMOTE_FROM_NEXT_SEED (1U << 3) + +WOLFSSL_API int wc_InitRng_ex2(WC_RNG* rng, void* heap, int devId, + word32 flags); +WOLFSSL_API int wc_InitRngNonce_ex2(WC_RNG* rng, const byte* nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + void* heap, int devId, word32 flags); +WOLFSSL_API int wc_InitRngNonce_ex(WC_RNG* rng, const byte* nonce, word32 nonceSz, void* heap, int devId); WOLFSSL_ABI WOLFSSL_API int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz); WOLFSSL_API int wc_RNG_GenerateByte(WC_RNG* rng, byte* b); @@ -566,6 +766,8 @@ WOLFSSL_API int wc_FreeRng(WC_RNG* rng); #define wc_InitRng_ex(rng, h, d) NOT_COMPILED_IN #define wc_InitRngNonce(rng, n, s) NOT_COMPILED_IN #define wc_InitRngNonce_ex(rng, n, s, h, d) NOT_COMPILED_IN +#define wc_InitRng_ex2(rng, h, d, f) NOT_COMPILED_IN +#define wc_InitRngNonce_ex2(rng, n, s, p, ps, h, d, f) NOT_COMPILED_IN #if defined(__ghs__) || defined(WC_NO_RNG_SIMPLE) /* some older compilers do not like macro function in expression */ #define wc_RNG_GenerateBlock(rng, b, s) NOT_COMPILED_IN @@ -585,10 +787,35 @@ WOLFSSL_API int wc_FreeRng(WC_RNG* rng); WOLFSSL_API int wc_SetSeed_Cb(wc_RngSeed_Cb cb); #endif +WOLFSSL_API int wc_RNG_GetStatus(const WC_RNG* rng); +WOLFSSL_API int wc_RNG_DRBG_Present(const WC_RNG* rng); + #ifdef HAVE_HASHDRBG WOLFSSL_API int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz); + WOLFSSL_API int wc_RNG_DRBG_Reseed_Nonce(WC_RNG* rng, const byte* seed, + word32 seedSz, const byte *nonce, + word32 nonceSz); + WOLFSSL_API int wc_RNG_DRBG_Stir(WC_RNG* rng, + const byte* seed, + word32 seedSz); + WOLFSSL_API int wc_RNG_DRBG_Stir_Nonce( + WC_RNG* rng, const byte* seed, word32 seedSz, + const byte *nonce, word32 nonceSz); + WOLFSSL_API int wc_RNG_DRBG_Reseed_Now(WC_RNG* rng, const byte* nonce, + word32 nonceSz); WOLFSSL_API int wc_RNG_TestSeed(const byte* seed, word32 seedSz); + +#ifdef WC_RNG_HAVE_RBGC + WOLFSSL_API int wc_RNG_DRBG_GetRBGCStratum(const WC_RNG* rng); + #ifdef WC_RNG_HAVE_NEXT_SEED + WOLFSSL_API int wc_RNG_DRBG_GetNextSeedRBGCStratum(const WC_RNG* rng); + #endif +#endif /* WC_RNG_HAVE_RBGC */ + WOLFSSL_API int wc_RNG_DRBG_GetReseedCtr(const WC_RNG* rng, + wc_drbg_reseed_ctr_t* reseedCtr); + WOLFSSL_API int wc_RNG_DRBG_ScheduleReseed(WC_RNG* rng); + #ifndef NO_SHA256 /* SHA-256 Hash_DRBG health test entry points. SHA-512-only builds * (NO_SHA256 + WOLFSSL_DRBG_SHA512) use wc_RNG_HealthTest_SHA512_ex @@ -697,6 +924,146 @@ WOLFSSL_API int wc_FreeRng(WC_RNG* rng); #endif /* HAVE_HASHDRBG */ +#ifdef WC_RNG_HAVE_RBGC + /* SP 800-90C RBG-chain spawn: instantiate child as a subordinate DRBG + * seeded from parent's generate output. The _New variants allocate the + * child from parent's heap; release them with ordinary wc_rng_free(). */ + WOLFSSL_API int wc_InitRngRBGC(WC_RNG* child, WC_RNG* parent, word32 flags); + WOLFSSL_API int wc_InitRngNonceRBGC(WC_RNG* child, WC_RNG* parent, + const byte* nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + word32 flags); + #ifndef WC_NO_CONSTRUCTORS + /* Flags are per-object (WC_RNG_INIT_FLAG_*), deliberately not + * inherited from the parent: a child's lock policy is its own. */ + WOLFSSL_API int wc_InitRngRBGC_New(WC_RNG** child, WC_RNG* parent, + word32 flags); + WOLFSSL_API int wc_InitRngNonceRBGC_New(WC_RNG** child, WC_RNG* parent, + const byte* nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + word32 flags); + #endif /* !WC_NO_CONSTRUCTORS */ + WOLFSSL_API int wc_RNG_DRBG_ReseedRBGC(WC_RNG* rng, WC_RNG* root, + const byte* nonce, word32 nonceSz); + WOLFSSL_API int wc_RNG_DRBG_StirRBGC(WC_RNG* rng, + WC_RNG* root, + const byte* nonce, + word32 nonceSz); +#endif /* WC_RNG_HAVE_RBGC */ + +#ifdef WC_RNG_HAVE_NEXT_SEED + #define WC_DRBG_NEXT_SEED_EMPTY 0 + /* All sentinel states are negative; non-negative values are banked byte + * counts. */ + #define WC_DRBG_NEXT_SEED_PRODUCING (-1) + #define WC_DRBG_NEXT_SEED_READY (-2) + #define WC_DRBG_NEXT_SEED_CONSUMING (-3) + #define WC_DRBG_NEXT_SEED_PURGED (-4) + + WOLFSSL_API int wc_RNG_DRBG_NextSeedGenerate(WC_RNG* rng, word32 n); +#ifdef WC_RNG_HAVE_RBGC + WOLFSSL_API int wc_RNG_DRBG_NextSeedGenerate_RBGC(WC_RNG* rng, + WC_RNG *root, + word32 n); +#endif + WOLFSSL_API int wc_RNG_DRBG_NextSeedCurrent(WC_RNG* rng, + WC_ATOMIC_INT_ARG* n); + WOLFSSL_API int wc_RNG_DRBG_NextSeedNow_Nonce(WC_RNG* rng, + const byte* nonce, + word32 nonceSz); + WOLFSSL_API int wc_RNG_DRBG_NextSeedNow(WC_RNG* rng); + WOLFSSL_API int wc_RNG_DRBG_NextStirStore(WC_RNG* rng, + const byte *nonce, word32 nonceSz); + WOLFSSL_API int wc_RNG_DRBG_NextStirNow(WC_RNG* rng); + +#endif /* WC_RNG_HAVE_NEXT_SEED */ + +#ifdef WC_RNG_HAVE_LOCK + #define WC_RNG_LOCK_FREE 0 + #define WC_RNG_LOCK_HELD (1U<<0) + #define WC_RNG_LOCK_REQUIRED (1U<<1) + #define WC_RNG_LOCK_ENTROPY_INVALIDATED (1U<<2) + #define WC_RNG_LOCK_ENTROPY_RECOVERING (1U<<3) + /* consumers' annotation bits start here (see e.g. rng_bank.h) */ + #define WC_RNG_LOCK_EXTRA_SHIFT 4U + + WOLFSSL_API int wc_RNG_lock_get(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits); + WOLFSSL_API int wc_RNG_lock_get_conditional(WC_RNG* rng, + WC_RNG_lock_arg_t expected_extra_bits, + WC_RNG_lock_arg_t want_extra_bits); + WOLFSSL_API int wc_RNG_lock_put(WC_RNG* rng, WC_RNG_lock_arg_t extra_bits); + WOLFSSL_API int wc_RNG_lock_put_conditional(WC_RNG* rng, + WC_RNG_lock_arg_t expected_extra_bits, + WC_RNG_lock_arg_t want_extra_bits); + WOLFSSL_API int wc_RNG_lock_read(WC_RNG* rng, WC_RNG_lock_arg_t* state); + WOLFSSL_API int wc_RNG_lock_set_extra(WC_RNG* rng, + WC_RNG_lock_arg_t extra_bits); + WOLFSSL_API int wc_RNG_lock_add_extra(WC_RNG* rng, + WC_RNG_lock_arg_t extra_bits); + WOLFSSL_API int wc_RNG_lock_clear_extra(WC_RNG* rng, + WC_RNG_lock_arg_t extra_bits); + #ifdef HAVE_HASHDRBG + WOLFSSL_API int wc_RNG_invalidate_entropy(WC_RNG* rng); + #endif +#endif /* WC_RNG_HAVE_LOCK */ + +#ifdef WC_RNG_HAVE_FREE_HOOK +/* Register a callback fired by wc_FreeRng() immediately before state + * destruction, e.g. to unlink the object from an external registry. + * One-shot: cleared before firing. A NULL free_hook unregisters. + * Reinitialization (wc_InitRng*() on a live object) clears any + * registered hook without firing it: hooks are per-lifetime. */ +WOLFSSL_API int wc_RNG_register_free_hook(WC_RNG* rng, + wc_RNG_free_hook_cb_t free_hook, + void *arg); +#endif + +#ifdef WC_RNG_HAVE_POOL + WOLFSSL_API int wc_RNG_Pool_Alloc(WC_RNG* rng, word32 size); + WOLFSSL_API int wc_RNG_Pool_Collect(WC_RNG* rng, word32 n); + WOLFSSL_API int wc_RNG_Pool_Collect2(WC_RNG* rng_dest, WC_RNG* rng_src, + word32 n); + WOLFSSL_API int wc_RNG_Pool_Extract(WC_RNG* rng, byte* out, word32* n); + WOLFSSL_API int wc_RNG_Pool_Current(WC_RNG* rng, word32* n); +#endif /* WC_RNG_HAVE_POOL */ + +#ifdef WC_RNG_DEBUG_STATS +struct wc_rng_debug_stats_snapshot { + wc_rng_debug_counter_t _stats_total_bytes_requested; + wc_rng_debug_counter_t _stats_total_bytes_produced; + wc_rng_debug_counter_t _stats_total_requests; + wc_rng_debug_counter_t _stats_reseeds; + wc_rng_debug_counter_t _stats_stirs; + wc_rng_debug_counter_t _stats_seed_failures; + wc_rng_debug_counter_t _stats_locks_taken; + wc_rng_debug_counter_t _stats_locks_released; + wc_rng_debug_counter_t _stats_locks_refused; +#ifdef WC_RNG_HAVE_RBGC + wc_rng_debug_counter_t _stats_RBGC_bytes_produced; + wc_rng_debug_counter_t _stats_RBGC_reseeds; +#endif +#ifdef WC_RNG_HAVE_POOL + wc_rng_debug_counter_t _stats_pool_bytes_produced; + wc_rng_debug_counter_t _stats_pool_bytes_missed; +#endif +#ifdef WC_RNG_HAVE_NEXT_SEED + wc_rng_debug_counter_t _stats_nextseedsprimary_redeemed; + wc_rng_debug_counter_t _stats_nextseedsRBGC_redeemed; + wc_rng_debug_counter_t _stats_nextstirs_redeemed; + wc_rng_debug_counter_t _stats_nextseedsbanked; + wc_rng_debug_counter_t _stats_nextstirs_banked; +#endif +}; + +WOLFSSL_API int wc_rng_debug_stats_snap(struct wc_rng_debug_stats_snapshot *s, + const WC_RNG *rng); +WOLFSSL_API int wc_rng_debug_stats_restore( + const struct wc_rng_debug_stats_snapshot *s, + WC_RNG *rng); +WOLFSSL_API int wc_rng_debug_stats_sum(struct wc_rng_debug_stats_snapshot *s, + const WC_RNG *rng); +#endif /* WC_RNG_DEBUG_STATS */ + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/wolfssl/wolfcrypt/rng_bank.h b/wolfssl/wolfcrypt/rng_bank.h index f1e102ef0fa..2361d1a9be7 100644 --- a/wolfssl/wolfcrypt/rng_bank.h +++ b/wolfssl/wolfcrypt/rng_bank.h @@ -40,30 +40,81 @@ #error WC_RNG_BANK_SUPPORT requires RNG support. #endif -#define WC_RNG_BANK_FLAG_NONE 0 -#define WC_RNG_BANK_FLAG_INITED (1<<0) -#define WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST (1<<1) -#define WC_RNG_BANK_FLAG_CAN_WAIT (1<<2) -#define WC_RNG_BANK_FLAG_NO_VECTOR_OPS (1<<3) -#define WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST (1<<4) -#define WC_RNG_BANK_FLAG_AFFINITY_LOCK (1<<5) - -#define WC_RNG_BANK_INST_LOCK_FREE 0 -#define WC_RNG_BANK_INST_LOCK_HELD (1<<0) -#define WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED (1<<1) -#define WC_RNG_BANK_INST_LOCK_VEC_OPS_INH (1<<2) +#if !defined(WOLFSSL_NO_ATOMICS) && !defined(WC_RNG_BANK_NO_DAEMON_SUPPORT) + #define WC_RNG_BANK_HAVE_DAEMON_SUPPORT + #define WC_RNG_BANK_DAEMON_MAGIC_FREE 0U +#endif + +#define WC_RNG_BANK_FLAG_NONE 0 +#define WC_RNG_BANK_FLAG_INITED (1U << 0) +#define WC_RNG_BANK_FLAG_CAN_FAIL_OVER_INST (1U << 1) +#define WC_RNG_BANK_FLAG_CAN_WAIT (1U << 2) +#define WC_RNG_BANK_FLAG_NO_VECTOR_OPS (1U << 3) +#define WC_RNG_BANK_FLAG_PREFER_AFFINITY_INST (1U << 4) +#define WC_RNG_BANK_FLAG_AFFINITY_LOCK (1U << 5) +#define WC_RNG_BANK_FLAG_STIR (1U << 6) +#define WC_RNG_BANK_FLAG_CONSUME_NEXT_SEED (1U << 7) +#define WC_RNG_BANK_FLAG_FOR_RECOVERY (1U << 8) +#define WC_RNG_BANK_FLAG_MAYBE_FOR_RECOVERY (1U << 9) +#define WC_RNG_BANK_FLAG_ERROR_ON_RNG_FAILED (1U << 10) +#define WC_RNG_BANK_FLAG_QUIET (1U << 11) +#define WC_RNG_BANK_FLAG_NO_CHECKOUT_REFCOUNTING (1U << 12) +#define WC_RNG_BANK_FLAG_RBGC (1U << 13) +#define WC_RNG_BANK_FLAG_DEFAULT_BANK (1U << 14) +#define WC_RNG_BANK_FLAG_PREDICTION_RESISTANCE (1U << 15) +#define WC_RNG_BANK_FLAG_AUTO_RECOVER_AND_PROMOTE (1U << 16) + +#ifdef WC_RNG_HAVE_LOCK + wc_static_assert(WC_RNG_LOCK_EXTRA_SHIFT == 4U); +#else /* !WC_RNG_HAVE_LOCK */ + /* Definitions for backward-compat / WC_RNG_NO_LOCK */ + #define WC_RNG_LOCK_FREE 0 + #define WC_RNG_LOCK_HELD (1U<<0) + #define WC_RNG_LOCK_REQUIRED (1U<<1) + #define WC_RNG_LOCK_ENTROPY_INVALIDATED (1U<<2) + #define WC_RNG_LOCK_ENTROPY_RECOVERING (1U<<3) + #define WC_RNG_LOCK_EXTRA_SHIFT 4U + #ifdef WOLFSSL_NO_ATOMICS + typedef word32 WC_RNG_lock_t; + typedef word32 WC_RNG_lock_arg_t; + #else + typedef wolfSSL_Atomic_Uint WC_RNG_lock_t; + typedef WC_ATOMIC_UINT_ARG WC_RNG_lock_arg_t; + #endif +#endif + +/* When WC_RNG_HAVE_LOCK, base lock states are in random.h and the lock word + * itself is in WC_RNG.lock; these annotation bits ride above the base bits via + * wc_RNG_lock_get() / _set_extra() / _clear_extra(). */ +#define WC_RNG_BANK_INST_LOCK_AFFINITY_LOCKED (1U<<(WC_RNG_LOCK_EXTRA_SHIFT+0)) +#define WC_RNG_BANK_INST_LOCK_VEC_OPS_INH (1U<<(WC_RNG_LOCK_EXTRA_SHIFT+1)) typedef int (*wc_affinity_lock_fn_t)(void *arg); typedef int (*wc_affinity_get_id_fn_t)(void *arg, int *id); typedef int (*wc_affinity_unlock_fn_t)(void *arg); +struct wc_rng_bank; + +typedef int (*wc_rng_bank_free_hook_cb_t)(const struct wc_rng_bank *bank, + void *arg); + +#define WC_RNG_BANK_INST_FLAG_NONE 0 +#define WC_RNG_BANK_INST_FLAG_ALREADY_WARNED (1U << 0) + struct wc_rng_bank_inst { -#ifdef WOLFSSL_NO_ATOMICS - int lock; -#else - wolfSSL_Atomic_Int lock; -#endif + #ifdef WC_RNG_HAVE_LOCK + /* the exclusivity latch lives in WC_RNG.lock (wc_RNG_lock_*()) -- + * in-FIPS-boundary, module-enforced. */ + #else + #ifdef WOLFSSL_NO_ATOMICS + word32 lock; + #else + wolfSSL_Atomic_Uint lock; + #endif + #endif + struct wc_rng_bank *bank; WC_RNG rng; + volatile word32 flags; }; #if defined(WOLFSSL_NO_MALLOC) && defined(NO_WOLFSSL_MEMORY) && \ @@ -78,16 +129,31 @@ struct wc_rng_bank_inst { struct wc_rng_bank { wolfSSL_Ref refcount; void *heap; + int devId; word32 flags; + wc_rng_bank_free_hook_cb_t free_hook; + void *free_hook_arg; wc_affinity_lock_fn_t affinity_lock_cb; wc_affinity_get_id_fn_t affinity_get_id_cb; wc_affinity_unlock_fn_t affinity_unlock_cb; void *cb_arg; /* if mutable, caller is responsible for thread safety. */ int n_rngs; + int first_failover_inst; +#ifdef WC_RNG_HAVE_NEXT_SEED + wolfSSL_Atomic_Int inst_op_gate; +#endif #ifdef WC_RNG_BANK_STATIC struct wc_rng_bank_inst rngs[WC_RNG_BANK_STATIC_SIZE]; #else - struct wc_rng_bank_inst *rngs; /* typically one per CPU ID, plus a few */ + struct wc_rng_bank_inst *rngs; +#endif +#ifdef WC_RNG_BANK_HAVE_DAEMON_SUPPORT + wolfSSL_Atomic_Uint daemon_magic; + void *daemon; /* e.g. a task_struct* for a wc_linuxkm_entropy_daemon() */ +#endif +#if defined(WC_RNG_HAVE_RBGC) || defined(WC_RNG_HAVE_NEXT_SEED) || \ + defined(WC_RNG_HAVE_POOL) + WC_RNG root_rng; #endif }; @@ -109,6 +175,20 @@ WOLFSSL_API int wc_rng_bank_init( void *heap, int devId); +WOLFSSL_API int wc_rng_bank_init_nonce( + struct wc_rng_bank *ctx, + int n_rngs, + word32 flags, + int timeout_secs, + void *heap, + int devId, + const byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz); + +WOLFSSL_API int wc_rng_bank_first_failover_inst_set( + struct wc_rng_bank *ctx, + int first_failover_inst); + WOLFSSL_API int wc_rng_bank_set_affinity_handlers( struct wc_rng_bank *ctx, wc_affinity_lock_fn_t affinity_lock_cb, @@ -141,34 +221,157 @@ WOLFSSL_API int wc_rng_bank_checkout( int timeout_secs, word32 flags); +#ifdef WC_RNG_BANK_HAVE_DAEMON_SUPPORT +/* Note, these APIs must be called in order, _reserve -> _register -> + * _unregister -> _release, for lifecycle hygiene. A registered daemon must be + * _unregister()ed, and the bank _release()d, before wc_rng_bank_fini(), + * otherwise _fini() will return BUSY_E. */ +WOLFSSL_API int wc_rng_bank_daemon_reserve(struct wc_rng_bank *bank, + WC_ATOMIC_UINT_ARG magic); +WOLFSSL_API int wc_rng_bank_daemon_register(struct wc_rng_bank *bank, + void *daemon, + WC_ATOMIC_UINT_ARG magic); +WOLFSSL_API int wc_rng_bank_daemon_unregister(struct wc_rng_bank *bank, + void **daemon, + WC_ATOMIC_UINT_ARG magic); +WOLFSSL_API int wc_rng_bank_daemon_release(struct wc_rng_bank *bank, + WC_ATOMIC_UINT_ARG magic); +#endif /* WC_RNG_BANK_HAVE_DAEMON_SUPPORT */ + +#if defined(WC_DRBG_BANKREF) && !defined(WC_HAVE_RNG_BANKREF) + /* forward compat for FIPS v5.2.4 random.h */ + #define WC_HAVE_RNG_BANKREF +#endif + +#ifdef WC_HAVE_RNG_BANKREF WOLFSSL_LOCAL int wc_local_rng_bank_checkout_for_bankref( struct wc_rng_bank *bank, struct wc_rng_bank_inst **rng_inst); +#endif + +WOLFSSL_API int wc_rng_bank_get_inst_id(struct wc_rng_bank_inst *rng_inst); + +static WC_INLINE int WC_ARG_NOT_NULL(1) wc_rng_bank_inst_flags_up( + struct wc_rng_bank_inst *rng_inst, word32 flags) +{ + if (! (rng_inst->flags & flags)) { + rng_inst->flags = rng_inst->flags | flags; + return 1; + } + else + return 0; +} + +static WC_INLINE int WC_ARG_NOT_NULL(1) wc_rng_bank_inst_flags_down( + struct wc_rng_bank_inst *rng_inst, word32 flags) +{ + if (rng_inst->flags & flags) { + rng_inst->flags = rng_inst->flags & ~flags; + return 1; + } + else + return 0; +} WOLFSSL_API int wc_rng_bank_checkin( struct wc_rng_bank *bank, struct wc_rng_bank_inst **rng_inst); +WOLFSSL_API int wc_rng_bank_inst_checkin( + struct wc_rng_bank_inst **rng_inst); + +#ifdef WC_RNG_HAVE_NEXT_SEED +WOLFSSL_API int wc_rng_bank_next_seed_generate( + struct wc_rng_bank *bank, + int inst_offset, + word32 n); +#ifdef WC_RNG_HAVE_RBGC +WOLFSSL_API int wc_rng_bank_next_seed_generate_rbgc( + struct wc_rng_bank *bank, + int inst_offset, + word32 n); +#endif +#endif + WOLFSSL_API int wc_rng_bank_inst_reinit( struct wc_rng_bank *bank, struct wc_rng_bank_inst *rng_inst, int timeout_secs, word32 flags); +WOLFSSL_API int wc_rng_bank_recover_inst( + struct wc_rng_bank *bank, + int inst_offset, + int timeout_secs, + word32 flags); + +#ifdef WC_RNG_HAVE_RBGC + +WOLFSSL_API int wc_rng_bank_spawn( + struct wc_rng_bank *bank, + WC_RNG *child_rng, + byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + int preferred_inst_offset, + int timeout_secs, + word32 flags); + +#ifndef WC_NO_CONSTRUCTORS +WOLFSSL_API int wc_rng_bank_spawn_new( + struct wc_rng_bank *bank, + WC_RNG **child_rng, + byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + int preferred_inst_offset, + int timeout_secs, + word32 flags); +#endif /* !WC_NO_CONSTRUCTORS */ + +#endif /* WC_RNG_HAVE_RBGC */ + +#ifdef HAVE_HASHDRBG + WOLFSSL_API int wc_rng_bank_seed(struct wc_rng_bank *bank, const byte* seed, word32 seedSz, + const byte *nonce, word32 nonceSz, int timeout_secs, word32 flags); +WOLFSSL_API int wc_rng_bank_seed_range(struct wc_rng_bank *bank, + int first_inst, int last_inst, + const byte* seed, word32 seedSz, + const byte *nonce, word32 nonceSz, + int timeout_secs, + word32 flags); + WOLFSSL_API int wc_rng_bank_reseed(struct wc_rng_bank *bank, + const byte *nonce, word32 nonceSz, int timeout_secs, word32 flags); -#if defined(WC_DRBG_BANKREF) && !defined(WC_HAVE_RNG_BANKREF) - /* forward compat for FIPS v5.2.4 random.h */ - #define WC_HAVE_RNG_BANKREF +WOLFSSL_API int wc_rng_bank_reseed_range(struct wc_rng_bank *bank, + int first_inst, int last_inst, + const byte *nonce, word32 nonceSz, + int timeout_secs, + word32 flags); + +WOLFSSL_API int wc_rng_bank_invalidate_entropy(struct wc_rng_bank *bank, + word32 flags); + +#endif /* HAVE_HASHDRBG */ + +#if defined(WC_RNG_HAVE_RBGC) || defined(WC_RNG_HAVE_NEXT_SEED) || \ + defined(WC_RNG_HAVE_POOL) +WOLFSSL_API int wc_rng_bank_root_rng_init(struct wc_rng_bank *bank, + const byte *nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + word32 flags); +WOLFSSL_API WC_RNG *wc_rng_bank_root_rng_get(struct wc_rng_bank *bank); #endif +WOLFSSL_API int wc_rng_bank_register_free_hook(struct wc_rng_bank *bank, + wc_rng_bank_free_hook_cb_t free_hook, void *arg); + #ifdef WC_HAVE_RNG_BANKREF WOLFSSL_API int wc_InitRng_BankRef(struct wc_rng_bank *bank, WC_RNG *rng); @@ -180,7 +383,133 @@ WOLFSSL_API int wc_rng_new_bankref(struct wc_rng_bank *bank, WC_RNG **rng); #endif #endif /* WC_HAVE_RNG_BANKREF */ -#define WC_RNG_BANK_INST_TO_RNG(rng_inst) (&(rng_inst)->rng) +#define WC_RNG_BANK_INST_TO_RNG(rng_inst) \ + ((rng_inst) ? (&(rng_inst)->rng) : NULL) +#define WC_RNG_BANK_OFFSET_TO_RNG(bank, n) \ + ((((int)(n) >= 0) && ((int)(n) < (int)(bank)->n_rngs)) ? \ + WC_RNG_BANK_INST_TO_RNG(&(bank)->rngs[n]) : NULL) + +#ifdef WC_RNG_HAVE_LOCK + /* Trivial shims to native lock facility in WC_RNG */ + static WC_INLINE int wc_rng_bank_inst_lock_get( + struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits) + { + return wc_RNG_lock_get(WC_RNG_BANK_INST_TO_RNG(inst), extra_bits); + } + static WC_INLINE int wc_rng_bank_inst_lock_get_conditional( + struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t expected_extra_bits, + WC_RNG_lock_arg_t want_extra_bits) + { + return wc_RNG_lock_get_conditional( + WC_RNG_BANK_INST_TO_RNG(inst), expected_extra_bits, want_extra_bits); + } + static WC_INLINE int wc_rng_bank_inst_lock_put( + struct wc_rng_bank_inst *inst) + { + return wc_RNG_lock_put(WC_RNG_BANK_INST_TO_RNG(inst), 0); + } + static WC_INLINE int wc_rng_bank_inst_lock_put_conditional( + struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t expect_extra_bits) + { + return wc_RNG_lock_put_conditional( + WC_RNG_BANK_INST_TO_RNG(inst), expect_extra_bits, 0); + } + static WC_INLINE int wc_rng_bank_inst_lock_read( + struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t *state) + { + return wc_RNG_lock_read(WC_RNG_BANK_INST_TO_RNG(inst), state); + } + static WC_INLINE int wc_rng_bank_inst_lock_set_extra( + struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits) + { + return wc_RNG_lock_set_extra(WC_RNG_BANK_INST_TO_RNG(inst), extra_bits); + } + static WC_INLINE int wc_rng_bank_inst_lock_add_extra( + struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits) + { + return wc_RNG_lock_add_extra(WC_RNG_BANK_INST_TO_RNG(inst), extra_bits); + } + static WC_INLINE int wc_rng_bank_inst_lock_clear_extra( + struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits) + { + return wc_RNG_lock_clear_extra( + WC_RNG_BANK_INST_TO_RNG(inst), extra_bits); + } + #ifdef HAVE_HASHDRBG + static WC_INLINE int wc_rng_bank_inst_invalidate_entropy( + struct wc_rng_bank_inst *inst) + { + return wc_RNG_invalidate_entropy(WC_RNG_BANK_INST_TO_RNG(inst)); + } + static WC_INLINE int wc_rng_bank_inst_reseed_now( + struct wc_rng_bank_inst *inst, const byte *nonce, word32 nonceSz) + { + return wc_RNG_DRBG_Reseed_Now( + WC_RNG_BANK_INST_TO_RNG(inst), nonce, nonceSz); + } + #ifdef WC_RNG_HAVE_RBGC + static WC_INLINE int wc_rng_bank_inst_reseed_rbgc( + struct wc_rng_bank_inst *inst, WC_RNG *root, + const byte *nonce, word32 nonceSz) + { + return wc_RNG_DRBG_ReseedRBGC( + WC_RNG_BANK_INST_TO_RNG(inst), root, nonce, nonceSz); + } + #endif /* WC_RNG_HAVE_RBGC */ + #endif /* HAVE_HASHDRBG */ +#else /* !WC_RNG_HAVE_LOCK */ + /* Prototypes for backward compat implementations in rng_bank.c */ + WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_get(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits); + WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_get_conditional( + struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t expected_extra_bits, + WC_RNG_lock_arg_t want_extra_bits); + WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_put(struct wc_rng_bank_inst *inst); + WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_put_conditional(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits); + WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_read(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t* state); + WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_set_extra(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits); + WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_add_extra(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits); + WOLFSSL_TEST_VIS int wc_rng_bank_inst_lock_clear_extra(struct wc_rng_bank_inst *inst, WC_RNG_lock_arg_t extra_bits); + #ifdef HAVE_HASHDRBG + WOLFSSL_TEST_VIS int wc_rng_bank_inst_invalidate_entropy( + struct wc_rng_bank_inst *inst); + WOLFSSL_TEST_VIS int wc_rng_bank_inst_reseed_now( + struct wc_rng_bank_inst *inst, const byte* nonce, word32 nonceSz); + #ifdef WC_RNG_HAVE_RBGC + WOLFSSL_TEST_VIS int wc_rng_bank_inst_reseed_rbgc( + struct wc_rng_bank_inst *inst, WC_RNG* root, const byte* nonce, + word32 nonceSz); + #endif /* WC_RNG_HAVE_RBGC */ + #endif /* HAVE_HASHDRBG */ +#endif /* !WC_RNG_HAVE_LOCK */ + +#if defined(HAVE_FIPS) && FIPS_VERSION3_LT(7,0,0) + #ifndef WC_DRBG_RESEED_CTR_TYPE_DEFINED + #define WC_DRBG_RESEED_CTR_TYPE_DEFINED + #if defined(WORD64_AVAILABLE) && FIPS_VERSION3_GE(5,2,4) && \ + FIPS_VERSION3_NE(6,0,0) + typedef word64 wc_drbg_reseed_ctr_t; + #else + typedef word32 wc_drbg_reseed_ctr_t; + #endif + #endif + + #define wc_InitRngRBGC(leaf, root, flags) \ + wc_InitRngNonceRBGC(leaf, root, NULL, 0, flags) + WOLFSSL_TEST_VIS int wc_RNG_GetStatus(const WC_RNG* rng); + WOLFSSL_TEST_VIS int wc_RNG_DRBG_Stir(WC_RNG* rng, const byte* seed, word32 seedSz); + WOLFSSL_TEST_VIS int wc_RNG_DRBG_Present(const WC_RNG* rng); + WOLFSSL_TEST_VIS int wc_InitRngNonceRBGC_New(WC_RNG** leaf, WC_RNG* root, + const byte* nonce, word32 nonceSz, + const byte *perso, word32 persoSz, + word32 flags); + WOLFSSL_TEST_VIS int wc_InitRngRBGC_New(WC_RNG** leaf, WC_RNG* root, word32 flags); + WOLFSSL_TEST_VIS int wc_RNG_DRBG_ScheduleReseed(WC_RNG* rng); +#endif /* HAVE_FIPS && FIPS_VERSION3_LT(7,0,0) */ + +#ifdef WC_RNG_DEBUG_STATS +WOLFSSL_API int wc_rng_bank_debug_stats_snap(struct wc_rng_debug_stats_snapshot *s, + struct wc_rng_bank *bank); +#endif #endif /* WC_RNG_BANK_SUPPORT */ diff --git a/wolfssl/wolfcrypt/rsa.h b/wolfssl/wolfcrypt/rsa.h index 0d0b0cecb88..3ec1e3f2d19 100644 --- a/wolfssl/wolfcrypt/rsa.h +++ b/wolfssl/wolfcrypt/rsa.h @@ -447,6 +447,7 @@ WOLFSSL_API int wc_RsaPublicKeyDecodeRaw(const byte* n, word32 nSz, #ifndef WC_NO_RNG WOLFSSL_API int wc_RsaSetRNG(RsaKey* key, WC_RNG* rng); + WOLFSSL_API int wc_RsaClearRNG(RsaKey* key); #endif #ifdef WC_RSA_NONBLOCK WOLFSSL_API int wc_RsaSetNonBlock(RsaKey* key, RsaNb* nb); diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 870c2d856e5..ffdbb4c2546 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -1527,7 +1527,7 @@ /* user needs to define XTIME to function that provides * seconds since Unix epoch */ #ifndef XTIME - #error XTIME must be defined in wolfSSL settings.h + #error XTIME must be defined in wolfSSL user_settings.h /* #define XTIME fnSecondsSinceEpoch */ #endif @@ -4640,15 +4640,20 @@ #endif #ifndef WC_RESEED_INTERVAL - /* In kernel mode, use the maximum reseed interval allowed by + /* In kernel mode, use the maximum mandatory reseed threshold allowed by * NIST SP 800-90A Rev. 1, to avoid unnecessary delays in DRBG * generation. */ #if defined(HAVE_FIPS) && \ FIPS_VERSION_LT(6,0) && FIPS_VERSION3_NE(5,2,4) #define WC_RESEED_INTERVAL UINT_MAX + #elif defined(WC_16BIT_CPU) || defined(WC_32BIT_CPU) || defined(NO_64BIT) + #define WC_RESEED_INTERVAL UINT_MAX + #elif defined(__x86_64__) || defined(__ia64__) || \ + defined(__aarch64__) || defined(__mips64) + #define WC_RESEED_INTERVAL (W64LIT(1) << 48) #else - #define WC_RESEED_INTERVAL (((word64)1UL)<<48UL) + #define WC_RESEED_INTERVAL UINT_MAX #endif #endif @@ -4656,6 +4661,11 @@ #define WC_VERBOSE_RNG #endif + #if defined(WC_VERBOSE_RNG) && defined(WOLFSSL_KERNEL_VERBOSE_DEBUG) && \ + !defined(WC_RNG_NO_DEBUG_STATS) && !defined(WC_RNG_WANT_DEBUG_STATS) + #define WC_RNG_WANT_DEBUG_STATS + #endif + #if WOLFSSL_GENERAL_ALIGNMENT < SIZEOF_LONG #undef WOLFSSL_GENERAL_ALIGNMENT #define WOLFSSL_GENERAL_ALIGNMENT SIZEOF_LONG @@ -5994,10 +6004,6 @@ blinding by defining WC_BLINDING_NO_RNG_ACKNOWLEDGE_WEAKNESS." #error "If TLS is enabled please make sure either client or server is enabled." #endif -#if defined(WC_RNG_BANK_SUPPORT) && defined(NO_ASN_TIME) - #undef WC_RNG_BANK_SUPPORT -#endif - /* The OCSP responder time-stamps every response it generates (producedAt, * thisUpdate and, for revoked certs, revocationDate), so it needs ASN time * support. */ diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 2ccaa95b98a..7b74e271c75 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -926,6 +926,11 @@ enum { #endif /* WOLFSSL_STATIC_MEMORY */ #endif +#if defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_STATIC_MEMORY) && \ + !defined(WC_NO_CONSTRUCTORS) + #define WC_NO_CONSTRUCTORS +#endif + #if defined(WOLFSSL_SMALL_STACK) && defined(WC_NO_CONSTRUCTORS) #error WOLFSSL_SMALL_STACK requires constructors. #endif diff --git a/wolfssl/wolfcrypt/wc_port.h b/wolfssl/wolfcrypt/wc_port.h index ca9e7823e67..c2b1f0ea6bf 100644 --- a/wolfssl/wolfcrypt/wc_port.h +++ b/wolfssl/wolfcrypt/wc_port.h @@ -620,8 +620,11 @@ #endif /* !WOLFSSL_NO_ATOMICS */ #ifdef WOLFSSL_NO_ATOMICS - typedef volatile int wolfSSL_Atomic_Int; - typedef volatile unsigned int wolfSSL_Atomic_Uint; + /* Note, not volatile. _NO_ATOMICS configs promise no concurrent mutation + * (single-threaded, or externally serialized); volatile would imply + * protection these types do not and cannot provide here. */ + typedef int wolfSSL_Atomic_Int; + typedef unsigned int wolfSSL_Atomic_Uint; #define WOLFSSL_ATOMIC_INITIALIZER(x) (x) #define WOLFSSL_ATOMIC_LOAD(x) (x) #define WOLFSSL_ATOMIC_STORE(x, val) (x) = (val) @@ -787,6 +790,88 @@ } #endif +/*** Macro abstractions for compare-and-exchange retry loops, allowing ***/ +/*** platform-specific instrumentation and failure paths. ***/ + +/* WC_CAS_WITH_RETRY_EXTRA_DECLS allows declaration and initialization of + * variables (e.g. a counter) just above the retry loop in + * WC_CAS_WITH_RETRY_BEGIN(). + */ +#ifndef WC_CAS_WITH_RETRY_EXTRA_DECLS + #define WC_CAS_WITH_RETRY_EXTRA_DECLS \ + struct wc_cas_with_retry_dummy_struct +#endif + +/* Note that freeform code after WC_CAS_WITH_RETRY_BEGIN() and before + * WC_CAS_WITH_RETRY_LOOP_UNTIL() sits inside the loop -- continue + * in that span omits the refresh of cur_var by the CAS (potentially inducing an + * infinite loop), and break in that span without setting result_var leaves it + * at WC_FAILURE. return and goto both behave normally in the freeform span. + */ +#define WC_CAS_WITH_RETRY_BEGIN(targetvar_p, cur_var, result_var) \ + do { \ + int WC_CAS_WITH_RETRY_keep_looping = 1; \ + WC_CAS_WITH_RETRY_EXTRA_DECLS; \ + \ + (result_var) = WC_NO_ERR_TRACE(WC_FAILURE); \ + \ + while (WC_CAS_WITH_RETRY_keep_looping) + +#define WC_CAS_WITH_RETRY_BEGIN_INIT_CUR(targetvar_p, cur_var, result_var) \ + do { \ + int WC_CAS_WITH_RETRY_keep_looping = 1; \ + WC_CAS_WITH_RETRY_EXTRA_DECLS; \ + \ + (cur_var) = WOLFSSL_ATOMIC_LOAD(*(targetvar_p)); \ + (result_var) = WC_NO_ERR_TRACE(WC_FAILURE); \ + \ + while (WC_CAS_WITH_RETRY_keep_looping) + +#ifndef WC_CAS_WITH_RETRY_ITER_CLAUSE + #define WC_CAS_WITH_RETRY_ITER_CLAUSE(targetvar_p, cur_var, \ + want_val, result_var) WC_DO_NOTHING +#endif + +/* The "until_clause" should be portable logic of overriding salience, used in + * situ by the direct (portable) user code. The _ITER_CLAUSE is for + * non-portable logic, such as CPU/scheduler relaxation/yield or deadlock + * detection, and is free to set result_var and break as it sees fit. + * + * Freeform code can appear between WC_CAS_WITH_RETRY_LOOP_UNTIL() and + * WC_CAS_WITH_RETRY_END(), and will be evaluated iff the CAS succeeds. + */ +#define WC_CAS_WITH_RETRY_LOOP_UNTIL(cmpxchg_method, targetvar_p, cur_var, \ + want_val, result_var, until_clause) \ + if (! cmpxchg_method(targetvar_p, &(cur_var), want_val)) { \ + (result_var) = (until_clause); \ + if ((result_var) != 0) \ + break; \ + { \ + WC_CAS_WITH_RETRY_ITER_CLAUSE(targetvar_p, cur_var, \ + want_val, result_var); \ + } \ + continue; \ + } \ + else { \ + (result_var) = 0; \ + WC_CAS_WITH_RETRY_keep_looping = 0; \ + } \ + WC_DO_NOTHING + +#ifndef WC_CAS_WITH_RETRY_FOREVER_CLAUSE + #define WC_CAS_WITH_RETRY_FOREVER_CLAUSE 0 +#endif + +#define WC_CAS_WITH_RETRY_LOOP_FOREVER(cmpxchg_method, targetvar_p, \ + cur_var, want_val, result_var) \ + WC_CAS_WITH_RETRY_LOOP_UNTIL(cmpxchg_method, targetvar_p, cur_var, \ + want_val, result_var, \ + WC_CAS_WITH_RETRY_FOREVER_CLAUSE) + +/* Note, WC_CAS_WITH_RETRY_END() has no side effects -- it's just the success + * arm that ends the loop. */ +#define WC_CAS_WITH_RETRY_END } while (0) + /* Reference counting. */ typedef struct wolfSSL_RefWithMutex { #if !defined(SINGLE_THREADED) @@ -1582,11 +1667,18 @@ WOLFSSL_ABI WOLFSSL_API int wolfCrypt_Cleanup(void); #endif #elif defined(TIME_OVERRIDES) - /* Override XTIME() and XGMTIME() functionality. - Requires user to provide these functions: - time_t XTIME(time_t * timer) {} - struct tm* XGMTIME(const time_t* timer, struct tm* tmp) {} - */ + /* User-supplied override XTIME() and XGMTIME() functionality. + * + * Requires user-supplied macro definitions for XTIME() and XGMTIME(), + * mapping to function with signatures time_t time_f(time_t * timer) and + * struct tm* gmtime_f(const time_t* timer, struct tm* tmp) respectively. + */ + #ifndef XTIME + #error TIME_OVERRIDES requires a user-supplied XTIME definition. + #endif + #ifndef XGMTIME + #error TIME_OVERRIDES requires a user-supplied XGMTIME definition. + #endif #ifndef HAVE_TIME_T_TYPE #define USE_WOLF_TIME_T #endif