forked from OSchip/llvm-project
[ubsan] Null-check pointers in -fsanitize=vptr (PR33881)
The instrumentation generated by -fsanitize=vptr does not null check a user pointer before loading from it. This causes crashes in the face of UB member calls (this=nullptr), i.e it's causing user programs to crash only after UBSan is turned on. The fix is to make run-time null checking a prerequisite for enabling -fsanitize=vptr, and to then teach UBSan to reuse these run-time null checks to make -fsanitize=vptr safe. Testing: check-clang, check-ubsan, a stage2 ubsan-enabled build Differential Revision: https://reviews.llvm.org/D35735 https://bugs.llvm.org/show_bug.cgi?id=33881 llvm-svn: 309007
This commit is contained in:
parent
657ac14816
commit
bbc953fed4
|
@ -155,7 +155,9 @@ Static Analyzer
|
|||
Undefined Behavior Sanitizer (UBSan)
|
||||
------------------------------------
|
||||
|
||||
...
|
||||
The C++ dynamic type check now requires run-time null checking (i.e,
|
||||
`-fsanitize=vptr` cannot be used without `-fsanitize=null`). This change does
|
||||
not impact users who rely on UBSan check groups (e.g `-fsanitize=undefined`).
|
||||
|
||||
Core Analysis Improvements
|
||||
==========================
|
||||
|
|
|
@ -130,11 +130,11 @@ Available checks are:
|
|||
it is often unintentional, so UBSan offers to catch it.
|
||||
- ``-fsanitize=vla-bound``: A variable-length array whose bound
|
||||
does not evaluate to a positive value.
|
||||
- ``-fsanitize=vptr``: Use of an object whose vptr indicates that
|
||||
it is of the wrong dynamic type, or that its lifetime has not
|
||||
begun or has ended. Incompatible with ``-fno-rtti``. Link must
|
||||
be performed by ``clang++``, not ``clang``, to make sure C++-specific
|
||||
parts of the runtime library and C++ standard libraries are present.
|
||||
- ``-fsanitize=vptr``: Use of an object whose vptr indicates that it is of
|
||||
the wrong dynamic type, or that its lifetime has not begun or has ended.
|
||||
Incompatible with ``-fno-rtti`` and ``-fno-sanitize=null``. Link must be
|
||||
performed by ``clang++``, not ``clang``, to make sure C++-specific parts of
|
||||
the runtime library and C++ standard libraries are present.
|
||||
|
||||
You can also use the following check groups:
|
||||
- ``-fsanitize=undefined``: All of the checks listed above other than
|
||||
|
|
|
@ -230,7 +230,10 @@ def warn_drv_enabling_rtti_with_exceptions : Warning<
|
|||
InGroup<DiagGroup<"rtti-for-exceptions">>;
|
||||
def warn_drv_disabling_vptr_no_rtti_default : Warning<
|
||||
"implicitly disabling vptr sanitizer because rtti wasn't enabled">,
|
||||
InGroup<DiagGroup<"auto-disable-vptr-sanitizer">>;
|
||||
InGroup<AutoDisableVptrSanitizer>;
|
||||
def warn_drv_disabling_vptr_no_null_check : Warning<
|
||||
"implicitly disabling vptr sanitizer because null checking wasn't enabled">,
|
||||
InGroup<AutoDisableVptrSanitizer>;
|
||||
def warn_drv_object_size_disabled_O0 : Warning<
|
||||
"the object size sanitizer has no effect at -O0, but is explicitly enabled: %0">,
|
||||
InGroup<InvalidCommandLineArgument>;
|
||||
|
|
|
@ -27,6 +27,7 @@ def GNUAnonymousStruct : DiagGroup<"gnu-anonymous-struct">;
|
|||
def GNUAutoType : DiagGroup<"gnu-auto-type">;
|
||||
def ArrayBounds : DiagGroup<"array-bounds">;
|
||||
def ArrayBoundsPointerArithmetic : DiagGroup<"array-bounds-pointer-arithmetic">;
|
||||
def AutoDisableVptrSanitizer : DiagGroup<"auto-disable-vptr-sanitizer">;
|
||||
def Availability : DiagGroup<"availability">;
|
||||
def Section : DiagGroup<"section">;
|
||||
def AutoImport : DiagGroup<"auto-import">;
|
||||
|
|
|
@ -604,20 +604,23 @@ void CodeGenFunction::EmitTypeCheck(TypeCheckKind TCK, SourceLocation Loc,
|
|||
auto PtrToAlloca =
|
||||
dyn_cast<llvm::AllocaInst>(Ptr->stripPointerCastsNoFollowAliases());
|
||||
|
||||
llvm::Value *IsNonNull = nullptr;
|
||||
bool IsGuaranteedNonNull =
|
||||
SkippedChecks.has(SanitizerKind::Null) || PtrToAlloca;
|
||||
bool AllowNullPointers = TCK == TCK_DowncastPointer || TCK == TCK_Upcast ||
|
||||
TCK == TCK_UpcastToVirtualBase;
|
||||
if ((SanOpts.has(SanitizerKind::Null) || AllowNullPointers) &&
|
||||
!SkippedChecks.has(SanitizerKind::Null) && !PtrToAlloca) {
|
||||
!IsGuaranteedNonNull) {
|
||||
// The glvalue must not be an empty glvalue.
|
||||
llvm::Value *IsNonNull = Builder.CreateIsNotNull(Ptr);
|
||||
IsNonNull = Builder.CreateIsNotNull(Ptr);
|
||||
|
||||
// The IR builder can constant-fold the null check if the pointer points to
|
||||
// a constant.
|
||||
bool PtrIsNonNull =
|
||||
IsGuaranteedNonNull =
|
||||
IsNonNull == llvm::ConstantInt::getTrue(getLLVMContext());
|
||||
|
||||
// Skip the null check if the pointer is known to be non-null.
|
||||
if (!PtrIsNonNull) {
|
||||
if (!IsGuaranteedNonNull) {
|
||||
if (AllowNullPointers) {
|
||||
// When performing pointer casts, it's OK if the value is null.
|
||||
// Skip the remaining checks in that case.
|
||||
|
@ -691,12 +694,24 @@ void CodeGenFunction::EmitTypeCheck(TypeCheckKind TCK, SourceLocation Loc,
|
|||
// -- the [pointer or glvalue] is used to access a non-static data member
|
||||
// or call a non-static member function
|
||||
CXXRecordDecl *RD = Ty->getAsCXXRecordDecl();
|
||||
bool HasNullCheck = IsGuaranteedNonNull || IsNonNull;
|
||||
if (SanOpts.has(SanitizerKind::Vptr) &&
|
||||
!SkippedChecks.has(SanitizerKind::Vptr) &&
|
||||
!SkippedChecks.has(SanitizerKind::Vptr) && HasNullCheck &&
|
||||
(TCK == TCK_MemberAccess || TCK == TCK_MemberCall ||
|
||||
TCK == TCK_DowncastPointer || TCK == TCK_DowncastReference ||
|
||||
TCK == TCK_UpcastToVirtualBase) &&
|
||||
RD && RD->hasDefinition() && RD->isDynamicClass()) {
|
||||
// Ensure that the pointer is non-null before loading it. If there is no
|
||||
// compile-time guarantee, reuse the run-time null check.
|
||||
if (!IsGuaranteedNonNull) {
|
||||
assert(IsNonNull && "Missing run-time null check");
|
||||
if (!Done)
|
||||
Done = createBasicBlock("vptr.null");
|
||||
llvm::BasicBlock *VptrNotNull = createBasicBlock("vptr.not.null");
|
||||
Builder.CreateCondBr(IsNonNull, VptrNotNull, Done);
|
||||
EmitBlock(VptrNotNull);
|
||||
}
|
||||
|
||||
// Compute a hash of the mangled name of the type.
|
||||
//
|
||||
// FIXME: This is not guaranteed to be deterministic! Move to a
|
||||
|
|
|
@ -306,6 +306,13 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
|
|||
Kinds &= ~Vptr;
|
||||
}
|
||||
|
||||
// Disable -fsanitize=vptr if -fsanitize=null is not enabled (the vptr
|
||||
// instrumentation is broken without run-time null checks).
|
||||
if ((Kinds & Vptr) && !(Kinds & Null)) {
|
||||
Kinds &= ~Vptr;
|
||||
D.Diag(diag::warn_drv_disabling_vptr_no_null_check);
|
||||
}
|
||||
|
||||
// Check that LTO is enabled if we need it.
|
||||
if ((Kinds & NeedsLTO) && !D.isUsingLTO()) {
|
||||
D.Diag(diag::err_drv_argument_only_allowed_with)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// RUN: %clang_cc1 -std=c++11 -fsanitize=signed-integer-overflow,integer-divide-by-zero,float-divide-by-zero,shift-base,shift-exponent,unreachable,return,vla-bound,alignment,null,vptr,object-size,float-cast-overflow,bool,enum,array-bounds,function -fsanitize-recover=signed-integer-overflow,integer-divide-by-zero,float-divide-by-zero,shift-base,shift-exponent,vla-bound,alignment,null,vptr,object-size,float-cast-overflow,bool,enum,array-bounds,function -emit-llvm %s -o - -triple x86_64-linux-gnu | opt -instnamer -S | FileCheck %s
|
||||
// RUN: %clang_cc1 -std=c++11 -fsanitize=vptr,address -fsanitize-recover=vptr,address -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-ASAN
|
||||
// RUN: %clang_cc1 -std=c++11 -fsanitize=vptr -fsanitize-recover=vptr -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=DOWNCAST-NULL
|
||||
// RUN: %clang_cc1 -std=c++11 -fsanitize=null,vptr,address -fsanitize-recover=null,vptr,address -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-ASAN
|
||||
// RUN: %clang_cc1 -std=c++11 -fsanitize=null,vptr -fsanitize-recover=null,vptr -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=DOWNCAST-NULL
|
||||
// RUN: %clang_cc1 -std=c++11 -fsanitize=function -emit-llvm %s -o - -triple x86_64-linux-gnux32 | FileCheck %s --check-prefix=CHECK-X32
|
||||
// RUN: %clang_cc1 -std=c++11 -fsanitize=function -emit-llvm %s -o - -triple i386-linux-gnu | FileCheck %s --check-prefix=CHECK-X86
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// RUN: %clang_cc1 -std=c++11 -triple %itanium_abi_triple -emit-llvm -fsanitize=vptr %s -o - | FileCheck %s
|
||||
// RUN: %clang_cc1 -std=c++11 -triple %itanium_abi_triple -emit-llvm -fsanitize=null,vptr %s -o - | FileCheck %s
|
||||
|
||||
struct Base1 {
|
||||
virtual void f1() {}
|
||||
|
@ -64,6 +64,11 @@ void t4() {
|
|||
// CHECK-NEXT: call void @__ubsan_handle_dynamic_type_cache{{[_a-z]*}}({{.*}} [[UBSAN_TI_DERIVED3]] {{.*}}, i{{[0-9]+}} %[[P1]]
|
||||
|
||||
static_cast<Base1 *>(badp)->f1(); //< No devirt, test 'badp isa Base1'.
|
||||
// We were able to skip the null check on the first type check because 'p'
|
||||
// is backed by an alloca. We can't skip the second null check because 'badp'
|
||||
// is a (bitcast (load ...)).
|
||||
// CHECK: call void @__ubsan_handle_type_mismatch
|
||||
//
|
||||
// CHECK: %[[BADP1:[0-9]+]] = ptrtoint %struct.Base1* {{%[0-9]+}} to i{{[0-9]+}}, !nosanitize
|
||||
// CHECK-NEXT: call void @__ubsan_handle_dynamic_type_cache{{[_a-z]*}}({{.*}} [[UBSAN_TI_BASE1]] {{.*}}, i{{[0-9]+}} %[[BADP1]]
|
||||
}
|
||||
|
@ -76,6 +81,8 @@ void t5() {
|
|||
// CHECK-NEXT: call void @__ubsan_handle_dynamic_type_cache{{[_a-z]*}}({{.*}} [[UBSAN_TI_DERIVED4_1]] {{.*}}, i{{[0-9]+}} %[[P1]]
|
||||
|
||||
static_cast<Base1 *>(badp)->f1(); //< Devirt Base1::f1 to Derived4::f1.
|
||||
// CHECK: call void @__ubsan_handle_type_mismatch
|
||||
//
|
||||
// CHECK: %[[BADP1:[0-9]+]] = ptrtoint %struct.Derived4* {{%[0-9]+}} to i{{[0-9]+}}, !nosanitize
|
||||
// CHECK-NEXT: call void @__ubsan_handle_dynamic_type_cache{{[_a-z]*}}({{.*}} [[UBSAN_TI_DERIVED4_2]] {{.*}}, i{{[0-9]+}} %[[BADP1]]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=alignment | FileCheck %s -check-prefixes=ALIGN,COMMON
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=null | FileCheck %s -check-prefixes=NULL,COMMON
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=object-size | FileCheck %s -check-prefixes=OBJSIZE,COMMON
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=null,vptr | FileCheck %s -check-prefixes=VPTR
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=vptr | FileCheck %s -check-prefixes=VPTR_NO_NULL
|
||||
|
||||
struct A {
|
||||
// COMMON-LABEL: define linkonce_odr void @_ZN1A10do_nothingEv
|
||||
|
@ -24,13 +26,55 @@ struct B {
|
|||
// NULL: icmp ne %struct.B* %{{.*}}, null, !nosanitize
|
||||
|
||||
// OBJSIZE-NOT: call i64 @llvm.objectsize
|
||||
// OBJSIZE: ret void
|
||||
}
|
||||
};
|
||||
|
||||
void force_irgen() {
|
||||
struct Animal {
|
||||
virtual const char *speak() = 0;
|
||||
};
|
||||
|
||||
struct Cat : Animal {
|
||||
const char *speak() override { return "meow"; }
|
||||
};
|
||||
|
||||
struct Dog : Animal {
|
||||
const char *speak() override { return "woof"; }
|
||||
};
|
||||
|
||||
// VPTR-LABEL: define void @_Z12invalid_castP3Cat
|
||||
void invalid_cast(Cat *cat = nullptr) {
|
||||
// First, null check the pointer:
|
||||
//
|
||||
// VPTR: [[ICMP:%.*]] = icmp ne %struct.Dog* {{.*}}, null
|
||||
// VPTR-NEXT: br i1 [[ICMP]]
|
||||
// VPTR: call void @__ubsan_handle_type_mismatch
|
||||
//
|
||||
// Once we're done emitting the null check, reuse the check to see if we can
|
||||
// proceed to the vptr check:
|
||||
//
|
||||
// VPTR: br i1 [[ICMP]]
|
||||
// VPTR: call void @__ubsan_handle_dynamic_type_cache_miss
|
||||
auto *badDog = reinterpret_cast<Dog *>(cat);
|
||||
badDog->speak();
|
||||
}
|
||||
|
||||
// VPTR_NO_NULL-LABEL: define void @_Z13invalid_cast2v
|
||||
void invalid_cast2() {
|
||||
// We've got a pointer to an alloca, so there's no run-time null check needed.
|
||||
// VPTR_NO_NULL-NOT: call void @__ubsan_handle_type_mismatch
|
||||
// VPTR_NO_NULL: call void @__ubsan_handle_dynamic_type_cache_miss
|
||||
Cat cat;
|
||||
cat.speak();
|
||||
}
|
||||
|
||||
int main() {
|
||||
A a;
|
||||
a.do_nothing();
|
||||
|
||||
B b;
|
||||
b.do_nothing();
|
||||
|
||||
invalid_cast();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux -emit-llvm -fsanitize=null %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NULL --check-prefix=ITANIUM
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-windows -emit-llvm -fsanitize=null %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NULL --check-prefix=MSABI
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux -emit-llvm -fsanitize=vptr %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-VPTR --check-prefix=ITANIUM
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-windows -emit-llvm -fsanitize=vptr %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-VPTR --check-prefix=MSABI
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux -emit-llvm -fsanitize=null,vptr %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-VPTR --check-prefix=ITANIUM
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-windows -emit-llvm -fsanitize=null,vptr %s -o - | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-VPTR --check-prefix=MSABI
|
||||
struct T {
|
||||
virtual ~T() {}
|
||||
virtual int v() { return 1; }
|
||||
|
|
|
@ -58,6 +58,10 @@
|
|||
// RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fno-rtti %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-NO-RTTI
|
||||
// CHECK-UNDEFINED-NO-RTTI-NOT: vptr
|
||||
|
||||
// RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fno-sanitize=null %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-NO-NULL
|
||||
// RUN: %clang -target x86_64-linux-gnu -fsanitize=vptr %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-NO-NULL
|
||||
// CHECK-VPTR-NO-NULL: warning: implicitly disabling vptr sanitizer because null checking wasn't enabled
|
||||
|
||||
// RUN: %clang -target x86_64-linux-gnu -fsanitize=address,thread -fno-rtti %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANA-SANT
|
||||
// CHECK-SANA-SANT: '-fsanitize=address' not allowed with '-fsanitize=thread'
|
||||
|
||||
|
@ -362,8 +366,8 @@
|
|||
// RUN: %clang -target x86_64-apple-darwin10 -mmacosx-version-min=10.8 -fsanitize=vptr %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-DARWIN-OLD
|
||||
// CHECK-VPTR-DARWIN-OLD: unsupported option '-fsanitize=vptr' for target 'x86_64-apple-darwin10'
|
||||
|
||||
// RUN: %clang -target x86_64-apple-darwin10 -mmacosx-version-min=10.9 -fsanitize=alignment,vptr %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-DARWIN-NEW
|
||||
// CHECK-VPTR-DARWIN-NEW: -fsanitize=alignment,vptr
|
||||
// RUN: %clang -target x86_64-apple-darwin10 -mmacosx-version-min=10.9 -fsanitize=alignment,null,vptr %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-VPTR-DARWIN-NEW
|
||||
// CHECK-VPTR-DARWIN-NEW: -fsanitize=alignment,null,vptr
|
||||
|
||||
// RUN: %clang -target armv7-apple-ios7 -miphoneos-version-min=7.0 -fsanitize=address %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ASAN-IOS
|
||||
// CHECK-ASAN-IOS: -fsanitize=address
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
// Make sure we only error/warn once, when trying to enable vptr and
|
||||
// undefined and have -fno-rtti
|
||||
// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=undefined -fsanitize=vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR -check-prefix=CHECK-OK %s
|
||||
// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=vptr %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s
|
||||
// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=vptr -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s
|
||||
// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR %s
|
||||
// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=null,vptr %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s
|
||||
// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=null,vptr -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s
|
||||
// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=null,vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR %s
|
||||
// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=undefined %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s
|
||||
// RUN: %clang -### -c -target x86_64-unknown-linux -fsanitize=undefined -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s
|
||||
// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=vptr %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-WARN %s
|
||||
// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=vptr -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s
|
||||
// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR %s
|
||||
// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=null,vptr %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-WARN %s
|
||||
// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=null,vptr -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s
|
||||
// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=null,vptr -fno-rtti %s 2>&1 | FileCheck -check-prefix=CHECK-SAN-ERROR %s
|
||||
// RUN: %clang -### -c -target x86_64-scei-ps4 -fsanitize=undefined -frtti %s 2>&1 | FileCheck -check-prefix=CHECK-OK %s
|
||||
|
||||
// Exceptions + no/default rtti
|
||||
|
|
Loading…
Reference in New Issue