mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
thunderbolt: test: add KUnit regression tests for XDomain property parser
Add three KUnit cases that exercise the defects fixed by the sibling
commits in this series by feeding crafted XDomain property blocks to
tb_property_parse_dir():
tb_test_property_parse_u32_wrap - entry->value = 0xffffff00 and
entry->length = 0x100 so their u32 sum 0x100000000 wraps to 0
under the block_len guard; without the fix the subsequent
parse_dwdata() reads attacker-directed OOB memory.
tb_test_property_parse_recursion - two DIRECTORY entries pointing
at each other, driving __tb_property_parse_dir() recursion;
without the fix the kernel stack is exhausted.
tb_test_property_parse_dir_len_underflow - a DIRECTORY entry with
length < 4 placed near the end of the block so the non-root UUID
kmemdup of 4 dwords from dir_offset reads OOB before the later
content_len = dir_len - 4 underflow path is reached.
Each test asserts tb_property_parse_dir() returns NULL on the
crafted input. With CONFIG_KASAN=y, running these on the pre-fix
kernel produces an oops inside __tb_property_parse_dir or its
callees: u32_wrap takes a page fault on the KASAN shadow lookup for
the wild ~16 GiB OOB offset; recursion trips a KASAN out-of-bounds
report in __unwind_start as the per-task kernel stack is consumed;
dir_len_underflow trips a KASAN slab-out-of-bounds report in
kmemdup_noprof reading 16 bytes past the 28-byte block. Post-fix
they pass cleanly.
The crafted blocks are populated by writing u32 dwords directly,
matching the existing root_directory[] style used elsewhere in
this file rather than imposing a private struct overlay.
Run with:
./tools/testing/kunit/kunit.py run --arch=x86_64 \
--kconfig_add CONFIG_PCI=y --kconfig_add CONFIG_NVMEM=y \
--kconfig_add CONFIG_USB4=y --kconfig_add CONFIG_USB4_KUNIT_TEST=y \
--kconfig_add CONFIG_KASAN=y 'thunderbolt.tb_test_property_parse_*'
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
This commit is contained in:
parent
cf0c38ee55
commit
c12b5ee30f
|
|
@ -2852,7 +2852,133 @@ static void tb_test_property_copy(struct kunit *test)
|
|||
tb_property_free_dir(src);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reproducers for three memory-safety defects in
|
||||
* drivers/thunderbolt/property.c reached from a crafted XDomain
|
||||
* PROPERTIES_RESPONSE payload. Without the fix these trip KASAN or
|
||||
* smash the kernel stack; with the fix each returns NULL cleanly.
|
||||
*
|
||||
* The on-wire entry layout matches struct tb_property_entry in
|
||||
* property.c (private to that translation unit): u32 key_hi, u32
|
||||
* key_lo, then a packed u32 = (type << 24) | (reserved << 16) |
|
||||
* length, then u32 value. Each entry is 4 dwords.
|
||||
*/
|
||||
|
||||
static void tb_test_property_parse_u32_wrap(struct kunit *test)
|
||||
{
|
||||
/*
|
||||
* 0x102 dwords: enough for the entry's length field (0x100) to
|
||||
* pass the "entry->length > block_len" gate so the wrap check
|
||||
* is actually exercised. parse_dwdata's downstream OOB read
|
||||
* lands ~16 GiB past the allocation regardless.
|
||||
*/
|
||||
u32 *block = kunit_kzalloc(test, 0x102 * sizeof(u32), GFP_KERNEL);
|
||||
struct tb_property_dir *dir;
|
||||
|
||||
KUNIT_ASSERT_NOT_NULL(test, block);
|
||||
|
||||
block[0] = 0x55584401; /* "UXD" v1 magic */
|
||||
block[1] = 0x00000004; /* Root directory length: one entry */
|
||||
|
||||
/*
|
||||
* DATA entry whose value 0xffffff00 + length 0x100 wrap to 0
|
||||
* in u32, passing the sum <= block_len guard even though the
|
||||
* real offset is far past the allocation.
|
||||
*/
|
||||
block[2] = 0x61616161; /* key_hi */
|
||||
block[3] = 0x61616161; /* key_lo */
|
||||
block[4] = 0x64000100; /* type=DATA, reserved=0, length=0x100 */
|
||||
block[5] = 0xffffff00; /* value */
|
||||
|
||||
dir = tb_property_parse_dir(block, 0x102);
|
||||
KUNIT_EXPECT_NULL(test, dir);
|
||||
tb_property_free_dir(dir);
|
||||
}
|
||||
|
||||
static void tb_test_property_parse_recursion(struct kunit *test)
|
||||
{
|
||||
/*
|
||||
* 10 dwords: rootdir header (2) + parent DIRECTORY entry (4) +
|
||||
* the child entry that lives at dir_offset(2) + UUID(4) = 6,
|
||||
* occupying block[6..9]. Each recursive level re-reads the
|
||||
* same block[6..9] as its first child entry, which is itself
|
||||
* a DIRECTORY pointing at offset 2.
|
||||
*/
|
||||
u32 *block = kunit_kzalloc(test, 10 * sizeof(u32), GFP_KERNEL);
|
||||
struct tb_property_dir *dir;
|
||||
|
||||
KUNIT_ASSERT_NOT_NULL(test, block);
|
||||
|
||||
block[0] = 0x55584401; /* "UXD" v1 magic */
|
||||
block[1] = 0x00000004; /* Root directory length: one entry */
|
||||
|
||||
/*
|
||||
* DIRECTORY entry pointing at dir_offset = 2 with length = 8.
|
||||
* Non-root parse derives content_offset = 6, content_len = 4,
|
||||
* nentries = 1. block[6..9] is read both as the parent's UUID
|
||||
* (kmemdup'd into dir->uuid) and as the single child entry --
|
||||
* which is itself a DIRECTORY pointing at offset 2, so the
|
||||
* recursion never terminates and the kernel stack is exhausted.
|
||||
*/
|
||||
block[2] = 0x61616161; /* key_hi */
|
||||
block[3] = 0x61616161; /* key_lo */
|
||||
block[4] = 0x44000008; /* type=DIRECTORY, reserved=0, length=8 */
|
||||
block[5] = 0x00000002; /* value = dir_offset */
|
||||
|
||||
block[6] = 0x62626262; /* doubles as UUID dword 0 / child key_hi */
|
||||
block[7] = 0x62626262; /* doubles as UUID dword 1 / child key_lo */
|
||||
block[8] = 0x44000008; /* type=DIRECTORY, reserved=0, length=8 */
|
||||
block[9] = 0x00000002; /* value = dir_offset (back at parent) */
|
||||
|
||||
dir = tb_property_parse_dir(block, 10);
|
||||
KUNIT_EXPECT_NULL(test, dir);
|
||||
tb_property_free_dir(dir);
|
||||
}
|
||||
|
||||
static void tb_test_property_parse_dir_len_underflow(struct kunit *test)
|
||||
{
|
||||
/*
|
||||
* Allocate exactly 7 dwords (28 bytes) so the kmalloc-32 chunk
|
||||
* leaves a 4-byte slab redzone tail that KASAN-Generic can flag.
|
||||
* With block_len = 7, dir_offset = 4, dir_len = 3, the non-root
|
||||
* UUID kmemdup reads 16 bytes from byte 16, so bytes 28..31 fall
|
||||
* in the redzone and trip a KASAN slab-out-of-bounds report on
|
||||
* the pre-fix kernel. Sizing the buffer at a power of two (32,
|
||||
* 64, ...) puts the over-read into the slab cache tail where
|
||||
* KASAN's generic shadow does not flag it, and the test reduces
|
||||
* to the downstream content_len = dir_len - 4 underflow path
|
||||
* which also returns NULL.
|
||||
*/
|
||||
u32 *block = kunit_kzalloc(test, 7 * sizeof(u32), GFP_KERNEL);
|
||||
struct tb_property_dir *dir;
|
||||
|
||||
KUNIT_ASSERT_NOT_NULL(test, block);
|
||||
|
||||
block[0] = 0x55584401; /* "UXD" v1 magic */
|
||||
block[1] = 0x00000004; /* Root directory length: one entry */
|
||||
|
||||
/*
|
||||
* DIRECTORY entry with length = 3 pointing at dir_offset = 4.
|
||||
* tb_property_entry_valid() permits value(4) + length(3) <=
|
||||
* block_len(7). Non-root parse begins with a kmemdup of 4
|
||||
* dwords from dir_offset for the UUID; that read runs past the
|
||||
* 28-byte allocation before the dir_len < 4 reject would fire.
|
||||
*/
|
||||
block[2] = 0x61616161; /* key_hi */
|
||||
block[3] = 0x61616161; /* key_lo */
|
||||
block[4] = 0x44000003; /* type=DIRECTORY, reserved=0, length=3 */
|
||||
block[5] = 0x00000004; /* value = dir_offset */
|
||||
/* block[6] is the start of the four UUID dwords; block[7..] is OOB. */
|
||||
|
||||
dir = tb_property_parse_dir(block, 7);
|
||||
KUNIT_EXPECT_NULL(test, dir);
|
||||
tb_property_free_dir(dir);
|
||||
}
|
||||
|
||||
static struct kunit_case tb_test_cases[] = {
|
||||
KUNIT_CASE(tb_test_property_parse_u32_wrap),
|
||||
KUNIT_CASE(tb_test_property_parse_recursion),
|
||||
KUNIT_CASE(tb_test_property_parse_dir_len_underflow),
|
||||
KUNIT_CASE(tb_test_path_basic),
|
||||
KUNIT_CASE(tb_test_path_not_connected_walk),
|
||||
KUNIT_CASE(tb_test_path_single_hop_walk),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user