/src/gpsd/fuzzer/FuzzDriversStructured.c
Line | Count | Source |
1 | | /* Copyright 2026 Ada Logics Ltd. |
2 | | Licensed under the Apache License, Version 2.0 (the "License"); |
3 | | you may not use this file except in compliance with the License. |
4 | | You may obtain a copy of the License at |
5 | | http://www.apache.org/licenses/LICENSE-2.0 |
6 | | Unless required by applicable law or agreed to in writing, software |
7 | | distributed under the License is distributed on an "AS IS" BASIS, |
8 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
9 | | See the License for the specific language governing permissions and |
10 | | limitations under the License. |
11 | | */ |
12 | | |
13 | | /* |
14 | | * FuzzDriversStructured - Structured fuzzing harness for gpsd |
15 | | * |
16 | | * This harness performs protocol-aware fuzzing by constructing valid |
17 | | * protocol packets with correct checksums from fuzzer input data. |
18 | | * This enables deeper coverage of protocol-specific parsing code. |
19 | | */ |
20 | | |
21 | | #include "gpsd_config.h" |
22 | | |
23 | | #include <errno.h> |
24 | | #include <fcntl.h> |
25 | | #include <stdint.h> |
26 | | #include <stdio.h> |
27 | | #include <stdlib.h> |
28 | | #include <string.h> |
29 | | #include <sys/socket.h> |
30 | | #include <sys/types.h> |
31 | | #include <unistd.h> |
32 | | |
33 | | #include "gpsd.h" |
34 | | |
35 | 42.7k | #define kMinInputLength 4 |
36 | 6.83M | #define kMaxInputLength 8192 |
37 | | |
38 | | // Safety cap for satellites_visible - matches MAXCHANNELS in gps.h |
39 | | #define MAX_SATS 184 |
40 | | |
41 | | // Protocol type selectors |
42 | 569 | #define PROTO_SIRF 0 |
43 | 3.38k | #define PROTO_UBX 1 |
44 | 144 | #define PROTO_ZODIAC 2 |
45 | 213 | #define PROTO_GEOSTAR 3 |
46 | 4.40k | #define PROTO_NAVCOM 4 |
47 | 3.06k | #define PROTO_NMEA 5 |
48 | 950 | #define PROTO_RTCM3 6 |
49 | 6.48k | #define PROTO_TSIP 7 |
50 | 1.67k | #define PROTO_GREIS 8 |
51 | 463 | #define PROTO_SKYTRAQ 9 |
52 | 21.3k | #define PROTO_COUNT 10 |
53 | | |
54 | | // Global session for reuse across fuzzer iterations |
55 | | static struct gps_device_t session; |
56 | | static struct gps_context_t context; |
57 | | static int pipe_fds[2] = {-1, -1}; |
58 | | |
59 | | // Packet buffer for constructing protocol messages |
60 | | static unsigned char packet_buf[kMaxInputLength + 256]; |
61 | | |
62 | | static void null_errout(const char *s) |
63 | 6.46k | { |
64 | 6.46k | (void)s; |
65 | 6.46k | } |
66 | | |
67 | | // SiRF checksum: 15-bit sum of payload bytes |
68 | | static uint16_t sirf_checksum(const uint8_t *payload, size_t len) |
69 | 569 | { |
70 | 569 | uint32_t sum = 0; |
71 | 43.5k | for (size_t i = 0; i < len; i++) { |
72 | 42.9k | sum += payload[i]; |
73 | 42.9k | } |
74 | 569 | return (uint16_t)(sum & 0x7fff); |
75 | 569 | } |
76 | | |
77 | | // UBX Fletcher-8 checksum |
78 | | static void ubx_checksum(const uint8_t *data, size_t len, uint8_t *ck_a, uint8_t *ck_b) |
79 | 3.38k | { |
80 | 3.38k | *ck_a = 0; |
81 | 3.38k | *ck_b = 0; |
82 | 575k | for (size_t i = 0; i < len; i++) { |
83 | 572k | *ck_a += data[i]; |
84 | 572k | *ck_b += *ck_a; |
85 | 572k | } |
86 | 3.38k | } |
87 | | |
88 | | // Zodiac checksum: negated sum of 16-bit words |
89 | | static uint16_t zodiac_checksum(const uint16_t *words, size_t count) |
90 | 288 | { |
91 | 288 | uint16_t sum = 0; |
92 | 3.25k | for (size_t i = 0; i < count; i++) { |
93 | 2.96k | sum += words[i]; |
94 | 2.96k | } |
95 | 288 | return (uint16_t)(-sum); |
96 | 288 | } |
97 | | |
98 | | // GeoStar checksum: XOR of 32-bit words |
99 | | static uint32_t geostar_checksum(const uint8_t *data, size_t len) |
100 | 213 | { |
101 | 213 | uint32_t cs = 0; |
102 | 2.17k | for (size_t i = 0; i + 3 < len; i += 4) { |
103 | 1.96k | uint32_t word = (uint32_t)data[i] | |
104 | 1.96k | ((uint32_t)data[i+1] << 8) | |
105 | 1.96k | ((uint32_t)data[i+2] << 16) | |
106 | 1.96k | ((uint32_t)data[i+3] << 24); |
107 | 1.96k | cs ^= word; |
108 | 1.96k | } |
109 | 213 | return cs; |
110 | 213 | } |
111 | | |
112 | | // Navcom checksum: XOR of bytes |
113 | | static uint8_t navcom_checksum(const uint8_t *data, size_t len) |
114 | 4.40k | { |
115 | 4.40k | uint8_t cs = 0; |
116 | 1.35M | for (size_t i = 0; i < len; i++) { |
117 | 1.34M | cs ^= data[i]; |
118 | 1.34M | } |
119 | 4.40k | return cs; |
120 | 4.40k | } |
121 | | |
122 | | // NMEA checksum: XOR of bytes between $ and * |
123 | | static uint8_t nmea_checksum(const uint8_t *data, size_t len) |
124 | 3.06k | { |
125 | 3.06k | uint8_t cs = 0; |
126 | 213k | for (size_t i = 0; i < len; i++) { |
127 | 210k | cs ^= data[i]; |
128 | 210k | } |
129 | 3.06k | return cs; |
130 | 3.06k | } |
131 | | |
132 | | // Skytraq checksum: XOR of payload bytes |
133 | | static uint8_t skytraq_checksum(const uint8_t *data, size_t len) |
134 | 463 | { |
135 | 463 | uint8_t cs = 0; |
136 | 53.4k | for (size_t i = 0; i < len; i++) { |
137 | 53.0k | cs ^= data[i]; |
138 | 53.0k | } |
139 | 463 | return cs; |
140 | 463 | } |
141 | | |
142 | | // Build a SiRF packet: 0xA0 0xA2 <len:2> <payload> <csum:2> 0xB0 0xB3 |
143 | | static size_t build_sirf_packet(const uint8_t *data, size_t len, uint8_t *out) |
144 | 569 | { |
145 | 569 | if (len > 1023) len = 1023; // SiRF max payload |
146 | | |
147 | 569 | out[0] = 0xA0; |
148 | 569 | out[1] = 0xA2; |
149 | 569 | out[2] = (uint8_t)((len >> 8) & 0x07); // Length high (3 bits) |
150 | 569 | out[3] = (uint8_t)(len & 0xFF); // Length low |
151 | 569 | memcpy(out + 4, data, len); |
152 | | |
153 | 569 | uint16_t cs = sirf_checksum(data, len); |
154 | 569 | out[4 + len] = (uint8_t)(cs >> 8); |
155 | 569 | out[5 + len] = (uint8_t)(cs & 0xFF); |
156 | 569 | out[6 + len] = 0xB0; |
157 | 569 | out[7 + len] = 0xB3; |
158 | | |
159 | 569 | return 8 + len; |
160 | 569 | } |
161 | | |
162 | | // Build a UBX packet: 0xB5 0x62 <class> <id> <len:2> <payload> <ck_a> <ck_b> |
163 | | static size_t build_ubx_packet(const uint8_t *data, size_t len, uint8_t *out) |
164 | 3.38k | { |
165 | 3.38k | if (len < 2) return 0; |
166 | 3.38k | if (len > 8192) len = 8192; |
167 | | |
168 | 3.38k | uint8_t msg_class = data[0]; |
169 | 3.38k | uint8_t msg_id = data[1]; |
170 | 3.38k | size_t payload_len = len - 2; |
171 | | |
172 | 3.38k | out[0] = 0xB5; |
173 | 3.38k | out[1] = 0x62; |
174 | 3.38k | out[2] = msg_class; |
175 | 3.38k | out[3] = msg_id; |
176 | 3.38k | out[4] = (uint8_t)(payload_len & 0xFF); |
177 | 3.38k | out[5] = (uint8_t)((payload_len >> 8) & 0xFF); |
178 | | |
179 | 3.38k | if (payload_len > 0) { |
180 | 3.36k | memcpy(out + 6, data + 2, payload_len); |
181 | 3.36k | } |
182 | | |
183 | 3.38k | uint8_t ck_a, ck_b; |
184 | 3.38k | ubx_checksum(out + 2, 4 + payload_len, &ck_a, &ck_b); |
185 | 3.38k | out[6 + payload_len] = ck_a; |
186 | 3.38k | out[7 + payload_len] = ck_b; |
187 | | |
188 | 3.38k | return 8 + payload_len; |
189 | 3.38k | } |
190 | | |
191 | | // Build a Zodiac packet: 0xFF 0x81 <id:2> <ndata:2> <flags:2> <hsum:2> <data> <dsum:2> |
192 | | static size_t build_zodiac_packet(const uint8_t *data, size_t len, uint8_t *out) |
193 | 144 | { |
194 | 144 | if (len < 2) return 0; |
195 | | |
196 | 144 | uint16_t msg_id = data[0] | ((uint16_t)data[1] << 8); |
197 | 144 | size_t ndata = (len - 2) / 2; // Number of 16-bit words |
198 | 144 | if (ndata > 100) ndata = 100; |
199 | | |
200 | 144 | out[0] = 0xFF; |
201 | 144 | out[1] = 0x81; |
202 | 144 | out[2] = (uint8_t)(msg_id & 0xFF); |
203 | 144 | out[3] = (uint8_t)((msg_id >> 8) & 0xFF); |
204 | 144 | out[4] = (uint8_t)(ndata & 0xFF); |
205 | 144 | out[5] = (uint8_t)((ndata >> 8) & 0xFF); |
206 | 144 | out[6] = 0x00; // flags |
207 | 144 | out[7] = 0x00; |
208 | | |
209 | | // Header checksum (words 0-3) |
210 | 144 | uint16_t hsum = zodiac_checksum((uint16_t*)out, 4); |
211 | 144 | out[8] = (uint8_t)(hsum & 0xFF); |
212 | 144 | out[9] = (uint8_t)((hsum >> 8) & 0xFF); |
213 | | |
214 | | // Copy data words |
215 | 144 | size_t data_bytes = ndata * 2; |
216 | 144 | if (len >= 2 + data_bytes) { |
217 | 144 | memcpy(out + 10, data + 2, data_bytes); |
218 | 144 | } else { |
219 | 0 | memset(out + 10, 0, data_bytes); |
220 | 0 | if (len > 2) { |
221 | 0 | memcpy(out + 10, data + 2, len - 2); |
222 | 0 | } |
223 | 0 | } |
224 | | |
225 | | // Data checksum |
226 | 144 | uint16_t dsum = zodiac_checksum((uint16_t*)(out + 10), ndata); |
227 | 144 | out[10 + data_bytes] = (uint8_t)(dsum & 0xFF); |
228 | 144 | out[11 + data_bytes] = (uint8_t)((dsum >> 8) & 0xFF); |
229 | | |
230 | 144 | return 12 + data_bytes; |
231 | 144 | } |
232 | | |
233 | | // Build a GeoStar packet: 'P' 'S' 'G' 'G' <id:2> <len:2> <data> <checksum:4> |
234 | | static size_t build_geostar_packet(const uint8_t *data, size_t len, uint8_t *out) |
235 | 213 | { |
236 | 213 | if (len < 2) return 0; |
237 | | |
238 | 213 | uint16_t msg_id = data[0] | ((uint16_t)data[1] << 8); |
239 | 213 | size_t nwords = (len - 2) / 4; // Number of 32-bit words |
240 | 213 | if (nwords > 100) nwords = 100; |
241 | | |
242 | 213 | out[0] = 'P'; |
243 | 213 | out[1] = 'S'; |
244 | 213 | out[2] = 'G'; |
245 | 213 | out[3] = 'G'; |
246 | 213 | out[4] = (uint8_t)(msg_id & 0xFF); |
247 | 213 | out[5] = (uint8_t)((msg_id >> 8) & 0xFF); |
248 | 213 | out[6] = (uint8_t)(nwords & 0xFF); |
249 | 213 | out[7] = (uint8_t)((nwords >> 8) & 0xFF); |
250 | | |
251 | 213 | size_t data_bytes = nwords * 4; |
252 | 213 | if (len >= 2 + data_bytes) { |
253 | 213 | memcpy(out + 8, data + 2, data_bytes); |
254 | 213 | } else { |
255 | 0 | memset(out + 8, 0, data_bytes); |
256 | 0 | if (len > 2) { |
257 | 0 | memcpy(out + 8, data + 2, len - 2); |
258 | 0 | } |
259 | 0 | } |
260 | | |
261 | | // Checksum covers header + data |
262 | 213 | size_t cs_len = 8 + data_bytes; |
263 | 213 | uint32_t cs = geostar_checksum(out, cs_len); |
264 | 213 | out[cs_len] = (uint8_t)(cs & 0xFF); |
265 | 213 | out[cs_len + 1] = (uint8_t)((cs >> 8) & 0xFF); |
266 | 213 | out[cs_len + 2] = (uint8_t)((cs >> 16) & 0xFF); |
267 | 213 | out[cs_len + 3] = (uint8_t)((cs >> 24) & 0xFF); |
268 | | |
269 | 213 | return cs_len + 4; |
270 | 213 | } |
271 | | |
272 | | // Build a Navcom packet: 0x02 0x99 0x66 <id> <len:2> <data> <checksum> 0x03 |
273 | | static size_t build_navcom_packet(const uint8_t *data, size_t len, uint8_t *out) |
274 | 4.40k | { |
275 | 4.40k | if (len < 1) return 0; |
276 | 4.40k | if (len > 1000) len = 1000; |
277 | | |
278 | 4.40k | uint8_t msg_id = data[0]; |
279 | 4.40k | size_t payload_len = len - 1; |
280 | | |
281 | 4.40k | out[0] = 0x02; |
282 | 4.40k | out[1] = 0x99; |
283 | 4.40k | out[2] = 0x66; |
284 | 4.40k | out[3] = msg_id; |
285 | 4.40k | out[4] = (uint8_t)((payload_len >> 8) & 0xFF); |
286 | 4.40k | out[5] = (uint8_t)(payload_len & 0xFF); |
287 | | |
288 | 4.40k | if (payload_len > 0) { |
289 | 4.40k | memcpy(out + 6, data + 1, payload_len); |
290 | 4.40k | } |
291 | | |
292 | | // Checksum covers ID and payload |
293 | 4.40k | uint8_t cs = navcom_checksum(out + 3, 3 + payload_len); |
294 | 4.40k | out[6 + payload_len] = cs; |
295 | 4.40k | out[7 + payload_len] = 0x03; // ETX |
296 | | |
297 | 4.40k | return 8 + payload_len; |
298 | 4.40k | } |
299 | | |
300 | | // Build an NMEA sentence with checksum |
301 | | static size_t build_nmea_packet(const uint8_t *data, size_t len, uint8_t *out) |
302 | 3.06k | { |
303 | | // List of NMEA sentence types to use |
304 | 3.06k | static const char *nmea_types[] = { |
305 | 3.06k | "GPGGA", "GPRMC", "GPGLL", "GPGSA", "GPGSV", "GPVTG", "GPZDA", |
306 | 3.06k | "GPGBS", "GPGST", "GPGRS", "GNGNS", "GPDTM", "GPTXT", |
307 | 3.06k | "GLGSA", "GLGSV", "GAGSA", "GAGSV", "GBGSA", "GBGSV", |
308 | 3.06k | "HCHDG", "SDDBT", "SDDPT", "WIMWV", "GPROT", "GPTHS" |
309 | 3.06k | }; |
310 | 3.06k | static const size_t num_types = sizeof(nmea_types) / sizeof(nmea_types[0]); |
311 | | |
312 | 3.06k | if (len < 1) return 0; |
313 | | |
314 | | // Use first byte to select sentence type |
315 | 3.06k | size_t type_idx = data[0] % num_types; |
316 | 3.06k | const char *sentence_type = nmea_types[type_idx]; |
317 | | |
318 | | // Build the sentence body from remaining data |
319 | 3.06k | out[0] = '$'; |
320 | 3.06k | size_t pos = 1; |
321 | | |
322 | | // Copy sentence type |
323 | 3.06k | size_t type_len = strlen(sentence_type); |
324 | 3.06k | memcpy(out + pos, sentence_type, type_len); |
325 | 3.06k | pos += type_len; |
326 | | |
327 | | // Add comma-separated fields from fuzzer data |
328 | 198k | for (size_t i = 1; i < len && pos < kMaxInputLength - 10; i++) { |
329 | 195k | if (data[i] == '\r' || data[i] == '\n' || data[i] == '*') { |
330 | 4.30k | out[pos++] = ','; // Replace invalid chars with comma |
331 | 191k | } else if (data[i] >= 32 && data[i] < 127) { |
332 | 122k | out[pos++] = data[i]; |
333 | 122k | } else { |
334 | 68.9k | out[pos++] = ','; |
335 | 68.9k | } |
336 | 195k | } |
337 | | |
338 | | // Compute checksum |
339 | 3.06k | uint8_t cs = nmea_checksum(out + 1, pos - 1); |
340 | | |
341 | | // Add checksum and line ending |
342 | 3.06k | out[pos++] = '*'; |
343 | 3.06k | static const char hex[] = "0123456789ABCDEF"; |
344 | 3.06k | out[pos++] = hex[(cs >> 4) & 0x0F]; |
345 | 3.06k | out[pos++] = hex[cs & 0x0F]; |
346 | 3.06k | out[pos++] = '\r'; |
347 | 3.06k | out[pos++] = '\n'; |
348 | | |
349 | 3.06k | return pos; |
350 | 3.06k | } |
351 | | |
352 | | // Build an RTCM3 packet: 0xD3 <len:2> <data> <crc24:3> |
353 | | // CRC-24Q is used for RTCM3 |
354 | | static uint32_t crc24q_hash(const uint8_t *data, size_t len) |
355 | 950 | { |
356 | 950 | static const uint32_t crc24q_table[256] = { |
357 | 950 | 0x000000, 0x864CFB, 0x8AD50D, 0x0C99F6, 0x93E6E1, 0x15AA1A, 0x1933EC, 0x9F7F17, |
358 | 950 | 0xA18139, 0x27CDC2, 0x2B5434, 0xAD18CF, 0x3267D8, 0xB42B23, 0xB8B2D5, 0x3EFE2E, |
359 | 950 | 0xC54E89, 0x430272, 0x4F9B84, 0xC9D77F, 0x56A868, 0xD0E493, 0xDC7D65, 0x5A319E, |
360 | 950 | 0x64CFB0, 0xE2834B, 0xEE1ABD, 0x685646, 0xF72951, 0x7165AA, 0x7DFC5C, 0xFBB0A7, |
361 | 950 | 0x0CD1E9, 0x8A9D12, 0x8604E4, 0x00481F, 0x9F3708, 0x197BF3, 0x15E205, 0x93AEFE, |
362 | 950 | 0xAD50D0, 0x2B1C2B, 0x2785DD, 0xA1C926, 0x3EB631, 0xB8FACA, 0xB4633C, 0x322FC7, |
363 | 950 | 0xC99F60, 0x4FD39B, 0x434A6D, 0xC50696, 0x5A7981, 0xDC357A, 0xD0AC8C, 0x56E077, |
364 | 950 | 0x681E59, 0xEE52A2, 0xE2CB54, 0x6487AF, 0xFBF8B8, 0x7DB443, 0x712DB5, 0xF7614E, |
365 | 950 | 0x19A3D2, 0x9FEF29, 0x9376DF, 0x153A24, 0x8A4533, 0x0C09C8, 0x00903E, 0x86DCC5, |
366 | 950 | 0xB822EB, 0x3E6E10, 0x32F7E6, 0xB4BB1D, 0x2BC40A, 0xAD88F1, 0xA11107, 0x275DFC, |
367 | 950 | 0xDCED5B, 0x5AA1A0, 0x563856, 0xD074AD, 0x4F0BBA, 0xC94741, 0xC5DEB7, 0x43924C, |
368 | 950 | 0x7D6C62, 0xFB2099, 0xF7B96F, 0x71F594, 0xEE8A83, 0x68C678, 0x645F8E, 0xE21375, |
369 | 950 | 0x15723B, 0x933EC0, 0x9FA736, 0x19EBCD, 0x8694DA, 0x00D821, 0x0C41D7, 0x8A0D2C, |
370 | 950 | 0xB4F302, 0x32BFF9, 0x3E260F, 0xB86AF4, 0x2715E3, 0xA15918, 0xADC0EE, 0x2B8C15, |
371 | 950 | 0xD03CB2, 0x567049, 0x5AE9BF, 0xDCA544, 0x43DA53, 0xC596A8, 0xC90F5E, 0x4F43A5, |
372 | 950 | 0x71BD8B, 0xF7F170, 0xFB6886, 0x7D247D, 0xE25B6A, 0x641791, 0x688E67, 0xEEC29C, |
373 | 950 | 0x3347A4, 0xB50B5F, 0xB992A9, 0x3FDE52, 0xA0A145, 0x26EDBE, 0x2A7448, 0xAC38B3, |
374 | 950 | 0x92C69D, 0x148A66, 0x181390, 0x9E5F6B, 0x01207C, 0x876C87, 0x8BF571, 0x0DB98A, |
375 | 950 | 0xF6092D, 0x7045D6, 0x7CDC20, 0xFA90DB, 0x65EFCC, 0xE3A337, 0xEF3AC1, 0x69763A, |
376 | 950 | 0x578814, 0xD1C4EF, 0xDD5D19, 0x5B11E2, 0xC46EF5, 0x42220E, 0x4EBBF8, 0xC8F703, |
377 | 950 | 0x3F964D, 0xB9DAB6, 0xB54340, 0x330FBB, 0xAC70AC, 0x2A3C57, 0x26A5A1, 0xA0E95A, |
378 | 950 | 0x9E1774, 0x185B8F, 0x14C279, 0x928E82, 0x0DF195, 0x8BBD6E, 0x872498, 0x016863, |
379 | 950 | 0xFAD8C4, 0x7C943F, 0x700DC9, 0xF64132, 0x693E25, 0xEF72DE, 0xE3EB28, 0x65A7D3, |
380 | 950 | 0x5B59FD, 0xDD1506, 0xD18CF0, 0x57C00B, 0xC8BF1C, 0x4EF3E7, 0x426A11, 0xC426EA, |
381 | 950 | 0x2AE476, 0xACA88D, 0xA0317B, 0x267D80, 0xB90297, 0x3F4E6C, 0x33D79A, 0xB59B61, |
382 | 950 | 0x8B654F, 0x0D29B4, 0x01B042, 0x87FCB9, 0x1883AE, 0x9ECF55, 0x9256A3, 0x141A58, |
383 | 950 | 0xEFAAFF, 0x69E604, 0x657FF2, 0xE33309, 0x7C4C1E, 0xFA00E5, 0xF69913, 0x70D5E8, |
384 | 950 | 0x4E2BC6, 0xC8673D, 0xC4FECB, 0x42B230, 0xDDCD27, 0x5B81DC, 0x57182A, 0xD154D1, |
385 | 950 | 0x26359F, 0xA07964, 0xACE092, 0x2AAC69, 0xB5D37E, 0x339F85, 0x3F0673, 0xB94A88, |
386 | 950 | 0x87B4A6, 0x01F85D, 0x0D61AB, 0x8B2D50, 0x145247, 0x921EBC, 0x9E874A, 0x18CBB1, |
387 | 950 | 0xE37B16, 0x6537ED, 0x69AE1B, 0xEFE2E0, 0x709DF7, 0xF6D10C, 0xFA48FA, 0x7C0401, |
388 | 950 | 0x42FA2F, 0xC4B6D4, 0xC82F22, 0x4E63D9, 0xD11CCE, 0x575035, 0x5BC9C3, 0xDD8538 |
389 | 950 | }; |
390 | | |
391 | 950 | uint32_t crc = 0; |
392 | 61.7k | for (size_t i = 0; i < len; i++) { |
393 | 60.7k | crc = ((crc << 8) & 0xFFFFFF) ^ crc24q_table[(crc >> 16) ^ data[i]]; |
394 | 60.7k | } |
395 | 950 | return crc; |
396 | 950 | } |
397 | | |
398 | | static size_t build_rtcm3_packet(const uint8_t *data, size_t len, uint8_t *out) |
399 | 950 | { |
400 | 950 | if (len > 1023) len = 1023; // RTCM3 max payload |
401 | | |
402 | 950 | out[0] = 0xD3; |
403 | 950 | out[1] = (uint8_t)((len >> 8) & 0x03); // High 2 bits of length |
404 | 950 | out[2] = (uint8_t)(len & 0xFF); // Low 8 bits |
405 | | |
406 | 950 | memcpy(out + 3, data, len); |
407 | | |
408 | | // CRC-24Q covers preamble + length + data |
409 | 950 | uint32_t crc = crc24q_hash(out, 3 + len); |
410 | 950 | out[3 + len] = (uint8_t)((crc >> 16) & 0xFF); |
411 | 950 | out[4 + len] = (uint8_t)((crc >> 8) & 0xFF); |
412 | 950 | out[5 + len] = (uint8_t)(crc & 0xFF); |
413 | | |
414 | 950 | return 6 + len; |
415 | 950 | } |
416 | | |
417 | | // Build a TSIP packet: DLE <id> <data with DLE stuffing> DLE ETX |
418 | | static size_t build_tsip_packet(const uint8_t *data, size_t len, uint8_t *out) |
419 | 6.48k | { |
420 | 6.48k | if (len < 1) return 0; |
421 | | |
422 | 6.48k | uint8_t msg_id = data[0]; |
423 | 6.48k | size_t pos = 0; |
424 | | |
425 | 6.48k | out[pos++] = 0x10; // DLE |
426 | 6.48k | out[pos++] = msg_id; |
427 | | |
428 | | // Copy payload with DLE stuffing |
429 | 6.62M | for (size_t i = 1; i < len && pos < kMaxInputLength - 4; i++) { |
430 | 6.61M | if (data[i] == 0x10) { |
431 | 65.0k | out[pos++] = 0x10; // Stuff DLE |
432 | 65.0k | } |
433 | 6.61M | out[pos++] = data[i]; |
434 | 6.61M | } |
435 | | |
436 | 6.48k | out[pos++] = 0x10; // DLE |
437 | 6.48k | out[pos++] = 0x03; // ETX |
438 | | |
439 | 6.48k | return pos; |
440 | 6.48k | } |
441 | | |
442 | | // Build a GREIS packet: <id:2> <data> <checksum> CR LF |
443 | | static size_t build_greis_packet(const uint8_t *data, size_t len, uint8_t *out) |
444 | 1.67k | { |
445 | 1.67k | if (len < 2) return 0; |
446 | 1.67k | if (len > 200) len = 200; |
447 | | |
448 | | // Standard GREIS message IDs |
449 | 1.67k | static const char *greis_ids[] = {"RE", "ER", "PM", "RC", "RD", "SI", "EL"}; |
450 | 1.67k | static const size_t num_ids = sizeof(greis_ids) / sizeof(greis_ids[0]); |
451 | | |
452 | 1.67k | size_t id_idx = data[0] % num_ids; |
453 | 1.67k | out[0] = greis_ids[id_idx][0]; |
454 | 1.67k | out[1] = greis_ids[id_idx][1]; |
455 | | |
456 | 1.67k | size_t payload_len = len - 2; |
457 | 1.67k | if (payload_len > 0) { |
458 | 1.66k | memcpy(out + 2, data + 2, payload_len); |
459 | 1.66k | } |
460 | | |
461 | | // Simple XOR checksum for GREIS |
462 | 1.67k | uint8_t cs = 0; |
463 | 64.3k | for (size_t i = 0; i < 2 + payload_len; i++) { |
464 | 62.6k | cs ^= out[i]; |
465 | 62.6k | } |
466 | 1.67k | out[2 + payload_len] = cs; |
467 | 1.67k | out[3 + payload_len] = '\r'; |
468 | 1.67k | out[4 + payload_len] = '\n'; |
469 | | |
470 | 1.67k | return 5 + payload_len; |
471 | 1.67k | } |
472 | | |
473 | | // Build a Skytraq packet: 0xA0 0xA1 <len:2> <payload> <checksum> 0x0D 0x0A |
474 | | static size_t build_skytraq_packet(const uint8_t *data, size_t len, uint8_t *out) |
475 | 463 | { |
476 | 463 | if (len > 1000) len = 1000; |
477 | | |
478 | 463 | out[0] = 0xA0; |
479 | 463 | out[1] = 0xA1; |
480 | 463 | out[2] = (uint8_t)((len >> 8) & 0xFF); |
481 | 463 | out[3] = (uint8_t)(len & 0xFF); |
482 | | |
483 | 463 | memcpy(out + 4, data, len); |
484 | | |
485 | 463 | uint8_t cs = skytraq_checksum(data, len); |
486 | 463 | out[4 + len] = cs; |
487 | 463 | out[5 + len] = 0x0D; |
488 | 463 | out[6 + len] = 0x0A; |
489 | | |
490 | 463 | return 7 + len; |
491 | 463 | } |
492 | | |
493 | | int LLVMFuzzerInitialize(int *argc, char ***argv) |
494 | 2 | { |
495 | 2 | if (pipe(pipe_fds) < 0) { |
496 | 0 | return -1; |
497 | 0 | } |
498 | | |
499 | 2 | int flags = fcntl(pipe_fds[0], F_GETFL, 0); |
500 | 2 | if (flags < 0 || fcntl(pipe_fds[0], F_SETFL, flags | O_NONBLOCK) < 0) { |
501 | 0 | close(pipe_fds[0]); |
502 | 0 | close(pipe_fds[1]); |
503 | 0 | return -1; |
504 | 0 | } |
505 | | |
506 | | // Also make write end non-blocking |
507 | 2 | flags = fcntl(pipe_fds[1], F_GETFL, 0); |
508 | 2 | if (flags >= 0) { |
509 | 2 | fcntl(pipe_fds[1], F_SETFL, flags | O_NONBLOCK); |
510 | 2 | } |
511 | | |
512 | 2 | gps_context_init(&context, "fuzz_structured"); |
513 | 2 | gpsd_init(&session, &context, "/dev/fuzz_structured"); |
514 | | |
515 | 2 | context.errout.debug = 0; |
516 | 2 | context.errout.report = null_errout; |
517 | | |
518 | | // Set fd BEFORE gpsd_clear so pps_thread.devicefd gets correct value |
519 | 2 | session.gpsdata.gps_fd = pipe_fds[0]; |
520 | | |
521 | | // Set sourcetype to PIPE to prevent NTP/PPS code paths from activating |
522 | | // (see ntpshm_link_activate which skips pipes) |
523 | 2 | session.sourcetype = SOURCE_PIPE; |
524 | | |
525 | 2 | return 0; |
526 | 2 | } |
527 | | |
528 | | // Drain any leftover data from the pipe |
529 | | static void drain_pipe(int fd) |
530 | 21.3k | { |
531 | 21.3k | unsigned char buf[4096]; |
532 | 21.3k | while (read(fd, buf, sizeof(buf)) > 0) { |
533 | | // Keep reading until empty |
534 | 0 | } |
535 | 21.3k | } |
536 | | |
537 | | int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) |
538 | 21.3k | { |
539 | 21.3k | if (Size < kMinInputLength || Size > kMaxInputLength) { |
540 | 3 | return 0; |
541 | 3 | } |
542 | | |
543 | | // Drain any leftover data from previous iteration |
544 | 21.3k | drain_pipe(pipe_fds[0]); |
545 | | |
546 | | // Set fd before gpsd_clear so pps_thread gets correct value |
547 | 21.3k | session.gpsdata.gps_fd = pipe_fds[0]; |
548 | | |
549 | 21.3k | gpsd_clear(&session); |
550 | 21.3k | session.device_type = NULL; |
551 | 21.3k | session.last_controller = NULL; |
552 | | |
553 | | // Ensure sourcetype stays set to prevent NTP/PPS activation |
554 | 21.3k | session.sourcetype = SOURCE_PIPE; |
555 | | |
556 | | // Clear satellite data to prevent out-of-bounds access in fill_dop |
557 | | // when satellites_visible gets corrupted by fuzzer-generated data |
558 | 21.3k | session.gpsdata.satellites_visible = 0; |
559 | 21.3k | gpsd_zero_satellites(&session.gpsdata); |
560 | | |
561 | | // First byte selects protocol type |
562 | 21.3k | uint8_t proto_type = Data[0] % PROTO_COUNT; |
563 | | |
564 | | // Second byte provides additional control |
565 | 21.3k | context.readonly = (Data[1] & 0x01); |
566 | 21.3k | context.passive = (Data[1] & 0x02); |
567 | | |
568 | | // Build protocol-specific packet from remaining data |
569 | 21.3k | size_t packet_len = 0; |
570 | 21.3k | const uint8_t *payload = Data + 2; |
571 | 21.3k | size_t payload_len = Size - 2; |
572 | | |
573 | 21.3k | switch (proto_type) { |
574 | 569 | case PROTO_SIRF: |
575 | 569 | packet_len = build_sirf_packet(payload, payload_len, packet_buf); |
576 | 569 | break; |
577 | 3.38k | case PROTO_UBX: |
578 | 3.38k | packet_len = build_ubx_packet(payload, payload_len, packet_buf); |
579 | 3.38k | break; |
580 | 144 | case PROTO_ZODIAC: |
581 | 144 | packet_len = build_zodiac_packet(payload, payload_len, packet_buf); |
582 | 144 | break; |
583 | 213 | case PROTO_GEOSTAR: |
584 | 213 | packet_len = build_geostar_packet(payload, payload_len, packet_buf); |
585 | 213 | break; |
586 | 4.40k | case PROTO_NAVCOM: |
587 | 4.40k | packet_len = build_navcom_packet(payload, payload_len, packet_buf); |
588 | 4.40k | break; |
589 | 3.06k | case PROTO_NMEA: |
590 | 3.06k | packet_len = build_nmea_packet(payload, payload_len, packet_buf); |
591 | 3.06k | break; |
592 | 950 | case PROTO_RTCM3: |
593 | 950 | packet_len = build_rtcm3_packet(payload, payload_len, packet_buf); |
594 | 950 | break; |
595 | 6.48k | case PROTO_TSIP: |
596 | 6.48k | packet_len = build_tsip_packet(payload, payload_len, packet_buf); |
597 | 6.48k | break; |
598 | 1.67k | case PROTO_GREIS: |
599 | 1.67k | packet_len = build_greis_packet(payload, payload_len, packet_buf); |
600 | 1.67k | break; |
601 | 463 | case PROTO_SKYTRAQ: |
602 | 463 | packet_len = build_skytraq_packet(payload, payload_len, packet_buf); |
603 | 463 | break; |
604 | 0 | default: |
605 | 0 | return 0; |
606 | 21.3k | } |
607 | | |
608 | 21.3k | if (packet_len == 0) { |
609 | 0 | return 0; |
610 | 0 | } |
611 | | |
612 | | // Write constructed packet to pipe |
613 | 21.3k | ssize_t written = write(pipe_fds[1], packet_buf, packet_len); |
614 | 21.3k | if (written < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { |
615 | 0 | return 0; |
616 | 0 | } |
617 | | |
618 | | // Process packets |
619 | 21.3k | int max_iterations = 100; |
620 | 2.15M | while (max_iterations-- > 0) { |
621 | 2.13M | gps_mask_t changed = gpsd_poll(&session); |
622 | 2.13M | if (changed == 0 || changed == ERROR_SET) { |
623 | 0 | break; |
624 | 0 | } |
625 | 2.13M | } |
626 | | |
627 | 21.3k | return 0; |
628 | 21.3k | } |