wave11: 488/227 — ROS2/OpenCV/Open3D/metaflow/kubeflow/optuna/openssl/mbedtls/wolfssl + Kafka Streams/Pulsar
This commit is contained in:
parent
6276aea2e5
commit
19333b378e
36 changed files with 4634 additions and 5 deletions
131
defects/wolfssl/patch/wolfssl-0001-alpn-find-match.md
Normal file
131
defects/wolfssl/patch/wolfssl-0001-alpn-find-match.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# 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
|
||||
143
defects/wolfssl/unit/WolfSslAlpnFindTest.java
Normal file
143
defects/wolfssl/unit/WolfSslAlpnFindTest.java
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package unit;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* wolfssl-0001 unit test
|
||||
*
|
||||
* Models the O(C×S) ALPN selection in ALPN_find_match() / TLSX_ALPN_Find()
|
||||
* and the O(C+S) fixed version using a hash set of server names.
|
||||
*
|
||||
* Compile: javac -d . WolfSslAlpnFindTest.java
|
||||
* Run: java unit.WolfSslAlpnFindTest
|
||||
*/
|
||||
public class WolfSslAlpnFindTest {
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Model TLSX_ALPN_Find: O(S) linear scan of server linked list //
|
||||
// ------------------------------------------------------------------ //
|
||||
static String tlsxAlpnFind(String[] serverList, String name) {
|
||||
for (String s : serverList) {
|
||||
if (s.length() == name.length() && s.equals(name))
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// DEFECTIVE: O(C × S) — ALPN_find_match calls TLSX_ALPN_Find(O(S)) //
|
||||
// for each of C client-offered names //
|
||||
// ------------------------------------------------------------------ //
|
||||
static String defectiveMatch(String[] serverList, String[] clientNames) {
|
||||
// Outer: iterate client list (wolfSSL uses client-preference order)
|
||||
for (String clientName : clientNames) {
|
||||
// Inner: TLSX_ALPN_Find — O(S) scan
|
||||
String found = tlsxAlpnFind(serverList, clientName);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// FIXED: O(C + S) — hash set of server names, O(1) lookup //
|
||||
// ------------------------------------------------------------------ //
|
||||
static String fixedMatch(String[] serverList, String[] clientNames) {
|
||||
// Build hash set of server names — O(S)
|
||||
HashMap<String, String> serverSet = new HashMap<>();
|
||||
for (String s : serverList) serverSet.put(s, s);
|
||||
|
||||
// Iterate client list — O(C), each lookup O(1) expected
|
||||
for (String clientName : clientNames) {
|
||||
String found = serverSet.get(clientName);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Counting versions //
|
||||
// ------------------------------------------------------------------ //
|
||||
static long[] ops = new long[2];
|
||||
|
||||
static String defectiveCounting(String[] serverList, String[] clientNames) {
|
||||
for (String cn : clientNames) {
|
||||
for (String sn : serverList) {
|
||||
ops[0]++;
|
||||
if (sn.equals(cn)) return sn;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String fixedCounting(String[] serverList, String[] clientNames) {
|
||||
HashMap<String, String> ss = new HashMap<>();
|
||||
for (String s : serverList) { ops[1]++; ss.put(s, s); }
|
||||
for (String cn : clientNames) {
|
||||
ops[1]++;
|
||||
String found = ss.get(cn);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static int pass = 0, fail = 0;
|
||||
|
||||
static void check(String label, boolean cond) {
|
||||
if (cond) { System.out.println(" PASS " + label); pass++; }
|
||||
else { System.out.println(" FAIL " + label); fail++; }
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== wolfssl-0001: ALPN_find_match O(C×S) defect ===\n");
|
||||
|
||||
// wolfSSL uses client-preference order (matches first client name found in server)
|
||||
String[] server = {"h2", "http/1.1", "grpc"};
|
||||
|
||||
// Test 1: first client name matches server
|
||||
String[] client1 = {"h2", "ftp"};
|
||||
check("defective: match h2", "h2".equals(defectiveMatch(server, client1)));
|
||||
check("fixed: match h2", "h2".equals(fixedMatch(server, client1)));
|
||||
|
||||
// Test 2: second client name matches
|
||||
String[] client2 = {"ftp", "grpc"};
|
||||
check("defective: match grpc", "grpc".equals(defectiveMatch(server, client2)));
|
||||
check("fixed: match grpc", "grpc".equals(fixedMatch(server, client2)));
|
||||
|
||||
// Test 3: client-preference wins (wolfSSL: first matching client entry)
|
||||
String[] client3 = {"grpc", "h2"};
|
||||
check("defective: client-pref → grpc", "grpc".equals(defectiveMatch(server, client3)));
|
||||
check("fixed: client-pref → grpc", "grpc".equals(fixedMatch(server, client3)));
|
||||
|
||||
// Test 4: no match
|
||||
String[] client4 = {"smtp", "pop3"};
|
||||
check("defective: no match → null", defectiveMatch(server, client4) == null);
|
||||
check("fixed: no match → null", fixedMatch(server, client4) == null);
|
||||
|
||||
// Test 5: complexity — adversarial client list
|
||||
int S = 5;
|
||||
int C = 400;
|
||||
String[] bigServer = new String[S];
|
||||
for (int i = 0; i < S; i++) bigServer[i] = "proto-server-" + i;
|
||||
|
||||
String[] bigClient = new String[C];
|
||||
for (int i = 0; i < C - 1; i++) bigClient[i] = "proto-client-" + i;
|
||||
bigClient[C - 1] = bigServer[S - 1]; // match at end — worst case
|
||||
|
||||
ops[0] = 0; ops[1] = 0;
|
||||
String rd = defectiveCounting(bigServer, bigClient);
|
||||
String rf = fixedCounting(bigServer, bigClient);
|
||||
|
||||
System.out.println("\n Complexity comparison (S=" + S + ", C=" + C + "):");
|
||||
System.out.println(" Defective ops: " + ops[0] + " (expected ~" + ((long)(C-1)*S + 1) + ")");
|
||||
System.out.println(" Fixed ops: " + ops[1] + " (expected ~" + (S + C) + ")");
|
||||
System.out.printf(" Speedup: %.1fx%n", (double) ops[0] / ops[1]);
|
||||
|
||||
check("defective and fixed agree on result",
|
||||
java.util.Objects.equals(rd, rf));
|
||||
check("defective ops > 4x fixed ops (quadratic vs linear)",
|
||||
ops[0] > 4 * ops[1]);
|
||||
|
||||
System.out.println("\n--- " + (pass + fail) + " tests: " + pass + " passed, " + fail + " failed ---");
|
||||
if (fail > 0) System.exit(1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue