clang API Documentation
00001 //== ArrayBoundCheckerV2.cpp ------------------------------------*- C++ -*--==// 00002 // 00003 // The LLVM Compiler Infrastructure 00004 // 00005 // This file is distributed under the University of Illinois Open Source 00006 // License. See LICENSE.TXT for details. 00007 // 00008 //===----------------------------------------------------------------------===// 00009 // 00010 // This file defines ArrayBoundCheckerV2, which is a path-sensitive check 00011 // which looks for an out-of-bound array element access. 00012 // 00013 //===----------------------------------------------------------------------===// 00014 00015 #include "ClangSACheckers.h" 00016 #include "clang/AST/CharUnits.h" 00017 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 00018 #include "clang/StaticAnalyzer/Core/Checker.h" 00019 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 00020 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 00021 #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" 00022 #include "llvm/ADT/SmallString.h" 00023 #include "llvm/Support/raw_ostream.h" 00024 00025 using namespace clang; 00026 using namespace ento; 00027 00028 namespace { 00029 class ArrayBoundCheckerV2 : 00030 public Checker<check::Location> { 00031 mutable std::unique_ptr<BuiltinBug> BT; 00032 00033 enum OOB_Kind { OOB_Precedes, OOB_Excedes, OOB_Tainted }; 00034 00035 void reportOOB(CheckerContext &C, ProgramStateRef errorState, 00036 OOB_Kind kind) const; 00037 00038 public: 00039 void checkLocation(SVal l, bool isLoad, const Stmt*S, 00040 CheckerContext &C) const; 00041 }; 00042 00043 // FIXME: Eventually replace RegionRawOffset with this class. 00044 class RegionRawOffsetV2 { 00045 private: 00046 const SubRegion *baseRegion; 00047 SVal byteOffset; 00048 00049 RegionRawOffsetV2() 00050 : baseRegion(nullptr), byteOffset(UnknownVal()) {} 00051 00052 public: 00053 RegionRawOffsetV2(const SubRegion* base, SVal offset) 00054 : baseRegion(base), byteOffset(offset) {} 00055 00056 NonLoc getByteOffset() const { return byteOffset.castAs<NonLoc>(); } 00057 const SubRegion *getRegion() const { return baseRegion; } 00058 00059 static RegionRawOffsetV2 computeOffset(ProgramStateRef state, 00060 SValBuilder &svalBuilder, 00061 SVal location); 00062 00063 void dump() const; 00064 void dumpToStream(raw_ostream &os) const; 00065 }; 00066 } 00067 00068 static SVal computeExtentBegin(SValBuilder &svalBuilder, 00069 const MemRegion *region) { 00070 while (true) 00071 switch (region->getKind()) { 00072 default: 00073 return svalBuilder.makeZeroArrayIndex(); 00074 case MemRegion::SymbolicRegionKind: 00075 // FIXME: improve this later by tracking symbolic lower bounds 00076 // for symbolic regions. 00077 return UnknownVal(); 00078 case MemRegion::ElementRegionKind: 00079 region = cast<SubRegion>(region)->getSuperRegion(); 00080 continue; 00081 } 00082 } 00083 00084 void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, 00085 const Stmt* LoadS, 00086 CheckerContext &checkerContext) const { 00087 00088 // NOTE: Instead of using ProgramState::assumeInBound(), we are prototyping 00089 // some new logic here that reasons directly about memory region extents. 00090 // Once that logic is more mature, we can bring it back to assumeInBound() 00091 // for all clients to use. 00092 // 00093 // The algorithm we are using here for bounds checking is to see if the 00094 // memory access is within the extent of the base region. Since we 00095 // have some flexibility in defining the base region, we can achieve 00096 // various levels of conservatism in our buffer overflow checking. 00097 ProgramStateRef state = checkerContext.getState(); 00098 ProgramStateRef originalState = state; 00099 00100 SValBuilder &svalBuilder = checkerContext.getSValBuilder(); 00101 const RegionRawOffsetV2 &rawOffset = 00102 RegionRawOffsetV2::computeOffset(state, svalBuilder, location); 00103 00104 if (!rawOffset.getRegion()) 00105 return; 00106 00107 // CHECK LOWER BOUND: Is byteOffset < extent begin? 00108 // If so, we are doing a load/store 00109 // before the first valid offset in the memory region. 00110 00111 SVal extentBegin = computeExtentBegin(svalBuilder, rawOffset.getRegion()); 00112 00113 if (Optional<NonLoc> NV = extentBegin.getAs<NonLoc>()) { 00114 SVal lowerBound = 00115 svalBuilder.evalBinOpNN(state, BO_LT, rawOffset.getByteOffset(), *NV, 00116 svalBuilder.getConditionType()); 00117 00118 Optional<NonLoc> lowerBoundToCheck = lowerBound.getAs<NonLoc>(); 00119 if (!lowerBoundToCheck) 00120 return; 00121 00122 ProgramStateRef state_precedesLowerBound, state_withinLowerBound; 00123 std::tie(state_precedesLowerBound, state_withinLowerBound) = 00124 state->assume(*lowerBoundToCheck); 00125 00126 // Are we constrained enough to definitely precede the lower bound? 00127 if (state_precedesLowerBound && !state_withinLowerBound) { 00128 reportOOB(checkerContext, state_precedesLowerBound, OOB_Precedes); 00129 return; 00130 } 00131 00132 // Otherwise, assume the constraint of the lower bound. 00133 assert(state_withinLowerBound); 00134 state = state_withinLowerBound; 00135 } 00136 00137 do { 00138 // CHECK UPPER BOUND: Is byteOffset >= extent(baseRegion)? If so, 00139 // we are doing a load/store after the last valid offset. 00140 DefinedOrUnknownSVal extentVal = 00141 rawOffset.getRegion()->getExtent(svalBuilder); 00142 if (!extentVal.getAs<NonLoc>()) 00143 break; 00144 00145 SVal upperbound 00146 = svalBuilder.evalBinOpNN(state, BO_GE, rawOffset.getByteOffset(), 00147 extentVal.castAs<NonLoc>(), 00148 svalBuilder.getConditionType()); 00149 00150 Optional<NonLoc> upperboundToCheck = upperbound.getAs<NonLoc>(); 00151 if (!upperboundToCheck) 00152 break; 00153 00154 ProgramStateRef state_exceedsUpperBound, state_withinUpperBound; 00155 std::tie(state_exceedsUpperBound, state_withinUpperBound) = 00156 state->assume(*upperboundToCheck); 00157 00158 // If we are under constrained and the index variables are tainted, report. 00159 if (state_exceedsUpperBound && state_withinUpperBound) { 00160 if (state->isTainted(rawOffset.getByteOffset())) 00161 reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted); 00162 return; 00163 } 00164 00165 // If we are constrained enough to definitely exceed the upper bound, report. 00166 if (state_exceedsUpperBound) { 00167 assert(!state_withinUpperBound); 00168 reportOOB(checkerContext, state_exceedsUpperBound, OOB_Excedes); 00169 return; 00170 } 00171 00172 assert(state_withinUpperBound); 00173 state = state_withinUpperBound; 00174 } 00175 while (false); 00176 00177 if (state != originalState) 00178 checkerContext.addTransition(state); 00179 } 00180 00181 void ArrayBoundCheckerV2::reportOOB(CheckerContext &checkerContext, 00182 ProgramStateRef errorState, 00183 OOB_Kind kind) const { 00184 00185 ExplodedNode *errorNode = checkerContext.generateSink(errorState); 00186 if (!errorNode) 00187 return; 00188 00189 if (!BT) 00190 BT.reset(new BuiltinBug(this, "Out-of-bound access")); 00191 00192 // FIXME: This diagnostics are preliminary. We should get far better 00193 // diagnostics for explaining buffer overruns. 00194 00195 SmallString<256> buf; 00196 llvm::raw_svector_ostream os(buf); 00197 os << "Out of bound memory access "; 00198 switch (kind) { 00199 case OOB_Precedes: 00200 os << "(accessed memory precedes memory block)"; 00201 break; 00202 case OOB_Excedes: 00203 os << "(access exceeds upper limit of memory block)"; 00204 break; 00205 case OOB_Tainted: 00206 os << "(index is tainted)"; 00207 break; 00208 } 00209 00210 checkerContext.emitReport(new BugReport(*BT, os.str(), errorNode)); 00211 } 00212 00213 void RegionRawOffsetV2::dump() const { 00214 dumpToStream(llvm::errs()); 00215 } 00216 00217 void RegionRawOffsetV2::dumpToStream(raw_ostream &os) const { 00218 os << "raw_offset_v2{" << getRegion() << ',' << getByteOffset() << '}'; 00219 } 00220 00221 00222 // Lazily computes a value to be used by 'computeOffset'. If 'val' 00223 // is unknown or undefined, we lazily substitute '0'. Otherwise, 00224 // return 'val'. 00225 static inline SVal getValue(SVal val, SValBuilder &svalBuilder) { 00226 return val.getAs<UndefinedVal>() ? svalBuilder.makeArrayIndex(0) : val; 00227 } 00228 00229 // Scale a base value by a scaling factor, and return the scaled 00230 // value as an SVal. Used by 'computeOffset'. 00231 static inline SVal scaleValue(ProgramStateRef state, 00232 NonLoc baseVal, CharUnits scaling, 00233 SValBuilder &sb) { 00234 return sb.evalBinOpNN(state, BO_Mul, baseVal, 00235 sb.makeArrayIndex(scaling.getQuantity()), 00236 sb.getArrayIndexType()); 00237 } 00238 00239 // Add an SVal to another, treating unknown and undefined values as 00240 // summing to UnknownVal. Used by 'computeOffset'. 00241 static SVal addValue(ProgramStateRef state, SVal x, SVal y, 00242 SValBuilder &svalBuilder) { 00243 // We treat UnknownVals and UndefinedVals the same here because we 00244 // only care about computing offsets. 00245 if (x.isUnknownOrUndef() || y.isUnknownOrUndef()) 00246 return UnknownVal(); 00247 00248 return svalBuilder.evalBinOpNN(state, BO_Add, x.castAs<NonLoc>(), 00249 y.castAs<NonLoc>(), 00250 svalBuilder.getArrayIndexType()); 00251 } 00252 00253 /// Compute a raw byte offset from a base region. Used for array bounds 00254 /// checking. 00255 RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state, 00256 SValBuilder &svalBuilder, 00257 SVal location) 00258 { 00259 const MemRegion *region = location.getAsRegion(); 00260 SVal offset = UndefinedVal(); 00261 00262 while (region) { 00263 switch (region->getKind()) { 00264 default: { 00265 if (const SubRegion *subReg = dyn_cast<SubRegion>(region)) { 00266 offset = getValue(offset, svalBuilder); 00267 if (!offset.isUnknownOrUndef()) 00268 return RegionRawOffsetV2(subReg, offset); 00269 } 00270 return RegionRawOffsetV2(); 00271 } 00272 case MemRegion::ElementRegionKind: { 00273 const ElementRegion *elemReg = cast<ElementRegion>(region); 00274 SVal index = elemReg->getIndex(); 00275 if (!index.getAs<NonLoc>()) 00276 return RegionRawOffsetV2(); 00277 QualType elemType = elemReg->getElementType(); 00278 // If the element is an incomplete type, go no further. 00279 ASTContext &astContext = svalBuilder.getContext(); 00280 if (elemType->isIncompleteType()) 00281 return RegionRawOffsetV2(); 00282 00283 // Update the offset. 00284 offset = addValue(state, 00285 getValue(offset, svalBuilder), 00286 scaleValue(state, 00287 index.castAs<NonLoc>(), 00288 astContext.getTypeSizeInChars(elemType), 00289 svalBuilder), 00290 svalBuilder); 00291 00292 if (offset.isUnknownOrUndef()) 00293 return RegionRawOffsetV2(); 00294 00295 region = elemReg->getSuperRegion(); 00296 continue; 00297 } 00298 } 00299 } 00300 return RegionRawOffsetV2(); 00301 } 00302 00303 void ento::registerArrayBoundCheckerV2(CheckerManager &mgr) { 00304 mgr.registerChecker<ArrayBoundCheckerV2>(); 00305 }