CVE-2023-28366 (C, published 2023-09-01T16:15Z)
Description: value: The broker in Eclipse Mosquitto 1.3.2 through 2.x before 2.0.16 has a memory leak that can be abused remotely when a client sends many QoS 2 messages with duplicate message IDs, and fails to respond to PUBREC commands. This occurs because of mishandling of EAGAIN from the libc send function.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-401 \u2014 Missing Release of Memory after Effective Lifetime
The product does not sufficiently track and release allocated memory after it has been used, which slowly consumes remaining memory.
Repository: https://github.com/eclipse/mosquitto
Files changed:
ChangeLog.txt
lib/packet_mosq.c
src/context.c
src/database.c
src/handle_publish.c
src/mosquitto_broker_internal.h
test/broker/03-publish-qos2-dup.py
test/broker/Makefile
test/broker/test.py
www/pages/security.md
Commit message: Fix for CVE-2023-28366


Vulnerable code:
	context->msgs_in.inflight_maximum = db.config->max_inflight_messages;
	context->msgs_in.inflight_quota = db.config->max_inflight_messages;
	struct mosquitto__packet *packet;

	if(context->current_out_packet){
		packet__cleanup(context->current_out_packet);
		mosquitto__free(context->current_out_packet);
		context->current_out_packet = NULL;
	}
	while(context->out_packet){
		packet__cleanup(context->out_packet);
		packet = context->out_packet;
		context->out_packet = context->out_packet->next;
		mosquitto__free(packet);
	}
	context->out_packet_count = 0;
	msg = mosquitto__malloc(sizeof(struct mosquitto_client_msg));
int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_msg_store **stored)
	struct mosquitto_client_msg *tail;
	*stored = NULL;
	DL_FOREACH(context->msgs_in.inflight, tail){
		if(tail->store->source_mid == mid){
			*stored = tail->store;
	DL_FOREACH(context->msgs_in.queued, tail){
		if(tail->store->source_mid == mid){
			*stored = tail->store;
		db__message_store_find(context, msg->source_mid, &stored);
	if(stored && msg->source_mid != 0 &&
			(stored->qos != msg->qos
			 || stored->payloadlen != msg->payloadlen
			 || strcmp(stored->topic, msg->topic)
			 || memcmp(stored->payload, msg->payload, msg->payloadlen) )){
		stored = NULL;
	if(!stored){
				|| db__ready_for_queue(context, msg->qos, &context->msgs_in)){
		dup = 1;
				if(send__pubrec(context, stored->source_mid, 0, NULL)) rc = 1;
	bool dup;
int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_msg_store **stored);

Fixed code:
- CVE-2023-28366: Fix memory leak in broker when clients send multiple QoS 2
  messages with the same message ID, but then never respond to the PUBREC
  commands.

#ifdef WITH_BROKER
	if(mosq->out_packet_count >= db.config->max_queued_messages){
		mosquitto__free(packet);
		if(mosq->is_dropping == false){
			mosq->is_dropping = true;
			log__printf(NULL, MOSQ_LOG_NOTICE,
					"Outgoing messages are being dropped for client %s.",
					mosq->id);
		}
		G_MSGS_DROPPED_INC();
		return MOSQ_ERR_SUCCESS;
	}
#endif

	context->msgs_in.inflight_maximum = 1;
	context->msgs_in.inflight_quota = 1;
static void context__cleanup_out_packets(struct mosquitto *context)
{
	struct mosquitto__packet *packet;

	if(!context) return;

	if(context->current_out_packet){
		packet__cleanup(context->current_out_packet);
		mosquitto__free(context->current_out_packet);
		context->current_out_packet = NULL;
	}
	while(context->out_packet){
		packet__cleanup(context->out_packet);
		packet = context->out_packet;
		context->out_packet = context->out_packet->next;
		mosquitto__free(packet);
	}
	context->out_packet_count = 0;
}


	context__cleanup_out_packets(context);
	context__cleanup_out_packets(context);
	msg = mosquitto__calloc(1, sizeof(struct mosquitto_client_msg));
	}else if(dir == mosq_md_in && msg->qos > 0 && state != mosq_ms_queued){
		util__decrement_receive_quota(context);
int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_client_msg **client_msg)
	struct mosquitto_client_msg *cmsg;

	*client_msg = NULL;
	DL_FOREACH(context->msgs_in.inflight, cmsg){
		if(cmsg->store->source_mid == mid){
			*client_msg = cmsg;
	DL_FOREACH(context->msgs_in.queued, cmsg){
		if(cmsg->store->source_mid == mid){
			*client_msg = cmsg;
			msg->dup = 0;
		msg->dup = 0;
	struct mosquitto_client_msg *cmsg_stored = NULL;
		db__message_store_find(context, msg->source_mid, &cmsg_stored);
	if(cmsg_stored && cmsg_stored->store && msg->source_mid != 0 &&
			(cmsg_stored->store->qos != msg->qos
			 || cmsg_stored->store->payloadlen != msg->payloadlen
			 || strcmp(cmsg_stored->store->topic, msg->topic)
			 || memcmp(cmsg_stored->store->payload, msg->payload, msg->payloadlen) )){
		cmsg_stored = NULL;
	if(!cmsg_stored){
				){
		dup = 0;
		stored = cmsg_stored->store;
		cmsg_stored->dup++;
		dup = cmsg_stored->dup;

				if(dup == 0 || dup == 1){
					rc2 = send__pubrec(context, stored->source_mid, 0, NULL);
					if(rc2) rc = rc2;
				}else{
					return MOSQ_ERR_PROTOCOL;
				}
	if(context->out_packet_count >= db.config->max_queued_messages){
		rc = MQTT_RC_QUOTA_EXCEEDED;
	}
	uint8_t dup;
int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_client_msg **client_msg);
#!/usr/bin/env python3

from mosq_test_helper import *

def do_test(proto_ver):
    rc = 1
    connect_packet = mosq_test.gen_connect("03-pub-qos2-dup-test", proto_ver=proto_ver)
    connack_packet = mosq_test.gen_connack(rc=0, proto_ver=proto_ver)

    mid = 1
    publish_packet = mosq_test.gen_publish("topic", qos=2, mid=mid, payload="message", proto_ver=proto_ver, dup=1)
    pubrec_packet = mosq_test.gen_pubrec(mid, proto_ver=proto_ver)

    disconnect_packet = mosq_test.gen_disconnect(reason_code=130, proto_ver=proto_ver)

    port = mosq_test.get_port()
    broker = mosq_test.start_broker(filename=os.path.basename(__file__), port=port)

    try:
        sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=port)
        mosq_test.do_send_receive(sock, publish_packet, pubrec_packet, "pubrec 1")
        mosq_test.do_send_receive(sock, publish_packet, pubrec_packet, "pubrec 2")
        if proto_ver == 5:
            mosq_test.do_send_receive(sock, publish_packet, disconnect_packet, "disconnect")
            rc = 0
        else:
            try:
                mosq_test.do_send_receive(sock, publish_packet, b"", "disconnect1")
                rc = 0
            except BrokenPipeError:
                rc = 0

        sock.close()
    except Exception as e:
        print(e)
    except mosq_test.TestError:
        pass
    finally:
        broker.terminate()
        broker.wait()
        (stdo, stde) = broker.communicate()
        if rc:
            print(stde.decode('utf-8'))
            print("proto_ver=%d" % (proto_ver))
            exit(rc)


def all_tests():
    rc = do_test(proto_ver=4)
    if rc:
        return rc;
    rc = do_test(proto_ver=5)
    if rc:
        return rc;
    return 0

if __name__ == '__main__':
    all_tests()
	./03-publish-qos2-dup.py
    (1, './03-publish-qos2-dup.py'),
* June 2023: [CVE-2023-28366]: Clients sending unacknowledged QoS 2 messages
  with duplicate message ids cause a memory leak. Affecting versions **1.3.2**
  to **2.0.15** inclusive, fixed in **2.0.16**.