Skip to content

Commit 557f8c5

Browse files
committed
ubsan: Reintroduce signed overflow sanitizer
In order to mitigate unexpected signed wrap-around[1], bring back the signed integer overflow sanitizer. It was removed in commit 6aaa31a ("ubsan: remove overflow checks") because it was effectively a no-op when combined with -fno-strict-overflow (which correctly changes signed overflow from being "undefined" to being explicitly "wrap around"). Compilers are adjusting their sanitizers to trap wrap-around and to detecting common code patterns that should not be instrumented (e.g. "var + offset < var"). Prepare for this and explicitly rename the option from "OVERFLOW" to "WRAP" to more accurately describe the behavior. To annotate intentional wrap-around arithmetic, the helpers wrapping_add/sub/mul_wrap() can be used for individual statements. At the function level, the __signed_wrap attribute can be used to mark an entire function as expecting its signed arithmetic to wrap around. For a single object file the Makefile can use "UBSAN_SIGNED_WRAP_target.o := n" to mark it as wrapping, and for an entire directory, "UBSAN_SIGNED_WRAP := n" can be used. Additionally keep these disabled under CONFIG_COMPILE_TEST for now. Link: KSPP/linux#26 [1] Cc: Miguel Ojeda <ojeda@kernel.org> Cc: Nathan Chancellor <nathan@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Hao Luo <haoluo@google.com> Reviewed-by: Marco Elver <elver@google.com> Reviewed-by: Justin Stitt <justinstitt@google.com> Signed-off-by: Kees Cook <keescook@chromium.org>
1 parent 918327e commit 557f8c5

File tree

7 files changed

+137
-2
lines changed

7 files changed

+137
-2
lines changed

include/linux/compiler_types.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,18 @@ struct ftrace_likely_data {
282282
#define __no_sanitize_or_inline __always_inline
283283
#endif
284284

285+
/* Do not trap wrapping arithmetic within an annotated function. */
286+
#ifdef CONFIG_UBSAN_SIGNED_WRAP
287+
# define __signed_wrap __attribute__((no_sanitize("signed-integer-overflow")))
288+
#else
289+
# define __signed_wrap
290+
#endif
291+
285292
/* Section for code which can't be instrumented at all */
286293
#define __noinstr_section(section) \
287294
noinline notrace __attribute((__section__(section))) \
288295
__no_kcsan __no_sanitize_address __no_profile __no_sanitize_coverage \
289-
__no_sanitize_memory
296+
__no_sanitize_memory __signed_wrap
290297

291298
#define noinstr __noinstr_section(".noinstr.text")
292299

lib/Kconfig.ubsan

+14-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ config UBSAN_LOCAL_BOUNDS
8787

8888
config UBSAN_SHIFT
8989
bool "Perform checking for bit-shift overflows"
90-
default UBSAN
9190
depends on $(cc-option,-fsanitize=shift)
9291
help
9392
This option enables -fsanitize=shift which checks for bit-shift
@@ -116,6 +115,20 @@ config UBSAN_UNREACHABLE
116115
This option enables -fsanitize=unreachable which checks for control
117116
flow reaching an expected-to-be-unreachable position.
118117

118+
config UBSAN_SIGNED_WRAP
119+
bool "Perform checking for signed arithmetic wrap-around"
120+
default UBSAN
121+
depends on !COMPILE_TEST
122+
depends on $(cc-option,-fsanitize=signed-integer-overflow)
123+
help
124+
This option enables -fsanitize=signed-integer-overflow which checks
125+
for wrap-around of any arithmetic operations with signed integers.
126+
This currently performs nearly no instrumentation due to the
127+
kernel's use of -fno-strict-overflow which converts all would-be
128+
arithmetic undefined behavior into wrap-around arithmetic. Future
129+
sanitizer versions will allow for wrap-around checking (rather than
130+
exclusively undefined behavior).
131+
119132
config UBSAN_BOOL
120133
bool "Perform checking for non-boolean values used as boolean"
121134
default UBSAN

lib/test_ubsan.c

+37
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,39 @@ typedef void(*test_ubsan_fp)(void);
1111
#config, IS_ENABLED(config) ? "y" : "n"); \
1212
} while (0)
1313

14+
static void test_ubsan_add_overflow(void)
15+
{
16+
volatile int val = INT_MAX;
17+
18+
UBSAN_TEST(CONFIG_UBSAN_SIGNED_WRAP);
19+
val += 2;
20+
}
21+
22+
static void test_ubsan_sub_overflow(void)
23+
{
24+
volatile int val = INT_MIN;
25+
volatile int val2 = 2;
26+
27+
UBSAN_TEST(CONFIG_UBSAN_SIGNED_WRAP);
28+
val -= val2;
29+
}
30+
31+
static void test_ubsan_mul_overflow(void)
32+
{
33+
volatile int val = INT_MAX / 2;
34+
35+
UBSAN_TEST(CONFIG_UBSAN_SIGNED_WRAP);
36+
val *= 3;
37+
}
38+
39+
static void test_ubsan_negate_overflow(void)
40+
{
41+
volatile int val = INT_MIN;
42+
43+
UBSAN_TEST(CONFIG_UBSAN_SIGNED_WRAP);
44+
val = -val;
45+
}
46+
1447
static void test_ubsan_divrem_overflow(void)
1548
{
1649
volatile int val = 16;
@@ -90,6 +123,10 @@ static void test_ubsan_misaligned_access(void)
90123
}
91124

92125
static const test_ubsan_fp test_ubsan_array[] = {
126+
test_ubsan_add_overflow,
127+
test_ubsan_sub_overflow,
128+
test_ubsan_mul_overflow,
129+
test_ubsan_negate_overflow,
93130
test_ubsan_shift_out_of_bounds,
94131
test_ubsan_out_of_bounds,
95132
test_ubsan_load_invalid_value,

lib/ubsan.c

+68
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,74 @@ static void ubsan_epilogue(void)
222222
check_panic_on_warn("UBSAN");
223223
}
224224

225+
static void handle_overflow(struct overflow_data *data, void *lhs,
226+
void *rhs, char op)
227+
{
228+
229+
struct type_descriptor *type = data->type;
230+
char lhs_val_str[VALUE_LENGTH];
231+
char rhs_val_str[VALUE_LENGTH];
232+
233+
if (suppress_report(&data->location))
234+
return;
235+
236+
ubsan_prologue(&data->location, type_is_signed(type) ?
237+
"signed-integer-overflow" :
238+
"unsigned-integer-overflow");
239+
240+
val_to_string(lhs_val_str, sizeof(lhs_val_str), type, lhs);
241+
val_to_string(rhs_val_str, sizeof(rhs_val_str), type, rhs);
242+
pr_err("%s %c %s cannot be represented in type %s\n",
243+
lhs_val_str,
244+
op,
245+
rhs_val_str,
246+
type->type_name);
247+
248+
ubsan_epilogue();
249+
}
250+
251+
void __ubsan_handle_add_overflow(void *data,
252+
void *lhs, void *rhs)
253+
{
254+
255+
handle_overflow(data, lhs, rhs, '+');
256+
}
257+
EXPORT_SYMBOL(__ubsan_handle_add_overflow);
258+
259+
void __ubsan_handle_sub_overflow(void *data,
260+
void *lhs, void *rhs)
261+
{
262+
handle_overflow(data, lhs, rhs, '-');
263+
}
264+
EXPORT_SYMBOL(__ubsan_handle_sub_overflow);
265+
266+
void __ubsan_handle_mul_overflow(void *data,
267+
void *lhs, void *rhs)
268+
{
269+
handle_overflow(data, lhs, rhs, '*');
270+
}
271+
EXPORT_SYMBOL(__ubsan_handle_mul_overflow);
272+
273+
void __ubsan_handle_negate_overflow(void *_data, void *old_val)
274+
{
275+
struct overflow_data *data = _data;
276+
char old_val_str[VALUE_LENGTH];
277+
278+
if (suppress_report(&data->location))
279+
return;
280+
281+
ubsan_prologue(&data->location, "negation-overflow");
282+
283+
val_to_string(old_val_str, sizeof(old_val_str), data->type, old_val);
284+
285+
pr_err("negation of %s cannot be represented in type %s:\n",
286+
old_val_str, data->type->type_name);
287+
288+
ubsan_epilogue();
289+
}
290+
EXPORT_SYMBOL(__ubsan_handle_negate_overflow);
291+
292+
225293
void __ubsan_handle_divrem_overflow(void *_data, void *lhs, void *rhs)
226294
{
227295
struct overflow_data *data = _data;

lib/ubsan.h

+4
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ typedef s64 s_max;
124124
typedef u64 u_max;
125125
#endif
126126

127+
void __ubsan_handle_add_overflow(void *data, void *lhs, void *rhs);
128+
void __ubsan_handle_sub_overflow(void *data, void *lhs, void *rhs);
129+
void __ubsan_handle_mul_overflow(void *data, void *lhs, void *rhs);
130+
void __ubsan_handle_negate_overflow(void *_data, void *old_val);
127131
void __ubsan_handle_divrem_overflow(void *_data, void *lhs, void *rhs);
128132
void __ubsan_handle_type_mismatch(struct type_mismatch_data *data, void *ptr);
129133
void __ubsan_handle_type_mismatch_v1(void *_data, void *ptr);

scripts/Makefile.lib

+3
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ ifeq ($(CONFIG_UBSAN),y)
177177
_c_flags += $(if $(patsubst n%,, \
178178
$(UBSAN_SANITIZE_$(basetarget).o)$(UBSAN_SANITIZE)y), \
179179
$(CFLAGS_UBSAN))
180+
_c_flags += $(if $(patsubst n%,, \
181+
$(UBSAN_SIGNED_WRAP_$(basetarget).o)$(UBSAN_SANITIZE_$(basetarget).o)$(UBSAN_SIGNED_WRAP)$(UBSAN_SANITIZE)y), \
182+
$(CFLAGS_UBSAN_SIGNED_WRAP))
180183
endif
181184

182185
ifeq ($(CONFIG_KCOV),y)

scripts/Makefile.ubsan

+3
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ ubsan-cflags-$(CONFIG_UBSAN_ENUM) += -fsanitize=enum
1313
ubsan-cflags-$(CONFIG_UBSAN_TRAP) += $(call cc-option,-fsanitize-trap=undefined,-fsanitize-undefined-trap-on-error)
1414

1515
export CFLAGS_UBSAN := $(ubsan-cflags-y)
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)

0 commit comments

Comments
 (0)