From 0b7fae5652317ce7ac53550bbbf5e13dea7e4a12 Mon Sep 17 00:00:00 2001 From: Acts1631 Date: Mon, 6 Jul 2026 10:41:01 -0400 Subject: [PATCH] otr: guard empty fragment against size_t underflow in reassembly In enqueue_otr_fragment(), after a ?OTR: reassembly has been opened (opc->full_msg != NULL), the end-tag check evaluates msg[msg_len - 1] where msg_len is size_t. If a PRIVMSG whose body decodes to an empty string reaches this path, msg_len is 0 and msg_len - 1 wraps to SIZE_MAX, indexing far off the page and crashing irssi (SIGSEGV). The initial-fragment path is safe because it is gated by 'pos &&' (strstr of an empty string returns NULL), but the open-reassembly path has no such guard. recode_in() returns a non-NULL empty string for an empty PRIVMSG body, and sig_message_private (signal_add_first on "message private") feeds it straight to otr_receive()/enqueue_otr_fragment(). Relays/bouncers such as bitlbee/znc and some ircds pass PRIVMSGs with an empty trailing parameter through to the client. Drop an empty fragment early: if a reassembly is open, keep waiting for more (OTR_MSG_WAIT_MORE); otherwise treat it as an original message (OTR_MSG_ORIGINAL), matching the existing behaviour of the else branch. --- src/otr/otr.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/otr/otr.c b/src/otr/otr.c index abe24726..8602f2a3 100644 --- a/src/otr/otr.c +++ b/src/otr/otr.c @@ -605,6 +605,15 @@ static enum otr_msg_status enqueue_otr_fragment(const char *msg, struct otr_peer /* We are going to use it quite a bit so ease our life a bit. */ msg_len = strlen(msg); + /* An empty fragment carries no data and no end tag. Without this + * guard, the end-tag check on the open-reassembly path indexes + * msg[msg_len - 1]; when msg_len == 0 (size_t) this underflows to + * SIZE_MAX and reads off the page, crashing irssi. */ + if (msg_len == 0) { + ret = opc->full_msg ? OTR_MSG_WAIT_MORE : OTR_MSG_ORIGINAL; + return ret; + } + if (opc->full_msg) { if (msg_len > (opc->msg_size - opc->msg_len)) { char *tmp_ptr;