Commit a1e4bd14e5 for openssl.org

commit a1e4bd14e59e62943fe048b944eac894cd0d8489
Author: Nikola Pajkovsky <nikolap@openssl.org>
Date:   Wed May 20 08:51:07 2026 +0200

    preserve stack thunks across sk_TYPE_dup

    ensure typed stack duplicates keep their compare, copy, and free thunks so
    later operations do not call typed callbacks through erased void * function
    pointer signatures.

    Signed-off-by: Nikola Pajkovsky <nikolap@openssl.org>

    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Milan Broz <mbroz@openssl.org>
    MergeDate: Thu Jun 25 12:12:48 2026
    (Merged from https://github.com/openssl/openssl/pull/31523)

diff --git a/include/openssl/safestack.h.in b/include/openssl/safestack.h.in
index a4c178d189..64ac28be04 100644
--- a/include/openssl/safestack.h.in
+++ b/include/openssl/safestack.h.in
@@ -222,15 +222,27 @@ extern "C" {
     }                                                                                                                      \
     static ossl_unused ossl_inline STACK_OF(t1) *sk_##t1##_dup(const STACK_OF(t1) *sk)                                     \
     {                                                                                                                      \
-        return (STACK_OF(t1) *)OPENSSL_sk_dup((const OPENSSL_STACK *)sk);                                                  \
+        OPENSSL_STACK *ret = OPENSSL_sk_dup((const OPENSSL_STACK *)sk);                                                    \
+        OPENSSL_sk_freefunc_thunk f_thunk;                                                                                 \
+                                                                                                                           \
+        f_thunk = (OPENSSL_sk_freefunc_thunk)sk_##t1##_freefunc_thunk;                                                     \
+        OPENSSL_sk_set_cmp_thunks(ret, sk_##t1##_cmpfunc_thunk);                                                           \
+        OPENSSL_sk_set_copy_thunks(ret, sk_##t1##_copyfunc_thunk);                                                         \
+        return (STACK_OF(t1) *)OPENSSL_sk_set_thunks(ret, f_thunk);                                                        \
     }                                                                                                                      \
     static ossl_unused ossl_inline STACK_OF(t1) *sk_##t1##_deep_copy(const STACK_OF(t1) *sk,                               \
         sk_##t1##_copyfunc copyfunc,                                                                                       \
         sk_##t1##_freefunc freefunc)                                                                                       \
     {                                                                                                                      \
-        return (STACK_OF(t1) *)OPENSSL_sk_deep_copy((const OPENSSL_STACK *)sk,                                             \
+        OPENSSL_STACK *ret = OPENSSL_sk_deep_copy((const OPENSSL_STACK *)sk,                                               \
             (OPENSSL_sk_copyfunc)copyfunc,                                                                                 \
             (OPENSSL_sk_freefunc)freefunc);                                                                                \
+        OPENSSL_sk_freefunc_thunk f_thunk;                                                                                 \
+                                                                                                                           \
+        f_thunk = (OPENSSL_sk_freefunc_thunk)sk_##t1##_freefunc_thunk;                                                     \
+        OPENSSL_sk_set_cmp_thunks(ret, sk_##t1##_cmpfunc_thunk);                                                           \
+        OPENSSL_sk_set_copy_thunks(ret, sk_##t1##_copyfunc_thunk);                                                         \
+        return (STACK_OF(t1) *)OPENSSL_sk_set_thunks(ret, f_thunk);                                                        \
     }                                                                                                                      \
     static ossl_unused ossl_inline sk_##t1##_compfunc sk_##t1##_set_cmp_func(STACK_OF(t1) *sk, sk_##t1##_compfunc compare) \
     {                                                                                                                      \
diff --git a/test/stack_test.c b/test/stack_test.c
index 848c843931..16d72f809e 100644
--- a/test/stack_test.c
+++ b/test/stack_test.c
@@ -344,11 +344,35 @@ static char *string_copy(const char *p)
     return OPENSSL_strdup(p);
 }

+static int string_cmp(const char *const *a, const char *const *b)
+{
+    return strcmp(*a, *b);
+}
+
 static void string_free(char *p)
 {
     OPENSSL_free(p);
 }

+static int push_string(STACK_OF(OPENSSL_STRING) *s, const char *str)
+{
+    char *p = OPENSSL_strdup(str);
+    int expected = sk_OPENSSL_STRING_num(s) + 1;
+    int pushed;
+
+    if (!TEST_ptr(p))
+        return 0;
+
+    pushed = sk_OPENSSL_STRING_push(s, p);
+    if (!TEST_int_eq(pushed, expected)) {
+        if (pushed <= 0)
+            OPENSSL_free(p);
+        return 0;
+    }
+
+    return 1;
+}
+
 static int test_SS_stack(void)
 {
     STACK_OF(TST_SS) *s = sk_TST_SS_new_null();
@@ -508,11 +532,88 @@ end:
     return testresult;
 }

+static int test_STRING_STACK_stack_ubsan(int idx)
+{
+    STACK_OF(OPENSSL_STRING) *s = NULL;
+    STACK_OF(OPENSSL_STRING) *t = NULL;
+    STACK_OF(OPENSSL_STRING) *u = NULL;
+    int testresult = 0;
+
+    switch (idx) {
+    case 0:
+        s = sk_OPENSSL_STRING_new_null();
+        break;
+    case 1:
+        s = sk_OPENSSL_STRING_new(string_cmp);
+        break;
+    case 2:
+        s = sk_OPENSSL_STRING_new_reserve(string_cmp, 2);
+        break;
+    case 3:
+        s = sk_OPENSSL_STRING_deep_copy(NULL, string_copy, string_free);
+        break;
+    case 4:
+        t = sk_OPENSSL_STRING_new(string_cmp);
+        if (!TEST_ptr(t))
+            goto end;
+        if (!push_string(t, "b")
+            || !push_string(t, "a"))
+            goto end;
+        s = sk_OPENSSL_STRING_dup(t);
+        if (s != NULL) {
+            sk_OPENSSL_STRING_free(t);
+            t = NULL;
+        }
+        break;
+    case 5:
+        t = sk_OPENSSL_STRING_deep_copy(NULL, string_copy, string_free);
+        if (!TEST_ptr(t))
+            goto end;
+        if (!push_string(t, "b")
+            || !push_string(t, "a"))
+            goto end;
+        s = sk_OPENSSL_STRING_deep_copy(t, string_copy, string_free);
+        break;
+    default:
+        goto end;
+    }
+    if (!TEST_ptr(s))
+        goto end;
+
+    if (idx < 4) {
+        if (!push_string(s, "b")
+            || !push_string(s, "a"))
+            goto end;
+    }
+
+    sk_OPENSSL_STRING_set_cmp_func(s, string_cmp);
+    sk_OPENSSL_STRING_sort(s);
+    if (!TEST_str_eq(sk_OPENSSL_STRING_value(s, 0), "a")
+        || !TEST_int_eq(sk_OPENSSL_STRING_find(s, "b"), 1))
+        goto end;
+
+    u = sk_OPENSSL_STRING_deep_copy(s, string_copy, string_free);
+    if (!TEST_ptr(u)
+        || !TEST_int_eq(sk_OPENSSL_STRING_num(u), 2)
+        || !TEST_ptr_ne(sk_OPENSSL_STRING_value(u, 0),
+            sk_OPENSSL_STRING_value(s, 0))
+        || !TEST_str_eq(sk_OPENSSL_STRING_value(u, 0), "a"))
+        goto end;
+
+    testresult = 1;
+end:
+    sk_OPENSSL_STRING_pop_free(u, string_free);
+    sk_OPENSSL_STRING_pop_free(s, string_free);
+    sk_OPENSSL_STRING_pop_free(t, string_free);
+    return testresult;
+}
+
 int setup_tests(void)
 {
     ADD_ALL_TESTS(test_int_stack, 4);
     ADD_ALL_TESTS(test_uchar_stack, 4);
     ADD_TEST(test_SS_stack);
+    ADD_ALL_TESTS(test_STRING_STACK_stack_ubsan, 6);
     ADD_TEST(test_SU_stack);
     ADD_MFAIL_TEST(test_OPENSSL_STRING_deep_copy_mfail);
     return 1;
diff --git a/util/perl/OpenSSL/stackhash.pm b/util/perl/OpenSSL/stackhash.pm
index e49e2d4def..b3446f7747 100644
--- a/util/perl/OpenSSL/stackhash.pm
+++ b/util/perl/OpenSSL/stackhash.pm
@@ -69,12 +69,25 @@ SKM_DEFINE_STACK_OF_INTERNAL(${nametype}, ${realtype}, ${plaintype})
 #define sk_${nametype}_find_all(sk, ptr, pnum) OPENSSL_sk_find_all(ossl_check_${nametype}_sk_type(sk), ossl_check_${nametype}_type(ptr), pnum)
 #define sk_${nametype}_sort(sk) OPENSSL_sk_sort(ossl_check_${nametype}_sk_type(sk))
 #define sk_${nametype}_is_sorted(sk) OPENSSL_sk_is_sorted(ossl_check_const_${nametype}_sk_type(sk))
-#define sk_${nametype}_dup(sk) ((STACK_OF(${nametype}) *)OPENSSL_sk_dup(ossl_check_const_${nametype}_sk_type(sk)))
+#define sk_${nametype}_dup(sk) \\
+    ((STACK_OF(${nametype}) *)OPENSSL_sk_set_thunks( \\
+        OPENSSL_sk_set_copy_thunks( \\
+            OPENSSL_sk_set_cmp_thunks( \\
+                OPENSSL_sk_dup(ossl_check_const_${nametype}_sk_type(sk)), \\
+                sk_${nametype}_cmpfunc_thunk), \\
+            sk_${nametype}_copyfunc_thunk), \\
+        sk_${nametype}_freefunc_thunk))
 #define sk_${nametype}_deep_copy(sk, copyfunc, freefunc) \\
-    ((STACK_OF(${nametype}) *)OPENSSL_sk_deep_copy( \\
-        ossl_check_const_${nametype}_sk_type(sk), \\
-        ossl_check_${nametype}_copyfunc_type(copyfunc), \\
-        ossl_check_${nametype}_freefunc_type(freefunc)))
+    ((STACK_OF(${nametype}) *)OPENSSL_sk_set_thunks( \\
+        OPENSSL_sk_set_copy_thunks( \\
+            OPENSSL_sk_set_cmp_thunks( \\
+                OPENSSL_sk_deep_copy( \\
+                    ossl_check_const_${nametype}_sk_type(sk), \\
+                    ossl_check_${nametype}_copyfunc_type(copyfunc), \\
+                    ossl_check_${nametype}_freefunc_type(freefunc)), \\
+                sk_${nametype}_cmpfunc_thunk), \\
+            sk_${nametype}_copyfunc_thunk), \\
+        sk_${nametype}_freefunc_thunk))
 #define sk_${nametype}_set_cmp_func(sk, cmp) ((sk_${nametype}_compfunc)OPENSSL_sk_set_cmp_func(ossl_check_${nametype}_sk_type(sk), ossl_check_${nametype}_compfunc_type(cmp)))
 END_MACROS