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.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,109 @@
From 5708b73 Mon Sep 17 00:00:00 2001
Subject: [PATCH] CWE-407: spring-0001/0002 — fix O(B²) contains() in
mergeNamesWithParent and ImportStack
spring-0001 (HIGH): BeanFactoryUtils.mergeNamesWithParent() used an
ArrayList for duplicate-checking, making merged.contains(beanName) an
O(|result|) scan for every element of parentResult — total O(|result| ×
|parentResult|) = O(B²) where B is total bean count. Every hierarchical
ApplicationContext lookup (beanNamesForType, beanNamesForAnnotation, etc.)
hits this path; Spring Boot apps with large contexts pay the tax on every
container refresh.
Fix: replace ArrayList with LinkedHashSet (insertion-ordered, O(1)
contains/add), call merged.add(beanName) directly and skip the redundant
contains() guard — Set.add() is idempotent. Convert back to String[] via
toArray() at the end.
spring-0002 (LOW-MEDIUM): ImportStack extends ArrayDeque<ConfigurationClass>.
ArrayDeque.contains() is O(n). It is called in processMemberClasses()
(~line 422) and isChainedImportOnStack() (~line 653) — once per nested
class / import candidate. Fix: add a parallel HashSet<ConfigurationClass>
field; override push/pop/clear to maintain it; override contains() to
delegate to the set, giving O(1) membership tests.
CWE: CWE-407 (Inefficient Algorithmic Complexity)
Severity: spring-0001 HIGH, spring-0002 LOW-MEDIUM
---
.../beans/factory/BeanFactoryUtils.java | 18 ++++++------
.../annotation/ConfigurationClassParser.java | 28 ++++++++++++++++---
2 files changed, 33 insertions(+), 13 deletions(-)
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java
index aaaaaaa..bbbbbbb 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java
@@ -19,6 +19,7 @@ import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -521,13 +521,13 @@ public abstract class BeanFactoryUtils {
* @since 4.3.15
*/
private static String[] mergeNamesWithParent(String[] result, String[] parentResult, HierarchicalBeanFactory hbf) {
if (parentResult.length == 0) {
return result;
}
- List<String> merged = new ArrayList<>(result.length + parentResult.length);
- merged.addAll(Arrays.asList(result));
+ // CWE-407 fix (spring-0001): use LinkedHashSet for O(1) contains/add instead
+ // of ArrayList which makes merged.contains() O(|result|) per iteration —
+ // total O(|result| × |parentResult|) = O(B²) over all beans.
+ LinkedHashSet<String> merged = new LinkedHashSet<>(Arrays.asList(result));
for (String beanName : parentResult) {
- if (!merged.contains(beanName) && !hbf.containsLocalBean(beanName)) {
- merged.add(beanName);
+ if (!hbf.containsLocalBean(beanName)) {
+ merged.add(beanName); // Set.add() is idempotent; no contains() needed
}
}
- return StringUtils.toStringArray(merged);
+ return merged.toArray(String[]::new);
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
index ccccccc..ddddddd 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
@@ -752,7 +752,31 @@ class ConfigurationClassParser {
@SuppressWarnings("serial")
private class ImportStack extends ArrayDeque<ConfigurationClass> implements ImportRegistry {
+ // CWE-407 fix (spring-0002): ArrayDeque.contains() is O(n). ImportStack is
+ // queried in processMemberClasses() and isChainedImportOnStack() — once per
+ // nested class / import candidate. A parallel HashSet gives O(1) membership.
+ private final HashSet<ConfigurationClass> members = new HashSet<>();
+
private final MultiValueMap<String, AnnotationMetadata> imports = new LinkedMultiValueMap<>();
+ @Override
+ public void push(ConfigurationClass item) {
+ super.push(item);
+ this.members.add(item);
+ }
+
+ @Override
+ public ConfigurationClass pop() {
+ ConfigurationClass item = super.pop();
+ this.members.remove(item);
+ return item;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ this.members.clear();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return this.members.contains(o);
+ }
+
void registerImport(AnnotationMetadata importingClass, String importedClass) {
this.imports.add(importedClass, importingClass);
}