diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b38f8473c5f4f..7aa827f7f0d2d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,11 @@ jobs: runs_on: ubuntu-22.04 shell: bash build_options: "LLVM=1 CROSS_COMPILE=x86_64-linux-gnu" + - displayTargetName: lkl-fuzzers + os: unix + runs_on: ubuntu-22.04 + shell: bash + build_options: "LKL_FUZZING=1 fuzzers" timeout-minutes: 100 env: CCACHE_DIR: ${{ github.workspace }}/.ccache diff --git a/arch/lkl/Kconfig b/arch/lkl/Kconfig index 1ab00b45e1dd07..cb319652ff49fa 100644 --- a/arch/lkl/Kconfig +++ b/arch/lkl/Kconfig @@ -42,6 +42,26 @@ config LKL select GENERIC_STRNLEN_USER select HAVE_ARCH_KASAN +config LKL_FUZZING + bool "LLVM fuzzing instrumentation" + default n + help + This configuration option enables fuzzing instrumentation + for the Linux kernel source files to enable coverage-guided + fuzzing. At the moment LKL supports libFuzzer fuzzing + engine only. + +config LKL_LINE_COV + bool "Code coverage instrumentation for fuzzers" + depends on LKL_FUZZING && CC_IS_CLANG + default n + help + This configuration option enables line code coverage + instrumentation for the Linux kernel to generate fuzzing + code coverage reports. When this option is enabled the + kernel source files are built with LLVM SanitizerCoverage + instrumentation. + config OUTPUT_FORMAT string "Output format" default "" diff --git a/arch/lkl/Makefile b/arch/lkl/Makefile index 2f31aa33eb2826..33919e41a0ad34 100644 --- a/arch/lkl/Makefile +++ b/arch/lkl/Makefile @@ -27,6 +27,28 @@ else # e.g., FreeBSD NPROC=$(shell sysctl -n hw.ncpu) endif +ifdef CONFIG_CC_IS_CLANG +# LKL fuzzing is based on libFuzzer which depends on clang compiler. +ifdef CONFIG_LKL_FUZZING +# This makes sure assembly code is compiled with clang's integerated assembler +# so the result object files are compatible with the fuzzing flag. +KBUILD_CFLAGS += -integrated-as + +# We want debug symbols for fuzzing (e.g. better stack trace in gdb). +KBUILD_CFLAGS += -g + +# Enabling libfuzzer instrumentation +KBUILD_CFLAGS += -fsanitize=fuzzer-no-link + +KEEP_EH_FRAMES := true +endif + +# This flag enables clang's extra instrumentation for visualizing line coverage. +ifdef CONFIG_LKL_LINE_COV +KBUILD_CFLAGS += -fprofile-instr-generate -fcoverage-mapping +endif +endif + LDFLAGS_vmlinux += -r LKL_ENTRY_POINTS := lkl_start_kernel lkl_sys_halt lkl_syscall lkl_trigger_irq \ lkl_get_free_irq lkl_put_irq lkl_is_running lkl_bug lkl_printf \ @@ -45,7 +67,7 @@ core-y += arch/lkl/drivers/ all: lkl.o arch/lkl/include/generated/uapi/asm/syscall_defs.h lkl.o: vmlinux - $(OBJCOPY) -R .eh_frame -R .syscall_defs $(foreach sym,$(LKL_ENTRY_POINTS),-G$(prefix)$(sym)) --prefix-symbols=$(prefix) vmlinux lkl.o + $(OBJCOPY) $(if $(KEEP_EH_FRAMES),,-R .eh_frame) -R .syscall_defs $(foreach sym,$(LKL_ENTRY_POINTS),-G$(prefix)$(sym)) --prefix-symbols=$(prefix) vmlinux lkl.o arch/lkl/include/generated/uapi/asm/syscall_defs.h: vmlinux $(OBJCOPY) -j .syscall_defs -O binary --set-section-flags .syscall_defs=alloc $< $@ diff --git a/arch/lkl/Makefile.postlink b/arch/lkl/Makefile.postlink new file mode 100644 index 00000000000000..4c7e9e42feb858 --- /dev/null +++ b/arch/lkl/Makefile.postlink @@ -0,0 +1,53 @@ +# The post-link vmlinux pass is only relevant when LKL is being built with +# libFuzzer instrumentation (-fsanitize=fuzzer). SanitizerCoverage +# emits GRP_COMDAT sections in the generated object files to group sections +# with instrumented code, coverage counters and relocations respectively. +# The linker semantics for incremental linking with section groups is unclear +# and results in the link-time errors. This extra post-link pass implements a +# workaround for this problem: +# +# 1) Vmlinux is incrementally linked with an empty linker script. Thus, the input +# sections aren't merged in the generated vmlinux image (ld and lld do still +# merge some common sections such as .text. .rela.text, .data and etc but it +# doesn't casue a problem). +# +# 2) Remove GRP_COMDAT group sections with name .group from the original vmlinux. +# +# 3) Relink the stripped vmlinux using the original LKL linker script. +# As there is no group sections in the input object, merging sections doesn't +# cause any issues. +# +# TODO: The above steps aren't solution to the problem but rather a short-term +# workaround. At the moment removing .group sections from the incrementally +# linked vmlinux doesn't seem to cause any issues as the only GRP_COMDAT sections +# present in the object files are the ones emited by SanitizerCoverage +# instrumentation. However, here is a couple of other things to take into +# consideration: +# +# * --gc-sections linker option could remove __sancov_pcs sections, thus, this +# option shouldn't be used when building fuzzers. +# +# * If the linker removes a function there might be 'dangling' __sancov_xxx +# sections. However, this isn't an issue for fuzzing. +# +# * __sancov_pcs and __sancov_cntrs sections need to be merged in the corresponding +# order (so that the value of the counters match to values of PC of their +# corresponding basic blocks). With this workaround this seem to be the case +# (while the linker might not guarantee this). However, even if these sections +# aren't merged in the order, technically, this won't be a problem for fuzzing. +ifneq ($(LKL_FUZZING),) +include scripts/Kbuild.include + +LKL_FUZZING_LDS := arch/lkl/kernel/vmlinux-fuzzing-postlink.lds + +.PHONY: vmlinux +vmlinux: $(LKL_FUZZING_LDS) + $(OBJCOPY) --remove-section=.group $@ $@.no_groups + $(LD) -r --build-id=sha1 -o $@ -T $< $@.no_groups + +clean: ; rm -rf $(LKL_FUZZING_LDS) vmlinux.no_groups +else # LKL_FUZZING +clean: ; +endif # LKL_FUZZING + +.PHONY: clean \ No newline at end of file diff --git a/arch/lkl/configs/fuzzing_defconfig b/arch/lkl/configs/fuzzing_defconfig new file mode 100644 index 00000000000000..8e2b348c4dd201 --- /dev/null +++ b/arch/lkl/configs/fuzzing_defconfig @@ -0,0 +1,193 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_NO_HZ_IDLE=y +# CONFIG_SYSFS_SYSCALL is not set +CONFIG_KALLSYMS_USE_DATA_SECTION=y +CONFIG_KALLSYMS_ALL=y +# CONFIG_BASE_FULL is not set +# CONFIG_FUTEX is not set +# CONFIG_SIGNALFD is not set +# CONFIG_TIMERFD is not set +# CONFIG_AIO is not set +# CONFIG_ADVISE_SYSCALLS is not set +CONFIG_EMBEDDED=y +# CONFIG_VM_EVENT_COUNTERS is not set +# CONFIG_COMPAT_BRK is not set +# CONFIG_BLK_DEV_BSG is not set +CONFIG_NET=y +CONFIG_INET=y +# CONFIG_WIRELESS is not set +# CONFIG_UEVENT_HELPER is not set +# CONFIG_FW_LOADER is not set +CONFIG_VIRTIO_BLK=y +CONFIG_NETDEVICES=y +CONFIG_VIRTIO_NET=y +CONFIG_VHOST_VSOCK=y +CONFIG_VIRTIO_VSOCKETS=y +CONFIG_VSOCKETS=y +# CONFIG_ETHERNET is not set +# CONFIG_WLAN is not set +# CONFIG_VT is not set +CONFIG_VIRTIO_MMIO=y +CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y +# CONFIG_FILE_LOCKING is not set +# CONFIG_DNOTIFY is not set +# CONFIG_INOTIFY_USER is not set +CONFIG_VFAT_FS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=y +CONFIG_NLS_CODEPAGE_775=y +CONFIG_NLS_CODEPAGE_850=y +CONFIG_NLS_CODEPAGE_852=y +CONFIG_NLS_CODEPAGE_855=y +CONFIG_NLS_CODEPAGE_857=y +CONFIG_NLS_CODEPAGE_860=y +CONFIG_NLS_CODEPAGE_861=y +CONFIG_NLS_CODEPAGE_862=y +CONFIG_NLS_CODEPAGE_863=y +CONFIG_NLS_CODEPAGE_864=y +CONFIG_NLS_CODEPAGE_865=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_869=y +CONFIG_NLS_CODEPAGE_936=y +CONFIG_NLS_CODEPAGE_950=y +CONFIG_NLS_CODEPAGE_932=y +CONFIG_NLS_CODEPAGE_949=y +CONFIG_NLS_CODEPAGE_874=y +CONFIG_NLS_ISO8859_8=y +CONFIG_NLS_CODEPAGE_1250=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_2=y +CONFIG_NLS_ISO8859_3=y +CONFIG_NLS_ISO8859_4=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_ISO8859_6=y +CONFIG_NLS_ISO8859_7=y +CONFIG_NLS_ISO8859_9=y +CONFIG_NLS_ISO8859_13=y +CONFIG_NLS_ISO8859_14=y +CONFIG_NLS_ISO8859_15=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_KOI8_U=y +CONFIG_NLS_MAC_ROMAN=y +CONFIG_NLS_MAC_CELTIC=y +CONFIG_NLS_MAC_CENTEURO=y +CONFIG_NLS_MAC_CROATIAN=y +CONFIG_NLS_MAC_CYRILLIC=y +CONFIG_NLS_MAC_GAELIC=y +CONFIG_NLS_MAC_GREEK=y +CONFIG_NLS_MAC_ICELAND=y +CONFIG_NLS_MAC_INUIT=y +CONFIG_NLS_MAC_ROMANIAN=y +CONFIG_NLS_MAC_TURKISH=y +CONFIG_NLS_UTF8=y +CONFIG_HZ_100=y +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_INFO_REDUCED=y +# CONFIG_ENABLE_WARN_DEPRECATED is not set +# CONFIG_ENABLE_MUST_CHECK is not set + +# +# HID support +# +CONFIG_HID=y +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=y +CONFIG_HID_GENERIC=y +# +# Special HID drivers +# +CONFIG_HID_A4TECH=y +CONFIG_HID_ACRUX=y +CONFIG_HID_ACRUX_FF=y +CONFIG_HID_APPLE=y +CONFIG_HID_ASUS=y +CONFIG_HID_AUREAL=y +CONFIG_HID_BELKIN=y +CONFIG_HID_CHERRY=y +CONFIG_HID_CHICONY=y +CONFIG_HID_COUGAR=y +CONFIG_HID_MACALLY=y +CONFIG_HID_CMEDIA=y +CONFIG_HID_CYPRESS=y +CONFIG_HID_DRAGONRISE=y +CONFIG_DRAGONRISE_FF=y +CONFIG_HID_EMS_FF=y +CONFIG_HID_ELECOM=y +CONFIG_HID_EZKEY=y +CONFIG_HID_GEMBIRD=y +CONFIG_HID_GFRM=y +CONFIG_HID_KEYTOUCH=y +CONFIG_HID_KYE=y +CONFIG_HID_WALTOP=y +CONFIG_HID_VIEWSONIC=y +CONFIG_HID_GYRATION=y +CONFIG_HID_ICADE=y +CONFIG_HID_ITE=y +CONFIG_HID_JABRA=y +CONFIG_HID_TWINHAN=y +CONFIG_HID_KENSINGTON=y +CONFIG_HID_LCPOWER=y +CONFIG_HID_LED=y +CONFIG_HID_LENOVO=y +CONFIG_HID_LOGITECH=y +CONFIG_HID_LOGITECH_HIDPP=y +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_LOGIWHEELS_FF=y +CONFIG_HID_MAGICMOUSE=y +CONFIG_HID_MALTRON=y +CONFIG_HID_MAYFLASH=y +CONFIG_HID_REDRAGON=y +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MONTEREY=y +CONFIG_HID_MULTITOUCH=y +CONFIG_HID_NTI=y +CONFIG_HID_ORTEK=y +CONFIG_HID_PANTHERLORD=y +CONFIG_PANTHERLORD_FF=y +CONFIG_HID_PETALYNX=y +CONFIG_HID_PICOLCD=y +CONFIG_HID_PICOLCD_BACKLIGHT=y +CONFIG_HID_PICOLCD_LCD=y +CONFIG_HID_PICOLCD_LEDS=y +CONFIG_HID_PLANTRONICS=y +CONFIG_HID_PRIMAX=y +CONFIG_HID_SAITEK=y +CONFIG_HID_SAMSUNG=y +CONFIG_HID_SPEEDLINK=y +CONFIG_HID_STEAM=y +CONFIG_HID_STEELSERIES=y +CONFIG_HID_SUNPLUS=y +CONFIG_HID_RMI=y +CONFIG_HID_GREENASIA=y +CONFIG_GREENASIA_FF=y +CONFIG_HID_SMARTJOYPLUS=y +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_HID_TIVO=y +CONFIG_HID_TOPSEED=y +CONFIG_HID_THINGM=y +CONFIG_HID_THRUSTMASTER=y +CONFIG_THRUSTMASTER_FF=y +CONFIG_HID_UDRAW_PS3=y +CONFIG_HID_WIIMOTE=y +CONFIG_HID_XINMO=y +CONFIG_HID_ZEROPLUS=y +CONFIG_ZEROPLUS_FF=y +CONFIG_HID_ZYDACRON=y +CONFIG_HID_SENSOR_HUB=y +CONFIG_HID_SENSOR_CUSTOM_SENSOR=y +CONFIG_HID_ALPS=y +# end of Special HID drivers +# end of HID support + +CONFIG_KASAN=y +CONFIG_KASAN_STACK_ENABLE=y +CONFIG_KASAN_GENERIC=y +CONFIG_KASAN_OUTLINE=y +CONFIG_FRAME_WARN=0 diff --git a/arch/lkl/kernel/Makefile b/arch/lkl/kernel/Makefile index bacb9572256e56..80a557a92df78f 100644 --- a/arch/lkl/kernel/Makefile +++ b/arch/lkl/kernel/Makefile @@ -1,4 +1,5 @@ extra-y := vmlinux.lds +extra-y += vmlinux-fuzzing-postlink.lds KASAN_SANITIZE_init.o := n KASAN_SANITIZE_stacktrace.o := n diff --git a/arch/lkl/kernel/setup.c b/arch/lkl/kernel/setup.c index 1d40c32cf195c0..29afdd9c8e323a 100644 --- a/arch/lkl/kernel/setup.c +++ b/arch/lkl/kernel/setup.c @@ -200,7 +200,11 @@ static int __init fs_setup(void) { int fd; - fd = sys_open("/init", O_CREAT, 0700); + // Pad '/init' to make sure it's 8 bytes, otherwise KASan would + // emit an error. The kernel's strncpy implementation attempts to read + // 8 bytes at once and, thus, triggers KASan violation for the 6-byte + // string. + fd = sys_open("/init\0\0", O_CREAT, 0700); WARN_ON(fd < 0); sys_close(fd); diff --git a/arch/lkl/kernel/vmlinux-fuzzing-postlink.lds.S b/arch/lkl/kernel/vmlinux-fuzzing-postlink.lds.S new file mode 100644 index 00000000000000..d4e78b5b2e7ed9 --- /dev/null +++ b/arch/lkl/kernel/vmlinux-fuzzing-postlink.lds.S @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define LKL_FUZZING_POSTLINK +#include "vmlinux.lds.S" diff --git a/arch/lkl/kernel/vmlinux.lds.S b/arch/lkl/kernel/vmlinux.lds.S index cf93cc292ea21d..167a5fb5e5ccb7 100644 --- a/arch/lkl/kernel/vmlinux.lds.S +++ b/arch/lkl/kernel/vmlinux.lds.S @@ -1,3 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +// When building LKL with libFuzzer instrumentation (-fsanitize=fuzzer) provide +// an empty linker script to avoid section merging. For additional information +// refer to arch/lkl/Makefile.postlink. +#if !defined(CONFIG_LKL_FUZZING) || defined(LKL_FUZZING_POSTLINK) #include #include #include @@ -67,5 +72,10 @@ SECTIONS STABS_DEBUG DWARF_DEBUG + // Don't include DISCARDS section to build the target with code coverage. +#if !defined(CONFIG_LKL_FUZZING) DISCARDS +#endif // !defined(CONFIG_LKL_FUZZING) } + +#endif // !defined(CONFIG_LKL_FUZZING) || defined(LKL_FUZZING_POSTLINK) diff --git a/arch/lkl/scripts/headers_install.py b/arch/lkl/scripts/headers_install.py index e22457d46a7924..36e965098a3a1c 100755 --- a/arch/lkl/scripts/headers_install.py +++ b/arch/lkl/scripts/headers_install.py @@ -128,6 +128,8 @@ def replace(h): find_headers("arch/lkl/include/uapi/asm/syscalls.h") headers.add("arch/lkl/include/uapi/asm/host_ops.h") +find_headers("include/uapi/linux/uhid.h") +find_headers("include/uapi/linux/input-event-codes.h") if 'LKL_INSTALL_ADDITIONAL_HEADERS' in os.environ: with open(os.environ['LKL_INSTALL_ADDITIONAL_HEADERS'], 'rU') as f: diff --git a/tools/lkl/Makefile b/tools/lkl/Makefile index 849912b63a4af8..a62b31b5b557f8 100644 --- a/tools/lkl/Makefile +++ b/tools/lkl/Makefile @@ -140,6 +140,12 @@ install: headers_install libraries_install programs_install run-tests: ./tests/run.py $(tests) +# Enable libFuzzer fuzzing instrumentation for the LKL fuzzers +$(OUTPUT)fuzzers/%$(EXESUF): LDFLAGS += -fsanitize=fuzzer --coverage + +FUZZ_TARGETS := $(fuzzers-y:%=$(OUTPUT)%$(EXESUF)) +fuzzers: $(FUZZ_TARGETS) + FORCE: ; .PHONY: all clean FORCE run-tests .PHONY: headers_install libraries_install programs_install install diff --git a/tools/lkl/Makefile.autoconf b/tools/lkl/Makefile.autoconf index 56edf8dda0540e..3c0d07ac6835d6 100644 --- a/tools/lkl/Makefile.autoconf +++ b/tools/lkl/Makefile.autoconf @@ -169,7 +169,19 @@ define do_autoconf_llvm $(eval LD_FMT := $(call llvm_target_to_ld_fmt)) endef +define do_autoconf_fuzzing + export KCONFIG := fuzzing_defconfig + export LLVM := 1 + export CROSS_COMPILE := x86_64-linux-gnu + $(eval LLVM := 1) + $(eval CROSS_COMPILE := x86_64-linux-gnu) + $(eval kasan := yes) + $(call set_kernel_config,LKL_FUZZING,y) + $(if $(LKL_LINE_COV),$(call set_kernel_config,LKL_LINE_COV,y)) +endef + define do_autoconf + $(if $(LKL_FUZZING),$(call do_autoconf_fuzzing)) $(if $(LLVM),$(call do_autoconf_llvm),$(call do_autoconf_gnu)) $(eval EXEC_FMT := $(shell echo $(LD_FMT) | cut -d "-" -f1)) $(call set_kernel_config,OUTPUT_FORMAT,\"$(LD_FMT)\") diff --git a/tools/lkl/Targets b/tools/lkl/Targets index 3e424699e1934c..7da425e774da91 100644 --- a/tools/lkl/Targets +++ b/tools/lkl/Targets @@ -26,3 +26,7 @@ progs-y += tests/disk progs-y += tests/disk-vfio-pci progs-y += tests/net-test progs-y += tests/config + +# LKL fuzzers +fuzzers-y += fuzzers/hid/hid-fuzzer + diff --git a/tools/lkl/fuzzers/hid/Build b/tools/lkl/fuzzers/hid/Build new file mode 100644 index 00000000000000..b9fb6a4e0d6ed5 --- /dev/null +++ b/tools/lkl/fuzzers/hid/Build @@ -0,0 +1,2 @@ +hid-fuzzer-y += hid-fuzzer.o +CFLAGS_hid-fuzzer.o += -O0 diff --git a/tools/lkl/fuzzers/hid/hid-fuzzer.c b/tools/lkl/fuzzers/hid/hid-fuzzer.c new file mode 100644 index 00000000000000..fa7669076f4cb8 --- /dev/null +++ b/tools/lkl/fuzzers/hid/hid-fuzzer.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "hid-fuzzer.h" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX_EVENT_SIZE 128 + +#define LKL_CALL(op) lkl_sys_##op + +#define LOG(fmt, ...) \ + do { \ + if (g_log_enabled) {\ + printf(fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +#define LOG_BYTES(title, data, size) \ + do { \ + if (g_log_enabled) {\ + dump_bytes(title, data, size); \ + } \ + } while (0) + +bool g_log_enabled = true; + +void dump_bytes(const char *title, const void *data, size_t size) +{ + const int kBytesPerLine = 16; // 16 bytes per line + const int kCharPerByte = 3; // format string: " %02X" + const int kAddrWidth = 9; // format string: "%08X:" + + const uint8_t *p = (const uint8_t *)data; + char line[kAddrWidth + kCharPerByte * kBytesPerLine + kBytesPerLine + 2]; + + printf("%s:size=%zu\n", title, size); + for (size_t i = 0; i < size; i++) { + size_t col = i % kBytesPerLine; + + if (col == 0) { + memset(line, ' ', sizeof(line)); + snprintf(line, sizeof(line), "%08zX: ", i); + } + + // Hex code display + snprintf(&line[kAddrWidth + col * kCharPerByte], + sizeof(line) - (kAddrWidth + col * kCharPerByte), " %02X", *p); + + // Printable display + line[kAddrWidth + kCharPerByte * kBytesPerLine + col + 1] = isprint(*p) ? *p : '.'; + + // Line ending + if (col == (kBytesPerLine - 1) || i == (size - 1)) { + // This erases the '\0' added by snprintf right after hex code + line[kAddrWidth + (col + 1) * kCharPerByte] = ' '; + + // This adds the '\0' at the end of entire line + line[kAddrWidth + kCharPerByte * kBytesPerLine + kBytesPerLine + 1] = '\0'; + printf("%s\n", line); + } + + p++; + } + printf("\n"); +} + +static int uhid_write(int fd, const struct lkl_uhid_event *ev) +{ + int size = sizeof(*ev); + ssize_t ret = LKL_CALL(write)(fd, (const char *)ev, size); + + if (ret < 0) { + LOG("Cannot write to uhid: %d\n", errno); + return ret; + } else if (ret != sizeof(*ev)) { + LOG("Wrong size written to uhid: %ld != %lu\n", ret, sizeof(ev)); + return -EFAULT; + } else { + return 0; + } +} + +static int uhid_create(int fd, uint16_t vid, uint16_t pid, + const void *data, size_t size) +{ + struct lkl_uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = LKL_UHID_CREATE; + strcpy((char *)ev.u.create.name, "test-uhid-device"); + ev.u.create.rd_data = (void *)data; + ev.u.create.rd_size = size; + ev.u.create.bus = BUS_USB; + ev.u.create.vendor = vid; + ev.u.create.product = pid; + ev.u.create.version = 0; + ev.u.create.country = 0; + + return uhid_write(fd, &ev); +} + +static uint32_t fix_uhid_event_type(uint32_t type) +{ + static uint32_t event_map[] = { + LKL_UHID_INPUT, + LKL_UHID_INPUT2, + LKL_UHID_GET_REPORT_REPLY, + LKL_UHID_SET_REPORT_REPLY + }; + + int index = type % ARRAY_SIZE(event_map); + return event_map[index]; +} + +static const char *event_type_to_name(uint32_t type) +{ + if (type == LKL_UHID_INPUT) + return "UHID_INPUT"; + + if (type == LKL_UHID_INPUT2) + return "UHID_INPUT2"; + + if (type == LKL_UHID_GET_REPORT_REPLY) + return "UHID_GET_REPORT_REPLY"; + + if (type == LKL_UHID_SET_REPORT_REPLY) + return "UHID_SET_REPORT_REPLY"; + + return "UNKNOWN"; +} + +// return consumed data size +static int init_uhid_message(const uint8_t *data, size_t size, struct lkl_uhid_event *ev) +{ + memset(ev, 0, sizeof(*ev)); + + size_t ev_size = MIN(MAX_EVENT_SIZE, sizeof(*ev)); + + ev_size = MIN(ev_size, size); + + if (ev_size < sizeof(ev->type)) // no enough data + return 0; + + memcpy(ev, data, ev_size); + ev->type = fix_uhid_event_type(ev->type); + + return ev_size; +} + +static void uhid_destroy(int fd) +{ + struct lkl_uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_DESTROY; + + uhid_write(fd, &ev); +} + +struct fuzz_data_t { + uint8_t rdesc_sz; + uint32_t msg_sz; + uint16_t vid; + uint16_t pid; + uint8_t payload[0]; +}; + +static int fuzz_data_fixup(struct fuzz_data_t *data, size_t data_sz) +{ + if (data_sz < sizeof(struct fuzz_data_t)) + return 0; + + if (data->rdesc_sz > 255) + data->rdesc_sz = 255; + + size_t sz = data_sz - sizeof(struct fuzz_data_t); + + if (data->rdesc_sz > sz) + data->rdesc_sz = sz; + + sz -= data->rdesc_sz; + + if (data->msg_sz > sz) + data->msg_sz = sz; + + return 1; +} + +static int uhid_fuzz(struct fuzz_data_t *data) +{ + if (data->rdesc_sz == 0) { + LOG("Empty fuzz dat\n"); + return 0; + } + + LOG("VID=%04X, PID=%04X, RDESC: %u bytes, msg: %u byetes\n", + data->vid, data->pid, data->rdesc_sz, data->msg_sz); + + LOG_BYTES("RDESC:", data->payload, data->rdesc_sz); + + int fd = LKL_CALL(open)("/dev/uhid", O_RDWR | O_CLOEXEC, 0); + + if (fd < 0) { + LOG("Cannot open /dev/uhid\n"); + return -1; + } + + int ret = uhid_create(fd, data->vid, data->pid, + data->payload, data->rdesc_sz); + + if (ret) { + close(fd); + LOG("Creating uhid device failed, %d\n", ret); + return -1; + } + + uint8_t *msg_data = data->payload + data->rdesc_sz; + uint32_t msg_data_size = data->msg_sz; + + while (msg_data_size > 0) { + struct lkl_uhid_event ev; + int consume = init_uhid_message(msg_data, msg_data_size, &ev); + + if (consume == 0) { // no more data + break; + } + + LOG("TYPE: %s ", event_type_to_name(ev.type)); + LOG_BYTES("DATA:", msg_data, consume); + msg_data += consume; + msg_data_size -= consume; + + uhid_write(fd, &ev); + } + + uhid_destroy(fd); + LKL_CALL(close)(fd); + return 0; +} + +static int initialize_lkl(void) +{ + if (!g_log_enabled) + lkl_host_ops.print = NULL; + + int ret = lkl_init(&lkl_host_ops); + + if (ret) { + LOG("lkl_init failed\n"); + return -1; + } + + ret = lkl_start_kernel("mem=50M kasan.fault=panic"); + if (ret) { + LOG("lkl_start_kernel failed\n"); + lkl_cleanup(); + return -1; + } + + lkl_mount_fs("sysfs"); + lkl_mount_fs("proc"); + lkl_mount_fs("dev"); + + // This is defined in miscdevice.h which is, however, not visible to + // userspace code. + #define UHID_MINOR 239 + + dev_t dev = makedev(MISC_MAJOR, UHID_MINOR); + int mknod_result = LKL_CALL(mknodat)(AT_FDCWD, "/dev/uhid", + S_IFCHR | 0600 /* S_IRUSR | S_IWUSR */, dev); + + if (mknod_result != 0) { + LOG("Create device file failed\n"); + return -1; + } + + return 0; +} + +void flush_coverage(void) +{ + LOG("Flushing coverage data...\n"); + __llvm_profile_write_file(); + LOG("Done...\n"); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + for (int i = 0; i < *argc; i++) { + if (strcmp((*argv)[i], "-quiet=1") == 0) { + g_log_enabled = false; + break; + } + } + + initialize_lkl(); + + __llvm_profile_initialize_file(); + atexit(flush_coverage); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) +{ + static int iter; + uint8_t data[sizeof(struct fuzz_data_t) + 128] = {0}; + + if (Size > sizeof(data)) + Size = sizeof(data); + + memcpy(data, Data, Size); + + struct fuzz_data_t *fuzz_data = (struct fuzz_data_t *)data; + int success = fuzz_data_fixup(fuzz_data, Size); + + if (success) { + uhid_fuzz(fuzz_data); + + iter++; + if (iter > 1000) { + flush_coverage(); + iter = 0; + } + } + + return 0; +} + diff --git a/tools/lkl/fuzzers/hid/hid-fuzzer.h b/tools/lkl/fuzzers/hid/hid-fuzzer.h new file mode 100644 index 00000000000000..1a61fa61c87d24 --- /dev/null +++ b/tools/lkl/fuzzers/hid/hid-fuzzer.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LKL_HID_FUZZER_H__ +#define __LKL_HID_FUZZER_H__ + +void __llvm_profile_initialize_file(void); +int __llvm_profile_write_file(void); + +#endif // __LKL_HID_FUZZER_H__ diff --git a/tools/lkl/fuzzers/hid/seeds/1 b/tools/lkl/fuzzers/hid/seeds/1 new file mode 100644 index 00000000000000..d9897995467b6a Binary files /dev/null and b/tools/lkl/fuzzers/hid/seeds/1 differ diff --git a/tools/lkl/fuzzers/hid/seeds/2 b/tools/lkl/fuzzers/hid/seeds/2 new file mode 100644 index 00000000000000..572b7839b6833c Binary files /dev/null and b/tools/lkl/fuzzers/hid/seeds/2 differ diff --git a/tools/lkl/fuzzers/hid/seeds/3 b/tools/lkl/fuzzers/hid/seeds/3 new file mode 100644 index 00000000000000..6deff109f3f111 Binary files /dev/null and b/tools/lkl/fuzzers/hid/seeds/3 differ diff --git a/tools/lkl/fuzzers/hid/seeds/4 b/tools/lkl/fuzzers/hid/seeds/4 new file mode 100644 index 00000000000000..f0e7c8d763d6fc Binary files /dev/null and b/tools/lkl/fuzzers/hid/seeds/4 differ diff --git a/tools/lkl/fuzzers/hid/seeds/5 b/tools/lkl/fuzzers/hid/seeds/5 new file mode 100644 index 00000000000000..1ba1fcb6034d20 Binary files /dev/null and b/tools/lkl/fuzzers/hid/seeds/5 differ diff --git a/tools/lkl/fuzzers/hid/seeds/6 b/tools/lkl/fuzzers/hid/seeds/6 new file mode 100644 index 00000000000000..da78f8a83821b5 Binary files /dev/null and b/tools/lkl/fuzzers/hid/seeds/6 differ