package unit; import java.util.*; /** * CWE-407 unit test: moby TweakCapabilities * File: daemon/pkg/oci/caps/utils.go — TweakCapabilities * * Slow: for each cap in allCaps, scan capDrop slice → O(n²) * Fast: build a HashSet from capDrop first → O(n) * * Run: javac -d . TweakCapabilitiesAlgorithm.java && java -ea unit.TweakCapabilitiesAlgorithm */ public class TweakCapabilitiesAlgorithm { // ── slow implementation (mirrors defective Go code) ────────────────────── static class SlowTweak { final long ops; final List result; SlowTweak(List allCaps, List capDrop) { long count = 0; List out = new ArrayList<>(); for (String c : allCaps) { // slices.Contains(capDrop, c) — linear scan boolean found = false; for (String d : capDrop) { count++; if (d.equals(c)) { found = true; break; } } if (!found) out.add(c); } this.ops = count; this.result = out; } } // ── fast implementation (proposed fix) ─────────────────────────────────── static class FastTweak { final long ops; final List result; FastTweak(List allCaps, List capDrop) { long count = 0; // Build drop set — O(|capDrop|) once Set dropSet = new HashSet<>(capDrop.size() * 2); for (String d : capDrop) { count++; dropSet.add(d); } List out = new ArrayList<>(); for (String c : allCaps) { count++; // O(1) map lookup if (!dropSet.contains(c)) out.add(c); } this.ops = count; this.result = out; } } // ── Node/Result types for test scaffolding ──────────────────────────────── static class Node { final String name; Node(String name) { this.name = name; } } static class Result { final long slowOps; final long fastOps; final List slowResult; final List fastResult; Result(long slowOps, long fastOps, List slowResult, List fastResult) { this.slowOps = slowOps; this.fastOps = fastOps; this.slowResult = slowResult; this.fastResult = fastResult; } } // ── test helpers ────────────────────────────────────────────────────────── static List makeCaps(int n) { List caps = new ArrayList<>(n); for (int i = 0; i < n; i++) caps.add("CAP_" + i); return caps; } static Result run(int nAllCaps, int nDrop) { List allCaps = makeCaps(nAllCaps); // Drop every other cap to maximise scan work List capDrop = new ArrayList<>(); for (int i = 0; i < nDrop; i++) capDrop.add("CAP_" + i); SlowTweak slow = new SlowTweak(allCaps, capDrop); FastTweak fast = new FastTweak(allCaps, capDrop); return new Result(slow.ops, fast.ops, slow.result, fast.result); } // ── tests ───────────────────────────────────────────────────────────────── static int passed = 0; static int total = 0; static void test(String name, boolean condition) { total++; if (condition) { passed++; System.out.println("PASS: " + name); } else { System.out.println("FAIL: " + name); } } public static void main(String[] args) { // T1: Realistic Linux cap count — N=41 (all caps), drop=41 { Result r = run(41, 41); // Slow should be O(n^2): up to 41*41 = 1681 ops // Fast should be O(n): ~41+41 = 82 ops test("T1-slow-is-quadratic [N=41,drop=41]", r.slowOps > r.fastOps * 5); // slow must be substantially more than fast test("T1-fast-is-linear [N=41,drop=41]", r.fastOps <= 41 + 41 + 5); // build-set + lookup + small constant double speedup = (double) r.slowOps / r.fastOps; test("T1-speedup>=10x [N=41]", speedup >= 10.0); System.out.printf(" slow=%d ops, fast=%d ops, speedup=%.1fx%n", r.slowOps, r.fastOps, speedup); // Results must match List ss = new ArrayList<>(r.slowResult); List fs = new ArrayList<>(r.fastResult); Collections.sort(ss); Collections.sort(fs); test("T1-results-match", ss.equals(fs)); } // T2: Expanded future capability set — N=200 { Result r = run(200, 200); double speedup = (double) r.slowOps / r.fastOps; test("T2-slow-is-quadratic [N=200]", r.slowOps > r.fastOps * 30); // slow must be substantially worse than fast test("T2-fast-is-linear [N=200]", r.fastOps <= 200 + 200 + 5); test("T2-speedup>=50x [N=200]", speedup >= 50.0); System.out.printf(" slow=%d ops, fast=%d ops, speedup=%.1fx%n", r.slowOps, r.fastOps, speedup); List ss = new ArrayList<>(r.slowResult); List fs = new ArrayList<>(r.fastResult); Collections.sort(ss); Collections.sort(fs); test("T2-results-match", ss.equals(fs)); } // T3: Default path — basics=14, drop=5 (typical docker run --cap-drop) { Result r = run(14, 5); test("T3-slow-ops>fast-ops [N=14,drop=5]", r.slowOps > r.fastOps); double speedup = (double) r.slowOps / r.fastOps; System.out.printf(" slow=%d ops, fast=%d ops, speedup=%.1fx%n", r.slowOps, r.fastOps, speedup); List ss = new ArrayList<>(r.slowResult); List fs = new ArrayList<>(r.fastResult); Collections.sort(ss); Collections.sort(fs); test("T3-results-match", ss.equals(fs)); } // T4: Empty drop list — no caps dropped, fast still correct { Result r = run(41, 0); test("T4-empty-drop-result-size", r.fastResult.size() == 41); test("T4-empty-drop-results-match", r.slowResult.equals(r.fastResult)); } System.out.println(); System.out.printf("%d/%d PASS%n", passed, total); if (passed != total) System.exit(1); } }