Commit 35a45420db for openssl.org

commit 35a45420dbdf4a435d0014ac64f13d11d2b4b229
Author: Nikola Pajkovsky <nikolap@openssl.org>
Date:   Tue Nov 10 12:00:21 2026 +0100

    crypto/lhash/lhash.c: fix NULL thunk dereference in OPENSSL_LH_doall*()

    doall_util_fn() called the per-table thunk pointers lh->daw and lh->daaw
    unconditionally.  Those thunks are only installed by lh_TYPE_new()
    through OPENSSL_LH_set_thunks(), so iterating a non-empty table created
    with the raw OPENSSL_LH_new() API dereferenced a NULL function pointer
    and crashed.  This is a regression since 3.3.0, where the thunks were
    introduced.

    Fall back to calling the caller supplied function directly
    when no thunk is present, matching the existing fallback for the hash
    and compare functions in getrn().  Tables from lh_TYPE_new()
    are unaffected because their thunks are never NULL.

    Add a regression test that inserts entries into a raw OPENSSL_LH_new()
    table and iterates it with both OPENSSL_LH_doall()
    and OPENSSL_LH_doall_arg(), checking that every element is visited
    exactly once.

    Resolves: https://github.com/openssl/openssl/issues/33496
    Fixes: 5c42ced0ff97 "Introduce hash thunking functions to do proper casting"
    Assisted-by: Pi:DeepSeek-V4.1-Flash-1017
    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Reviewed-by: Andrew Dinh <andrewd@openssl.org>
    Reviewed-by: Bob Beck <beck@openssl.org>
    Merge-date: Sat Nov 14 04:26:02 2026
    Merged-from: https://github.com/openssl/openssl/pull/33636

diff --git a/crypto/lhash/lhash.c b/crypto/lhash/lhash.c
index ad8c7a6280..94632626c3 100644
--- a/crypto/lhash/lhash.c
+++ b/crypto/lhash/lhash.c
@@ -203,10 +203,22 @@ static void doall_util_fn(OPENSSL_LHASH *lh, int use_arg,
         a = lh->b[i];
         while (a != NULL) {
             n = a->next;
-            if (use_arg)
-                wfunc_arg(a->data, arg, func_arg);
-            else
-                wfunc(a->data, func);
+            /*
+             * Tables created with OPENSSL_LH_new() have no thunks, so fall
+             * back to calling the caller supplied function directly, as
+             * getrn() does for the hash and compare functions.
+             */
+            if (use_arg) {
+                if (wfunc_arg != NULL)
+                    wfunc_arg(a->data, arg, func_arg);
+                else
+                    func_arg(a->data, arg);
+            } else {
+                if (wfunc != NULL)
+                    wfunc(a->data, func);
+                else
+                    func(a->data);
+            }
             a = n;
         }
     }
diff --git a/test/lhash_test.c b/test/lhash_test.c
index ed41f4c78f..11b0ea7fb8 100644
--- a/test/lhash_test.c
+++ b/test/lhash_test.c
@@ -185,6 +185,83 @@ end:
     return testresult;
 }

+/*
+ * A table created with the raw OPENSSL_LH_new() API has no thunk functions
+ * installed (those are only set up by the type-specific lh_TYPE_new() macro),
+ * so OPENSSL_LH_doall() and OPENSSL_LH_doall_arg() must fall back to calling
+ * the caller supplied function directly instead of dereferencing the NULL
+ * thunk pointers.
+ */
+static unsigned long raw_int_hash(const void *p)
+{
+    return int_hash(p);
+}
+
+static int raw_int_cmp(const void *p, const void *q)
+{
+    return int_cmp(p, q);
+}
+
+static void raw_int_doall(void *v)
+{
+    int_doall(v);
+}
+
+static void raw_int_doall_arg(void *p, void *arg)
+{
+    int_doall_arg(p, arg);
+}
+
+static int test_raw_lhash_doall(void)
+{
+    OPENSSL_LHASH *h = OPENSSL_LH_new(&raw_int_hash, &raw_int_cmp);
+    size_t i;
+    int testresult = 0;
+
+    if (!TEST_ptr(h))
+        goto end;
+
+    for (i = 0; i < n_int_tests; i++)
+        if (!TEST_ptr_null(OPENSSL_LH_insert(h, int_tests + i))) {
+            TEST_info("raw int insert %zu", i);
+            goto end;
+        }
+
+    if (!TEST_size_t_eq((size_t)OPENSSL_LH_num_items(h), n_int_tests))
+        goto end;
+
+    memset(int_found, 0, sizeof(int_found));
+    int_not_found = 0;
+    OPENSSL_LH_doall(h, &raw_int_doall);
+    if (!TEST_int_eq(int_not_found, 0)) {
+        TEST_info("raw lhash doall encountered a not found condition");
+        goto end;
+    }
+    for (i = 0; i < n_int_tests; i++)
+        if (!TEST_int_eq(int_found[i], 1)) {
+            TEST_info("raw lhash doall %zu", i);
+            goto end;
+        }
+
+    memset(int_found, 0, sizeof(int_found));
+    int_not_found = 0;
+    OPENSSL_LH_doall_arg(h, &raw_int_doall_arg, int_found);
+    if (!TEST_int_eq(int_not_found, 0)) {
+        TEST_info("raw lhash doall arg encountered a not found condition");
+        goto end;
+    }
+    for (i = 0; i < n_int_tests; i++)
+        if (!TEST_int_eq(int_found[i], 1)) {
+            TEST_info("raw lhash doall arg %zu", i);
+            goto end;
+        }
+
+    testresult = 1;
+end:
+    OPENSSL_LH_free(h);
+    return testresult;
+}
+
 static int int_filter_all(HT_VALUE *v, void *arg)
 {
     return 1;
@@ -968,6 +1045,7 @@ end:
 int setup_tests(void)
 {
     ADD_TEST(test_int_lhash);
+    ADD_TEST(test_raw_lhash_doall);
     ADD_TEST(test_stress);
     ADD_ALL_TESTS(test_int_hashtable, 2);
     ADD_ALL_TESTS(test_hashtable_collisions, 2);