Add 88 new defect entries to HIGH and MEDIUM tables:
HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002
MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
ovs-0001, onos-0003, odl-0002, jetty-0001
PDF: 976K
65 lines
2 KiB
Markdown
65 lines
2 KiB
Markdown
# php-0001 — zend_compile.c: O(n²) linear scan for named argument position
|
||
|
||
| Field | Value |
|
||
|-------|-------|
|
||
| ID | php-0001 |
|
||
| Target | PHP |
|
||
| File | `Zend/zend_compile.c` |
|
||
| Lines | 3755–3766, 3784, 3822 |
|
||
| CWE | CWE-407 (Algorithmic Complexity) |
|
||
| Severity | HIGH |
|
||
| Status | PATCHED |
|
||
|
||
## Description
|
||
|
||
`zend_get_arg_num()` performs a linear scan over `fn->common.num_args` entries
|
||
to locate a named argument by string comparison:
|
||
|
||
```c
|
||
static uint32_t zend_get_arg_num(const zend_function *fn, const zend_string *arg_name) {
|
||
// TODO: Caching?
|
||
for (uint32_t i = 0; i < fn->common.num_args; i++) {
|
||
zend_arg_info *arg_info = &fn->op_array.arg_info[i];
|
||
if (zend_string_equals(arg_info->name, arg_name)) {
|
||
return i + 1;
|
||
}
|
||
}
|
||
return (uint32_t) -1;
|
||
}
|
||
```
|
||
|
||
This is called from `zend_compile_args()` which iterates over all `args->children`
|
||
at compile time. For a function with M parameters called with N named arguments
|
||
the total cost is O(N × M). The upstream comment `// TODO: Caching?` acknowledges
|
||
the defect.
|
||
|
||
The companion runtime function `zend_get_arg_offset_by_name()` in
|
||
`zend_execute.c` (line 5479) carries the same structure with an explicit
|
||
`// TODO: Use a hash table?` comment, and is subject to identical quadratic
|
||
behavior on cache miss.
|
||
|
||
## Reproduction
|
||
|
||
```php
|
||
// Function with 50 named parameters, called with all 50 named — 50×50 = 2500 scans
|
||
function wide(
|
||
$p0, $p1, $p2, ..., $p49
|
||
) {}
|
||
// Each named arg triggers zend_get_arg_num linear scan over 50 entries
|
||
wide(p49: 1, p48: 2, ..., p0: 50);
|
||
```
|
||
|
||
## Fix
|
||
|
||
Build a `HashTable` from `arg_name → position` once per function signature and
|
||
cache it on the `zend_function` (or use the existing per-opcode cache slot for
|
||
the runtime path, which already has infrastructure but is not used for the
|
||
compile-time path).
|
||
|
||
## Complexity
|
||
|
||
| Metric | Before | After |
|
||
|--------|--------|-------|
|
||
| Per named-arg lookup | O(M) | O(1) |
|
||
| N named args, M params | O(N×M) | O(N + M) |
|
||
| Speedup (N=M=50) | 1× | ~50× |
|