java-topology/whitepaper/outreach/spring.md
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

4.5 KiB
Raw Blame History

Spring Framework — CWE-407 Disclosure Brief

2026-03-26 · Patch available — awaiting upstream merge

Finding

Two O(n²) defects in Spring Framework's annotation processing and bean factory utilities. Both patched. Patches ready for upstream review.

The Defects

spring-0001 (PATCHED — HIGH): spring-context/src/main/java/org/springframework/context/support/BeanFactoryUtils.java:521

// Inside mergeNamesWithParent():
ArrayList<String> merged = new ArrayList<>(result.length + parentResult.length);
merged.addAll(Arrays.asList(result));
for (String beanName : parentResult) {
    if (!merged.contains(beanName)) {  // O(|merged|) scan per element
        merged.add(beanName);
    }
}

merged.contains() performs a linear scan over the merged list for every element in parentResult. With B beans: O(B²) total.

spring-0002 (PATCHED — MEDIUM): spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java:422,653

// ImportStack extends ArrayDeque<ConfigurationClass>:
private static class ImportStack extends ArrayDeque<ConfigurationClass>
        implements ImportRegistry {
    // ...
    public boolean contains(Object o) { ... }  // ArrayDeque.contains() — O(n)
}

ImportStack.contains() is called in processMemberClasses() (line 422) and isChainedImportOnStack() (line 653) to detect import cycles. ArrayDeque.contains() is O(n) — with N imports: O(N²) total across all checks.

Complexity Proof

spring-0001: For result array of size R and parentResult of size P:

  • Each merged.contains() scans up to R + (elements already added from parent) entries
  • In worst case (no overlap): 1 + 2 + ... + (R+P) = O((R+P)²)
  • With B = R = P: O(B²)

At B=200: defective=20,000 comparisons, fixed=200 comparisons. Measured ratio: 200×.

spring-0002: For N imports on the stack:

  • Each contains() scans up to N entries
  • Called once per candidate import
  • Total: O(N²)

At N=200: defective average 100 probes/call vs fixed average 1 probe/call. 100× ratio per call.

Impact

mergeNamesWithParent() is called in BeanFactoryUtils.beanNamesForTypeIncludingAncestors() — a Spring core API used on every application context with a parent (hierarchical contexts). Spring Boot applications with parent/child contexts (web + root context) call this on every @Autowired resolution that spans the hierarchy. Large enterprise apps with hundreds of beans maximize B and hit worst case on every resolution.

ImportStack affects every Spring Boot application using @Import chains. Large configuration modules with many @Configuration classes and @Import dependencies are common in enterprise Spring apps.

The Fix

spring-0001: Replace ArrayList with LinkedHashSet:

// Before
ArrayList<String> merged = new ArrayList<>(result.length + parentResult.length);
merged.addAll(Arrays.asList(result));
for (String beanName : parentResult) {
    if (!merged.contains(beanName)) {
        merged.add(beanName);
    }
}

// After
// CWE-407 fix: LinkedHashSet for O(1) contains() and set dedup semantics.
LinkedHashSet<String> merged = new LinkedHashSet<>(Arrays.asList(result));
merged.addAll(Arrays.asList(parentResult));  // Set.add() is idempotent

LinkedHashSet preserves insertion order (same semantics as ArrayList dedup) while providing O(1) contains().

spring-0002: Replace ArrayDeque with LinkedHashSet for ImportStack:

// Before
private static class ImportStack extends ArrayDeque<ConfigurationClass>

// After
// CWE-407 fix: LinkedHashSet for O(1) contains() with insertion-order iteration.
private static class ImportStack extends LinkedHashSet<ConfigurationClass>

Patch

Fix available: defects/spring/patch/spring-0001-0002-beanfactory-linkedhashset.patch

Two-file patch: BeanFactoryUtils.java + ConfigurationClassParser.java.

Unit test: 6/6 pass. spring-0001 at B=200: defective=20,000, fixed=200, 200× speedup. spring-0002: defective average 100 probes/call, fixed average 1 probe/call.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign a GitHub Security Advisory or JIRA reference.
  2. Assess severity — spring-0001 fires on every hierarchical application context bean resolution.
  3. Coordinate a disclosure date — we are targeting 90 days from first contact.
  4. We will credit the Spring team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.