77 lines
3.3 KiB
Markdown
77 lines
3.3 KiB
Markdown
# UNDF: UNDF-2026-000000415
|
||
# UNDF: (pending)
|
||
# hazelcast-0001: SerializationUtil.getInterfaces — O(2^D) diamond re-traversal; Collections.addAll() return value ignored
|
||
|
||
## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal in interface hierarchy collection for serializer lookup
|
||
|
||
| Field | Value |
|
||
|--------------|-------|
|
||
| ID | hazelcast-0001 |
|
||
| Severity | HIGH |
|
||
| Ecosystem | hazelcast |
|
||
| Package | hazelcast |
|
||
| File | `hazelcast/src/main/java/com/hazelcast/internal/serialization/impl/SerializationUtil.java` |
|
||
| Lines | 150–158 |
|
||
| Complexity | O(2^D) on diamond interface hierarchies |
|
||
| Hot path | Called per serialization of a type not yet seen by the serialization service (first-time serializer lookup) |
|
||
|
||
## Defect
|
||
|
||
`SerializationUtil.getInterfaces` recursively collects all interfaces of a class into a `Set<Class<?>>`.
|
||
Although it uses a Set, the recursion guard is MISSING — `Collections.addAll(interfaces, classes)` fills
|
||
the set but the `for` loop then recurses into every interface unconditionally, regardless of whether
|
||
each was already present in the set:
|
||
|
||
```java
|
||
// SerializationUtil.java:150-158 (DEFECT)
|
||
static void getInterfaces(Class<?> clazz, Set<Class<?>> interfaces) {
|
||
final var classes = clazz.getInterfaces();
|
||
if (classes.length > 0) {
|
||
Collections.addAll(interfaces, classes); // adds to set (ignored return vals)
|
||
for (Class<?> cl : classes) {
|
||
getInterfaces(cl, interfaces); // DEFECT: recurses unconditionally
|
||
} // ignores whether cl was already present
|
||
}
|
||
}
|
||
```
|
||
|
||
On a diamond hierarchy (interface I1 extends Base; interface I2 extends Base; class C implements I1, I2):
|
||
- `getInterfaces(C)`:
|
||
- `Collections.addAll(interfaces, [I1, I2])` → both added
|
||
- recurse into I1: `getInterfaces(I1)`:
|
||
- adds Base to set, recurses into Base
|
||
- recurse into I2: `getInterfaces(I2)`:
|
||
- `Collections.addAll(interfaces, [Base])` → Base already present, add() returns false (IGNORED)
|
||
- STILL recurses into Base → unnecessary re-traversal
|
||
|
||
At diamond depth D, Base is visited 2^D times.
|
||
|
||
Called from `AbstractSerializationService.lookupCustomSerializer()` (line 663, and again once per
|
||
superclass in the superclass chain at line 669), which is invoked on every first serialization
|
||
of a previously-unseen class type.
|
||
|
||
## Fix
|
||
|
||
Replace `Collections.addAll + unconditional loop` with a per-element `interfaces.add()` guard:
|
||
|
||
```java
|
||
// AFTER — O(N+E) where N=interfaces, E=inheritance edges
|
||
static void getInterfaces(Class<?> clazz, Set<Class<?>> interfaces) {
|
||
for (Class<?> cl : clazz.getInterfaces()) {
|
||
if (interfaces.add(cl)) { // add returns false if already present → skip
|
||
getInterfaces(cl, interfaces); // only recurse if newly added
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Speedup
|
||
|
||
| Diamond depth (D) | Before (visits) | After (visits) | Speedup |
|
||
|------------------|----------------|----------------|---------|
|
||
| 5 | 31 | 5 | 6× |
|
||
| 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).
|