# 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. 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 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 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 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 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 members = new HashSet<>(); + private final MultiValueMap 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); }