Commit 4cef487f2a for openssl.org

commit 4cef487f2a5615e59d919e6fd4094a02038950ff
Author: Norbert Pocs <norbertp@openssl.org>
Date:   Sat Jul 18 16:49:06 2026 +0200

    Guard against NULL data in empty ASN1_STRINGs

    Since 28179061bf a zero-length ASN1_STRING has data == NULL in
    fuzzing builds instead of a 1-byte allocation. Several call sites
    did pointer arithmetic or memcpy on the data pointer before any
    length check, which is undefined behaviour for NULL even with a
    zero offset and aborts the fuzz targets under UBSan:

    - asn1_string_canon: skip canonicalisation of empty values
    - do_buf, do_hex_dump: return early on an empty buffer
    - i2d_ocsp_nonce: skip the memcpy for an empty nonce

    The loops at these sites were already no-ops for zero length, so
    there is no behaviour change outside sanitizer builds.

    Assisted-By: Claude:claude-fable-5

    Signed-off-by: Norbert Pocs <norbertp@openssl.org>

    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    MergeDate: Tue Jul 21 12:54:48 2026
    (Merged from https://github.com/openssl/openssl/pull/31998)

diff --git a/crypto/asn1/a_strex.c b/crypto/asn1/a_strex.c
index e488c87f5b..57a14f6f90 100644
--- a/crypto/asn1/a_strex.c
+++ b/crypto/asn1/a_strex.c
@@ -142,6 +142,10 @@ static int do_buf(const unsigned char *buf, int buflen,
     const unsigned char *p, *q;
     uint32_t c;

+    if (buflen < 0)
+        return -1;
+    if (buflen == 0)
+        return 0;
     p = buf;
     q = buf + buflen;
     outlen = 0;
@@ -236,6 +240,10 @@ static int do_hex_dump(char_io *io_ch, void *arg, unsigned char *buf,
     unsigned char *p, *q;
     char hextmp[2];

+    if (buflen < 0)
+        return -1;
+    if (buflen == 0)
+        return 0;
     if (arg) {
         p = buf;
         q = buf + buflen;
diff --git a/crypto/ocsp/v3_ocsp.c b/crypto/ocsp/v3_ocsp.c
index d31c74ef45..408c2a1548 100644
--- a/crypto/ocsp/v3_ocsp.c
+++ b/crypto/ocsp/v3_ocsp.c
@@ -143,7 +143,7 @@ static void *ocsp_nonce_new(void)
 static int i2d_ocsp_nonce(const void *a, unsigned char **pp)
 {
     const ASN1_OCTET_STRING *os = a;
-    if (pp) {
+    if (pp != NULL && os->length > 0) {
         memcpy(*pp, os->data, os->length);
         *pp += os->length;
     }
@@ -164,7 +164,8 @@ static void *d2i_ocsp_nonce(void *a, const unsigned char **pp, long length)
     if (!ASN1_OCTET_STRING_set(os, *pp, length))
         goto err;

-    *pp += length;
+    if (length > 0)
+        *pp += length;

     if (pos)
         *pos = os;
diff --git a/crypto/x509/x_name.c b/crypto/x509/x_name.c
index 90588c8c67..961f474e5b 100644
--- a/crypto/x509/x_name.c
+++ b/crypto/x509/x_name.c
@@ -404,8 +404,10 @@ static int asn1_string_canon(ASN1_STRING *out, const ASN1_STRING *in)

     out->type = V_ASN1_UTF8STRING;
     out->length = ASN1_STRING_to_UTF8(&out->data, in);
-    if (out->length == -1)
+    if (out->length < 0)
         return 0;
+    if (out->length == 0)
+        return 1;

     to = out->data;
     from = to;