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
200 lines
7.4 KiB
Java
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);
|
|
}
|
|
}
|