xref: /openbmc/libpldm/tests/fuzz/fd-fuzz.cpp (revision a189696768c2aea88202c4e9530d76a69bd1947e)
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 <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 */
24 static const uint32_t MAX_PART = 200;
25 
26 /* Maximum "send" buffer. Should be larger than any expected sent message */
27 static const uint32_t MAX_SEND = 1024;
28 
29 /* Arbitrary EID */
30 static const uint8_t FIXED_ADDR = 20;
31 
32 static const uint8_t PROGRESS_PERCENT = 5;
33 
34 static const ssize_t FUZZCTRL_SIZE = 0x400;
35 
36 static bool printf_enabled;
37 // NOLINTNEXTLINE(cert-dcl50-cpp)
debug_printf(const char * fmt,...)38 static 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 
49 struct 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 */
fuzz_chance(struct fuzz_ops_ctx * ctx,uint8_t percent)65 static 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
79 static 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 */
88 static 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 
cb_device_identifiers(void * ctx LIBPLDM_CC_UNUSED,uint8_t * descriptors_count,const struct pldm_descriptor ** descriptors)96 static 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 
106 static 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 
136 static const struct pldm_firmware_component_standalone* comp_list[1] = {
137     &comp,
138 };
139 
cb_components(void * ctx,uint16_t * ret_entry_count,const struct pldm_firmware_component_standalone *** ret_entries)140 static 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 
cb_imageset_versions(void * ctx,struct pldm_firmware_string * active,struct pldm_firmware_string * pending)156 static 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 
175 static enum pldm_component_response_codes
cb_update_component(void * ctx,bool update,const struct pldm_firmware_update_component * comp)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 
cb_transfer_size(void * ctx,uint32_t ua_max_transfer_size)201 static 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 
215 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)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 
cb_verify(void * ctx,const struct pldm_firmware_update_component * comp,bool * ret_pending,uint8_t * ret_percent_complete LIBPLDM_CC_UNUSED)245 static 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 
cb_apply(void * ctx,const struct pldm_firmware_update_component * comp,bool * ret_pending,uint8_t * ret_percent_complete LIBPLDM_CC_UNUSED)278 static 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 
cb_activate(void * ctx,bool self_contained LIBPLDM_CC_UNUSED,uint16_t * ret_estimated_time LIBPLDM_CC_UNUSED)313 static 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 
cb_cancel_update_component(void * ctx,const struct pldm_firmware_update_component * comp)327 static 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 
cb_now(void * ctx)339 static 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 
348 static 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 
LLVMFuzzerInitialize(int * argc LIBPLDM_CC_UNUSED,char *** argv LIBPLDM_CC_UNUSED)362 extern "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 
LLVMFuzzerTestOneInput(uint8_t * input,size_t len)369 extern "C" int LLVMFuzzerTestOneInput(uint8_t* input, size_t len)
370 {
371     PLDM_MSGBUF_DEFINE_P(fuzzproto);
372     PLDM_MSGBUF_DEFINE_P(fuzzctrl);
373     int rc;
374 
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     }
438     rc = pldm_msgbuf_discard(fuzzproto, rc);
439     rc = pldm_msgbuf_discard(fuzzctrl, rc);
440     (void)rc;
441 
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 */
run_standalone()464 static 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
main(int argc,char ** argv)480 int 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
500