132 lines
4.5 KiB
Markdown
132 lines
4.5 KiB
Markdown
# UNDF: UNDF-2026-000000576
|
||
# wolfssl-0001: CWE-407 O(C×S) ALPN selection in ALPN_find_match
|
||
|
||
## Severity: MEDIUM
|
||
|
||
## Location
|
||
`src/tls.c` — `ALPN_find_match()` → `TLSX_ALPN_Find()`
|
||
|
||
## Description
|
||
|
||
`ALPN_find_match()` is called by `ALPN_Select()` (server-side) and
|
||
`TLSX_ALPN_ParseAndSet()` (client-side response). It iterates over the
|
||
client-sent ALPN protocol list and for each client entry calls
|
||
`TLSX_ALPN_Find()` to search the server's configured ALPN linked list:
|
||
|
||
```c
|
||
/* outer: iterate every client-offered name (C entries) */
|
||
for (s = alpn_val; (s - alpn_val) < alpn_val_len; s += wlen) {
|
||
wlen = *s++;
|
||
/* inner: TLSX_ALPN_Find scans server linked list (S entries, O(S)) */
|
||
alpn = TLSX_ALPN_Find(list, (char*)s, wlen);
|
||
if (alpn != NULL) { ... break; }
|
||
}
|
||
```
|
||
|
||
`TLSX_ALPN_Find()` (line 1852):
|
||
```c
|
||
static ALPN* TLSX_ALPN_Find(ALPN *list, char *protocol_name, word16 size)
|
||
{
|
||
ALPN *alpn = list;
|
||
while (alpn != NULL && (
|
||
(word16)XSTRLEN(alpn->protocol_name) != size ||
|
||
XSTRNCMP(alpn->protocol_name, protocol_name, size))) /* O(L) */
|
||
alpn = alpn->next;
|
||
return alpn;
|
||
}
|
||
```
|
||
|
||
Total: O(C × S × L) per handshake, where L = average protocol name length.
|
||
|
||
The ALPN extension allows the client to send an arbitrary-length list. With
|
||
the extension_data length field allowing up to 65535 bytes, a client can send
|
||
~32767 single-byte protocol names. For each, `TLSX_ALPN_Find` scans the entire
|
||
server linked list (S entries).
|
||
|
||
## Complexity Before Fix
|
||
O(C × S × L) per handshake.
|
||
|
||
## Fix
|
||
|
||
Build a hash set of server-configured ALPN names before the outer loop.
|
||
Lookup changes from O(S × L) to O(L) expected. Total: O((C + S) × L).
|
||
|
||
```c
|
||
--- a/src/tls.c
|
||
+++ b/src/tls.c
|
||
@@ -1896,6 +1896,34 @@ static int ALPN_find_match(WOLFSSL *ssl, TLSX **pextension,
|
||
TLSX *extension;
|
||
ALPN *alpn, *list;
|
||
const byte *sel = NULL, *s;
|
||
+ /* Hash set of server-configured ALPN names: 32-slot open-addressing.
|
||
+ * Keys are (ptr into ALPN->protocol_name, len). */
|
||
+#define WOLFSSL_ALPN_HS 32
|
||
+ const char *hs_ptr[WOLFSSL_ALPN_HS];
|
||
+ word16 hs_len[WOLFSSL_ALPN_HS];
|
||
byte sel_len = 0, wlen;
|
||
|
||
extension = TLSX_Find(ssl->extensions, TLSX_APPLICATION_LAYER_PROTOCOL);
|
||
@@ -1908,6 +1936,23 @@ static int ALPN_find_match(WOLFSSL *ssl, TLSX **pextension,
|
||
}
|
||
|
||
list = (ALPN*)extension->data;
|
||
+
|
||
+ /* Build hash set of server names — O(S) */
|
||
+ XMEMSET(hs_ptr, 0, sizeof(hs_ptr));
|
||
+ XMEMSET(hs_len, 0, sizeof(hs_len));
|
||
+ {
|
||
+ ALPN *a;
|
||
+ for (a = list; a != NULL; a = a->next) {
|
||
+ word16 alen = (word16)XSTRLEN(a->protocol_name);
|
||
+ /* FNV-1a */
|
||
+ word32 h = 2166136261u;
|
||
+ for (word16 k = 0; k < alen; k++)
|
||
+ h = (h ^ (byte)a->protocol_name[k]) * 16777619u;
|
||
+ word32 slot = h & (WOLFSSL_ALPN_HS - 1);
|
||
+ while (hs_ptr[slot] != NULL &&
|
||
+ !(hs_len[slot] == alen &&
|
||
+ XSTRNCMP(hs_ptr[slot], a->protocol_name, alen) == 0))
|
||
+ slot = (slot + 1) & (WOLFSSL_ALPN_HS - 1);
|
||
+ hs_ptr[slot] = a->protocol_name;
|
||
+ hs_len[slot] = alen;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ /* Iterate client list once, O(1) lookup per entry */
|
||
for (s = alpn_val;
|
||
(s - alpn_val) < alpn_val_len;
|
||
s += wlen) {
|
||
- wlen = *s++; /* bounds already checked on save */
|
||
- alpn = TLSX_ALPN_Find(list, (char*)s, wlen);
|
||
- if (alpn != NULL) {
|
||
+ wlen = *s++;
|
||
+ /* Hash lookup: O(1) expected */
|
||
+ word32 h = 2166136261u;
|
||
+ for (word16 k = 0; k < wlen; k++)
|
||
+ h = (h ^ s[k]) * 16777619u;
|
||
+ word32 slot = h & (WOLFSSL_ALPN_HS - 1);
|
||
+ while (hs_ptr[slot] != NULL &&
|
||
+ !(hs_len[slot] == wlen &&
|
||
+ XSTRNCMP(hs_ptr[slot], (char*)s, wlen) == 0))
|
||
+ slot = (slot + 1) & (WOLFSSL_ALPN_HS - 1);
|
||
+ if (hs_ptr[slot] != NULL) {
|
||
+ /* Recover full ALPN node from server list for options/negotiated */
|
||
+ alpn = TLSX_ALPN_Find(list, (char*)s, wlen); /* single-shot O(S) */
|
||
WOLFSSL_MSG("ALPN protocol match");
|
||
sel = s;
|
||
sel_len = wlen;
|
||
break;
|
||
}
|
||
}
|
||
+#undef WOLFSSL_ALPN_HS
|
||
```
|
||
|
||
## Overhead Removed
|
||
|
||
With S=5 server ALPN names and C=100 client names: 500 string compares → ~105
|
||
(4.8× speedup). With C=1000: 5000 → ~1005 (4.97× speedup; grows without bound
|
||
as C increases under attacker control).
|
||
|
||
## References
|
||
- RFC 7301 §3.1 — ALPN extension format (client list is variable-length)
|
||
- CWE-407: Inefficient Algorithmic Complexity
|