diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -471,6 +471,16 @@ def CStringOutOfBounds : Checker<"OutOfBounds">, HelpText<"Check for out-of-bounds access in string functions">, Dependencies<[CStringModeling]>, + CheckerOptions<[ + CmdLineOption + ]>, Documentation; def CStringBufferOverlap : Checker<"BufferOverlap">, diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp @@ -36,6 +36,7 @@ enum OOB_Kind { OOB_Precedes, OOB_Excedes, OOB_Tainted }; void reportOOB(CheckerContext &C, ProgramStateRef errorState, OOB_Kind kind, + SVal InterestingValue = UndefinedVal(), std::unique_ptr Visitor = nullptr) const; public: @@ -205,10 +206,10 @@ // If we are under constrained and the index variables are tainted, report. if (state_exceedsUpperBound && state_withinUpperBound) { - SVal ByteOffset = rawOffset.getByteOffset(); - if (isTainted(state, ByteOffset)) { + if (isTainted(state, *upperboundToCheck)) { reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted, - std::make_unique(ByteOffset)); + *upperboundToCheck, + std::make_unique(*upperboundToCheck)); return; } } else if (state_exceedsUpperBound) { @@ -229,7 +230,7 @@ void ArrayBoundCheckerV2::reportOOB( CheckerContext &checkerContext, ProgramStateRef errorState, OOB_Kind kind, - std::unique_ptr Visitor) const { + SVal InterestingValue, std::unique_ptr Visitor) const { ExplodedNode *errorNode = checkerContext.generateErrorNode(errorState); if (!errorNode) @@ -257,6 +258,7 @@ } auto BR = std::make_unique(*BT, os.str(), errorNode); + BR->markInteresting(InterestingValue); BR->addVisitor(std::move(Visitor)); checkerContext.emitReport(std::move(BR)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -14,6 +14,7 @@ #include "InterCheckerAPI.h" #include "clang/Basic/CharInfo.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Checkers/Taint.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -55,7 +56,8 @@ enum class AccessKind { write, read }; static ErrorMessage createOutOfBoundErrorMsg(StringRef FunctionDescription, - AccessKind Access) { + AccessKind Access, + bool IsTainted = false) { ErrorMessage Message; llvm::raw_svector_ostream Os(Message); @@ -64,9 +66,9 @@ << &FunctionDescription.data()[1]; if (Access == AccessKind::write) { - Os << " overflows the destination buffer"; + Os << (IsTainted ? " might" : "") << " overflows the destination buffer"; } else { // read access - Os << " accesses out-of-bound array element"; + Os << (IsTainted ? " might" : "") << " accesses out-of-bound array element"; } return Message; @@ -94,6 +96,9 @@ bool CheckCStringNotNullTerm = false; bool CheckCStringUninitializedRead = false; + // Taint makes no sense for NullArg, BufferOverlap and NotNullTerm checks. + bool ConsiderTaintForCStringOutOfBounds = false; + CheckerNameRef CheckNameCStringNullArg; CheckerNameRef CheckNameCStringOutOfBounds; CheckerNameRef CheckNameCStringBufferOverlap; @@ -369,6 +374,15 @@ createOutOfBoundErrorMsg(CurrentFunctionDescription, Access); emitOutOfBoundsBug(C, StOutBound, Buffer.Expression, Message); return nullptr; + } else if (Filter.CheckCStringOutOfBounds && + Filter.ConsiderTaintForCStringOutOfBounds && StOutBound && + StInBound && + (taint::isTainted(state, Idx) || taint::isTainted(state, Size))) { + // FIXME: Explain whether the index or the extent was tainted. + // FIXME: Add a visitor explaining where the taint was introduced. + ErrorMessage Message = createOutOfBoundErrorMsg(CurrentFunctionDescription, + Access, /*IsTainted=*/true); + emitOutOfBoundsBug(C, StOutBound, Buffer.Expression, Message); } // Ensure that we wouldn't read uninitialized value. @@ -2489,8 +2503,20 @@ \ bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } +#define REGISTER_CHECKER_WITH_TAINT(name) \ + void ento::register##name(CheckerManager &mgr) { \ + CStringChecker *checker = mgr.getChecker(); \ + checker->Filter.Check##name = true; \ + checker->Filter.CheckName##name = mgr.getCurrentCheckerName(); \ + checker->Filter.ConsiderTaintFor##name = \ + mgr.getAnalyzerOptions().getCheckerBooleanOption( \ + mgr.getCurrentCheckerName().getName(), "ConsiderTaint"); \ + } \ + \ + bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } + REGISTER_CHECKER(CStringNullArg) -REGISTER_CHECKER(CStringOutOfBounds) REGISTER_CHECKER(CStringBufferOverlap) REGISTER_CHECKER(CStringNotNullTerm) REGISTER_CHECKER(CStringUninitializedRead) +REGISTER_CHECKER_WITH_TAINT(CStringOutOfBounds) diff --git a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -19,6 +19,7 @@ #include "clang/Basic/Builtins.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Checkers/Taint.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -26,6 +27,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/Support/YAMLTraits.h" #include @@ -38,7 +40,7 @@ using namespace ento; using namespace taint; -using llvm::ImmutableSet; +using llvm::ImmutableList; namespace { @@ -432,13 +434,31 @@ } // namespace yaml } // namespace llvm -/// A set which is used to pass information from call pre-visit instruction -/// to the call post-visit. The values are signed integers, which are either -/// ReturnValueIndex, or indexes of the pointer/reference argument, which -/// points to data, which should be tainted on return. +namespace { +struct FunctionParameter { + SVal Value; + ArgIdxTy Index; + FunctionParameter(SVal Value, ArgIdxTy Index) : Value{Value}, Index{Index} {} + + void Profile(llvm::FoldingSetNodeID &ID) const { + Value.Profile(ID); + ID.AddInteger(Index); + } +}; + +bool operator==(const FunctionParameter& lhs, const FunctionParameter& rhs) { + return lhs.Value == rhs.Value && lhs.Index == rhs.Index; +} +} // namespace + +REGISTER_TRAIT_WITH_PROGRAMSTATE(ReturnsTainted, bool) + +/// A list which is used to pass information from call pre-visit program point +/// to the call post-visit. Contains the symbolic value of the tainted +/// pointer/reference parameter and its index. REGISTER_MAP_WITH_PROGRAMSTATE(TaintArgsOnPostVisit, const LocationContext *, - ImmutableSet) -REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(ArgIdxFactory, ArgIdxTy) + ImmutableList) +REGISTER_LIST_FACTORY_WITH_PROGRAMSTATE(FunctionParamFactory, FunctionParameter) void GenericTaintRuleParser::validateArgVector(const std::string &Option, const ArgVecTy &Args) const { @@ -783,40 +803,92 @@ // checkPostStmt. ProgramStateRef State = C.getState(); const StackFrameContext *CurrentFrame = C.getStackFrame(); + ExplodedNode *Pred = C.getPredecessor(); // Depending on what was tainted at pre-visit, we determined a set of // arguments which should be tainted after the function returns. These are // stored in the state as TaintArgsOnPostVisit set. - TaintArgsOnPostVisitTy TaintArgsMap = State->get(); - const ImmutableSet *TaintArgs = TaintArgsMap.lookup(CurrentFrame); + if (State->get()) { + State = State->set(false); + State = taint::addTaint(State, Call.getReturnValue()); + const NoteTag *Note = + C.getNoteTag([RetVal = Call.getReturnValue()]( + PathSensitiveBugReport &BR) -> std::string { + return BR.isInteresting(RetVal) ? "Returned tainted value" : ""; + }); + Pred = C.addTransition(State, Pred, Note); + } + + TaintArgsOnPostVisitTy TaintArgsMap = State->get(); + const ImmutableList *TaintArgs = TaintArgsMap.lookup(CurrentFrame); if (!TaintArgs) return; assert(!TaintArgs->isEmpty()); - LLVM_DEBUG(for (ArgIdxTy I + LLVM_DEBUG(for (FunctionParameter FP : *TaintArgs) { llvm::dbgs() << "PostCall<"; Call.dump(llvm::dbgs()); - llvm::dbgs() << "> actually wants to taint arg index: " << I << '\n'; + llvm::dbgs() << "> actually wants to taint arg index: " << FP.Index << '\n'; }); - for (ArgIdxTy ArgNum : *TaintArgs) { + struct Pack { + SVal PreValue; + SVal PostValue; + ArgIdxTy Index; + }; + SmallVector ActualTaintedParams; + + for (FunctionParameter Param : *TaintArgs) { // Special handling for the tainted return value. - if (ArgNum == ReturnValueIndex) { + if (Param.Index == ReturnValueIndex) { State = addTaint(State, Call.getReturnValue()); continue; } // The arguments are pointer arguments. The data they are pointing at is // tainted after the call. - if (auto V = getPointeeOf(C, Call.getArgSVal(ArgNum))) + if (auto V = getPointeeOf(C, Call.getArgSVal(Param.Index))) { State = addTaint(State, *V); + ActualTaintedParams.push_back({Param.Value, V.getValue(), Param.Index}); + } } // Clear up the taint info from the state. State = State->remove(CurrentFrame); - C.addTransition(State); + const NoteTag *Note = [&]() -> const NoteTag * { + if (ActualTaintedParams.empty()) + return nullptr; + return C.getNoteTag( + [ActualTaintedParams](PathSensitiveBugReport &BR) -> std::string { + const auto Interesting = [&BR](const auto &Param) -> bool { + return BR.isInteresting(Param.PostValue); + }; + + auto InterestingTaintedParams = + llvm::make_filter_range(ActualTaintedParams, Interesting); + + if (InterestingTaintedParams.empty()) + return ""; + + SmallVector MsgParts; + for (const auto &Param : InterestingTaintedParams) { + BR.markInteresting(Param.PreValue); + const auto ParamOrdinal = Param.Index + 1; + MsgParts.emplace_back((Twine(std::to_string(ParamOrdinal)) + + llvm::getOrdinalSuffix(ParamOrdinal)) + .str()); + } + + return (Twine("Propagated taint to the ") + + llvm::join(std::move(MsgParts), ", ") + + (MsgParts.size() == 1 ? " parameter" : " parameters")) + .str(); + }); + }(); + + C.addTransition(State, Pred, Note); } void GenericTaintChecker::printState(raw_ostream &Out, ProgramStateRef State, @@ -856,11 +928,18 @@ /// A rule is relevant if PropSrcArgs is empty, or if any of its signified /// args are tainted in context of the current CallEvent. bool IsMatching = PropSrcArgs.isEmpty(); - ForEachCallArg( - [this, &C, &IsMatching, &State](ArgIdxTy I, const Expr *E, SVal) { - IsMatching = IsMatching || (PropSrcArgs.contains(I) && - isTaintedOrPointsToTainted(E, State, C)); - }); + ArgIdxTy TaintedArgIdx; + ForEachCallArg([this, &C, &IsMatching, &State, + &TaintedArgIdx](ArgIdxTy I, const Expr *E, SVal) { + if (IsMatching) + return; + + IsMatching = + PropSrcArgs.contains(I) && isTaintedOrPointsToTainted(E, State, C); + + if (IsMatching) + TaintedArgIdx = I; + }); if (!IsMatching) return; @@ -876,28 +955,39 @@ return IsNonConstRef || IsNonConstPtr; }; + struct Pack { + SVal PreValue; + SVal PostValue; + unsigned Index; + }; + SmallVector ActualTaintedParams; + /// Propagate taint where it is necessary. - auto &F = State->getStateManager().get_context(); - ImmutableSet Result = F.getEmptySet(); + auto &F = State->getStateManager().get_context(); + ImmutableList Result = F.getEmptyList(); ForEachCallArg( [&](ArgIdxTy I, const Expr *E, SVal V) { if (PropDstArgs.contains(I)) { LLVM_DEBUG(llvm::dbgs() << "PreCall<"; Call.dump(llvm::dbgs()); llvm::dbgs() << "> prepares tainting arg index: " << I << '\n';); - Result = F.add(Result, I); + if (I == ReturnValueIndex) + State = State->set(true); + else + Result = F.add({V, I}, Result); + return; } // TODO: We should traverse all reachable memory regions via the // escaping parameter. Instead of doing that we simply mark only the // referred memory region as tainted. if (WouldEscape(V, E->getType())) { - LLVM_DEBUG(if (!Result.contains(I)) { + LLVM_DEBUG(if (!Result.contains({V, I})) { llvm::dbgs() << "PreCall<"; Call.dump(llvm::dbgs()); llvm::dbgs() << "> prepares tainting arg index: " << I << '\n'; }); - Result = F.add(Result, I); + Result = F.add({V, I}, Result); } }); @@ -924,6 +1014,7 @@ if (ExplodedNode *N = C.generateNonFatalErrorNode()) { auto report = std::make_unique(BT, Msg, N); report->addRange(E->getSourceRange()); + report->markInteresting(*TaintedSVal); report->addVisitor(std::make_unique(*TaintedSVal)); C.emitReport(std::move(report)); return true; @@ -994,9 +1085,9 @@ return; ProgramStateRef State = C.getState(); - auto &F = State->getStateManager().get_context(); - ImmutableSet Result = F.add(F.getEmptySet(), ReturnValueIndex); - State = State->set(C.getStackFrame(), Result); + State = State->set(true); + auto &F = State->getStateManager().get_context(); + State = State->set(C.getStackFrame(), F.create({Call.getReturnValue(), ReturnValueIndex})); C.addTransition(State); } diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -60,6 +60,7 @@ #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Lexer.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Checkers/Taint.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -214,16 +215,24 @@ REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, SymbolRef, RefState) +namespace { +struct StateAndPred { + ProgramStateRef State; + ExplodedNode *Pred; +}; +} // namespace + /// Check if the memory associated with this symbol was released. static bool isReleased(SymbolRef Sym, CheckerContext &C); /// Update the RefState to reflect the new memory allocation. /// The optional \p RetVal parameter specifies the newly allocated pointer /// value; if unspecified, the value of expression \p E is used. -static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, - ProgramStateRef State, - AllocationFamily Family, - Optional RetVal = None); +static StateAndPred MallocUpdateRefState(CheckerContext &C, const Expr *E, + ProgramStateRef State, + ExplodedNode *Pred, + AllocationFamily Family, + Optional RetVal = None); //===----------------------------------------------------------------------===// // The modeling of memory reallocation. @@ -455,9 +464,9 @@ /// Process C++ operator new()'s allocation, which is the part of C++ /// new-expression that goes before the constructor. LLVM_NODISCARD - ProgramStateRef processNewAllocation(const CXXAllocatorCall &Call, - CheckerContext &C, - AllocationFamily Family) const; + StateAndPred processNewAllocation(const CXXAllocatorCall &Call, + CheckerContext &C, + AllocationFamily Family) const; /// Perform a zero-allocation check. /// @@ -490,9 +499,10 @@ /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. LLVM_NODISCARD - ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, - const OwnershipAttr *Att, - ProgramStateRef State) const; + StateAndPred MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, + const OwnershipAttr *Att, + ProgramStateRef State, + ExplodedNode *Pred) const; /// Models memory allocation. /// @@ -504,10 +514,10 @@ /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. LLVM_NODISCARD - static ProgramStateRef MallocMemAux(CheckerContext &C, const CallEvent &Call, - const Expr *SizeEx, SVal Init, - ProgramStateRef State, - AllocationFamily Family); + static StateAndPred MallocMemAux(CheckerContext &C, const CallEvent &Call, + const Expr *SizeEx, SVal Init, + ProgramStateRef State, ExplodedNode *Pred, + AllocationFamily Family); /// Models memory allocation. /// @@ -519,17 +529,17 @@ /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. LLVM_NODISCARD - static ProgramStateRef MallocMemAux(CheckerContext &C, const CallEvent &Call, - SVal Size, SVal Init, - ProgramStateRef State, - AllocationFamily Family); + static StateAndPred MallocMemAux(CheckerContext &C, const CallEvent &Call, + SVal Size, SVal Init, ProgramStateRef State, + ExplodedNode *Pred, AllocationFamily Family); // Check if this malloc() for special flags. At present that means M_ZERO or // __GFP_ZERO (in which case, treat it like calloc). LLVM_NODISCARD - llvm::Optional - performKernelMalloc(const CallEvent &Call, CheckerContext &C, - const ProgramStateRef &State) const; + llvm::Optional performKernelMalloc(const CallEvent &Call, + CheckerContext &C, + ProgramStateRef State, + ExplodedNode *Pred) const; /// Model functions with the ownership_takes and ownership_holds attributes. /// @@ -619,10 +629,10 @@ /// has an '_n' suffix, such as g_realloc_n. /// \returns The ProgramState right after reallocation. LLVM_NODISCARD - ProgramStateRef ReallocMemAux(CheckerContext &C, const CallEvent &Call, - bool ShouldFreeOnFail, ProgramStateRef State, - AllocationFamily Family, - bool SuffixWithN = false) const; + StateAndPred ReallocMemAux(CheckerContext &C, const CallEvent &Call, + bool ShouldFreeOnFail, ProgramStateRef State, + ExplodedNode *Pred, AllocationFamily Family, + bool SuffixWithN = false) const; /// Evaluates the buffer size that needs to be allocated. /// @@ -639,8 +649,8 @@ /// \param [in] State The \c ProgramState right before reallocation. /// \returns The ProgramState right after allocation. LLVM_NODISCARD - static ProgramStateRef CallocMem(CheckerContext &C, const CallEvent &Call, - ProgramStateRef State); + static StateAndPred CallocMem(CheckerContext &C, const CallEvent &Call, + ProgramStateRef State, ExplodedNode *Pred); /// See if deallocation happens in a suspicious context. If so, escape the /// pointers that otherwise would have been deallocated and return true. @@ -1138,9 +1148,10 @@ return Func && Func->hasAttr(); } -llvm::Optional +llvm::Optional MallocChecker::performKernelMalloc(const CallEvent &Call, CheckerContext &C, - const ProgramStateRef &State) const { + ProgramStateRef State, + ExplodedNode *Pred) const { // 3-argument malloc(), as commonly used in {Free,Net,Open}BSD Kernels: // // void *malloc(unsigned long size, struct malloc_type *mtp, int flags); @@ -1211,7 +1222,7 @@ // If M_ZERO is set, treat this like calloc (initialized). if (TrueState && !FalseState) { SVal ZeroVal = C.getSValBuilder().makeZeroVal(Ctx.CharTy); - return MallocMemAux(C, Call, Call.getArgExpr(0), ZeroVal, TrueState, + return MallocMemAux(C, Call, Call.getArgExpr(0), ZeroVal, TrueState, Pred, AF_Malloc); } @@ -1231,24 +1242,24 @@ void MallocChecker::checkBasicAlloc(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, - AF_Malloc); - State = ProcessZeroAllocCheck(Call, 0, State); - C.addTransition(State); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; + Pair = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), Pair.State, + Pair.Pred, AF_Malloc); + Pair.State = ProcessZeroAllocCheck(Call, 0, Pair.State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkKernelMalloc(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - llvm::Optional MaybeState = - performKernelMalloc(Call, C, State); - if (MaybeState.hasValue()) - State = MaybeState.getValue(); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; + llvm::Optional MaybePair = + performKernelMalloc(Call, C, Pair.State, Pair.Pred); + if (MaybePair.hasValue()) + Pair = MaybePair.getValue(); else - State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, - AF_Malloc); - C.addTransition(State); + Pair = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), Pair.State, + Pair.Pred, AF_Malloc); + C.addTransition(Pair.State, Pair.Pred); } static bool isStandardRealloc(const CallEvent &Call) { @@ -1289,19 +1300,18 @@ // https://bugs.llvm.org/show_bug.cgi?id=46253 if (!isStandardRealloc(Call) && !isGRealloc(Call)) return; - ProgramStateRef State = C.getState(); - State = ReallocMemAux(C, Call, ShouldFreeOnFail, State, AF_Malloc); - State = ProcessZeroAllocCheck(Call, 1, State); - C.addTransition(State); + StateAndPred Pair = ReallocMemAux(C, Call, ShouldFreeOnFail, C.getState(), + C.getPredecessor(), AF_Malloc); + Pair.State = ProcessZeroAllocCheck(Call, 1, Pair.State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkCalloc(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - State = CallocMem(C, Call, State); - State = ProcessZeroAllocCheck(Call, 0, State); - State = ProcessZeroAllocCheck(Call, 1, State); - C.addTransition(State); + StateAndPred Pair = CallocMem(C, Call, C.getState(), C.getPredecessor()); + Pair.State = ProcessZeroAllocCheck(Call, 0, Pair.State); + Pair.State = ProcessZeroAllocCheck(Call, 1, Pair.State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkFree(const CallEvent &Call, CheckerContext &C) const { @@ -1316,33 +1326,32 @@ void MallocChecker::checkAlloca(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, - AF_Alloca); - State = ProcessZeroAllocCheck(Call, 0, State); - C.addTransition(State); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; + Pair = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), Pair.State, + Pair.Pred, AF_Alloca); + Pair.State = ProcessZeroAllocCheck(Call, 0, Pair.State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkStrdup(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); if (!CE) return; - State = MallocUpdateRefState(C, CE, State, AF_Malloc); - C.addTransition(State); + StateAndPred Pair = + MallocUpdateRefState(C, CE, C.getState(), C.getPredecessor(), AF_Malloc); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkIfNameIndex(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; // Should we model this differently? We can allocate a fixed number of // elements with zeros in the last one. - State = - MallocMemAux(C, Call, UnknownVal(), UnknownVal(), State, AF_IfNameIndex); - - C.addTransition(State); + Pair = MallocMemAux(C, Call, UnknownVal(), UnknownVal(), Pair.State, + Pair.Pred, AF_IfNameIndex); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkIfFreeNameIndex(const CallEvent &Call, @@ -1356,7 +1365,7 @@ void MallocChecker::checkCXXNewOrCXXDelete(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; bool IsKnownToBeAllocatedMemory = false; const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); if (!CE) @@ -1371,85 +1380,88 @@ const FunctionDecl *FD = C.getCalleeDecl(CE); switch (FD->getOverloadedOperator()) { case OO_New: - State = - MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State, AF_CXXNew); - State = ProcessZeroAllocCheck(Call, 0, State); + Pair = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), Pair.State, + Pair.Pred, AF_CXXNew); + Pair.State = ProcessZeroAllocCheck(Call, 0, Pair.State); break; case OO_Array_New: - State = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State, - AF_CXXNewArray); - State = ProcessZeroAllocCheck(Call, 0, State); + Pair = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), Pair.State, + Pair.Pred, AF_CXXNewArray); + Pair.State = ProcessZeroAllocCheck(Call, 0, Pair.State); break; case OO_Delete: - State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, - AF_CXXNew); + Pair.State = FreeMemAux(C, Call, Pair.State, 0, false, + IsKnownToBeAllocatedMemory, AF_CXXNew); break; case OO_Array_Delete: - State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, - AF_CXXNewArray); + Pair.State = FreeMemAux(C, Call, Pair.State, 0, false, + IsKnownToBeAllocatedMemory, AF_CXXNewArray); break; default: llvm_unreachable("not a new/delete operator"); } - C.addTransition(State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkGMalloc0(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; SValBuilder &svalBuilder = C.getSValBuilder(); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); - State = MallocMemAux(C, Call, Call.getArgExpr(0), zeroVal, State, AF_Malloc); - State = ProcessZeroAllocCheck(Call, 0, State); - C.addTransition(State); + Pair = MallocMemAux(C, Call, Call.getArgExpr(0), zeroVal, Pair.State, + Pair.Pred, AF_Malloc); + Pair.State = ProcessZeroAllocCheck(Call, 0, Pair.State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkGMemdup(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - State = - MallocMemAux(C, Call, Call.getArgExpr(1), UnknownVal(), State, AF_Malloc); - State = ProcessZeroAllocCheck(Call, 1, State); - C.addTransition(State); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; + Pair = MallocMemAux(C, Call, Call.getArgExpr(1), UnknownVal(), Pair.State, + Pair.Pred, AF_Malloc); + Pair.State = ProcessZeroAllocCheck(Call, 1, Pair.State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkGMallocN(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; SVal Init = UndefinedVal(); SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); - State = MallocMemAux(C, Call, TotalSize, Init, State, AF_Malloc); - State = ProcessZeroAllocCheck(Call, 0, State); - State = ProcessZeroAllocCheck(Call, 1, State); - C.addTransition(State); + Pair = + MallocMemAux(C, Call, TotalSize, Init, Pair.State, Pair.Pred, AF_Malloc); + Pair.State = ProcessZeroAllocCheck(Call, 0, Pair.State); + Pair.State = ProcessZeroAllocCheck(Call, 1, Pair.State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkGMallocN0(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; SValBuilder &SB = C.getSValBuilder(); SVal Init = SB.makeZeroVal(SB.getContext().CharTy); SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); - State = MallocMemAux(C, Call, TotalSize, Init, State, AF_Malloc); - State = ProcessZeroAllocCheck(Call, 0, State); - State = ProcessZeroAllocCheck(Call, 1, State); - C.addTransition(State); + Pair = + MallocMemAux(C, Call, TotalSize, Init, Pair.State, Pair.Pred, AF_Malloc); + Pair.State = ProcessZeroAllocCheck(Call, 0, Pair.State); + Pair.State = ProcessZeroAllocCheck(Call, 1, Pair.State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkReallocN(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - State = ReallocMemAux(C, Call, /*ShouldFreeOnFail=*/false, State, AF_Malloc, - /*SuffixWithN=*/true); - State = ProcessZeroAllocCheck(Call, 1, State); - State = ProcessZeroAllocCheck(Call, 2, State); - C.addTransition(State); + StateAndPred Pair = ReallocMemAux(C, Call, /*ShouldFreeOnFail=*/false, + C.getState(), C.getPredecessor(), AF_Malloc, + /*SuffixWithN=*/true); + Pair.State = ProcessZeroAllocCheck(Call, 1, Pair.State); + Pair.State = ProcessZeroAllocCheck(Call, 2, Pair.State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkOwnershipAttr(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); if (!CE) return; @@ -1464,16 +1476,16 @@ for (const auto *I : FD->specific_attrs()) { switch (I->getOwnKind()) { case OwnershipAttr::Returns: - State = MallocMemReturnsAttr(C, Call, I, State); + Pair = MallocMemReturnsAttr(C, Call, I, Pair.State, Pair.Pred); break; case OwnershipAttr::Takes: case OwnershipAttr::Holds: - State = FreeMemAttr(C, Call, I, State); + Pair.State = FreeMemAttr(C, Call, I, Pair.State); break; } } } - C.addTransition(State); + C.addTransition(Pair.State, Pair.Pred); } void MallocChecker::checkPostCall(const CallEvent &Call, @@ -1613,41 +1625,42 @@ return false; } -ProgramStateRef +StateAndPred MallocChecker::processNewAllocation(const CXXAllocatorCall &Call, CheckerContext &C, AllocationFamily Family) const { if (!isStandardNewDelete(Call)) - return nullptr; + return {nullptr, C.getPredecessor()}; const CXXNewExpr *NE = Call.getOriginExpr(); const ParentMap &PM = C.getLocationContext()->getParentMap(); - ProgramStateRef State = C.getState(); + StateAndPred Pair = {C.getState(), C.getPredecessor()}; // Non-trivial constructors have a chance to escape 'this', but marking all // invocations of trivial constructors as escaped would cause too great of // reduction of true positives, so let's just do that for constructors that // have an argument of a pointer-to-record type. if (!PM.isConsumedExpr(NE) && hasNonTrivialConstructorCall(NE)) - return State; + return Pair; // The return value from operator new is bound to a specified initialization // value (if any) and we don't want to loose this value. So we call // MallocUpdateRefState() instead of MallocMemAux() which breaks the // existing binding. SVal Target = Call.getObjectUnderConstruction(); - State = MallocUpdateRefState(C, NE, State, Family, Target); - State = ProcessZeroAllocCheck(Call, 0, State, Target); - return State; + + Pair = MallocUpdateRefState(C, NE, Pair.State, Pair.Pred, Family, Target); + Pair.State = ProcessZeroAllocCheck(Call, 0, Pair.State, Target); + return Pair; } void MallocChecker::checkNewAllocator(const CXXAllocatorCall &Call, CheckerContext &C) const { if (!C.wasInlined) { - ProgramStateRef State = processNewAllocation( + StateAndPred Pair = processNewAllocation( Call, C, (Call.getOriginExpr()->isArray() ? AF_CXXNewArray : AF_CXXNew)); - C.addTransition(State); + C.addTransition(Pair.State, Pair.Pred); } } @@ -1698,48 +1711,49 @@ C.addTransition(State); } -ProgramStateRef -MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, - const OwnershipAttr *Att, - ProgramStateRef State) const { +StateAndPred MallocChecker::MallocMemReturnsAttr(CheckerContext &C, + const CallEvent &Call, + const OwnershipAttr *Att, + ProgramStateRef State, + ExplodedNode *Pred) const { if (!State) - return nullptr; + return {nullptr, Pred}; if (Att->getModule()->getName() != "malloc") - return nullptr; + return {nullptr, Pred}; OwnershipAttr::args_iterator I = Att->args_begin(), E = Att->args_end(); if (I != E) { return MallocMemAux(C, Call, Call.getArgExpr(I->getASTIndex()), - UndefinedVal(), State, AF_Malloc); + UndefinedVal(), State, Pred, AF_Malloc); } - return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, AF_Malloc); + return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, Pred, + AF_Malloc); } -ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, - const CallEvent &Call, - const Expr *SizeEx, SVal Init, - ProgramStateRef State, - AllocationFamily Family) { +StateAndPred MallocChecker::MallocMemAux( + CheckerContext &C, const CallEvent &Call, const Expr *SizeEx, SVal Init, + ProgramStateRef State, ExplodedNode *Pred, AllocationFamily Family) { if (!State) - return nullptr; + return {nullptr, Pred}; assert(SizeEx); - return MallocMemAux(C, Call, C.getSVal(SizeEx), Init, State, Family); + return MallocMemAux(C, Call, C.getSVal(SizeEx), Init, State, Pred, Family); } -ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, - const CallEvent &Call, SVal Size, - SVal Init, ProgramStateRef State, - AllocationFamily Family) { +StateAndPred MallocChecker::MallocMemAux(CheckerContext &C, + const CallEvent &Call, SVal Size, + SVal Init, ProgramStateRef State, + ExplodedNode *Pred, + AllocationFamily Family) { if (!State) - return nullptr; + return {nullptr, Pred}; const Expr *CE = Call.getOriginExpr(); // We expect the malloc functions to return a pointer. if (!Loc::isLocType(CE->getType())) - return nullptr; + return {nullptr, Pred}; // Bind the return value to the symbolic value from the heap region. // TODO: We could rewrite post visit to eval call; 'malloc' does not have @@ -1757,16 +1771,26 @@ // Set the region's extent. State = setDynamicExtent(State, RetVal.getAsRegion(), Size.castAs(), svalBuilder); + if (taint::isTainted(State, Size)) { + const NoteTag *Note = + C.getNoteTag([Size](PathSensitiveBugReport &BR) -> std::string { + if (BR.isInteresting(Size)) + return "Allocating tainted amount of memory"; + return ""; + }); + Pred = C.addTransition(State, Pred, Note); + } - return MallocUpdateRefState(C, CE, State, Family); + return MallocUpdateRefState(C, CE, State, Pred, Family); } -static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, - ProgramStateRef State, - AllocationFamily Family, - Optional RetVal) { +static StateAndPred MallocUpdateRefState(CheckerContext &C, const Expr *E, + ProgramStateRef State, + ExplodedNode *Pred, + AllocationFamily Family, + Optional RetVal) { if (!State) - return nullptr; + return {nullptr, Pred}; // Get the return value. if (!RetVal) @@ -1774,7 +1798,7 @@ // We expect the malloc functions to return a pointer. if (!RetVal->getAs()) - return nullptr; + return {nullptr, Pred}; SymbolRef Sym = RetVal->getAsLocSymbol(); // This is a return value of a function that was not inlined, such as malloc() @@ -1782,7 +1806,8 @@ assert(Sym); // Set the symbol's state to Allocated. - return State->set(Sym, RefState::getAllocated(Family, E)); + State = State->set(Sym, RefState::getAllocated(Family, E)); + return {State, Pred}; } ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, @@ -2571,24 +2596,25 @@ } } -ProgramStateRef +StateAndPred MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call, bool ShouldFreeOnFail, ProgramStateRef State, - AllocationFamily Family, bool SuffixWithN) const { + ExplodedNode *Pred, AllocationFamily Family, + bool SuffixWithN) const { if (!State) - return nullptr; + return {nullptr, Pred}; const CallExpr *CE = cast(Call.getOriginExpr()); if (SuffixWithN && CE->getNumArgs() < 3) - return nullptr; + return {nullptr, Pred}; else if (CE->getNumArgs() < 2) - return nullptr; + return {nullptr, Pred}; const Expr *arg0Expr = CE->getArg(0); SVal Arg0Val = C.getSVal(arg0Expr); if (!Arg0Val.getAs()) - return nullptr; + return {nullptr, Pred}; DefinedOrUnknownSVal arg0Val = Arg0Val.castAs(); SValBuilder &svalBuilder = C.getSValBuilder(); @@ -2604,7 +2630,7 @@ if (SuffixWithN) TotalSize = evalMulForBufferSize(C, Arg1, CE->getArg(2)); if (!TotalSize.getAs()) - return nullptr; + return {nullptr, Pred}; // Compare the size argument to 0. DefinedOrUnknownSVal SizeZero = @@ -2624,13 +2650,12 @@ // If the ptr is NULL and the size is not 0, the call is equivalent to // malloc(size). if (PrtIsNull && !SizeIsZero) { - ProgramStateRef stateMalloc = MallocMemAux( - C, Call, TotalSize, UndefinedVal(), StatePtrIsNull, Family); - return stateMalloc; + return MallocMemAux(C, Call, TotalSize, UndefinedVal(), StatePtrIsNull, + Pred, Family); } if (PrtIsNull && SizeIsZero) - return State; + return {State, Pred}; assert(!PrtIsNull); @@ -2644,16 +2669,16 @@ // any constrains on the output pointer. if (ProgramStateRef stateFree = FreeMemAux( C, Call, StateSizeIsZero, 0, false, IsKnownToBeAllocated, Family)) - return stateFree; + return {stateFree, Pred}; // Default behavior. if (ProgramStateRef stateFree = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocated, Family)) { - ProgramStateRef stateRealloc = - MallocMemAux(C, Call, TotalSize, UnknownVal(), stateFree, Family); - if (!stateRealloc) - return nullptr; + StateAndPred ReallocState = + MallocMemAux(C, Call, TotalSize, UnknownVal(), stateFree, Pred, Family); + if (!ReallocState.State) + return ReallocState; OwnershipAfterReallocKind Kind = OAR_ToBeFreedAfterFailure; if (ShouldFreeOnFail) @@ -2671,30 +2696,30 @@ // Record the info about the reallocated symbol so that we could properly // process failed reallocation. - stateRealloc = stateRealloc->set(ToPtr, - ReallocPair(FromPtr, Kind)); + ReallocState.State = ReallocState.State->set( + ToPtr, ReallocPair(FromPtr, Kind)); // The reallocated symbol should stay alive for as long as the new symbol. C.getSymbolManager().addSymbolDependency(ToPtr, FromPtr); - return stateRealloc; + return ReallocState; } - return nullptr; + return {nullptr, Pred}; } -ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, - const CallEvent &Call, - ProgramStateRef State) { +StateAndPred MallocChecker::CallocMem(CheckerContext &C, const CallEvent &Call, + ProgramStateRef State, + ExplodedNode *Pred) { if (!State) - return nullptr; + return {nullptr, Pred}; if (Call.getNumArgs() < 2) - return nullptr; + return {nullptr, Pred}; SValBuilder &svalBuilder = C.getSValBuilder(); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); - return MallocMemAux(C, Call, TotalSize, zeroVal, State, AF_Malloc); + return MallocMemAux(C, Call, TotalSize, zeroVal, State, Pred, AF_Malloc); } MallocChecker::LeakInfo MallocChecker::getAllocationSite(const ExplodedNode *N, diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -2261,6 +2261,12 @@ // How to handle multiple metadata for the same region? if (const auto *meta = dyn_cast(sym)) markInteresting(meta->getRegion(), TKind); + + auto SubSyms = llvm::make_range(sym->symbol_begin(), sym->symbol_end()); + for (SymbolRef SubSym : SubSyms) { + if (SymbolData::classof(SubSym)) + insertToInterestingnessMap(InterestingSymbols, SubSym, TKind); + } } void PathSensitiveBugReport::markNotInteresting(SymbolRef sym) { @@ -2341,10 +2347,25 @@ return None; // We don't currently consider metadata symbols to be interesting // even if we know their region is interesting. Is that correct behavior? - auto It = InterestingSymbols.find(sym); - if (It == InterestingSymbols.end()) - return None; - return It->getSecond(); + auto TryToLookupTrackingKind = + [this](SymbolRef Sym) -> Optional { + auto It = InterestingSymbols.find(Sym); + if (It == InterestingSymbols.end()) + return None; + return It->getSecond(); + }; + + if (auto MaybeTK = TryToLookupTrackingKind(sym)) + return MaybeTK; + + auto SubSyms = llvm::make_range(sym->symbol_begin(), sym->symbol_end()); + for (SymbolRef SubSym : SubSyms) { + if (SymbolData::classof(SubSym)) { + if (auto MaybeTK = TryToLookupTrackingKind(SubSym)) + return MaybeTK; + } + } + return None; } Optional diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -12,6 +12,7 @@ // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtExec = 0x04 // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtRead = 0x01 // CHECK-NEXT: alpha.security.taint.TaintPropagation:Config = "" +// CHECK-NEXT: alpha.unix.cstring.OutOfBounds:ConsiderTaint = false // CHECK-NEXT: apiModeling.StdCLibraryFunctions:DisplayLoadedSummaries = false // CHECK-NEXT: apiModeling.StdCLibraryFunctions:ModelPOSIX = false // CHECK-NEXT: apply-fixits = false diff --git a/clang/test/Analysis/string.c b/clang/test/Analysis/string.c --- a/clang/test/Analysis/string.c +++ b/clang/test/Analysis/string.c @@ -1,10 +1,13 @@ -// RUN: %clang_analyze_cc1 -verify %s -Wno-null-dereference \ +// RUN: %clang_analyze_cc1 %s -Wno-null-dereference \ // RUN: -analyzer-checker=core \ // RUN: -analyzer-checker=unix.cstring \ // RUN: -analyzer-checker=unix.Malloc \ +// RUN: -analyzer-checker=alpha.security.taint \ // RUN: -analyzer-checker=alpha.unix.cstring \ // RUN: -analyzer-checker=debug.ExprInspection \ -// RUN: -analyzer-config eagerly-assume=false +// RUN: -analyzer-config alpha.unix.cstring.OutOfBounds:ConsiderTaint=true \ +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -verify=expected,tainted,OOB-consider-tainted // // RUN: %clang_analyze_cc1 -verify %s -Wno-null-dereference -DUSE_BUILTINS \ // RUN: -analyzer-checker=core \ @@ -22,7 +25,7 @@ // RUN: -analyzer-checker=debug.ExprInspection \ // RUN: -analyzer-config eagerly-assume=false // -// RUN: %clang_analyze_cc1 -verify %s -Wno-null-dereference \ +// RUN: %clang_analyze_cc1 %s -Wno-null-dereference \ // RUN: -DUSE_BUILTINS -DVARIANT \ // RUN: -analyzer-checker=core \ // RUN: -analyzer-checker=alpha.security.taint \ @@ -30,7 +33,8 @@ // RUN: -analyzer-checker=unix.Malloc \ // RUN: -analyzer-checker=alpha.unix.cstring \ // RUN: -analyzer-checker=debug.ExprInspection \ -// RUN: -analyzer-config eagerly-assume=false +// RUN: -analyzer-config eagerly-assume=false \ +// RUN: -verify=expected,tainted // // RUN: %clang_analyze_cc1 -verify %s -Wno-null-dereference \ // RUN: -DSUPPRESS_OUT_OF_BOUND \ @@ -481,6 +485,23 @@ } #endif +void strcat_overflow_tainted_dst_extent(char *y) { + int n; + scanf("%d", &n); + char *p = malloc(n); // tainted-warning {{Untrusted data is used to specify the buffer size}} + p[0] = '\0'; + + if (strlen(y) == 3) + strcat(p, y); // OOB-consider-tainted-warning {{String concatenation function might overflows the destination buffer}} + free(p); +} + +void strcat_overflow_tainted_src_content(char *y) { + char x[4] = "12"; + scanf("%s100", y); // Assuming that y is at least 100 bytes long. + strcat(x, y); // FIXME: The extent is not tainted, but we should still emit a warning here. +} + void strcat_no_overflow(char *y) { char x[5] = "12"; if (strlen(y) == 2) diff --git a/clang/test/Analysis/taint-diagnostic-visitor.c b/clang/test/Analysis/taint-diagnostic-visitor.c --- a/clang/test/Analysis/taint-diagnostic-visitor.c +++ b/clang/test/Analysis/taint-diagnostic-visitor.c @@ -1,28 +1,31 @@ -// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.security.taint,core,alpha.security.ArrayBoundV2 -analyzer-output=text -verify %s +// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.security.taint,core,unix.Malloc,alpha.security.ArrayBoundV2 -analyzer-output=text -verify %s // This file is for testing enhanced diagnostics produced by the GenericTaintChecker int scanf(const char *restrict format, ...); int system(const char *command); +typedef __typeof(sizeof(int)) size_t; +void *malloc(size_t size); +void free(void *ptr); void taintDiagnostic(void) { char buf[128]; - scanf("%s", buf); // expected-note {{Taint originated here}} + scanf("%s", buf); // expected-note {{Propagated taint to the 2nd parameter}} system(buf); // expected-warning {{Untrusted data is passed to a system call}} // expected-note {{Untrusted data is passed to a system call (CERT/STR02-C. Sanitize data passed to complex subsystems)}} } int taintDiagnosticOutOfBound(void) { int index; int Array[] = {1, 2, 3, 4, 5}; - scanf("%d", &index); // expected-note {{Taint originated here}} + scanf("%d", &index); // expected-note {{Taint originated here}} expected-note {{Propagated taint to the 2nd parameter}} return Array[index]; // expected-warning {{Out of bound memory access (index is tainted)}} // expected-note@-1 {{Out of bound memory access (index is tainted)}} } int taintDiagnosticDivZero(int operand) { scanf("%d", &operand); // expected-note {{Value assigned to 'operand'}} - // expected-note@-1 {{Taint originated here}} + // expected-note@-1 {{Taint originated here}} expected-note@-1 {{Propagated taint to the 2nd parameter}} return 10 / operand; // expected-warning {{Division by a tainted value, possibly zero}} // expected-note@-1 {{Division by a tainted value, possibly zero}} } @@ -30,7 +33,23 @@ void taintDiagnosticVLA(void) { int x; scanf("%d", &x); // expected-note {{Value assigned to 'x'}} - // expected-note@-1 {{Taint originated here}} + // expected-note@-1 {{Taint originated here}} expected-note@-1 {{Propagated taint to the 2nd parameter}} int vla[x]; // expected-warning {{Declared variable-length array (VLA) has tainted size}} // expected-note@-1 {{Declared variable-length array (VLA) has tainted size}} } + +void taintDiagnosticMalloc(int conj) { + int x; + scanf("%d", &x); // expected-note {{Taint originated here}} + // expected-note@-1 2 {{Propagated taint to the 2nd parameter}} Once for malloc(tainted), once for BoundsV2. + + int *p = (int *)malloc(x + conj); // Generic taint checker forbids tainted allocation. + // expected-warning@-1 {{Untrusted data is used to specify the buffer size}} + // expected-note@-2 {{Untrusted data is used to specify the buffer size}} + // expected-note@-3 {{Allocating tainted amount of memory}} + + p[1] = 1; // BoundsV2 checker can not prove that the access is safe. + // expected-warning@-1 {{Out of bound memory access (index is tainted)}} + // expected-note@-2 {{Out of bound memory access (index is tainted)}} + free(p); +}