imagemagick/gimp: CWE-407 findings

imagemagick-0001: UHDR coder GetImageListLength() O(N) in for-loop = O(N²) MEDIUM
imagemagick-0002: SyncImageList nested scene-dedup O(N²) MEDIUM
gimp-0001: layer_stack_cmp g_list_find in loop O(S²×L²) MEDIUM
gimp-0002: remove_from_layer_stack nested g_list_remove O(C×S×L) LOW-MEDIUM

4 defects total, 4/4 unit tests PASS
This commit is contained in:
russell@unturf.com 2026-03-30 11:21:24 -04:00
parent b5c2a3d989
commit 9b60b61e9c
4 changed files with 304 additions and 0 deletions

View file

@ -0,0 +1,160 @@
import java.util.*;
/**
* CWE-407 simulation tests for GIMP defects.
*
* gimp-0001: layer stack dedup — O(S² × L²) via nested g_list_find
* gimp-0002: remove_from_layer_stack — O(C × S × L) nested g_list_remove
*/
public class GimpTest {
// ---------------------------------------------------------------
// gimp-0001: layer stack dedup O(S² × L²)
// ---------------------------------------------------------------
/** Simulate GList-based layer stack comparison (defective) */
static long layerStackCmpDefective(List<Object> layers1, List<Object> layers2) {
long ops = 0;
// g_list_length — O(L) each
ops += layers1.size(); // simulated traversal
ops += layers2.size();
if (layers1.size() != layers2.size())
return ops;
// g_list_find in loop — O(L²)
for (Object item : layers1) {
for (Object item2 : layers2) {
ops++;
if (item == item2) break;
}
}
return ops;
}
/** Simulate GHashTable-based comparison (fixed) */
static long layerStackCmpFixed(List<Object> layers1, List<Object> layers2) {
long ops = 0;
ops += layers1.size();
ops += layers2.size();
if (layers1.size() != layers2.size())
return ops;
// Build hash set from layers2: O(L)
Set<Object> set = new HashSet<>(layers2);
ops += layers2.size();
// Membership test: O(L)
for (Object item : layers1) {
ops++;
if (!set.contains(item)) break;
}
return ops;
}
/** Simulate rec_remove_layer_stack_dups — defective O(S² × cmp) */
static long recRemoveDupsDefective(List<List<Object>> stack) {
long ops = 0;
for (int i = 0; i < stack.size(); i++) {
// g_slist_find_custom scans remaining entries
for (int j = i + 1; j < stack.size(); j++) {
ops += layerStackCmpDefective(stack.get(i), stack.get(j));
}
}
return ops;
}
/** Simulate fixed dedup — O(S × L) using hash */
static long recRemoveDupsFixed(List<List<Object>> stack) {
long ops = 0;
Set<Set<Object>> seen = new HashSet<>();
for (List<Object> entry : stack) {
Set<Object> entrySet = new HashSet<>(entry);
ops += entry.size(); // building set
ops++; // hash lookup
seen.add(entrySet);
}
return ops;
}
static void testLayerStackDedup() {
int S = 50; // stack entries (undo states)
int L = 10; // layers per selection
// Build stack with unique selections (worst case)
List<List<Object>> stack = new ArrayList<>();
for (int i = 0; i < S; i++) {
List<Object> selection = new ArrayList<>();
for (int j = 0; j < L; j++) {
selection.add(new Object()); // unique objects
}
stack.add(selection);
}
long defectOps = recRemoveDupsDefective(stack);
long fixedOps = recRemoveDupsFixed(stack);
double ratio = (double) defectOps / fixedOps;
System.out.printf("gimp-0001 layer stack dedup:%n");
System.out.printf(" S=%d L=%d defect_ops=%d fixed_ops=%d ratio=%.1fx%n",
S, L, defectOps, fixedOps, ratio);
assert ratio > 20.0 : "Expected significant overhead, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// gimp-0002: remove_from_layer_stack O(C × S × L)
// ---------------------------------------------------------------
/** Simulate defective removal: g_list_remove per child per stack entry */
static long removeFromStackDefective(int children, int stackSize, int layersPerEntry) {
long ops = 0;
// For each child (plus the layer itself = children+1)
for (int c = 0; c < children + 1; c++) {
// For each stack entry
for (int s = 0; s < stackSize; s++) {
// g_list_remove scans the list: O(L)
for (int l = 0; l < layersPerEntry; l++) {
ops++;
}
}
}
return ops;
}
/** Simulate fixed removal: hash set + single pass per stack entry */
static long removeFromStackFixed(int children, int stackSize, int layersPerEntry) {
long ops = 0;
// Build hash set of all layers to remove: O(C)
ops += children + 1;
// Single pass over each stack entry: O(S × L)
for (int s = 0; s < stackSize; s++) {
for (int l = 0; l < layersPerEntry; l++) {
ops++; // hash lookup per layer
}
}
return ops;
}
static void testRemoveFromLayerStack() {
int C = 20; // children in group layer
int S = 50; // undo stack entries
int L = 20; // layers per stack entry
long defectOps = removeFromStackDefective(C, S, L);
long fixedOps = removeFromStackFixed(C, S, L);
double ratio = (double) defectOps / fixedOps;
System.out.printf("gimp-0002 remove_from_layer_stack:%n");
System.out.printf(" C=%d S=%d L=%d defect_ops=%d fixed_ops=%d ratio=%.1fx%n",
C, S, L, defectOps, fixedOps, ratio);
assert ratio > 10.0 : "Expected significant overhead, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// Main
// ---------------------------------------------------------------
public static void main(String[] args) {
testLayerStackDedup();
testRemoveFromLayerStack();
System.out.println("\nAll GIMP CWE-407 tests PASS");
}
}