diff --git a/src/otr/otr.c b/src/otr/otr.c index 94cb65aa..08c85d22 100644 --- a/src/otr/otr.c +++ b/src/otr/otr.c @@ -615,6 +615,18 @@ static enum otr_msg_status enqueue_otr_fragment(const char *msg, struct otr_peer } if (opc->full_msg) { + /* Reject the reassembly if appending this fragment would exceed + * the maximum assembled size. Without this check a peer can keep + * the reassembly open forever (fragments never end with '.') and + * grow opc->full_msg without bound, exhausting memory. */ + if (opc->msg_len + msg_len + 1 > OTR_REASM_MAX_SIZE) { + free(opc->full_msg); + opc->full_msg = NULL; + opc->msg_size = opc->msg_len = 0; + ret = OTR_MSG_ERROR; + return ret; + } + /* Grow the buffer unless it already has room for msg_len bytes * *plus* the NUL terminator written below. Using '>' here (instead * of '>=') is an off-by-one: when msg_len exactly equals the @@ -674,6 +686,12 @@ static enum otr_msg_status enqueue_otr_fragment(const char *msg, struct otr_peer */ pos = strstr(msg, OTR_MSG_BEGIN_TAG); if (pos && (pos == msg) && msg[msg_len - 1] != OTR_MSG_END_TAG) { + /* Reject oversized initial fragment. */ + if ((msg_len * 2) + 1 > OTR_REASM_MAX_SIZE) { + ret = OTR_MSG_ERROR; + return ret; + } + /* Allocate full message buffer with an extra for NULL byte. */ opc->full_msg = g_new0(char, (msg_len * 2) + 1); if (!opc->full_msg) { diff --git a/src/otr/otr.h b/src/otr/otr.h index e0324a13..d5c32fd3 100644 --- a/src/otr/otr.h +++ b/src/otr/otr.h @@ -54,6 +54,12 @@ #define OTR_MSG_BEGIN_TAG "?OTR:" #define OTR_MSG_END_TAG '.' +/* Maximum total size of a reassembled multi-fragment OTR message. Incoming + * ?OTR: fragments are concatenated into a per-peer heap buffer until the + * end tag is received; this caps that buffer so a peer cannot grow it + * without bound by sending fragments that never terminate. */ +#define OTR_REASM_MAX_SIZE (256 * 1024) + /* IRC /me command marker and len. */ #define OTR_IRC_MARKER_ME "/me " #define OTR_IRC_MARKER_ME_LEN sizeof(OTR_IRC_MARKER_ME) - 1