Commit b1389437f5 for openssl.org

commit b1389437f56ace99f2a59a8a78fbf3f69b487d34
Author: Nikola Pajkovsky <nikolap@openssl.org>
Date:   Tue May 12 08:49:31 2026 +0200

    stack: use a copy thunk for typed stack deep copies

    typed safestack wrappers pass type-specific copy callbacks such as
    TYPE *(*)(const TYPE *) to OPENSSL_sk_deep_copy().  The generic stack code
    then called those callbacks through OPENSSL_sk_copyfunc,
    void *(*)(const void *), which is an incompatible function pointer type and
    triggers UBSan.

    Add an OPENSSL_sk_copyfunc_thunk and store it on typed stacks, mirroring the
    existing compare/free thunk pattern.  Generated safestack helpers now install
    a per-type copy thunk when constructing a stack, and internal_copy() uses that
    thunk when deep-copying typed stacks.  This preserves the generic stack API
    while ensuring typed copy callbacks are invoked through their real signature.

    Fixes: https://github.com/openssl/project/issues/1951
    Signed-off-by: Nikola Pajkovsky <nikolap@openssl.org>

    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Reviewed-by: Neil Horman <nhorman@openssl.org>
    MergeDate: Wed May 20 15:53:45 2026
    (Merged from https://github.com/openssl/openssl/pull/31151)

diff --git a/crypto/stack/stack.c b/crypto/stack/stack.c
index bd3c6f54ef..5799bf9d7c 100644
--- a/crypto/stack/stack.c
+++ b/crypto/stack/stack.c
@@ -33,6 +33,7 @@ struct stack_st {
     OPENSSL_sk_compfunc comp;
     int (*cmp_thunk)(OPENSSL_sk_compfunc, const void *, const void *);
     OPENSSL_sk_freefunc_thunk free_thunk;
+    OPENSSL_sk_copyfunc_thunk copy_thunk;
 };

 OPENSSL_sk_compfunc OPENSSL_sk_set_cmp_func(OPENSSL_STACK *sk,
@@ -88,7 +89,12 @@ static OPENSSL_STACK *internal_copy(const OPENSSL_STACK *sk,
         for (i = 0; i < ret->num; ++i) {
             if (sk->data[i] == NULL)
                 continue;
-            if ((ret->data[i] = copy_func(sk->data[i])) == NULL) {
+            if (ret->copy_thunk != NULL)
+                ret->data[i] = ret->copy_thunk(copy_func, sk->data[i]);
+            else
+                ret->data[i] = copy_func(sk->data[i]);
+
+            if (ret->data[i] == NULL) {
                 while (--i >= 0)
                     free_with_thunk(ret, free_func, ret->data[i]);
                 goto err;
@@ -266,6 +272,14 @@ OPENSSL_STACK *OPENSSL_sk_set_cmp_thunks(OPENSSL_STACK *st, int (*c_thunk)(int (
     return st;
 }

+OPENSSL_STACK *OPENSSL_sk_set_copy_thunks(OPENSSL_STACK *st, OPENSSL_sk_copyfunc_thunk cp_thunk)
+{
+    if (st != NULL)
+        st->copy_thunk = cp_thunk;
+
+    return st;
+}
+
 int OPENSSL_sk_insert(OPENSSL_STACK *st, const void *data, int loc)
 {
     int cmp_ret;
diff --git a/doc/man3/DEFINE_STACK_OF.pod b/doc/man3/DEFINE_STACK_OF.pod
index a9d678bacc..5f4fcaa487 100644
--- a/doc/man3/DEFINE_STACK_OF.pod
+++ b/doc/man3/DEFINE_STACK_OF.pod
@@ -16,7 +16,8 @@ OPENSSL_sk_dup, OPENSSL_sk_find, OPENSSL_sk_find_ex, OPENSSL_sk_find_all,
 OPENSSL_sk_free, OPENSSL_sk_insert, OPENSSL_sk_is_sorted, OPENSSL_sk_new,
 OPENSSL_sk_new_null, OPENSSL_sk_new_reserve, OPENSSL_sk_num, OPENSSL_sk_pop,
 OPENSSL_sk_pop_free, OPENSSL_sk_push, OPENSSL_sk_reserve, OPENSSL_sk_set,
-OPENSSL_sk_set_thunks, OPENSSL_sk_set_cmp_thunks, OPENSSL_sk_set_cmp_func, OPENSSL_sk_shift,
+OPENSSL_sk_set_thunks, OPENSSL_sk_set_cmp_thunks, OPENSSL_sk_set_copy_thunks,
+OPENSSL_sk_set_cmp_func, OPENSSL_sk_shift,
 OPENSSL_sk_sort, OPENSSL_sk_unshift, OPENSSL_sk_value, OPENSSL_sk_zero
 - stack container

@@ -240,11 +241,13 @@ OPENSSL_sk_free(), OPENSSL_sk_insert(), OPENSSL_sk_is_sorted(),
 OPENSSL_sk_new(), OPENSSL_sk_new_null(), OPENSSL_sk_new_reserve(),
 OPENSSL_sk_num(), OPENSSL_sk_pop(), OPENSSL_sk_pop_free(), OPENSSL_sk_push(),
 OPENSSL_sk_reserve(), OPENSSL_sk_set(), OPENSSL_sk_set_cmp_func(),
-OPENSSL_sk_set_thunks(), OPENSSL_sk_set_cmp_thunks(), OPENSSL_sk_shift(), OPENSSL_sk_sort(),
+OPENSSL_sk_set_thunks(), OPENSSL_sk_set_cmp_thunks(),
+OPENSSL_sk_set_copy_thunks(), OPENSSL_sk_shift(), OPENSSL_sk_sort(),
 OPENSSL_sk_unshift(), OPENSSL_sk_value(), OPENSSL_sk_zero().

-OPENSSL_sk_set_thunks() and OPENSSL_sk_set_cmp_thunks(), while public by necessity, are actually
-internal and should not be used.
+OPENSSL_sk_set_thunks(), OPENSSL_sk_set_cmp_thunks(), and
+OPENSSL_sk_set_copy_thunks(), while public by necessity, are actually internal
+and should not be used.

 =head1 RETURN VALUES

@@ -303,7 +306,9 @@ was changed to return 0 in this condition as for other errors.

 OPENSSL_sk_set_thunks() was added in OpenSSL 3.6.0.

-OPENSSL_sk_set_cmp_thunks() was added in OpenSSL 4.0.0
+OPENSSL_sk_set_cmp_thunks() was added in OpenSSL 4.0.0.
+
+OPENSSL_sk_set_copy_thunks() was added in OpenSSL 4.1.0.

 =head1 COPYRIGHT

diff --git a/include/openssl/safestack.h.in b/include/openssl/safestack.h.in
index 67e0448252..a4c178d189 100644
--- a/include/openssl/safestack.h.in
+++ b/include/openssl/safestack.h.in
@@ -46,6 +46,11 @@ extern "C" {
         sk_##t1##_freefunc freefunc = (sk_##t1##_freefunc)freefunc_arg;                                                  \
         freefunc((t3 *)ptr);                                                                                             \
     }                                                                                                                    \
+    static ossl_inline void *sk_##t1##_copyfunc_thunk(OPENSSL_sk_copyfunc copyfunc_arg, const void *ptr)                 \
+    {                                                                                                                    \
+        sk_##t1##_copyfunc copyfunc = (sk_##t1##_copyfunc)copyfunc_arg;                                                  \
+        return (void *)copyfunc((const t3 *)ptr);                                                                        \
+    }                                                                                                                    \
     static ossl_inline int sk_##t1##_cmpfunc_thunk(int (*cmp)(const void *, const void *), const void *a, const void *b) \
     {                                                                                                                    \
         int (*realcmp)(const t3 *const *a, const t3 *const *b) = (int (*)(const t3 *const *a, const t3 *const *b))(cmp); \
@@ -89,6 +94,11 @@ extern "C" {
         sk_##t1##_freefunc freefunc = (sk_##t1##_freefunc)freefunc_arg;                                                    \
         freefunc((t3 *)ptr);                                                                                               \
     }                                                                                                                      \
+    static ossl_inline void *sk_##t1##_copyfunc_thunk(OPENSSL_sk_copyfunc copyfunc_arg, const void *ptr)                   \
+    {                                                                                                                      \
+        sk_##t1##_copyfunc copyfunc = (sk_##t1##_copyfunc)copyfunc_arg;                                                    \
+        return (void *)copyfunc((const t3 *)ptr);                                                                          \
+    }                                                                                                                      \
     static ossl_inline int sk_##t1##_cmpfunc_thunk(int (*cmp)(const void *, const void *), const void *a, const void *b)   \
     {                                                                                                                      \
         int (*realcmp)(const t3 *const *a, const t3 *const *b) = (int (*)(const t3 *const *a, const t3 *const *b))(cmp);   \
@@ -112,6 +122,7 @@ extern "C" {
                                                                                                                            \
         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##_new_null(void)                                                  \
@@ -121,6 +132,7 @@ extern "C" {
                                                                                                                            \
         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##_new_reserve(sk_##t1##_compfunc compare, int n)                  \
@@ -130,6 +142,7 @@ extern "C" {
                                                                                                                            \
         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 int sk_##t1##_reserve(STACK_OF(t1) *sk, int n)                                          \
diff --git a/include/openssl/stack.h b/include/openssl/stack.h
index 67e4d25897..7cab36c4f6 100644
--- a/include/openssl/stack.h
+++ b/include/openssl/stack.h
@@ -26,6 +26,7 @@ typedef int (*OPENSSL_sk_compfunc)(const void *, const void *);
 typedef void (*OPENSSL_sk_freefunc)(void *);
 typedef void (*OPENSSL_sk_freefunc_thunk)(OPENSSL_sk_freefunc, void *);
 typedef void *(*OPENSSL_sk_copyfunc)(const void *);
+typedef void *(*OPENSSL_sk_copyfunc_thunk)(OPENSSL_sk_copyfunc, const void *);

 int OPENSSL_sk_num(const OPENSSL_STACK *);
 void *OPENSSL_sk_value(const OPENSSL_STACK *, int);
@@ -37,6 +38,7 @@ OPENSSL_STACK *OPENSSL_sk_new_null(void);
 OPENSSL_STACK *OPENSSL_sk_new_reserve(OPENSSL_sk_compfunc c, int n);
 OPENSSL_STACK *OPENSSL_sk_set_thunks(OPENSSL_STACK *st, OPENSSL_sk_freefunc_thunk f_thunk);
 OPENSSL_STACK *OPENSSL_sk_set_cmp_thunks(OPENSSL_STACK *st, int (*c_thunk)(int (*)(const void *, const void *), const void *, const void *));
+OPENSSL_STACK *OPENSSL_sk_set_copy_thunks(OPENSSL_STACK *st, OPENSSL_sk_copyfunc_thunk cp_thunk);
 int OPENSSL_sk_reserve(OPENSSL_STACK *st, int n);
 void OPENSSL_sk_free(OPENSSL_STACK *);
 void OPENSSL_sk_pop_free(OPENSSL_STACK *st, OPENSSL_sk_freefunc func);
diff --git a/test/stack_test.c b/test/stack_test.c
index aa29e6c822..c74cca2167 100644
--- a/test/stack_test.c
+++ b/test/stack_test.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-2025 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2017-2026 The OpenSSL Project Authors. All Rights Reserved.
  * Copyright (c) 2017, Oracle and/or its affiliates.  All rights reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
@@ -339,6 +339,16 @@ static void SS_free(SS *p)
     OPENSSL_free(p);
 }

+static char *string_copy(const char *p)
+{
+    return OPENSSL_strdup(p);
+}
+
+static void string_free(char *p)
+{
+    OPENSSL_free(p);
+}
+
 static int test_SS_stack(void)
 {
     STACK_OF(SS) *s = sk_SS_new_null();
@@ -413,6 +423,55 @@ end:
     return testresult;
 }

+static int test_OPENSSL_STRING_deep_copy_mfail(void)
+{
+    STACK_OF(OPENSSL_STRING) *s = sk_OPENSSL_STRING_new_null();
+    STACK_OF(OPENSSL_STRING) *r = NULL;
+    static const char *strings[] = {
+        "alpha", "beta", "gamma"
+    };
+    char *p;
+    int i;
+    int testresult = 0;
+
+    if (!TEST_ptr(s))
+        goto end;
+
+    for (i = 0; i < (int)OSSL_NELEM(strings); i++) {
+        p = OPENSSL_strdup(strings[i]);
+        if (!TEST_ptr(p)
+            || !TEST_int_eq(sk_OPENSSL_STRING_push(s, p), i + 1)) {
+            OPENSSL_free(p);
+            goto end;
+        }
+    }
+
+    MFAIL_start();
+    r = sk_OPENSSL_STRING_deep_copy(s, string_copy, string_free);
+    MFAIL_end();
+
+    if (r == NULL)
+        goto end;
+
+    if (!TEST_int_eq(sk_OPENSSL_STRING_num(r), sk_OPENSSL_STRING_num(s)))
+        goto end;
+
+    for (i = 0; i < sk_OPENSSL_STRING_num(s); i++) {
+        char *src = sk_OPENSSL_STRING_value(s, i);
+        char *dst = sk_OPENSSL_STRING_value(r, i);
+
+        if (!TEST_ptr_ne(dst, src)
+            || !TEST_str_eq(dst, src))
+            goto end;
+    }
+
+    testresult = 1;
+end:
+    sk_OPENSSL_STRING_pop_free(r, string_free);
+    sk_OPENSSL_STRING_pop_free(s, string_free);
+    return testresult;
+}
+
 static int test_SU_stack(void)
 {
     STACK_OF(SU) *s = sk_SU_new_null();
@@ -455,5 +514,6 @@ int setup_tests(void)
     ADD_ALL_TESTS(test_uchar_stack, 4);
     ADD_TEST(test_SS_stack);
     ADD_TEST(test_SU_stack);
+    ADD_MFAIL_TEST(test_OPENSSL_STRING_deep_copy_mfail);
     return 1;
 }
diff --git a/util/libcrypto.num b/util/libcrypto.num
index dbaac8b37f..659eb5cbf4 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5720,3 +5720,4 @@ CRYPTO_atomic_load_ptr                  ?	4_1_0	EXIST::FUNCTION:
 CRYPTO_atomic_store_ptr                 ?	4_1_0	EXIST::FUNCTION:
 CRYPTO_atomic_cmp_exch_ptr              ?	4_1_0	EXIST::FUNCTION:
 EVP_EC_affine2oct                       ?	4_1_0	EXIST::FUNCTION:
+OPENSSL_sk_set_copy_thunks              ?	4_1_0	EXIST::FUNCTION:
diff --git a/util/perl/OpenSSL/stackhash.pm b/util/perl/OpenSSL/stackhash.pm
index cab5738d18..39f71e1ce7 100644
--- a/util/perl/OpenSSL/stackhash.pm
+++ b/util/perl/OpenSSL/stackhash.pm
@@ -28,9 +28,9 @@ sub generate_stack_macros_int {
 SKM_DEFINE_STACK_OF_INTERNAL(${nametype}, ${realtype}, ${plaintype})
 #define sk_${nametype}_num(sk) OPENSSL_sk_num(ossl_check_const_${nametype}_sk_type(sk))
 #define sk_${nametype}_value(sk, idx) ((${realtype} *)OPENSSL_sk_value(ossl_check_const_${nametype}_sk_type(sk), (idx)))
-#define sk_${nametype}_new(cmp) ((STACK_OF(${nametype}) *)OPENSSL_sk_set_cmp_thunks(OPENSSL_sk_new(ossl_check_${nametype}_compfunc_type(cmp)), sk_${nametype}_cmpfunc_thunk))
-#define sk_${nametype}_new_null() ((STACK_OF(${nametype}) *)OPENSSL_sk_set_thunks(OPENSSL_sk_new_null(), sk_${nametype}_freefunc_thunk))
-#define sk_${nametype}_new_reserve(cmp, n) ((STACK_OF(${nametype}) *)OPENSSL_sk_set_cmp_thunks(OPENSSL_sk_new_reserve(ossl_check_${nametype}_compfunc_type(cmp), (n)), sk_${nametype}_cmpfunc_thunk))
+#define sk_${nametype}_new(cmp) ((STACK_OF(${nametype}) *)OPENSSL_sk_set_copy_thunks(OPENSSL_sk_set_cmp_thunks(OPENSSL_sk_new(ossl_check_${nametype}_compfunc_type(cmp)), sk_${nametype}_cmpfunc_thunk), sk_${nametype}_copyfunc_thunk))
+#define sk_${nametype}_new_null() ((STACK_OF(${nametype}) *)OPENSSL_sk_set_thunks(OPENSSL_sk_set_copy_thunks(OPENSSL_sk_new_null(), sk_${nametype}_copyfunc_thunk), sk_${nametype}_freefunc_thunk))
+#define sk_${nametype}_new_reserve(cmp, n) ((STACK_OF(${nametype}) *)OPENSSL_sk_set_copy_thunks(OPENSSL_sk_set_cmp_thunks(OPENSSL_sk_new_reserve(ossl_check_${nametype}_compfunc_type(cmp), (n)), sk_${nametype}_cmpfunc_thunk), sk_${nametype}_copyfunc_thunk))
 #define sk_${nametype}_reserve(sk, n) OPENSSL_sk_reserve(ossl_check_${nametype}_sk_type(sk), (n))
 #define sk_${nametype}_free(sk) OPENSSL_sk_free(ossl_check_${nametype}_sk_type(sk))
 #define sk_${nametype}_zero(sk) OPENSSL_sk_zero(ossl_check_${nametype}_sk_type(sk))