Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions data/netboot-fuzz-dict.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"tftp://"
"tftp://["
"tftp://[]"
"tftp://[ABCD]"
# TFTP bootfile URL option, in network byteorder
"\x3b\x00"
"/"
214 changes: 214 additions & 0 deletions fuzz-netboot.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// SPDX-License-Identifier: BSD-2-Clause-Patent
/*
* fuzz-netboot.c - fuzz TFTP netboot code.
*/
#include <stdint.h>
#include <stdio.h>

#ifndef SHIM_UNIT_TEST
#define SHIM_UNIT_TEST
#endif

#include "shim.h"

extern EFI_PXE_BASE_CODE *pxe;
extern CHAR8 *full_path;

UINT8 mok_policy = 0;
UINTN hsi_status = 0;

/* A struct to track fuzzing input bytes */
typedef struct {
const uint8_t *data;
size_t len;
} state_t;

/* Consumes `len` fuzzing input bytes into `dst` */
static int
fuzzer_consume_bytes(state_t *state, void *dst, size_t len)
{
if (state->len < len)
return 1;

memcpy(dst, state->data, len);
state->data += len;
state->len -= len;
return 0;
}

/* Returns a random length of bytes that `state` is guaranteed to
* be able to satisfy */
static int
fuzzer_consume_len(state_t *state, size_t *len)
{
if (state->len <= sizeof(*len))
return 1;

fuzzer_consume_bytes(state, len, sizeof(*len));
*len %= state->len;

return 0;
}

/* Consumes a `BOOLEAN` from the fuzzing input bytes */
static int
fuzzer_consume_bool(state_t *state, BOOLEAN *b)
{
int ret, val = 0;

ret = fuzzer_consume_bytes(state, &val, 1);
if (!ret)
*b = val & 1;
return ret;
}

/* Global fuzzing state, set from LLVMFuzzerTestOneInput() so that
* mtftp_xfer() can access input bytes */
static state_t *gstate = NULL;

static EFI_STATUS EFIAPI
mtftp_xfer(struct _EFI_PXE_BASE_CODE_PROTOCOL *pxe,
EFI_PXE_BASE_CODE_TFTP_OPCODE op, VOID *buf,
BOOLEAN overwrite UNUSED, UINT64 *bufsize, UINT64 *blocksize UNUSED,
EFI_IP_ADDRESS *addr UNUSED, UINT8 *filename UNUSED,
EFI_PXE_BASE_CODE_MTFTP_INFO *info UNUSED, BOOLEAN dontusebuf UNUSED)
{
EFI_STATUS status;
size_t size;
unsigned int i;
EFI_PXE_BASE_CODE_TFTP_ERROR *error;
uint8_t c;

if (op != EFI_PXE_BASE_CODE_TFTP_READ_FILE) {
status = EFI_UNSUPPORTED;
goto out_err;
}

if (fuzzer_consume_len(gstate, &size)) {
status = EFI_TFTP_ERROR;
goto out_err;
}

if (*bufsize < size) {
status = EFI_BUFFER_TOO_SMALL;
goto out_err;
}

fuzzer_consume_bytes(gstate, buf, size);

*bufsize = size;
return EFI_SUCCESS;

out_err:
pxe->Mode->TftpErrorReceived = 1;
error = &pxe->Mode->TftpError;
for (i = 0;
i < sizeof(error->ErrorString) / sizeof(error->ErrorString[0]);
++i) {
if (fuzzer_consume_bytes(gstate, &c, sizeof(c)))
error->ErrorString[i] = c;
}
return status;
}

static int
fuzzer_init_mode(state_t *state, EFI_PXE_BASE_CODE_MODE *mode)
{
#define FUZZ_GET_BOOL(_state, _dst) \
do { \
if (fuzzer_consume_bool(_state, _dst) != 0) \
goto out; \
} while (0)

#define FUZZ_GET_BYTES(_state, _dst) \
do { \
if (fuzzer_consume_bytes(_state, _dst, sizeof(*_dst)) != 0) \
goto out; \
} while (0)

memset(mode, 0, sizeof(*mode));

FUZZ_GET_BOOL(state, &mode->DhcpAckReceived);
FUZZ_GET_BOOL(state, &mode->ProxyOfferReceived);
FUZZ_GET_BOOL(state, &mode->PxeReplyReceived);
FUZZ_GET_BOOL(state, &mode->UsingIpv6);

if (mode->UsingIpv6) {
FUZZ_GET_BYTES(state, &mode->DhcpAck.Dhcpv6);
FUZZ_GET_BYTES(state, &mode->PxeReply.Dhcpv6);
FUZZ_GET_BYTES(state, &mode->ProxyOffer.Dhcpv6);
} else {
FUZZ_GET_BYTES(state, &mode->DhcpAck.Dhcpv4);
FUZZ_GET_BYTES(state, &mode->PxeReply.Dhcpv4);
FUZZ_GET_BYTES(state, &mode->ProxyOffer.Dhcpv4);
}

return 0;

out:
return -1;
}

static char *
fuzzer_create_name(state_t *state)
{
char *name;
size_t name_len = 0;

if (fuzzer_consume_len(state, &name_len) || !name_len)
return NULL;

name = calloc(1, name_len);
if (name)
fuzzer_consume_bytes(state, name, name_len - 1);

return name;
}

static int
fuzzer_main(state_t *state)
{
EFI_STATUS status;
char *name = NULL, *netbootname;
void *sourcebuffer = NULL;
UINT64 sourcesize;

if (fuzzer_init_mode(state, pxe->Mode))
return -1;

name = fuzzer_create_name(state);
netbootname = name ? name : "boot64.efi";

status = parseNetbootinfo(NULL, netbootname);
if (EFI_ERROR(status))
goto out;

status = FetchNetbootimage(NULL, &sourcebuffer, &sourcesize, 0);
if (!EFI_ERROR(status))
FreePool(sourcebuffer);

out:
if (full_path) {
FreePool(full_path);
full_path = NULL;
}
if (name)
free(name);
return 0;
}

static EFI_PXE_BASE_CODE_MODE fuzz_mode = { 0 };

static EFI_PXE_BASE_CODE fuzz_pxe = {
.Mtftp = mtftp_xfer,
.Mode = &fuzz_mode,
};

int
LLVMFuzzerTestOneInput(const UINT8 *data, size_t size)
{
state_t state = { .data = data, .len = size };
pxe = &fuzz_pxe;
gstate = &state;
return fuzzer_main(&state);
}
3 changes: 3 additions & 0 deletions include/fuzz.mk
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ libefi-test.a :
fuzz-sbat_FILES = csv.c lib/variables.c lib/guid.c sbat_var.S mock-variables.c
fuzz-sbat :: CFLAGS+=-DHAVE_GET_VARIABLE -DHAVE_GET_VARIABLE_ATTR -DHAVE_SHIM_LOCK_GUID

fuzz-netboot_FILES = lib/string.c
fuzz-netboot :: FUZZ_ARGS+=-dict=$(TOPDIR)/data/netboot-fuzz-dict.txt

fuzzers := $(patsubst %.c,%,$(wildcard fuzz-*.c))

$(fuzzers) :: fuzz-% : | libefi-test.a
Expand Down
2 changes: 1 addition & 1 deletion include/netboot.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

extern BOOLEAN findNetboot(EFI_HANDLE image_handle);

extern EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle, CHAR8 *name);
extern EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle, CONST CHAR8 *name);

extern EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer,
UINT64 *bufsiz, int flags);
Expand Down
19 changes: 13 additions & 6 deletions netboot.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@
#define TFTP_ERROR_EXISTS 6 /* File already exists. */
#define TFTP_ERROR_NO_USER 7 /* No such user. */

static EFI_PXE_BASE_CODE *pxe;
/* Fuzzing harness needs access to some variables that are normally static */
#ifdef SHIM_ENABLE_LIBFUZZER
#define __expose_libfuzzer
#else
#define __expose_libfuzzer static
#endif

__expose_libfuzzer EFI_PXE_BASE_CODE *pxe;
static EFI_IP_ADDRESS tftp_addr;
static CHAR8 *full_path;
__expose_libfuzzer CHAR8 *full_path;


typedef struct {
Expand Down Expand Up @@ -193,7 +200,7 @@ static CHAR8 *str2ip6(CHAR8 *str)
return (CHAR8 *)ip;
}

static BOOLEAN extract_tftp_info(CHAR8 *url, CHAR8 *name)
static BOOLEAN extract_tftp_info(CHAR8 *url, CONST CHAR8 *name)
{
CHAR8 *start, *end;
CHAR8 ip6str[40];
Expand Down Expand Up @@ -259,7 +266,7 @@ static BOOLEAN extract_tftp_info(CHAR8 *url, CHAR8 *name)
return TRUE;
}

static EFI_STATUS parseDhcp6(CHAR8 *name)
static EFI_STATUS parseDhcp6(CONST CHAR8 *name)
{
EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw;
CHAR8 *bootfile_url;
Expand All @@ -275,7 +282,7 @@ static EFI_STATUS parseDhcp6(CHAR8 *name)
return EFI_SUCCESS;
}

static EFI_STATUS parseDhcp4(CHAR8 *name)
static EFI_STATUS parseDhcp4(CONST CHAR8 *name)
{
CHAR8 *template;
INTN template_len = 0;
Expand Down Expand Up @@ -345,7 +352,7 @@ static EFI_STATUS parseDhcp4(CHAR8 *name)
return EFI_SUCCESS;
}

EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle UNUSED, CHAR8 *netbootname)
EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle UNUSED, CONST CHAR8 *netbootname)
{

EFI_STATUS efi_status;
Expand Down