From 68131a09bea0de4682114a1388df8c9b70cd2782 Mon Sep 17 00:00:00 2001 From: Acts1631 Date: Sun, 5 Jul 2026 21:01:21 -0400 Subject: [PATCH] ctcp: cap CTCP body length used in dynamic signal name (IRC path) The body of a CTCP message received over IRC (PRIVMSG/NOTICE) is concatenated into a dynamic signal name and emitted via signal_emit in ctcp_msg/ctcp_reply (ctcp.c) and the DCC sub-dispatch ctcp_msg_dcc/ctcp_reply_dcc (dcc.c). The first whitespace-separated token becomes the CTCP command and is interned as a signal name; the remainder is delivered to any matching handler, including loaded perl scripts. signal_emit interns unknown signal names permanently in the global signal-name registry (module_get_uniq_id_str g_strdup's the name into two hash tables that are freed only at exit). With no length or count cap on the IRC-CTCP path, a remote user sending many distinct CTCP command names grows the registry without bound, exhausting memory and crashing the victim's irssi. --- src/irc/core/ctcp.c | 14 ++++++++++++++ src/irc/dcc/dcc.c | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/irc/core/ctcp.c b/src/irc/core/ctcp.c index 42b93411..a7b0924a 100644 --- a/src/irc/core/ctcp.c +++ b/src/irc/core/ctcp.c @@ -234,6 +234,15 @@ static void ctcp_msg(IRC_SERVER_REC *server, const char *data, if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_CTCPS)) return; + /* Cap the CTCP body length that is concatenated into the signal name. + * The body comes from a remote user over IRC (PRIVMSG) 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-DCC-chat (dcc-chat.c). */ + if (strlen(data) > 256) + return; + str = g_strconcat("ctcp msg ", data, NULL); args = strchr(str+9, ' '); if (args != NULL) *args++ = '\0'; else args = ""; @@ -254,6 +263,11 @@ static void ctcp_reply(IRC_SERVER_REC *server, const char *data, if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_CTCPS)) return; + /* Cap the CTCP body length concatenated into the signal name; see + * ctcp_msg() for the rationale. */ + if (strlen(data) > 256) + return; + str = g_strconcat("ctcp reply ", data, NULL); args = strchr(str+11, ' '); if (args != NULL) *args++ = '\0'; else args = ""; diff --git a/src/irc/dcc/dcc.c b/src/irc/dcc/dcc.c index bb1c6c22..fd13165a 100644 --- a/src/irc/dcc/dcc.c +++ b/src/irc/dcc/dcc.c @@ -364,6 +364,11 @@ static void ctcp_msg_dcc(IRC_SERVER_REC *server, const char *data, if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_DCC)) return; + /* Cap the CTCP body length concatenated into the signal name; see + * ctcp_msg() in ctcp.c for the rationale. */ + if (strlen(data) > 256) + return; + str = g_strconcat("ctcp msg dcc ", data, NULL); args = strchr(str+13, ' '); if (args != NULL) *args++ = '\0'; else args = ""; @@ -386,6 +391,11 @@ static void ctcp_reply_dcc(IRC_SERVER_REC *server, const char *data, if (ignore_check(SERVER(server), nick, addr, target, data, MSGLEVEL_DCC)) return; + /* Cap the CTCP body length concatenated into the signal name; see + * ctcp_msg() in ctcp.c for the rationale. */ + if (strlen(data) > 256) + return; + str = g_strconcat("ctcp reply dcc ", data, NULL); args = strchr(str+15, ' '); if (args != NULL) *args++ = '\0'; else args = "";