Skip to content

Commit 9cfac07

Browse files
authored
Merge pull request #480 from aidangarske/fenrir-fixes-1
Add Testing Validation and Fixes for wolfMQTT
2 parents afd7962 + fc7648b commit 9cfac07

7 files changed

Lines changed: 828 additions & 22 deletions

File tree

scripts/broker.test

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,119 @@ else
880880
fi
881881
fi
882882

883+
# --- Test 23: $-prefix wildcard guard [MQTT-4.7.2] ---
884+
echo ""
885+
echo "--- Test 23: \$-prefix wildcard guard [MQTT-4.7.2] ---"
886+
if [ "$skip_plain" = "yes" ]; then
887+
echo "SKIP: \$-prefix wildcard guard (plain listener disabled)"
888+
elif [ "$has_wildcards" = "no" ]; then
889+
echo "SKIP: \$-prefix wildcard guard (wildcards not built)"
890+
else
891+
start_broker
892+
# Subscribe to '#' (should NOT receive $SYS messages per MQTT-4.7.2)
893+
rm -f "${TMP_DIR}/t23_wild.ready" "${TMP_DIR}/t23_exact.ready"
894+
timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "#" -i "sub_wild_dollar" \
895+
-R "${TMP_DIR}/t23_wild.ready" >"${TMP_DIR}/t23_wild.log" 2>&1 &
896+
T23_WILD_PID=$!
897+
# Subscribe to exact '$SYS/test' (SHOULD receive the message)
898+
timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n '$SYS/test' -i "sub_exact_dollar" \
899+
-R "${TMP_DIR}/t23_exact.ready" >"${TMP_DIR}/t23_exact.log" 2>&1 &
900+
T23_EXACT_PID=$!
901+
TEST_PIDS+=($T23_WILD_PID $T23_EXACT_PID)
902+
wait_for_file "${TMP_DIR}/t23_wild.ready" 5
903+
wait_for_file "${TMP_DIR}/t23_exact.ready" 5
904+
# Publish to $SYS/test
905+
./$pub_bin -T -h 127.0.0.1 -p $port -n '$SYS/test' -m "dollar_sys_msg" \
906+
>"${TMP_DIR}/t23_pub.log" 2>&1
907+
sleep 0.3
908+
kill $T23_WILD_PID $T23_EXACT_PID 2>/dev/null
909+
wait $T23_WILD_PID 2>/dev/null || true
910+
wait $T23_EXACT_PID 2>/dev/null || true
911+
TEST_PIDS=()
912+
T23_WILD_GOT=no
913+
T23_EXACT_GOT=no
914+
grep -q "dollar_sys_msg" "${TMP_DIR}/t23_wild.log" 2>/dev/null && T23_WILD_GOT=yes
915+
grep -q "dollar_sys_msg" "${TMP_DIR}/t23_exact.log" 2>/dev/null && T23_EXACT_GOT=yes
916+
if [ "$T23_WILD_GOT" = "no" ] && [ "$T23_EXACT_GOT" = "yes" ]; then
917+
echo "PASS: \$-prefix wildcard guard (# blocked, exact matched)"
918+
else
919+
echo "FAIL: \$-prefix wildcard guard (wild_got=$T23_WILD_GOT, exact_got=$T23_EXACT_GOT)"
920+
FAIL=1
921+
fi
922+
fi
923+
924+
# --- Test 24: PUBLISH topic wildcard rejection [MQTT-3.3.2-2] ---
925+
echo ""
926+
echo "--- Test 24: PUBLISH topic wildcard rejection [MQTT-3.3.2-2] ---"
927+
if [ "$skip_plain" = "yes" ]; then
928+
echo "SKIP: PUBLISH wildcard rejection (plain listener disabled)"
929+
elif [ "$has_wildcards" = "no" ]; then
930+
echo "SKIP: PUBLISH wildcard rejection (wildcards not built)"
931+
else
932+
start_broker
933+
# Subscribe to a wildcard filter that would match if the broker allowed it
934+
rm -f "${TMP_DIR}/t24_sub.ready"
935+
timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "test/wild/+" -i "sub_wild24" \
936+
-R "${TMP_DIR}/t24_sub.ready" >"${TMP_DIR}/t24_sub.log" 2>&1 &
937+
T24_SUB_PID=$!
938+
TEST_PIDS+=($T24_SUB_PID)
939+
wait_for_file "${TMP_DIR}/t24_sub.ready" 5
940+
# Attempt to PUBLISH with '+' in the topic name (must be rejected by broker)
941+
./$pub_bin -T -h 127.0.0.1 -p $port -n "test/+/card" -m "bad_wildcard_plus" \
942+
>"${TMP_DIR}/t24_pub_plus.log" 2>&1
943+
# Attempt to PUBLISH with '#' in the topic name (must be rejected by broker)
944+
./$pub_bin -T -h 127.0.0.1 -p $port -n "test/#" -m "bad_wildcard_hash" \
945+
>"${TMP_DIR}/t24_pub_hash.log" 2>&1
946+
sleep 0.3
947+
kill $T24_SUB_PID 2>/dev/null
948+
wait $T24_SUB_PID 2>/dev/null || true
949+
TEST_PIDS=()
950+
# Verify subscriber did NOT receive either message
951+
T24_GOT_PLUS=no
952+
T24_GOT_HASH=no
953+
grep -q "bad_wildcard_plus" "${TMP_DIR}/t24_sub.log" 2>/dev/null && T24_GOT_PLUS=yes
954+
grep -q "bad_wildcard_hash" "${TMP_DIR}/t24_sub.log" 2>/dev/null && T24_GOT_HASH=yes
955+
if [ "$T24_GOT_PLUS" = "no" ] && [ "$T24_GOT_HASH" = "no" ]; then
956+
echo "PASS: PUBLISH topic wildcard rejection (+ and # blocked)"
957+
else
958+
echo "FAIL: PUBLISH wildcard rejection (got_plus=$T24_GOT_PLUS, got_hash=$T24_GOT_HASH)"
959+
FAIL=1
960+
fi
961+
fi
962+
963+
# --- Test 25: Multi-level wildcard matches parent [MQTT-4.7.1.2] ---
964+
echo ""
965+
echo "--- Test 25: Multi-level wildcard matches parent [MQTT-4.7.1.2] ---"
966+
if [ "$skip_plain" = "yes" ]; then
967+
echo "SKIP: Multi-level wildcard parent match (plain listener disabled)"
968+
elif [ "$has_wildcards" = "no" ]; then
969+
echo "SKIP: Multi-level wildcard parent match (wildcards not built)"
970+
else
971+
start_broker
972+
# Subscribe to 'sport/#' — per MQTT-4.7.1.2 this must also match 'sport'
973+
rm -f "${TMP_DIR}/t25_sub.ready"
974+
timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "sport/#" -i "sub_parent25" \
975+
-R "${TMP_DIR}/t25_sub.ready" >"${TMP_DIR}/t25_sub.log" 2>&1 &
976+
T25_SUB_PID=$!
977+
TEST_PIDS+=($T25_SUB_PID)
978+
wait_for_file "${TMP_DIR}/t25_sub.ready" 5
979+
# Publish to 'sport' (parent level, no trailing slash)
980+
./$pub_bin -T -h 127.0.0.1 -p $port -n "sport" -m "parent_match_msg" \
981+
>"${TMP_DIR}/t25_pub.log" 2>&1
982+
sleep 0.3
983+
kill $T25_SUB_PID 2>/dev/null
984+
wait $T25_SUB_PID 2>/dev/null || true
985+
TEST_PIDS=()
986+
T25_GOT=no
987+
grep -q "parent_match_msg" "${TMP_DIR}/t25_sub.log" 2>/dev/null && T25_GOT=yes
988+
if [ "$T25_GOT" = "yes" ]; then
989+
echo "PASS: Multi-level wildcard matches parent (sport/# matched sport)"
990+
else
991+
echo "FAIL: Multi-level wildcard parent match (got=$T25_GOT)"
992+
FAIL=1
993+
fi
994+
fi
995+
883996
# --- WebSocket Tests ---
884997
ws_client_bin="examples/websocket/websocket_client"
885998
has_websocket=no

src/mqtt_broker.c

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,7 @@
3535

3636
#ifdef WOLFMQTT_BROKER
3737

38-
/* Secure memory zeroing - prevents compiler dead-store elimination */
39-
#ifdef ENABLE_MQTT_TLS
40-
#include <wolfssl/wolfcrypt/memory.h>
41-
#define BROKER_FORCE_ZERO(mem, len) wc_ForceZero(mem, (word32)(len))
42-
#else
43-
/* Local implementation matching wolfCrypt's ForceZero */
44-
static void BrokerForceZero(void* mem, word32 len)
45-
{
46-
volatile byte* p = (volatile byte*)mem;
47-
word32 i;
48-
for (i = 0; i < len; i++) {
49-
p[i] = 0;
50-
}
51-
}
52-
#define BROKER_FORCE_ZERO(mem, len) BrokerForceZero(mem, (word32)(len))
53-
#endif
38+
#define BROKER_FORCE_ZERO(mem, len) Mqtt_ForceZero(mem, (word32)(len))
5439

5540
/* -------------------------------------------------------------------------- */
5641
/* Platform includes */
@@ -887,6 +872,7 @@ static int callback_broker_mqtt(struct lws *wsi,
887872
WOLFMQTT_FREE(ws->tx_pending);
888873
ws->tx_pending = NULL;
889874
ws->tx_len = 0;
875+
ws->status = -1;
890876
return -1;
891877
}
892878
WOLFMQTT_FREE(ws->tx_pending);
@@ -1037,6 +1023,12 @@ static int BrokerWsNetWrite(void* context, const byte* buf, int buf_len,
10371023
return MQTT_CODE_ERROR_NETWORK;
10381024
}
10391025

1026+
/* Check if the write callback reported an error (it frees tx_pending
1027+
* and sets status to -1 before returning -1 to lws) */
1028+
if (ws->status < 0) {
1029+
return MQTT_CODE_ERROR_NETWORK;
1030+
}
1031+
10401032
return buf_len;
10411033
}
10421034

@@ -2552,6 +2544,10 @@ static int BrokerTopicMatch(const char* filter, const char* topic)
25522544
f++;
25532545
}
25542546
else if (*t == '/' || *f == '/') {
2547+
/* [MQTT-4.7.1.2] 'topic/#' must also match 'topic' itself */
2548+
if (*f == '/' && f[1] == '#' && f[2] == '\0' && *t == '\0') {
2549+
return 1;
2550+
}
25552551
return 0;
25562552
}
25572553
}
@@ -3865,6 +3861,18 @@ int MqttBroker_Start(MqttBroker* broker)
38653861
if (broker->auth_user || broker->auth_pass) {
38663862
WBLOG_INFO(broker, "broker: auth enabled user=%s",
38673863
broker->auth_user ? broker->auth_user : "(null)");
3864+
#ifdef ENABLE_MQTT_TLS
3865+
#ifndef WOLFMQTT_BROKER_NO_INSECURE
3866+
if (broker->use_tls &&
3867+
broker->port != broker->port_tls) {
3868+
WBLOG_ERR(broker,
3869+
"broker: WARNING: auth credentials exposed on plaintext "
3870+
"port %d. Rebuild with ./configure --disable-broker-insecure "
3871+
"for TLS-only",
3872+
broker->port);
3873+
}
3874+
#endif
3875+
#endif
38683876
}
38693877
#endif
38703878

src/mqtt_client.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
#include "wolfmqtt/mqtt_client.h"
2828

29+
#define CLIENT_FORCE_ZERO(mem, len) Mqtt_ForceZero(mem, (word32)(len))
30+
2931
/* DOCUMENTED BUILD OPTIONS:
3032
*
3133
* WOLFMQTT_MULTITHREAD: Enables multi-thread support with mutex protection on
@@ -1664,6 +1666,14 @@ int MqttClient_Connect(MqttClient *client, MqttConnect *mc_connect)
16641666
}
16651667

16661668
if (mc_connect->stat.write == MQTT_MSG_BEGIN) {
1669+
#ifdef DEBUG_WOLFMQTT
1670+
/* Warn if credentials are being sent without TLS */
1671+
if ((mc_connect->username != NULL || mc_connect->password != NULL) &&
1672+
!(MqttClient_Flags(client, 0, 0) & MQTT_CLIENT_FLAG_IS_TLS)) {
1673+
PRINTF("Warning: MQTT credentials are being sent without TLS");
1674+
}
1675+
#endif
1676+
16671677
/* Flag write active / lock mutex */
16681678
if ((rc = MqttWriteStart(client, &mc_connect->stat)) != 0) {
16691679
return rc;
@@ -1714,11 +1724,17 @@ int MqttClient_Connect(MqttClient *client, MqttConnect *mc_connect)
17141724
&& client->write.total > 0
17151725
#endif
17161726
) {
1717-
/* keep send locked and return early */
1727+
/* keep send locked and return early.
1728+
* Note: tx_buf still contains credentials until write completes */
17181729
return rc;
17191730
}
17201731
#endif
17211732
MqttWriteStop(client, &mc_connect->stat);
1733+
1734+
/* Clear tx_buf to remove any plaintext credentials from memory.
1735+
* Use xfer (saved before MqttWriteStop zeroes client->write) */
1736+
CLIENT_FORCE_ZERO(client->tx_buf, xfer);
1737+
17221738
if (rc != xfer) {
17231739
MqttClient_CancelMessage(client, (MqttObject*)mc_connect);
17241740
return rc;

src/mqtt_packet.c

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,11 @@ int MqttDecode_Vbi(byte *buf, word32 *value, word32 buf_len)
244244
rc++;
245245
} while ((encodedByte & MQTT_PACKET_LEN_ENCODE_MASK) != 0);
246246

247+
/* [MQTT-1.5.5-1] Reject non-canonical overlong encodings */
248+
if (rc > 1 && encodedByte == 0) {
249+
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA);
250+
}
251+
247252
return (int)rc;
248253
}
249254

@@ -1530,10 +1535,6 @@ int MqttEncode_PublishResp(byte* tx_buf, int tx_buf_len, byte type,
15301535
#ifdef WOLFMQTT_V5
15311536
if (publish_resp->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5)
15321537
{
1533-
if (publish_resp->reason_code != MQTT_REASON_SUCCESS) {
1534-
/* Reason Code */
1535-
remain_len++;
1536-
}
15371538
if (publish_resp->props != NULL) {
15381539
/* Determine length of properties */
15391540
props_len = MqttEncode_Props((MqttPacketType)type,
@@ -1550,6 +1551,11 @@ int MqttEncode_PublishResp(byte* tx_buf, int tx_buf_len, byte type,
15501551
else
15511552
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PROPERTY);
15521553
}
1554+
if (publish_resp->reason_code != MQTT_REASON_SUCCESS ||
1555+
publish_resp->props != NULL) {
1556+
/* Reason Code */
1557+
remain_len++;
1558+
}
15531559
}
15541560
#endif
15551561

@@ -1574,7 +1580,8 @@ int MqttEncode_PublishResp(byte* tx_buf, int tx_buf_len, byte type,
15741580
#ifdef WOLFMQTT_V5
15751581
if (publish_resp->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5)
15761582
{
1577-
if (publish_resp->reason_code != MQTT_REASON_SUCCESS) {
1583+
if (publish_resp->reason_code != MQTT_REASON_SUCCESS ||
1584+
publish_resp->props != NULL) {
15781585
/* Encode the Reason Code */
15791586
*tx_payload++ = publish_resp->reason_code;
15801587
}
@@ -1620,6 +1627,12 @@ int MqttDecode_PublishResp(byte* rx_buf, int rx_buf_len, byte type,
16201627
if (header_len < 0) {
16211628
return header_len;
16221629
}
1630+
1631+
/* Validate remain_len (need at least packet_id) */
1632+
if (remain_len < MQTT_DATA_LEN_SIZE) {
1633+
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA);
1634+
}
1635+
16231636
rx_payload = &rx_buf[header_len];
16241637

16251638
/* Decode variable header */
@@ -1698,6 +1711,11 @@ int MqttEncode_Subscribe(byte *tx_buf, int tx_buf_len,
16981711
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG);
16991712
}
17001713

1714+
/* [MQTT-2.3.1-1] SUBSCRIBE packets require a non-zero packet identifier */
1715+
if (subscribe->packet_id == 0) {
1716+
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_ID);
1717+
}
1718+
17011719
/* Determine packet length */
17021720
remain_len = MQTT_DATA_LEN_SIZE; /* For packet_id */
17031721
for (i = 0; i < subscribe->topic_count; i++) {
@@ -1898,6 +1916,12 @@ int MqttDecode_SubscribeAck(byte* rx_buf, int rx_buf_len,
18981916
if (header_len < 0) {
18991917
return header_len;
19001918
}
1919+
1920+
/* Validate remain_len (need at least packet_id) */
1921+
if (remain_len < MQTT_DATA_LEN_SIZE) {
1922+
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA);
1923+
}
1924+
19011925
rx_payload = &rx_buf[header_len];
19021926

19031927
/* Decode variable header */
@@ -1982,6 +2006,11 @@ int MqttEncode_Unsubscribe(byte *tx_buf, int tx_buf_len,
19822006
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG);
19832007
}
19842008

2009+
/* [MQTT-2.3.1-1] UNSUBSCRIBE packets require a non-zero packet identifier */
2010+
if (unsubscribe->packet_id == 0) {
2011+
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PACKET_ID);
2012+
}
2013+
19852014
/* Determine packet length */
19862015
remain_len = MQTT_DATA_LEN_SIZE; /* For packet_id */
19872016
for (i = 0; i < unsubscribe->topic_count; i++) {
@@ -2170,6 +2199,12 @@ int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len,
21702199
if (header_len < 0) {
21712200
return header_len;
21722201
}
2202+
2203+
/* Validate remain_len (need at least packet_id) */
2204+
if (remain_len < MQTT_DATA_LEN_SIZE) {
2205+
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA);
2206+
}
2207+
21732208
rx_payload = &rx_buf[header_len];
21742209

21752210
/* Decode variable header */

tests/include.am

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
# included from Top Level Makefile.am
33
# All paths should be given relative to the root
44

5+
# Unit tests for packet encode/decode
6+
check_PROGRAMS += tests/unit_test
7+
tests_unit_test_SOURCES = tests/unit_test.c
8+
tests_unit_test_LDADD = src/libwolfmqtt.la
9+
tests_unit_test_DEPENDENCIES = src/libwolfmqtt.la
10+
tests_unit_test_CPPFLAGS = $(AM_CPPFLAGS)
11+
512
if BUILD_FUZZ
613
if BUILD_BROKER
714
noinst_PROGRAMS += tests/fuzz/broker_fuzz

0 commit comments

Comments
 (0)