| //=== VLASizeChecker.cpp - Undefined dereference checker --------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This defines VLASizeChecker, a builtin check in ExprEngine that |
| // performs checks for declaration of VLA of undefined or zero size. |
| // In addition, VLASizeChecker is responsible for defining the extent |
| // of the MemRegion that represents a VLA. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "Taint.h" |
| #include "clang/AST/CharUnits.h" |
| #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
| #include "clang/StaticAnalyzer/Core/Checker.h" |
| #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/SmallString.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| using namespace clang; |
| using namespace ento; |
| using namespace taint; |
| |
| namespace { |
| class VLASizeChecker |
| : public Checker<check::PreStmt<DeclStmt>, |
| check::PreStmt<UnaryExprOrTypeTraitExpr>> { |
| mutable std::unique_ptr<BugType> BT; |
| enum VLASize_Kind { |
| VLA_Garbage, |
| VLA_Zero, |
| VLA_Tainted, |
| VLA_Negative, |
| VLA_Overflow |
| }; |
| |
| /// Check a VLA for validity. |
| /// Every dimension of the array and the total size is checked for validity. |
| /// Returns null or a new state where the size is validated. |
| /// 'ArraySize' will contain SVal that refers to the total size (in char) |
| /// of the array. |
| ProgramStateRef checkVLA(CheckerContext &C, ProgramStateRef State, |
| const VariableArrayType *VLA, SVal &ArraySize) const; |
| /// Check a single VLA index size expression for validity. |
| ProgramStateRef checkVLAIndexSize(CheckerContext &C, ProgramStateRef State, |
| const Expr *SizeE) const; |
| |
| void reportBug(VLASize_Kind Kind, const Expr *SizeE, ProgramStateRef State, |
| CheckerContext &C, |
| std::unique_ptr<BugReporterVisitor> Visitor = nullptr) const; |
| |
| public: |
| void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; |
| void checkPreStmt(const UnaryExprOrTypeTraitExpr *UETTE, |
| CheckerContext &C) const; |
| }; |
| } // end anonymous namespace |
| |
| ProgramStateRef VLASizeChecker::checkVLA(CheckerContext &C, |
| ProgramStateRef State, |
| const VariableArrayType *VLA, |
| SVal &ArraySize) const { |
| assert(VLA && "Function should be called with non-null VLA argument."); |
| |
| const VariableArrayType *VLALast = nullptr; |
| llvm::SmallVector<const Expr *, 2> VLASizes; |
| |
| // Walk over the VLAs for every dimension until a non-VLA is found. |
| // There is a VariableArrayType for every dimension (fixed or variable) until |
| // the most inner array that is variably modified. |
| // Dimension sizes are collected into 'VLASizes'. 'VLALast' is set to the |
| // innermost VLA that was encountered. |
| // In "int vla[x][2][y][3]" this will be the array for index "y" (with type |
| // int[3]). 'VLASizes' contains 'x', '2', and 'y'. |
| while (VLA) { |
| const Expr *SizeE = VLA->getSizeExpr(); |
| State = checkVLAIndexSize(C, State, SizeE); |
| if (!State) |
| return nullptr; |
| VLASizes.push_back(SizeE); |
| VLALast = VLA; |
| VLA = C.getASTContext().getAsVariableArrayType(VLA->getElementType()); |
| }; |
| assert(VLALast && |
| "Array should have at least one variably-modified dimension."); |
| |
| ASTContext &Ctx = C.getASTContext(); |
| SValBuilder &SVB = C.getSValBuilder(); |
| CanQualType SizeTy = Ctx.getSizeType(); |
| uint64_t SizeMax = |
| SVB.getBasicValueFactory().getMaxValue(SizeTy).getZExtValue(); |
| |
| // Get the element size. |
| CharUnits EleSize = Ctx.getTypeSizeInChars(VLALast->getElementType()); |
| NonLoc ArrSize = |
| SVB.makeIntVal(EleSize.getQuantity(), SizeTy).castAs<NonLoc>(); |
| |
| // Try to calculate the known real size of the array in KnownSize. |
| uint64_t KnownSize = 0; |
| if (const llvm::APSInt *KV = SVB.getKnownValue(State, ArrSize)) |
| KnownSize = KV->getZExtValue(); |
| |
| for (const Expr *SizeE : VLASizes) { |
| auto SizeD = C.getSVal(SizeE).castAs<DefinedSVal>(); |
| // Convert the array length to size_t. |
| NonLoc IndexLength = |
| SVB.evalCast(SizeD, SizeTy, SizeE->getType()).castAs<NonLoc>(); |
| // Multiply the array length by the element size. |
| SVal Mul = SVB.evalBinOpNN(State, BO_Mul, ArrSize, IndexLength, SizeTy); |
| if (auto MulNonLoc = Mul.getAs<NonLoc>()) |
| ArrSize = *MulNonLoc; |
| else |
| // Extent could not be determined. |
| return State; |
| |
| if (const llvm::APSInt *IndexLVal = SVB.getKnownValue(State, IndexLength)) { |
| // Check if the array size will overflow. |
| // Size overflow check does not work with symbolic expressions because a |
| // overflow situation can not be detected easily. |
| uint64_t IndexL = IndexLVal->getZExtValue(); |
| // FIXME: See https://reviews.llvm.org/D80903 for discussion of |
| // some difference in assume and getKnownValue that leads to |
| // unexpected behavior. Just bail on IndexL == 0 at this point. |
| if (IndexL == 0) |
| return nullptr; |
| |
| if (KnownSize <= SizeMax / IndexL) { |
| KnownSize *= IndexL; |
| } else { |
| // Array size does not fit into size_t. |
| reportBug(VLA_Overflow, SizeE, State, C); |
| return nullptr; |
| } |
| } else { |
| KnownSize = 0; |
| } |
| } |
| |
| ArraySize = ArrSize; |
| |
| return State; |
| } |
| |
| ProgramStateRef VLASizeChecker::checkVLAIndexSize(CheckerContext &C, |
| ProgramStateRef State, |
| const Expr *SizeE) const { |
| SVal SizeV = C.getSVal(SizeE); |
| |
| if (SizeV.isUndef()) { |
| reportBug(VLA_Garbage, SizeE, State, C); |
| return nullptr; |
| } |
| |
| // See if the size value is known. It can't be undefined because we would have |
| // warned about that already. |
| if (SizeV.isUnknown()) |
| return nullptr; |
| |
| // Check if the size is tainted. |
| if (isTainted(State, SizeV)) { |
| reportBug(VLA_Tainted, SizeE, nullptr, C, |
| std::make_unique<TaintBugVisitor>(SizeV)); |
| return nullptr; |
| } |
| |
| // Check if the size is zero. |
| DefinedSVal SizeD = SizeV.castAs<DefinedSVal>(); |
| |
| ProgramStateRef StateNotZero, StateZero; |
| std::tie(StateNotZero, StateZero) = State->assume(SizeD); |
| |
| if (StateZero && !StateNotZero) { |
| reportBug(VLA_Zero, SizeE, StateZero, C); |
| return nullptr; |
| } |
| |
| // From this point on, assume that the size is not zero. |
| State = StateNotZero; |
| |
| // Check if the size is negative. |
| SValBuilder &SVB = C.getSValBuilder(); |
| |
| QualType SizeTy = SizeE->getType(); |
| DefinedOrUnknownSVal Zero = SVB.makeZeroVal(SizeTy); |
| |
| SVal LessThanZeroVal = SVB.evalBinOp(State, BO_LT, SizeD, Zero, SizeTy); |
| if (Optional<DefinedSVal> LessThanZeroDVal = |
| LessThanZeroVal.getAs<DefinedSVal>()) { |
| ConstraintManager &CM = C.getConstraintManager(); |
| ProgramStateRef StatePos, StateNeg; |
| |
| std::tie(StateNeg, StatePos) = CM.assumeDual(State, *LessThanZeroDVal); |
| if (StateNeg && !StatePos) { |
| reportBug(VLA_Negative, SizeE, State, C); |
| return nullptr; |
| } |
| State = StatePos; |
| } |
| |
| return State; |
| } |
| |
| void VLASizeChecker::reportBug( |
| VLASize_Kind Kind, const Expr *SizeE, ProgramStateRef State, |
| CheckerContext &C, std::unique_ptr<BugReporterVisitor> Visitor) const { |
| // Generate an error node. |
| ExplodedNode *N = C.generateErrorNode(State); |
| if (!N) |
| return; |
| |
| if (!BT) |
| BT.reset(new BuiltinBug( |
| this, "Dangerous variable-length array (VLA) declaration")); |
| |
| SmallString<256> buf; |
| llvm::raw_svector_ostream os(buf); |
| os << "Declared variable-length array (VLA) "; |
| switch (Kind) { |
| case VLA_Garbage: |
| os << "uses a garbage value as its size"; |
| break; |
| case VLA_Zero: |
| os << "has zero size"; |
| break; |
| case VLA_Tainted: |
| os << "has tainted size"; |
| break; |
| case VLA_Negative: |
| os << "has negative size"; |
| break; |
| case VLA_Overflow: |
| os << "has too large size"; |
| break; |
| } |
| |
| auto report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); |
| report->addVisitor(std::move(Visitor)); |
| report->addRange(SizeE->getSourceRange()); |
| bugreporter::trackExpressionValue(N, SizeE, *report); |
| C.emitReport(std::move(report)); |
| } |
| |
| void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { |
| if (!DS->isSingleDecl()) |
| return; |
| |
| ASTContext &Ctx = C.getASTContext(); |
| SValBuilder &SVB = C.getSValBuilder(); |
| ProgramStateRef State = C.getState(); |
| QualType TypeToCheck; |
| |
| const VarDecl *VD = dyn_cast<VarDecl>(DS->getSingleDecl()); |
| |
| if (VD) |
| TypeToCheck = VD->getType().getCanonicalType(); |
| else if (const auto *TND = dyn_cast<TypedefNameDecl>(DS->getSingleDecl())) |
| TypeToCheck = TND->getUnderlyingType().getCanonicalType(); |
| else |
| return; |
| |
| const VariableArrayType *VLA = Ctx.getAsVariableArrayType(TypeToCheck); |
| if (!VLA) |
| return; |
| |
| // Check the VLA sizes for validity. |
| |
| SVal ArraySize; |
| |
| State = checkVLA(C, State, VLA, ArraySize); |
| if (!State) |
| return; |
| |
| auto ArraySizeNL = ArraySize.getAs<NonLoc>(); |
| if (!ArraySizeNL) { |
| // Array size could not be determined but state may contain new assumptions. |
| C.addTransition(State); |
| return; |
| } |
| |
| // VLASizeChecker is responsible for defining the extent of the array. |
| if (VD) { |
| State = |
| setDynamicExtent(State, State->getRegion(VD, C.getLocationContext()), |
| ArraySize.castAs<DefinedOrUnknownSVal>(), SVB); |
| } |
| |
| // Remember our assumptions! |
| C.addTransition(State); |
| } |
| |
| void VLASizeChecker::checkPreStmt(const UnaryExprOrTypeTraitExpr *UETTE, |
| CheckerContext &C) const { |
| // Want to check for sizeof. |
| if (UETTE->getKind() != UETT_SizeOf) |
| return; |
| |
| // Ensure a type argument. |
| if (!UETTE->isArgumentType()) |
| return; |
| |
| const VariableArrayType *VLA = C.getASTContext().getAsVariableArrayType( |
| UETTE->getTypeOfArgument().getCanonicalType()); |
| // Ensure that the type is a VLA. |
| if (!VLA) |
| return; |
| |
| ProgramStateRef State = C.getState(); |
| SVal ArraySize; |
| State = checkVLA(C, State, VLA, ArraySize); |
| if (!State) |
| return; |
| |
| C.addTransition(State); |
| } |
| |
| void ento::registerVLASizeChecker(CheckerManager &mgr) { |
| mgr.registerChecker<VLASizeChecker>(); |
| } |
| |
| bool ento::shouldRegisterVLASizeChecker(const CheckerManager &mgr) { |
| return true; |
| } |