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
102
defects/weechat/patch/weechat-0003-irc-channel-search-hashmap.md
Normal file
102
defects/weechat/patch/weechat-0003-irc-channel-search-hashmap.md
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# weechat-0003 — irc_channel_search O(C) linked-list scan — no hash index
|
||||
|
||||
## Ecosystem
|
||||
weechat (C)
|
||||
|
||||
## Severity
|
||||
MEDIUM — hot path: irc_channel_search() is called ~30+ times per IRC protocol
|
||||
message in irc-protocol.c (JOIN, PART, KICK, MODE, TOPIC, PRIVMSG, etc.)
|
||||
|
||||
## Location
|
||||
`src/plugins/irc/irc-channel.c`
|
||||
- Function: `irc_channel_search` (~line 92): O(C) linear walk of `server->channels`
|
||||
|
||||
`src/plugins/irc/irc-protocol.c`
|
||||
- ~30+ call sites including: JOIN (~line 1560, 1587), PART (~line 1753),
|
||||
KICK (~line 1928), MODE (~line 2180), NICK (~line 2297), PRIVMSG (~line 3112),
|
||||
numeric 353/366/332 handlers, etc.
|
||||
|
||||
## Description
|
||||
|
||||
`irc_channel_search` scans a linked list of all channels on a server to find
|
||||
a channel by name:
|
||||
|
||||
```c
|
||||
// irc-channel.c:92
|
||||
struct t_irc_channel *
|
||||
irc_channel_search (struct t_irc_server *server, const char *channel_name)
|
||||
{
|
||||
struct t_irc_channel *ptr_channel;
|
||||
|
||||
if (!server || !channel_name)
|
||||
return NULL;
|
||||
|
||||
for (ptr_channel = server->channels; ptr_channel;
|
||||
ptr_channel = ptr_channel->next_channel) // O(C) walk
|
||||
{
|
||||
if (irc_server_strcasecmp (server, ptr_channel->name, channel_name) == 0)
|
||||
return ptr_channel;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
```
|
||||
|
||||
With C channels on a server, each call is O(C). In `irc-protocol.c`, this
|
||||
function is called ~30+ times across different protocol message handlers,
|
||||
each of which processes one message. The cost per message is O(C) per
|
||||
`irc_channel_search` call.
|
||||
|
||||
In extreme cases (e.g., a bot joined to thousands of channels, or an IRC
|
||||
network with large JOIN parameter lists), the cumulative cost per protocol
|
||||
event grows significantly.
|
||||
|
||||
The NICK handler at line ~2297 calls `irc_channel_search` once, then
|
||||
iterates over all C channels calling `irc_nick_search` (O(N) each) — that
|
||||
O(C×N) pattern is weechat-0001. The standalone `irc_channel_search` O(C)
|
||||
cost itself is weechat-0003.
|
||||
|
||||
## Fix
|
||||
|
||||
Add a `channels_hashtable` to `struct t_irc_server` (channel_name → `t_irc_channel*`),
|
||||
mirroring the `nicks_hashtable` fix from weechat-0001:
|
||||
|
||||
```c
|
||||
--- a/src/plugins/irc/irc-server.h
|
||||
+++ b/src/plugins/irc/irc-server.h
|
||||
@@ struct t_irc_server {
|
||||
struct t_irc_channel *channels;
|
||||
+ struct t_hashtable *channels_hashtable; /* channel_name(lower) → t_irc_channel* */
|
||||
|
||||
--- a/src/plugins/irc/irc-channel.c
|
||||
+++ b/src/plugins/irc/irc-channel.c
|
||||
struct t_irc_channel *
|
||||
irc_channel_search (struct t_irc_server *server, const char *channel_name)
|
||||
{
|
||||
- for (ptr_channel = server->channels; ptr_channel; ...)
|
||||
- if (irc_server_strcasecmp (...) == 0) return ptr_channel;
|
||||
+ if (server->channels_hashtable) {
|
||||
+ char lower[512];
|
||||
+ snprintf(lower, sizeof(lower), "%s", channel_name);
|
||||
+ /* lowercase using irc_server_casemapping */
|
||||
+ return weechat_hashtable_get(server->channels_hashtable, lower);
|
||||
+ }
|
||||
+ /* fallback to linear scan */
|
||||
+ ...
|
||||
}
|
||||
```
|
||||
|
||||
## Complexity
|
||||
|
||||
| Variant | Cost per lookup |
|
||||
|---------|----------------|
|
||||
| Before | O(C) — linear scan through all channels |
|
||||
| After | O(1) — hash table lookup |
|
||||
| Speedup | C× — at C=1000 channels: 1000× |
|
||||
|
||||
## Notes
|
||||
- IRC servers typically allow users to join 100-2000 channels
|
||||
- Bots joining many channels (monitoring, bridging) are common and hit this hard
|
||||
- The `t_irc_server.channels` list is modified on JOIN/PART; the hashtable
|
||||
must be kept in sync in `irc_channel_new()` and `irc_channel_free()`
|
||||
- weechat-0001 fixed `nicks_hashtable`; this defect is the parallel issue
|
||||
for channel lookup on the server
|
||||
114
defects/weechat/unit/WeechatChannelSearchTest.java
Normal file
114
defects/weechat/unit/WeechatChannelSearchTest.java
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* weechat-0003 — CWE-407: irc_channel_search O(C) linked-list scan, no hash index
|
||||
*
|
||||
* Models src/plugins/irc/irc-channel.c irc_channel_search():
|
||||
* Slow: linear walk of server->channels linked list → O(C)
|
||||
* called ~30+ times per IRC protocol message
|
||||
* Fast: HashMap<lowerName, channel> → O(1) per lookup
|
||||
*
|
||||
* Impact: bots/clients joined to hundreds of channels see O(C × M) per message
|
||||
* where C = channels, M = channel search calls per message handler.
|
||||
*/
|
||||
public class WeechatChannelSearchTest {
|
||||
|
||||
static class IrcChannel {
|
||||
final String name;
|
||||
IrcChannel next;
|
||||
IrcChannel(String name) { this.name = name.toLowerCase(); }
|
||||
}
|
||||
|
||||
static class IrcServer {
|
||||
IrcChannel channels; // linked list head (slow)
|
||||
int channelCount;
|
||||
final Map<String, IrcChannel> channelsMap = new HashMap<>(); // fast
|
||||
}
|
||||
|
||||
// Build linked list of C channels
|
||||
static void buildServer(IrcServer server, int C) {
|
||||
server.channels = null;
|
||||
server.channelCount = C;
|
||||
for (int i = C - 1; i >= 0; i--) {
|
||||
IrcChannel ch = new IrcChannel("#channel" + i);
|
||||
ch.next = server.channels;
|
||||
server.channels = ch;
|
||||
server.channelsMap.put(ch.name, ch);
|
||||
}
|
||||
}
|
||||
|
||||
// --- SLOW: O(C) linear scan (defect) ---
|
||||
static IrcChannel channelSearchSlow(IrcServer server, String name) {
|
||||
String lower = name.toLowerCase();
|
||||
for (IrcChannel ch = server.channels; ch != null; ch = ch.next) {
|
||||
if (ch.name.equals(lower)) return ch;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- FAST: O(1) hash lookup (fix) ---
|
||||
static IrcChannel channelSearchFast(IrcServer server, String name) {
|
||||
return server.channelsMap.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
// Simulate a protocol message handler that calls irc_channel_search N times
|
||||
static long simulateMessageHandlerSlow(IrcServer server, String[] targets, int callsPerTarget) {
|
||||
long ops = 0;
|
||||
for (String target : targets) {
|
||||
for (int i = 0; i < callsPerTarget; i++) {
|
||||
// each channelSearchSlow = O(C) — count worst-case ops
|
||||
String lower = target.toLowerCase();
|
||||
for (IrcChannel ch = server.channels; ch != null; ch = ch.next) {
|
||||
ops++;
|
||||
if (ch.name.equals(lower)) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static long simulateMessageHandlerFast(IrcServer server, String[] targets, int callsPerTarget) {
|
||||
long ops = 0;
|
||||
for (String target : targets) {
|
||||
for (int i = 0; i < callsPerTarget; i++) {
|
||||
ops++; // O(1) hash lookup
|
||||
server.channelsMap.get(target.toLowerCase());
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("weechat-0003 CWE-407: irc_channel_search O(C) vs O(1)");
|
||||
System.out.println("=======================================================");
|
||||
|
||||
IrcServer server = new IrcServer();
|
||||
|
||||
// Parameters: C channels, M=3 search calls per message handler (conservative)
|
||||
int[][] params = { {100, 3}, {500, 5}, {1000, 10} };
|
||||
|
||||
for (int[] p : params) {
|
||||
int C = p[0], callsPerMsg = p[1];
|
||||
buildServer(server, C);
|
||||
|
||||
// Simulate 100 incoming IRC messages each needing callsPerMsg channel lookups
|
||||
String[] targets = new String[100];
|
||||
for (int i = 0; i < 100; i++)
|
||||
targets[i] = "#channel" + (i % C); // random channels
|
||||
|
||||
long slowOps = simulateMessageHandlerSlow(server, targets, callsPerMsg);
|
||||
long fastOps = simulateMessageHandlerFast(server, targets, callsPerMsg);
|
||||
double ratio = (double) slowOps / fastOps;
|
||||
|
||||
System.out.printf(" C=%4d channels, %d lookups/msg, 100 msgs: slow=%,7d ops fast=%,5d ops speedup=%.0fx%n",
|
||||
C, callsPerMsg, slowOps, fastOps, ratio);
|
||||
|
||||
assert ratio >= (double) C / 4 :
|
||||
"Expected speedup >= " + (C/4) + "x but got " + ratio + " (C=" + C + ")";
|
||||
}
|
||||
|
||||
System.out.println("\nPASS");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue