linux/lib/tests/randstruct_kunit.c
Kees Cook f55aef7e0c lib/tests: randstruct: Add deep function pointer layout test
The recent fix in commit c2ea09b193d2 ("randstruct: gcc-plugin: Remove
bogus void member") has fixed another issue: it was not always detecting
composite structures made only of function pointers and structures of
function pointers. Add a test for this case, and break out the layout
tests since this issue is actually a problem for Clang as well[1].

Link: https://github.com/llvm/llvm-project/issues/138355 [1]
Link: https://lore.kernel.org/r/20250502224116.work.591-kees@kernel.org
Signed-off-by: Kees Cook <kees@kernel.org>
2025-05-08 09:42:40 -07:00

334 lines
8.9 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Test cases for struct randomization, i.e. CONFIG_RANDSTRUCT=y.
*
* For example, see:
* "Running tests with kunit_tool" at Documentation/dev-tools/kunit/start.rst
* ./tools/testing/kunit/kunit.py run randstruct [--raw_output] \
* [--make_option LLVM=1] \
* --kconfig_add CONFIG_RANDSTRUCT_FULL=y
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <kunit/test.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#define DO_MANY_MEMBERS(macro, args...) \
macro(a, args) \
macro(b, args) \
macro(c, args) \
macro(d, args) \
macro(e, args) \
macro(f, args) \
macro(g, args) \
macro(h, args)
#define do_enum(x, ignored) MEMBER_NAME_ ## x,
enum randstruct_member_names {
DO_MANY_MEMBERS(do_enum)
MEMBER_NAME_MAX,
};
/* Make sure the macros are working: want 8 test members. */
_Static_assert(MEMBER_NAME_MAX == 8, "Number of test members changed?!");
/* This is an unsigned long member to match the function pointer size */
#define unsigned_long_member(x, ignored) unsigned long x;
struct randstruct_untouched {
DO_MANY_MEMBERS(unsigned_long_member)
};
/* Struct explicitly marked with __randomize_layout. */
struct randstruct_shuffled {
DO_MANY_MEMBERS(unsigned_long_member)
} __randomize_layout;
#undef unsigned_long_member
/* Struct implicitly randomized from being all func ptrs. */
#define func_member(x, ignored) size_t (*x)(int);
struct randstruct_funcs_untouched {
DO_MANY_MEMBERS(func_member)
} __no_randomize_layout;
struct randstruct_funcs_shuffled {
DO_MANY_MEMBERS(func_member)
};
#define func_body(x, ignored) \
static noinline size_t func_##x(int arg) \
{ \
return offsetof(struct randstruct_funcs_untouched, x); \
}
DO_MANY_MEMBERS(func_body)
/* Various mixed types. */
#define mixed_members \
bool a; \
short b; \
unsigned int c __aligned(16); \
size_t d; \
char e; \
u64 f; \
union { \
struct randstruct_shuffled shuffled; \
uintptr_t g; \
}; \
union { \
void *ptr; \
char h; \
};
struct randstruct_mixed_untouched {
mixed_members
};
struct randstruct_mixed_shuffled {
mixed_members
} __randomize_layout;
#undef mixed_members
struct contains_randstruct_untouched {
int before;
struct randstruct_untouched untouched;
int after;
};
struct contains_randstruct_shuffled {
int before;
struct randstruct_shuffled shuffled;
int after;
};
struct contains_func_untouched {
struct randstruct_funcs_shuffled inner;
DO_MANY_MEMBERS(func_member)
} __no_randomize_layout;
struct contains_func_shuffled {
struct randstruct_funcs_shuffled inner;
DO_MANY_MEMBERS(func_member)
};
#undef func_member
#define check_mismatch(x, untouched, shuffled) \
if (offsetof(untouched, x) != offsetof(shuffled, x)) \
mismatches++; \
kunit_info(test, #shuffled "::" #x " @ %zu (vs %zu)\n", \
offsetof(shuffled, x), \
offsetof(untouched, x)); \
#define check_pair(outcome, untouched, shuffled, checker...) \
mismatches = 0; \
DO_MANY_MEMBERS(checker, untouched, shuffled) \
kunit_info(test, "Differing " #untouched " vs " #shuffled " member positions: %d\n", \
mismatches); \
KUNIT_##outcome##_MSG(test, mismatches, 0, \
#untouched " vs " #shuffled " layouts: unlucky or broken?\n");
static void randstruct_layout_same(struct kunit *test)
{
int mismatches;
check_pair(EXPECT_EQ, struct randstruct_untouched, struct randstruct_untouched,
check_mismatch)
check_pair(EXPECT_GT, struct randstruct_untouched, struct randstruct_shuffled,
check_mismatch)
}
static void randstruct_layout_mixed(struct kunit *test)
{
int mismatches;
check_pair(EXPECT_EQ, struct randstruct_mixed_untouched, struct randstruct_mixed_untouched,
check_mismatch)
check_pair(EXPECT_GT, struct randstruct_mixed_untouched, struct randstruct_mixed_shuffled,
check_mismatch)
}
static void randstruct_layout_fptr(struct kunit *test)
{
int mismatches;
check_pair(EXPECT_EQ, struct randstruct_untouched, struct randstruct_untouched,
check_mismatch)
check_pair(EXPECT_GT, struct randstruct_untouched, struct randstruct_funcs_shuffled,
check_mismatch)
check_pair(EXPECT_GT, struct randstruct_funcs_untouched, struct randstruct_funcs_shuffled,
check_mismatch)
}
#define check_mismatch_prefixed(x, prefix, untouched, shuffled) \
check_mismatch(prefix.x, untouched, shuffled)
static void randstruct_layout_fptr_deep(struct kunit *test)
{
int mismatches;
if (IS_ENABLED(CONFIG_CC_IS_CLANG))
kunit_skip(test, "Clang randstruct misses inner functions: https://github.com/llvm/llvm-project/issues/138355");
check_pair(EXPECT_EQ, struct contains_func_untouched, struct contains_func_untouched,
check_mismatch_prefixed, inner)
check_pair(EXPECT_GT, struct contains_func_untouched, struct contains_func_shuffled,
check_mismatch_prefixed, inner)
}
#undef check_pair
#undef check_mismatch
#define check_mismatch(x, ignore) \
KUNIT_EXPECT_EQ_MSG(test, untouched->x, shuffled->x, \
"Mismatched member value in %s initializer\n", \
name);
static void test_check_init(struct kunit *test, const char *name,
struct randstruct_untouched *untouched,
struct randstruct_shuffled *shuffled)
{
DO_MANY_MEMBERS(check_mismatch)
}
static void test_check_mixed_init(struct kunit *test, const char *name,
struct randstruct_mixed_untouched *untouched,
struct randstruct_mixed_shuffled *shuffled)
{
DO_MANY_MEMBERS(check_mismatch)
}
#undef check_mismatch
#define check_mismatch(x, ignore) \
KUNIT_EXPECT_EQ_MSG(test, untouched->untouched.x, \
shuffled->shuffled.x, \
"Mismatched member value in %s initializer\n", \
name);
static void test_check_contained_init(struct kunit *test, const char *name,
struct contains_randstruct_untouched *untouched,
struct contains_randstruct_shuffled *shuffled)
{
DO_MANY_MEMBERS(check_mismatch)
}
#undef check_mismatch
#define check_mismatch(x, ignore) \
KUNIT_EXPECT_PTR_EQ_MSG(test, untouched->x, shuffled->x, \
"Mismatched member value in %s initializer\n", \
name);
static void test_check_funcs_init(struct kunit *test, const char *name,
struct randstruct_funcs_untouched *untouched,
struct randstruct_funcs_shuffled *shuffled)
{
DO_MANY_MEMBERS(check_mismatch)
}
#undef check_mismatch
static void randstruct_initializers(struct kunit *test)
{
#define init_members \
.a = 1, \
.b = 3, \
.c = 5, \
.d = 7, \
.e = 11, \
.f = 13, \
.g = 17, \
.h = 19,
struct randstruct_untouched untouched = {
init_members
};
struct randstruct_shuffled shuffled = {
init_members
};
struct randstruct_mixed_untouched mixed_untouched = {
init_members
};
struct randstruct_mixed_shuffled mixed_shuffled = {
init_members
};
struct contains_randstruct_untouched contains_untouched = {
.untouched = {
init_members
},
};
struct contains_randstruct_shuffled contains_shuffled = {
.shuffled = {
init_members
},
};
#define func_member(x, ignored) \
.x = func_##x,
struct randstruct_funcs_untouched funcs_untouched = {
DO_MANY_MEMBERS(func_member)
};
struct randstruct_funcs_shuffled funcs_shuffled = {
DO_MANY_MEMBERS(func_member)
};
test_check_init(test, "named", &untouched, &shuffled);
test_check_init(test, "unnamed", &untouched,
&(struct randstruct_shuffled){
init_members
});
test_check_contained_init(test, "named", &contains_untouched, &contains_shuffled);
test_check_contained_init(test, "unnamed", &contains_untouched,
&(struct contains_randstruct_shuffled){
.shuffled = (struct randstruct_shuffled){
init_members
},
});
test_check_contained_init(test, "named", &contains_untouched, &contains_shuffled);
test_check_contained_init(test, "unnamed copy", &contains_untouched,
&(struct contains_randstruct_shuffled){
/* full struct copy initializer */
.shuffled = shuffled,
});
test_check_mixed_init(test, "named", &mixed_untouched, &mixed_shuffled);
test_check_mixed_init(test, "unnamed", &mixed_untouched,
&(struct randstruct_mixed_shuffled){
init_members
});
test_check_funcs_init(test, "named", &funcs_untouched, &funcs_shuffled);
test_check_funcs_init(test, "unnamed", &funcs_untouched,
&(struct randstruct_funcs_shuffled){
DO_MANY_MEMBERS(func_member)
});
#undef func_member
#undef init_members
}
static int randstruct_test_init(struct kunit *test)
{
if (!IS_ENABLED(CONFIG_RANDSTRUCT))
kunit_skip(test, "Not built with CONFIG_RANDSTRUCT=y");
return 0;
}
static struct kunit_case randstruct_test_cases[] = {
KUNIT_CASE(randstruct_layout_same),
KUNIT_CASE(randstruct_layout_mixed),
KUNIT_CASE(randstruct_layout_fptr),
KUNIT_CASE(randstruct_layout_fptr_deep),
KUNIT_CASE(randstruct_initializers),
{}
};
static struct kunit_suite randstruct_test_suite = {
.name = "randstruct",
.init = randstruct_test_init,
.test_cases = randstruct_test_cases,
};
kunit_test_suites(&randstruct_test_suite);
MODULE_DESCRIPTION("Test cases for struct randomization");
MODULE_LICENSE("GPL");