java-topology/defects/ffmpeg/unit/FFmpegTest.java
russell@unturf.com fbc56da95b curl/ffmpeg: CWE-407 findings — awssigv4 bubble sort + filtergraph format merge
curl-0001 (UNDF-2026-000000040): lib/http_aws_sigv4.c make_headers() sorts
canonical SigV4 headers using an O(H^2) bubble sort (do/while/for). Every
SigV4 HTTP request pays H^2 comparisons. At H=200: 35,024 vs 794 (44x).
Fix: qsort on a temporary pointer array, O(H log H).

ffmpeg-0001 (UNDF-2026-000000070): libavfilter/formats.c MERGE_FORMATS macro
intersects two pixel-format lists via nested loop O(A*B). With ~380 pixel
formats (AV_PIX_FMT_NB), worst case is 144,400 comparisons per link per
avfilter_graph_config() call. At A=B=380: 72,390 vs 3,420 (21x).
Fix: sort b, bsearch for membership, O((A+B) log B).

Unit tests: CurlTest.java, FFmpegTest.java — both PASS.
2026-03-30 09:38:07 -04:00

154 lines
6 KiB
Java

import java.util.*;
/**
* CWE-407 simulation: FFmpeg libavfilter/formats.c MERGE_FORMATS nested loop
*
* libavfilter/formats.c MERGE_FORMATS macro intersects two format lists using
* a nested loop: for i in a.formats: for j in b.formats: if a[i]==b[j] ...
* Complexity: O(A * B) where A = |a->nb_formats|, B = |b->nb_formats|.
*
* This is called during avfilter_graph_config() format negotiation for every
* link in the filter graph. AVPixelFormat has ~380 entries (AV_PIX_FMT_NB).
* A filter that accepts all formats triggers worst-case A=B=380:
* 380 * 380 = 144,400 comparisons per link.
* A graph with L links pays L * 144,400 comparisons just for format merging.
*
* Fix: sort a copy of b, then use binary search for O(log B) membership test.
* Overall: O((A+B) log B) — at A=B=380 that is ~3,420 comparisons, 42x fewer.
*
* This test simulates both approaches and verifies correctness + speedup.
*/
public class FFmpegTest {
static int nestedLoopOps;
static int bsearchOps;
/**
* Simulate the original MERGE_FORMATS nested-loop intersection.
* Returns the intersection set.
*/
static int[] mergeFormatsNested(int[] a, int[] b) {
nestedLoopOps = 0;
List<Integer> result = new ArrayList<>();
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < b.length; j++) {
nestedLoopOps++;
if (a[i] == b[j]) {
result.add(a[i]);
break;
}
}
}
return result.stream().mapToInt(Integer::intValue).toArray();
}
/**
* Simulate the patched MERGE_FORMATS: sort b, then bsearch for each a[i].
* Returns the intersection set.
*/
static int[] mergeFormatsBsearch(int[] a, int[] b) {
bsearchOps = 0;
// Sort b (O(B log B))
int[] sortedB = b.clone();
Arrays.sort(sortedB); // counts as B log B but we only count comparisons
// For each a[i], bsearch sortedB
List<Integer> result = new ArrayList<>();
for (int i = 0; i < a.length; i++) {
bsearchOps++; // one binary search per a[i], modeled as log(B) ops
// (Java's Arrays.binarySearch is O(log B); we count it as 1 unit
// representing log(B) comparisons for clarity)
int idx = Arrays.binarySearch(sortedB, a[i]);
if (idx >= 0) {
result.add(a[i]);
}
}
return result.stream().mapToInt(Integer::intValue).toArray();
}
/**
* Build a synthetic pixel-format list of size n drawn from [0, maxFmt).
* Represents a filter that supports n distinct pixel formats.
*/
static int[] buildFormatList(int n, int maxFmt, long seed) {
Random rng = new Random(seed);
Set<Integer> seen = new LinkedHashSet<>();
while (seen.size() < n) {
seen.add(rng.nextInt(maxFmt));
}
return seen.stream().mapToInt(Integer::intValue).toArray();
}
/** Check that two sorted int arrays are equal */
static boolean equal(int[] a, int[] b) {
int[] sa = a.clone(); Arrays.sort(sa);
int[] sb = b.clone(); Arrays.sort(sb);
return Arrays.equals(sa, sb);
}
public static void main(String[] args) {
final int AV_PIX_FMT_NB = 380; // approximate AV_PIX_FMT_NB
System.out.println("CWE-407 simulation: FFmpeg MERGE_FORMATS nested loop");
System.out.println("=====================================================");
System.out.printf("%-8s %-8s %-14s %-14s %-10s%n",
"A", "B", "Nested ops", "Bsearch ops", "Ratio");
boolean allPassed = true;
int[][] sizes = {{50, 50}, {100, 100}, {200, 200}, {380, 380}};
for (int[] sz : sizes) {
int A = sz[0], B = sz[1];
int[] fmtA = buildFormatList(A, AV_PIX_FMT_NB, 42L);
int[] fmtB = buildFormatList(B, AV_PIX_FMT_NB, 99L);
int[] resNested = mergeFormatsNested(fmtA, fmtB);
int nested = nestedLoopOps;
int[] resBsearch = mergeFormatsBsearch(fmtA, fmtB);
// bsearch units represent log(B) comparisons each; scale for display
int bsearchScaled = (int)(bsearchOps * Math.ceil(Math.log(B) / Math.log(2)));
if (!equal(resNested, resBsearch)) {
System.out.printf("FAIL: intersection results differ at A=%d B=%d%n", A, B);
allPassed = false;
continue;
}
double ratio = (double) nested / Math.max(bsearchScaled, 1);
System.out.printf("%-8d %-8d %-14d %-14d %-10.1f%n",
A, B, nested, bsearchScaled, ratio);
if (nested <= bsearchScaled && A >= 50) {
System.out.printf(
"FAIL: expected nested > bsearch at A=%d B=%d%n", A, B);
allPassed = false;
}
}
// Worst-case check: at A=B=380, nested should be >= 10x bsearch ops
int[] fmtA = buildFormatList(AV_PIX_FMT_NB, AV_PIX_FMT_NB, 1L);
int[] fmtB = buildFormatList(AV_PIX_FMT_NB, AV_PIX_FMT_NB, 2L);
mergeFormatsNested(fmtA, fmtB);
int wNested = nestedLoopOps;
mergeFormatsBsearch(fmtA, fmtB);
int wBsearch = (int)(bsearchOps * Math.ceil(Math.log(AV_PIX_FMT_NB) / Math.log(2)));
double wRatio = (double) wNested / Math.max(wBsearch, 1);
System.out.printf("%nWorst-case A=B=%d: nested=%d bsearch~=%d ratio=%.1fx%n",
AV_PIX_FMT_NB, wNested, wBsearch, wRatio);
if (wRatio < 5.0) {
System.out.printf("FAIL: ratio %.1f too low (expected >= 5x)%n", wRatio);
allPassed = false;
}
System.out.println();
if (allPassed) {
System.out.println("PASS: bsearch approach uses fewer operations at all sizes.");
System.out.println("PASS: both approaches produce identical intersection results.");
} else {
System.out.println("FAIL");
System.exit(1);
}
}
}