]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MINOR: ssl: reject an embedded NUL in the ssl_*_dn(entry) fetches
authorWilliam Lallemand <wlallemand@haproxy.com>
Wed, 12 Aug 2026 09:19:54 +0000 (09:19 +0000)
committerWilliam Lallemand <wlallemand@haproxy.com>
Wed, 12 Aug 2026 12:51:25 +0000 (14:51 +0200)
ssl_sock_get_dn_entry() copied the ASN.1-declared bytes of a DN entry
verbatim, keeping the exact length. pat_match_str()'s tree branch
(used by the default, exact-match "-m str" ACLs) then looks the value
up with ebst_lookup(), which treats it as a NUL-terminated C string.

A certificate with e.g. CN = "admin\0.attacker-owned.example" is
therefore matched and authorized as "admin": the producer keeps the
full value, but the ebtree consumer silently truncates at the first
NUL. ACLs using "-i" are unaffected since they take the length-correct
list branch instead of the tree.

Reject such a value in the fetch itself, before it reaches the
pattern-matching layer: a NUL followed by any further non-NUL byte
now makes ssl_sock_get_dn_entry() fail as if the entry did not exist,
so ssl_c_s_dn(CN)/ssl_c_i_dn(CN)/ssl_r_dn(CN) and any map/ACL relying
on it can't be bypassed this way. Trailing NUL byte(s) with nothing
after them aren't a truncation risk, so a single one is kept in the
returned length instead of the whole ASN.1 length, and any extra ones
are skipped.

The impact is low since it would need to be signed by the CA anyway.

This should be backported to every stable branch.

Reported-by: Vivek Parikh <vivek.parikh@breachx.ai>
doc/configuration.txt
src/ssl_utils.c

index 2e961e51851c94d20806dfff373c8d0e9b723fc7..ad493e013e098c3c3b205528a4c62467aa9256a8 100644 (file)
@@ -26364,6 +26364,9 @@ ssl_c_i_dn([<entry>[,<occ>[,<format>]]]) : string
   LDAP v3.
   If you'd like to modify the format only you can specify an empty string
   and zero for the first two parameters. Example: ssl_c_i_dn(,0,rfc2253)
+  If the requested entry's ASN.1 value contains an embedded NUL byte
+  followed by other data, it is considered malformed and no data is
+  returned.
 
 ssl_c_key_alg : string
   Returns the name of the algorithm used to generate the key of the certificate
@@ -26413,6 +26416,9 @@ ssl_c_r_dn([<entry>[,<occ>[,<format>]]]) : string
   different protocols. Currently supported is rfc2253 for LDAP v3. If you'd like
   to modify the format only you can specify an empty string and zero for the
   first two parameters. Example: ssl_c_r_dn(,0,rfc2253)
+  If the requested entry's ASN.1 value contains an embedded NUL byte
+  followed by other data, it is considered malformed and no data is
+  returned.
 
 ssl_c_s_dn([<entry>[,<occ>[,<format>]]]) : string
   When the incoming connection was made over an SSL/TLS transport layer,
@@ -26428,6 +26434,9 @@ ssl_c_s_dn([<entry>[,<occ>[,<format>]]]) : string
   LDAP v3.
   If you'd like to modify the format only you can specify an empty string
   and zero for the first two parameters. Example: ssl_c_s_dn(,0,rfc2253)
+  If the requested entry's ASN.1 value contains an embedded NUL byte
+  followed by other data, it is considered malformed and no data is
+  returned.
 
 ssl_c_san : string
   When the incoming connection was made over an SSL/TLS transport layer, and was
@@ -26499,6 +26508,9 @@ ssl_f_i_dn([<entry>[,<occ>[,<format>]]]) : string
   LDAP v3.
   If you'd like to modify the format only you can specify an empty string
   and zero for the first two parameters. Example: ssl_f_i_dn(,0,rfc2253)
+  If the requested entry's ASN.1 value contains an embedded NUL byte
+  followed by other data, it is considered malformed and no data is
+  returned.
 
 ssl_f_key_alg : string
   Returns the name of the algorithm used to generate the key of the certificate
@@ -26529,6 +26541,9 @@ ssl_f_s_dn([<entry>[,<occ>[,<format>]]]) : string
   LDAP v3.
   If you'd like to modify the format only you can specify an empty string
   and zero for the first two parameters. Example: ssl_f_s_dn(,0,rfc2253)
+  If the requested entry's ASN.1 value contains an embedded NUL byte
+  followed by other data, it is considered malformed and no data is
+  returned.
 
 ssl_f_serial : binary
   Returns the serial of the certificate presented by the frontend when the
@@ -26954,6 +26969,9 @@ ssl_s_i_dn([<entry>[,<occ>[,<format>]]]) : string
   LDAP v3.
   If you'd like to modify the format only you can specify an empty string
   and zero for the first two parameters. Example: ssl_s_i_dn(,0,rfc2253)
+  If the requested entry's ASN.1 value contains an embedded NUL byte
+  followed by other data, it is considered malformed and no data is
+  returned.
 
 ssl_s_key_alg : string
   Returns the name of the algorithm used to generate the key of the certificate
@@ -26984,6 +27002,9 @@ ssl_s_s_dn([<entry>[,<occ>[,<format>]]]) : string
   LDAP v3.
   If you'd like to modify the format only you can specify an empty string
   and zero for the first two parameters. Example: ssl_s_s_dn(,0,rfc2253)
+  If the requested entry's ASN.1 value contains an embedded NUL byte
+  followed by other data, it is considered malformed and no data is
+  returned.
 
 ssl_s_serial : binary
   Returns the serial of the certificate presented by the server when the
index 836e44cc21b3a8f070fd1649edabbacb4c8c360d..61f410629221c2fe5a0b5a37d1f6cc1a9f5d71be 100644 (file)
@@ -194,6 +194,7 @@ int ssl_sock_get_dn_entry(__X509_NAME_CONST__ X509_NAME *a, const struct buffer
        __X509_NAME_CONST__ ASN1_OBJECT *obj;
        __X509_NAME_CONST__ ASN1_STRING *data;
        const unsigned char *data_ptr;
+       const unsigned char *nul;
        int data_len;
        int i, j, n;
        int cur = 0;
@@ -235,6 +236,21 @@ int ssl_sock_get_dn_entry(__X509_NAME_CONST__ X509_NAME *a, const struct buffer
                if (data_len > out->size)
                        return -1;
 
+               /* reject embedded NUL (truncation/injection risk for NUL-terminated
+                * consumers); trailing NUL(s) carry nothing after them so they're
+                * harmless padding, just keep one and skip the extra ones
+                */
+               nul = memchr(data_ptr, 0, data_len);
+               if (nul) {
+                       const unsigned char *p;
+
+                       for (p = nul; p < data_ptr + data_len; p++) {
+                               if (*p)
+                                       return 0;
+                       }
+                       data_len = nul - data_ptr + 1;
+               }
+
                memcpy(out->area, data_ptr, data_len);
                out->data = data_len;
                return 1;