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