Skip to content

Commit 70b076b

Browse files
committed
add more fuzzers
1 parent 0d89a64 commit 70b076b

20 files changed

+1516
-2
lines changed

fuzzing/broker/Makefile

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ FUZZERS:= \
1111
broker_fuzz_psk_file \
1212
broker_fuzz_queue_msg \
1313
broker_fuzz_read_handle \
14-
broker_fuzz_test_config
14+
broker_fuzz_test_config \
15+
broker_fuzz_property_read_all \
16+
broker_fuzz_subscribe \
17+
broker_fuzz_proxy_v1_decode \
18+
broker_fuzz_ws_prepare_packet \
19+
broker_fuzz_bridge_remap_topic_in \
20+
broker_fuzz_will_set \
21+
broker_fuzz_properties_to_json
1522

1623
LOCAL_CPPFLAGS+=-I${R}/include/ -I${R}/src -I${R}/lib -I${R} -I${R}/common -I${R}/deps \
1724
-DWITH_BRIDGE -DWITH_BROKER -DWITH_CONTROL -DWITH_EPOLL \
@@ -81,5 +88,49 @@ broker_fuzz_test_config : broker_fuzz_test_config.cpp ${R}/src/mosquitto_broker.
8188
cp ${R}/fuzzing/corpora/broker_fuzz_test_config_seed_corpus.zip ${OUT}/$@_seed_corpus.zip
8289
cp ${R}/fuzzing/corpora/broker_conf.dict ${OUT}/$@.dict
8390

91+
# Top 10 selected harnesses for upstream submission
92+
93+
# MQTT v5 property parsing - tests complex property validation logic
94+
broker_fuzz_property_read_all : broker_fuzz_property_read_all.cpp ${R}/src/mosquitto_broker.a
95+
$(CXX) $(LOCAL_CXXFLAGS) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $< $(BROKER_A) $(LOCAL_LIBADD)
96+
install $@ ${OUT}/$@
97+
cp ${R}/fuzzing/corpora/$@_seed_corpus.zip ${OUT}/$@_seed_corpus.zip
98+
99+
# MQTT SUBSCRIBE packet handler - tests subscription tree management
100+
broker_fuzz_subscribe : broker_fuzz_subscribe.cpp ${R}/src/mosquitto_broker.a
101+
$(CXX) $(LOCAL_CXXFLAGS) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $< $(BROKER_A) $(LOCAL_LIBADD)
102+
install $@ ${OUT}/$@
103+
cp ${R}/fuzzing/corpora/$@_seed_corpus.zip ${OUT}/$@_seed_corpus.zip
104+
105+
# PROXY protocol v1 decoder - tests network protocol parsing
106+
broker_fuzz_proxy_v1_decode : broker_fuzz_proxy_v1_decode.cpp ${R}/src/mosquitto_broker.a
107+
$(CXX) $(LOCAL_CXXFLAGS) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $< $(BROKER_A) $(LOCAL_LIBADD)
108+
install $@ ${OUT}/$@
109+
cp ${R}/fuzzing/corpora/$@_seed_corpus.zip ${OUT}/$@_seed_corpus.zip
110+
111+
# WebSocket packet preparation - tests WebSocket frame building
112+
broker_fuzz_ws_prepare_packet : broker_fuzz_ws_prepare_packet.cpp ${R}/src/mosquitto_broker.a
113+
$(CXX) $(LOCAL_CXXFLAGS) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $< $(BROKER_A) $(LOCAL_LIBADD)
114+
install $@ ${OUT}/$@
115+
cp ${R}/fuzzing/corpora/$@_seed_corpus.zip ${OUT}/$@_seed_corpus.zip
116+
117+
# Bridge topic remapping - tests bridge configuration and topic rewriting
118+
broker_fuzz_bridge_remap_topic_in : broker_fuzz_bridge_remap_topic_in.cpp ${R}/src/mosquitto_broker.a
119+
$(CXX) $(LOCAL_CXXFLAGS) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $< $(BROKER_A) $(LOCAL_LIBADD)
120+
install $@ ${OUT}/$@
121+
cp ${R}/fuzzing/corpora/$@_seed_corpus.zip ${OUT}/$@_seed_corpus.zip
122+
123+
# MQTT Will message configuration - tests QoS feature and payload validation
124+
broker_fuzz_will_set : broker_fuzz_will_set.cpp ${R}/src/mosquitto_broker.a
125+
$(CXX) $(LOCAL_CXXFLAGS) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $< $(BROKER_A) $(LOCAL_LIBADD)
126+
install $@ ${OUT}/$@
127+
cp ${R}/fuzzing/corpora/$@_seed_corpus.zip ${OUT}/$@_seed_corpus.zip
128+
129+
# Property to JSON conversion - tests property serialization
130+
broker_fuzz_properties_to_json : broker_fuzz_properties_to_json.cpp ${R}/src/mosquitto_broker.a
131+
$(CXX) $(LOCAL_CXXFLAGS) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $< $(BROKER_A) $(LOCAL_LIBADD)
132+
install $@ ${OUT}/$@
133+
cp ${R}/fuzzing/corpora/$@_seed_corpus.zip ${OUT}/$@_seed_corpus.zip
134+
84135
clean:
85136
rm -f *.o $(FUZZERS) $(PACKET_FUZZERS)
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#include <cstddef>
2+
#include <cstdint>
3+
#include <cstdlib>
4+
#include <cstring>
5+
6+
// Include project headers (absolute paths from the workspace).
7+
// Wrap C headers in extern "C" so C++ compilation uses correct C linkage.
8+
extern "C" {
9+
#include "/src/mosquitto/src/mosquitto_broker_internal.h"
10+
#include "/src/mosquitto/lib/mosquitto_internal.h"
11+
#include "/src/mosquitto/include/mosquitto/libcommon_memory.h"
12+
#include "/src/mosquitto/include/mosquitto/libcommon_topic.h"
13+
}
14+
15+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
16+
{
17+
if(!Data || Size == 0) return 0;
18+
19+
// Simple deterministic parser of the input buffer into several NUL-terminated strings
20+
// and an enum value for direction. We consume bytes from Data sequentially.
21+
size_t idx = 0;
22+
auto remaining = [&](void)->size_t { return (idx < Size) ? (Size - idx) : 0; };
23+
24+
auto take_len = [&](size_t maxlen)->size_t {
25+
if(remaining() == 0) return 0;
26+
// Use next byte to determine a requested length (bounded by available data and maxlen).
27+
unsigned char b = Data[idx++];
28+
size_t len = (size_t)(b) % (maxlen + 1);
29+
if(len > remaining()) len = remaining();
30+
return len;
31+
};
32+
33+
auto take_cstr = [&](size_t maxlen)->char* {
34+
size_t len = take_len(maxlen);
35+
// If no data, return an empty allocated string (function expects valid C-string pointers).
36+
if(len == 0){
37+
char *s = (char*)malloc(1);
38+
if(s) s[0] = '\0';
39+
return s;
40+
}
41+
char *s = (char*)malloc(len + 1);
42+
if(!s) return nullptr;
43+
memcpy(s, Data + idx, len);
44+
s[len] = '\0';
45+
idx += len;
46+
return s;
47+
};
48+
49+
// Partition input into: initial topic, remote_topic (subscription), remote_prefix, local_prefix, and a direction byte.
50+
// maxlen chosen to be reasonable to avoid overly large allocations.
51+
const size_t kMaxPiece = 4096;
52+
53+
char *initial_topic = take_cstr(kMaxPiece);
54+
char *remote_topic = take_cstr(kMaxPiece);
55+
char *remote_prefix = take_cstr(kMaxPiece);
56+
char *local_prefix = take_cstr(kMaxPiece);
57+
58+
// Direction byte: if available, use it; otherwise default to bd_in.
59+
enum mosquitto__bridge_direction dir = bd_in;
60+
if(remaining() > 0){
61+
unsigned char b = Data[idx++];
62+
dir = (b % 3 == 0) ? bd_out : ((b % 3 == 1) ? bd_in : bd_both);
63+
}
64+
65+
// Ensure we have at least an empty topic string
66+
if(!initial_topic){
67+
initial_topic = (char*)malloc(1);
68+
if(initial_topic) initial_topic[0] = '\0';
69+
}
70+
if(!remote_topic){
71+
remote_topic = (char*)malloc(1);
72+
if(remote_topic) remote_topic[0] = '\0';
73+
}
74+
// Note: remote_prefix/local_prefix may be NULL (meaning not used). We keep allocated empty strings to
75+
// allow behavior where prefixes exist but are empty.
76+
if(!remote_prefix){
77+
remote_prefix = nullptr; // treat as no prefix
78+
}
79+
if(!local_prefix){
80+
local_prefix = nullptr;
81+
}
82+
83+
// Build minimal mosquitto context and bridge topic list to exercise bridge__remap_topic_in.
84+
struct mosquitto *context = (struct mosquitto*)calloc(1, sizeof(struct mosquitto));
85+
if(!context){
86+
free(initial_topic);
87+
free(remote_topic);
88+
if(remote_prefix) free(remote_prefix);
89+
if(local_prefix) free(local_prefix);
90+
return 0;
91+
}
92+
93+
// Allocate bridge
94+
struct mosquitto__bridge *bridge = (struct mosquitto__bridge*)calloc(1, sizeof(struct mosquitto__bridge));
95+
if(!bridge){
96+
free(initial_topic);
97+
free(remote_topic);
98+
if(remote_prefix) free(remote_prefix);
99+
if(local_prefix) free(local_prefix);
100+
free(context);
101+
return 0;
102+
}
103+
bridge->topics = NULL;
104+
bridge->topic_remapping = true; // enable remapping behavior
105+
context->bridge = bridge;
106+
107+
// Allocate a single bridge topic node and populate fields.
108+
struct mosquitto__bridge_topic *topic_node = (struct mosquitto__bridge_topic*)calloc(1, sizeof(struct mosquitto__bridge_topic));
109+
if(!topic_node){
110+
free(initial_topic);
111+
free(remote_topic);
112+
if(remote_prefix) free(remote_prefix);
113+
if(local_prefix) free(local_prefix);
114+
free(bridge);
115+
free(context);
116+
return 0;
117+
}
118+
119+
// Set direction
120+
topic_node->direction = dir;
121+
122+
// The function expects remote_topic to be set (it calls mosquitto_topic_matches_sub on it).
123+
// Copy remote_topic into the struct (use mosquitto_strdup if available, otherwise fallback to strdup)
124+
#ifdef mosquitto_strdup
125+
topic_node->remote_topic = mosquitto_strdup(remote_topic ? remote_topic : "");
126+
#else
127+
topic_node->remote_topic = remote_topic ? strdup(remote_topic) : strdup("");
128+
#endif
129+
130+
// remote_prefix/local_prefix: set to NULL if empty to emulate the "not set" state
131+
if(remote_prefix && strlen(remote_prefix) > 0){
132+
#ifdef mosquitto_strdup
133+
topic_node->remote_prefix = mosquitto_strdup(remote_prefix);
134+
#else
135+
topic_node->remote_prefix = strdup(remote_prefix);
136+
#endif
137+
}else{
138+
topic_node->remote_prefix = NULL;
139+
}
140+
if(local_prefix && strlen(local_prefix) > 0){
141+
#ifdef mosquitto_strdup
142+
topic_node->local_prefix = mosquitto_strdup(local_prefix);
143+
#else
144+
topic_node->local_prefix = strdup(local_prefix);
145+
#endif
146+
}else{
147+
topic_node->local_prefix = NULL;
148+
}
149+
150+
// Link the single node into bridge->topics
151+
bridge->topics = topic_node;
152+
topic_node->next = NULL;
153+
154+
// Prepare the topic pointer expected by bridge__remap_topic_in.
155+
#ifdef mosquitto_strdup
156+
char *topic_for_call = mosquitto_strdup(initial_topic ? initial_topic : "");
157+
#else
158+
char *topic_for_call = initial_topic ? strdup(initial_topic) : strdup("");
159+
#endif
160+
161+
// Call the target function.
162+
// It may free and replace *topic_for_call; that's expected.
163+
if(topic_for_call){
164+
bridge__remap_topic_in(context, &topic_for_call);
165+
}
166+
167+
// Cleanup: free the potentially modified topic pointer and all allocated structures.
168+
if(topic_for_call){
169+
// Use mosquitto_FREE macro which sets ptr to NULL after freeing (declared in libcommon_memory.h).
170+
#ifdef mosquitto_FREE
171+
mosquitto_FREE(topic_for_call);
172+
#else
173+
free(topic_for_call);
174+
topic_for_call = nullptr;
175+
#endif
176+
}
177+
178+
// Free bridge topic node strings and node
179+
if(topic_node){
180+
if(topic_node->remote_topic){
181+
#ifdef mosquitto_FREE
182+
mosquitto_FREE(topic_node->remote_topic);
183+
#else
184+
free(topic_node->remote_topic);
185+
#endif
186+
}
187+
if(topic_node->remote_prefix){
188+
#ifdef mosquitto_FREE
189+
mosquitto_FREE(topic_node->remote_prefix);
190+
#else
191+
free(topic_node->remote_prefix);
192+
#endif
193+
}
194+
if(topic_node->local_prefix){
195+
#ifdef mosquitto_FREE
196+
mosquitto_FREE(topic_node->local_prefix);
197+
#else
198+
free(topic_node->local_prefix);
199+
#endif
200+
}
201+
free(topic_node);
202+
}
203+
204+
// Free bridge and context
205+
free(bridge);
206+
207+
// Note: some mosquitto internals might have allocated resources; since we only used a minimal context,
208+
// free the top-level struct.
209+
free(context);
210+
211+
// Free local temporary allocations that we created for partitioning (those not moved into topic_node)
212+
// initial_topic and remote_topic were either strdup'd into topic_for_call / topic_node->remote_topic earlier,
213+
// but we may have leftover allocations for remote_prefix/local_prefix when we set them to NULL.
214+
if(remote_topic){
215+
// remote_topic was used to initialize topic_node->remote_topic via strdup/mosquitto_strdup.
216+
free(remote_topic);
217+
}
218+
if(remote_prefix){
219+
free(remote_prefix);
220+
}
221+
if(local_prefix){
222+
free(local_prefix);
223+
}
224+
// initial_topic was strdup'ed to topic_for_call earlier; we freed topic_for_call via mosquitto_FREE or free,
225+
// but if we didn't use mosquitto_strdup for initial_topic then initial_topic still points to malloced buffer.
226+
if(initial_topic){
227+
free(initial_topic);
228+
}
229+
230+
return 0;
231+
}

0 commit comments

Comments
 (0)