From cedcc9d5eea65fdd2d48821d76432169c4a735d5 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 29 Mar 2026 19:30:55 -0400 Subject: [PATCH] =?UTF-8?q?tomcat-0001/0002:=20BeanSupportFull/Standalone?= =?UTF-8?q?=20populateFromInterfaces=20diamond=20O(2^D);=20count=20666?= =?UTF-8?q?=E2=86=92668?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- defects/activemq/patch/CLEAN.md | 12 +++ ...portfull-populatefrominterfaces-diamond.md | 94 +++++++++++++++++++ ...andalone-populatefrominterfaces-diamond.md | 86 +++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 defects/activemq/patch/CLEAN.md create mode 100644 defects/tomcat/patch/tomcat-0001-beansupportfull-populatefrominterfaces-diamond.md create mode 100644 defects/tomcat/patch/tomcat-0002-beansupportstandalone-populatefrominterfaces-diamond.md diff --git a/defects/activemq/patch/CLEAN.md b/defects/activemq/patch/CLEAN.md new file mode 100644 index 000000000..74ec689a9 --- /dev/null +++ b/defects/activemq/patch/CLEAN.md @@ -0,0 +1,12 @@ +# CLEAN — Apache ActiveMQ Classic + +Scanned 2026-03-29 for CWE-407 (algorithmic complexity). + +## Findings + +- `IntrospectionSupport.java` — CLEAN: reflection on methods only, no recursive class traversal +- `AnnotatedMBean.java` — CLEAN: simple non-recursive `getInterfaces()` iteration +- `AsyncAnnotatedMBean.java` — CLEAN: same pattern, non-recursive +- OpenWire generator files — CLEAN: code generation context, no runtime recursion + +**Result: No actionable CWE-407 defects.** diff --git a/defects/tomcat/patch/tomcat-0001-beansupportfull-populatefrominterfaces-diamond.md b/defects/tomcat/patch/tomcat-0001-beansupportfull-populatefrominterfaces-diamond.md new file mode 100644 index 000000000..de1ff2987 --- /dev/null +++ b/defects/tomcat/patch/tomcat-0001-beansupportfull-populatefrominterfaces-diamond.md @@ -0,0 +1,94 @@ +# UNDF: (pending) +# tomcat-0001: BeanSupportFull.populateFromInterfaces — O(2^D) diamond re-traversal without visited set + +## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal in Jakarta EL bean property collection + +| Field | Value | +|--------------|-------| +| ID | tomcat-0001 | +| Severity | MEDIUM | +| Ecosystem | tomcat | +| Package | jakarta.el | +| File | `java/jakarta/el/BeanSupportFull.java` | +| Lines | 57–73 | +| Complexity | O(2^D) on diamond interface hierarchies | +| Hot path | Called during EL expression evaluation for first access to a bean type (cached after first use) | + +## Defect + +`BeanSupportFull.populateFromInterfaces` recursively collects bean property descriptors from a class's +full interface hierarchy. It has NO visited set — it recurses unconditionally into every interface and +every superclass, causing diamond hierarchies to be traversed exponentially: + +```java +// BeanSupportFull.java:57-73 (DEFECT) +private void populateFromInterfaces(Class aClass) throws IntrospectionException { + Class[] interfaces = aClass.getInterfaces(); + for (Class ifs : interfaces) { + BeanInfo info = Introspector.getBeanInfo(ifs); + PropertyDescriptor[] pds = info.getPropertyDescriptors(); + for (PropertyDescriptor pd : pds) { + if (!this.properties.containsKey(pd.getName())) { + this.properties.put(pd.getName(), new BeanPropertyFull(this.type, pd)); + } + } + populateFromInterfaces(ifs); // DEFECT: unconditional recursion — no visited guard + } + Class superclass = aClass.getSuperclass(); + if (superclass != null) { + populateFromInterfaces(superclass); // DEFECT: unconditional recursion — no visited guard + } +} +``` + +On a diamond (I1 and I2 both extend Base; class C implements I1 and I2): +- `populateFromInterfaces(C)`: + - Recurse into I1: `populateFromInterfaces(I1)` → processes Base, recurses into Base's interfaces + - Recurse into I2: `populateFromInterfaces(I2)` → processes Base AGAIN, recurses into Base's interfaces AGAIN + +The property dedup at line 63 (`properties.containsKey`) prevents duplicate property additions but does +NOT prevent re-traversal — the recursive calls at lines 67 and 71 still happen. Every `Introspector.getBeanInfo(ifs)` and subsequent loop re-executes for already-visited nodes. + +At diamond depth D, Base is traversed 2^D times. + +## Fix + +Add a `Set> visited` parameter with a public wrapper: + +```java +// AFTER — O(N+E) where N=interfaces, E=hierarchy edges +private void populateFromInterfaces(Class aClass) throws IntrospectionException { + populateFromInterfaces(aClass, new HashSet<>()); +} + +private void populateFromInterfaces(Class aClass, Set> visited) + throws IntrospectionException { + if (!visited.add(aClass)) { return; } // skip already-traversed types + + Class[] interfaces = aClass.getInterfaces(); + for (Class ifs : interfaces) { + BeanInfo info = Introspector.getBeanInfo(ifs); + PropertyDescriptor[] pds = info.getPropertyDescriptors(); + for (PropertyDescriptor pd : pds) { + if (!this.properties.containsKey(pd.getName())) { + this.properties.put(pd.getName(), new BeanPropertyFull(this.type, pd)); + } + } + populateFromInterfaces(ifs, visited); + } + Class superclass = aClass.getSuperclass(); + if (superclass != null) { + populateFromInterfaces(superclass, visited); + } +} +``` + +## Speedup + +| Diamond depth (D) | Before (traversals) | After (traversals) | Speedup | +|------------------|--------------------|--------------------|---------| +| 5 | 31 | 5 | 6× | +| 10 | 1,023 | 10 | 102× | +| 15 | 32,767 | 15 | 2,184× | + +Growth before: O(2^D). Growth after: O(D). diff --git a/defects/tomcat/patch/tomcat-0002-beansupportstandalone-populatefrominterfaces-diamond.md b/defects/tomcat/patch/tomcat-0002-beansupportstandalone-populatefrominterfaces-diamond.md new file mode 100644 index 000000000..7c7e0cebd --- /dev/null +++ b/defects/tomcat/patch/tomcat-0002-beansupportstandalone-populatefrominterfaces-diamond.md @@ -0,0 +1,86 @@ +# UNDF: (pending) +# tomcat-0002: BeanSupportStandalone.populateFromInterfaces — O(2^D) diamond re-traversal without visited set + +## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal in standalone Jakarta EL bean property collection + +| Field | Value | +|--------------|-------| +| ID | tomcat-0002 | +| Severity | MEDIUM | +| Ecosystem | tomcat | +| Package | jakarta.el | +| File | `java/jakarta/el/BeanSupportStandalone.java` | +| Lines | 181–196 | +| Complexity | O(2^D) on diamond interface hierarchies | +| Hot path | Called during EL bean property discovery when not using full Introspector (standalone/embedded mode) | + +## Defect + +`BeanSupportStandalone.populateFromInterfaces` has the same diamond re-traversal defect as +`BeanSupportFull.populateFromInterfaces` (tomcat-0001). No visited set; recursion is unconditional: + +```java +// BeanSupportStandalone.java:181-196 (DEFECT) +private void populateFromInterfaces(Class aClass) { + Class[] interfaces = aClass.getInterfaces(); + for (Class ifs : interfaces) { + PropertyDescriptor[] pds = getPropertyDescriptors(type); // note: uses 'type', not 'ifs' + for (PropertyDescriptor pd : pds) { + if (!this.properties.containsKey(pd.getName())) { + this.properties.put(pd.getName(), new BeanPropertyStandalone(this.type, pd)); + } + } + populateFromInterfaces(ifs); // DEFECT: unconditional recursion — no visited guard + } + Class superclass = aClass.getSuperclass(); + if (superclass != null) { + populateFromInterfaces(superclass); // DEFECT: unconditional recursion — no visited guard + } +} +``` + +On a diamond hierarchy, the `populateFromInterfaces` recursion visits shared ancestor nodes 2^D times. +The property dedup at line 186 (`properties.containsKey`) prevents duplicate properties but does NOT +prevent the re-traversal cost — all recursive calls happen regardless. + +Same root cause as tomcat-0001; independent occurrence in the standalone implementation. + +## Fix + +Add a `Set> visited` parameter with a public wrapper: + +```java +// AFTER — O(N+E) where N=interfaces, E=hierarchy edges +private void populateFromInterfaces(Class aClass) { + populateFromInterfaces(aClass, new HashSet<>()); +} + +private void populateFromInterfaces(Class aClass, Set> visited) { + if (!visited.add(aClass)) { return; } // skip already-traversed types + + Class[] interfaces = aClass.getInterfaces(); + for (Class ifs : interfaces) { + PropertyDescriptor[] pds = getPropertyDescriptors(ifs); // also fix: use ifs, not type + for (PropertyDescriptor pd : pds) { + if (!this.properties.containsKey(pd.getName())) { + this.properties.put(pd.getName(), new BeanPropertyStandalone(this.type, pd)); + } + } + populateFromInterfaces(ifs, visited); + } + Class superclass = aClass.getSuperclass(); + if (superclass != null) { + populateFromInterfaces(superclass, visited); + } +} +``` + +## Speedup + +| Diamond depth (D) | Before (traversals) | After (traversals) | Speedup | +|------------------|--------------------|--------------------|---------| +| 5 | 31 | 5 | 6× | +| 10 | 1,023 | 10 | 102× | +| 15 | 32,767 | 15 | 2,184× | + +Growth before: O(2^D). Growth after: O(D).