Skip to content

Commit

Permalink
Restrict topic hierarchy to 200 levels to prevent possible stack over…
Browse files Browse the repository at this point in the history
…flow.

Closes #1412. Thanks to Ryan Shaw.
  • Loading branch information
ralight committed Sep 15, 2019
1 parent 095bbb5 commit dceeb6f
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 6 deletions.
8 changes: 8 additions & 0 deletions ChangeLog.txt
@@ -1,3 +1,11 @@
1.6.6 - 20190915
================

Broker:
- Restrict topic hierarchy to 200 levels to prevent possible stack overflow.
Closes #1412.


1.6.5 - 20190912
================

Expand Down
45 changes: 45 additions & 0 deletions lib/util_topic.c
Expand Up @@ -49,29 +49,51 @@ and the Eclipse Distribution License is available at
int mosquitto_pub_topic_check(const char *str)
{
int len = 0;
#ifdef WITH_BROKER
int hier_count = 0;
#endif
while(str && str[0]){
if(str[0] == '+' || str[0] == '#'){
return MOSQ_ERR_INVAL;
}
#ifdef WITH_BROKER
else if(str[0] == '/'){
hier_count++;
}
#endif
len++;
str = &str[1];
}
if(len > 65535) return MOSQ_ERR_INVAL;
#ifdef WITH_BROKER
if(hier_count > TOPIC_HIERARCHY_LIMIT) return MOSQ_ERR_INVAL;
#endif

return MOSQ_ERR_SUCCESS;
}

int mosquitto_pub_topic_check2(const char *str, size_t len)
{
size_t i;
#ifdef WITH_BROKER
int hier_count = 0;
#endif

if(len > 65535) return MOSQ_ERR_INVAL;

for(i=0; i<len; i++){
if(str[i] == '+' || str[i] == '#'){
return MOSQ_ERR_INVAL;
}
#ifdef WITH_BROKER
else if(str[i] == '/'){
hier_count++;
}
#endif
}
#ifdef WITH_BROKER
if(hier_count > TOPIC_HIERARCHY_LIMIT) return MOSQ_ERR_INVAL;
#endif

return MOSQ_ERR_SUCCESS;
}
Expand All @@ -87,6 +109,10 @@ int mosquitto_sub_topic_check(const char *str)
{
char c = '\0';
int len = 0;
#ifdef WITH_BROKER
int hier_count = 0;
#endif

while(str && str[0]){
if(str[0] == '+'){
if((c != '\0' && c != '/') || (str[1] != '\0' && str[1] != '/')){
Expand All @@ -97,11 +123,19 @@ int mosquitto_sub_topic_check(const char *str)
return MOSQ_ERR_INVAL;
}
}
#ifdef WITH_BROKER
else if(str[0] == '/'){
hier_count++;
}
#endif
len++;
c = str[0];
str = &str[1];
}
if(len > 65535) return MOSQ_ERR_INVAL;
#ifdef WITH_BROKER
if(hier_count > TOPIC_HIERARCHY_LIMIT) return MOSQ_ERR_INVAL;
#endif

return MOSQ_ERR_SUCCESS;
}
Expand All @@ -110,6 +144,9 @@ int mosquitto_sub_topic_check2(const char *str, size_t len)
{
char c = '\0';
size_t i;
#ifdef WITH_BROKER
int hier_count = 0;
#endif

if(len > 65535) return MOSQ_ERR_INVAL;

Expand All @@ -123,8 +160,16 @@ int mosquitto_sub_topic_check2(const char *str, size_t len)
return MOSQ_ERR_INVAL;
}
}
#ifdef WITH_BROKER
else if(str[i] == '/'){
hier_count++;
}
#endif
c = str[i];
}
#ifdef WITH_BROKER
if(hier_count > TOPIC_HIERARCHY_LIMIT) return MOSQ_ERR_INVAL;
#endif

return MOSQ_ERR_SUCCESS;
}
Expand Down
3 changes: 3 additions & 0 deletions src/mosquitto_broker_internal.h
Expand Up @@ -73,6 +73,9 @@ and the Eclipse Distribution License is available at

#define WEBSOCKET_CLIENT -2


#define TOPIC_HIERARCHY_LIMIT 200

/* ========================================
* UHPA data types
* ======================================== */
Expand Down
7 changes: 7 additions & 0 deletions src/subs.c
Expand Up @@ -220,6 +220,7 @@ static int sub__topic_tokenise(const char *subtopic, struct sub__token **topics)
int start, stop, tlen;
int i;
char *topic;
int count = 0;

assert(subtopic);
assert(topics);
Expand All @@ -242,6 +243,7 @@ static int sub__topic_tokenise(const char *subtopic, struct sub__token **topics)

stop = 0;
for(i=start; i<len+1; i++){
count++;
if(subtopic[i] == '/' || subtopic[i] == '\0'){
stop = i;

Expand All @@ -262,6 +264,11 @@ static int sub__topic_tokenise(const char *subtopic, struct sub__token **topics)
}
}

if(count > TOPIC_HIERARCHY_LIMIT){
/* Set limit on hierarchy levels, to restrict stack usage. */
goto cleanup;
}

return MOSQ_ERR_SUCCESS;

cleanup:
Expand Down
36 changes: 36 additions & 0 deletions test/broker/02-subscribe-long-topic.py
@@ -0,0 +1,36 @@
#!/usr/bin/env python3

# Test whether a SUBSCRIBE to a topic with 65535 hierarchy characters fails
# This needs checking with MOSQ_USE_VALGRIND=1 to detect memory failures
# https://github.com/eclipse/mosquitto/issues/1412

from mosq_test_helper import *

rc = 1
mid = 1
keepalive = 60
connect_packet = mosq_test.gen_connect("subscribe-long-test", keepalive=keepalive)
connack_packet = mosq_test.gen_connack(rc=0)

subscribe_packet = mosq_test.gen_subscribe(mid, "/"*65535, 0)
suback_packet = mosq_test.gen_suback(mid, 0)

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, subscribe_packet, b"", "suback")

rc = 0

sock.close()
finally:
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))

exit(rc)

37 changes: 37 additions & 0 deletions test/broker/03-publish-long-topic.py
@@ -0,0 +1,37 @@
#!/usr/bin/env python3

# Test whether a PUBLISH to a topic with 65535 hierarchy characters fails
# This needs checking with MOSQ_USE_VALGRIND=1 to detect memory failures
# https://github.com/eclipse/mosquitto/issues/1412


from mosq_test_helper import *

rc = 1
mid = 19
keepalive = 60
connect_packet = mosq_test.gen_connect("pub-qos1-test", keepalive=keepalive)
connack_packet = mosq_test.gen_connack(rc=0)

publish_packet = mosq_test.gen_publish("/"*65535, qos=1, mid=mid, payload="message")
puback_packet = mosq_test.gen_puback(mid)

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, puback_packet, "puback")

rc = 0

sock.close()
finally:
broker.terminate()
broker.wait()
(stdo, stde) = broker.communicate()
if rc:
print(stde.decode('utf-8'))

exit(rc)

1 change: 1 addition & 0 deletions test/broker/Makefile
Expand Up @@ -75,6 +75,7 @@ endif
./02-subpub-qos2.py
./02-subscribe-dollar-v5.py
./02-subscribe-invalid-utf8.py
./02-subscribe-long-topic.py
./02-subscribe-persistence-flipflop.py
./02-subscribe-qos0.py
./02-subscribe-qos1.py
Expand Down
1 change: 1 addition & 0 deletions test/broker/test.py
Expand Up @@ -54,6 +54,7 @@
(1, './02-subpub-qos2.py'),
(1, './02-subscribe-dollar-v5.py'),
(1, './02-subscribe-invalid-utf8.py'),
(1, './02-subscribe-long-topic.py'),
(1, './02-subscribe-persistence-flipflop.py'),
(1, './02-subscribe-qos0.py'),
(1, './02-subscribe-qos1.py'),
Expand Down
18 changes: 12 additions & 6 deletions test/mosq_test.py
Expand Up @@ -478,19 +478,25 @@ def gen_pubrel(mid, dup=False, proto_ver=4, reason_code=-1, properties=None):
def gen_pubcomp(mid, proto_ver=4, reason_code=-1, properties=None):
return _gen_command_with_mid(112, mid, proto_ver, reason_code, properties)


def gen_subscribe(mid, topic, qos, proto_ver=4, properties=b""):
topic = topic.encode("utf-8")
packet = struct.pack("!B", 130)
if proto_ver == 5:
if properties == b"":
pack_format = "!BBHBH"+str(len(topic))+"sB"
return struct.pack(pack_format, 130, 2+1+2+len(topic)+1, mid, 0, len(topic), topic, qos)
packet += pack_remaining_length(2+1+2+len(topic)+1)
pack_format = "!HBH"+str(len(topic))+"sB"
return packet + struct.pack(pack_format, mid, 0, len(topic), topic, qos)
else:
properties = mqtt5_props.prop_finalise(properties)
pack_format = "!BBH"+str(len(properties))+"s"+"H"+str(len(topic))+"sB"
return struct.pack(pack_format, 130, 2+1+2+len(topic)+len(properties), mid, properties, len(topic), topic, qos)
packet += pack_remaining_length(2+1+2+len(topic)+len(properties))
pack_format = "!H"+str(len(properties))+"s"+"H"+str(len(topic))+"sB"
return packet + struct.pack(pack_format, mid, properties, len(topic), topic, qos)
else:
pack_format = "!BBHH"+str(len(topic))+"sB"
return struct.pack(pack_format, 130, 2+2+len(topic)+1, mid, len(topic), topic, qos)
packet += pack_remaining_length(2+2+len(topic)+1)
pack_format = "!HH"+str(len(topic))+"sB"
return packet + struct.pack(pack_format, mid, len(topic), topic, qos)


def gen_suback(mid, qos, proto_ver=4):
if proto_ver == 5:
Expand Down

1 comment on commit dceeb6f

@ckrey
Copy link

@ckrey ckrey commented on dceeb6f Oct 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the changes to src/subs.c you limit the length of a Topic Name to 200, not just the number of hierarchy levels:

$ mosquitto_pub -m 'topic too long' -d -q 2 -t '123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789'
Client mosq-T8zmW8VvLMUGMhpTjZ sending CONNECT
Client mosq-T8zmW8VvLMUGMhpTjZ received CONNACK (0)
Client mosq-T8zmW8VvLMUGMhpTjZ sending PUBLISH (d0, q2, r0, m1, '123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789', ... (14 bytes))
Client mosq-T8zmW8VvLMUGMhpTjZ received PUBREC (Mid: 1)
Client mosq-T8zmW8VvLMUGMhpTjZ sending PUBREL (m1)
Error: The connection was lost.

Please sign in to comment.