linux-security-module.vger.kernel.org archive mirror
 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).