blob: e238f27e6894e73df721c3c52109019b350b5a6a [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{
371 int rc;
372 struct pldm_msgbuf _fuzzctrl;
373 struct pldm_msgbuf* fuzzctrl = &_fuzzctrl;
374 struct pldm_msgbuf _fuzzproto;
375 struct pldm_msgbuf* fuzzproto = &_fuzzproto;
376
377 /* Split input into two parts. First FUZZCTRL_SIZE (0x400 bytes currently)
378 * is used for fuzzing control (random choices etc).
379 * The remainder is a PLDM packet stream, of length:data */
380 if (len < FUZZCTRL_SIZE)
381 {
382 return 0;
383 }
384 size_t proto_size = len - FUZZCTRL_SIZE;
385 rc = pldm_msgbuf_init_errno(fuzzctrl, 0, input, FUZZCTRL_SIZE);
386 assert(rc == 0);
387 rc =
388 pldm_msgbuf_init_errno(fuzzproto, 0, &input[FUZZCTRL_SIZE], proto_size);
389 assert(rc == 0);
390
391 auto ops_ctx = std::make_unique<fuzz_ops_ctx>();
392 memset(ops_ctx.get(), 0x0, sizeof(fuzz_ops_ctx));
393 /* callbacks may consume bytes from the fuzz control */
394 ops_ctx->fuzz_ctrl = fuzzctrl;
395
396 struct pldm_fd* fd = pldm_fd_new(&fuzz_ops, ops_ctx.get(), NULL);
397 assert(fd);
398
399 while (true)
400 {
401 /* Arbitrary length send buffer */
402 uint32_t send_len;
403 rc = pldm_msgbuf_extract_uint32(fuzzctrl, send_len);
404 if (rc)
405 {
406 break;
407 }
408 send_len %= (MAX_SEND + 1);
409 std::vector<uint8_t> send_buf(send_len);
410
411 size_t len = send_buf.size();
412 /* Either perform pldm_fd_handle_msg() or pldm_fd_progress() */
413 if (fuzz_chance(ops_ctx.get(), PROGRESS_PERCENT))
414 {
415 uint8_t address = FIXED_ADDR;
416 pldm_fd_progress(fd, send_buf.data(), &len, &address);
417 }
418 else
419 {
420 uint32_t part_len;
421 rc = pldm_msgbuf_extract_uint32(fuzzproto, part_len);
422 if (rc)
423 {
424 break;
425 }
426 part_len = std::min(part_len, MAX_PART);
427 /* Fresh allocation so that ASAN will notice overflow reads */
428 std::vector<uint8_t> part_buf(part_len);
429 rc = pldm_msgbuf_extract_array_uint8(
430 fuzzproto, part_len, part_buf.data(), part_buf.size());
431 if (rc != 0)
432 {
433 break;
434 }
435 pldm_fd_handle_msg(fd, FIXED_ADDR, part_buf.data(), part_buf.size(),
436 send_buf.data(), &len);
437 }
438 assert(len <= send_buf.size());
439 }
440
441 free(fd);
442 return 0;
443}
444
445#ifdef HFND_FUZZING_ENTRY_FUNCTION
446#define USING_HONGGFUZZ 1
447#else
448#define USING_HONGGFUZZ 0
449#endif
450
451#ifdef __AFL_FUZZ_TESTCASE_LEN
452#define USING_AFL 1
453#else
454#define USING_AFL 0
455#endif
456
457#if USING_AFL
458__AFL_FUZZ_INIT();
459#endif
460
461#if !USING_AFL && !USING_HONGGFUZZ
462/* Let it build without AFL taking stdin instead */
463static void run_standalone()
464{
465 while (true)
466 {
467 unsigned char buf[1024000];
468 ssize_t len = read(STDIN_FILENO, buf, sizeof(buf));
469 if (len <= 0)
470 {
471 break;
472 }
473 LLVMFuzzerTestOneInput(buf, len);
474 }
475}
476#endif
477
478#if !USING_HONGGFUZZ
479int main(int argc, char** argv)
480{
481 LLVMFuzzerInitialize(&argc, &argv);
482
483#if USING_AFL
484 __AFL_INIT();
485 uint8_t* buf = __AFL_FUZZ_TESTCASE_BUF;
486
487 while (__AFL_LOOP(100000))
488 {
489 size_t len = __AFL_FUZZ_TESTCASE_LEN;
490 LLVMFuzzerTestOneInput(buf, len);
491 }
492#else
493 run_standalone();
494#endif
495
496 return 0;
497}
498#endif // !USING_HONGGFUZZ