-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Kees Cook
authored and
Greg Kroah-Hartman
committed
Jul 18, 2014
1 parent
b142518
commit 0a8adf5
Showing
7 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
* This module provides an interface to trigger and test firmware loading. | ||
* | ||
* It is designed to be used for basic evaluation of the firmware loading | ||
* subsystem (for example when validating firmware verification). It lacks | ||
* any extra dependencies, and will not normally be loaded by the system | ||
* unless explicitly requested by name. | ||
*/ | ||
|
||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
|
||
#include <linux/init.h> | ||
#include <linux/module.h> | ||
#include <linux/printk.h> | ||
#include <linux/firmware.h> | ||
#include <linux/device.h> | ||
#include <linux/fs.h> | ||
#include <linux/miscdevice.h> | ||
#include <linux/slab.h> | ||
#include <linux/uaccess.h> | ||
|
||
static DEFINE_MUTEX(test_fw_mutex); | ||
static const struct firmware *test_firmware; | ||
|
||
static ssize_t test_fw_misc_read(struct file *f, char __user *buf, | ||
size_t size, loff_t *offset) | ||
{ | ||
ssize_t rc = 0; | ||
|
||
mutex_lock(&test_fw_mutex); | ||
if (test_firmware) | ||
rc = simple_read_from_buffer(buf, size, offset, | ||
test_firmware->data, | ||
test_firmware->size); | ||
mutex_unlock(&test_fw_mutex); | ||
return rc; | ||
} | ||
|
||
static const struct file_operations test_fw_fops = { | ||
.owner = THIS_MODULE, | ||
.read = test_fw_misc_read, | ||
}; | ||
|
||
static struct miscdevice test_fw_misc_device = { | ||
.minor = MISC_DYNAMIC_MINOR, | ||
.name = "test_firmware", | ||
.fops = &test_fw_fops, | ||
}; | ||
|
||
static ssize_t trigger_request_store(struct device *dev, | ||
struct device_attribute *attr, | ||
const char *buf, size_t count) | ||
{ | ||
int rc; | ||
char *name; | ||
|
||
name = kzalloc(count + 1, GFP_KERNEL); | ||
if (!name) | ||
return -ENOSPC; | ||
memcpy(name, buf, count); | ||
|
||
pr_info("loading '%s'\n", name); | ||
|
||
mutex_lock(&test_fw_mutex); | ||
release_firmware(test_firmware); | ||
test_firmware = NULL; | ||
rc = request_firmware(&test_firmware, name, dev); | ||
if (rc) | ||
pr_info("load of '%s' failed: %d\n", name, rc); | ||
pr_info("loaded: %zu\n", test_firmware ? test_firmware->size : 0); | ||
mutex_unlock(&test_fw_mutex); | ||
|
||
kfree(name); | ||
|
||
return count; | ||
} | ||
static DEVICE_ATTR_WO(trigger_request); | ||
|
||
static int __init test_firmware_init(void) | ||
{ | ||
int rc; | ||
|
||
rc = misc_register(&test_fw_misc_device); | ||
if (rc) { | ||
pr_err("could not register misc device: %d\n", rc); | ||
return rc; | ||
} | ||
rc = device_create_file(test_fw_misc_device.this_device, | ||
&dev_attr_trigger_request); | ||
if (rc) { | ||
pr_err("could not create sysfs interface: %d\n", rc); | ||
goto dereg; | ||
} | ||
|
||
pr_warn("interface ready\n"); | ||
|
||
return 0; | ||
dereg: | ||
misc_deregister(&test_fw_misc_device); | ||
return rc; | ||
} | ||
|
||
module_init(test_firmware_init); | ||
|
||
static void __exit test_firmware_exit(void) | ||
{ | ||
release_firmware(test_firmware); | ||
device_remove_file(test_fw_misc_device.this_device, | ||
&dev_attr_trigger_request); | ||
misc_deregister(&test_fw_misc_device); | ||
pr_warn("removed interface\n"); | ||
} | ||
|
||
module_exit(test_firmware_exit); | ||
|
||
MODULE_AUTHOR("Kees Cook <keescook@chromium.org>"); | ||
MODULE_LICENSE("GPL"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Makefile for firmware loading selftests | ||
|
||
# No binaries, but make sure arg-less "make" doesn't trigger "run_tests" | ||
all: | ||
|
||
fw_filesystem: | ||
@if /bin/sh ./fw_filesystem.sh ; then \ | ||
echo "fw_filesystem: ok"; \ | ||
else \ | ||
echo "fw_filesystem: [FAIL]"; \ | ||
exit 1; \ | ||
fi | ||
|
||
fw_userhelper: | ||
@if /bin/sh ./fw_userhelper.sh ; then \ | ||
echo "fw_userhelper: ok"; \ | ||
else \ | ||
echo "fw_userhelper: [FAIL]"; \ | ||
exit 1; \ | ||
fi | ||
|
||
run_tests: all fw_filesystem fw_userhelper | ||
|
||
# Nothing to clean up. | ||
clean: | ||
|
||
.PHONY: all clean run_tests fw_filesystem fw_userhelper |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#!/bin/sh | ||
# This validates that the kernel will load firmware out of its list of | ||
# firmware locations on disk. Since the user helper does similar work, | ||
# we reset the custom load directory to a location the user helper doesn't | ||
# know so we can be sure we're not accidentally testing the user helper. | ||
set -e | ||
|
||
modprobe test_firmware | ||
|
||
DIR=/sys/devices/virtual/misc/test_firmware | ||
|
||
OLD_TIMEOUT=$(cat /sys/class/firmware/timeout) | ||
OLD_FWPATH=$(cat /sys/module/firmware_class/parameters/path) | ||
|
||
FWPATH=$(mktemp -d) | ||
FW="$FWPATH/test-firmware.bin" | ||
|
||
test_finish() | ||
{ | ||
echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout | ||
echo -n "$OLD_PATH" >/sys/module/firmware_class/parameters/path | ||
rm -f "$FW" | ||
rmdir "$FWPATH" | ||
} | ||
|
||
trap "test_finish" EXIT | ||
|
||
# Turn down the timeout so failures don't take so long. | ||
echo 1 >/sys/class/firmware/timeout | ||
# Set the kernel search path. | ||
echo -n "$FWPATH" >/sys/module/firmware_class/parameters/path | ||
|
||
# This is an unlikely real-world firmware content. :) | ||
echo "ABCD0123" >"$FW" | ||
|
||
NAME=$(basename "$FW") | ||
|
||
# Request a firmware that doesn't exist, it should fail. | ||
echo -n "nope-$NAME" >"$DIR"/trigger_request | ||
if diff -q "$FW" /dev/test_firmware >/dev/null ; then | ||
echo "$0: firmware was not expected to match" >&2 | ||
exit 1 | ||
else | ||
echo "$0: timeout works" | ||
fi | ||
|
||
# This should succeed via kernel load or will fail after 1 second after | ||
# being handed over to the user helper, which won't find the fw either. | ||
if ! echo -n "$NAME" >"$DIR"/trigger_request ; then | ||
echo "$0: could not trigger request" >&2 | ||
exit 1 | ||
fi | ||
|
||
# Verify the contents are what we expect. | ||
if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then | ||
echo "$0: firmware was not loaded" >&2 | ||
exit 1 | ||
else | ||
echo "$0: filesystem loading works" | ||
fi | ||
|
||
exit 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
#!/bin/sh | ||
# This validates that the kernel will fall back to using the user helper | ||
# to load firmware it can't find on disk itself. We must request a firmware | ||
# that the kernel won't find, and any installed helper (e.g. udev) also | ||
# won't find so that we can do the load ourself manually. | ||
set -e | ||
|
||
modprobe test_firmware | ||
|
||
DIR=/sys/devices/virtual/misc/test_firmware | ||
|
||
OLD_TIMEOUT=$(cat /sys/class/firmware/timeout) | ||
|
||
FWPATH=$(mktemp -d) | ||
FW="$FWPATH/test-firmware.bin" | ||
|
||
test_finish() | ||
{ | ||
echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout | ||
rm -f "$FW" | ||
rmdir "$FWPATH" | ||
} | ||
|
||
load_fw() | ||
{ | ||
local name="$1" | ||
local file="$2" | ||
|
||
# This will block until our load (below) has finished. | ||
echo -n "$name" >"$DIR"/trigger_request & | ||
|
||
# Give kernel a chance to react. | ||
local timeout=10 | ||
while [ ! -e "$DIR"/"$name"/loading ]; do | ||
sleep 0.1 | ||
timeout=$(( $timeout - 1 )) | ||
if [ "$timeout" -eq 0 ]; then | ||
echo "$0: firmware interface never appeared" >&2 | ||
exit 1 | ||
fi | ||
done | ||
|
||
echo 1 >"$DIR"/"$name"/loading | ||
cat "$file" >"$DIR"/"$name"/data | ||
echo 0 >"$DIR"/"$name"/loading | ||
|
||
# Wait for request to finish. | ||
wait | ||
} | ||
|
||
trap "test_finish" EXIT | ||
|
||
# This is an unlikely real-world firmware content. :) | ||
echo "ABCD0123" >"$FW" | ||
NAME=$(basename "$FW") | ||
|
||
# Test failure when doing nothing (timeout works). | ||
echo 1 >/sys/class/firmware/timeout | ||
echo -n "$NAME" >"$DIR"/trigger_request | ||
if diff -q "$FW" /dev/test_firmware >/dev/null ; then | ||
echo "$0: firmware was not expected to match" >&2 | ||
exit 1 | ||
else | ||
echo "$0: timeout works" | ||
fi | ||
|
||
# Put timeout high enough for us to do work but not so long that failures | ||
# slow down this test too much. | ||
echo 4 >/sys/class/firmware/timeout | ||
|
||
# Load this script instead of the desired firmware. | ||
load_fw "$NAME" "$0" | ||
if diff -q "$FW" /dev/test_firmware >/dev/null ; then | ||
echo "$0: firmware was not expected to match" >&2 | ||
exit 1 | ||
else | ||
echo "$0: firmware comparison works" | ||
fi | ||
|
||
# Do a proper load, which should work correctly. | ||
load_fw "$NAME" "$FW" | ||
if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then | ||
echo "$0: firmware was not loaded" >&2 | ||
exit 1 | ||
else | ||
echo "$0: user helper firmware loading works" | ||
fi | ||
|
||
exit 0 |