1 | //=== VLASizeChecker.cpp - Undefined dereference checker --------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This defines VLASizeChecker, a builtin check in ExprEngine that |
10 | // performs checks for declaration of VLA of undefined or zero size. |
11 | // In addition, VLASizeChecker is responsible for defining the extent |
12 | // of the MemRegion that represents a VLA. |
13 | // |
14 | //===----------------------------------------------------------------------===// |
15 | |
16 | #include "clang/AST/CharUnits.h" |
17 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
18 | #include "clang/StaticAnalyzer/Checkers/Taint.h" |
19 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
20 | #include "clang/StaticAnalyzer/Core/Checker.h" |
21 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
22 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
23 | #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" |
24 | #include "llvm/ADT/STLExtras.h" |
25 | #include "llvm/ADT/SmallString.h" |
26 | #include "llvm/Support/raw_ostream.h" |
27 | #include <optional> |
28 | |
29 | using namespace clang; |
30 | using namespace ento; |
31 | using namespace taint; |
32 | |
33 | namespace { |
34 | class VLASizeChecker |
35 | : public Checker<check::PreStmt<DeclStmt>, |
36 | check::PreStmt<UnaryExprOrTypeTraitExpr>> { |
37 | const BugType BT{this, "Dangerous variable-length array (VLA) declaration" }; |
38 | const BugType TaintBT{this, |
39 | "Dangerous variable-length array (VLA) declaration" , |
40 | categories::TaintedData}; |
41 | enum VLASize_Kind { VLA_Garbage, VLA_Zero, VLA_Negative, VLA_Overflow }; |
42 | |
43 | /// Check a VLA for validity. |
44 | /// Every dimension of the array and the total size is checked for validity. |
45 | /// Returns null or a new state where the size is validated. |
46 | /// 'ArraySize' will contain SVal that refers to the total size (in char) |
47 | /// of the array. |
48 | ProgramStateRef checkVLA(CheckerContext &C, ProgramStateRef State, |
49 | const VariableArrayType *VLA, SVal &ArraySize) const; |
50 | /// Check a single VLA index size expression for validity. |
51 | ProgramStateRef checkVLAIndexSize(CheckerContext &C, ProgramStateRef State, |
52 | const Expr *SizeE) const; |
53 | |
54 | void reportBug(VLASize_Kind Kind, const Expr *SizeE, ProgramStateRef State, |
55 | CheckerContext &C) const; |
56 | |
57 | void reportTaintBug(const Expr *SizeE, ProgramStateRef State, |
58 | CheckerContext &C, SVal TaintedSVal) const; |
59 | |
60 | public: |
61 | void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; |
62 | void checkPreStmt(const UnaryExprOrTypeTraitExpr *UETTE, |
63 | CheckerContext &C) const; |
64 | }; |
65 | } // end anonymous namespace |
66 | |
67 | ProgramStateRef VLASizeChecker::checkVLA(CheckerContext &C, |
68 | ProgramStateRef State, |
69 | const VariableArrayType *VLA, |
70 | SVal &ArraySize) const { |
71 | assert(VLA && "Function should be called with non-null VLA argument." ); |
72 | |
73 | const VariableArrayType *VLALast = nullptr; |
74 | llvm::SmallVector<const Expr *, 2> VLASizes; |
75 | |
76 | // Walk over the VLAs for every dimension until a non-VLA is found. |
77 | // There is a VariableArrayType for every dimension (fixed or variable) until |
78 | // the most inner array that is variably modified. |
79 | // Dimension sizes are collected into 'VLASizes'. 'VLALast' is set to the |
80 | // innermost VLA that was encountered. |
81 | // In "int vla[x][2][y][3]" this will be the array for index "y" (with type |
82 | // int[3]). 'VLASizes' contains 'x', '2', and 'y'. |
83 | while (VLA) { |
84 | const Expr *SizeE = VLA->getSizeExpr(); |
85 | State = checkVLAIndexSize(C, State, SizeE); |
86 | if (!State) |
87 | return nullptr; |
88 | VLASizes.push_back(Elt: SizeE); |
89 | VLALast = VLA; |
90 | VLA = C.getASTContext().getAsVariableArrayType(T: VLA->getElementType()); |
91 | }; |
92 | assert(VLALast && |
93 | "Array should have at least one variably-modified dimension." ); |
94 | |
95 | ASTContext &Ctx = C.getASTContext(); |
96 | SValBuilder &SVB = C.getSValBuilder(); |
97 | CanQualType SizeTy = Ctx.getSizeType(); |
98 | uint64_t SizeMax = |
99 | SVB.getBasicValueFactory().getMaxValue(T: SizeTy).getZExtValue(); |
100 | |
101 | // Get the element size. |
102 | CharUnits EleSize = Ctx.getTypeSizeInChars(T: VLALast->getElementType()); |
103 | NonLoc ArrSize = |
104 | SVB.makeIntVal(integer: EleSize.getQuantity(), type: SizeTy).castAs<NonLoc>(); |
105 | |
106 | // Try to calculate the known real size of the array in KnownSize. |
107 | uint64_t KnownSize = 0; |
108 | if (const llvm::APSInt *KV = SVB.getKnownValue(state: State, val: ArrSize)) |
109 | KnownSize = KV->getZExtValue(); |
110 | |
111 | for (const Expr *SizeE : VLASizes) { |
112 | auto SizeD = C.getSVal(S: SizeE).castAs<DefinedSVal>(); |
113 | // Convert the array length to size_t. |
114 | NonLoc IndexLength = |
115 | SVB.evalCast(V: SizeD, CastTy: SizeTy, OriginalTy: SizeE->getType()).castAs<NonLoc>(); |
116 | // Multiply the array length by the element size. |
117 | SVal Mul = SVB.evalBinOpNN(state: State, op: BO_Mul, lhs: ArrSize, rhs: IndexLength, resultTy: SizeTy); |
118 | if (auto MulNonLoc = Mul.getAs<NonLoc>()) |
119 | ArrSize = *MulNonLoc; |
120 | else |
121 | // Extent could not be determined. |
122 | return State; |
123 | |
124 | if (const llvm::APSInt *IndexLVal = SVB.getKnownValue(state: State, val: IndexLength)) { |
125 | // Check if the array size will overflow. |
126 | // Size overflow check does not work with symbolic expressions because a |
127 | // overflow situation can not be detected easily. |
128 | uint64_t IndexL = IndexLVal->getZExtValue(); |
129 | // FIXME: See https://reviews.llvm.org/D80903 for discussion of |
130 | // some difference in assume and getKnownValue that leads to |
131 | // unexpected behavior. Just bail on IndexL == 0 at this point. |
132 | if (IndexL == 0) |
133 | return nullptr; |
134 | |
135 | if (KnownSize <= SizeMax / IndexL) { |
136 | KnownSize *= IndexL; |
137 | } else { |
138 | // Array size does not fit into size_t. |
139 | reportBug(Kind: VLA_Overflow, SizeE, State, C); |
140 | return nullptr; |
141 | } |
142 | } else { |
143 | KnownSize = 0; |
144 | } |
145 | } |
146 | |
147 | ArraySize = ArrSize; |
148 | |
149 | return State; |
150 | } |
151 | |
152 | ProgramStateRef VLASizeChecker::checkVLAIndexSize(CheckerContext &C, |
153 | ProgramStateRef State, |
154 | const Expr *SizeE) const { |
155 | SVal SizeV = C.getSVal(S: SizeE); |
156 | |
157 | if (SizeV.isUndef()) { |
158 | reportBug(Kind: VLA_Garbage, SizeE, State, C); |
159 | return nullptr; |
160 | } |
161 | |
162 | // See if the size value is known. It can't be undefined because we would have |
163 | // warned about that already. |
164 | if (SizeV.isUnknown()) |
165 | return nullptr; |
166 | |
167 | // Check if the size is zero. |
168 | DefinedSVal SizeD = SizeV.castAs<DefinedSVal>(); |
169 | |
170 | ProgramStateRef StateNotZero, StateZero; |
171 | std::tie(args&: StateNotZero, args&: StateZero) = State->assume(Cond: SizeD); |
172 | |
173 | if (StateZero && !StateNotZero) { |
174 | reportBug(Kind: VLA_Zero, SizeE, State: StateZero, C); |
175 | return nullptr; |
176 | } |
177 | |
178 | // From this point on, assume that the size is not zero. |
179 | State = StateNotZero; |
180 | |
181 | // Check if the size is negative. |
182 | SValBuilder &SVB = C.getSValBuilder(); |
183 | |
184 | QualType SizeTy = SizeE->getType(); |
185 | DefinedOrUnknownSVal Zero = SVB.makeZeroVal(type: SizeTy); |
186 | |
187 | SVal LessThanZeroVal = |
188 | SVB.evalBinOp(state: State, op: BO_LT, lhs: SizeD, rhs: Zero, type: SVB.getConditionType()); |
189 | ProgramStateRef StatePos, StateNeg; |
190 | if (std::optional<DefinedSVal> LessThanZeroDVal = |
191 | LessThanZeroVal.getAs<DefinedSVal>()) { |
192 | ConstraintManager &CM = C.getConstraintManager(); |
193 | |
194 | std::tie(args&: StateNeg, args&: StatePos) = CM.assumeDual(State, Cond: *LessThanZeroDVal); |
195 | if (StateNeg && !StatePos) { |
196 | reportBug(Kind: VLA_Negative, SizeE, State, C); |
197 | return nullptr; |
198 | } |
199 | State = StatePos; |
200 | } |
201 | |
202 | // Check if the size is tainted. |
203 | if ((StateNeg || StateZero) && isTainted(State, V: SizeV)) { |
204 | reportTaintBug(SizeE, State, C, TaintedSVal: SizeV); |
205 | return nullptr; |
206 | } |
207 | |
208 | return State; |
209 | } |
210 | |
211 | void VLASizeChecker::reportTaintBug(const Expr *SizeE, ProgramStateRef State, |
212 | CheckerContext &C, SVal TaintedSVal) const { |
213 | // Generate an error node. |
214 | ExplodedNode *N = C.generateErrorNode(State); |
215 | if (!N) |
216 | return; |
217 | |
218 | SmallString<256> buf; |
219 | llvm::raw_svector_ostream os(buf); |
220 | os << "Declared variable-length array (VLA) " ; |
221 | os << "has tainted (attacker controlled) size that can be 0 or negative" ; |
222 | |
223 | auto report = std::make_unique<PathSensitiveBugReport>(args: TaintBT, args: os.str(), args&: N); |
224 | report->addRange(R: SizeE->getSourceRange()); |
225 | bugreporter::trackExpressionValue(N, E: SizeE, R&: *report); |
226 | // The vla size may be a complex expression where multiple memory locations |
227 | // are tainted. |
228 | for (auto Sym : getTaintedSymbols(State, V: TaintedSVal)) |
229 | report->markInteresting(sym: Sym); |
230 | C.emitReport(R: std::move(report)); |
231 | } |
232 | |
233 | void VLASizeChecker::reportBug(VLASize_Kind Kind, const Expr *SizeE, |
234 | ProgramStateRef State, CheckerContext &C) const { |
235 | // Generate an error node. |
236 | ExplodedNode *N = C.generateErrorNode(State); |
237 | if (!N) |
238 | return; |
239 | |
240 | SmallString<256> buf; |
241 | llvm::raw_svector_ostream os(buf); |
242 | os << "Declared variable-length array (VLA) " ; |
243 | switch (Kind) { |
244 | case VLA_Garbage: |
245 | os << "uses a garbage value as its size" ; |
246 | break; |
247 | case VLA_Zero: |
248 | os << "has zero size" ; |
249 | break; |
250 | case VLA_Negative: |
251 | os << "has negative size" ; |
252 | break; |
253 | case VLA_Overflow: |
254 | os << "has too large size" ; |
255 | break; |
256 | } |
257 | |
258 | auto report = std::make_unique<PathSensitiveBugReport>(args: BT, args: os.str(), args&: N); |
259 | report->addRange(R: SizeE->getSourceRange()); |
260 | bugreporter::trackExpressionValue(N, E: SizeE, R&: *report); |
261 | C.emitReport(R: std::move(report)); |
262 | } |
263 | |
264 | void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { |
265 | if (!DS->isSingleDecl()) |
266 | return; |
267 | |
268 | ASTContext &Ctx = C.getASTContext(); |
269 | SValBuilder &SVB = C.getSValBuilder(); |
270 | ProgramStateRef State = C.getState(); |
271 | QualType TypeToCheck; |
272 | |
273 | const VarDecl *VD = dyn_cast<VarDecl>(Val: DS->getSingleDecl()); |
274 | |
275 | if (VD) |
276 | TypeToCheck = VD->getType().getCanonicalType(); |
277 | else if (const auto *TND = dyn_cast<TypedefNameDecl>(Val: DS->getSingleDecl())) |
278 | TypeToCheck = TND->getUnderlyingType().getCanonicalType(); |
279 | else |
280 | return; |
281 | |
282 | const VariableArrayType *VLA = Ctx.getAsVariableArrayType(T: TypeToCheck); |
283 | if (!VLA) |
284 | return; |
285 | |
286 | // Check the VLA sizes for validity. |
287 | |
288 | SVal ArraySize; |
289 | |
290 | State = checkVLA(C, State, VLA, ArraySize); |
291 | if (!State) |
292 | return; |
293 | |
294 | if (!isa<NonLoc>(Val: ArraySize)) { |
295 | // Array size could not be determined but state may contain new assumptions. |
296 | C.addTransition(State); |
297 | return; |
298 | } |
299 | |
300 | // VLASizeChecker is responsible for defining the extent of the array. |
301 | if (VD) { |
302 | State = |
303 | setDynamicExtent(State, MR: State->getRegion(D: VD, LC: C.getLocationContext()), |
304 | Extent: ArraySize.castAs<NonLoc>(), SVB); |
305 | } |
306 | |
307 | // Remember our assumptions! |
308 | C.addTransition(State); |
309 | } |
310 | |
311 | void VLASizeChecker::checkPreStmt(const UnaryExprOrTypeTraitExpr *UETTE, |
312 | CheckerContext &C) const { |
313 | // Want to check for sizeof. |
314 | if (UETTE->getKind() != UETT_SizeOf) |
315 | return; |
316 | |
317 | // Ensure a type argument. |
318 | if (!UETTE->isArgumentType()) |
319 | return; |
320 | |
321 | const VariableArrayType *VLA = C.getASTContext().getAsVariableArrayType( |
322 | T: UETTE->getTypeOfArgument().getCanonicalType()); |
323 | // Ensure that the type is a VLA. |
324 | if (!VLA) |
325 | return; |
326 | |
327 | ProgramStateRef State = C.getState(); |
328 | SVal ArraySize; |
329 | State = checkVLA(C, State, VLA, ArraySize); |
330 | if (!State) |
331 | return; |
332 | |
333 | C.addTransition(State); |
334 | } |
335 | |
336 | void ento::registerVLASizeChecker(CheckerManager &mgr) { |
337 | mgr.registerChecker<VLASizeChecker>(); |
338 | } |
339 | |
340 | bool ento::shouldRegisterVLASizeChecker(const CheckerManager &mgr) { |
341 | return true; |
342 | } |
343 | |