diff options
author | Evgeniy Stepanov <eugeni.stepanov@gmail.com> | 2017-08-29 20:03:51 +0000 |
---|---|---|
committer | Evgeniy Stepanov <eugeni.stepanov@gmail.com> | 2017-08-29 20:03:51 +0000 |
commit | b4377ba533acae9ef51757bb3b215155bcd2b8d5 (patch) | |
tree | 568dbbdac2a23c6f9996618ac544a8a002364e88 | |
parent | 5214ac8d2954efaa976ffe7f598980ade819e5dc (diff) |
Minimal runtime for UBSan.
Summary:
An implementation of ubsan runtime library suitable for use in production.
Minimal attack surface.
* No stack traces.
* Definitely no C++ demangling.
* No UBSAN_OPTIONS=log_file=/path (very suid-unfriendly). And no UBSAN_OPTIONS in general.
* as simple as possible
Minimal CPU and RAM overhead.
* Source locations unnecessary in the presence of (split) debug info.
* Values and types (as in A+B overflows T) can be reconstructed from register/stack dumps, once you know what type of error you are looking at.
* above two items save 3% binary size.
When UBSan is used with -ftrap-function=abort, sometimes it is hard to reason about failures. This library replaces abort with a slightly more informative message without much extra overhead. Since ubsan interface in not stable, this code must reside in compiler-rt.
Reviewers: pcc, kcc
Subscribers: srhines, mgorny, aprantl, krytarowski, llvm-commits
Differential Revision: https://reviews.llvm.org/D36810
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@312029 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | include/clang/Driver/Options.td | 4 | ||||
-rw-r--r-- | include/clang/Driver/SanitizerArgs.h | 2 | ||||
-rw-r--r-- | include/clang/Frontend/CodeGenOptions.def | 2 | ||||
-rw-r--r-- | lib/CodeGen/CGExpr.cpp | 49 | ||||
-rw-r--r-- | lib/Driver/SanitizerArgs.cpp | 39 | ||||
-rw-r--r-- | lib/Driver/ToolChains/CommonArgs.cpp | 11 | ||||
-rw-r--r-- | lib/Frontend/CompilerInvocation.cpp | 1 | ||||
-rw-r--r-- | test/CodeGen/unsigned-overflow-minimal.c | 21 | ||||
-rw-r--r-- | test/Driver/fsanitize.c | 29 | ||||
-rw-r--r-- | test/Driver/sanitizer-ld.c | 8 |
10 files changed, 141 insertions, 25 deletions
diff --git a/include/clang/Driver/Options.td b/include/clang/Driver/Options.td index d80e9d6c45..2f8ededbc0 100644 --- a/include/clang/Driver/Options.td +++ b/include/clang/Driver/Options.td @@ -885,6 +885,10 @@ def fsanitize_undefined_trap_on_error : Flag<["-"], "fsanitize-undefined-trap-on Group<f_clang_Group>; def fno_sanitize_undefined_trap_on_error : Flag<["-"], "fno-sanitize-undefined-trap-on-error">, Group<f_clang_Group>; +def fsanitize_minimal_runtime : Flag<["-"], "fsanitize-minimal-runtime">, + Group<f_clang_Group>; +def fno_sanitize_minimal_runtime : Flag<["-"], "fno-sanitize-minimal-runtime">, + Group<f_clang_Group>; def fsanitize_link_cxx_runtime : Flag<["-"], "fsanitize-link-c++-runtime">, Group<f_clang_Group>; def fsanitize_cfi_cross_dso : Flag<["-"], "fsanitize-cfi-cross-dso">, diff --git a/include/clang/Driver/SanitizerArgs.h b/include/clang/Driver/SanitizerArgs.h index b6154da50a..24b009f91b 100644 --- a/include/clang/Driver/SanitizerArgs.h +++ b/include/clang/Driver/SanitizerArgs.h @@ -43,6 +43,7 @@ class SanitizerArgs { bool TsanMemoryAccess = true; bool TsanFuncEntryExit = true; bool TsanAtomics = true; + bool MinimalRuntime = false; public: /// Parses the sanitizer arguments from an argument list. @@ -58,6 +59,7 @@ class SanitizerArgs { !Sanitizers.has(SanitizerKind::Address); } bool needsUbsanRt() const; + bool requiresMinimalRuntime() const { return MinimalRuntime; } bool needsDfsanRt() const { return Sanitizers.has(SanitizerKind::DataFlow); } bool needsSafeStackRt() const { return SafeStackRuntime; } bool needsCfiRt() const; diff --git a/include/clang/Frontend/CodeGenOptions.def b/include/clang/Frontend/CodeGenOptions.def index a806c48765..b58a5d5725 100644 --- a/include/clang/Frontend/CodeGenOptions.def +++ b/include/clang/Frontend/CodeGenOptions.def @@ -152,6 +152,8 @@ CODEGENOPT(SanitizeMemoryTrackOrigins, 2, 0) ///< Enable tracking origins in CODEGENOPT(SanitizeMemoryUseAfterDtor, 1, 0) ///< Enable use-after-delete detection ///< in MemorySanitizer CODEGENOPT(SanitizeCfiCrossDso, 1, 0) ///< Enable cross-dso support in CFI. +CODEGENOPT(SanitizeMinimalRuntime, 1, 0) ///< Use "_minimal" sanitizer runtime for + ///< diagnostics. CODEGENOPT(SanitizeCoverageType, 2, 0) ///< Type of sanitizer coverage ///< instrumentation. CODEGENOPT(SanitizeCoverageIndirectCalls, 1, 0) ///< Enable sanitizer coverage diff --git a/lib/CodeGen/CGExpr.cpp b/lib/CodeGen/CGExpr.cpp index 5f8f34e9bc..bff3fcf6db 100644 --- a/lib/CodeGen/CGExpr.cpp +++ b/lib/CodeGen/CGExpr.cpp @@ -2724,13 +2724,16 @@ static void emitCheckHandlerCall(CodeGenFunction &CGF, assert(IsFatal || RecoverKind != CheckRecoverableKind::Unrecoverable); bool NeedsAbortSuffix = IsFatal && RecoverKind != CheckRecoverableKind::Unrecoverable; + bool MinimalRuntime = CGF.CGM.getCodeGenOpts().SanitizeMinimalRuntime; const SanitizerHandlerInfo &CheckInfo = SanitizerHandlers[CheckHandler]; const StringRef CheckName = CheckInfo.Name; - std::string FnName = - ("__ubsan_handle_" + CheckName + - (CheckInfo.Version ? "_v" + llvm::utostr(CheckInfo.Version) : "") + - (NeedsAbortSuffix ? "_abort" : "")) - .str(); + std::string FnName = "__ubsan_handle_" + CheckName.str(); + if (CheckInfo.Version && !MinimalRuntime) + FnName += "_v" + llvm::utostr(CheckInfo.Version); + if (MinimalRuntime) + FnName += "_minimal"; + if (NeedsAbortSuffix) + FnName += "_abort"; bool MayReturn = !IsFatal || RecoverKind == CheckRecoverableKind::AlwaysRecoverable; @@ -2817,24 +2820,26 @@ void CodeGenFunction::EmitCheck( // representing operand values. SmallVector<llvm::Value *, 4> Args; SmallVector<llvm::Type *, 4> ArgTypes; - Args.reserve(DynamicArgs.size() + 1); - ArgTypes.reserve(DynamicArgs.size() + 1); - - // Emit handler arguments and create handler function type. - if (!StaticArgs.empty()) { - llvm::Constant *Info = llvm::ConstantStruct::getAnon(StaticArgs); - auto *InfoPtr = - new llvm::GlobalVariable(CGM.getModule(), Info->getType(), false, - llvm::GlobalVariable::PrivateLinkage, Info); - InfoPtr->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); - CGM.getSanitizerMetadata()->disableSanitizerForGlobal(InfoPtr); - Args.push_back(Builder.CreateBitCast(InfoPtr, Int8PtrTy)); - ArgTypes.push_back(Int8PtrTy); - } + if (!CGM.getCodeGenOpts().SanitizeMinimalRuntime) { + Args.reserve(DynamicArgs.size() + 1); + ArgTypes.reserve(DynamicArgs.size() + 1); + + // Emit handler arguments and create handler function type. + if (!StaticArgs.empty()) { + llvm::Constant *Info = llvm::ConstantStruct::getAnon(StaticArgs); + auto *InfoPtr = + new llvm::GlobalVariable(CGM.getModule(), Info->getType(), false, + llvm::GlobalVariable::PrivateLinkage, Info); + InfoPtr->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + CGM.getSanitizerMetadata()->disableSanitizerForGlobal(InfoPtr); + Args.push_back(Builder.CreateBitCast(InfoPtr, Int8PtrTy)); + ArgTypes.push_back(Int8PtrTy); + } - for (size_t i = 0, n = DynamicArgs.size(); i != n; ++i) { - Args.push_back(EmitCheckValue(DynamicArgs[i])); - ArgTypes.push_back(IntPtrTy); + for (size_t i = 0, n = DynamicArgs.size(); i != n; ++i) { + Args.push_back(EmitCheckValue(DynamicArgs[i])); + ArgTypes.push_back(IntPtrTy); + } } llvm::FunctionType *FnType = diff --git a/lib/Driver/SanitizerArgs.cpp b/lib/Driver/SanitizerArgs.cpp index 6afa487295..a2a1373c8d 100644 --- a/lib/Driver/SanitizerArgs.cpp +++ b/lib/Driver/SanitizerArgs.cpp @@ -29,6 +29,7 @@ enum : SanitizerMask { NeedsUbsanRt = Undefined | Integer | Nullability | CFI, NeedsUbsanCxxRt = Vptr | CFI, NotAllowedWithTrap = Vptr, + NotAllowedWithMinimalRuntime = Vptr, RequiresPIE = DataFlow, NeedsUnwindTables = Address | Thread | Memory | DataFlow, SupportsCoverage = Address | KernelAddress | Memory | Leak | Undefined | @@ -41,6 +42,7 @@ enum : SanitizerMask { Nullability | LocalBounds | CFI, TrappingDefault = CFI, CFIClasses = CFIVCall | CFINVCall | CFIDerivedCast | CFIUnrelatedCast, + CompatibleWithMinimalRuntime = TrappingSupported, }; enum CoverageFeature { @@ -212,6 +214,10 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, SanitizerMask TrappingKinds = parseSanitizeTrapArgs(D, Args); SanitizerMask InvalidTrappingKinds = TrappingKinds & NotAllowedWithTrap; + MinimalRuntime = + Args.hasFlag(options::OPT_fsanitize_minimal_runtime, + options::OPT_fno_sanitize_minimal_runtime, MinimalRuntime); + // The object size sanitizer should not be enabled at -O0. Arg *OptLevel = Args.getLastArg(options::OPT_O_Group); bool RemoveObjectSizeAtO0 = @@ -249,6 +255,18 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, DiagnosedKinds |= KindsToDiagnose; } Add &= ~InvalidTrappingKinds; + + if (MinimalRuntime) { + if (SanitizerMask KindsToDiagnose = + Add & NotAllowedWithMinimalRuntime & ~DiagnosedKinds) { + std::string Desc = describeSanitizeArg(*I, KindsToDiagnose); + D.Diag(diag::err_drv_argument_not_allowed_with) + << Desc << "-fsanitize-minimal-runtime"; + DiagnosedKinds |= KindsToDiagnose; + } + Add &= ~NotAllowedWithMinimalRuntime; + } + if (SanitizerMask KindsToDiagnose = Add & ~Supported & ~DiagnosedKinds) { std::string Desc = describeSanitizeArg(*I, KindsToDiagnose); D.Diag(diag::err_drv_unsupported_opt_for_target) @@ -285,6 +303,9 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, // Silently discard any unsupported sanitizers implicitly enabled through // group expansion. Add &= ~InvalidTrappingKinds; + if (MinimalRuntime) { + Add &= ~NotAllowedWithMinimalRuntime; + } Add &= Supported; if (Add & Fuzzer) @@ -496,6 +517,21 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, Stats = Args.hasFlag(options::OPT_fsanitize_stats, options::OPT_fno_sanitize_stats, false); + if (MinimalRuntime) { + SanitizerMask IncompatibleMask = + Kinds & ~setGroupBits(CompatibleWithMinimalRuntime); + if (IncompatibleMask) + D.Diag(clang::diag::err_drv_argument_not_allowed_with) + << "-fsanitize-minimal-runtime" + << lastArgumentForMask(D, Args, IncompatibleMask); + + SanitizerMask NonTrappingCfi = Kinds & CFI & ~TrappingKinds; + if (NonTrappingCfi) + D.Diag(clang::diag::err_drv_argument_only_allowed_with) + << "fsanitize-minimal-runtime" + << "fsanitize-trap=cfi"; + } + // Parse -f(no-)?sanitize-coverage flags if coverage is supported by the // enabled sanitizers. for (const auto *Arg : Args) { @@ -762,6 +798,9 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, if (Stats) CmdArgs.push_back("-fsanitize-stats"); + if (MinimalRuntime) + CmdArgs.push_back("-fsanitize-minimal-runtime"); + if (AsanFieldPadding) CmdArgs.push_back(Args.MakeArgString("-fsanitize-address-field-padding=" + llvm::utostr(AsanFieldPadding))); diff --git a/lib/Driver/ToolChains/CommonArgs.cpp b/lib/Driver/ToolChains/CommonArgs.cpp index 690f3b3a96..d052bb2001 100644 --- a/lib/Driver/ToolChains/CommonArgs.cpp +++ b/lib/Driver/ToolChains/CommonArgs.cpp @@ -555,6 +555,7 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args, if (SanArgs.needsAsanRt() && SanArgs.needsSharedAsanRt()) { SharedRuntimes.push_back("asan"); } + // The stats_client library is also statically linked into DSOs. if (SanArgs.needsStatsRt()) StaticRuntimes.push_back("stats_client"); @@ -588,9 +589,13 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args, StaticRuntimes.push_back("tsan_cxx"); } if (SanArgs.needsUbsanRt()) { - StaticRuntimes.push_back("ubsan_standalone"); - if (SanArgs.linkCXXRuntimes()) - StaticRuntimes.push_back("ubsan_standalone_cxx"); + if (SanArgs.requiresMinimalRuntime()) { + StaticRuntimes.push_back("ubsan_minimal"); + } else { + StaticRuntimes.push_back("ubsan_standalone"); + if (SanArgs.linkCXXRuntimes()) + StaticRuntimes.push_back("ubsan_standalone_cxx"); + } } if (SanArgs.needsSafeStackRt()) { NonWholeStaticRuntimes.push_back("safestack"); diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index abf6b0b302..2cc93c1f13 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -830,6 +830,7 @@ static bool ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, InputKind IK, getLastArgIntValue(Args, OPT_fsanitize_memory_track_origins_EQ, 0, Diags); Opts.SanitizeMemoryUseAfterDtor = Args.hasArg(OPT_fsanitize_memory_use_after_dtor); + Opts.SanitizeMinimalRuntime = Args.hasArg(OPT_fsanitize_minimal_runtime); Opts.SanitizeCfiCrossDso = Args.hasArg(OPT_fsanitize_cfi_cross_dso); Opts.SanitizeStats = Args.hasArg(OPT_fsanitize_stats); if (Arg *A = Args.getLastArg(OPT_fsanitize_address_use_after_scope, diff --git a/test/CodeGen/unsigned-overflow-minimal.c b/test/CodeGen/unsigned-overflow-minimal.c new file mode 100644 index 0000000000..d4b89664f8 --- /dev/null +++ b/test/CodeGen/unsigned-overflow-minimal.c @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=unsigned-integer-overflow -fsanitize-minimal-runtime %s -emit-llvm -o - | FileCheck %s + +unsigned long li, lj, lk; + +// CHECK-LABEL: define void @testlongadd() +void testlongadd() { + // CHECK: call void @__ubsan_handle_add_overflow_minimal_abort() + li = lj + lk; +} + +// CHECK-LABEL: define void @testlongsub() +void testlongsub() { + // CHECK: call void @__ubsan_handle_sub_overflow_minimal_abort() + li = lj - lk; +} + +// CHECK-LABEL: define void @testlongmul() +void testlongmul() { + // CHECK: call void @__ubsan_handle_mul_overflow_minimal_abort() + li = lj * lk; +} diff --git a/test/Driver/fsanitize.c b/test/Driver/fsanitize.c index 93b8a7fddd..928f05d9b9 100644 --- a/test/Driver/fsanitize.c +++ b/test/Driver/fsanitize.c @@ -558,3 +558,32 @@ // Make sure there are no *.{o,bc} or -l passed before the ASan library. // CHECK-ASAN-PS4-NOT: {{(\.(o|bc)"? |-l).*-lSceDbgAddressSanitizer_stub_weak}} // CHECK-ASAN-PS4: -lSceDbgAddressSanitizer_stub_weak + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ASAN-MINIMAL +// CHECK-ASAN-MINIMAL: error: invalid argument '-fsanitize-minimal-runtime' not allowed with '-fsanitize=address' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=thread -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-TSAN-MINIMAL +// CHECK-TSAN-MINIMAL: error: invalid argument '-fsanitize-minimal-runtime' not allowed with '-fsanitize=thread' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UBSAN-MINIMAL +// CHECK-UBSAN-MINIMAL: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function),?){19}"}} +// CHECK-UBSAN-MINIMAL: "-fsanitize-minimal-runtime" + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fsanitize=vptr -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UBSAN-VPTR-MINIMAL +// CHECK-UBSAN-VPTR-MINIMAL: error: invalid argument '-fsanitize=vptr' not allowed with '-fsanitize-minimal-runtime' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-minimal-runtime -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-ASAN-UBSAN-MINIMAL +// CHECK-ASAN-UBSAN-MINIMAL: error: invalid argument '-fsanitize-minimal-runtime' not allowed with '-fsanitize=address' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -flto -fvisibility=hidden -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-MINIMAL +// CHECK-CFI-MINIMAL: "-fsanitize=cfi-derived-cast,cfi-icall,cfi-unrelated-cast,cfi-nvcall,cfi-vcall" +// CHECK-CFI-MINIMAL: "-fsanitize-trap=cfi-derived-cast,cfi-icall,cfi-unrelated-cast,cfi-nvcall,cfi-vcall" +// CHECK-CFI-MINIMAL: "-fsanitize-minimal-runtime" + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -fno-sanitize-trap=cfi-icall -flto -fvisibility=hidden -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-NOTRAP-MINIMAL +// CHECK-CFI-NOTRAP-MINIMAL: error: invalid argument 'fsanitize-minimal-runtime' only allowed with 'fsanitize-trap=cfi' + +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -fno-sanitize-trap=cfi-icall -fno-sanitize=cfi-icall -flto -fvisibility=hidden -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-NOICALL-MINIMAL +// CHECK-CFI-NOICALL-MINIMAL: "-fsanitize=cfi-derived-cast,cfi-unrelated-cast,cfi-nvcall,cfi-vcall" +// CHECK-CFI-NOICALL-MINIMAL: "-fsanitize-trap=cfi-derived-cast,cfi-unrelated-cast,cfi-nvcall,cfi-vcall" +// CHECK-CFI-NOICALL-MINIMAL: "-fsanitize-minimal-runtime" diff --git a/test/Driver/sanitizer-ld.c b/test/Driver/sanitizer-ld.c index efdd124577..83231c9937 100644 --- a/test/Driver/sanitizer-ld.c +++ b/test/Driver/sanitizer-ld.c @@ -234,6 +234,14 @@ // CHECK-UBSAN-LINUX-CXX-NOT: libclang_rt.asan // CHECK-UBSAN-LINUX-CXX: "-lpthread" +// RUN: %clang -fsanitize=undefined -fsanitize-minimal-runtime %s -### -o %t.o 2>&1 \ +// RUN: -target i386-unknown-linux -fuse-ld=ld \ +// RUN: --sysroot=%S/Inputs/basic_linux_tree \ +// RUN: | FileCheck --check-prefix=CHECK-UBSAN-MINIMAL-LINUX %s +// CHECK-UBSAN-MINIMAL-LINUX: "{{.*}}ld{{(.exe)?}}" +// CHECK-UBSAN-MINIMAL-LINUX: "-whole-archive" "{{.*}}libclang_rt.ubsan_minimal-i386.a" "-no-whole-archive" +// CHECK-UBSAN-MINIMAL-LINUX: "-lpthread" + // RUN: %clang -fsanitize=address,undefined %s -### -o %t.o 2>&1 \ // RUN: -target i386-unknown-linux -fuse-ld=ld \ // RUN: --sysroot=%S/Inputs/basic_linux_tree \ |