[PATCH 4/5] lkdtm/bugs: Add basic Overflow Behavior Types test

Kees Cook posted 5 patches 16 hours ago
[PATCH 4/5] lkdtm/bugs: Add basic Overflow Behavior Types test
Posted by Kees Cook 16 hours ago
Exercise the end-to-end build and trap infrastructure in the kernel for
__ob_trap, __ob_wrap, and associated sanitizer ignore patterns (i.e. idiom
exclusions). Add a test for each of the basic overflow conditions under
CONFIG_OVERFLOW_BEHAVIOR_TYPES=y, as well as the corner cases associated
with promotion, casting, etc.

For example, executing this test with CONFIG_OVERFLOW_BEHAVIOR_TYPES_WARN=y
(instead of CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP=y), will show:

    $ echo OBT_ASSIGN_TRUNCATE_TO | cat >/sys/kernel/debug/provoke-crash/DIRECT
    $ dmesg
    ...
    lkdtm: Performing direct entry OBT_ASSIGN_TRUNCATE_TO
    UBSAN: implicit-conversion in ../drivers/misc/lkdtm/bugs.c:825:10
    cannot represent 'int' value 2147483647 during reference binding to 'u8t' (aka '__ob_trap u8'), truncated to 255

Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: <linux-kselftest@vger.kernel.org>
---
 drivers/misc/lkdtm/bugs.c               | 253 ++++++++++++++++++++++++
 tools/testing/selftests/lkdtm/tests.txt |  10 +
 2 files changed, 263 insertions(+)

diff --git a/drivers/misc/lkdtm/bugs.c b/drivers/misc/lkdtm/bugs.c
index e0098f314570..f00c9099957e 100644
--- a/drivers/misc/lkdtm/bugs.c
+++ b/drivers/misc/lkdtm/bugs.c
@@ -817,6 +817,249 @@ static noinline void lkdtm_CORRUPT_PAC(void)
 #endif
 }
 
+static void lkdtm_OBT_ASSIGN_TRUNCATE_TO(void)
+{
+	volatile int big = INT_MAX;
+	volatile int wide_low_value = 5;
+	u8 __ob_trap narrow_low_value = 0;
+	s32 __ob_trap same = 0;
+	u8 __ob_trap small = 0;
+
+	pr_info("Performing same-width assignment to OBT\n");
+	same = big;
+
+	pr_info("Performing small-value assignment to OBT\n");
+	narrow_low_value = wide_low_value;
+
+	pr_info("Expecting trap on truncated assignment to OBT\n");
+	small = big;
+
+	pr_err("FAIL: survived overflowing truncated assignment to OBT: %d -> %u (ok: %d -> %u)\n",
+		same, small, wide_low_value, narrow_low_value);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
+static void lkdtm_OBT_ASSIGN_TRUNCATE_FROM(void)
+{
+	volatile s32 __ob_trap big = INT_MAX;
+	volatile s32 __ob_trap wide_low_value = 5;
+	u8 narrow_low_value = 0;
+	s32 same = 0;
+	u8 small = 0;
+
+	pr_info("Performing same-width assignment from OBT\n");
+	same = big;
+
+	pr_info("Performing small-value assignment from OBT\n");
+	narrow_low_value = wide_low_value;
+
+	pr_info("Expecting trap on truncated assignment from OBT\n");
+	small = big;
+
+	pr_err("FAIL: survived overflowing truncated assignment from OBT: %d -> %u (ok: %d -> %u)\n",
+		same, small, wide_low_value, narrow_low_value);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
+static void lkdtm_OBT_CAST_TRUNCATE(void)
+{
+	volatile u32 __ob_trap big = INT_MAX;
+	u32 trunc = 0;
+	u32 small = 0;
+
+	pr_info("Performing wrapping too-small cast\n");
+	trunc = (u16 __ob_wrap)big;
+
+	pr_info("Expecting trap on too-small cast\n");
+	small = (s16)big;
+
+	pr_err("FAIL: survived truncated casting: %u -> %u (ok: %u -> %u)\n",
+		big, small, big, trunc);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
+static void lkdtm_OBT_CAST_SIGNED(void)
+{
+	volatile u32 __ob_trap big = UINT_MAX;
+	s32 neg = 0;
+	s32 small = 0;
+
+	pr_info("Performing explicit sign-changing cast\n");
+	neg = (s32 __ob_wrap)big;
+
+	pr_info("Expecting trap on unexpected sign-changing cast\n");
+	small = (s32)big;
+
+	pr_err("FAIL: survived lossy sign conversion: %u -> %d (forced: %u -> %d)\n",
+		big, small, big, neg);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
+static void lkdtm_OBT_MUL(void)
+{
+	/* Promotion means no overflow checking can happen. */
+	volatile u8 __ob_trap a8 = 100;
+	volatile u8 __ob_trap b8 = 3;
+	unsigned int promoted;
+	/* 32-bit or larger, however, get checked. */
+	volatile u32 __ob_trap a = UINT_MAX - 1;
+	volatile u32 __ob_trap b = 2;
+	unsigned long long happy;
+	unsigned long long outcome;
+
+	/* Promotion means a * b happens as "int __ob_trap", so no trap. */
+	pr_info("Performing promoted overflowing unsigned multiplication\n");
+	promoted = a8 * b8;
+
+	pr_info("Performing non-overflowing unsigned multiplication\n");
+	happy = b * b;
+
+	pr_info("Expecting trap on overflowing unsigned multiplication\n");
+	outcome = a * b;
+
+	pr_err("FAIL: survived unsigned multiplication overflow: %u * %u -> %llu (ok: %u * %u -> %llu, %u)\n",
+		a, b, outcome, b, b, happy, promoted);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
+static void lkdtm_OBT_MUL_SIGNED(void)
+{
+	/* Promotion means no overflow checking can happen. */
+	volatile s8 __ob_trap a8 = 100;
+	volatile s8 __ob_trap b8 = 3;
+	int promoted;
+	/* 32-bit or larger, however, get checked. */
+	volatile s32 __ob_trap a = INT_MAX - 1;
+	volatile s32 __ob_trap b = 2;
+	signed long long happy;
+	signed long long outcome;
+
+	/* Promotion means a8 * b8 happens as "int __ob_trap", so no trap. */
+	pr_info("Performing promoted overflowing signed multiplication\n");
+	promoted = a8 * b8;
+
+	pr_info("Performing non-overflowing signed multiplication\n");
+	happy = b * b;
+
+	pr_info("Expecting trap on overflowing signed multiplication\n");
+	outcome = a * b;
+
+	pr_err("FAIL: survived signed multiplication overflow: %d * %d -> %lld (ok: %d * %d -> %lld, %d)\n",
+		a, b, outcome, b, b, happy, promoted);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
+static void lkdtm_OBT_ADD(void)
+{
+	/* Promotion means no overflow checking can happen. */
+	volatile u8 __ob_trap a8 = 250;
+	volatile u8 __ob_trap b8 = 30;
+	unsigned int promoted;
+	/* 32-bit or larger, however, get checked. */
+	volatile u32 __ob_trap a = UINT_MAX - 1;
+	volatile u32 __ob_trap b = 2;
+	unsigned long long happy;
+	unsigned long long outcome;
+
+	/* Promotion means a8 + b8 happens as "int __ob_trap", so no trap. */
+	pr_info("Performing promoted overflowing unsigned addition\n");
+	promoted = a8 + b8;
+
+	pr_info("Performing idiomatic unsigned overflow addition test\n");
+	if (a + b < a) {
+		/* Report status so test isn't elided by compiler. */
+		pr_info("ok: overflow contained by conditional\n");
+	}
+
+	pr_info("Performing non-overflowing unsigned addition\n");
+	happy = b + b;
+
+	pr_info("Expecting trap on overflowing unsigned addition\n");
+	outcome = a + b;
+
+	pr_err("FAIL: survived unsigned addition overflow: %u + %u -> %llu (ok: %u + %u -> %llu)\n",
+		a, b, outcome, b, b, happy);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
+static void lkdtm_OBT_ADD_SIGNED(void)
+{
+	/* Promotion means no overflow checking can happen. */
+	volatile s8 __ob_trap a8 = 120;
+	volatile s8 __ob_trap b8 = 30;
+	int promoted;
+	/* 32-bit or larger, however, get checked. */
+	volatile s32 __ob_trap a = INT_MAX - 1;
+	volatile s32 __ob_trap b = 2;
+	signed long long happy;
+	signed long long outcome;
+
+	/* Promotion means a8 + b8 happens as "int __ob_trap", so no trap. */
+	pr_info("Performing promoted overflowing signed addition\n");
+	promoted = a8 + b8;
+
+	pr_info("Performing idiomatic signed overflow addition test\n");
+	if (a + b < a) {
+		/* Report status so test isn't elided by compiler. */
+		pr_info("ok: overflow contained by conditional\n");
+	}
+
+	pr_info("Performing non-overflowing signed addition\n");
+	happy = b + b;
+
+	pr_info("Expecting trap on overflowing signed addition\n");
+	outcome = a + b;
+
+	pr_err("FAIL: survived signed addition overflow: %u + %u -> %llu (ok: %u + %u -> %llu)\n",
+		a, b, outcome, b, b, happy);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
+static void lkdtm_OBT_NEGATED_UNSIGNED(void)
+{
+	volatile unsigned long __ob_trap value = 256;
+	size_t outcome;
+
+	pr_info("Expecting trap on overflowing unsigned negation\n");
+	outcome = value & -value;
+
+	pr_err("FAIL: survived negated unsigned value: %lu -> %zu\n",
+		value, outcome);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
+static void lkdtm_OBT_POSTFIX_OPERATORS(void)
+{
+	volatile int target = 300;
+	volatile int flag = 0;
+	int i;
+	u8 __ob_wrap wrapper = 0; /* Explicitly wrapping. */
+	u8 __ob_trap counter = 0;
+
+	pr_info("Performing u8 __ob_wrap post-increment past 255\n");
+	for (i = 0; i < target; i++)
+		wrapper++;
+	if (wrapper != 44)
+		pr_err("FAIL: wrapped incorrecty: %u\n", wrapper);
+
+	pr_info("Performing idiomatic post-decrement zero test\n");
+	counter = target / 2;
+	while (counter--)
+		if (flag)
+			break;
+	if (counter != 255)
+		pr_err("FAIL: u8 __ob_trap post-decrement zero-test did not wrap: %u\n",
+			counter);
+
+	pr_info("Expecting trap on u8 __ob_trap post-increment past 255\n");
+	counter = 0;
+	for (i = 0; i < target; i++)
+		counter++;
+
+	pr_err("FAIL: survived overflowed post-increment: %u\n", counter);
+	pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
+}
+
 static struct crashtype crashtypes[] = {
 	CRASHTYPE(PANIC),
 	CRASHTYPE(PANIC_STOP_IRQOFF),
@@ -850,6 +1093,16 @@ static struct crashtype crashtypes[] = {
 	CRASHTYPE(UNSET_SMEP),
 	CRASHTYPE(DOUBLE_FAULT),
 	CRASHTYPE(CORRUPT_PAC),
+	CRASHTYPE(OBT_ASSIGN_TRUNCATE_TO),
+	CRASHTYPE(OBT_ASSIGN_TRUNCATE_FROM),
+	CRASHTYPE(OBT_CAST_TRUNCATE),
+	CRASHTYPE(OBT_CAST_SIGNED),
+	CRASHTYPE(OBT_MUL),
+	CRASHTYPE(OBT_MUL_SIGNED),
+	CRASHTYPE(OBT_ADD),
+	CRASHTYPE(OBT_ADD_SIGNED),
+	CRASHTYPE(OBT_NEGATED_UNSIGNED),
+	CRASHTYPE(OBT_POSTFIX_OPERATORS),
 };
 
 struct crashtype_category bugs_crashtypes = {
diff --git a/tools/testing/selftests/lkdtm/tests.txt b/tools/testing/selftests/lkdtm/tests.txt
index e62b85b591be..231299ba3959 100644
--- a/tools/testing/selftests/lkdtm/tests.txt
+++ b/tools/testing/selftests/lkdtm/tests.txt
@@ -87,3 +87,13 @@ FORTIFY_STR_MEMBER detected buffer overflow
 FORTIFY_MEM_OBJECT detected buffer overflow
 FORTIFY_MEM_MEMBER detected field-spanning write
 PPC_SLB_MULTIHIT Recovered
+OBT_ASSIGN_TRUNCATE_TO traps: UBSAN: integer truncation
+OBT_ASSIGN_TRUNCATE_FROM traps: UBSAN: integer truncation
+OBT_CAST_TRUNCATE traps: UBSAN: integer truncation
+OBT_CAST_SIGNED traps: UBSAN: integer truncation
+OBT_MUL traps: UBSAN: integer multiplication overflow
+OBT_MUL_SIGNED traps: UBSAN: integer multiplication overflow
+OBT_ADD traps: UBSAN: integer addition overflow
+OBT_ADD_SIGNED traps: UBSAN: integer addition overflow
+OBT_NEGATED_UNSIGNED traps: UBSAN: negation overflow
+OBT_POSTFIX_OPERATORS traps: UBSAN: integer truncation
-- 
2.34.1
Re: [PATCH 4/5] lkdtm/bugs: Add basic Overflow Behavior Types test
Posted by Justin Stitt 15 hours ago
Hi,

On Tue, Mar 31, 2026 at 9:37 AM Kees Cook <kees@kernel.org> wrote:
>
> Exercise the end-to-end build and trap infrastructure in the kernel for
> __ob_trap, __ob_wrap, and associated sanitizer ignore patterns (i.e. idiom
> exclusions). Add a test for each of the basic overflow conditions under
> CONFIG_OVERFLOW_BEHAVIOR_TYPES=y, as well as the corner cases associated
> with promotion, casting, etc.
>
> For example, executing this test with CONFIG_OVERFLOW_BEHAVIOR_TYPES_WARN=y
> (instead of CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP=y), will show:
>
>     $ echo OBT_ASSIGN_TRUNCATE_TO | cat >/sys/kernel/debug/provoke-crash/DIRECT
>     $ dmesg
>     ...
>     lkdtm: Performing direct entry OBT_ASSIGN_TRUNCATE_TO
>     UBSAN: implicit-conversion in ../drivers/misc/lkdtm/bugs.c:825:10
>     cannot represent 'int' value 2147483647 during reference binding to 'u8t' (aka '__ob_trap u8'), truncated to 255
>
> Signed-off-by: Kees Cook <kees@kernel.org>
> ---
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Shuah Khan <shuah@kernel.org>
> Cc: <linux-kselftest@vger.kernel.org>
> ---
>  drivers/misc/lkdtm/bugs.c               | 253 ++++++++++++++++++++++++
>  tools/testing/selftests/lkdtm/tests.txt |  10 +
>  2 files changed, 263 insertions(+)
>
> diff --git a/drivers/misc/lkdtm/bugs.c b/drivers/misc/lkdtm/bugs.c
> index e0098f314570..f00c9099957e 100644
> --- a/drivers/misc/lkdtm/bugs.c
> +++ b/drivers/misc/lkdtm/bugs.c
> @@ -817,6 +817,249 @@ static noinline void lkdtm_CORRUPT_PAC(void)
>  #endif
>  }
>
> +static void lkdtm_OBT_ASSIGN_TRUNCATE_TO(void)
> +{
> +       volatile int big = INT_MAX;
> +       volatile int wide_low_value = 5;
> +       u8 __ob_trap narrow_low_value = 0;
> +       s32 __ob_trap same = 0;
> +       u8 __ob_trap small = 0;
> +
> +       pr_info("Performing same-width assignment to OBT\n");
> +       same = big;
> +
> +       pr_info("Performing small-value assignment to OBT\n");
> +       narrow_low_value = wide_low_value;
> +
> +       pr_info("Expecting trap on truncated assignment to OBT\n");
> +       small = big;
> +
> +       pr_err("FAIL: survived overflowing truncated assignment to OBT: %d -> %u (ok: %d -> %u)\n",
> +               same, small, wide_low_value, narrow_low_value);
> +       pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
> +}
> +
> +static void lkdtm_OBT_ASSIGN_TRUNCATE_FROM(void)
> +{
> +       volatile s32 __ob_trap big = INT_MAX;
> +       volatile s32 __ob_trap wide_low_value = 5;
> +       u8 narrow_low_value = 0;
> +       s32 same = 0;
> +       u8 small = 0;
> +
> +       pr_info("Performing same-width assignment from OBT\n");
> +       same = big;
> +
> +       pr_info("Performing small-value assignment from OBT\n");
> +       narrow_low_value = wide_low_value;
> +
> +       pr_info("Expecting trap on truncated assignment from OBT\n");
> +       small = big;
> +
> +       pr_err("FAIL: survived overflowing truncated assignment from OBT: %d -> %u (ok: %d -> %u)\n",
> +               same, small, wide_low_value, narrow_low_value);
> +       pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
> +}
> +
> +static void lkdtm_OBT_CAST_TRUNCATE(void)
> +{
> +       volatile u32 __ob_trap big = INT_MAX;
> +       u32 trunc = 0;
> +       u32 small = 0;
> +
> +       pr_info("Performing wrapping too-small cast\n");
> +       trunc = (u16 __ob_wrap)big;
> +
> +       pr_info("Expecting trap on too-small cast\n");
> +       small = (s16)big;
> +
> +       pr_err("FAIL: survived truncated casting: %u -> %u (ok: %u -> %u)\n",
> +               big, small, big, trunc);
> +       pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
> +}
> +
> +static void lkdtm_OBT_CAST_SIGNED(void)
> +{
> +       volatile u32 __ob_trap big = UINT_MAX;
> +       s32 neg = 0;
> +       s32 small = 0;
> +
> +       pr_info("Performing explicit sign-changing cast\n");
> +       neg = (s32 __ob_wrap)big;
> +
> +       pr_info("Expecting trap on unexpected sign-changing cast\n");
> +       small = (s32)big;
> +
> +       pr_err("FAIL: survived lossy sign conversion: %u -> %d (forced: %u -> %d)\n",
> +               big, small, big, neg);
> +       pr_expected_config(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP);
> +}

Note to travelers and testers, we still have a compiler patch in
flight [1] that fixes a bug with sign-change instrumentation
concerning OBTs.

> +
> +static void lkdtm_OBT_MUL(void)
> +{
> +       /* Promotion means no overflow checking can happen. */
> +       volatile u8 __ob_trap a8 = 100;

<snip>

[1]: https://github.com/llvm/llvm-project/pull/188340

Justin