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

0 views
Skip to first unread message

Kees Cook

unread,
Mar 31, 2026, 12:37:28 PM (10 days ago) Mar 31
to Peter Zijlstra, Kees Cook, Arnd Bergmann, Greg Kroah-Hartman, Shuah Khan, linux-k...@vger.kernel.org, Justin Stitt, Linus Torvalds, Marco Elver, Jonathan Corbet, Nathan Chancellor, Nicolas Schier, Miguel Ojeda, Andrew Morton, linux-...@vger.kernel.org, kasa...@googlegroups.com, linux-h...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev
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 <ke...@kernel.org>
---
Cc: Arnd Bergmann <ar...@arndb.de>
Cc: Greg Kroah-Hartman <gre...@linuxfoundation.org>
Cc: Shuah Khan <sh...@kernel.org>
Cc: <linux-k...@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

Kees Cook

unread,
Mar 31, 2026, 12:37:28 PM (10 days ago) Mar 31
to Peter Zijlstra, Kees Cook, Justin Stitt, Linus Torvalds, Marco Elver, Jonathan Corbet, Nathan Chancellor, Nicolas Schier, Arnd Bergmann, Greg Kroah-Hartman, Miguel Ojeda, Andrew Morton, linux-...@vger.kernel.org, kasa...@googlegroups.com, linux-h...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev
Hi,

This is the spiritual successor to the "Mitigating unexpected arithmetic
overflow" thread from 2024[1]. After a long discussion there, and
subsequent discussions with Peter at Linux Plumbers, Justin went off to
build a type-based solution. After more than a year of RFCs and feedback
from compiler folks and with an eye toward potentially making this part
of the C Standard in the future, what has evolved is Overflow Behavior
Types[2], which are first-class native types that mirror the existing
native scalar types in C. They are created using a type qualifier
("__ob_trap" and "__ob_wrap"), but they are their own distinct
types. e.g. "int" and "int __ob_trap" are different types, though
they are mostly interchangable (e.g. format strings, implicit casts,
etc), with some specific instrumentation in cases where wrap-around or
truncation is possible.

This series provides support for building with them enabled, adds
documentation, adds tests, and proposes the new typedefs (see the last
patch in the series) for the corresponding kernel scalar types. With this,
we can start converting variables (and types) that are never supposed
to overflow/underflow to these new types[3]. (Or types that are always
supposed to overflow/underflow.)

Enjoy! :)

-Kees

Link: https://lore.kernel.org/lkml/202404291502.612E0A10@keescook/ [1]
Link: https://clang.llvm.org/docs/OverflowBehaviorTypes.html [2]


Justin Stitt (2):
hardening: Introduce Overflow Behavior Types support
compiler_attributes: Add overflow_behavior macros __ob_trap and
__ob_wrap

Kees Cook (3):
refcount: Remove unused __signed_wrap function annotations
lkdtm/bugs: Add basic Overflow Behavior Types test
types: Add standard __ob_trap and __ob_wrap scalar types

lib/Kconfig.ubsan | 18 -
security/Kconfig.hardening | 50 ++-
Makefile | 1 +
scripts/basic/Makefile | 2 +-
scripts/Makefile.lib | 7 +-
scripts/Makefile.obt | 28 ++
scripts/Makefile.ubsan | 10 -
scripts/Makefile.warn | 7 +
scripts/integer-wrap-ignore.scl | 3 +-
Documentation/dev-tools/ubsan.rst | 13 +
Documentation/process/arithmetic-overflow.rst | 323 ++++++++++++++++++
Documentation/process/deprecated.rst | 39 +++
Documentation/process/index.rst | 1 +
include/linux/compiler-version.h | 2 +-
include/linux/compiler_attributes.h | 12 +
include/linux/compiler_types.h | 9 +-
include/linux/refcount.h | 10 +-
include/linux/sched.h | 3 +-
include/linux/types.h | 24 ++
include/linux/ubsan.h | 12 +-
drivers/misc/lkdtm/bugs.c | 253 ++++++++++++++
lib/ubsan.c | 17 +-
MAINTAINERS | 10 +
kernel/configs/hardening.config | 1 -
tools/testing/selftests/lkdtm/tests.txt | 10 +
25 files changed, 807 insertions(+), 58 deletions(-)
create mode 100644 scripts/Makefile.obt
create mode 100644 Documentation/process/arithmetic-overflow.rst

--
2.34.1

Justin Stitt

unread,
Mar 31, 2026, 1:16:47 PM (10 days ago) Mar 31
to Kees Cook, Peter Zijlstra, Arnd Bergmann, Greg Kroah-Hartman, Shuah Khan, linux-k...@vger.kernel.org, Linus Torvalds, Marco Elver, Jonathan Corbet, Nathan Chancellor, Nicolas Schier, Miguel Ojeda, Andrew Morton, linux-...@vger.kernel.org, kasa...@googlegroups.com, linux-h...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev
Hi,
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
Reply all
Reply to author
Forward
0 new messages