Skip to content

Commit 65ac5b8

Browse files
authored
Merge pull request github#5847 from MathiasVP/improve-wrong-in-detecting-and-handling-memory-allocation-errors
Improve wrong in detecting and handling memory allocation errors
2 parents 2241d7b + fc7d9c2 commit 65ac5b8

10 files changed

+358
-130
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ make sure to handle the possibility of null pointers if <code>new(std::nothrow)
1717

1818
</recommendation>
1919
<example>
20-
<sample src="WrongInDetectingAndHandlingMemoryAllocationErrors.cpp" />
20+
<sample src="IncorrectAllocationErrorHandling.cpp" />
2121

2222
</example>
2323
<references>
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/**
2+
* @name Incorrect allocation-error handling
3+
* @description `operator new` throws an exception on allocation failures, while `operator new(std::nothrow)` returns a null pointer. Mixing up these two failure conditions can result in unexpected behavior.
4+
* @kind problem
5+
* @id cpp/incorrect-allocation-error-handling
6+
* @problem.severity warning
7+
* @precision medium
8+
* @tags correctness
9+
* security
10+
* external/cwe/cwe-570
11+
*/
12+
13+
import cpp
14+
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
15+
import semmle.code.cpp.controlflow.Guards
16+
17+
/**
18+
* A C++ `delete` or `delete[]` expression.
19+
*/
20+
class DeleteOrDeleteArrayExpr extends Expr {
21+
DeleteOrDeleteArrayExpr() { this instanceof DeleteExpr or this instanceof DeleteArrayExpr }
22+
23+
DeallocationFunction getDeallocator() {
24+
result = [this.(DeleteExpr).getDeallocator(), this.(DeleteArrayExpr).getDeallocator()]
25+
}
26+
27+
Destructor getDestructor() {
28+
result = [this.(DeleteExpr).getDestructor(), this.(DeleteArrayExpr).getDestructor()]
29+
}
30+
}
31+
32+
/** Gets the `Constructor` invoked when `newExpr` allocates memory. */
33+
Constructor getConstructorForAllocation(NewOrNewArrayExpr newExpr) {
34+
result.getACallToThisFunction() = newExpr.getInitializer()
35+
}
36+
37+
/** Gets the `Destructor` invoked when `deleteExpr` deallocates memory. */
38+
Destructor getDestructorForDeallocation(DeleteOrDeleteArrayExpr deleteExpr) {
39+
result = deleteExpr.getDestructor()
40+
}
41+
42+
/** Holds if the evaluation of `newExpr` may throw an exception. */
43+
predicate newMayThrow(NewOrNewArrayExpr newExpr) {
44+
functionMayThrow(newExpr.getAllocator()) or
45+
functionMayThrow(getConstructorForAllocation(newExpr))
46+
}
47+
48+
/** Holds if the evaluation of `deleteExpr` may throw an exception. */
49+
predicate deleteMayThrow(DeleteOrDeleteArrayExpr deleteExpr) {
50+
functionMayThrow(deleteExpr.getDeallocator()) or
51+
functionMayThrow(getDestructorForDeallocation(deleteExpr))
52+
}
53+
54+
/**
55+
* Holds if the function may throw an exception when called. That is, if the body of the function looks
56+
* like it might throw an exception, and the function does not have a `noexcept` or `throw()` specifier.
57+
*/
58+
predicate functionMayThrow(Function f) {
59+
(not exists(f.getBlock()) or stmtMayThrow(f.getBlock())) and
60+
not f.isNoExcept() and
61+
not f.isNoThrow()
62+
}
63+
64+
/** Holds if the evaluation of `stmt` may throw an exception. */
65+
predicate stmtMayThrow(Stmt stmt) {
66+
stmtMayThrow(stmt.(BlockStmt).getAStmt())
67+
or
68+
convertedExprMayThrow(stmt.(ExprStmt).getExpr())
69+
or
70+
convertedExprMayThrow(stmt.(DeclStmt).getADeclaration().(Variable).getInitializer().getExpr())
71+
or
72+
exists(IfStmt ifStmt | ifStmt = stmt |
73+
convertedExprMayThrow(ifStmt.getCondition()) or
74+
stmtMayThrow([ifStmt.getThen(), ifStmt.getElse()])
75+
)
76+
or
77+
exists(ConstexprIfStmt constIfStmt | constIfStmt = stmt |
78+
stmtMayThrow([constIfStmt.getThen(), constIfStmt.getElse()])
79+
)
80+
or
81+
exists(Loop loop | loop = stmt |
82+
convertedExprMayThrow(loop.getCondition()) or
83+
stmtMayThrow(loop.getStmt())
84+
)
85+
or
86+
// The case for `Loop` already checked the condition and the statement.
87+
convertedExprMayThrow(stmt.(RangeBasedForStmt).getUpdate())
88+
or
89+
// The case for `Loop` already checked the condition and the statement.
90+
exists(ForStmt forStmt | forStmt = stmt |
91+
stmtMayThrow(forStmt.getInitialization())
92+
or
93+
convertedExprMayThrow(forStmt.getUpdate())
94+
)
95+
or
96+
exists(SwitchStmt switchStmt | switchStmt = stmt |
97+
convertedExprMayThrow(switchStmt.getExpr()) or
98+
stmtMayThrow(switchStmt.getStmt())
99+
)
100+
or
101+
// NOTE: We don't include `TryStmt` as those exceptions are not "observable" outside the function.
102+
stmtMayThrow(stmt.(Handler).getBlock())
103+
or
104+
convertedExprMayThrow(stmt.(CoReturnStmt).getExpr())
105+
or
106+
convertedExprMayThrow(stmt.(ReturnStmt).getExpr())
107+
}
108+
109+
/** Holds if the evaluation of `e` (including conversions) may throw an exception. */
110+
predicate convertedExprMayThrow(Expr e) {
111+
exprMayThrow(e)
112+
or
113+
convertedExprMayThrow(e.getConversion())
114+
}
115+
116+
/** Holds if the evaluation of `e` may throw an exception. */
117+
predicate exprMayThrow(Expr e) {
118+
e instanceof DynamicCast
119+
or
120+
e instanceof TypeidOperator
121+
or
122+
e instanceof ThrowExpr
123+
or
124+
newMayThrow(e)
125+
or
126+
deleteMayThrow(e)
127+
or
128+
convertedExprMayThrow(e.(UnaryOperation).getOperand())
129+
or
130+
exists(BinaryOperation binOp | binOp = e |
131+
convertedExprMayThrow([binOp.getLeftOperand(), binOp.getRightOperand()])
132+
)
133+
or
134+
exists(Assignment assign | assign = e |
135+
convertedExprMayThrow([assign.getLValue(), assign.getRValue()])
136+
)
137+
or
138+
exists(CommaExpr comma | comma = e |
139+
convertedExprMayThrow([comma.getLeftOperand(), comma.getRightOperand()])
140+
)
141+
or
142+
exists(StmtExpr stmtExpr | stmtExpr = e |
143+
convertedExprMayThrow(stmtExpr.getResultExpr()) or
144+
stmtMayThrow(stmtExpr.getStmt())
145+
)
146+
or
147+
convertedExprMayThrow(e.(Conversion).getExpr())
148+
or
149+
exists(FunctionCall fc | fc = e |
150+
not exists(fc.getTarget()) or
151+
functionMayThrow(fc.getTarget()) or
152+
convertedExprMayThrow(fc.getAnArgument())
153+
)
154+
}
155+
156+
/** An allocator that might throw an exception. */
157+
class ThrowingAllocator extends Function {
158+
ThrowingAllocator() {
159+
exists(NewOrNewArrayExpr newExpr |
160+
newExpr.getAllocator() = this and
161+
functionMayThrow(this)
162+
)
163+
}
164+
}
165+
166+
/** The `std::bad_alloc` exception and its `bsl` variant. */
167+
class BadAllocType extends Class {
168+
BadAllocType() { this.hasGlobalOrStdOrBslName("bad_alloc") }
169+
}
170+
171+
/**
172+
* A catch block that catches a `std::bad_alloc` (or any of its superclasses), or a catch
173+
* block that catches every exception (i.e., `catch(...)`).
174+
*/
175+
class BadAllocCatchBlock extends CatchBlock {
176+
BadAllocCatchBlock() {
177+
this.getParameter().getUnspecifiedType().stripType() =
178+
any(BadAllocType badAlloc).getABaseClass*()
179+
or
180+
not exists(this.getParameter())
181+
}
182+
}
183+
184+
/**
185+
* Holds if `newExpr` is embedded in a `try` statement with a catch block `catchBlock` that
186+
* catches a `std::bad_alloc` exception, but nothing in the `try` block (including the `newExpr`)
187+
* will throw that exception.
188+
*/
189+
predicate noThrowInTryBlock(NewOrNewArrayExpr newExpr, BadAllocCatchBlock catchBlock) {
190+
exists(TryStmt try |
191+
not stmtMayThrow(try.getStmt()) and
192+
try.getACatchClause() = catchBlock and
193+
newExpr.getEnclosingBlock().getEnclosingBlock*() = try.getStmt()
194+
)
195+
}
196+
197+
/**
198+
* Holds if `newExpr` is handles allocation failures by throwing an exception, yet
199+
* the guard condition `guard` compares the result of `newExpr` to a null value.
200+
*/
201+
predicate nullCheckInThrowingNew(NewOrNewArrayExpr newExpr, GuardCondition guard) {
202+
newExpr.getAllocator() instanceof ThrowingAllocator and
203+
(
204+
// Handles null comparisons.
205+
guard.ensuresEq(globalValueNumber(newExpr).getAnExpr(), any(NullValue null), _, _, _)
206+
or
207+
// Handles `if(ptr)` and `if(!ptr)` cases.
208+
guard = globalValueNumber(newExpr).getAnExpr()
209+
)
210+
}
211+
212+
from NewOrNewArrayExpr newExpr, Element element, string msg, string elementString
213+
where
214+
not newExpr.isFromUninstantiatedTemplate(_) and
215+
(
216+
noThrowInTryBlock(newExpr, element) and
217+
msg = "This allocation cannot throw. $@ is unnecessary." and
218+
elementString = "This catch block"
219+
or
220+
nullCheckInThrowingNew(newExpr, element) and
221+
msg = "This allocation cannot return null. $@ is unnecessary." and
222+
elementString = "This check"
223+
)
224+
select newExpr, msg, element, elementString

cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql

Lines changed: 0 additions & 93 deletions
This file was deleted.

cpp/ql/src/semmle/code/cpp/exprs/Expr.qll

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,24 @@ class NewOrNewArrayExpr extends Expr, @any_new_expr {
850850
this.getAllocatorCall()
851851
.getArgument(this.getAllocator().(OperatorNewAllocationFunction).getPlacementArgument())
852852
}
853+
854+
/**
855+
* For `operator new`, this gets the call or expression that initializes the allocated object, if any.
856+
*
857+
* As examples, for `new int(4)`, this will be `4`, and for `new std::vector(4)`, this will
858+
* be a call to the constructor `std::vector::vector(size_t)` with `4` as an argument.
859+
*
860+
* For `operator new[]`, this gets the call or expression that initializes the first element of the
861+
* array, if any.
862+
*
863+
* This will either be a call to the default constructor for the array's element type (as
864+
* in `new std::string[10]`), or a literal zero for arrays of scalars which are zero-initialized
865+
* due to extra parentheses (as in `new int[10]()`).
866+
*
867+
* At runtime, the constructor will be called once for each element in the array, but the
868+
* constructor call only exists once in the AST.
869+
*/
870+
final Expr getInitializer() { result = this.getChild(1) }
853871
}
854872

855873
/**
@@ -871,14 +889,6 @@ class NewExpr extends NewOrNewArrayExpr, @new_expr {
871889
override Type getAllocatedType() {
872890
new_allocated_type(underlyingElement(this), unresolveElement(result))
873891
}
874-
875-
/**
876-
* Gets the call or expression that initializes the allocated object, if any.
877-
*
878-
* As examples, for `new int(4)`, this will be `4`, and for `new std::vector(4)`, this will
879-
* be a call to the constructor `std::vector::vector(size_t)` with `4` as an argument.
880-
*/
881-
Expr getInitializer() { result = this.getChild(1) }
882892
}
883893

884894
/**
@@ -909,18 +919,6 @@ class NewArrayExpr extends NewOrNewArrayExpr, @new_array_expr {
909919
result = getType().getUnderlyingType().(PointerType).getBaseType()
910920
}
911921

912-
/**
913-
* Gets the call or expression that initializes the first element of the array, if any.
914-
*
915-
* This will either be a call to the default constructor for the array's element type (as
916-
* in `new std::string[10]`), or a literal zero for arrays of scalars which are zero-initialized
917-
* due to extra parentheses (as in `new int[10]()`).
918-
*
919-
* At runtime, the constructor will be called once for each element in the array, but the
920-
* constructor call only exists once in the AST.
921-
*/
922-
Expr getInitializer() { result = this.getChild(1) }
923-
924922
/**
925923
* Gets the extent of the non-constant array dimension, if any.
926924
*

0 commit comments

Comments
 (0)