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

110 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`
```java
// 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`
```java
// 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`:
```java
// 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`:
```java
// 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.