Skip to content

[Clang] Implement labelled type filtering for overflow/truncation sanitizers w/ SSCLs #107332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,14 @@ Sanitizers
This new flag should allow those projects to enable integer sanitizers with
less noise.

- Arithmetic overflow sanitizers ``-fsanitize=signed-integer-overflow`` and
``-fsanitize=unsigned-integer-overflow`` as well as the implicit integer
truncation sanitizers ``-fsanitize=implicit-signed-integer-truncation`` and
``-fsanitize=implicit-unsigned-integer-truncation`` now properly support the
"type" prefix within `Sanitizer Special Case Lists (SSCL)
<https://clang.llvm.org/docs/SanitizerSpecialCaseList.html>`_. See that link
for examples.

Python Binding Changes
----------------------
- Fixed an issue that led to crashes when calling ``Type.get_exception_specification_kind``.
Expand Down
61 changes: 58 additions & 3 deletions clang/docs/SanitizerSpecialCaseList.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ file at compile-time.
Goal and usage
==============

Users of sanitizer tools, such as :doc:`AddressSanitizer`, :doc:`ThreadSanitizer`
or :doc:`MemorySanitizer` may want to disable or alter some checks for
certain source-level entities to:
Users of sanitizer tools, such as :doc:`AddressSanitizer`,
:doc:`ThreadSanitizer`, :doc:`MemorySanitizer` or :doc:`UndefinedBehaviorSanitizer`
may want to disable or alter some checks for certain source-level entities to:

* speedup hot function, which is known to be correct;
* ignore a function that does some low-level magic (e.g. walks through the
Expand Down Expand Up @@ -48,6 +48,61 @@ Example
$ clang -fsanitize=address -fsanitize-ignorelist=ignorelist.txt foo.c ; ./a.out
# No error report here.

Usage with UndefinedBehaviorSanitizer
=====================================

The arithmetic overflow sanitizers ``unsigned-integer-overflow`` and
``signed-integer-overflow`` as well as the implicit integer truncation
sanitizers ``implicit-signed-integer-truncation`` and
``implicit-unsigned-integer-truncation`` support the ability to adjust
instrumentation based on type.

By default, supported sanitizers will have their instrumentation disabled for
types specified within an ignorelist.

.. code-block:: bash

$ cat foo.c
void foo() {
int a = 2147483647; // INT_MAX
++a; // Normally, an overflow with -fsanitize=signed-integer-overflow
}
$ cat ignorelist.txt
[signed-integer-overflow]
type:int
$ clang -fsanitize=signed-integer-overflow -fsanitize-ignorelist=ignorelist.txt foo.c ; ./a.out
# no signed-integer-overflow error

For example, supplying the above ``ignorelist.txt`` to
``-fsanitize-ignorelist=ignorelist.txt`` disables overflow sanitizer
instrumentation for arithmetic operations containing values of type ``int``.

The ``=sanitize`` category is also supported. Any types assigned to the
``sanitize`` category will have their sanitizer instrumentation remain. If the
same type appears within or across ignorelists with different categories the
``sanitize`` category takes precedence -- regardless of order.

With this, one may disable instrumentation for some or all types and
specifically allow instrumentation for one or many types -- including types
created via ``typedef``. This is a way to achieve a sort of "allowlist" for
supported sanitizers.

.. code-block:: bash

$ cat ignorelist.txt
[implicit-signed-integer-truncation]
type:*
type:T=sanitize

$ cat foo.c
typedef char T;
typedef char U;
void foo(int toobig) {
T a = toobig; // instrumented
U b = toobig; // not instrumented
char c = toobig; // also not instrumented
}

Format
======

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,9 @@ class ASTContext : public RefCountedBase<ASTContext> {

const NoSanitizeList &getNoSanitizeList() const { return *NoSanitizeL; }

bool isTypeIgnoredBySanitizer(const SanitizerMask &Mask,
const QualType &Ty) const;

const XRayFunctionFilter &getXRayFilter() const {
return *XRayFilter;
}
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,15 @@ ASTContext::getCanonicalTemplateTemplateParmDecl(
return CanonTTP;
}

/// Check if a type can have its sanitizer instrumentation elided based on its
/// presence within an ignorelist.
bool ASTContext::isTypeIgnoredBySanitizer(const SanitizerMask &Mask,
const QualType &Ty) const {
std::string TyName = Ty.getUnqualifiedType().getAsString(getPrintingPolicy());
return NoSanitizeL->containsType(Mask, TyName) &&
!NoSanitizeL->containsType(Mask, TyName, "sanitize");
}

TargetCXXABI::Kind ASTContext::getCXXABIKind() const {
auto Kind = getTargetInfo().getCXXABI().getKind();
return getLangOpts().CXXABI.value_or(Kind);
Expand Down
36 changes: 32 additions & 4 deletions clang/lib/CodeGen/CGExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ static bool CanElideOverflowCheck(const ASTContext &Ctx, const BinOpInfo &Op) {
if (!Op.mayHaveIntegerOverflow())
return true;

if (Op.Ty->isSignedIntegerType() &&
Ctx.isTypeIgnoredBySanitizer(SanitizerKind::SignedIntegerOverflow,
Op.Ty)) {
return true;
}

if (Op.Ty->isUnsignedIntegerType() &&
Ctx.isTypeIgnoredBySanitizer(SanitizerKind::UnsignedIntegerOverflow,
Op.Ty)) {
return true;
}

const UnaryOperator *UO = dyn_cast<UnaryOperator>(Op.E);

if (UO && UO->getOpcode() == UO_Minus &&
Expand Down Expand Up @@ -1121,6 +1133,10 @@ void ScalarExprEmitter::EmitIntegerTruncationCheck(Value *Src, QualType SrcType,
if (!CGF.SanOpts.has(Check.second.second))
return;

// Does some SSCL ignore this type?
if (CGF.getContext().isTypeIgnoredBySanitizer(Check.second.second, DstType))
return;

llvm::Constant *StaticArgs[] = {
CGF.EmitCheckSourceLocation(Loc), CGF.EmitCheckTypeDescriptor(SrcType),
CGF.EmitCheckTypeDescriptor(DstType),
Expand Down Expand Up @@ -1231,6 +1247,15 @@ void ScalarExprEmitter::EmitIntegerSignChangeCheck(Value *Src, QualType SrcType,
// Because here sign change check is interchangeable with truncation check.
return;
}
// Does an SSCL have an entry for the DstType under its respective sanitizer
// section?
if (DstSigned && CGF.getContext().isTypeIgnoredBySanitizer(
SanitizerKind::ImplicitSignedIntegerTruncation, DstType))
return;
if (!DstSigned &&
CGF.getContext().isTypeIgnoredBySanitizer(
SanitizerKind::ImplicitUnsignedIntegerTruncation, DstType))
return;
// That's it. We can't rule out any more cases with the data we have.

CodeGenFunction::SanitizerScope SanScope(&CGF);
Expand Down Expand Up @@ -2780,10 +2805,11 @@ llvm::Value *ScalarExprEmitter::EmitIncDecConsiderOverflowBehavior(
return Builder.CreateNSWAdd(InVal, Amount, Name);
[[fallthrough]];
case LangOptions::SOB_Trapping:
if (!E->canOverflow())
BinOpInfo Info = createBinOpInfoFromIncDec(
E, InVal, IsInc, E->getFPFeaturesInEffect(CGF.getLangOpts()));
if (!E->canOverflow() || CanElideOverflowCheck(CGF.getContext(), Info))
return Builder.CreateNSWAdd(InVal, Amount, Name);
return EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
E, InVal, IsInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
return EmitOverflowCheckedBinOp(Info);
}
llvm_unreachable("Unknown SignedOverflowBehaviorTy");
}
Expand Down Expand Up @@ -2986,7 +3012,9 @@ ScalarExprEmitter::EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
value = EmitIncDecConsiderOverflowBehavior(E, value, isInc);
} else if (E->canOverflow() && type->isUnsignedIntegerType() &&
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) &&
!excludeOverflowPattern) {
!excludeOverflowPattern &&
!CGF.getContext().isTypeIgnoredBySanitizer(
SanitizerKind::UnsignedIntegerOverflow, E->getType())) {
value = EmitOverflowCheckedBinOp(createBinOpInfoFromIncDec(
E, value, isInc, E->getFPFeaturesInEffect(CGF.getLangOpts())));
} else {
Expand Down
58 changes: 58 additions & 0 deletions clang/test/CodeGen/ubsan-type-ignorelist-category-2.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// RUN: rm -rf %t
// RUN: split-file %s %t

// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/order-0.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/order-1.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/order-2.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/order-3.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/order-4.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/order-5.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/order-6.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/order-7.ignorelist -emit-llvm %t/test.c -o - | FileCheck %s

// The same type can appear multiple times within an ignorelist. This is a test
// to make sure "=sanitize" has priority regardless of the order in which
// duplicate type entries appear. This is a precautionary measure; we would
// much rather eagerly sanitize than silently forgo sanitization.

//--- order-0.ignorelist
type:int
type:int=sanitize

//--- order-1.ignorelist
type:int=sanitize
type:int

//--- order-2.ignorelist
type:in*
type:int=sanitize

//--- order-3.ignorelist
type:in*=sanitize
type:int

//--- order-4.ignorelist
type:int
type:in*=sanitize

//--- order-5.ignorelist
type:int=sanitize
type:in*

//--- order-6.ignorelist
type:int=sanitize
type:in*

//--- order-7.ignorelist
type:int
type:int=sanitize




//--- test.c
// CHECK-LABEL: @test
void test(int A) {
// CHECK: @llvm.sadd.with.overflow.i32
++A;
}
98 changes: 98 additions & 0 deletions clang/test/CodeGen/ubsan-type-ignorelist-category.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// RUN: rm -rf %t
// RUN: split-file %s %t

// Verify ubsan doesn't emit checks for ignorelisted types
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/int.ignorelist -emit-llvm %t/test.cpp -o - | FileCheck %s --check-prefix=INT
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/nosection.ignorelist -emit-llvm %t/test.cpp -o - | FileCheck %s --check-prefix=INT
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/nosan-same-as-no-category.ignorelist -emit-llvm %t/test.cpp -o - | FileCheck %s --check-prefix=INT
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=signed-integer-overflow,unsigned-integer-overflow -fsanitize-ignorelist=%t/myty.ignorelist -emit-llvm %t/test.cpp -o - | FileCheck %s --check-prefix=MYTY
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=implicit-signed-integer-truncation,implicit-unsigned-integer-truncation -fsanitize-ignorelist=%t/trunc.ignorelist -emit-llvm %t/test.cpp -o - | FileCheck %s --check-prefix=TRUNC
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=implicit-signed-integer-truncation,implicit-unsigned-integer-truncation -fsanitize-ignorelist=%t/docs.ignorelist -emit-llvm %t/test.cpp -o - | FileCheck %s --check-prefix=TRUNC2

//--- int.ignorelist
[{unsigned-integer-overflow,signed-integer-overflow}]
type:int

//--- nosection.ignorelist
type:int

//--- nosan-same-as-no-category.ignorelist
type:int

//--- myty.ignorelist
[{unsigned-integer-overflow,signed-integer-overflow}]
type:*
type:myty=sanitize

//--- trunc.ignorelist
[{implicit-signed-integer-truncation,implicit-unsigned-integer-truncation}]
type:char
type:unsigned char

//--- docs.ignorelist
[implicit-signed-integer-truncation]
type:*
type:T=sanitize

//--- test.cpp
// INT-LABEL: ignore_int
void ignore_int(int A, int B, unsigned C, unsigned D, long E) {
// INT: llvm.uadd.with.overflow.i32
(void)(C+D);
// INT-NOT: llvm.sadd.with.overflow.i32
(void)(A+B);
// INT: llvm.sadd.with.overflow.i64
(void)(++E);
}


typedef unsigned long myty;
typedef myty derivative;
// INT-LABEL: ignore_all_except_myty
// MYTY-LABEL: ignore_all_except_myty
void ignore_all_except_myty(myty A, myty B, int C, unsigned D, derivative E) {
// MYTY-NOT: llvm.sadd.with.overflow.i32
(void)(++C);

// MYTY-NOT: llvm.uadd.with.overflow.i32
(void)(D+D);

// MYTY-NOT: llvm.umul.with.overflow.i64
(void)(E*2);

// MYTY: llvm.uadd.with.overflow.i64
(void)(A+B);
}

// INT-LABEL: truncation
// MYTY-LABEL: truncation
// TRUNC-LABEL: truncation
void truncation(char A, int B, unsigned char C, short D) {
// TRUNC-NOT: %handler.implicit_conversion
A = B;
// TRUNC-NOT: %handler.implicit_conversion
A = C;
// TRUNC-NOT: %handler.implicit_conversion
C = B;

// TRUNC: %handler.implicit_conversion
D = B;

(void)A;
(void)D;
}


// Matches the example from clang/docs/SanitizerSpecialCaseList.rst
typedef char T;
typedef char U;
// TRUNC2-LABEL: docs_example
void docs_example(int toobig) {
// TRUNC2: %handler.implicit_conversion
T a = toobig;
// TRUNC2-NOT: %handler.implicit_conversion
U b = toobig;
// TRUNC2-NOT: %handler.implicit_conversion
char c = toobig;
}