nmap-0002 + haproxy-0004 + nginx-0004 + weechat-0003 + zeek-0002 + curl-0004: 6 new CWE-407 defects in network tools; count 693→699
nmap-0002: nmap.cc merge_port_lists O(N²) port dedup → unordered_set O(N); ~65000x at max range haproxy-0004: http_ana.c http_capture_headers O(H×C) cap_hdr walk per request → pre-built HashMap O(H) nginx-0004: ngx_http_upstream_keepalive_module.c keepalive_get_peer O(C) sockaddr scan per upstream request → HashMap O(1) weechat-0003: irc-channel.c irc_channel_search O(C) linked-list scan per message handler → channels_hashtable O(1) zeek-0002: Attr.cc Attributes::AddAttrs O(A²) triple-Find/RemoveAttr per attr → unordered_map index O(A) curl-0004: mime.c search_header O(P×H) 3x per part per mime_add_headers → pre-indexed header name set O(P)
This commit is contained in:
parent
421f3352c7
commit
ba818693db
21 changed files with 2213 additions and 0 deletions
113
defects/nmap/patch/nmap-0002-merge-port-lists-O-N2.md
Normal file
113
defects/nmap/patch/nmap-0002-merge-port-lists-O-N2.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# nmap-0002 — nmap.cc merge_port_lists O(N²) ping-port dedup
|
||||
|
||||
## Ecosystem
|
||||
nmap (C++)
|
||||
|
||||
## Severity
|
||||
MEDIUM — startup/pre-scan, not per-packet, but triggered by user-provided port lists
|
||||
|
||||
## Location
|
||||
`nmap.cc`
|
||||
- `insert_port_into_merge_list` (~line 329): O(N) linear scan per insertion
|
||||
- `merge_port_lists` (~line 343): calls `insert_port_into_merge_list` N1+N2 times
|
||||
- `validate_scan_lists` (~line 419): calls `merge_port_lists` to combine SYN+ACK ping port lists
|
||||
|
||||
## Description
|
||||
|
||||
When `-PS` (SYN ping) and `-PA` (ACK ping) are both used with explicit port lists
|
||||
(e.g. `nmap -PS1-65535 -PA1-65535 target`), `validate_scan_lists` calls
|
||||
`merge_port_lists(syn_ping_ports, count1, ack_ping_ports, count2, ...)` to
|
||||
produce a deduplicated combined list.
|
||||
|
||||
`insert_port_into_merge_list` performs a full O(N) linear scan on each call:
|
||||
|
||||
```c
|
||||
static void insert_port_into_merge_list(unsigned short *mlist,
|
||||
int *merged_port_count,
|
||||
unsigned short p) {
|
||||
int i;
|
||||
// make sure the port isn't already in the list
|
||||
for (i = 0; i < *merged_port_count; i++) { // O(N)
|
||||
if (mlist[i] == p) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mlist[*merged_port_count] = p;
|
||||
(*merged_port_count)++;
|
||||
}
|
||||
```
|
||||
|
||||
`merge_port_lists` calls this function (count1 + count2) times, so the total
|
||||
cost is O((count1 + count2)²). With count1 = count2 = 65535 this is ~8.5 billion
|
||||
operations (port values are uint16_t, max 65535).
|
||||
|
||||
## Fix
|
||||
|
||||
Use `std::unordered_set<uint16_t>` for O(1) membership test:
|
||||
|
||||
```cpp
|
||||
--- a/nmap.cc
|
||||
+++ b/nmap.cc
|
||||
@@ -329,15 +329,6 @@
|
||||
-static void insert_port_into_merge_list(unsigned short *mlist,
|
||||
- int *merged_port_count,
|
||||
- unsigned short p) {
|
||||
- int i;
|
||||
- // make sure the port isn't already in the list
|
||||
- for (i = 0; i < *merged_port_count; i++) {
|
||||
- if (mlist[i] == p) {
|
||||
- return;
|
||||
- }
|
||||
- }
|
||||
- mlist[*merged_port_count] = p;
|
||||
- (*merged_port_count)++;
|
||||
-}
|
||||
-
|
||||
static unsigned short *merge_port_lists(unsigned short *port_list1, int count1,
|
||||
unsigned short *port_list2, int count2,
|
||||
int *merged_port_count) {
|
||||
- int i;
|
||||
- unsigned short *merged_port_list = NULL;
|
||||
-
|
||||
- *merged_port_count = 0;
|
||||
-
|
||||
- merged_port_list =
|
||||
- (unsigned short *) safe_zalloc((count1 + count2) * sizeof(unsigned short));
|
||||
-
|
||||
- for (i = 0; i < count1; i++) {
|
||||
- insert_port_into_merge_list(merged_port_list,
|
||||
- merged_port_count,
|
||||
- port_list1[i]);
|
||||
- }
|
||||
- for (i = 0; i < count2; i++) {
|
||||
- insert_port_into_merge_list(merged_port_list,
|
||||
- merged_port_count,
|
||||
- port_list2[i]);
|
||||
- }
|
||||
+ std::unordered_set<uint16_t> seen;
|
||||
+ seen.reserve(count1 + count2);
|
||||
+ for (int i = 0; i < count1; i++) seen.insert(port_list1[i]);
|
||||
+ for (int i = 0; i < count2; i++) seen.insert(port_list2[i]);
|
||||
+
|
||||
+ *merged_port_count = (int)seen.size();
|
||||
+ unsigned short *merged_port_list =
|
||||
+ (unsigned short *) safe_zalloc(seen.size() * sizeof(unsigned short));
|
||||
+ int j = 0;
|
||||
+ for (uint16_t p : seen) merged_port_list[j++] = p;
|
||||
```
|
||||
|
||||
## Complexity
|
||||
|
||||
| Variant | Cost |
|
||||
|---------|------|
|
||||
| Before | O((N1+N2)²) — up to ~8.5B ops at 65535+65535 |
|
||||
| After | O(N1+N2) — ~131K ops |
|
||||
| Speedup | ~65000× at maximum port range |
|
||||
|
||||
## Notes
|
||||
- `validate_scan_lists` is called once per scan invocation from `nmap_main`
|
||||
- The defect only triggers when both `-PS` and `-PA` (or other overlapping
|
||||
ping probe types) are used with large port ranges
|
||||
- The fix preserves all existing semantics; note that port order may change
|
||||
(iteration order of `unordered_set` is not guaranteed) but the code that
|
||||
consumes the merged list (`syn_ping_ports`) does not depend on order
|
||||
Loading…
Add table
Add a link
Reference in a new issue