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.
4.5 KiB
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.
- Confirm receipt and assign a GitHub Security Advisory or JIRA reference.
- Assess severity — spring-0001 fires on every hierarchical application context bean resolution.
- Coordinate a disclosure date — we are targeting 90 days from first contact.
- 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.