Commit 68c0321e90 for openssl.org

commit 68c0321e90d015ebe851b33761c0acdbaebc12bd
Author: Bob Beck <beck@openssl.org>
Date:   Fri Apr 24 14:28:54 2026 -0600

    Provide ASN1_STRING_new_not_owned()

    This function provides the ability to construct an ASN1_STRING
    containing data that is not owned by the constructed ASN1_STRING. The
    resulting ASN1_STRING, when freed, will not free the data, and it is
    the caller's resposibility to ensure that the data lives past the
    lifetime of any returned ASN1_STRING.

    Why? you may ask? Many places where ->data and ->length were used
    directly in the past before the opaquification of ASN1_STRING were
    for this purpose, whether used for actual static data, or to turn
    bytes created by and in control of the caller into an ASN1_STRING
    for temporary use as an input. This function makes this easier
    to do without making copies.

    The function deliberately does not allow the creation of a BIT_STRING
    as this would require also always providing unused bits, which is
    annoying and unnecessary for almost all potential use cases.

    For: https://github.com/openssl/openssl/issues/29861
    For: https://github.com/openssl/openssl/issues/30162

    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    MergeDate: Wed Jun  3 11:42:49 2026
    (Merged from https://github.com/openssl/openssl/pull/30964)

diff --git a/CHANGES.md b/CHANGES.md
index 03c6d76033..08bf6045bb 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -109,6 +109,12 @@ OpenSSL Releases

    *Bob Beck*

+ * The API function `ASN1_STRING_new_not_owned` has been added to the
+   libcrypto. It provides the ability to construct an ASN1_STRING with data
+   for which ownership is not taken by the created ASN1_STRING object.
+
+   *Bob Beck*
+
  * Added AVX2 optimized ML-DSA NTT operations on `x86_64`.

    *Marcel Cornu and Tomasz Kantecki*
diff --git a/crypto/asn1/a_bitstr.c b/crypto/asn1/a_bitstr.c
index 9976cc478e..914ff98400 100644
--- a/crypto/asn1/a_bitstr.c
+++ b/crypto/asn1/a_bitstr.c
@@ -263,12 +263,6 @@ int ASN1_BIT_STRING_set1(ASN1_BIT_STRING *abs, const uint8_t *data, size_t lengt
     if (length > 0 && (data[length - 1] & ((1 << unused_bits) - 1)) != 0)
         return 0;

-    /*
-     * XXX - ASN1_STRING_set() and asn1_bit_string_set_unused_bits() preserve the
-     * state of flags irrelevant to ASN1_BIT_STRING. Should we explicitly
-     * clear them?
-     */
-
     if (!ASN1_STRING_set(abs, data, (int)length))
         return 0;
     abs->type = V_ASN1_BIT_STRING;
diff --git a/crypto/asn1/asn1_lib.c b/crypto/asn1/asn1_lib.c
index 28898b49ff..99903564e2 100644
--- a/crypto/asn1/asn1_lib.c
+++ b/crypto/asn1/asn1_lib.c
@@ -248,9 +248,15 @@ int ASN1_object_size(int constructed, int length, int tag)
     return ret + length;
 }

-void ossl_asn1_bit_string_set_unused_bits(ASN1_STRING *str, unsigned int num)
+void ossl_asn1_bit_string_clear_unused_bits(ASN1_STRING *str)
 {
     str->flags &= ~0x07;
+    str->flags &= ~ASN1_STRING_FLAG_BITS_LEFT;
+}
+
+void ossl_asn1_bit_string_set_unused_bits(ASN1_STRING *str, unsigned int num)
+{
+    ossl_asn1_bit_string_clear_unused_bits(str);
     str->flags |= ASN1_STRING_FLAG_BITS_LEFT | (num & 0x07);
 }

@@ -309,6 +315,13 @@ int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len_in)
         ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_LARGE);
         return 0;
     }
+
+    if ((str->flags & ASN1_STRING_FLAG_DATA_NOT_OWNED)) {
+        str->data = NULL;
+        str->length = 0;
+        str->flags &= ~ASN1_STRING_FLAG_DATA_NOT_OWNED;
+    }
+
     if ((size_t)str->length <= len || str->data == NULL) {
         c = str->data;
 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
@@ -337,12 +350,17 @@ int ASN1_STRING_set(ASN1_STRING *str, const void *_data, int len_in)
         str->data[len] = '\0';
 #endif
     }
+    ossl_asn1_bit_string_clear_unused_bits(str);
+
     return 1;
 }

 void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len)
 {
-    OPENSSL_free(str->data);
+    if (!(str->flags & ASN1_STRING_FLAG_DATA_NOT_OWNED)) {
+        OPENSSL_clear_free(str->data, str->length);
+    }
+    str->flags &= ~ASN1_STRING_FLAG_DATA_NOT_OWNED;
     str->data = data;
     str->length = len;
 }
@@ -363,30 +381,75 @@ ASN1_STRING *ASN1_STRING_type_new(int type)
     return ret;
 }

-void ossl_asn1_string_embed_free(ASN1_STRING *a, int embed)
+ASN1_STRING *ASN1_STRING_new_not_owned(int type, const uint8_t *data,
+    size_t length)
+{
+    ASN1_STRING *ret;
+
+    if (type == V_ASN1_BIT_STRING)
+        return NULL;
+
+    if (data == NULL || length == 0)
+        return NULL;
+
+    if (length > INT_MAX)
+        return NULL;
+
+    ret = OPENSSL_zalloc(sizeof(*ret));
+    if (ret == NULL)
+        return NULL;
+
+    ret->type = type;
+    ret->data = (unsigned char *)data;
+    ret->length = (int)length;
+    ret->flags |= ASN1_STRING_FLAG_DATA_NOT_OWNED;
+
+    return ret;
+}
+
+void ossl_asn1_string_free_internal(ASN1_STRING *a, int clear, int embed)
 {
     if (a == NULL)
         return;
-    if (!(a->flags & ASN1_STRING_FLAG_NDEF))
-        OPENSSL_free(a->data);
-    if (embed == 0)
-        OPENSSL_free(a);
+
+    if ((a->flags & ASN1_STRING_FLAG_DATA_NOT_OWNED)) {
+        a->data = NULL;
+        a->length = 0;
+        a->flags &= ~ASN1_STRING_FLAG_DATA_NOT_OWNED;
+    }
+
+    if (!(a->flags & ASN1_STRING_FLAG_NDEF)) {
+        if (clear)
+            OPENSSL_clear_free(a->data, a->length);
+        else
+            OPENSSL_free(a->data);
+    }
+    /*
+     * TODO(beck): Add an assert here to verify that the embed arg is
+     * always set to match the flag, and then get rid of the arg.
+     */
+    if (!embed && !(a->flags & ASN1_STRING_FLAG_EMBED)) {
+        if (clear)
+            OPENSSL_clear_free(a, sizeof(*a));
+        else
+            OPENSSL_free(a);
+    }
 }

 void ASN1_STRING_free(ASN1_STRING *a)
 {
     if (a == NULL)
         return;
-    ossl_asn1_string_embed_free(a, a->flags & ASN1_STRING_FLAG_EMBED);
+
+    ossl_asn1_string_free_internal(a, 0, a->flags & ASN1_STRING_FLAG_EMBED);
 }

 void ASN1_STRING_clear_free(ASN1_STRING *a)
 {
     if (a == NULL)
         return;
-    if (a->data && !(a->flags & ASN1_STRING_FLAG_NDEF))
-        OPENSSL_cleanse(a->data, a->length);
-    ASN1_STRING_free(a);
+
+    ossl_asn1_string_free_internal(a, 1, a->flags & ASN1_STRING_FLAG_EMBED);
 }

 int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b)
diff --git a/crypto/asn1/asn1_local.h b/crypto/asn1/asn1_local.h
index 3f230683d7..46d7aca7a2 100644
--- a/crypto/asn1/asn1_local.h
+++ b/crypto/asn1/asn1_local.h
@@ -50,7 +50,7 @@ DEFINE_STACK_OF(MIME_PARAM)
 typedef struct mime_header_st MIME_HEADER;
 DEFINE_STACK_OF(MIME_HEADER)

-void ossl_asn1_string_embed_free(ASN1_STRING *a, int embed);
+void ossl_asn1_string_free_internal(ASN1_STRING *a, int clear, int embed);

 int ossl_asn1_get_choice_selector(ASN1_VALUE **pval, const ASN1_ITEM *it);
 int ossl_asn1_get_choice_selector_const(const ASN1_VALUE **pval,
diff --git a/crypto/asn1/tasn_fre.c b/crypto/asn1/tasn_fre.c
index f8068832ab..df24111578 100644
--- a/crypto/asn1/tasn_fre.c
+++ b/crypto/asn1/tasn_fre.c
@@ -205,7 +205,7 @@ void ossl_asn1_primitive_free(ASN1_VALUE **pval, const ASN1_ITEM *it, int embed)
         break;

     default:
-        ossl_asn1_string_embed_free((ASN1_STRING *)*pval, embed);
+        ossl_asn1_string_free_internal((ASN1_STRING *)*pval, 0, embed);
         break;
     }
     *pval = NULL;
diff --git a/doc/man3/ASN1_STRING_new.pod b/doc/man3/ASN1_STRING_new.pod
index 642b6f4777..7698f7411e 100644
--- a/doc/man3/ASN1_STRING_new.pod
+++ b/doc/man3/ASN1_STRING_new.pod
@@ -2,7 +2,8 @@

 =head1 NAME

-ASN1_STRING_new, ASN1_STRING_type_new, ASN1_STRING_free -
+ASN1_STRING_new, ASN1_STRING_type_new, ASN1_STRING_free,
+ASN1_STRING_new_not_owned -
 ASN1_STRING allocation functions

 =head1 SYNOPSIS
@@ -11,6 +12,7 @@ ASN1_STRING allocation functions

  ASN1_STRING *ASN1_STRING_new(void);
  ASN1_STRING *ASN1_STRING_type_new(int type);
+ ASN1_STRING *ASN1_STRING_new_not_owned(int type, const uint8_t *data, size_t length);
  void ASN1_STRING_free(ASN1_STRING *a);

 =head1 DESCRIPTION
@@ -21,6 +23,15 @@ is undefined.
 ASN1_STRING_type_new() returns an allocated B<ASN1_STRING> structure of
 type I<type>.

+ASN1_STRING_new_not_owned() returns an allocated B<ASN1_STRING>
+structure of type I<type>, and sets the data returned string to the
+bytes at I<data> of length I<len>. Ownership of I<data> is not
+transferred, and it is the caller's responsibility to ensure that
+I<data> outlives any successfully returned result. The provided I<type>
+must not be V_ASN1_BIT_STRING, the provided I<length> must be greater
+than 0, and I<data> must not be NULL. It is an error if the provided
+data size exceeds the internal limit of the ASN1_STRING implementation.
+
 ASN1_STRING_free() frees up I<a>.
 If I<a> is NULL nothing is done.

@@ -29,10 +40,16 @@ If I<a> is NULL nothing is done.
 Other string types call the B<ASN1_STRING> functions. For example
 ASN1_OCTET_STRING_new() calls ASN1_STRING_type_new(V_ASN1_OCTET_STRING).

+ASN1_STRING_new_not_owned() does not automatically call strlen()
+to determine a length, or guarantee that the data is a C string. The
+data contained in the string and the lifetime of it are the
+responsibility of the caller.
+
 =head1 RETURN VALUES

-ASN1_STRING_new() and ASN1_STRING_type_new() return a valid
-B<ASN1_STRING> structure or NULL if an error occurred.
+ASN1_STRING_new(), ASN1_STRING_type_new(), and
+ASN1_STRING_new_not_owned() return a valid B<ASN1_STRING>
+structure or NULL if an error occurred.

 ASN1_STRING_free() does not return a value.

@@ -40,6 +57,10 @@ ASN1_STRING_free() does not return a value.

 L<ERR_get_error(3)>

+=head1 HISTORY
+
+ASN1_STRING_new_not_owned() was added in OpenSSL 4.1.
+
 =head1 COPYRIGHT

 Copyright 2002-2023 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/include/crypto/asn1.h b/include/crypto/asn1.h
index f7dc7852f6..bc556588d0 100644
--- a/include/crypto/asn1.h
+++ b/include/crypto/asn1.h
@@ -43,6 +43,9 @@
 /* String is embedded and only content should be freed */
 #define ASN1_STRING_FLAG_EMBED 0x080

+/* Data is static and should not be freed. */
+#define ASN1_STRING_FLAG_DATA_NOT_OWNED 0x100
+
 /* This is the base type that holds just about everything :-) */
 struct asn1_string_st {
     int length;
@@ -183,6 +186,7 @@ EVP_PKEY *ossl_d2i_PrivateKey_legacy(int keytype, EVP_PKEY **a,
     OSSL_LIB_CTX *libctx, const char *propq);
 X509_ALGOR *ossl_X509_ALGOR_from_nid(int nid, int ptype, void *pval);

+void ossl_asn1_bit_string_clear_unused_bits(ASN1_STRING *str);
 void ossl_asn1_bit_string_set_unused_bits(ASN1_STRING *str, unsigned int num);

 int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in,
diff --git a/include/openssl/asn1.h.in b/include/openssl/asn1.h.in
index cce56701c9..21d9e772c6 100644
--- a/include/openssl/asn1.h.in
+++ b/include/openssl/asn1.h.in
@@ -530,6 +530,8 @@ void ASN1_STRING_clear_free(ASN1_STRING *a);
 int ASN1_STRING_copy(ASN1_STRING *dst, const ASN1_STRING *str);
 DECLARE_ASN1_DUP_FUNCTION(ASN1_STRING)
 ASN1_STRING *ASN1_STRING_type_new(int type);
+ASN1_STRING *ASN1_STRING_new_not_owned(int type, const uint8_t *data,
+    size_t length);
 int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b);
 /*
  * Since this is used to store all sorts of things, via macros, for now,
diff --git a/test/asn1_string_test.c b/test/asn1_string_test.c
index d64f54da5e..baf5525b38 100644
--- a/test/asn1_string_test.c
+++ b/test/asn1_string_test.c
@@ -389,9 +389,96 @@ asn1_bit_string_set1_test(int idx)
     return abs_set1_test(abs_set1_tests, idx);
 }

+static int
+asn1_string_new_not_owned_test(void)
+{
+    int success = 0;
+    ASN1_STRING *tmp = NULL;
+    char *tmpstring = NULL;
+    static const uint8_t data[] = { 0xba, 0xdb, 0x0b, 0xba, 0xdb, 0x0b, 0xba, 0xdb, 0x0b };
+    static const uint8_t data2[] = { 0xba, 0xdb, 0x0b, 0xba, 0xdb, 0x0b, 0xba, 0xdb, 0x0b };
+
+    if (!TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, data, sizeof(data))))
+        goto err;
+
+    ASN1_STRING_clear_free(tmp);
+    tmp = NULL;
+
+    if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
+        goto err;
+
+    if (!TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, data, sizeof(data))))
+        goto err;
+
+    if (!TEST_true(ASN1_STRING_set(tmp, "muppet", (int)strlen("muppet"))))
+        goto err;
+
+    if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
+        goto err;
+
+    if (!TEST_int_eq(ASN1_STRING_length(tmp), (int)strlen("muppet")))
+        goto err;
+
+    if (!TEST_mem_eq(ASN1_STRING_get0_data(tmp), strlen("muppet"), "muppet", strlen("muppet")))
+        goto err;
+
+    ASN1_STRING_clear_free(tmp);
+    tmp = NULL;
+
+    if (!TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, data, sizeof(data))))
+        goto err;
+
+    if (!TEST_ptr(tmpstring = strdup("puppet")))
+        goto err;
+
+    ASN1_STRING_set0(tmp, tmpstring, 4);
+
+    if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
+        goto err;
+
+    if (!TEST_int_eq(ASN1_STRING_length(tmp), 4))
+        goto err;
+
+    if (!TEST_mem_eq(ASN1_STRING_get0_data(tmp), strlen("puppet"), "puppet", strlen("puppet")))
+        goto err;
+
+    memset((uint8_t *)ASN1_STRING_get0_data(tmp), 'z', ASN1_STRING_length(tmp));
+
+    if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
+        goto err;
+
+    if (!TEST_mem_eq(tmpstring, strlen("puppet"), "zzzzet", strlen("puppet")))
+        goto err;
+
+    ASN1_STRING_clear_free(tmp);
+    tmpstring = NULL;
+    tmp = NULL;
+
+    if (TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_BIT_STRING, data, sizeof(data))))
+        goto err;
+
+    if (TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, NULL, sizeof(data))))
+        goto err;
+
+    if (TEST_ptr(tmp = ASN1_STRING_new_not_owned(V_ASN1_OCTET_STRING, data, 0)))
+        goto err;
+
+    if (!TEST_mem_eq(data, sizeof(data), data2, sizeof(data2)))
+        goto err;
+
+    success = 1;
+
+err:
+    ASN1_STRING_clear_free(tmp);
+    free(tmpstring);
+
+    return success;
+}
+
 int setup_tests(void)
 {
     ADD_ALL_TESTS(asn1_bit_string_get_length_test, OSSL_NELEM(abs_get_length_tests));
     ADD_ALL_TESTS(asn1_bit_string_set1_test, OSSL_NELEM(abs_set1_tests));
+    ADD_TEST(asn1_string_new_not_owned_test);
     return 1;
 }
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 659eb5cbf4..777dfbf70e 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5721,3 +5721,4 @@ 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:
+ASN1_STRING_new_not_owned               ?	4_1_0	EXIST::FUNCTION: