Index: lib/Transforms/Instrumentation/PoisonChecking.cpp =================================================================== --- lib/Transforms/Instrumentation/PoisonChecking.cpp +++ lib/Transforms/Instrumentation/PoisonChecking.cpp @@ -182,6 +182,61 @@ }; } +static void generatePoisonChecksForInboundsGEP(IRBuilder<> &B, + GetElementPtrInst& GEP, + SmallVector &Checks) { + assert(GEP.isInBounds()); + if (GEP.getType()->isVectorTy()) + return; + + auto &DL = GEP.getModule()->getDataLayout(); + Value *Obj = GetUnderlyingObject(GEP.getPointerOperand(), DL); + if (!Obj) + return; + // CAUTION: At this point we know that Obj is *some* pointer w/in + // Allocation A, but we do NOT know that A.start == Obj. As such, there + // may be inbounds addresses between the unknown A.start and Obj. We also do + // not know if Obj <= I.getPointerOperand(). + + // Even if we know the distance between Obj and A.end, we still don't know + // the offset of Obj w/in A. + uint64_t SizeInBytes; + const bool GenUpperCheck = getObjectSize(Obj, SizeInBytes, DL, nullptr); + + auto isKnownAllocationStart = [](Value *V) { + // TODO: expand this and integrate w/MemoryBuiltins.h + return isa(V); + }; + + bool IsNonNegativeOffset = true; + for (unsigned i = 1; i < GEP.getNumOperands(); i++) + IsNonNegativeOffset &= isKnownNonNegative(GEP.getOperand(i), DL); + + // If we know the offset is positive, the only way the resulting address can + // be less than the base is if we wrapped around the address space (and no + // allocation can wrap around). If we know that Obj is actually a start an + // an allocation, than any address below that must be out of bounds. + const bool GenLowerCheck = isKnownAllocationStart(Obj) || IsNonNegativeOffset; + + if (!GenUpperCheck && !GenLowerCheck) + return; + + auto *I8PtrTy = B.getInt8PtrTy(GEP.getType()->getPointerAddressSpace()); + auto *NewGEP = cast(GEP.clone()); + NewGEP->setIsInBounds(false); + NewGEP->insertBefore(&GEP); + auto *Addr = B.CreateBitCast(NewGEP, I8PtrTy); + auto *Base = B.CreateBitCast(Obj, I8PtrTy, "lower.limit"); + + if (GenUpperCheck) { + auto *UpperLimit = B.CreateGEP(Base, B.getInt64(SizeInBytes), + "upper.limit"); + Checks.push_back(B.CreateICmp(ICmpInst::ICMP_UGT, Addr, UpperLimit)); + } + if (GenLowerCheck) + Checks.push_back(B.CreateICmp(ICmpInst::ICMP_ULT, Addr, Base)); +} + static Value* generatePoisonChecks(Instruction &I) { IRBuilder<> B(&I); SmallVector Checks; @@ -216,6 +271,13 @@ Checks.push_back(Check); break; } + case Instruction::GetElementPtr: { + auto &GEP = cast(I); + if (!GEP.isInBounds()) + break; + generatePoisonChecksForInboundsGEP(B, GEP, Checks); + break; + } }; return buildOrChain(B, Checks); } Index: test/Instrumentation/PoisonChecking/basic-flag-validation.ll =================================================================== --- test/Instrumentation/PoisonChecking/basic-flag-validation.ll +++ test/Instrumentation/PoisonChecking/basic-flag-validation.ll @@ -320,3 +320,53 @@ ret <4 x i32> %res } + +define void @gep_inbounds_generic(i8* %base, i32 %n) { +; CHECK-LABEL: @gep_inbounds_generic( +; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds i8, i8* [[BASE:%.*]], i32 [[N:%.*]] +; CHECK-NEXT: store i8 0, i8* [[P]] +; CHECK-NEXT: ret void +; + %p = getelementptr inbounds i8, i8* %base, i32 %n + store i8 0, i8* %p + ret void +} + +define void @gep_inbounds_positive_offset(i8* %base) { +; CHECK-LABEL: @gep_inbounds_positive_offset( +; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, i8* [[BASE:%.*]], i32 10 +; CHECK-NEXT: [[TMP2:%.*]] = icmp ult i8* [[TMP1]], [[BASE]] +; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds i8, i8* [[BASE]], i32 10 +; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[TMP2]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP3]]) +; CHECK-NEXT: store i8 0, i8* [[P]] +; CHECK-NEXT: ret void +; + %p = getelementptr inbounds i8, i8* %base, i32 10 + store i8 0, i8* %p + ret void +} + +define void @gep_inbounds_known_base_and_size(i32 %n) { +; CHECK-LABEL: @gep_inbounds_known_base_and_size( +; CHECK-NEXT: [[ALLOCA:%.*]] = alloca [200 x i8] +; CHECK-NEXT: [[BASE:%.*]] = bitcast [200 x i8]* [[ALLOCA]] to i8* +; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, i8* [[BASE]], i32 [[N:%.*]] +; CHECK-NEXT: [[LOWER_LIMIT:%.*]] = bitcast [200 x i8]* [[ALLOCA]] to i8* +; CHECK-NEXT: [[UPPER_LIMIT:%.*]] = getelementptr i8, i8* [[LOWER_LIMIT]], i64 200 +; CHECK-NEXT: [[TMP2:%.*]] = icmp ugt i8* [[TMP1]], [[UPPER_LIMIT]] +; CHECK-NEXT: [[TMP3:%.*]] = icmp ult i8* [[TMP1]], [[LOWER_LIMIT]] +; CHECK-NEXT: [[TMP4:%.*]] = or i1 [[TMP2]], [[TMP3]] +; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds i8, i8* [[BASE]], i32 [[N]] +; CHECK-NEXT: [[TMP5:%.*]] = xor i1 [[TMP4]], true +; CHECK-NEXT: call void @__poison_checker_assert(i1 [[TMP5]]) +; CHECK-NEXT: store i8 0, i8* [[P]] +; CHECK-NEXT: ret void +; + %alloca = alloca [200 x i8] + %base = bitcast [200 x i8]* %alloca to i8* + %p = getelementptr inbounds i8, i8* %base, i32 %n + store i8 0, i8* %p + ret void +} +