HID: roccat: add KUnit test for kone profile-index bounds

Drive kone_keep_values_up_to_date() with a crafted switch-profile event;
an out-of-range value reads past profiles[] (KASAN slab-out-of-bounds on
an unpatched tree). A benign control with an in-range value exercises the
same path. The test object is sized to end at profiles[] so the over-read
lands in the KASAN redzone.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
This commit is contained in:
Michael Bommarito
2026-06-17 23:00:36 -04:00
committed by Jiri Kosina
parent 43fae42628
commit 7a5f1acd06
2 changed files with 66 additions and 0 deletions

View File

@@ -1079,6 +1079,15 @@ config HID_ROCCAT
Say Y here if you have a Roccat mouse or keyboard and want
support for its special functionalities.
config HID_ROCCAT_KONE_KUNIT_TEST
bool "KUnit tests for the Roccat Kone driver" if !KUNIT_ALL_TESTS
depends on HID_ROCCAT=y && KUNIT=y
default KUNIT_ALL_TESTS
help
Enable the KUnit regression tests for the Roccat Kone driver,
covering bounds checking of device-supplied profile indices.
If unsure, say N.
config HID_SAITEK
tristate "Saitek (Mad Catz) non-fully HID-compliant devices"
help

View File

@@ -919,3 +919,60 @@ module_exit(kone_exit);
MODULE_AUTHOR("Stefan Achatz");
MODULE_DESCRIPTION("USB Roccat Kone driver");
MODULE_LICENSE("GPL v2");
#if IS_ENABLED(CONFIG_HID_ROCCAT_KONE_KUNIT_TEST)
#include <kunit/test.h>
/*
* Regression test for the out-of-bounds read in
* kone_keep_values_up_to_date(): a malicious USB device sends a
* "switch profile" HID event (event == kone_mouse_event_switch_profile)
* with an attacker-chosen value in 0..255, which is used unbounded as
* profiles[value - 1]. On an unpatched kernel the attack case triggers a
* KASAN slab-out-of-bounds read; the fix must leave actual_dpi unchanged.
*/
static void kone_profile_index_oob_test(struct kunit *test)
{
struct kone_device *kone;
struct kone_mouse_event ev = {};
/*
* Allocate only up to the end of profiles[] so that any index past
* the 5-element array is IMMEDIATELY out of bounds and lands in the
* KASAN redzone (a far over-read would hit unrelated valid memory and
* escape KASAN).
*/
size_t sz = offsetof(struct kone_device, profiles) +
sizeof(kone->profiles);
kone = kunit_kzalloc(test, sz, GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, kone);
kone->profiles[0].startup_dpi = 0x42;
/* benign control: a valid in-range value drives the SAME path and
* must succeed (proves the trigger reaches the real code).
*/
ev.event = kone_mouse_event_switch_profile;
ev.value = 1;
kone_keep_values_up_to_date(kone, &ev);
KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42);
/* attack: value == ARRAY_SIZE(profiles) + 1 reads profiles[5], one
* element past the array end -> KASAN slab-out-of-bounds read on an
* unpatched kernel. The fix must reject it (actual_dpi unchanged).
*/
ev.value = ARRAY_SIZE(kone->profiles) + 1;
kone_keep_values_up_to_date(kone, &ev);
KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42);
}
static struct kunit_case kone_test_cases[] = {
KUNIT_CASE(kone_profile_index_oob_test),
{}
};
static struct kunit_suite kone_test_suite = {
.name = "hid-roccat-kone",
.test_cases = kone_test_cases,
};
kunit_test_suite(kone_test_suite);
#endif /* CONFIG_HID_ROCCAT_KONE_KUNIT_TEST */