Skip to content

Commit

Permalink
Fix for CVE-2023-28366
Browse files Browse the repository at this point in the history
  • Loading branch information
ralight committed Aug 16, 2023
1 parent b1c29e8 commit 6113eac
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 39 deletions.
3 changes: 3 additions & 0 deletions ChangeLog.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
Security:
- 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.
- Broker will now reject Will messages that attempt to publish to $CONTROL/.
- Broker now validates usernames provided in a TLS certificate or TLS-PSK
identity are valid UTF-8.
Expand Down
15 changes: 15 additions & 0 deletions lib/packet_mosq.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,21 @@ int packet__queue(struct mosquitto *mosq, struct mosquitto__packet *packet)

packet->next = NULL;
pthread_mutex_lock(&mosq->out_packet_mutex);

#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

if(mosq->out_packet){
mosq->out_packet_last->next = packet;
}else{
Expand Down
41 changes: 25 additions & 16 deletions src/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ struct mosquitto *context__init(mosq_sock_t sock)
}
}
context->bridge = NULL;
context->msgs_in.inflight_maximum = db.config->max_inflight_messages;
context->msgs_in.inflight_maximum = 1;
context->msgs_out.inflight_maximum = db.config->max_inflight_messages;
context->msgs_in.inflight_quota = db.config->max_inflight_messages;
context->msgs_in.inflight_quota = 1;
context->msgs_out.inflight_quota = db.config->max_inflight_messages;
context->max_qos = 2;
#ifdef WITH_TLS
Expand All @@ -98,6 +98,27 @@ struct mosquitto *context__init(mosq_sock_t sock)
return context;
}

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


/*
* This will result in any outgoing packets going unsent. If we're disconnected
* forcefully then it is usually an error condition and shouldn't be a problem,
Expand All @@ -106,8 +127,6 @@ struct mosquitto *context__init(mosq_sock_t sock)
*/
void context__cleanup(struct mosquitto *context, bool force_free)
{
struct mosquitto__packet *packet;

if(!context) return;

if(force_free){
Expand All @@ -121,6 +140,7 @@ void context__cleanup(struct mosquitto *context, bool force_free)
#endif

alias__free_all(context);
context__cleanup_out_packets(context);

mosquitto__free(context->auth_method);
context->auth_method = NULL;
Expand Down Expand Up @@ -148,18 +168,7 @@ void context__cleanup(struct mosquitto *context, bool force_free)
context->id = NULL;
}
packet__cleanup(&(context->in_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;
context__cleanup_out_packets(context);
#if defined(WITH_BROKER) && defined(__GLIBC__) && defined(WITH_ADNS)
if(context->adns){
gai_cancel(context->adns);
Expand Down
25 changes: 15 additions & 10 deletions src/database.c
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ int db__message_insert(struct mosquitto *context, uint16_t mid, enum mosquitto_m
}
#endif

msg = mosquitto__malloc(sizeof(struct mosquitto_client_msg));
msg = mosquitto__calloc(1, sizeof(struct mosquitto_client_msg));
if(!msg) return MOSQ_ERR_NOMEM;
msg->prev = NULL;
msg->next = NULL;
Expand Down Expand Up @@ -613,6 +613,8 @@ int db__message_insert(struct mosquitto *context, uint16_t mid, enum mosquitto_m

if(dir == mosq_md_out && msg->qos > 0 && state != mosq_ms_queued){
util__decrement_send_quota(context);
}else if(dir == mosq_md_in && msg->qos > 0 && state != mosq_ms_queued){
util__decrement_receive_quota(context);
}

if(dir == mosq_md_out && update){
Expand Down Expand Up @@ -796,23 +798,24 @@ int db__message_store(const struct mosquitto *source, struct mosquitto_msg_store
return MOSQ_ERR_SUCCESS;
}

int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_msg_store **stored)
int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_client_msg **client_msg)
{
struct mosquitto_client_msg *tail;
struct mosquitto_client_msg *cmsg;

*client_msg = NULL;

if(!context) return MOSQ_ERR_INVAL;

*stored = NULL;
DL_FOREACH(context->msgs_in.inflight, tail){
if(tail->store->source_mid == mid){
*stored = tail->store;
DL_FOREACH(context->msgs_in.inflight, cmsg){
if(cmsg->store->source_mid == mid){
*client_msg = cmsg;
return MOSQ_ERR_SUCCESS;
}
}

DL_FOREACH(context->msgs_in.queued, tail){
if(tail->store->source_mid == mid){
*stored = tail->store;
DL_FOREACH(context->msgs_in.queued, cmsg){
if(cmsg->store->source_mid == mid){
*client_msg = cmsg;
return MOSQ_ERR_SUCCESS;
}
}
Expand Down Expand Up @@ -914,6 +917,7 @@ static int db__message_reconnect_reset_incoming(struct mosquitto *context)
}else{
/* Message state can be preserved here because it should match
* whatever the client has got. */
msg->dup = 0;
}
}

Expand All @@ -924,6 +928,7 @@ static int db__message_reconnect_reset_incoming(struct mosquitto *context)
* will be sent out of order.
*/
DL_FOREACH_SAFE(context->msgs_in.queued, msg, tmp){
msg->dup = 0;
db__msg_add_to_queued_stats(&context->msgs_in, msg);
if(db__ready_for_flight(context, mosq_md_in, msg->qos)){
switch(msg->qos){
Expand Down
35 changes: 24 additions & 11 deletions src/handle_publish.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ int handle__publish(struct mosquitto *context)
uint8_t header = context->in_packet.command;
int res = 0;
struct mosquitto_msg_store *msg, *stored = NULL;
struct mosquitto_client_msg *cmsg_stored = NULL;
size_t len;
uint16_t slen;
char *topic_mount;
Expand Down Expand Up @@ -287,24 +288,24 @@ int handle__publish(struct mosquitto *context)
}

if(msg->qos > 0){
db__message_store_find(context, msg->source_mid, &stored);
db__message_store_find(context, msg->source_mid, &cmsg_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) )){
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) )){

log__printf(NULL, MOSQ_LOG_WARNING, "Reused message ID %u from %s detected. Clearing from storage.", msg->source_mid, context->id);
db__message_remove_incoming(context, msg->source_mid);
stored = NULL;
cmsg_stored = NULL;
}

if(!stored){
if(!cmsg_stored){
if(msg->qos == 0
|| db__ready_for_flight(context, mosq_md_in, msg->qos)
|| db__ready_for_queue(context, msg->qos, &context->msgs_in)){
){

dup = 0;
rc = db__message_store(context, msg, message_expiry_interval, 0, mosq_mo_client);
Expand All @@ -316,10 +317,13 @@ int handle__publish(struct mosquitto *context)
}
stored = msg;
msg = NULL;
dup = 0;
}else{
db__msg_store_free(msg);
msg = NULL;
dup = 1;
stored = cmsg_stored->store;
cmsg_stored->dup++;
dup = cmsg_stored->dup;
}

switch(stored->qos){
Expand All @@ -345,11 +349,17 @@ int handle__publish(struct mosquitto *context)
}else{
res = 0;
}

/* db__message_insert() returns 2 to indicate dropped message
* due to queue. This isn't an error so don't disconnect them. */
/* FIXME - this is no longer necessary due to failing early above */
if(!res){
if(send__pubrec(context, stored->source_mid, 0, NULL)) rc = 1;
if(dup == 0 || dup == 1){
rc2 = send__pubrec(context, stored->source_mid, 0, NULL);
if(rc2) rc = rc2;
}else{
return MOSQ_ERR_PROTOCOL;
}
}else if(res == 1){
rc = 1;
}
Expand All @@ -374,6 +384,9 @@ int handle__publish(struct mosquitto *context)
}
db__msg_store_free(msg);
}
if(context->out_packet_count >= db.config->max_queued_messages){
rc = MQTT_RC_QUOTA_EXCEEDED;
}
return rc;
}

4 changes: 2 additions & 2 deletions src/mosquitto_broker_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ struct mosquitto_client_msg{
bool retain;
enum mosquitto_msg_direction direction;
enum mosquitto_msg_state state;
bool dup;
uint8_t dup;
};


Expand Down Expand Up @@ -651,7 +651,7 @@ void db__message_dequeue_first(struct mosquitto *context, struct mosquitto_msg_d
int db__messages_delete(struct mosquitto *context, bool force_free);
int db__messages_easy_queue(struct mosquitto *context, const char *topic, uint8_t qos, uint32_t payloadlen, const void *payload, int retain, uint32_t message_expiry_interval, mosquitto_property **properties);
int db__message_store(const struct mosquitto *source, struct mosquitto_msg_store *stored, uint32_t message_expiry_interval, dbid_t store_id, enum mosquitto_msg_origin origin);
int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_msg_store **stored);
int db__message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_client_msg **client_msg);
void db__msg_store_add(struct mosquitto_msg_store *store);
void db__msg_store_remove(struct mosquitto_msg_store *store);
void db__msg_store_ref_inc(struct mosquitto_msg_store *store);
Expand Down
58 changes: 58 additions & 0 deletions test/broker/03-publish-qos2-dup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/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()
1 change: 1 addition & 0 deletions test/broker/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ msg_sequence_test:
./03-publish-qos1-no-subscribers-v5.py
./03-publish-qos1-retain-disabled.py
./03-publish-qos1.py
./03-publish-qos2-dup.py
./03-publish-qos2-max-inflight.py
./03-publish-qos2.py

Expand Down
1 change: 1 addition & 0 deletions test/broker/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
(1, './03-publish-qos1-no-subscribers-v5.py'),
(1, './03-publish-qos1-retain-disabled.py'),
(1, './03-publish-qos1.py'),
(1, './03-publish-qos2-dup.py'),
(1, './03-publish-qos2-max-inflight.py'),
(1, './03-publish-qos2.py'),

Expand Down
3 changes: 3 additions & 0 deletions www/pages/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ follow the steps on [Eclipse Security] page to report it.
Listed with most recent first. Further information on security related issues
can be found in the [security category].

* 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**.
* August 2022: Deleting the anonymous group in the dynamic security plugin
could lead to a crash. Affecting versions **2.0.0** to **2.0.14** inclusive,
fixed in **2.0.15**.
Expand Down

0 comments on commit 6113eac

Please sign in to comment.