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:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
|
|
@ -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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue