115 lines
3.8 KiB
Markdown
115 lines
3.8 KiB
Markdown
# UNDF: UNDF-2026-000000498
|
||
# php-0003: zend_do_implement_interfaces — O(I²) interface dedup linear scan
|
||
|
||
## Summary
|
||
|
||
| Field | Value |
|
||
|-------------|-------|
|
||
| ID | php-0003 |
|
||
| Severity | MEDIUM |
|
||
| CWE | CWE-407 (Algorithmic Complexity) |
|
||
| File | `Zend/zend_inheritance.c` |
|
||
| Function | `zend_do_implement_interfaces` |
|
||
| Lines | 2259–2290 |
|
||
| Speedup | ~250x op-count at I=500 interfaces |
|
||
|
||
## Defect
|
||
|
||
When PHP links a class that implements I interfaces (including inherited ones),
|
||
`zend_do_implement_interfaces` builds a deduplicated `interfaces[]` array.
|
||
For each new interface, it scans the already-accumulated array linearly:
|
||
|
||
```c
|
||
// zend_inheritance.c:2259
|
||
for (i = 0; i < ce->num_interfaces; i++) {
|
||
zend_class_entry *iface = interfaces[num_parent_interfaces + i];
|
||
// ...
|
||
for (uint32_t j = 0; j < num_interfaces; j++) { // O(num_interfaces)
|
||
if (interfaces[j] == iface) { // pointer equality
|
||
// skip duplicate
|
||
...
|
||
break;
|
||
}
|
||
}
|
||
if (iface) {
|
||
interfaces[num_interfaces] = iface;
|
||
num_interfaces++;
|
||
}
|
||
}
|
||
```
|
||
|
||
The outer loop runs `ce->num_interfaces` times.
|
||
The inner loop scans up to `num_interfaces` (parent + accumulated so far) each time.
|
||
Total: O(P×I + I²) where P = parent interface count, I = new interface count.
|
||
|
||
In deeply nested interface hierarchies (Symfony, Laravel, Drupal), a class
|
||
can implement dozens of transitive interfaces. E.g., a class implementing
|
||
5 interfaces where each interface extends 10 others = 50+ total interfaces.
|
||
|
||
## Root Cause
|
||
|
||
The `interfaces[]` array is a flat C array with no corresponding hash set.
|
||
Duplicate detection uses pointer equality but relies on a linear scan.
|
||
|
||
## Fix
|
||
|
||
Use a temporary `HashTable` (or a pointer-keyed hash set) to track which
|
||
interface pointers have already been added:
|
||
|
||
```diff
|
||
--- a/Zend/zend_inheritance.c
|
||
+++ b/Zend/zend_inheritance.c
|
||
@@ -2251,7 +2251,9 @@ static void zend_do_implement_interfaces(...)
|
||
{
|
||
uint32_t num_parent_interfaces = ce->parent ? ce->parent->num_interfaces : 0;
|
||
uint32_t num_interfaces = num_parent_interfaces;
|
||
+ HashTable iface_set;
|
||
+ zend_hash_init(&iface_set, num_parent_interfaces + ce->num_interfaces, NULL, NULL, 0);
|
||
+ /* pre-populate with parent interfaces */
|
||
+ for (uint32_t k = 0; k < num_parent_interfaces; k++) {
|
||
+ zend_hash_index_add_empty_element(&iface_set, (zend_ulong)(uintptr_t)interfaces[k]);
|
||
+ }
|
||
|
||
for (i = 0; i < ce->num_interfaces; i++) {
|
||
zend_class_entry *iface = interfaces[num_parent_interfaces + i];
|
||
- /* O(num_interfaces) linear scan for duplicate */
|
||
- for (uint32_t j = 0; j < num_interfaces; j++) {
|
||
- if (interfaces[j] == iface) {
|
||
- /* duplicate found */
|
||
- ...
|
||
- iface = NULL;
|
||
- break;
|
||
- }
|
||
- }
|
||
+ /* O(1) hash lookup for duplicate */
|
||
+ if (zend_hash_index_find(&iface_set, (zend_ulong)(uintptr_t)iface) != NULL) {
|
||
+ /* duplicate: inherit constants but don't add to interfaces[] */
|
||
+ ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) {
|
||
+ do_inherit_constant_check(ce, c, key);
|
||
+ } ZEND_HASH_FOREACH_END();
|
||
+ continue;
|
||
+ }
|
||
if (iface) {
|
||
interfaces[num_interfaces] = iface;
|
||
+ zend_hash_index_add_empty_element(&iface_set, (zend_ulong)(uintptr_t)iface);
|
||
num_interfaces++;
|
||
}
|
||
}
|
||
+ zend_hash_destroy(&iface_set);
|
||
```
|
||
|
||
## Measurement
|
||
|
||
Simulated with `PhpInterfaceDedupAlgorithm.java`:
|
||
|
||
| I | slow ops | fast ops | ratio |
|
||
|------|----------|----------|-------|
|
||
| 50 | 1,275 | 50 | 25× |
|
||
| 200 | 20,100 | 200 | 100× |
|
||
| 500 | 125,250 | 500 | 250× |
|
||
|
||
## References
|
||
|
||
- `Zend/zend_inheritance.c` lines 2251–2312 (`zend_do_implement_interfaces`)
|
||
- `Zend/zend_inheritance.c` lines 2268–2285 (inner dedup loop)
|
||
- Related: php-0004 (`zend_do_inherit_interfaces` same pattern)
|