Skip to content

Commit

Permalink
Add libFuzzer support for csv.c
Browse files Browse the repository at this point in the history
shim takes several forms of input from several sources that are not
necessarily trustworthy.  As such, we need to take measures to validate
that we don't have unacceptable results from bad inputs.  One such
measure is "fuzzing" the inputs which parse untrusted data by running
them with randomized or partially randomized input.

This change adds such testing using clang's "libFuzzer" to our CSV
parser.  I've run this on 24-cores at 4GHz for half an hour, and so far
each fuzzer has converged on 79% coverage.  I expect the 21% that's not
getting covered are the EFI API mock interfaces we're building in from
test.c and similar.  So far no errors have been found, which is what was
expected since this particular API is being manually fuzzed with ~8kB of
/dev/urandom on every build since 2021-02-23.

Signed-off-by: Peter Jones <pjones@redhat.com>
  • Loading branch information
vathpela committed Jun 28, 2023
1 parent 61e9894 commit f4fa540
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Make.local
/scan-results/
[Ss]creenlog*
shim_cert.h
/fuzz-*
!/fuzz-*.c
/test-*
!/test-*.c
!/test-data/
Expand Down
20 changes: 18 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,15 @@ else
$(PESIGN) -n certdb -i $< -c "shim" -s -o $@ -f
endif

fuzz fuzz-clean fuzz-coverage fuzz-lto :
@make -f $(TOPDIR)/include/fuzz.mk \
COMPILER="$(COMPILER)" \
CROSS_COMPILE="$(CROSS_COMPILE)" \
CLANG_WARNINGS="$(CLANG_WARNINGS)" \
ARCH_DEFINES="$(ARCH_DEFINES)" \
EFI_INCLUDES="$(EFI_INCLUDES)" \
fuzz-clean $@

test test-clean test-coverage test-lto :
@make -f $(TOPDIR)/include/test.mk \
COMPILER="$(COMPILER)" \
Expand All @@ -299,14 +308,21 @@ test test-clean test-coverage test-lto :
EFI_INCLUDES="$(EFI_INCLUDES)" \
test-clean $@

$(patsubst %.c,%,$(wildcard fuzz-*.c)) :
@make -f $(TOPDIR)/include/fuzz.mk EFI_INCLUDES="$(EFI_INCLUDES)" ARCH_DEFINES="$(ARCH_DEFINES)" $@

$(patsubst %.c,%,$(wildcard test-*.c)) :
@make -f $(TOPDIR)/include/test.mk EFI_INCLUDES="$(EFI_INCLUDES)" ARCH_DEFINES="$(ARCH_DEFINES)" $@

.PHONY : $(patsubst %.c,%,$(wildcard test-*.c)) test
clean-fuzz-objs:
@make -f $(TOPDIR)/include/fuzz.mk EFI_INCLUDES="$(EFI_INCLUDES)" ARCH_DEFINES="$(ARCH_DEFINES)" clean

clean-test-objs:
@make -f $(TOPDIR)/include/test.mk EFI_INCLUDES="$(EFI_INCLUDES)" ARCH_DEFINES="$(ARCH_DEFINES)" clean

.PHONY : $(patsubst %.c,%,$(wildcard fuzz-*.c)) fuzz
.PHONY : $(patsubst %.c,%,$(wildcard test-*.c)) test

clean-gnu-efi:
@if [ -d gnu-efi ] ; then \
$(MAKE) -C gnu-efi \
Expand Down Expand Up @@ -340,7 +356,7 @@ clean-cryptlib-objs:
$(MAKE) -C Cryptlib -f $(TOPDIR)/Cryptlib/Makefile clean ; \
fi

clean: clean-shim-objs clean-test-objs clean-gnu-efi clean-openssl-objs clean-cryptlib-objs clean-lib-objs
clean: clean-shim-objs clean-fuzz-objs clean-test-objs clean-gnu-efi clean-openssl-objs clean-cryptlib-objs clean-lib-objs

GITTAG = $(VERSION)

Expand Down
71 changes: 71 additions & 0 deletions fuzz-csv.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: BSD-2-Clause-Patent
/*
* test-csv.c - test our csv parser
*/

#ifndef SHIM_UNIT_TEST
#define SHIM_UNIT_TEST
#endif
#include "shim.h"

#include <stdio.h>

int
test_csv_simple_fuzz(char *random_bin, size_t random_bin_len)
{
list_t entry_list;
size_t i;
char *current, *end;
list_t *pos = NULL;
EFI_STATUS efi_status;

INIT_LIST_HEAD(&entry_list);

current = &random_bin[0];
current = current + 1 - 1;
end = current + random_bin_len - 1;
*end = '\0';

efi_status = parse_csv_data(current, end, 7, &entry_list);
if (efi_status != EFI_SUCCESS)
return 0;
if (list_size(&entry_list) <= 1)
goto fail;

i = 0;
list_for_each(pos, &entry_list) {
struct csv_row *csv_row;

csv_row = list_entry(pos, struct csv_row, list);
i++;
}

free_csv_list(&entry_list);

return 0;
fail:
free_csv_list(&entry_list);
return -1;
}

int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
int rc;
uint8_t *data_copy;

if (size < 1)
return 0;

data_copy = malloc(size);
if (!data_copy)
return -1;

memcpy(data_copy, data, size);
rc = test_csv_simple_fuzz((char *)data_copy, size);
free(data_copy);

return rc; // Values other than 0 and -1 are reserved for future use.
}

// vim:fenc=utf-8:tw=75:noet
94 changes: 94 additions & 0 deletions include/fuzz.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
# fuzz.mk - makefile to fuzz local test programs
#

.SUFFIXES:

include Make.defaults

CC = clang
VALGRIND ?=
DEBUG_PRINTS ?= 0
OPTIMIZATIONS=-fsanitize=fuzzer,address -Og -ggdb
CFLAGS = $(OPTIMIZATIONS) -std=gnu11 \
-isystem $(TOPDIR)/include/system \
$(EFI_INCLUDES) \
-Iinclude -iquote . \
-isystem /usr/include \
-isystem $(shell $(CC) $(ARCH_CFLAGS) -print-file-name=include) \
$(ARCH_CFLAGS) \
-fshort-wchar \
-fno-builtin \
-rdynamic \
-fno-inline \
-fno-eliminate-unused-debug-types \
-fno-eliminate-unused-debug-symbols \
-gpubnames \
-grecord-gcc-switches \
$(if $(findstring clang,$(CC)),-Wno-unknown-warning-option) \
$(DEFAULT_WARNFLAGS) \
-Wsign-compare \
-Wno-deprecated-declarations \
$(if $(findstring gcc,$(CC)),-Wno-unused-but-set-variable) \
-Wno-unused-but-set-variable \
-Wno-unused-variable \
-Wno-pointer-sign \
$(DEFAULT_WERRFLAGS) \
-Werror=nonnull \
$(shell $(CC) -Werror=nonnull-compare -E -x c /dev/null >/dev/null 2>&1 && echo -Werror=nonnull-compare) \
$(ARCH_DEFINES) \
-DEFI_FUNCTION_WRAPPER \
-DGNU_EFI_USE_MS_ABI -DPAGE_SIZE=4096 \
-DSHIM_UNIT_TEST \
-DSHIM_ENABLE_LIBFUZZER \
"-DDEFAULT_DEBUG_PRINT_STATE=$(DEBUG_PRINTS)"

# On some systems (e.g. Arch Linux), limits.h is in the "include-fixed" instead
# of the "include" directory
CFLAGS += -isystem $(shell $(CC) $(ARCH_CFLAGS) -print-file-name=include-fixed)

# And on Debian also check the multi-arch include path
CFLAGS += -isystem /usr/include/$(shell $(CC) $(ARCH_CFLAGS) -print-multiarch)

libefi-test.a :
$(MAKE) -C gnu-efi \
COMPILER="$(COMPILER)" \
CC="$(CC)" \
ARCH=$(ARCH_GNUEFI) \
TOPDIR=$(TOPDIR)/gnu-efi \
-f $(TOPDIR)/gnu-efi/Makefile \
clean lib
mv gnu-efi/$(ARCH)/lib/libefi.a $@
$(MAKE) -C gnu-efi \
COMPILER="$(COMPILER)" \
ARCH=$(ARCH_GNUEFI) \
TOPDIR=$(TOPDIR)/gnu-efi \
-f $(TOPDIR)/gnu-efi/Makefile \
clean

fuzz-str_FILES = lib/string.c

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

$(fuzzers) :: fuzz-% : | libefi-test.a

$(fuzzers) :: fuzz-% : test.c fuzz-%.c $(fuzz-%_FILES)
$(CC) $(CFLAGS) -o $@ $(sort $^ $(wildcard $*.c) $(fuzz-$*_FILES)) libefi-test.a -lefivar
$(VALGRIND) ./$@ -max_len=4096 -jobs=24

fuzz : $(fuzzers)
$(MAKE) -f include/fuzz.mk fuzz-clean

fuzz-clean :
@rm -vf random.bin libefi-test.a
@rm -vf vgcore.* fuzz*.log

clean : fuzz-clean

all : fuzz-clean fuzz

.PHONY: $(fuzzers) all fuzz clean
.SECONDARY: random.bin

# vim:ft=make

0 comments on commit f4fa540

Please sign in to comment.