Commit 4da7c360aa for openssl.org

commit 4da7c360aad106a1b04d3d87c3a0e53d8041568f
Author: Jim Alves-Foss <drjimaf@gmail.com>
Date:   Wed Nov 4 23:28:40 2026 -0800

    x509: check issuer type in X509_ACERT_set1_issuerName()

    X509_ACERT_ISSUER keeps the issuer in a union: u.v1Form (a stack
    of GENERAL_NAME) or u.v2Form (an X509_ACERT_ISSUER_V2FORM), selected
    by the type field.  A certificate decoded with d2i_X509_ACERT() whose
    AttCertIssuer used the v1Form branch has type 0 and a GENERAL_NAMES
    stack in u.v1Form.

    X509_ACERT_set1_issuerName() read u.v2Form without looking at type,
    so for such a certificate it reinterpreted the stack as a v2Form
    and passed a bogus pointer to GENERAL_NAMES_free() through
    replace_dirName().

    Check type as well, as X509_ACERT_get0_issuerName() already does.
    If the issuer is not a v2Form, create one, free the old v1Form stack
    and switch the type, which matches the existing comment that only v2Form
    is supported so it is always the form created.  The new v2Form
    is allocated before the old stack is freed, so the certificate is left
    unchanged if allocation fails.

    Add a test that decodes an attribute certificate with a v1Form issuer
    and sets the issuer name on it.  Without the change it fails
    in an ASan/UBSan build.

    Resolves: https://github.com/openssl/openssl/issues/33549
    Fixes: 9e1a8b5ecce7 "Attribute certificate getter and setter API"
    Assisted-by: Claude Code:Anthropic Claude
    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    Merge-date: Tue Nov 10 10:53:41 2026
    Merged-from: https://github.com/openssl/openssl/pull/33550

diff --git a/crypto/x509/x509aset.c b/crypto/x509/x509aset.c
index 17b988c67c..f32648a543 100644
--- a/crypto/x509/x509aset.c
+++ b/crypto/x509/x509aset.c
@@ -145,22 +145,24 @@ void X509_ACERT_set0_holder_digest(X509_ACERT *x,

 int X509_ACERT_set1_issuerName(X509_ACERT *x, const X509_NAME *name)
 {
+    X509_ACERT_ISSUER *issuer = &x->acinfo->issuer;
     X509_ACERT_ISSUER_V2FORM *v2Form;

-    v2Form = x->acinfo->issuer.u.v2Form;
-
     /* only v2Form is supported, so always create that version */
-    if (v2Form == NULL) {
+    if (issuer->type != X509_ACERT_ISSUER_V2 || issuer->u.v2Form == NULL) {
         v2Form = X509_ACERT_ISSUER_V2FORM_new();
         if (v2Form == NULL) {
             ERR_raise(ERR_LIB_X509, ERR_R_ASN1_LIB);
             return 0;
         }
-        x->acinfo->issuer.u.v2Form = v2Form;
-        x->acinfo->issuer.type = X509_ACERT_ISSUER_V2;
+        /* a decoded certificate may hold a v1Form issuer in the same union */
+        if (issuer->type != X509_ACERT_ISSUER_V2)
+            GENERAL_NAMES_free(issuer->u.v1Form);
+        issuer->u.v2Form = v2Form;
+        issuer->type = X509_ACERT_ISSUER_V2;
     }

-    return replace_dirName(&v2Form->issuerName, name);
+    return replace_dirName(&issuer->u.v2Form->issuerName, name);
 }

 int X509_ACERT_set1_serialNumber(X509_ACERT *x, const ASN1_INTEGER *serial)
diff --git a/test/x509_acert_test.c b/test/x509_acert_test.c
index 50ae74f7d3..adffbc9152 100644
--- a/test/x509_acert_test.c
+++ b/test/x509_acert_test.c
@@ -149,6 +149,51 @@ done:
     return ret;
 }

+/*
+ * AttributeCertificateInfo whose AttCertIssuer uses the v1Form branch of the
+ * CHOICE (a plain SEQUENCE OF GeneralName holding one dNSName).
+ */
+static const unsigned char acert_v1_issuer[] = {
+    0x30, 0x60, 0x30, 0x4c, 0x02, 0x01, 0x01, 0x30, 0x00, 0x30, 0x0d, 0x82,
+    0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+    0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+    0x0b, 0x05, 0x00, 0x02, 0x01, 0x01, 0x30, 0x22, 0x18, 0x0f, 0x31, 0x39,
+    0x37, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0x5a, 0x18, 0x0f, 0x31, 0x39, 0x37, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30,
+    0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x00, 0x30, 0x0d, 0x06, 0x09,
+    0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03,
+    0x01, 0x00
+};
+
+/*
+ * Setting the issuer name on a decoded certificate whose issuer used the
+ * v1Form branch must not treat the v1Form stack as a v2Form.
+ */
+static int test_set1_issuerName_v1form(void)
+{
+    int ret = 0;
+    X509_ACERT *acert = NULL;
+    X509_NAME *name = NULL;
+    const X509_NAME *got;
+    const unsigned char *p = acert_v1_issuer;
+
+    if (!TEST_ptr(acert = d2i_X509_ACERT(NULL, &p, (long)sizeof(acert_v1_issuer)))
+        || !TEST_ptr_null(X509_ACERT_get0_issuerName(acert))
+        || !TEST_ptr(name = X509_NAME_new())
+        || !TEST_true(X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
+            (unsigned char *)"test", -1, -1, 0))
+        || !TEST_true(X509_ACERT_set1_issuerName(acert, name))
+        || !TEST_ptr(got = X509_ACERT_get0_issuerName(acert))
+        || !TEST_int_eq(X509_NAME_cmp(got, name), 0))
+        goto done;
+
+    ret = 1;
+done:
+    X509_NAME_free(name);
+    X509_ACERT_free(acert);
+    return ret;
+}
+
 OPT_TEST_DECLARE_USAGE("[<attribute certs (PEM)>...]\n")
 int setup_tests(void)
 {
@@ -168,6 +213,7 @@ int setup_tests(void)
     ADD_ALL_TESTS(test_print_acert, (int)cnt);
     ADD_TEST(test_acert_sign);
     ADD_ALL_TESTS(test_object_group_attr, OSSL_NELEM(ietf_syntax_tests));
+    ADD_TEST(test_set1_issuerName_v1form);

     return 1;
 }