All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kees Cook <kees@kernel.org>
To: Arnd Bergmann <arnd@arndb.de>
Cc: "Kees Cook" <kees@kernel.org>, "Bill Wendling" <morbo@google.com>,
	"Andrew Morton" <akpm@linux-foundation.org>,
	"Nathan Chancellor" <nathan@kernel.org>,
	"Nick Desaulniers" <nick.desaulniers+lkml@gmail.com>,
	"Justin Stitt" <justinstitt@google.com>,
	"Petr Mladek" <pmladek@suse.com>,
	"David Gow" <davidgow@google.com>, "Rae Moar" <rmoar@google.com>,
	"Tamir Duberstein" <tamird@gmail.com>,
	"Diego Vieira" <diego.daniel.professional@gmail.com>,
	"Luis Chamberlain" <mcgrof@kernel.org>,
	llvm@lists.linux.dev,
	"Dr. David Alan Gilbert" <linux@treblig.org>,
	"Mark Brown" <broonie@kernel.org>,
	WangYuli <wangyuli@uniontech.com>,
	"Mickaël Salaün" <mic@digikod.net>,
	"Günther Noack" <gnoack@google.com>,
	"Gustavo A. R. Silva" <gustavoars@kernel.org>,
	"Paul Moore" <paul@paul-moore.com>,
	"James Morris" <jmorris@namei.org>,
	"Serge E. Hallyn" <serge@hallyn.com>,
	linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org,
	linux-security-module@vger.kernel.org
Subject: [PATCH 2/3] lib/tests: Add randstruct KUnit test
Date: Sat, 26 Apr 2025 18:38:34 -0700	[thread overview]
Message-ID: <20250427013836.877214-2-kees@kernel.org> (raw)
In-Reply-To: <20250427013604.work.926-kees@kernel.org>

Perform basic validation about layout randomization and initialization
tracking when using CONFIG_RANDSTRUCT=y. Tested using:

$ ./tools/testing/kunit/kunit.py run \
	--kconfig_add CONFIG_RANDSTRUCT_FULL=y \
	randstruct
[17:22:30] ================= randstruct (2 subtests) ==================
[17:22:30] [PASSED] randstruct_layout
[17:22:30] [PASSED] randstruct_initializers
[17:22:30] =================== [PASSED] randstruct ====================
[17:22:30] ============================================================
[17:22:30] Testing complete. Ran 2 tests: passed: 2
[17:22:30] Elapsed time: 5.091s total, 0.001s configuring, 4.974s building, 0.086s running

Adding "--make_option LLVM=1" can be used to test Clang, which also
passes.

Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Bill Wendling <morbo@google.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Nathan Chancellor <nathan@kernel.org>
Cc: Nick Desaulniers <nick.desaulniers+lkml@gmail.com>
Cc: Justin Stitt <justinstitt@google.com>
Cc: Petr Mladek <pmladek@suse.com>
Cc: David Gow <davidgow@google.com>
Cc: Rae Moar <rmoar@google.com>
Cc: Tamir Duberstein <tamird@gmail.com>
Cc: Diego Vieira <diego.daniel.professional@gmail.com>
Cc: Luis Chamberlain <mcgrof@kernel.org>
Cc: <llvm@lists.linux.dev>
---
 MAINTAINERS                  |   1 +
 lib/Kconfig.debug            |   8 +
 lib/tests/Makefile           |   1 +
 lib/tests/randstruct_kunit.c | 283 +++++++++++++++++++++++++++++++++++
 4 files changed, 293 insertions(+)
 create mode 100644 lib/tests/randstruct_kunit.c

diff --git a/MAINTAINERS b/MAINTAINERS
index fa1e04e87d1d..aeb3e7911852 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12879,6 +12879,7 @@ F:	include/linux/overflow.h
 F:	include/linux/randomize_kstack.h
 F:	include/linux/ucopysize.h
 F:	kernel/configs/hardening.config
+F:	lib/tests/randstruct_kunit.c
 F:	lib/tests/usercopy_kunit.c
 F:	mm/usercopy.c
 F:	security/Kconfig.hardening
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index f9051ab610d5..6479cec900c7 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2863,6 +2863,14 @@ config OVERFLOW_KUNIT_TEST
 
 	  If unsure, say N.
 
+config RANDSTRUCT_KUNIT_TEST
+	tristate "Test randstruct structure layout randomization at runtime" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  Builds unit tests for the checking CONFIG_RANDSTRUCT=y, which
+	  randomizes structure layouts.
+
 config STACKINIT_KUNIT_TEST
 	tristate "Test level of stack variable initialization" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 5a4794c1826e..56d645014482 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_MEMCPY_KUNIT_TEST) += memcpy_kunit.o
 CFLAGS_overflow_kunit.o = $(call cc-disable-warning, tautological-constant-out-of-range-compare)
 obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_kunit.o
 obj-$(CONFIG_PRINTF_KUNIT_TEST) += printf_kunit.o
+obj-$(CONFIG_RANDSTRUCT_KUNIT_TEST) += randstruct_kunit.o
 obj-$(CONFIG_SCANF_KUNIT_TEST) += scanf_kunit.o
 obj-$(CONFIG_SIPHASH_KUNIT_TEST) += siphash_kunit.o
 obj-$(CONFIG_SLUB_KUNIT_TEST) += slub_kunit.o
diff --git a/lib/tests/randstruct_kunit.c b/lib/tests/randstruct_kunit.c
new file mode 100644
index 000000000000..6fc23dfa57b6
--- /dev/null
+++ b/lib/tests/randstruct_kunit.c
@@ -0,0 +1,283 @@
+// 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);
+
+/* 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)
+};
+#undef 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;
+};
+
+static void randstruct_layout(struct kunit *test)
+{
+	int mismatches;
+
+#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)		\
+	mismatches = 0;						\
+	DO_MANY_MEMBERS(check_mismatch, 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");
+
+	check_pair(EXPECT_EQ, struct randstruct_untouched, struct randstruct_untouched)
+	check_pair(EXPECT_GT, struct randstruct_untouched, struct randstruct_shuffled)
+	check_pair(EXPECT_GT, struct randstruct_untouched, struct randstruct_funcs_shuffled)
+	check_pair(EXPECT_GT, struct randstruct_funcs_untouched, struct randstruct_funcs_shuffled)
+	check_pair(EXPECT_GT, struct randstruct_mixed_untouched, struct randstruct_mixed_shuffled)
+#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),
+	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");
-- 
2.34.1


  parent reply	other threads:[~2025-04-27  1:38 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-04-27  1:38 [PATCH 0/3] randstruct: gcc-plugin: Remove bogus void member Kees Cook
2025-04-27  1:38 ` [PATCH 1/3] " Kees Cook
2025-04-27  1:38 ` Kees Cook [this message]
2025-04-27  3:47   ` [PATCH 2/3] lib/tests: Add randstruct KUnit test kernel test robot
2025-04-27  3:47   ` kernel test robot
2025-04-30 18:56     ` Kees Cook
2025-04-27  6:04   ` kernel test robot
2025-04-29  7:44   ` David Gow
2025-04-30 18:56     ` Kees Cook
2025-04-27  1:38 ` [PATCH 3/3] Revert "hardening: Disable GCC randstruct for COMPILE_TEST" Kees Cook
2025-05-30  0:06   ` Thiago Jung Bauermann
2025-05-30  5:12     ` Kees Cook
2025-05-30 19:09       ` Nathan Chancellor
2025-05-30 19:37         ` Kees Cook
2025-05-30 22:31         ` Kees Cook
2025-05-20 15:18 ` [PATCH 0/3] randstruct: gcc-plugin: Remove bogus void member Mickaël Salaün
2025-05-20 16:14   ` Kees Cook

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250427013836.877214-2-kees@kernel.org \
    --to=kees@kernel.org \
    --cc=akpm@linux-foundation.org \
    --cc=arnd@arndb.de \
    --cc=broonie@kernel.org \
    --cc=davidgow@google.com \
    --cc=diego.daniel.professional@gmail.com \
    --cc=gnoack@google.com \
    --cc=gustavoars@kernel.org \
    --cc=jmorris@namei.org \
    --cc=justinstitt@google.com \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=linux@treblig.org \
    --cc=llvm@lists.linux.dev \
    --cc=mcgrof@kernel.org \
    --cc=mic@digikod.net \
    --cc=morbo@google.com \
    --cc=nathan@kernel.org \
    --cc=nick.desaulniers+lkml@gmail.com \
    --cc=paul@paul-moore.com \
    --cc=pmladek@suse.com \
    --cc=rmoar@google.com \
    --cc=serge@hallyn.com \
    --cc=tamird@gmail.com \
    --cc=wangyuli@uniontech.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.