blob: e238f27e6894e73df721c3c52109019b350b5a6a [file] [log] [blame]
/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
/* Fuzzing should always have assertions */
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <libpldm/base.h>
#include <libpldm/firmware_fd.h>
#include <libpldm/firmware_update.h>
#include <libpldm/sizes.h>
#include <cstdarg>
#include <cstdint>
#include <cstring>
#include <memory>
#include <vector>
#include "array.h"
#include "msgbuf.h"
/* Avoid out-of-memory, and
* avoid wasting time on inputs larger than MCTP message limits */
static const uint32_t MAX_PART = 200;
/* Maximum "send" buffer. Should be larger than any expected sent message */
static const uint32_t MAX_SEND = 1024;
/* Arbitrary EID */
static const uint8_t FIXED_ADDR = 20;
static const uint8_t PROGRESS_PERCENT = 5;
static const ssize_t FUZZCTRL_SIZE = 0x400;
static bool printf_enabled;
// NOLINTNEXTLINE(cert-dcl50-cpp)
static void debug_printf(const char* fmt, ...)
{
if (printf_enabled)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
}
struct fuzz_ops_ctx
{
struct pldm_msgbuf* fuzz_ctrl;
/* Details of in-progress update, for consistency checking */
bool current_update;
struct pldm_firmware_update_component update_comp;
uint32_t offset;
bool transferred;
bool verified;
bool applied;
uint64_t now;
};
/* Returns true with roughly `percent` chance */
static bool fuzz_chance(struct fuzz_ops_ctx* ctx, uint8_t percent)
{
uint8_t v;
assert(percent <= 100);
int rc = pldm_msgbuf_extract_uint8(ctx->fuzz_ctrl, v);
if (rc != 0)
{
return false;
}
uint8_t cutoff = (uint32_t)percent * UINT8_MAX / 100;
return v <= cutoff;
}
// openbmc 49871
static const uint8_t openbmc_iana[] = {
0xcf,
0xc2,
0x00,
0x00,
};
/* An arbitrary but valid set of descriptors.
* Short to be readily discoverable by fuzzing */
static const pldm_descriptor FIXED_DESCRIPTORS[] = {
{
.descriptor_type = PLDM_FWUP_IANA_ENTERPRISE_ID,
.descriptor_length = 4,
.descriptor_data = openbmc_iana,
},
};
static int cb_device_identifiers(void* ctx LIBPLDM_CC_UNUSED,
uint8_t* descriptors_count,
const struct pldm_descriptor** descriptors)
{
debug_printf("cb_device_identifiers\n");
*descriptors_count = 1;
*descriptors = FIXED_DESCRIPTORS;
return 0;
}
static const struct pldm_firmware_component_standalone comp = {
.comp_classification = PLDM_COMP_UNKNOWN,
.comp_identifier = 0,
.comp_classification_index = 0,
.active_ver =
{
.comparison_stamp = 1,
.str =
{
.str_type = PLDM_STR_TYPE_UTF_8,
.str_len = 3,
.str_data = "zzz",
},
.date = {0},
},
.pending_ver =
{
.comparison_stamp = 1,
.str =
{
.str_type = PLDM_STR_TYPE_UNKNOWN,
.str_len = 4,
.str_data = "fnnn",
},
.date = {0},
},
.comp_activation_methods = {0},
.capabilities_during_update = {0},
};
static const struct pldm_firmware_component_standalone* comp_list[1] = {
&comp,
};
static int cb_components(
void* ctx, uint16_t* ret_entry_count,
const struct pldm_firmware_component_standalone*** ret_entries)
{
debug_printf("cb_components\n");
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
*ret_entry_count = ARRAY_SIZE(comp_list);
*ret_entries = comp_list;
if (fuzz_chance(fuzz_ctx, 4))
{
return -EINVAL;
}
return 0;
}
static int cb_imageset_versions(void* ctx, struct pldm_firmware_string* active,
struct pldm_firmware_string* pending)
{
debug_printf("cb_imageset_versions\n");
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
active->str_type = PLDM_STR_TYPE_ASCII;
active->str_len = 4;
memcpy(active->str_data, "1234", 4);
pending->str_type = PLDM_STR_TYPE_ASCII;
pending->str_len = 4;
memcpy(pending->str_data, "1235", 4);
if (fuzz_chance(fuzz_ctx, 4))
{
return -EINVAL;
}
return 0;
}
static enum pldm_component_response_codes
cb_update_component(void* ctx, bool update,
const struct pldm_firmware_update_component* comp)
{
debug_printf("cb_update_component update=%d\n", update);
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
if (fuzz_chance(fuzz_ctx, 4))
{
return PLDM_CRC_COMP_PREREQUISITES_NOT_MET;
}
if (update)
{
/* Set up a new update */
assert(!fuzz_ctx->current_update);
debug_printf("cb_update_component set current_update=true\n");
fuzz_ctx->current_update = true;
fuzz_ctx->transferred = false;
fuzz_ctx->verified = false;
fuzz_ctx->applied = false;
fuzz_ctx->offset = 0;
memcpy(&fuzz_ctx->update_comp, comp, sizeof(*comp));
}
return PLDM_CRC_COMP_CAN_BE_UPDATED;
}
static uint32_t cb_transfer_size(void* ctx, uint32_t ua_max_transfer_size)
{
debug_printf("cb_transfer_size ua_size=%zu\n",
(ssize_t)ua_max_transfer_size);
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
if (fuzz_chance(fuzz_ctx, 50))
{
// Sometimes adjust it
return MAX_PART - 20;
}
return ua_max_transfer_size;
}
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)
{
debug_printf("cb_firmware_data offset=%zu len %zu\n", (size_t)offset,
(size_t)len);
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
assert(fuzz_ctx->current_update);
assert(!fuzz_ctx->transferred);
assert(!fuzz_ctx->verified);
assert(!fuzz_ctx->applied);
assert(offset == fuzz_ctx->offset);
fuzz_ctx->offset += len;
assert(fuzz_ctx->offset <= fuzz_ctx->update_comp.comp_image_size);
assert(memcmp(comp, &fuzz_ctx->update_comp, sizeof(*comp)) == 0);
if (fuzz_ctx->offset == fuzz_ctx->update_comp.comp_image_size)
{
fuzz_ctx->transferred = true;
}
if (fuzz_chance(fuzz_ctx, 2))
{
return PLDM_FWUP_TRANSFER_ERROR_IMAGE_CORRUPT;
}
return PLDM_FWUP_TRANSFER_SUCCESS;
}
static uint8_t cb_verify(void* ctx,
const struct pldm_firmware_update_component* comp,
bool* ret_pending,
uint8_t* ret_percent_complete LIBPLDM_CC_UNUSED)
{
debug_printf("cb_verify\n");
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
assert(fuzz_ctx->current_update);
assert(fuzz_ctx->transferred);
assert(!fuzz_ctx->verified);
assert(!fuzz_ctx->applied);
assert(memcmp(comp, &fuzz_ctx->update_comp, sizeof(*comp)) == 0);
if (fuzz_chance(fuzz_ctx, 5))
{
debug_printf("cb_verify set failure\n");
return PLDM_FWUP_VERIFY_ERROR_VERSION_MISMATCH;
}
if (fuzz_chance(fuzz_ctx, 50))
{
debug_printf("cb_verify set ret_pending=true\n");
*ret_pending = true;
}
else
{
fuzz_ctx->verified = true;
}
return PLDM_SUCCESS;
}
static uint8_t cb_apply(void* ctx,
const struct pldm_firmware_update_component* comp,
bool* ret_pending,
uint8_t* ret_percent_complete LIBPLDM_CC_UNUSED)
{
debug_printf("cb_apply\n");
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
assert(fuzz_ctx->current_update);
assert(fuzz_ctx->transferred);
assert(fuzz_ctx->verified);
assert(!fuzz_ctx->applied);
assert(memcmp(comp, &fuzz_ctx->update_comp, sizeof(*comp)) == 0);
if (fuzz_chance(fuzz_ctx, 5))
{
debug_printf("cb_apply set failure\n");
return PLDM_FWUP_APPLY_FAILURE_MEMORY_ISSUE;
}
if (fuzz_chance(fuzz_ctx, 50))
{
debug_printf("cb_apply set ret_pending=true\n");
*ret_pending = true;
}
else
{
debug_printf("cb_apply set current_update=false\n");
fuzz_ctx->current_update = false;
fuzz_ctx->applied = true;
}
return PLDM_SUCCESS;
}
static uint8_t cb_activate(void* ctx, bool self_contained LIBPLDM_CC_UNUSED,
uint16_t* ret_estimated_time LIBPLDM_CC_UNUSED)
{
debug_printf("cb_activate\n");
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
assert(!fuzz_ctx->current_update);
if (fuzz_chance(fuzz_ctx, 5))
{
return PLDM_ERROR;
}
return PLDM_SUCCESS;
}
static void cb_cancel_update_component(
void* ctx, const struct pldm_firmware_update_component* comp)
{
debug_printf("cb_cancel_update_component\n");
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
assert(fuzz_ctx->current_update);
assert(fuzz_ctx->offset <= fuzz_ctx->update_comp.comp_image_size);
assert(memcmp(comp, &fuzz_ctx->update_comp, sizeof(*comp)) == 0);
fuzz_ctx->current_update = false;
}
static uint64_t cb_now(void* ctx)
{
struct fuzz_ops_ctx* fuzz_ctx = static_cast<struct fuzz_ops_ctx*>(ctx);
// Arbitrary 3s increment. FD code has a 1s retry timeout.
fuzz_ctx->now += 3000;
return fuzz_ctx->now;
}
static const struct pldm_fd_ops fuzz_ops = {
.device_identifiers = cb_device_identifiers,
.components = cb_components,
.imageset_versions = cb_imageset_versions,
.update_component = cb_update_component,
.transfer_size = cb_transfer_size,
.firmware_data = cb_firmware_data,
.verify = cb_verify,
.apply = cb_apply,
.activate = cb_activate,
.cancel_update_component = cb_cancel_update_component,
.now = cb_now,
};
extern "C" int LLVMFuzzerInitialize(int* argc LIBPLDM_CC_UNUSED,
char*** argv LIBPLDM_CC_UNUSED)
{
printf_enabled = getenv("TRACEFWFD");
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(uint8_t* input, size_t len)
{
int rc;
struct pldm_msgbuf _fuzzctrl;
struct pldm_msgbuf* fuzzctrl = &_fuzzctrl;
struct pldm_msgbuf _fuzzproto;
struct pldm_msgbuf* fuzzproto = &_fuzzproto;
/* Split input into two parts. First FUZZCTRL_SIZE (0x400 bytes currently)
* is used for fuzzing control (random choices etc).
* The remainder is a PLDM packet stream, of length:data */
if (len < FUZZCTRL_SIZE)
{
return 0;
}
size_t proto_size = len - FUZZCTRL_SIZE;
rc = pldm_msgbuf_init_errno(fuzzctrl, 0, input, FUZZCTRL_SIZE);
assert(rc == 0);
rc =
pldm_msgbuf_init_errno(fuzzproto, 0, &input[FUZZCTRL_SIZE], proto_size);
assert(rc == 0);
auto ops_ctx = std::make_unique<fuzz_ops_ctx>();
memset(ops_ctx.get(), 0x0, sizeof(fuzz_ops_ctx));
/* callbacks may consume bytes from the fuzz control */
ops_ctx->fuzz_ctrl = fuzzctrl;
struct pldm_fd* fd = pldm_fd_new(&fuzz_ops, ops_ctx.get(), NULL);
assert(fd);
while (true)
{
/* Arbitrary length send buffer */
uint32_t send_len;
rc = pldm_msgbuf_extract_uint32(fuzzctrl, send_len);
if (rc)
{
break;
}
send_len %= (MAX_SEND + 1);
std::vector<uint8_t> send_buf(send_len);
size_t len = send_buf.size();
/* Either perform pldm_fd_handle_msg() or pldm_fd_progress() */
if (fuzz_chance(ops_ctx.get(), PROGRESS_PERCENT))
{
uint8_t address = FIXED_ADDR;
pldm_fd_progress(fd, send_buf.data(), &len, &address);
}
else
{
uint32_t part_len;
rc = pldm_msgbuf_extract_uint32(fuzzproto, part_len);
if (rc)
{
break;
}
part_len = std::min(part_len, MAX_PART);
/* Fresh allocation so that ASAN will notice overflow reads */
std::vector<uint8_t> part_buf(part_len);
rc = pldm_msgbuf_extract_array_uint8(
fuzzproto, part_len, part_buf.data(), part_buf.size());
if (rc != 0)
{
break;
}
pldm_fd_handle_msg(fd, FIXED_ADDR, part_buf.data(), part_buf.size(),
send_buf.data(), &len);
}
assert(len <= send_buf.size());
}
free(fd);
return 0;
}
#ifdef HFND_FUZZING_ENTRY_FUNCTION
#define USING_HONGGFUZZ 1
#else
#define USING_HONGGFUZZ 0
#endif
#ifdef __AFL_FUZZ_TESTCASE_LEN
#define USING_AFL 1
#else
#define USING_AFL 0
#endif
#if USING_AFL
__AFL_FUZZ_INIT();
#endif
#if !USING_AFL && !USING_HONGGFUZZ
/* Let it build without AFL taking stdin instead */
static void run_standalone()
{
while (true)
{
unsigned char buf[1024000];
ssize_t len = read(STDIN_FILENO, buf, sizeof(buf));
if (len <= 0)
{
break;
}
LLVMFuzzerTestOneInput(buf, len);
}
}
#endif
#if !USING_HONGGFUZZ
int main(int argc, char** argv)
{
LLVMFuzzerInitialize(&argc, &argv);
#if USING_AFL
__AFL_INIT();
uint8_t* buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(100000))
{
size_t len = __AFL_FUZZ_TESTCASE_LEN;
LLVMFuzzerTestOneInput(buf, len);
}
#else
run_standalone();
#endif
return 0;
}
#endif // !USING_HONGGFUZZ