From 8de4c6f2522a4eae3d03d34f7e97152d1742fbfd Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 29 Mar 2026 18:02:38 -0400 Subject: [PATCH] =?UTF-8?q?weld-0001/0002:=20CDI=20Beans.recursiveStereoty?= =?UTF-8?q?peSearch=20+=20addInheritedInterceptorBindings=20diamond=20O(2^?= =?UTF-8?q?D);=20count=20643=E2=86=92645?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UNDF-REGISTRY.json | 4 +- ...ans-recursive-stereotype-search-diamond.md | 118 ++++++++++++++++++ ...rceptors-add-inherited-bindings-diamond.md | 96 ++++++++++++++ 3 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 defects/weld/patch/weld-0001-beans-recursive-stereotype-search-diamond.md create mode 100644 defects/weld/patch/weld-0002-interceptors-add-inherited-bindings-diamond.md diff --git a/UNDF-REGISTRY.json b/UNDF-REGISTRY.json index 1c50057fb..276beafe6 100644 --- a/UNDF-REGISTRY.json +++ b/UNDF-REGISTRY.json @@ -584,5 +584,7 @@ "micronaut-0004": "UNDF-2026-000000484", "starrocks-0002": "UNDF-2026-000000495", "substrate-0003": "UNDF-2026-000000504", - "erlang-0004": "UNDF-2026-000000528" + "erlang-0004": "UNDF-2026-000000528", + "weld-0001": "UNDF-2026-000000530", + "weld-0002": "UNDF-2026-000000550" } diff --git a/defects/weld/patch/weld-0001-beans-recursive-stereotype-search-diamond.md b/defects/weld/patch/weld-0001-beans-recursive-stereotype-search-diamond.md new file mode 100644 index 000000000..d6d5e342e --- /dev/null +++ b/defects/weld/patch/weld-0001-beans-recursive-stereotype-search-diamond.md @@ -0,0 +1,118 @@ +# UNDF: UNDF-2026-000000530 +# UNDF: (pending) +# weld-0001: Beans.recursiveStereotypeSearch — no visited set O(2^D) on diamond stereotype hierarchy + +## CWE-407 — Algorithmic Complexity: Exponential Recursion on Diamond CDI Stereotype Hierarchy + +| Field | Value | +|--------------|-------| +| ID | weld-0001 | +| Severity | HIGH | +| Ecosystem | weld | +| Package | weld-impl | +| File | `impl/src/main/java/org/jboss/weld/util/Beans.java` | +| Lines | 783–804 | +| Complexity | O(2^D) where D = diamond depth in stereotype annotation hierarchy | +| Hot path | Called at CDI container startup for every bean type | + +## Defect + +`Beans.recursiveStereotypeSearch` traverses the CDI stereotype annotation hierarchy +recursively with **no visited-set guard**. It iterates all annotations of a stereotype, +and for each annotation that is itself a `@Stereotype`, recurses without tracking +which stereotypes have already been processed. + +On a diamond-shaped stereotype hierarchy (meta-stereotype @A annotated with @B and @C; +both @B and @C are themselves stereotypes annotated with @D), the traversal is: + +``` +recursiveStereotypeSearch(@A) + → recursiveStereotypeSearch(@B) → recursiveStereotypeSearch(@D) + → recursiveStereotypeSearch(@C) → recursiveStereotypeSearch(@D) ← D visited twice +``` + +At depth D: `@D` is visited 2^D times. At D=24: 16,777,216 redundant calls. + +```java +// impl/src/main/java/org/jboss/weld/util/Beans.java:783-804 (DEFECT) +private static void recursiveStereotypeSearch(Class stereotype, + AnnotationSearchResult annotationSearchResult) { + Priority priorityAnnotation = stereotype.getAnnotation(Priority.class); + if (priorityAnnotation != null) { + annotationSearchResult.addPriority(priorityAnnotation.value()); + } + // ... + // perform a recursive search for more stereotypes + for (Annotation annotation : stereotype.getAnnotations()) { + Class annotationClass = annotation.annotationType(); + if (annotationClass.getAnnotation(Stereotype.class) != null) { + recursiveStereotypeSearch(annotationClass, annotationSearchResult); + // NO visited set: @D reachable via @B and @C is re-traversed 2^D times + } + } +} +``` + +Called from `annotationSearch` (line 770-781) for every bean annotation at startup. + +## Fix + +Add a `Set> visited` guard to prevent re-traversal: + +```java +// AFTER — O(N+E): visited set prevents exponential re-traversal +public static AnnotationSearchResult annotationSearch(Annotated type) { + AnnotationSearchResult annotationSearchResult = new AnnotationSearchResult(); + Set> visited = new HashSet<>(); + for (Annotation annotation : type.getAnnotations()) { + Class annotationClass = annotation.annotationType(); + if (annotationClass.getAnnotation(Stereotype.class) != null) { + recursiveStereotypeSearch(annotationClass, annotationSearchResult, visited); + } + } + return annotationSearchResult; +} + +private static void recursiveStereotypeSearch(Class stereotype, + AnnotationSearchResult annotationSearchResult, + Set> visited) { + if (!visited.add(stereotype)) { // O(1): already processed, skip + return; + } + Priority priorityAnnotation = stereotype.getAnnotation(Priority.class); + if (priorityAnnotation != null) { + annotationSearchResult.addPriority(priorityAnnotation.value()); + } + Reserve reserve = stereotype.getAnnotation(Reserve.class); + if (reserve != null) { + annotationSearchResult.setReserve(reserve.annotationType()); + } + Alternative alternative = stereotype.getAnnotation(Alternative.class); + if (alternative != null) { + annotationSearchResult.setAlternative(alternative.annotationType()); + } + for (Annotation annotation : stereotype.getAnnotations()) { + Class annotationClass = annotation.annotationType(); + if (annotationClass.getAnnotation(Stereotype.class) != null) { + recursiveStereotypeSearch(annotationClass, annotationSearchResult, visited); + } + } +} +``` + +## Speedup + +| Diamond depth (D) | Before (calls) | After (calls) | Speedup | +|------------------|---------------|---------------|---------| +| 10 | 1,023 | 10 | 102× | +| 15 | 32,767 | 15 | 2,184× | +| 20 | 1,048,575 | 20 | 52,428× | + +Growth before: O(2^D). Growth after: O(D). + +## Impact + +Weld is the CDI reference implementation, used by WildFly, GlassFish, Payara, and +indirectly by Quarkus (CDI layer). Diamond CDI stereotype hierarchies arise naturally +in enterprise patterns: a shared `@ApplicationScoped @Transactional @Named` meta-stereotype +and multiple extending stereotypes converging on it. diff --git a/defects/weld/patch/weld-0002-interceptors-add-inherited-bindings-diamond.md b/defects/weld/patch/weld-0002-interceptors-add-inherited-bindings-diamond.md new file mode 100644 index 000000000..dbf77efcc --- /dev/null +++ b/defects/weld/patch/weld-0002-interceptors-add-inherited-bindings-diamond.md @@ -0,0 +1,96 @@ +# UNDF: UNDF-2026-000000550 +# UNDF: (pending) +# weld-0002: Interceptors.addInheritedInterceptorBindings — no visited set O(2^D) diamond re-traversal + +## CWE-407 — Algorithmic Complexity: Exponential Re-traversal on Diamond Interceptor Binding Hierarchy + +| Field | Value | +|--------------|-------| +| ID | weld-0002 | +| Severity | MEDIUM | +| Ecosystem | weld | +| Package | weld-impl | +| File | `impl/src/main/java/org/jboss/weld/util/Interceptors.java` | +| Lines | 91–100 | +| Complexity | O(2^D) where D = diamond depth in @InterceptorBinding meta-annotation hierarchy | +| Hot path | Called at CDI container startup during interceptor binding flattening | + +## Defect + +`Interceptors.addInheritedInterceptorBindings` recursively traverses the CDI +interceptor binding meta-annotation hierarchy with **no visited-set guard**. +It recurses into every meta-binding annotation without tracking which types +have already been processed. + +On a diamond interceptor hierarchy (@A meta-annotated with @B and @C; both @B +and @C meta-annotated with @D), the traversal visits @D twice: + +``` +addInheritedInterceptorBindings(@A) + → addInheritedInterceptorBindings(@B) → addInheritedInterceptorBindings(@D) + → addInheritedInterceptorBindings(@C) → addInheritedInterceptorBindings(@D) ← D again +``` + +At depth D: 2^D redundant traversals. + +```java +// impl/src/main/java/org/jboss/weld/util/Interceptors.java:91-100 (DEFECT) +private static void addInheritedInterceptorBindings(EnhancedAnnotatedType clazz, + Class bindingType, + MetaAnnotationStore metaAnnotationStore, + Set flattenInterceptorBindings) { + Set metaBindings = metaAnnotationStore.getInterceptorBindingModel(bindingType) + .getInheritedInterceptionBindingTypes(); + addInterceptorBindings(clazz, metaBindings, flattenInterceptorBindings, metaAnnotationStore); + for (Annotation metaBinding : metaBindings) { + addInheritedInterceptorBindings(clazz, metaBinding.annotationType(), metaAnnotationStore, + flattenInterceptorBindings); + // NO visited set: @D reachable via @B and @C is re-traversed + } +} +``` + +Note: `flattenInterceptorBindings` is a `Set`, so duplicate +insertions are rejected — but the recursive traversal still happens, repeating +the same work exponentially. + +## Fix + +Add a `Set> visited` parameter threaded through the +recursion. Guard at entry with `visited.add(bindingType)`. + +```java +// AFTER — O(N+E): visited set prevents exponential re-traversal +// Caller: +for (Annotation annotation : rawBindings) { + addInheritedInterceptorBindings(clazz, annotation.annotationType(), + metaAnnotationStore, flattenInterceptorBindings, new HashSet<>()); +} + +private static void addInheritedInterceptorBindings(EnhancedAnnotatedType clazz, + Class bindingType, + MetaAnnotationStore metaAnnotationStore, + Set flattenInterceptorBindings, + Set> visited) { + if (!visited.add(bindingType)) { // O(1): already processed + return; + } + Set metaBindings = metaAnnotationStore.getInterceptorBindingModel(bindingType) + .getInheritedInterceptionBindingTypes(); + addInterceptorBindings(clazz, metaBindings, flattenInterceptorBindings, metaAnnotationStore); + for (Annotation metaBinding : metaBindings) { + addInheritedInterceptorBindings(clazz, metaBinding.annotationType(), + metaAnnotationStore, flattenInterceptorBindings, visited); + } +} +``` + +## Speedup + +| Diamond depth (D) | Before (calls) | After (calls) | Speedup | +|------------------|---------------|---------------|---------| +| 10 | 1,023 | 10 | 102× | +| 15 | 32,767 | 15 | 2,184× | +| 20 | 1,048,575 | 20 | 52,428× | + +Growth before: O(2^D). Growth after: O(D).