Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ubsan/overflow: Rework integer overflow sanitizer option to turn on everything

Since we're going to approach integer overflow mitigation a type at a
time, we need to enable all of the associated sanitizers, and then opt
into types one at a time.

Rename the existing "signed wrap" sanitizer to just the entire topic area:
"integer wrap". Enable the implicit integer truncation sanitizers, with
required callbacks and tests.

Notably, this requires features (currently) only available in Clang,
so we can depend on the cc-option tests to determine availability
instead of doing version tests.

Link: https://lore.kernel.org/r/20250307041914.937329-1-kees@kernel.org
Signed-off-by: Kees Cook <kees@kernel.org>

+68 -23
+1 -1
include/linux/compiler_types.h
··· 360 360 #endif 361 361 362 362 /* Do not trap wrapping arithmetic within an annotated function. */ 363 - #ifdef CONFIG_UBSAN_SIGNED_WRAP 363 + #ifdef CONFIG_UBSAN_INTEGER_WRAP 364 364 # define __signed_wrap __attribute__((no_sanitize("signed-integer-overflow"))) 365 365 #else 366 366 # define __signed_wrap
+1 -1
kernel/configs/hardening.config
··· 46 46 # CONFIG_UBSAN_SHIFT is not set 47 47 # CONFIG_UBSAN_DIV_ZERO is not set 48 48 # CONFIG_UBSAN_UNREACHABLE is not set 49 - # CONFIG_UBSAN_SIGNED_WRAP is not set 49 + # CONFIG_UBSAN_INTEGER_WRAP is not set 50 50 # CONFIG_UBSAN_BOOL is not set 51 51 # CONFIG_UBSAN_ENUM is not set 52 52 # CONFIG_UBSAN_ALIGNMENT is not set
+10 -11
lib/Kconfig.ubsan
··· 116 116 This option enables -fsanitize=unreachable which checks for control 117 117 flow reaching an expected-to-be-unreachable position. 118 118 119 - config UBSAN_SIGNED_WRAP 120 - bool "Perform checking for signed arithmetic wrap-around" 119 + config UBSAN_INTEGER_WRAP 120 + bool "Perform checking for integer arithmetic wrap-around" 121 121 default UBSAN 122 122 depends on !COMPILE_TEST 123 - # The no_sanitize attribute was introduced in GCC with version 8. 124 - depends on !CC_IS_GCC || GCC_VERSION >= 80000 125 123 depends on $(cc-option,-fsanitize=signed-integer-overflow) 124 + depends on $(cc-option,-fsanitize=unsigned-integer-overflow) 125 + depends on $(cc-option,-fsanitize=implicit-signed-integer-truncation) 126 + depends on $(cc-option,-fsanitize=implicit-unsigned-integer-truncation) 126 127 help 127 - This option enables -fsanitize=signed-integer-overflow which checks 128 - for wrap-around of any arithmetic operations with signed integers. 129 - This currently performs nearly no instrumentation due to the 130 - kernel's use of -fno-strict-overflow which converts all would-be 131 - arithmetic undefined behavior into wrap-around arithmetic. Future 132 - sanitizer versions will allow for wrap-around checking (rather than 133 - exclusively undefined behavior). 128 + This option enables all of the sanitizers involved in integer overflow 129 + (wrap-around) mitigation: signed-integer-overflow, unsigned-integer-overflow, 130 + implicit-signed-integer-truncation, and implicit-unsigned-integer-truncation. 131 + This is currently limited only to the size_t type while testing and 132 + compiler development continues. 134 133 135 134 config UBSAN_BOOL 136 135 bool "Perform checking for non-boolean values used as boolean"
+14 -4
lib/test_ubsan.c
··· 15 15 { 16 16 volatile int val = INT_MAX; 17 17 18 - UBSAN_TEST(CONFIG_UBSAN_SIGNED_WRAP); 18 + UBSAN_TEST(CONFIG_UBSAN_INTEGER_WRAP); 19 19 val += 2; 20 20 } 21 21 ··· 24 24 volatile int val = INT_MIN; 25 25 volatile int val2 = 2; 26 26 27 - UBSAN_TEST(CONFIG_UBSAN_SIGNED_WRAP); 27 + UBSAN_TEST(CONFIG_UBSAN_INTEGER_WRAP); 28 28 val -= val2; 29 29 } 30 30 ··· 32 32 { 33 33 volatile int val = INT_MAX / 2; 34 34 35 - UBSAN_TEST(CONFIG_UBSAN_SIGNED_WRAP); 35 + UBSAN_TEST(CONFIG_UBSAN_INTEGER_WRAP); 36 36 val *= 3; 37 37 } 38 38 ··· 40 40 { 41 41 volatile int val = INT_MIN; 42 42 43 - UBSAN_TEST(CONFIG_UBSAN_SIGNED_WRAP); 43 + UBSAN_TEST(CONFIG_UBSAN_INTEGER_WRAP); 44 44 val = -val; 45 45 } 46 46 ··· 51 51 52 52 UBSAN_TEST(CONFIG_UBSAN_DIV_ZERO); 53 53 val /= val2; 54 + } 55 + 56 + static void test_ubsan_truncate_signed(void) 57 + { 58 + volatile long val = LONG_MAX; 59 + volatile int val2 = 0; 60 + 61 + UBSAN_TEST(CONFIG_UBSAN_INTEGER_WRAP); 62 + val2 = val; 54 63 } 55 64 56 65 static void test_ubsan_shift_out_of_bounds(void) ··· 136 127 test_ubsan_sub_overflow, 137 128 test_ubsan_mul_overflow, 138 129 test_ubsan_negate_overflow, 130 + test_ubsan_truncate_signed, 139 131 test_ubsan_shift_out_of_bounds, 140 132 test_ubsan_out_of_bounds, 141 133 test_ubsan_load_invalid_value,
+26 -2
lib/ubsan.c
··· 44 44 case ubsan_shift_out_of_bounds: 45 45 return "UBSAN: shift out of bounds"; 46 46 #endif 47 - #if defined(CONFIG_UBSAN_DIV_ZERO) || defined(CONFIG_UBSAN_SIGNED_WRAP) 47 + #if defined(CONFIG_UBSAN_DIV_ZERO) || defined(CONFIG_UBSAN_INTEGER_WRAP) 48 48 /* 49 49 * SanitizerKind::IntegerDivideByZero and 50 50 * SanitizerKind::SignedIntegerOverflow emit ··· 79 79 case ubsan_type_mismatch: 80 80 return "UBSAN: type mismatch"; 81 81 #endif 82 - #ifdef CONFIG_UBSAN_SIGNED_WRAP 82 + #ifdef CONFIG_UBSAN_INTEGER_WRAP 83 83 /* 84 84 * SanitizerKind::SignedIntegerOverflow emits 85 85 * SanitizerHandler::AddOverflow, SanitizerHandler::SubOverflow, ··· 303 303 } 304 304 EXPORT_SYMBOL(__ubsan_handle_negate_overflow); 305 305 306 + void __ubsan_handle_implicit_conversion(void *_data, void *from_val, void *to_val) 307 + { 308 + struct implicit_conversion_data *data = _data; 309 + char from_val_str[VALUE_LENGTH]; 310 + char to_val_str[VALUE_LENGTH]; 311 + 312 + if (suppress_report(&data->location)) 313 + return; 314 + 315 + val_to_string(from_val_str, sizeof(from_val_str), data->from_type, from_val); 316 + val_to_string(to_val_str, sizeof(to_val_str), data->to_type, to_val); 317 + 318 + ubsan_prologue(&data->location, "implicit-conversion"); 319 + 320 + pr_err("cannot represent %s value %s during %s %s, truncated to %s\n", 321 + data->from_type->type_name, 322 + from_val_str, 323 + type_check_kinds[data->type_check_kind], 324 + data->to_type->type_name, 325 + to_val_str); 326 + 327 + ubsan_epilogue(); 328 + } 329 + EXPORT_SYMBOL(__ubsan_handle_implicit_conversion); 306 330 307 331 void __ubsan_handle_divrem_overflow(void *_data, void *lhs, void *rhs) 308 332 {
+8
lib/ubsan.h
··· 62 62 struct type_descriptor *type; 63 63 }; 64 64 65 + struct implicit_conversion_data { 66 + struct source_location location; 67 + struct type_descriptor *from_type; 68 + struct type_descriptor *to_type; 69 + unsigned char type_check_kind; 70 + }; 71 + 65 72 struct type_mismatch_data { 66 73 struct source_location location; 67 74 struct type_descriptor *type; ··· 149 142 void ubsan_linkage __ubsan_handle_mul_overflow(void *data, void *lhs, void *rhs); 150 143 void ubsan_linkage __ubsan_handle_negate_overflow(void *_data, void *old_val); 151 144 void ubsan_linkage __ubsan_handle_divrem_overflow(void *_data, void *lhs, void *rhs); 145 + void ubsan_linkage __ubsan_handle_implicit_conversion(void *_data, void *lhs, void *rhs); 152 146 void ubsan_linkage __ubsan_handle_type_mismatch(struct type_mismatch_data *data, void *ptr); 153 147 void ubsan_linkage __ubsan_handle_type_mismatch_v1(void *_data, void *ptr); 154 148 void ubsan_linkage __ubsan_handle_out_of_bounds(void *_data, void *index);
+2 -2
scripts/Makefile.lib
··· 166 166 $(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_SANITIZE)$(is-kernel-object)), \ 167 167 $(CFLAGS_UBSAN)) 168 168 _c_flags += $(if $(patsubst n%,, \ 169 - $(UBSAN_SIGNED_WRAP_$(target-stem).o)$(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_SIGNED_WRAP)$(UBSAN_SANITIZE)$(is-kernel-object)), \ 170 - $(CFLAGS_UBSAN_SIGNED_WRAP)) 169 + $(UBSAN_INTEGER_WRAP_$(target-stem).o)$(UBSAN_SANITIZE_$(target-stem).o)$(UBSAN_INTEGER_WRAP)$(UBSAN_SANITIZE)$(is-kernel-object)), \ 170 + $(CFLAGS_UBSAN_INTEGER_WRAP)) 171 171 endif 172 172 173 173 ifeq ($(CONFIG_KCOV),y)
+6 -2
scripts/Makefile.ubsan
··· 14 14 15 15 export CFLAGS_UBSAN := $(ubsan-cflags-y) 16 16 17 - ubsan-signed-wrap-cflags-$(CONFIG_UBSAN_SIGNED_WRAP) += -fsanitize=signed-integer-overflow 18 - export CFLAGS_UBSAN_SIGNED_WRAP := $(ubsan-signed-wrap-cflags-y) 17 + ubsan-integer-wrap-cflags-$(CONFIG_UBSAN_INTEGER_WRAP) += \ 18 + -fsanitize=signed-integer-overflow \ 19 + -fsanitize=unsigned-integer-overflow \ 20 + -fsanitize=implicit-signed-integer-truncation \ 21 + -fsanitize=implicit-unsigned-integer-truncation 22 + export CFLAGS_UBSAN_INTEGER_WRAP := $(ubsan-integer-wrap-cflags-y)