diff options
author | Roman Lebedev <lebedev.ri@gmail.com> | 2018-07-30 18:58:30 +0000 |
---|---|---|
committer | Roman Lebedev <lebedev.ri@gmail.com> | 2018-07-30 18:58:30 +0000 |
commit | b69ba22773e0c567c8c6e5bde5388d58d3e75110 (patch) | |
tree | d602f5b9407829138f42e4c3fd0199627de6a271 /clang/lib | |
parent | eb4a9bc3432320082e7e8bad583f833caed88142 (diff) | |
download | bcm5719-llvm-b69ba22773e0c567c8c6e5bde5388d58d3e75110.tar.gz bcm5719-llvm-b69ba22773e0c567c8c6e5bde5388d58d3e75110.zip |
[clang][ubsan] Implicit Conversion Sanitizer - integer truncation - clang part
Summary:
C and C++ are interesting languages. They are statically typed, but weakly.
The implicit conversions are allowed. This is nice, allows to write code
while balancing between getting drowned in everything being convertible,
and nothing being convertible. As usual, this comes with a price:
```
unsigned char store = 0;
bool consume(unsigned int val);
void test(unsigned long val) {
if (consume(val)) {
// the 'val' is `unsigned long`, but `consume()` takes `unsigned int`.
// If their bit widths are different on this platform, the implicit
// truncation happens. And if that `unsigned long` had a value bigger
// than UINT_MAX, then you may or may not have a bug.
// Similarly, integer addition happens on `int`s, so `store` will
// be promoted to an `int`, the sum calculated (0+768=768),
// and the result demoted to `unsigned char`, and stored to `store`.
// In this case, the `store` will still be 0. Again, not always intended.
store = store + 768; // before addition, 'store' was promoted to int.
}
// But yes, sometimes this is intentional.
// You can either make the conversion explicit
(void)consume((unsigned int)val);
// or mask the value so no bits will be *implicitly* lost.
(void)consume((~((unsigned int)0)) & val);
}
```
Yes, there is a `-Wconversion`` diagnostic group, but first, it is kinda
noisy, since it warns on everything (unlike sanitizers, warning on an
actual issues), and second, there are cases where it does **not** warn.
So a Sanitizer is needed. I don't have any motivational numbers, but i know
i had this kind of problem 10-20 times, and it was never easy to track down.
The logic to detect whether an truncation has happened is pretty simple
if you think about it - https://godbolt.org/g/NEzXbb - basically, just
extend (using the new, not original!, signedness) the 'truncated' value
back to it's original width, and equality-compare it with the original value.
The most non-trivial thing here is the logic to detect whether this
`ImplicitCastExpr` AST node is **actually** an implicit conversion, //or//
part of an explicit cast. Because the explicit casts are modeled as an outer
`ExplicitCastExpr` with some `ImplicitCastExpr`'s as **direct** children.
https://godbolt.org/g/eE1GkJ
Nowadays, we can just use the new `part_of_explicit_cast` flag, which is set
on all the implicitly-added `ImplicitCastExpr`'s of an `ExplicitCastExpr`.
So if that flag is **not** set, then it is an actual implicit conversion.
As you may have noted, this isn't just named `-fsanitize=implicit-integer-truncation`.
There are potentially some more implicit conversions to be warned about.
Namely, implicit conversions that result in sign change; implicit conversion
between different floating point types, or between fp and an integer,
when again, that conversion is lossy.
One thing i know isn't handled is bitfields.
This is a clang part.
The compiler-rt part is D48959.
Fixes [[ https://bugs.llvm.org/show_bug.cgi?id=21530 | PR21530 ]], [[ https://bugs.llvm.org/show_bug.cgi?id=37552 | PR37552 ]], [[ https://bugs.llvm.org/show_bug.cgi?id=35409 | PR35409 ]].
Partially fixes [[ https://bugs.llvm.org/show_bug.cgi?id=9821 | PR9821 ]].
Fixes https://github.com/google/sanitizers/issues/940. (other than sign-changing implicit conversions)
Reviewers: rjmccall, rsmith, samsonov, pcc, vsk, eugenis, efriedma, kcc, erichkeane
Reviewed By: rsmith, vsk, erichkeane
Subscribers: erichkeane, klimek, #sanitizers, aaron.ballman, RKSimon, dtzWill, filcab, danielaustin, ygribov, dvyukov, milianw, mclow.lists, cfe-commits, regehr
Tags: #sanitizers
Differential Revision: https://reviews.llvm.org/D48958
llvm-svn: 338288
Diffstat (limited to 'clang/lib')
-rw-r--r-- | clang/lib/CodeGen/CGExprScalar.cpp | 105 | ||||
-rw-r--r-- | clang/lib/CodeGen/CodeGenFunction.h | 1 | ||||
-rw-r--r-- | clang/lib/Driver/SanitizerArgs.cpp | 10 | ||||
-rw-r--r-- | clang/lib/Driver/ToolChain.cpp | 4 |
4 files changed, 97 insertions, 23 deletions
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index 783f74c5026..b9fcb507a6e 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -299,13 +299,31 @@ public: Value *Src, QualType SrcType, QualType DstType, llvm::Type *DstTy, SourceLocation Loc); + /// Known implicit conversion check kinds. + /// Keep in sync with the enum of the same name in ubsan_handlers.h + enum ImplicitConversionCheckKind : unsigned char { + ICCK_IntegerTruncation = 0, + }; + + /// Emit a check that an [implicit] truncation of an integer does not + /// discard any bits. It is not UB, so we use the value after truncation. + void EmitIntegerTruncationCheck(Value *Src, QualType SrcType, Value *Dst, + QualType DstType, SourceLocation Loc); + /// Emit a conversion from the specified type to the specified destination /// type, both of which are LLVM scalar types. - Value *EmitScalarConversion(Value *Src, QualType SrcTy, QualType DstTy, - SourceLocation Loc); + struct ScalarConversionOpts { + bool TreatBooleanAsSigned; + bool EmitImplicitIntegerTruncationChecks; - Value *EmitScalarConversion(Value *Src, QualType SrcTy, QualType DstTy, - SourceLocation Loc, bool TreatBooleanAsSigned); + ScalarConversionOpts() + : TreatBooleanAsSigned(false), + EmitImplicitIntegerTruncationChecks(false) {} + }; + Value * + EmitScalarConversion(Value *Src, QualType SrcTy, QualType DstTy, + SourceLocation Loc, + ScalarConversionOpts Opts = ScalarConversionOpts()); /// Emit a conversion from the specified complex type to the specified /// destination type, where the destination type is an LLVM scalar type. @@ -923,18 +941,59 @@ void ScalarExprEmitter::EmitFloatConversionCheck( SanitizerHandler::FloatCastOverflow, StaticArgs, OrigSrc); } -/// Emit a conversion from the specified type to the specified destination type, -/// both of which are LLVM scalar types. -Value *ScalarExprEmitter::EmitScalarConversion(Value *Src, QualType SrcType, - QualType DstType, - SourceLocation Loc) { - return EmitScalarConversion(Src, SrcType, DstType, Loc, false); +void ScalarExprEmitter::EmitIntegerTruncationCheck(Value *Src, QualType SrcType, + Value *Dst, QualType DstType, + SourceLocation Loc) { + if (!CGF.SanOpts.has(SanitizerKind::ImplicitIntegerTruncation)) + return; + + llvm::Type *SrcTy = Src->getType(); + llvm::Type *DstTy = Dst->getType(); + + // We only care about int->int conversions here. + // We ignore conversions to/from pointer and/or bool. + if (!(SrcType->isIntegerType() && DstType->isIntegerType())) + return; + + assert(isa<llvm::IntegerType>(SrcTy) && isa<llvm::IntegerType>(DstTy) && + "clang integer type lowered to non-integer llvm type"); + + unsigned SrcBits = SrcTy->getScalarSizeInBits(); + unsigned DstBits = DstTy->getScalarSizeInBits(); + // This must be truncation. Else we do not care. + if (SrcBits <= DstBits) + return; + + assert(!DstType->isBooleanType() && "we should not get here with booleans."); + + CodeGenFunction::SanitizerScope SanScope(&CGF); + + llvm::Value *Check = nullptr; + + // 1. Extend the truncated value back to the same width as the Src. + bool InputSigned = DstType->isSignedIntegerOrEnumerationType(); + Check = Builder.CreateIntCast(Dst, SrcTy, InputSigned, "anyext"); + // 2. Equality-compare with the original source value + Check = Builder.CreateICmpEQ(Check, Src, "truncheck"); + // If the comparison result is 'i1 false', then the truncation was lossy. + + llvm::Constant *StaticArgs[] = { + CGF.EmitCheckSourceLocation(Loc), CGF.EmitCheckTypeDescriptor(SrcType), + CGF.EmitCheckTypeDescriptor(DstType), + llvm::ConstantInt::get(Builder.getInt8Ty(), ICCK_IntegerTruncation)}; + CGF.EmitCheck(std::make_pair(Check, SanitizerKind::ImplicitIntegerTruncation), + SanitizerHandler::ImplicitConversion, StaticArgs, {Src, Dst}); } +/// Emit a conversion from the specified type to the specified destination type, +/// both of which are LLVM scalar types. Value *ScalarExprEmitter::EmitScalarConversion(Value *Src, QualType SrcType, QualType DstType, SourceLocation Loc, - bool TreatBooleanAsSigned) { + ScalarConversionOpts Opts) { + QualType NoncanonicalSrcType = SrcType; + QualType NoncanonicalDstType = DstType; + SrcType = CGF.getContext().getCanonicalType(SrcType); DstType = CGF.getContext().getCanonicalType(DstType); if (SrcType == DstType) return Src; @@ -1083,7 +1142,7 @@ Value *ScalarExprEmitter::EmitScalarConversion(Value *Src, QualType SrcType, if (isa<llvm::IntegerType>(SrcTy)) { bool InputSigned = SrcType->isSignedIntegerOrEnumerationType(); - if (SrcType->isBooleanType() && TreatBooleanAsSigned) { + if (SrcType->isBooleanType() && Opts.TreatBooleanAsSigned) { InputSigned = true; } if (isa<llvm::IntegerType>(DstTy)) @@ -1118,6 +1177,10 @@ Value *ScalarExprEmitter::EmitScalarConversion(Value *Src, QualType SrcType, } } + if (Opts.EmitImplicitIntegerTruncationChecks) + EmitIntegerTruncationCheck(Src, NoncanonicalSrcType, Res, + NoncanonicalDstType, Loc); + return Res; } @@ -1812,16 +1875,26 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { return Builder.CreateVectorSplat(NumElements, Elt, "splat"); } - case CK_IntegralCast: + case CK_IntegralCast: { + ScalarConversionOpts Opts; + if (CGF.SanOpts.has(SanitizerKind::ImplicitIntegerTruncation)) { + if (auto *ICE = dyn_cast<ImplicitCastExpr>(CE)) + Opts.EmitImplicitIntegerTruncationChecks = !ICE->isPartOfExplicitCast(); + } + return EmitScalarConversion(Visit(E), E->getType(), DestTy, + CE->getExprLoc(), Opts); + } case CK_IntegralToFloating: case CK_FloatingToIntegral: case CK_FloatingCast: return EmitScalarConversion(Visit(E), E->getType(), DestTy, CE->getExprLoc()); - case CK_BooleanToSignedIntegral: + case CK_BooleanToSignedIntegral: { + ScalarConversionOpts Opts; + Opts.TreatBooleanAsSigned = true; return EmitScalarConversion(Visit(E), E->getType(), DestTy, - CE->getExprLoc(), - /*TreatBooleanAsSigned=*/true); + CE->getExprLoc(), Opts); + } case CK_IntegralToBoolean: return EmitIntToBoolConversion(Visit(E)); case CK_PointerToBoolean: diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 1f8f3367dbe..f9e28423297 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -116,6 +116,7 @@ enum TypeEvaluationKind { SANITIZER_CHECK(DynamicTypeCacheMiss, dynamic_type_cache_miss, 0) \ SANITIZER_CHECK(FloatCastOverflow, float_cast_overflow, 0) \ SANITIZER_CHECK(FunctionTypeMismatch, function_type_mismatch, 0) \ + SANITIZER_CHECK(ImplicitConversion, implicit_conversion, 0) \ SANITIZER_CHECK(InvalidBuiltin, invalid_builtin, 0) \ SANITIZER_CHECK(LoadInvalidValue, load_invalid_value, 0) \ SANITIZER_CHECK(MissingReturn, missing_return, 0) \ diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index bdc17d11c92..55036daa444 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -27,22 +27,22 @@ using namespace clang::driver; using namespace llvm::opt; enum : SanitizerMask { - NeedsUbsanRt = Undefined | Integer | Nullability | CFI, + NeedsUbsanRt = Undefined | Integer | ImplicitConversion | Nullability | CFI, NeedsUbsanCxxRt = Vptr | CFI, NotAllowedWithTrap = Vptr, NotAllowedWithMinimalRuntime = Vptr, RequiresPIE = DataFlow | HWAddress | Scudo, NeedsUnwindTables = Address | HWAddress | Thread | Memory | DataFlow, SupportsCoverage = Address | HWAddress | KernelAddress | KernelHWAddress | - Memory | Leak | Undefined | Integer | Nullability | - DataFlow | Fuzzer | FuzzerNoLink, - RecoverableByDefault = Undefined | Integer | Nullability, + Memory | Leak | Undefined | Integer | ImplicitConversion | + Nullability | DataFlow | Fuzzer | FuzzerNoLink, + RecoverableByDefault = Undefined | Integer | ImplicitConversion | Nullability, Unrecoverable = Unreachable | Return, AlwaysRecoverable = KernelAddress | KernelHWAddress, LegacyFsanitizeRecoverMask = Undefined | Integer, NeedsLTO = CFI, TrappingSupported = (Undefined & ~Vptr) | UnsignedIntegerOverflow | - Nullability | LocalBounds | CFI, + ImplicitConversion | Nullability | LocalBounds | CFI, TrappingDefault = CFI, CFIClasses = CFIVCall | CFINVCall | CFIMFCall | CFIDerivedCast | CFIUnrelatedCast, diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp index d62ba125334..39a6d9b4cc7 100644 --- a/clang/lib/Driver/ToolChain.cpp +++ b/clang/lib/Driver/ToolChain.cpp @@ -803,8 +803,8 @@ SanitizerMask ToolChain::getSupportedSanitizers() const { using namespace SanitizerKind; SanitizerMask Res = (Undefined & ~Vptr & ~Function) | (CFI & ~CFIICall) | - CFICastStrict | UnsignedIntegerOverflow | Nullability | - LocalBounds; + CFICastStrict | UnsignedIntegerOverflow | + ImplicitConversion | Nullability | LocalBounds; if (getTriple().getArch() == llvm::Triple::x86 || getTriple().getArch() == llvm::Triple::x86_64 || getTriple().getArch() == llvm::Triple::arm || |