import java.util.*; /** * CWE-407 simulation: curl http_aws_sigv4.c make_headers() bubble sort * * curl/lib/http_aws_sigv4.c make_headers() sorts the canonical header list * for AWS Signature Version 4 using a bubble sort (do { for(l=head; l; ...) } * while(again)). Complexity: O(H^2) where H = number of custom headers. * * Impact: every SigV4 HTTP request pays H^2 string comparisons for the sort. * At H=200 headers that is 40,000 comparisons; H=500 is 250,000 comparisons. * Fix: replace with qsort (pointer-array copy), giving O(H log H). * * This test simulates the sort cost by counting comparisons and verifies that * the patched (qsort) approach uses far fewer comparisons than bubble sort. */ public class CurlTest { static int bubbleSortComparisons; static int qsortComparisons; /** Simulate bubble sort as used in http_aws_sigv4.c */ static void bubbleSort(String[] headers) { bubbleSortComparisons = 0; boolean again = true; while (again) { again = false; for (int i = 0; i + 1 < headers.length; i++) { bubbleSortComparisons++; String a = headers[i].split(":")[0]; String b = headers[i + 1].split(":")[0]; if (a.compareTo(b) > 0) { String tmp = headers[i]; headers[i] = headers[i + 1]; headers[i + 1] = tmp; again = true; } } } } /** Simulate fixed approach: qsort on pointer array */ static void qsortSimulated(String[] headers) { qsortComparisons = 0; // Java Arrays.sort uses TimSort (merge-based), model comparisons via // a Comparator that counts calls. Arrays.sort(headers, (a, b) -> { qsortComparisons++; String ka = a.split(":")[0]; String kb = b.split(":")[0]; return ka.compareTo(kb); }); } /** Build a worst-case (reverse-sorted) header list of size H */ static String[] buildReverseHeaders(int h) { String[] hdrs = new String[h]; for (int i = 0; i < h; i++) { // reverse alphabetical: "z-header", "y-header", ... char c = (char) ('z' - (i % 26)); int seq = i / 26; hdrs[i] = c + "-header-" + seq + ": value" + i; } return hdrs; } /** Verify both algorithms produce the same sorted result */ static boolean sortedEqual(String[] a, String[] b) { if (a.length != b.length) return false; for (int i = 0; i < a.length; i++) { if (!a[i].equals(b[i])) return false; } return true; } public static void main(String[] args) { System.out.println("CWE-407 simulation: curl AWS SigV4 header bubble sort"); System.out.println("======================================================="); System.out.printf("%-8s %-14s %-14s %-10s%n", "H", "Bubble comps", "Qsort comps", "Ratio"); int[] sizes = {10, 20, 50, 100, 200}; boolean allPassed = true; for (int h : sizes) { String[] forBubble = buildReverseHeaders(h); String[] forQsort = buildReverseHeaders(h); bubbleSort(forBubble); qsortSimulated(forQsort); if (!sortedEqual(forBubble, forQsort)) { System.out.println("FAIL: sort results differ at H=" + h); allPassed = false; continue; } double ratio = (double) bubbleSortComparisons / qsortComparisons; System.out.printf("%-8d %-14d %-14d %-10.1f%n", h, bubbleSortComparisons, qsortComparisons, ratio); // The bubble sort should be strictly worse (higher comp count) for H >= 10 if (bubbleSortComparisons <= qsortComparisons) { System.out.printf("FAIL: expected bubble > qsort at H=%d%n", h); allPassed = false; } } // Worst-case ratio check at H=100: bubble should do >= 50x more comparisons String[] t1 = buildReverseHeaders(100); String[] t2 = buildReverseHeaders(100); bubbleSort(t1); qsortSimulated(t2); double worstRatio = (double) bubbleSortComparisons / qsortComparisons; if (worstRatio < 10.0) { System.out.printf("FAIL: ratio %.1f too low at H=100 (expected >= 10x)%n", worstRatio); allPassed = false; } System.out.println(); if (allPassed) { System.out.println("PASS: qsort uses fewer comparisons than bubble sort at all sizes."); System.out.println("PASS: both algorithms produce identical sorted output."); } else { System.out.println("FAIL"); System.exit(1); } } }