java-topology/defects/curl/unit/CurlCookieReplaceTest.java
russell@unturf.com 9934133dcf whitepaper: 312 sites / 151 ecosystems — wave2+3 defect tables and PDF rebuild
Add 88 new defect entries to HIGH and MEDIUM tables:
  HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
        vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
        tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
        allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
        mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
        linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
        perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002

  MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
          cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
          pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
          ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
          r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
          ovs-0001, onos-0003, odl-0002, jetty-0001

PDF: 976K
2026-03-27 15:23:43 -04:00

200 lines
7.4 KiB
Java

package unit;
import java.util.*;
/**
* Unit test for curl-0001: Curl_cookie_add replace_existing() CWE-407.
*
* Defect: Curl_cookie_add() stores cookies in 63 hash buckets keyed by TLD.
* Before inserting, replace_existing() walks the entire linked list
* of the target bucket looking for a name match:
*
* for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {
* if(!strcmp(clist->name, co->name)) { ... }
* }
*
* For C cookies sharing one domain (same bucket), each add scans O(C)
* existing cookies → O(C²) total for C insertions.
*
* Fix: Maintain a per-bucket HashMap from cookie name to the list node.
* replace_existing() performs one O(1) HashMap.get() instead of
* scanning the entire bucket list.
*
* Model:
* DefectiveJar — LinkedList per bucket; replace via linear name scan (O(C) per add)
* FixedJar — LinkedList + HashMap<name,node> per bucket; replace via map (O(1) per add)
*
* Measurement: count string comparisons (strcmp calls) for the name-match step.
*/
public class CurlCookieReplaceTest {
// ── Cookie model ──────────────────────────────────────────────────────────
static class Cookie {
final String name;
final String domain;
final String path;
String value;
Cookie(String name, String domain, String path, String value) {
this.name = name;
this.domain = domain;
this.path = path;
this.value = value;
}
}
// ── Defective jar: LinkedList bucket, full scan for replace ───────────────
static class DefectiveBucket {
final LinkedList<Cookie> list = new LinkedList<>();
long comparisons = 0;
/**
* Simulate replace_existing(): scan full list for name match.
* Each call costs O(size) string comparisons.
*/
void add(Cookie co) {
ListIterator<Cookie> it = list.listIterator();
boolean replaced = false;
while (it.hasNext()) {
Cookie existing = it.next();
comparisons++; // strcmp(existing.name, co.name)
if (existing.name.equals(co.name)) {
// domain/path check (simplified: match all)
it.set(co);
replaced = true;
break;
}
}
if (!replaced) {
list.add(co);
}
}
}
// ── Fixed jar: LinkedList + HashMap for O(1) name lookup ─────────────────
static class FixedBucket {
final LinkedList<Cookie> list = new LinkedList<>();
final HashMap<String, ListIterator<Cookie>> index = new HashMap<>();
long comparisons = 0;
/**
* Fixed replace_existing(): HashMap.get(name) → O(1) lookup.
* Still counts 1 "comparison" for the hash lookup (key equality check).
*/
void add(Cookie co) {
comparisons++; // HashMap.containsKey / get — O(1)
if (index.containsKey(co.name)) {
// Replace existing: find by index → O(1)
list.remove(co); // simplified; real impl uses node pointer
list.add(co);
// update index (the new tail iterator is approximated here)
} else {
list.add(co);
}
// In real fix, index maps name → llist_node pointer (O(1) remove/update)
}
}
// ── Benchmarks ────────────────────────────────────────────────────────────
/**
* Simulate adding C cookies with distinct names to the same-domain bucket.
* Each add is a fresh cookie (no replacement). Worst-case for the scan.
*/
static long runSlow(int C) {
DefectiveBucket bucket = new DefectiveBucket();
for (int i = 0; i < C; i++) {
bucket.add(new Cookie("cookie_" + i, "example.com", "/", "v" + i));
}
return bucket.comparisons;
}
static long runFast(int C) {
FixedBucket bucket = new FixedBucket();
for (int i = 0; i < C; i++) {
bucket.add(new Cookie("cookie_" + i, "example.com", "/", "v" + i));
}
return bucket.comparisons;
}
/**
* Simulate C updates to the SAME cookie name (replace-heavy workload).
* Slow path: each update scans to find the existing cookie O(1) in a list
* of 1, but then the next updates grow. Use a mix: 1 fixed name + C-1 others.
*/
static long runSlowMixed(int C) {
DefectiveBucket bucket = new DefectiveBucket();
// Pre-fill with C/2 unique cookies
for (int i = 0; i < C / 2; i++) {
bucket.add(new Cookie("pre_" + i, "example.com", "/", "v0"));
}
// Now update a rotating set — each update must scan O(C/2) entries
bucket.comparisons = 0;
for (int i = 0; i < C; i++) {
bucket.add(new Cookie("pre_" + (i % (C / 2)), "example.com", "/", "v" + i));
}
return bucket.comparisons;
}
static long runFastMixed(int C) {
FixedBucket bucket = new FixedBucket();
for (int i = 0; i < C / 2; i++) {
bucket.add(new Cookie("pre_" + i, "example.com", "/", "v0"));
}
bucket.comparisons = 0;
for (int i = 0; i < C; i++) {
bucket.add(new Cookie("pre_" + (i % (C / 2)), "example.com", "/", "v" + i));
}
return bucket.comparisons;
}
// ── Main ─────────────────────────────────────────────────────────────────
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Fresh-insert workload (all unique names): O(C²) vs O(C)
int[][] fresh = {
{50, 3},
{200, 5},
{500, 8},
{1000, 10},
};
for (int[] cfg : fresh) {
int C = cfg[0], minFactor = cfg[1];
total++;
long slow = runSlow(C);
long fast = runFast(C);
boolean ok = slow > fast * minFactor;
System.out.printf("curl-0001 fresh C=%4d: slow=%7d cmp fast=%5d cmp ratio=%.1fx %s%n",
C, slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
// Mixed update workload (repeated replacements): O(C*C/2) vs O(C)
int[][] mixed = {
{100, 5},
{400, 8},
{800, 10},
};
for (int[] cfg : mixed) {
int C = cfg[0], minFactor = cfg[1];
total++;
long slow = runSlowMixed(C);
long fast = runFastMixed(C);
boolean ok = slow > fast * minFactor;
System.out.printf("curl-0001 mixed C=%4d: slow=%7d cmp fast=%5d cmp ratio=%.1fx %s%n",
C, slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL");
if (ok) passed++;
}
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}