Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

test: add firmware_class loader test

This provides a simple interface to trigger the firmware_class loader
to test built-in, filesystem, and user helper modes. Additionally adds
tests via the new interface to the selftests tree.

Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Kees Cook and committed by
Greg Kroah-Hartman
0a8adf58 b1425189

+310
+13
lib/Kconfig.debug
··· 1649 1649 1650 1650 If unsure, say N. 1651 1651 1652 + config TEST_FIRMWARE 1653 + tristate "Test firmware loading via userspace interface" 1654 + default n 1655 + depends on FW_LOADER 1656 + help 1657 + This builds the "test_firmware" module that creates a userspace 1658 + interface for testing firmware loading. This can be used to 1659 + control the triggering of firmware loading without needing an 1660 + actual firmware-using device. The contents can be rechecked by 1661 + userspace. 1662 + 1663 + If unsure, say N. 1664 + 1652 1665 source "samples/Kconfig" 1653 1666 1654 1667 source "lib/Kconfig.kgdb"
+1
lib/Makefile
··· 34 34 obj-$(CONFIG_TEST_MODULE) += test_module.o 35 35 obj-$(CONFIG_TEST_USER_COPY) += test_user_copy.o 36 36 obj-$(CONFIG_TEST_BPF) += test_bpf.o 37 + obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o 37 38 38 39 ifeq ($(CONFIG_DEBUG_KOBJECT),y) 39 40 CFLAGS_kobject.o += -DDEBUG
+117
lib/test_firmware.c
··· 1 + /* 2 + * This module provides an interface to trigger and test firmware loading. 3 + * 4 + * It is designed to be used for basic evaluation of the firmware loading 5 + * subsystem (for example when validating firmware verification). It lacks 6 + * any extra dependencies, and will not normally be loaded by the system 7 + * unless explicitly requested by name. 8 + */ 9 + 10 + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 11 + 12 + #include <linux/init.h> 13 + #include <linux/module.h> 14 + #include <linux/printk.h> 15 + #include <linux/firmware.h> 16 + #include <linux/device.h> 17 + #include <linux/fs.h> 18 + #include <linux/miscdevice.h> 19 + #include <linux/slab.h> 20 + #include <linux/uaccess.h> 21 + 22 + static DEFINE_MUTEX(test_fw_mutex); 23 + static const struct firmware *test_firmware; 24 + 25 + static ssize_t test_fw_misc_read(struct file *f, char __user *buf, 26 + size_t size, loff_t *offset) 27 + { 28 + ssize_t rc = 0; 29 + 30 + mutex_lock(&test_fw_mutex); 31 + if (test_firmware) 32 + rc = simple_read_from_buffer(buf, size, offset, 33 + test_firmware->data, 34 + test_firmware->size); 35 + mutex_unlock(&test_fw_mutex); 36 + return rc; 37 + } 38 + 39 + static const struct file_operations test_fw_fops = { 40 + .owner = THIS_MODULE, 41 + .read = test_fw_misc_read, 42 + }; 43 + 44 + static struct miscdevice test_fw_misc_device = { 45 + .minor = MISC_DYNAMIC_MINOR, 46 + .name = "test_firmware", 47 + .fops = &test_fw_fops, 48 + }; 49 + 50 + static ssize_t trigger_request_store(struct device *dev, 51 + struct device_attribute *attr, 52 + const char *buf, size_t count) 53 + { 54 + int rc; 55 + char *name; 56 + 57 + name = kzalloc(count + 1, GFP_KERNEL); 58 + if (!name) 59 + return -ENOSPC; 60 + memcpy(name, buf, count); 61 + 62 + pr_info("loading '%s'\n", name); 63 + 64 + mutex_lock(&test_fw_mutex); 65 + release_firmware(test_firmware); 66 + test_firmware = NULL; 67 + rc = request_firmware(&test_firmware, name, dev); 68 + if (rc) 69 + pr_info("load of '%s' failed: %d\n", name, rc); 70 + pr_info("loaded: %zu\n", test_firmware ? test_firmware->size : 0); 71 + mutex_unlock(&test_fw_mutex); 72 + 73 + kfree(name); 74 + 75 + return count; 76 + } 77 + static DEVICE_ATTR_WO(trigger_request); 78 + 79 + static int __init test_firmware_init(void) 80 + { 81 + int rc; 82 + 83 + rc = misc_register(&test_fw_misc_device); 84 + if (rc) { 85 + pr_err("could not register misc device: %d\n", rc); 86 + return rc; 87 + } 88 + rc = device_create_file(test_fw_misc_device.this_device, 89 + &dev_attr_trigger_request); 90 + if (rc) { 91 + pr_err("could not create sysfs interface: %d\n", rc); 92 + goto dereg; 93 + } 94 + 95 + pr_warn("interface ready\n"); 96 + 97 + return 0; 98 + dereg: 99 + misc_deregister(&test_fw_misc_device); 100 + return rc; 101 + } 102 + 103 + module_init(test_firmware_init); 104 + 105 + static void __exit test_firmware_exit(void) 106 + { 107 + release_firmware(test_firmware); 108 + device_remove_file(test_fw_misc_device.this_device, 109 + &dev_attr_trigger_request); 110 + misc_deregister(&test_fw_misc_device); 111 + pr_warn("removed interface\n"); 112 + } 113 + 114 + module_exit(test_firmware_exit); 115 + 116 + MODULE_AUTHOR("Kees Cook <keescook@chromium.org>"); 117 + MODULE_LICENSE("GPL");
+1
tools/testing/selftests/Makefile
··· 11 11 TARGETS += powerpc 12 12 TARGETS += user 13 13 TARGETS += sysctl 14 + TARGETS += firmware 14 15 15 16 all: 16 17 for TARGET in $(TARGETS); do \
+27
tools/testing/selftests/firmware/Makefile
··· 1 + # Makefile for firmware loading selftests 2 + 3 + # No binaries, but make sure arg-less "make" doesn't trigger "run_tests" 4 + all: 5 + 6 + fw_filesystem: 7 + @if /bin/sh ./fw_filesystem.sh ; then \ 8 + echo "fw_filesystem: ok"; \ 9 + else \ 10 + echo "fw_filesystem: [FAIL]"; \ 11 + exit 1; \ 12 + fi 13 + 14 + fw_userhelper: 15 + @if /bin/sh ./fw_userhelper.sh ; then \ 16 + echo "fw_userhelper: ok"; \ 17 + else \ 18 + echo "fw_userhelper: [FAIL]"; \ 19 + exit 1; \ 20 + fi 21 + 22 + run_tests: all fw_filesystem fw_userhelper 23 + 24 + # Nothing to clean up. 25 + clean: 26 + 27 + .PHONY: all clean run_tests fw_filesystem fw_userhelper
+62
tools/testing/selftests/firmware/fw_filesystem.sh
··· 1 + #!/bin/sh 2 + # This validates that the kernel will load firmware out of its list of 3 + # firmware locations on disk. Since the user helper does similar work, 4 + # we reset the custom load directory to a location the user helper doesn't 5 + # know so we can be sure we're not accidentally testing the user helper. 6 + set -e 7 + 8 + modprobe test_firmware 9 + 10 + DIR=/sys/devices/virtual/misc/test_firmware 11 + 12 + OLD_TIMEOUT=$(cat /sys/class/firmware/timeout) 13 + OLD_FWPATH=$(cat /sys/module/firmware_class/parameters/path) 14 + 15 + FWPATH=$(mktemp -d) 16 + FW="$FWPATH/test-firmware.bin" 17 + 18 + test_finish() 19 + { 20 + echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout 21 + echo -n "$OLD_PATH" >/sys/module/firmware_class/parameters/path 22 + rm -f "$FW" 23 + rmdir "$FWPATH" 24 + } 25 + 26 + trap "test_finish" EXIT 27 + 28 + # Turn down the timeout so failures don't take so long. 29 + echo 1 >/sys/class/firmware/timeout 30 + # Set the kernel search path. 31 + echo -n "$FWPATH" >/sys/module/firmware_class/parameters/path 32 + 33 + # This is an unlikely real-world firmware content. :) 34 + echo "ABCD0123" >"$FW" 35 + 36 + NAME=$(basename "$FW") 37 + 38 + # Request a firmware that doesn't exist, it should fail. 39 + echo -n "nope-$NAME" >"$DIR"/trigger_request 40 + if diff -q "$FW" /dev/test_firmware >/dev/null ; then 41 + echo "$0: firmware was not expected to match" >&2 42 + exit 1 43 + else 44 + echo "$0: timeout works" 45 + fi 46 + 47 + # This should succeed via kernel load or will fail after 1 second after 48 + # being handed over to the user helper, which won't find the fw either. 49 + if ! echo -n "$NAME" >"$DIR"/trigger_request ; then 50 + echo "$0: could not trigger request" >&2 51 + exit 1 52 + fi 53 + 54 + # Verify the contents are what we expect. 55 + if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then 56 + echo "$0: firmware was not loaded" >&2 57 + exit 1 58 + else 59 + echo "$0: filesystem loading works" 60 + fi 61 + 62 + exit 0
+89
tools/testing/selftests/firmware/fw_userhelper.sh
··· 1 + #!/bin/sh 2 + # This validates that the kernel will fall back to using the user helper 3 + # to load firmware it can't find on disk itself. We must request a firmware 4 + # that the kernel won't find, and any installed helper (e.g. udev) also 5 + # won't find so that we can do the load ourself manually. 6 + set -e 7 + 8 + modprobe test_firmware 9 + 10 + DIR=/sys/devices/virtual/misc/test_firmware 11 + 12 + OLD_TIMEOUT=$(cat /sys/class/firmware/timeout) 13 + 14 + FWPATH=$(mktemp -d) 15 + FW="$FWPATH/test-firmware.bin" 16 + 17 + test_finish() 18 + { 19 + echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout 20 + rm -f "$FW" 21 + rmdir "$FWPATH" 22 + } 23 + 24 + load_fw() 25 + { 26 + local name="$1" 27 + local file="$2" 28 + 29 + # This will block until our load (below) has finished. 30 + echo -n "$name" >"$DIR"/trigger_request & 31 + 32 + # Give kernel a chance to react. 33 + local timeout=10 34 + while [ ! -e "$DIR"/"$name"/loading ]; do 35 + sleep 0.1 36 + timeout=$(( $timeout - 1 )) 37 + if [ "$timeout" -eq 0 ]; then 38 + echo "$0: firmware interface never appeared" >&2 39 + exit 1 40 + fi 41 + done 42 + 43 + echo 1 >"$DIR"/"$name"/loading 44 + cat "$file" >"$DIR"/"$name"/data 45 + echo 0 >"$DIR"/"$name"/loading 46 + 47 + # Wait for request to finish. 48 + wait 49 + } 50 + 51 + trap "test_finish" EXIT 52 + 53 + # This is an unlikely real-world firmware content. :) 54 + echo "ABCD0123" >"$FW" 55 + NAME=$(basename "$FW") 56 + 57 + # Test failure when doing nothing (timeout works). 58 + echo 1 >/sys/class/firmware/timeout 59 + echo -n "$NAME" >"$DIR"/trigger_request 60 + if diff -q "$FW" /dev/test_firmware >/dev/null ; then 61 + echo "$0: firmware was not expected to match" >&2 62 + exit 1 63 + else 64 + echo "$0: timeout works" 65 + fi 66 + 67 + # Put timeout high enough for us to do work but not so long that failures 68 + # slow down this test too much. 69 + echo 4 >/sys/class/firmware/timeout 70 + 71 + # Load this script instead of the desired firmware. 72 + load_fw "$NAME" "$0" 73 + if diff -q "$FW" /dev/test_firmware >/dev/null ; then 74 + echo "$0: firmware was not expected to match" >&2 75 + exit 1 76 + else 77 + echo "$0: firmware comparison works" 78 + fi 79 + 80 + # Do a proper load, which should work correctly. 81 + load_fw "$NAME" "$FW" 82 + if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then 83 + echo "$0: firmware was not loaded" >&2 84 + exit 1 85 + else 86 + echo "$0: user helper firmware loading works" 87 + fi 88 + 89 + exit 0