java-topology/defects/spring/patch/spring-0001-0002-beanfactory-linkedhashset.patch

110 lines
4.8 KiB
Diff
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.

# UNDF: UNDF-2026-000000295
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);
}