Commit c5f9e88fa6 for openssl.org

commit c5f9e88fa6442992722bcae365d730db62d1ec0a
Author: Simo Sorce <simo@redhat.com>
Date:   Mon Dec 1 16:36:40 2025 -0500

    Add support for deferred FIPS self-tests

    Add a new -defer_tests option to openssl fipsinstall and a corresponding
    defer-tests configuration parameter for the FIPS provider.

    This allows the execution of self-tests to be postponed until the
    first time an algorithm is used, instead of running all tests
    during module initialization. This reduces startup time.

    Update the self-test framework to handle the new SELF_TEST_STATE_DEFER
    state, ensuring deferred tests are skipped at load and run on demand.

    Signed-off-by: Simo Sorce <simo@redhat.com>

    Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
    Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
    (Merged from https://github.com/openssl/openssl/pull/29222)

diff --git a/CHANGES.md b/CHANGES.md
index 48a378d618..98e78e5d10 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -32,6 +32,11 @@ OpenSSL 4.0

 ### Changes between 3.6 and 4.0 [xx XXX xxxx]

+ * FIPS self tests can now be deferred and run as needed when installing
+   the fips module with the -defer_tests option.
+
+   *Simo Sorce*
+
  * OPENSSL_cleanup() now runs in a global destructor, or not at all by default.

    OpenSSL_cleanup() will no longer by default free global objects when run from
diff --git a/apps/fipsinstall.c b/apps/fipsinstall.c
index ea54a00cff..1341a3f826 100644
--- a/apps/fipsinstall.c
+++ b/apps/fipsinstall.c
@@ -77,7 +77,8 @@ typedef enum OPTION_choice {
     OPT_NO_PBKDF2_LOWER_BOUND_CHECK,
     OPT_ECDH_COFACTOR_CHECK,
     OPT_SELF_TEST_ONLOAD,
-    OPT_SELF_TEST_ONINSTALL
+    OPT_SELF_TEST_ONINSTALL,
+    OPT_DEFER_TESTS
 } OPTION_CHOICE;

 const OPTIONS fipsinstall_options[] = {
@@ -150,6 +151,7 @@ const OPTIONS fipsinstall_options[] = {
         "Disable lower bound check for PBKDF2" },
     { "ecdh_cofactor_check", OPT_ECDH_COFACTOR_CHECK, '-',
         "Enable Cofactor check for ECDH" },
+    { "defer_tests", OPT_DEFER_TESTS, '-', "Enables test deferral" },
     OPT_SECTION("Input"),
     { "in", OPT_IN, '<', "Input config file, used when verifying" },

@@ -197,6 +199,7 @@ typedef struct {
     unsigned int x942kdf_key_check : 1;
     unsigned int pbkdf2_lower_bound_check : 1;
     unsigned int ecdh_cofactor_check : 1;
+    unsigned int defer_tests : 1;
 } FIPS_OPTS;

 /* Pedantic FIPS compliance */
@@ -231,6 +234,7 @@ static const FIPS_OPTS pedantic_opts = {
     1, /* x942kdf_key_check */
     1, /* pbkdf2_lower_bound_check */
     1, /* ecdh_cofactor_check */
+    0, /* defer_tests */
 };

 /* Default FIPS settings for backward compatibility */
@@ -265,6 +269,7 @@ static FIPS_OPTS fips_opts = {
     0, /* x942kdf_key_check */
     1, /* pbkdf2_lower_bound_check */
     0, /* ecdh_cofactor_check */
+    0, /* defer_tests */
 };

 static int check_non_pedantic_fips(int pedantic, const char *name)
@@ -488,7 +493,10 @@ static int write_config_fips_section(BIO *out, const char *section,
                opts->ecdh_cofactor_check ? "1" : "0")
             <= 0
         || !print_mac(out, OSSL_PROV_FIPS_PARAM_MODULE_MAC, module_mac,
-            module_mac_len))
+            module_mac_len)
+        || BIO_printf(out, "%s = %s\n", OSSL_PROV_FIPS_PARAM_DEFER_TESTS,
+               opts->defer_tests ? "1" : "0")
+            <= 0)
         goto end;

     if (install_mac != NULL
@@ -802,6 +810,9 @@ int fipsinstall_main(int argc, char **argv)
             set_selftest_onload_option = 1;
             fips_opts.self_test_onload = 0;
             break;
+        case OPT_DEFER_TESTS:
+            fips_opts.defer_tests = 1;
+            break;
         }
     }

diff --git a/doc/man1/openssl-fipsinstall.pod.in b/doc/man1/openssl-fipsinstall.pod.in
index 2db5acd242..a66945952a 100644
--- a/doc/man1/openssl-fipsinstall.pod.in
+++ b/doc/man1/openssl-fipsinstall.pod.in
@@ -54,6 +54,7 @@ B<openssl fipsinstall>
 [B<-corrupt_desc> I<selftest_description>]
 [B<-corrupt_type> I<selftest_type>]
 [B<-config> I<parent_config>]
+[B<-defer_tests>]

 =head1 DESCRIPTION

@@ -396,6 +397,12 @@ data that is included by the base C<parent_config> configuration file.
 See L<config(5)> for further information on how to set up a provider section.
 All other options are ignored if '-config' is used.

+=item B<-defer_tests>
+
+Configure the module to not run all self-tests at startup and allow tests
+execution to be deferred to the first time the algorithm to be tested is
+invoked.
+
 =back

 =head1 NOTES
@@ -487,6 +494,10 @@ B<-x963kdf_key_check>,
 B<-x942kdf_key_check>,
 B<-ecdh_cofactor_check>

+The following options was added in OpenSSL 4.0:
+
+B<-defer_tests>
+
 =head1 COPYRIGHT

 Copyright 2019-2025 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man5/fips_config.pod b/doc/man5/fips_config.pod
index c3f7b8f3ab..786e567b39 100644
--- a/doc/man5/fips_config.pod
+++ b/doc/man5/fips_config.pod
@@ -52,6 +52,13 @@ Regardless of the value, the operation (e.g., key generation) that called the
 continuous test will return an error code if its continuous test fails. The
 operation may then be retried if the error mode has not been triggered.

+=item B<defer-tests>
+
+If set to C<1>, the module will not run the self tests during initialization.
+Instead, the self tests will be run on demand when a particular algorithm is
+used.
+The default value of C<0> runs the self tests during initialization.
+
 =item B<module-mac>

 The calculated MAC of the FIPS provider file.
diff --git a/include/openssl/fips_names.h b/include/openssl/fips_names.h
index 3e310bb4a8..ef84955a9f 100644
--- a/include/openssl/fips_names.h
+++ b/include/openssl/fips_names.h
@@ -38,6 +38,12 @@ extern "C" {
  */
 #define OSSL_PROV_FIPS_PARAM_CONDITIONAL_ERRORS "conditional-errors"

+/*
+ * A boolean that determines if all the FIPS conditional self-test are executed
+ * at module startup or deferred and run only when an algorithm is invoked
+ */
+#define OSSL_PROV_FIPS_PARAM_DEFER_TESTS "defer-tests"
+
 /* The following are provided for backwards compatibility */
 #define OSSL_PROV_FIPS_PARAM_SECURITY_CHECKS OSSL_PROV_PARAM_SECURITY_CHECKS
 #define OSSL_PROV_FIPS_PARAM_TLS1_PRF_EMS_CHECK OSSL_PROV_PARAM_TLS1_PRF_EMS_CHECK
diff --git a/providers/fips/fipsprov.c b/providers/fips/fipsprov.c
index 1a50d45926..768e6aedc5 100644
--- a/providers/fips/fipsprov.c
+++ b/providers/fips/fipsprov.c
@@ -157,7 +157,7 @@ static int fips_random_bytes(ossl_unused void *vprov, int which,
  */
 static int fips_get_params_from_core(FIPS_GLOBAL *fgbl)
 {
-    OSSL_PARAM core_params[32], *p = core_params;
+    OSSL_PARAM core_params[33], *p = core_params;

 #define OSSL_FIPS_PARAM(structname, paramname)                 \
     *p++ = OSSL_PARAM_construct_utf8_ptr(                      \
@@ -1326,7 +1326,7 @@ static int FIPS_kat_deferred(OSSL_LIB_CTX *libctx, self_test_id_t id)
          * record this test as invoked by the original test, for marking
          * it later as also satisfied
          */
-        if (st_all_tests[id].state == SELF_TEST_STATE_INIT)
+        if (st_all_tests[id].state == SELF_TEST_STATE_DEFER)
             st_all_tests[id].state = SELF_TEST_STATE_IMPLICIT;
         /*
          * A self test is in progress for this thread so we let this
@@ -1347,7 +1347,7 @@ static int FIPS_kat_deferred(OSSL_LIB_CTX *libctx, self_test_id_t id)
          * test and marked it as passed
          */
         switch (st_all_tests[id].state) {
-        case SELF_TEST_STATE_INIT:
+        case SELF_TEST_STATE_DEFER:
             break;
         case SELF_TEST_STATE_PASSED:
             ret = 1;
@@ -1438,7 +1438,7 @@ int ossl_deferred_self_test(OSSL_LIB_CTX *libctx, self_test_id_t id)
      * Immediately mark it and return.
      */
     if (ossl_fips_self_testing()) {
-        if (st_all_tests[id].state == SELF_TEST_STATE_INIT)
+        if (st_all_tests[id].state == SELF_TEST_STATE_DEFER)
             st_all_tests[id].state = SELF_TEST_STATE_IMPLICIT;
         return 1;
     }
diff --git a/providers/fips/include/fips_selftest_params.inc b/providers/fips/include/fips_selftest_params.inc
index df942d9cea..170ebc4c71 100644
--- a/providers/fips/include/fips_selftest_params.inc
+++ b/providers/fips/include/fips_selftest_params.inc
@@ -1,3 +1,4 @@
 OSSL_FIPS_PARAM(module_filename, OSSL_PROV_PARAM_CORE_MODULE_FILENAME)
 OSSL_FIPS_PARAM(module_checksum_data, OSSL_PROV_FIPS_PARAM_MODULE_MAC)
 OSSL_FIPS_PARAM(conditional_error_check, OSSL_PROV_FIPS_PARAM_CONDITIONAL_ERRORS)
+OSSL_FIPS_PARAM(defer_tests, OSSL_PROV_FIPS_PARAM_DEFER_TESTS)
diff --git a/providers/fips/self_test.c b/providers/fips/self_test.c
index 2630129795..58d5936d7e 100644
--- a/providers/fips/self_test.c
+++ b/providers/fips/self_test.c
@@ -325,12 +325,24 @@ int SELF_TEST_post(SELF_TEST_POST_PARAMS *st, int on_demand_test)
         goto end;
     }

-    if (on_demand_test)
-        /* ensure all states are cleared so all tests are repeated */
-        for (int i = 0; i < ST_ID_MAX; i++)
+    if ((st->defer_tests != NULL)
+        && strcmp(st->defer_tests, "1") == 0) {
+        /* Mark all non executed tests as deferred */
+        for (int i = 0; i < ST_ID_MAX; i++) {
+            if (st_all_tests[i].state == SELF_TEST_STATE_INIT)
+                st_all_tests[i].state = SELF_TEST_STATE_DEFER;
+        }
+    }
+
+    if (on_demand_test) {
+        /* ensure all states are cleared so all tests are forcibly
+         * repeated */
+        for (int i = 0; i < ST_ID_MAX; i++) {
             st_all_tests[i].state = SELF_TEST_STATE_INIT;
+        }
+    }

-    if (on_demand_test && !SELF_TEST_kats(ev, st->libctx)) {
+    if (!SELF_TEST_kats(ev, st->libctx)) {
         ERR_raise(ERR_LIB_PROV, PROV_R_SELF_TEST_KAT_FAILURE);
         goto end;
     }
diff --git a/providers/fips/self_test.h b/providers/fips/self_test.h
index f9b6a2531a..387c5fb0f9 100644
--- a/providers/fips/self_test.h
+++ b/providers/fips/self_test.h
@@ -20,6 +20,9 @@ typedef struct self_test_post_params_st {
     /* Used for continuous tests */
     const char *conditional_error_check;

+    /* Used to decide whether to defer tests or not */
+    const char *defer_tests;
+
     /* BIO callbacks supplied to the FIPS provider */
     OSSL_FUNC_BIO_new_file_fn *bio_new_file_cb;
     OSSL_FUNC_BIO_new_membuf_fn *bio_new_buffer_cb;
@@ -54,11 +57,12 @@ enum st_test_category {
 };

 enum st_test_state {
-    SELF_TEST_STATE_INIT = 0,
-    SELF_TEST_STATE_IN_PROGRESS,
-    SELF_TEST_STATE_PASSED,
-    SELF_TEST_STATE_FAILED,
-    SELF_TEST_STATE_IMPLICIT,
+    SELF_TEST_STATE_INIT = 0, /* Test has not been execute yet */
+    SELF_TEST_STATE_IN_PROGRESS, /* Test is currently being executed */
+    SELF_TEST_STATE_PASSED, /* Test is marked as passed */
+    SELF_TEST_STATE_FAILED, /* Test failed */
+    SELF_TEST_STATE_IMPLICIT, /* Marks test as implicitly handled */
+    SELF_TEST_STATE_DEFER, /* Like INIT, but mark test as deferred */
 };

 /* used to store raw parameters for keys and algorithms */
diff --git a/providers/fips/self_test_kats.c b/providers/fips/self_test_kats.c
index fd6363a315..f017bc47bf 100644
--- a/providers/fips/self_test_kats.c
+++ b/providers/fips/self_test_kats.c
@@ -1175,6 +1175,7 @@ int SELF_TEST_kats_execute(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx,
      */
     switch (st_all_tests[id].state) {
     case SELF_TEST_STATE_INIT:
+    case SELF_TEST_STATE_DEFER:
         break;
     case SELF_TEST_STATE_FAILED:
         return 0;
@@ -1277,8 +1278,9 @@ int SELF_TEST_kats(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx)
     }

     for (i = 0; i < ST_ID_MAX; i++)
-        if (!SELF_TEST_kats_execute(st, libctx, i, 0))
-            ret = 0;
+        if (st_all_tests[i].state == SELF_TEST_STATE_INIT)
+            if (!SELF_TEST_kats_execute(st, libctx, i, 0))
+                ret = 0;

     RAND_set0_private(libctx, saved_rand);
     /* The above call will cause main_rand to be freed */
diff --git a/test/recipes/00-prep_fipsmodule_cnf.t b/test/recipes/00-prep_fipsmodule_cnf.t
old mode 100644
new mode 100755
index 4e3a6d85e8..e95900941e
--- a/test/recipes/00-prep_fipsmodule_cnf.t
+++ b/test/recipes/00-prep_fipsmodule_cnf.t
@@ -30,7 +30,7 @@ my $fipsmoduleconf = bldtop_file('test', 'fipsmodule.cnf');
 plan tests => 1;

 # Create the $fipsmoduleconf file
-ok(run(app(['openssl', 'fipsinstall', '-pedantic',
+ok(run(app(['openssl', 'fipsinstall', '-pedantic', '-defer_tests',
             '-module', $fipsmodule, '-provider_name', 'fips',
             '-section_name', 'fips_sect', '-out', $fipsmoduleconf])),
    "fips install");