llvm-project/clang/test/CodeGen/ignore-overflow-pattern.c
Justin Stitt 295fe0bd43
[Clang] Re-land Overflow Pattern Exclusions (#104889)
Introduce "-fsanitize-undefined-ignore-overflow-pattern=" which can
be used to disable sanitizer instrumentation for common overflow-dependent
code patterns.

For a wide selection of projects, proper overflow sanitization could
help catch bugs and solve security vulnerabilities. Unfortunately, in
some cases the integer overflow sanitizers are too noisy for their users
and are often left disabled. Providing users with a method to disable
sanitizer instrumentation of common patterns could mean more projects
actually utilize the sanitizers in the first place.

One such project that has opted to not use integer overflow (or
truncation) sanitizers is the Linux Kernel. There has been some
discussion[1] recently concerning mitigation strategies for unexpected
arithmetic overflow. This discussion is still ongoing and a succinct
article[2] accurately sums up the discussion. In summary, many Kernel
developers do not want to introduce more arithmetic wrappers when
most developers understand the code patterns as they are.

Patterns like:

  if (base + offset < base) { ... }

or

  while (i--) { ... }

or

  #define SOME -1UL

are extremely common in a code base like the Linux Kernel. It is
perhaps too much to ask of kernel developers to use arithmetic wrappers
in these cases. For example:

  while (wrapping_post_dec(i)) { ... }

which wraps some builtin would not fly. This would incur too many
changes to existing code; the code churn would be too much, at least too
much to justify turning on overflow sanitizers.

Currently, this commit tackles three pervasive idioms:

1. "if (a + b < a)" or some logically-equivalent re-ordering like "if (a > b + a)"
2. "while (i--)" (for unsigned) a post-decrement always overflows here
3. "-1UL, -2UL, etc" negation of unsigned constants will always overflow

The patterns that are excluded can be chosen from the following list:

- add-overflow-test
- post-decr-while
- negated-unsigned-const

These can be enabled with a comma-separated list:

  -fsanitize-undefined-ignore-overflow-pattern=add-overflow-test,negated-unsigned-const

"all" or "none" may also be used to specify that all patterns should be
excluded or that none should be.

[1] https://lore.kernel.org/all/202404291502.612E0A10@keescook/
[2] https://lwn.net/Articles/979747/

CCs: @efriedma-quic @kees @jyknight @fmayer @vitalybuka
Signed-off-by: Justin Stitt <justinstitt@google.com>
Co-authored-by: Bill Wendling <morbo@google.com>
2024-08-20 20:13:44 +00:00

182 lines
5.2 KiB
C

// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-undefined-ignore-overflow-pattern=all %s -emit-llvm -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-undefined-ignore-overflow-pattern=all -fwrapv %s -emit-llvm -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-undefined-ignore-overflow-pattern=add-overflow-test %s -emit-llvm -o - | FileCheck %s --check-prefix=ADD
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-undefined-ignore-overflow-pattern=negated-unsigned-const %s -emit-llvm -o - | FileCheck %s --check-prefix=NEGATE
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-undefined-ignore-overflow-pattern=post-decr-while %s -emit-llvm -o - | FileCheck %s --check-prefix=WHILE
// Ensure some common overflow-dependent or overflow-prone code patterns don't
// trigger the overflow sanitizers. In many cases, overflow warnings caused by
// these patterns are seen as "noise" and result in users turning off
// sanitization all together.
// A pattern like "if (a + b < a)" simply checks for overflow and usually means
// the user is trying to handle it gracefully.
// Similarly, a pattern resembling "while (i--)" is extremely common and
// warning on its inevitable overflow can be seen as superfluous. Do note that
// using "i" in future calculations can be tricky because it will still
// wrap-around.
// Another common pattern that, in some cases, is found to be too noisy is
// unsigned negation, for example:
// unsigned long A = -1UL;
// CHECK-NOT: handle{{.*}}overflow
extern unsigned a, b, c;
extern unsigned some(void);
// ADD-LABEL: @basic_commutativity
// WHILE-LABEL: @basic_commutativity
// NEGATE-LABEL: @basic_commutativity
// WHILE: handler.add_overflow
// NEGATE: handler.add_overflow
// ADD-NOT: handler.add_overflow
void basic_commutativity(void) {
if (a + b < a)
c = 9;
if (a + b < b)
c = 9;
if (b + a < b)
c = 9;
if (b + a < a)
c = 9;
if (a > a + b)
c = 9;
if (a > b + a)
c = 9;
if (b > a + b)
c = 9;
if (b > b + a)
c = 9;
}
// ADD-LABEL: @arguments_and_commutativity
// WHILE-LABEL: @arguments_and_commutativity
// NEGATE-LABEL: @arguments_and_commutativity
// WHILE: handler.add_overflow
// NEGATE: handler.add_overflow
// ADD-NOT: handler.add_overflow
void arguments_and_commutativity(unsigned V1, unsigned V2) {
if (V1 + V2 < V1)
c = 9;
if (V1 + V2 < V2)
c = 9;
if (V2 + V1 < V2)
c = 9;
if (V2 + V1 < V1)
c = 9;
if (V1 > V1 + V2)
c = 9;
if (V1 > V2 + V1)
c = 9;
if (V2 > V1 + V2)
c = 9;
if (V2 > V2 + V1)
c = 9;
}
// ADD-LABEL: @pointers
// WHILE-LABEL: @pointers
// NEGATE-LABEL: @pointers
// WHILE: handler.add_overflow
// NEGATE: handler.add_overflow
// ADD-NOT: handler.add_overflow
void pointers(unsigned *P1, unsigned *P2, unsigned V1) {
if (*P1 + *P2 < *P1)
c = 9;
if (*P1 + V1 < V1)
c = 9;
if (V1 + *P2 < *P2)
c = 9;
}
struct OtherStruct {
unsigned foo, bar;
};
struct MyStruct {
unsigned base, offset;
struct OtherStruct os;
};
extern struct MyStruct ms;
// ADD-LABEL: @structs
// WHILE-LABEL: @structs
// NEGATE-LABEL: @structs
// WHILE: handler.add_overflow
// NEGATE: handler.add_overflow
// ADD-NOT: handler.add_overflow
void structs(void) {
if (ms.base + ms.offset < ms.base)
c = 9;
}
// ADD-LABEL: @nestedstructs
// WHILE-LABEL: @nestedstructs
// NEGATE-LABEL: @nestedstructs
// WHILE: handler.add_overflow
// NEGATE: handler.add_overflow
// ADD-NOT: handler.add_overflow
void nestedstructs(void) {
if (ms.os.foo + ms.os.bar < ms.os.foo)
c = 9;
}
// ADD-LABEL: @constants
// WHILE-LABEL: @constants
// NEGATE-LABEL: @constants
// WHILE: handler.add_overflow
// NEGATE: handler.add_overflow
// ADD-NOT: handler.add_overflow
// Normally, this would be folded into a simple call to the overflow handler
// and a store. Excluding this pattern results in just a store.
void constants(void) {
unsigned base = 4294967295;
unsigned offset = 1;
if (base + offset < base)
c = 9;
}
// ADD-LABEL: @common_while
// NEGATE-LABEL: @common_while
// WHILE-LABEL: @common_while
// ADD: usub.with.overflow
// NEGATE: usub.with.overflow
// WHILE: %dec = add i32 %0, -1
void common_while(unsigned i) {
// This post-decrement usually causes overflow sanitizers to trip on the very
// last operation.
while (i--) {
some();
}
}
// ADD-LABEL: @negation
// NEGATE-LABEL: @negation
// WHILE-LABEL @negation
// ADD: negate_overflow
// NEGATE-NOT: negate_overflow
// WHILE: negate_overflow
// Normally, these assignments would trip the unsigned overflow sanitizer.
void negation(void) {
#define SOME -1UL
unsigned long A = -1UL;
unsigned long B = -2UL;
unsigned long C = -SOME;
(void)A;(void)B;(void)C;
}
// ADD-LABEL: @function_call
// WHILE-LABEL: @function_call
// NEGATE-LABEL: @function_call
// WHILE: handler.add_overflow
// NEGATE: handler.add_overflow
// ADD-NOT: handler.add_overflow
void function_call(void) {
if (b + some() < b)
c = 9;
}