dubbo-0001: AnnotationUtils.getAllMetaAnnotations recurses into meta-annotation graph without a visited set — diamond shapes cause O(2^D) calls (UNDF-2026-000000238) cxf-0001: SchemaUtil.parseImports + WSDLServiceBuilder.parseImports use ArrayList.contains as visited guard on WSDL import graph — O(N²) membership checks (UNDF-2026-000000237) Struts, RabbitMQ, Camel: CLEAN
116 lines
4.4 KiB
Markdown
116 lines
4.4 KiB
Markdown
# UNDF: UNDF-2026-000000238
|
|
# dubbo-0001: AnnotationUtils.getAllMetaAnnotations diamond recursion O(2^D)
|
|
|
|
## Classification
|
|
- **CWE**: CWE-407 (Inefficient Algorithmic Complexity)
|
|
- **Severity**: MEDIUM
|
|
- **Component**: Apache Dubbo — `dubbo-common`
|
|
- **File**: `dubbo-common/src/main/java/org/apache/dubbo/common/utils/AnnotationUtils.java`
|
|
- **Method**: `getAllMetaAnnotations(Class<? extends Annotation>, Predicate<Annotation>...)`
|
|
|
|
## Description
|
|
|
|
`getAllMetaAnnotations` recursively traverses the meta-annotation graph of a Java annotation
|
|
type without a visited set. When the annotation DAG has a diamond shape — two annotations B and
|
|
C both carry meta-annotation D, and a top-level annotation A carries both B and C — the method
|
|
visits D twice. At depth D the call count is O(2^D).
|
|
|
|
## Defect Code
|
|
|
|
```java
|
|
// dubbo-common/.../utils/AnnotationUtils.java lines 281-296
|
|
static List<Annotation> getAllMetaAnnotations(
|
|
Class<? extends Annotation> annotationType, Predicate<Annotation>... annotationsToFilter) {
|
|
|
|
List<Annotation> allMetaAnnotations = new LinkedList<>();
|
|
|
|
List<Annotation> metaAnnotations = getMetaAnnotations(annotationType); // direct meta-annotations
|
|
|
|
allMetaAnnotations.addAll(metaAnnotations);
|
|
|
|
for (Annotation metaAnnotation : metaAnnotations) {
|
|
// Get the nested meta annotations recursively
|
|
allMetaAnnotations.addAll(getAllMetaAnnotations(metaAnnotation.annotationType()));
|
|
// NO visited set: diamond causes 2^D recursive calls
|
|
}
|
|
|
|
return unmodifiableList(filterAll(allMetaAnnotations, annotationsToFilter));
|
|
}
|
|
```
|
|
|
|
## Diamond Example
|
|
|
|
```
|
|
@A
|
|
|-meta-> @B
|
|
| |-meta-> @D <-- visited ONCE per path
|
|
|-meta-> @C
|
|
|-meta-> @D <-- visited AGAIN (second path)
|
|
|
|
Depth 1 diamond: getAllMetaAnnotations(@D) called 2 times
|
|
Depth 2 diamond: 4 times
|
|
Depth D: 2^D times
|
|
```
|
|
|
|
In a Dubbo deployment with composite Dubbo service annotations (e.g., `@DubboService` carrying
|
|
`@Service`, `@Component`, and custom composed annotations), this pattern can occur at annotation
|
|
processor startup time. The method is called from `findMetaAnnotations`, which is called during
|
|
service registration and reference injection on every annotated class.
|
|
|
|
## Callers
|
|
|
|
- `findMetaAnnotations(Class<? extends Annotation>, Class<A>)` — line 334
|
|
- `findMetaAnnotations(AnnotatedElement, Class<A>)` — line 351 (calls `findMetaAnnotations` on
|
|
each declared annotation's type, which internally calls `getAllMetaAnnotations`)
|
|
|
|
## Complexity
|
|
|
|
| D (diamond depth) | Call count | Speedup with fix |
|
|
|---|---|---|
|
|
| 1 | 2 | 2x |
|
|
| 5 | 32 | 32x |
|
|
| 10 | 1024 | 1024x |
|
|
| 15 | 32768 | ~33000x |
|
|
|
|
In practice D is small (3-5 for typical Java meta-annotation hierarchies), but Dubbo's rich
|
|
composed-annotation ecosystem (e.g., `@DubboService` + `@EnableDubbo` + framework annotations)
|
|
can create D=5+ chains, giving 32x+ redundant work at startup.
|
|
|
|
## Fix
|
|
|
|
Pass a `Set<Class<? extends Annotation>> visited` accumulator through the recursion:
|
|
|
|
```java
|
|
static List<Annotation> getAllMetaAnnotations(
|
|
Class<? extends Annotation> annotationType, Predicate<Annotation>... annotationsToFilter) {
|
|
Set<Class<? extends Annotation>> visited = new LinkedHashSet<>();
|
|
return getAllMetaAnnotations(annotationType, visited, annotationsToFilter);
|
|
}
|
|
|
|
private static List<Annotation> getAllMetaAnnotations(
|
|
Class<? extends Annotation> annotationType,
|
|
Set<Class<? extends Annotation>> visited,
|
|
Predicate<Annotation>... annotationsToFilter) {
|
|
|
|
List<Annotation> allMetaAnnotations = new LinkedList<>();
|
|
List<Annotation> metaAnnotations = getMetaAnnotations(annotationType);
|
|
allMetaAnnotations.addAll(metaAnnotations);
|
|
|
|
for (Annotation metaAnnotation : metaAnnotations) {
|
|
Class<? extends Annotation> metaType = metaAnnotation.annotationType();
|
|
if (visited.add(metaType)) { // skip if already visited
|
|
allMetaAnnotations.addAll(getAllMetaAnnotations(metaType, visited));
|
|
}
|
|
}
|
|
|
|
return unmodifiableList(filterAll(allMetaAnnotations, annotationsToFilter));
|
|
}
|
|
```
|
|
|
|
## Evidence
|
|
|
|
- File: `dubbo-common/src/main/java/org/apache/dubbo/common/utils/AnnotationUtils.java`
|
|
- Lines 281-296: recursive call with no visited guard
|
|
- No `Set`, `Map`, or `cache` parameter in method signature
|
|
- `getMetaAnnotations` excludes `@Target`, `@Retention`, `@Documented` to avoid Java built-in
|
|
annotation self-references — but does not prevent application-defined diamond shapes
|