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.
This commit is contained in:
Acts1631 2026-07-06 10:41:01 -04:00
commit 0b7fae5652

View file

@ -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;