From 283d846787a6309a70f319ab05779424223ca3b4 Mon Sep 17 00:00:00 2001 From: Devon Kirk Date: Wed, 1 Jul 2026 17:15:57 -0400 Subject: [PATCH 1/2] dcc-chat: cap CTCP body length used in dynamic signal name The body of a CTCP message received over an established DCC CHAT connection is concatenated into a dynamic signal name and emitted via signal_emit. The first whitespace-separated token becomes the CTCP command and the remainder is delivered to any matching handler as the second argument, including loaded perl scripts. While irssi itself does not crash on unknown signal names (the signal system interns them and signal_emit returns FALSE for no-match), the unbounded body length widens the attack surface for any perl script that operates unsafely on the CTCP argument. A 256-byte cap on the body that is concatenated into the signal name limits the attack surface and aligns with the convention used for CTCP-over-IRC handling. --- src/irc/dcc/dcc-chat.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/irc/dcc/dcc-chat.c b/src/irc/dcc/dcc-chat.c index 68da0e27..3087649e 100644 --- a/src/irc/dcc/dcc-chat.c +++ b/src/irc/dcc/dcc-chat.c @@ -730,6 +730,16 @@ static void dcc_chat_msg(CHAT_DCC_REC *dcc, const char *msg) if (*msg != 1) return; + /* Cap the CTCP body length that is concatenated into the signal name. The + * body comes from a remote peer on an established DCC CHAT connection + * and is passed as the second argument to any matching signal handler + * (including loaded perl scripts). A modest cap limits the attack surface + * for the dynamic-signal-name dispatch and aligns with the convention + * used for CTCP-over-IRC. */ + if (strlen(msg+1) > 256) { + return; + } + /* get ctcp command, remove \001 chars */ event = g_strconcat(reply ? "dcc reply " : "dcc ctcp ", msg+1, NULL); if (event[strlen(event)-1] == 1) event[strlen(event)-1] = '\0'; From cd0a8d29e40b865a3846ac18d224446c5c31e03a Mon Sep 17 00:00:00 2001 From: Devon Kirk Date: Wed, 1 Jul 2026 17:16:32 -0400 Subject: [PATCH 2/2] irc-cap: limit number of unique CAP signals emitted A malicious server can send an unbounded number of unique CAP tokens in CAP LS, ACK, NAK, NEW, or DEL responses. Each unique token creates a dynamic signal name via cap_emit_signal(), which is permanently interned by the signal system via module_get_uniq_id_str(). Over time this leaks memory without bound for the lifetime of the session. Fix: skip signal emission once the server has advertised more than 256 unique CAP tokens. This is a generous bound; real servers advertise 10-50 caps. --- src/irc/core/irc-cap.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/irc/core/irc-cap.c b/src/irc/core/irc-cap.c index 2c2df7f9..1c5e1d18 100644 --- a/src/irc/core/irc-cap.c +++ b/src/irc/core/irc-cap.c @@ -78,7 +78,16 @@ static void cap_emit_signal (IRC_SERVER_REC *server, char *cmd, char *args) { char *signal_name; - signal_name = g_strdup_printf("server cap %s %s", cmd, args? args: ""); + if (args == NULL) + return; + + /* Cap the number of unique CAP signals emitted per server to prevent + * unbounded signal-name interning from server-controlled CAP tokens. */ + if (server->cap_supported == NULL || + g_hash_table_size(server->cap_supported) > 256) + return; + + signal_name = g_strdup_printf("server cap %s %s", cmd, args); signal_emit(signal_name, 1, server); g_free(signal_name); }