java-topology/defects/mbedtls/patch/mbedtls-0001-alpn-parse-ext.md

4.5 KiB
Raw Permalink Blame History

UNDF: UNDF-2026-000000458

mbedtls-0001: CWE-407 O(S×C) ALPN selection in mbedtls_ssl_parse_alpn_ext

Severity: MEDIUM

Location

library/ssl_tls.cmbedtls_ssl_parse_alpn_ext()

Description

mbedtls_ssl_parse_alpn_ext() selects a common ALPN protocol from the client's ClientHello extension. The server has a NULL-terminated list of S configured protocol names (ssl->conf->alpn_list); the client sends a length-prefixed list of C protocol names.

The current implementation:

/* outer: iterate every server-configured ALPN name (S entries) */
for (const char *const *alpn = ssl->conf->alpn_list; *alpn != NULL; alpn++) {
    size_t const alpn_len = strlen(*alpn);
    p = protocol_name_list;
    /* inner: scan entire client list for each server entry */
    while (p < protocol_name_list_end) {
        protocol_name_len = *p++;
        if (protocol_name_len == alpn_len &&
            memcmp(p, *alpn, alpn_len) == 0) {   /* O(L) per call */
            ssl->alpn_chosen = *alpn;
            return 0;
        }
        p += protocol_name_len;
    }
}

Total work: O(S × C × L) where L = average protocol name length.

An attacker can send a ClientHello with C distinct ALPN names (the extension length field allows up to 65535 bytes; at a minimum name length of 1 byte each that is ~32767 names). Each triggers a full scan of the server's ALPN list.

Complexity Before Fix

O(S × C × L) per handshake.

Fix

Build a hash map of the client's ALPN list once (O(C×L)), then look up each server-preferred entry in O(L) expected. Total: O((C + S) × L).

--- a/library/ssl_tls.c
+++ b/library/ssl_tls.c
@@ -8401,6 +8401,10 @@ int mbedtls_ssl_parse_alpn_ext(mbedtls_ssl_context *ssl,
 {
     const unsigned char *p = buf;
     size_t protocol_name_list_len;
+    /* Hash set: store (ptr, len) of each client name; 64-slot open-addressing */
+#define ALPN_HS 64
+    const unsigned char *hs_ptr[ALPN_HS];
+    uint8_t              hs_len[ALPN_HS];
     const unsigned char *protocol_name_list;
     const unsigned char *protocol_name_list_end;
     size_t protocol_name_len;
@@ -8441,15 +8445,30 @@ int mbedtls_ssl_parse_alpn_ext(mbedtls_ssl_context *ssl,
         p += protocol_name_len;
     }

-    /* Use our order of preference */
-    for (const char *const *alpn = ssl->conf->alpn_list; *alpn != NULL; alpn++) {
-        size_t const alpn_len = strlen(*alpn);
-        p = protocol_name_list;
-        while (p < protocol_name_list_end) {
-            protocol_name_len = *p++;
-            if (protocol_name_len == alpn_len &&
-                memcmp(p, *alpn, alpn_len) == 0) {
-                ssl->alpn_chosen = *alpn;
-                return 0;
-            }
-            p += protocol_name_len;
+    /* Build hash set of client-offered names (key = content, len pair) */
+    memset(hs_ptr, 0, sizeof(hs_ptr));
+    memset(hs_len, 0, sizeof(hs_len));
+    p = protocol_name_list;
+    while (p < protocol_name_list_end) {
+        protocol_name_len = *p++;
+        /* FNV-1a hash of content bytes */
+        uint32_t h = 2166136261u;
+        for (size_t k = 0; k < protocol_name_len; k++)
+            h = (h ^ p[k]) * 16777619u;
+        uint32_t slot = h & (ALPN_HS - 1);
+        while (hs_ptr[slot] != NULL &&
+               !(hs_len[slot] == protocol_name_len &&
+                 memcmp(hs_ptr[slot], p, protocol_name_len) == 0))
+            slot = (slot + 1) & (ALPN_HS - 1);
+        hs_ptr[slot] = p;
+        hs_len[slot] = (uint8_t) protocol_name_len;
+        p += protocol_name_len;
+    }
+
+    /* Use our order of preference — now O(S) with O(1) lookup per entry */
+    for (const char *const *alpn = ssl->conf->alpn_list; *alpn != NULL; alpn++) {
+        size_t const alpn_len = strlen(*alpn);
+        uint32_t h = 2166136261u;
+        for (size_t k = 0; k < alpn_len; k++)
+            h = (h ^ (unsigned char)(*alpn)[k]) * 16777619u;
+        uint32_t slot = h & (ALPN_HS - 1);
+        while (hs_ptr[slot] != NULL) {
+            if (hs_len[slot] == alpn_len &&
+                memcmp(hs_ptr[slot], *alpn, alpn_len) == 0) {
+                ssl->alpn_chosen = *alpn;
+                return 0;
+            }
+            slot = (slot + 1) & (ALPN_HS - 1);
         }
     }
+#undef ALPN_HS

Overhead Removed

With S=10 server ALPN names and C=100 client ALPN names: ~1000 memcmp calls → ~110 (10× speedup). With C=1000: ~10000 → ~1010 (10× speedup, grows unbounded with client-controlled C).

References

  • RFC 7301 §3.1 — ALPN extension format (client list is variable-length)
  • CWE-407: Inefficient Algorithmic Complexity