[PATCH 2/5] hardening: Introduce Overflow Behavior Types support

Kees Cook
2026-03-31T16:37:26Z

From: Justin Stitt <justinstitt@google.com>

Replace CONFIG_UBSAN_INTEGER_WRAP with CONFIG_OVERFLOW_BEHAVIOR_TYPES,
which a more distinct implementation of the integer wrapping logic,
with the introduction of the Overflow Behavior Types in Clang 23+. While
this uses sanitizers, it isn't really part of the "undefined behavior"
sanitizer collection (there is nothing "undefined" going on, but the
warn/trap infrastructure is still used for common instrumentation).

As OBTs will be annotated in code, we no longer need type annotation in
the sanitizer SCL file. A build with CONFIG_OVERFLOW_BEHAVIOR_TYPES=y
will result in no new instrumentation being added (as there are no users
of the coming __ob_trap and __ob_wrap annotations yet).

Signed-off-by: Justin Stitt <justinstitt@google.com>
Co-developed-by: Kees Cook <kees@kernel.org>
Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Nathan Chancellor <nathan@kernel.org>
Cc: Nicolas Schier <nsc@kernel.org>
Cc: <linux-kbuild@vger.kernel.org>
Cc: <kasan-dev@googlegroups.com>
---
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 +-
include/linux/compiler-version.h | 2 +-
include/linux/sched.h | 3 +-
include/linux/ubsan.h | 12 +++++++-
lib/ubsan.c | 17 ++++++-----
MAINTAINERS | 9 ++++++
kernel/configs/hardening.config | 1 -
15 files changed, 125 insertions(+), 45 deletions(-)
create mode 100644 scripts/Makefile.obt

diff --git a/lib/Kconfig.ubsan b/lib/Kconfig.ubsan
index 1ecaae7064d2..666286e0d294 100644
--- a/lib/Kconfig.ubsan
+++ b/lib/Kconfig.ubsan
@@ -116,24 +116,6 @@ config UBSAN_UNREACHABLE
This option enables -fsanitize=unreachable which checks for control
flow reaching an expected-to-be-unreachable position.

-config UBSAN_INTEGER_WRAP
- bool "Perform checking for integer arithmetic wrap-around"
- # This is very experimental so drop the next line if you really want it
- depends on BROKEN
- depends on !COMPILE_TEST
- depends on $(cc-option,-fsanitize-undefined-ignore-overflow-pattern=all)
- depends on $(cc-option,-fsanitize=signed-integer-overflow)
- depends on $(cc-option,-fsanitize=unsigned-integer-overflow)
- depends on $(cc-option,-fsanitize=implicit-signed-integer-truncation)
- depends on $(cc-option,-fsanitize=implicit-unsigned-integer-truncation)
- depends on $(cc-option,-fsanitize-ignorelist=/dev/null)
- help
- This option enables all of the sanitizers involved in integer overflow
- (wrap-around) mitigation: signed-integer-overflow, unsigned-integer-overflow,
- implicit-signed-integer-truncation, and implicit-unsigned-integer-truncation.
- This is currently limited only to the size_t type while testing and
- compiler development continues.
-
config UBSAN_BOOL
bool "Perform checking for non-boolean values used as boolean"
default UBSAN
diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening
index 86f8768c63d4..0c9e03c8a5a7 100644
--- a/security/Kconfig.hardening
+++ b/security/Kconfig.hardening
@@ -211,7 +211,55 @@ config ZERO_CALL_USED_REGS

endmenu

-menu "Bounds checking"
+menu "Bounds safety"
+
+config CC_HAS_OVERFLOW_BEHAVIOR_TYPES
+ # Clang 23+
+ depends on CC_IS_CLANG
+ def_bool $(cc-option,-Xclang -fexperimental-overflow-behavior-types)
+
+config OVERFLOW_BEHAVIOR_TYPES
+ bool
+ help
+ Selected if either OVERFLOW_BEHAVIOR_TYPES_TRAP or
+ OVERFLOW_BEHAVIOR_TYPES_WARN is chosen.
+
+choice
+ prompt "Perform checking for integer arithmetic wrap-around"
+ default OVERFLOW_BEHAVIOR_TYPES_TRAP if CC_HAS_OVERFLOW_BEHAVIOR_TYPES
+ default OVERFLOW_BEHAVIOR_TYPES_WARN if COMPILE_TEST && CC_HAS_OVERFLOW_BEHAVIOR_TYPES
+ default OVERFLOW_BEHAVIOR_TYPES_NONE
+ help
+ This option enables Overflow Behavior Types and all of the
+ sanitizers involved in integer overflow (wrap-around) mitigation:
+ signed-integer-overflow, unsigned-integer-overflow,
+ implicit-signed-integer-truncation, implicit-unsigned-integer-truncation,
+ and implicit-integer-sign-change. Only types (and variables)
+ annotated with __ob_wrap or __ob_trap will be instrumented by the
+ compiler.
+
+ config OVERFLOW_BEHAVIOR_TYPES_TRAP
+ bool "Trap when __ob_trap types overflow (mitigate)"
+ depends on CC_HAS_OVERFLOW_BEHAVIOR_TYPES
+ select OVERFLOW_BEHAVIOR_TYPES
+ help
+ Enables Overflow Behavior Types and traps when __ob_trap
+ annotated variables overflow or underflow.
+
+ config OVERFLOW_BEHAVIOR_TYPES_WARN
+ bool "Warn when __ob_trap types overflow (informational)"
+ depends on CC_HAS_OVERFLOW_BEHAVIOR_TYPES
+ select OVERFLOW_BEHAVIOR_TYPES
+ help
+ Enables Overflow Behavior Types and warns when __ob_trap
+ annotated variables overflow or underflow.
+
+ config OVERFLOW_BEHAVIOR_TYPES_NONE
+ bool "Disable any handling of __ob_trap types (disabled)"
+ help
+ No special handling of __ob_trap annotated types.
+
+endchoice

config FORTIFY_SOURCE
bool "Harden common str/mem functions against buffer overflows"
diff --git a/Makefile b/Makefile
index 2446085983f7..8e7336d22488 100644
--- a/Makefile
+++ b/Makefile
@@ -1123,6 +1123,7 @@ include-$(CONFIG_KASAN) += scripts/Makefile.kasan
include-$(CONFIG_KCSAN) += scripts/Makefile.kcsan
include-$(CONFIG_KMSAN) += scripts/Makefile.kmsan
include-$(CONFIG_UBSAN) += scripts/Makefile.ubsan
+include-$(CONFIG_OVERFLOW_BEHAVIOR_TYPES) += scripts/Makefile.obt
include-$(CONFIG_KCOV) += scripts/Makefile.kcov
include-$(CONFIG_RANDSTRUCT) += scripts/Makefile.randstruct
include-$(CONFIG_KSTACK_ERASE) += scripts/Makefile.kstack_erase
diff --git a/scripts/basic/Makefile b/scripts/basic/Makefile
index fb8e2c38fbc7..098760fc403a 100644
--- a/scripts/basic/Makefile
+++ b/scripts/basic/Makefile
@@ -18,4 +18,4 @@ always-$(CONFIG_RANDSTRUCT) += randstruct.seed
# integer-wrap: if the .scl file changes, we need to do a full rebuild.
$(obj)/../../include/generated/integer-wrap.h: $(srctree)/scripts/integer-wrap-ignore.scl FORCE
$(call if_changed,touch)
-always-$(CONFIG_UBSAN_INTEGER_WRAP) += ../../include/generated/integer-wrap.h
+always-$(CONFIG_OVERFLOW_BEHAVIOR_TYPES) += ../../include/generated/integer-wrap.h
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 0718e39cedda..d0e5cf2b0e3f 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -76,9 +76,12 @@ ifeq ($(CONFIG_UBSAN),y)
_c_flags += $(if $(patsubst n%,, \
$(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_SANITIZE)$(is-kernel-object)), \
$(CFLAGS_UBSAN))
+endif
+
+ifeq ($(CONFIG_OVERFLOW_BEHAVIOR_TYPES),y)
_c_flags += $(if $(patsubst n%,, \
- $(UBSAN_INTEGER_WRAP_$(target-stem).o)$(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_INTEGER_WRAP)$(UBSAN_SANITIZE)$(is-kernel-object)), \
- $(CFLAGS_UBSAN_INTEGER_WRAP))
+ $(OVERFLOW_BEHAVIOR_TYPES_$(target-stem).o)$(OVERFLOW_BEHAVIOR_TYPES)$(is-kernel-object)), \
+ $(CFLAGS_OVERFLOW_BEHAVIOR_TYPES))
endif

ifeq ($(CONFIG_KCOV),y)
diff --git a/scripts/Makefile.obt b/scripts/Makefile.obt
new file mode 100644
index 000000000000..f87f87e148f9
--- /dev/null
+++ b/scripts/Makefile.obt
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# Instead of specifying `all`, let's pick our patterns piecemeal so we aren't
+# blindsided by compiler upgrades.
+OVERFLOW_BEHAVIOR_TYPES_IGNORE_PATTERNS := negated-unsigned-const,$\
+ unsigned-post-decr-while,$\
+ add-signed-overflow-test,$\
+ add-unsigned-overflow-test
+
+OVERFLOW_BEHAVIOR_TYPES_SANITIZERS := signed-integer-overflow,$\
+ unsigned-integer-overflow,$\
+ implicit-signed-integer-truncation,$\
+ implicit-unsigned-integer-truncation,$\
+ implicit-integer-sign-change
+
+overflow-behavior-types-cflags-$(CONFIG_OVERFLOW_BEHAVIOR_TYPES) += \
+ -DOVERFLOW_BEHAVIOR_TYPES \
+ -Xclang -fexperimental-overflow-behavior-types \
+ -fsanitize=$(OVERFLOW_BEHAVIOR_TYPES_SANITIZERS) \
+ -fsanitize-undefined-ignore-overflow-pattern=$(OVERFLOW_BEHAVIOR_TYPES_IGNORE_PATTERNS) \
+ -fsanitize-ignorelist=$(srctree)/scripts/integer-wrap-ignore.scl \
+ -Wno-incompatible-pointer-types-discards-overflow-behavior
+overflow-behavior-types-cflags-$(CONFIG_OVERFLOW_BEHAVIOR_TYPES_WARN) += \
+ -fno-sanitize-trap=$(OVERFLOW_BEHAVIOR_TYPES_SANITIZERS)
+overflow-behavior-types-cflags-$(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP) += \
+ -fsanitize-trap=$(OVERFLOW_BEHAVIOR_TYPES_SANITIZERS)
+
+export CFLAGS_OVERFLOW_BEHAVIOR_TYPES := $(overflow-behavior-types-cflags-y)
diff --git a/scripts/Makefile.ubsan b/scripts/Makefile.ubsan
index 734a102e6b56..fb49302078e5 100644
--- a/scripts/Makefile.ubsan
+++ b/scripts/Makefile.ubsan
@@ -16,13 +16,3 @@ ubsan-cflags-$(CONFIG_UBSAN_ENUM) += -fsanitize=enum
ubsan-cflags-$(CONFIG_UBSAN_TRAP) += $(CFLAGS_UBSAN_TRAP)

export CFLAGS_UBSAN := $(ubsan-cflags-y)
-
-ubsan-integer-wrap-cflags-$(CONFIG_UBSAN_INTEGER_WRAP) += \
- -DINTEGER_WRAP \
- -fsanitize-undefined-ignore-overflow-pattern=all \
- -fsanitize=signed-integer-overflow \
- -fsanitize=unsigned-integer-overflow \
- -fsanitize=implicit-signed-integer-truncation \
- -fsanitize=implicit-unsigned-integer-truncation \
- -fsanitize-ignorelist=$(srctree)/scripts/integer-wrap-ignore.scl
-export CFLAGS_UBSAN_INTEGER_WRAP := $(ubsan-integer-wrap-cflags-y)
diff --git a/scripts/Makefile.warn b/scripts/Makefile.warn
index 5567da6c7dfe..eae0ecb88da4 100644
--- a/scripts/Makefile.warn
+++ b/scripts/Makefile.warn
@@ -49,6 +49,13 @@ KBUILD_CFLAGS += $(call cc-option, -Wno-format-truncation-non-kprintf)
# because -Wuninitialized will still flag when an uninitialized const variable
# is used.
KBUILD_CFLAGS += $(call cc-option, -Wno-default-const-init-unsafe)
+
+# Clang with Overflow Behavior Types support but building a kernel without
+# CONFIG_OVERFLOW_BEHAVIOR_TYPES needs to explicitly ignore the attribute.
+ifndef CONFIG_OVERFLOW_BEHAVIOR_TYPES
+KBUILD_CFLAGS += $(call cc-option, -Wno-overflow-behavior-attribute-ignored)
+endif
+
else

# gcc inanely warns about local variables called 'main'
diff --git a/scripts/integer-wrap-ignore.scl b/scripts/integer-wrap-ignore.scl
index 431c3053a4a2..8168d376ffff 100644
--- a/scripts/integer-wrap-ignore.scl
+++ b/scripts/integer-wrap-ignore.scl
@@ -1,3 +1,2 @@
-[{unsigned-integer-overflow,signed-integer-overflow,implicit-signed-integer-truncation,implicit-unsigned-integer-truncation}]
+[{unsigned-integer-overflow,signed-integer-overflow,implicit-signed-integer-truncation,implicit-unsigned-integer-truncation,implicit-integer-sign-change}]
type:*
-type:size_t=sanitize
diff --git a/include/linux/compiler-version.h b/include/linux/compiler-version.h
index ac1665a98a15..20033781ff15 100644
--- a/include/linux/compiler-version.h
+++ b/include/linux/compiler-version.h
@@ -39,6 +39,6 @@
* may have changed, which may impact the expected behaviors that should
* not differ between compilation units.
*/
-#ifdef INTEGER_WRAP
+#ifdef OVERFLOW_BEHAVIOR_TYPES
#include <generated/integer-wrap.h>
#endif
diff --git a/include/linux/sched.h b/include/linux/sched.h
index a7b4a980eb2f..d3e2ae0e2fe4 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -48,6 +48,7 @@
#include <linux/uidgid_types.h>
#include <linux/tracepoint-defs.h>
#include <linux/unwind_deferred_types.h>
+#include <linux/ubsan.h>
#include <asm/kmap_size.h>
#include <linux/time64.h>
#ifndef COMPILE_OFFSETS
@@ -1271,7 +1272,7 @@ struct task_struct {
struct held_lock held_locks[MAX_LOCK_DEPTH];
#endif

-#if defined(CONFIG_UBSAN) && !defined(CONFIG_UBSAN_TRAP)
+#ifdef NEED_SANITIZER_WARN_HANDLER
unsigned int in_ubsan;
#endif

diff --git a/include/linux/ubsan.h b/include/linux/ubsan.h
index 3ab8d38aedb8..7fedff0cdf8e 100644
--- a/include/linux/ubsan.h
+++ b/include/linux/ubsan.h
@@ -2,7 +2,17 @@
#ifndef _LINUX_UBSAN_H
#define _LINUX_UBSAN_H

-#if defined(CONFIG_UBSAN_TRAP) || defined(CONFIG_UBSAN_KVM_EL2)
+#if defined(CONFIG_UBSAN_TRAP) || defined(CONFIG_UBSAN_KVM_EL2) || \
+ defined(CONFIG_OVERFLOW_BEHAVIOR_TYPES_TRAP)
+# define NEED_SANITIZER_TRAP_HANDLER
+#endif
+
+#if (defined(CONFIG_UBSAN) && !defined(CONFIG_UBSAN_TRAP)) || \
+ defined(CONFIG_OVERFLOW_BEHAVIOR_TYPES_WARN)
+# define NEED_SANITIZER_WARN_HANDLER
+#endif
+
+#ifdef NEED_SANITIZER_TRAP_HANDLER
const char *report_ubsan_failure(u32 check_type);
#else
static inline const char *report_ubsan_failure(u32 check_type)
diff --git a/lib/ubsan.c b/lib/ubsan.c
index 456e3dd8f4ea..19edb5cedb5a 100644
--- a/lib/ubsan.c
+++ b/lib/ubsan.c
@@ -19,7 +19,7 @@

#include "ubsan.h"

-#if defined(CONFIG_UBSAN_TRAP) || defined(CONFIG_UBSAN_KVM_EL2)
+#ifdef NEED_SANITIZER_TRAP_HANDLER
/*
* Only include matches for UBSAN checks that are actually compiled in.
* The mappings of struct SanitizerKind (the -fsanitize=xxx args) to
@@ -44,7 +44,7 @@ const char *report_ubsan_failure(u32 check_type)
case ubsan_shift_out_of_bounds:
return "UBSAN: shift out of bounds";
#endif
-#if defined(CONFIG_UBSAN_DIV_ZERO) || defined(CONFIG_UBSAN_INTEGER_WRAP)
+#if defined(CONFIG_UBSAN_DIV_ZERO) || defined(CONFIG_OVERFLOW_BEHAVIOR_TYPES)
/*
* SanitizerKind::IntegerDivideByZero and
* SanitizerKind::SignedIntegerOverflow emit
@@ -79,7 +79,7 @@ const char *report_ubsan_failure(u32 check_type)
case ubsan_type_mismatch:
return "UBSAN: type mismatch";
#endif
-#ifdef CONFIG_UBSAN_INTEGER_WRAP
+#ifdef CONFIG_OVERFLOW_BEHAVIOR_TYPES
/*
* SanitizerKind::SignedIntegerOverflow emits
* SanitizerHandler::AddOverflow, SanitizerHandler::SubOverflow,
@@ -91,15 +91,18 @@ const char *report_ubsan_failure(u32 check_type)
return "UBSAN: integer subtraction overflow";
case ubsan_mul_overflow:
return "UBSAN: integer multiplication overflow";
+ case ubsan_implicit_conversion:
+ return "UBSAN: integer truncation";
+ case ubsan_negate_overflow:
+ return "UBSAN: negation overflow";
#endif
default:
return "UBSAN: unrecognized failure code";
}
}
+#endif /* NEED_SANITIZER_TRAP_HANDLER */

-#endif
-
-#ifndef CONFIG_UBSAN_TRAP
+#ifdef NEED_SANITIZER_WARN_HANDLER
static const char * const type_check_kinds[] = {
"load of",
"store to",
@@ -558,4 +561,4 @@ void __ubsan_handle_alignment_assumption(void *_data, unsigned long ptr,
}
EXPORT_SYMBOL(__ubsan_handle_alignment_assumption);

-#endif /* !CONFIG_UBSAN_TRAP */
+#endif /* NEED_SANITIZER_WARN_HANDLER */
diff --git a/MAINTAINERS b/MAINTAINERS
index 61bf550fd37c..a9f067164203 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19919,6 +19919,15 @@ F: Documentation/devicetree/bindings/media/i2c/ovti,ov2659.txt
F: drivers/media/i2c/ov2659.c
F: include/media/i2c/ov2659.h

+OVERFLOW BEHAVIOR TYPES
+M: Kees Cook <kees@kernel.org>
+M: Justin Stitt <justinstitt@google.com>
+L: linux-hardening@vger.kernel.org
+S: Supported
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/hardening
+F: scripts/integer-wrap-ignore.scl
+F: scripts/Makefile.obt
+
OVERLAY FILESYSTEM
M: Miklos Szeredi <miklos@szeredi.hu>
M: Amir Goldstein <amir73il@gmail.com>
diff --git a/kernel/configs/hardening.config b/kernel/configs/hardening.config
index 7c3924614e01..aabe28005a3d 100644
--- a/kernel/configs/hardening.config
+++ b/kernel/configs/hardening.config
@@ -46,7 +46,6 @@ CONFIG_UBSAN_BOUNDS=y
# CONFIG_UBSAN_SHIFT is not set
# CONFIG_UBSAN_DIV_ZERO is not set
# CONFIG_UBSAN_UNREACHABLE is not set
-# CONFIG_UBSAN_INTEGER_WRAP is not set
# CONFIG_UBSAN_BOOL is not set
# CONFIG_UBSAN_ENUM is not set
# CONFIG_UBSAN_ALIGNMENT is not set
--
2.34.1