blob: 9049ab8b2a7ca0356305761c1aea82c4e89f37d1 [file] [log] [blame]
Matt Johnstonabe9b372024-11-11 17:17:44 +08001/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
2
3/* Fuzzing should always have assertions */
4#ifdef NDEBUG
5#undef NDEBUG
6#endif
7
8#include <libpldm/base.h>
9#include <libpldm/firmware_fd.h>
10#include <libpldm/firmware_update.h>
11#include <libpldm/sizes.h>
12
13#include <cstdarg>
14#include <cstdint>
15#include <cstring>
16#include <memory>
17#include <vector>
18
19#include "array.h"
20#include "msgbuf.h"
21
22/* Avoid out-of-memory, and
23 * avoid wasting time on inputs larger than MCTP message limits */
24static const uint32_t MAX_PART = 200;
25
26/* Maximum "send" buffer. Should be larger than any expected sent message */
27static const uint32_t MAX_SEND = 1024;
28
29/* Arbitrary EID */
30static const uint8_t FIXED_ADDR = 20;
31
32static const uint8_t PROGRESS_PERCENT = 5;
33
34static const ssize_t FUZZCTRL_SIZE = 0x400;
35
36static bool printf_enabled;
37// NOLINTNEXTLINE(cert-dcl50-cpp)
38static void debug_printf(const char* fmt, ...)
39{
40 if (printf_enabled)
41 {
42 va_list ap;
43 va_start(ap, fmt);
44 vprintf(fmt, ap);
45 va_end(ap);
46 }
47}
48
49struct fuzz_ops_ctx
50{
51 struct pldm_msgbuf* fuzz_ctrl;
52
53 /* Details of in-progress update, for consistency checking */
54 bool current_update;
55 struct pldm_firmware_update_component update_comp;
56 uint32_t offset;
57 bool transferred;
58 bool verified;
59 bool applied;
60
61 uint64_t now;
62};
63
64/* Returns true with roughly `percent` chance */
65static bool fuzz_chance(struct fuzz_ops_ctx* ctx, uint8_t percent)
66{
67 uint8_t v;
68 assert(percent <= 100);
69 int rc = pldm_msgbuf_extract_uint8(ctx->fuzz_ctrl, v);
70 if (rc != 0)
71 {
72 return false;
73 }
74 uint8_t cutoff = (uint32_t)percent * UINT8_MAX / 100;
75 return v <= cutoff;
76}
77
78// openbmc 49871
79static const uint8_t openbmc_iana[] = {
80 0xcf,
81 0xc2,
82 0x00,
83 0x00,
84};
85
86/* An arbitrary but valid set of descriptors.
87 * Short to be readily discoverable by fuzzing */
88static const pldm_descriptor FIXED_DESCRIPTORS[] = {
89 {
90 .descriptor_type = PLDM_FWUP_IANA_ENTERPRISE_ID,
91 .descriptor_length = 4,
92 .descriptor_data = openbmc_iana,
93 },
94};
95
96static int cb_device_identifiers(void* ctx LIBPLDM_CC_UNUSED,
97 uint8_t* descriptors_count,
98 const struct pldm_descriptor** descriptors)
99{
100 debug_printf("cb_device_identifiers\n");
101 *descriptors_count = 1;
102 *descriptors = FIXED_DESCRIPTORS;
103 return 0;
104}
105
106static const struct pldm_firmware_component_standalone comp = {
107 .comp_classification = PLDM_COMP_UNKNOWN,
108 .comp_identifier = 0,
109 .comp_classification_index = 0,
110 .active_ver =
111 {
112 .comparison_stamp = 1,
113 .str =
114 {
115 .str_type = PLDM_STR_TYPE_UTF_8,
116 .str_len = 3,
117 .str_data = "zzz",
118 },
119 .date = {0},
120 },
121 .pending_ver =
122 {
123 .comparison_stamp = 1,
124 .str =
125 {
126 .str_type = PLDM_STR_TYPE_UNKNOWN,
127 .str_len = 4,
128 .str_data = "fnnn",
129 },
130 .date = {0},
131 },
132 .comp_activation_methods = {0},
133 .capabilities_during_update = {0},
134};
135
136static const struct pldm_firmware_component_standalone* comp_list[1] = {
137 &comp,
138};
139
140static int cb_components(
141 void* ctx, uint16_t* ret_entry_count,
142 const struct pldm_firmware_component_standalone*** ret_entries)
143{
144 debug_printf("cb_components\n");
145 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
146
147 *ret_entry_count = ARRAY_SIZE(comp_list);
148 *ret_entries = comp_list;
149 if (fuzz_chance(fuzz_ctx, 4))
150 {
151 return -EINVAL;
152 }
153 return 0;
154}
155
156static int cb_imageset_versions(void* ctx, struct pldm_firmware_string* active,
157 struct pldm_firmware_string* pending)
158{
159 debug_printf("cb_imageset_versions\n");
160 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
161
162 active->str_type = PLDM_STR_TYPE_ASCII;
163 active->str_len = 4;
164 memcpy(active->str_data, "1234", 4);
165 pending->str_type = PLDM_STR_TYPE_ASCII;
166 pending->str_len = 4;
167 memcpy(pending->str_data, "1235", 4);
168 if (fuzz_chance(fuzz_ctx, 4))
169 {
170 return -EINVAL;
171 }
172 return 0;
173}
174
175static enum pldm_component_response_codes
176 cb_update_component(void* ctx, bool update,
177 const struct pldm_firmware_update_component* comp)
178{
179 debug_printf("cb_update_component update=%d\n", update);
180 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
181
182 if (fuzz_chance(fuzz_ctx, 4))
183 {
184 return PLDM_CRC_COMP_PREREQUISITES_NOT_MET;
185 }
186 if (update)
187 {
188 /* Set up a new update */
189 assert(!fuzz_ctx->current_update);
190 debug_printf("cb_update_component set current_update=true\n");
191 fuzz_ctx->current_update = true;
192 fuzz_ctx->transferred = false;
193 fuzz_ctx->verified = false;
194 fuzz_ctx->applied = false;
195 fuzz_ctx->offset = 0;
196 memcpy(&fuzz_ctx->update_comp, comp, sizeof(*comp));
197 }
198 return PLDM_CRC_COMP_CAN_BE_UPDATED;
199}
200
201static uint32_t cb_transfer_size(void* ctx, uint32_t ua_max_transfer_size)
202{
203 debug_printf("cb_transfer_size ua_size=%zu\n",
204 (ssize_t)ua_max_transfer_size);
205 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
206
207 if (fuzz_chance(fuzz_ctx, 50))
208 {
209 // Sometimes adjust it
210 return MAX_PART - 20;
211 }
212 return ua_max_transfer_size;
213}
214
215static uint8_t
216 cb_firmware_data(void* ctx, uint32_t offset,
217 const uint8_t* data LIBPLDM_CC_UNUSED, uint32_t len,
218 const struct pldm_firmware_update_component* comp)
219{
220 debug_printf("cb_firmware_data offset=%zu len %zu\n", (size_t)offset,
221 (size_t)len);
222 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
223
224 assert(fuzz_ctx->current_update);
225 assert(!fuzz_ctx->transferred);
226 assert(!fuzz_ctx->verified);
227 assert(!fuzz_ctx->applied);
228 assert(offset == fuzz_ctx->offset);
229 fuzz_ctx->offset += len;
230 assert(fuzz_ctx->offset <= fuzz_ctx->update_comp.comp_image_size);
231 assert(memcmp(comp, &fuzz_ctx->update_comp, sizeof(*comp)) == 0);
232
233 if (fuzz_ctx->offset == fuzz_ctx->update_comp.comp_image_size)
234 {
235 fuzz_ctx->transferred = true;
236 }
237
238 if (fuzz_chance(fuzz_ctx, 2))
239 {
240 return PLDM_FWUP_TRANSFER_ERROR_IMAGE_CORRUPT;
241 }
242 return PLDM_FWUP_TRANSFER_SUCCESS;
243}
244
245static uint8_t cb_verify(void* ctx,
246 const struct pldm_firmware_update_component* comp,
247 bool* ret_pending,
248 uint8_t* ret_percent_complete LIBPLDM_CC_UNUSED)
249{
250 debug_printf("cb_verify\n");
251 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
252
253 assert(fuzz_ctx->current_update);
254 assert(fuzz_ctx->transferred);
255 assert(!fuzz_ctx->verified);
256 assert(!fuzz_ctx->applied);
257 assert(memcmp(comp, &fuzz_ctx->update_comp, sizeof(*comp)) == 0);
258
259 if (fuzz_chance(fuzz_ctx, 5))
260 {
261 debug_printf("cb_verify set failure\n");
262 return PLDM_FWUP_VERIFY_ERROR_VERSION_MISMATCH;
263 }
264
265 if (fuzz_chance(fuzz_ctx, 50))
266 {
267 debug_printf("cb_verify set ret_pending=true\n");
268 *ret_pending = true;
269 }
270 else
271 {
272 fuzz_ctx->verified = true;
273 }
274
275 return PLDM_SUCCESS;
276}
277
278static uint8_t cb_apply(void* ctx,
279 const struct pldm_firmware_update_component* comp,
280 bool* ret_pending,
281 uint8_t* ret_percent_complete LIBPLDM_CC_UNUSED)
282{
283 debug_printf("cb_apply\n");
284 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
285
286 assert(fuzz_ctx->current_update);
287 assert(fuzz_ctx->transferred);
288 assert(fuzz_ctx->verified);
289 assert(!fuzz_ctx->applied);
290 assert(memcmp(comp, &fuzz_ctx->update_comp, sizeof(*comp)) == 0);
291
292 if (fuzz_chance(fuzz_ctx, 5))
293 {
294 debug_printf("cb_apply set failure\n");
295 return PLDM_FWUP_APPLY_FAILURE_MEMORY_ISSUE;
296 }
297
298 if (fuzz_chance(fuzz_ctx, 50))
299 {
300 debug_printf("cb_apply set ret_pending=true\n");
301 *ret_pending = true;
302 }
303 else
304 {
305 debug_printf("cb_apply set current_update=false\n");
306 fuzz_ctx->current_update = false;
307 fuzz_ctx->applied = true;
308 }
309
310 return PLDM_SUCCESS;
311}
312
313static uint8_t cb_activate(void* ctx, bool self_contained LIBPLDM_CC_UNUSED,
314 uint16_t* ret_estimated_time LIBPLDM_CC_UNUSED)
315{
316 debug_printf("cb_activate\n");
317 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
318
319 assert(!fuzz_ctx->current_update);
320 if (fuzz_chance(fuzz_ctx, 5))
321 {
322 return PLDM_ERROR;
323 }
324 return PLDM_SUCCESS;
325}
326
327static void cb_cancel_update_component(
328 void* ctx, const struct pldm_firmware_update_component* comp)
329{
330 debug_printf("cb_cancel_update_component\n");
331 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
332
333 assert(fuzz_ctx->current_update);
334 assert(fuzz_ctx->offset <= fuzz_ctx->update_comp.comp_image_size);
335 assert(memcmp(comp, &fuzz_ctx->update_comp, sizeof(*comp)) == 0);
336 fuzz_ctx->current_update = false;
337}
338
339static uint64_t cb_now(void* ctx)
340{
341 struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
342
343 // Arbitrary 3s increment. FD code has a 1s retry timeout.
344 fuzz_ctx->now += 3000;
345 return fuzz_ctx->now;
346}
347
348static const struct pldm_fd_ops fuzz_ops = {
349 .device_identifiers = cb_device_identifiers,
350 .components = cb_components,
351 .imageset_versions = cb_imageset_versions,
352 .update_component = cb_update_component,
353 .transfer_size = cb_transfer_size,
354 .firmware_data = cb_firmware_data,
355 .verify = cb_verify,
356 .apply = cb_apply,
357 .activate = cb_activate,
358 .cancel_update_component = cb_cancel_update_component,
359 .now = cb_now,
360};
361
362extern "C" int LLVMFuzzerInitialize(int* argc LIBPLDM_CC_UNUSED,
363 char*** argv LIBPLDM_CC_UNUSED)
364{
365 printf_enabled = getenv("TRACEFWFD");
366 return 0;
367}
368
369extern "C" int LLVMFuzzerTestOneInput(uint8_t* input, size_t len)
370{
Andrew Jefferya1896962025-03-03 21:41:25 +1030371 PLDM_MSGBUF_DEFINE_P(fuzzproto);
372 PLDM_MSGBUF_DEFINE_P(fuzzctrl);
Matt Johnstonabe9b372024-11-11 17:17:44 +0800373 int rc;
Matt Johnstonabe9b372024-11-11 17:17:44 +0800374
375 /* Split input into two parts. First FUZZCTRL_SIZE (0x400 bytes currently)
376 * is used for fuzzing control (random choices etc).
377 * The remainder is a PLDM packet stream, of length:data */
378 if (len < FUZZCTRL_SIZE)
379 {
380 return 0;
381 }
382 size_t proto_size = len - FUZZCTRL_SIZE;
383 rc = pldm_msgbuf_init_errno(fuzzctrl, 0, input, FUZZCTRL_SIZE);
384 assert(rc == 0);
385 rc =
386 pldm_msgbuf_init_errno(fuzzproto, 0, &input[FUZZCTRL_SIZE], proto_size);
387 assert(rc == 0);
388
389 auto ops_ctx = std::make_unique<fuzz_ops_ctx>();
390 memset(ops_ctx.get(), 0x0, sizeof(fuzz_ops_ctx));
391 /* callbacks may consume bytes from the fuzz control */
392 ops_ctx->fuzz_ctrl = fuzzctrl;
393
394 struct pldm_fd* fd = pldm_fd_new(&fuzz_ops, ops_ctx.get(), NULL);
395 assert(fd);
396
397 while (true)
398 {
399 /* Arbitrary length send buffer */
400 uint32_t send_len;
401 rc = pldm_msgbuf_extract_uint32(fuzzctrl, send_len);
402 if (rc)
403 {
404 break;
405 }
406 send_len %= (MAX_SEND + 1);
407 std::vector<uint8_t> send_buf(send_len);
408
409 size_t len = send_buf.size();
410 /* Either perform pldm_fd_handle_msg() or pldm_fd_progress() */
411 if (fuzz_chance(ops_ctx.get(), PROGRESS_PERCENT))
412 {
413 uint8_t address = FIXED_ADDR;
414 pldm_fd_progress(fd, send_buf.data(), &len, &address);
415 }
416 else
417 {
418 uint32_t part_len;
419 rc = pldm_msgbuf_extract_uint32(fuzzproto, part_len);
420 if (rc)
421 {
422 break;
423 }
424 part_len = std::min(part_len, MAX_PART);
425 /* Fresh allocation so that ASAN will notice overflow reads */
426 std::vector<uint8_t> part_buf(part_len);
427 rc = pldm_msgbuf_extract_array_uint8(
428 fuzzproto, part_len, part_buf.data(), part_buf.size());
429 if (rc != 0)
430 {
431 break;
432 }
433 pldm_fd_handle_msg(fd, FIXED_ADDR, part_buf.data(), part_buf.size(),
434 send_buf.data(), &len);
435 }
436 assert(len <= send_buf.size());
437 }
Andrew Jefferya1896962025-03-03 21:41:25 +1030438 rc = pldm_msgbuf_discard(fuzzproto, rc);
439 rc = pldm_msgbuf_discard(fuzzctrl, rc);
440 (void)rc;
Matt Johnstonabe9b372024-11-11 17:17:44 +0800441
442 free(fd);
443 return 0;
444}
445
446#ifdef HFND_FUZZING_ENTRY_FUNCTION
447#define USING_HONGGFUZZ 1
448#else
449#define USING_HONGGFUZZ 0
450#endif
451
452#ifdef __AFL_FUZZ_TESTCASE_LEN
453#define USING_AFL 1
454#else
455#define USING_AFL 0
456#endif
457
458#if USING_AFL
459__AFL_FUZZ_INIT();
460#endif
461
462#if !USING_AFL && !USING_HONGGFUZZ
463/* Let it build without AFL taking stdin instead */
464static void run_standalone()
465{
466 while (true)
467 {
468 unsigned char buf[1024000];
469 ssize_t len = read(STDIN_FILENO, buf, sizeof(buf));
470 if (len <= 0)
471 {
472 break;
473 }
474 LLVMFuzzerTestOneInput(buf, len);
475 }
476}
477#endif
478
479#if !USING_HONGGFUZZ
480int main(int argc, char** argv)
481{
482 LLVMFuzzerInitialize(&argc, &argv);
483
484#if USING_AFL
485 __AFL_INIT();
486 uint8_t* buf = __AFL_FUZZ_TESTCASE_BUF;
487
488 while (__AFL_LOOP(100000))
489 {
490 size_t len = __AFL_FUZZ_TESTCASE_LEN;
491 LLVMFuzzerTestOneInput(buf, len);
492 }
493#else
494 run_standalone();
495#endif
496
497 return 0;
498}
499#endif // !USING_HONGGFUZZ