-
Notifications
You must be signed in to change notification settings - Fork 1.8k
CPP: Add query for CWE-476: NULL Pointer Dereference when using exception handling blocks #8245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3d1f4d5
0257011
a9a2ca3
e99eaeb
62ecf54
2959150
cd561dd
ccbb443
22cf3f7
151c93f
b950942
61860c9
73de757
275b29a
75244ef
2d4d7aa
185a60f
6dec118
b98ddc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
... | ||
try { | ||
if (checkValue) throw exception(); | ||
bufMyData = new myData*[sizeInt]; | ||
|
||
} | ||
catch (...) | ||
{ | ||
for (size_t i = 0; i < sizeInt; i++) | ||
{ | ||
delete[] bufMyData[i]->buffer; // BAD | ||
delete bufMyData[i]; | ||
} | ||
... | ||
try { | ||
if (checkValue) throw exception(); | ||
bufMyData = new myData*[sizeInt]; | ||
|
||
} | ||
catch (...) | ||
{ | ||
for (size_t i = 0; i < sizeInt; i++) | ||
{ | ||
if(bufMyData[i]) | ||
{ | ||
delete[] bufMyData[i]->buffer; // GOOD | ||
delete bufMyData[i]; | ||
} | ||
} | ||
|
||
... | ||
catch (const exception &) { | ||
delete valData; | ||
throw; | ||
} | ||
catch (...) | ||
{ | ||
delete valData; // BAD | ||
... | ||
catch (const exception &) { | ||
delete valData; | ||
valData = NULL; | ||
throw; | ||
} | ||
catch (...) | ||
{ | ||
delete valData; // GOOD | ||
... | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<!DOCTYPE qhelp PUBLIC | ||
"-//Semmle//qhelp//EN" | ||
"qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
<p>When releasing memory in a catch block, be sure that the memory was allocated and has not already been released.</p> | ||
|
||
</overview> | ||
|
||
<example> | ||
<p>The following example shows erroneous and fixed ways to use exception handling.</p> | ||
<sample src="DangerousUseOfExceptionBlocks.cpp" /> | ||
|
||
</example> | ||
<references> | ||
|
||
<li> | ||
CERT C Coding Standard: | ||
<a href="https://wiki.sei.cmu.edu/confluence/display/c/EXP34-C.+Do+not+dereference+null+pointers">EXP34-C. Do not dereference null pointers</a>. | ||
</li> | ||
|
||
</references> | ||
</qhelp> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
/** | ||
* @name Dangerous use of exception blocks. | ||
* @description When clearing the data in the catch block, you must be sure that the memory was allocated before the exception. | ||
* @kind problem | ||
* @id cpp/dangerous-use-of-exception-blocks | ||
* @problem.severity warning | ||
* @precision medium | ||
* @tags correctness | ||
* security | ||
* external/cwe/cwe-476 | ||
ihsinme marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* external/cwe/cwe-415 | ||
*/ | ||
|
||
import cpp | ||
|
||
/** Holds if `vr` may be released in the `try` block associated with `cb`, or in a `catch` block prior to `cb`. */ | ||
pragma[inline] | ||
predicate doubleCallDelete(BlockStmt b, CatchAnyBlock cb, Variable vr) { | ||
// Search for exceptions after freeing memory. | ||
exists(Expr e1 | | ||
// `e1` is a delete of `vr` | ||
( | ||
ihsinme marked this conversation as resolved.
Show resolved
Hide resolved
|
||
e1 = vr.getAnAccess().getEnclosingStmt().(ExprStmt).getExpr().(DeleteArrayExpr) or | ||
e1 = vr.getAnAccess().getEnclosingStmt().(ExprStmt).getExpr().(DeleteExpr) | ||
) and | ||
e1.getEnclosingFunction() = cb.getEnclosingFunction() and | ||
// there is no assignment `vr = 0` in the `try` block after `e1` | ||
not exists(AssignExpr ae | | ||
ae.getLValue().(VariableAccess).getTarget() = vr and | ||
ae.getRValue().getValue() = "0" and | ||
e1.getASuccessor+() = ae and | ||
ae.getEnclosingStmt().getParentStmt*() = b | ||
) and | ||
// `e2` is a `throw` (or a function call that may throw) that occurs in the `try` or `catch` block after `e1` | ||
exists(Expr e2, ThrowExpr th | | ||
( | ||
e2 = th or | ||
e2 = th.getEnclosingFunction().getACallToThisFunction() | ||
) and | ||
e2.getEnclosingStmt().getParentStmt*() = b and | ||
e1.getASuccessor+() = e2 | ||
) and | ||
e1.getEnclosingStmt().getParentStmt*() = b and | ||
( | ||
// Search for a situation where there is a release in the block of `try`. | ||
b = cb.getTryStmt().getStmt() | ||
or | ||
// Search for a situation when there is a higher catch block that also frees memory. | ||
exists(b.(CatchBlock).getParameter()) | ||
) and | ||
// Exclude the presence of a check in catch block. | ||
not exists(IfStmt ifst | ifst.getEnclosingStmt().getParentStmt*() = cb.getAStmt()) | ||
) | ||
} | ||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** Holds if an exception can be thrown before the memory is allocated, and when the exception is handled, an attempt is made to access unallocated memory in the catch block. */ | ||
pragma[inline] | ||
predicate pointerDereference(CatchAnyBlock cb, Variable vr, Variable vro) { | ||
// Search exceptions before allocating memory. | ||
exists(Expr e0, Expr e1 | | ||
( | ||
// `e0` is a `new` expression (or equivalent function call) assigned to `vro` | ||
exists(AssignExpr ase | | ||
ihsinme marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ase = vro.getAnAccess().getEnclosingStmt().(ExprStmt).getExpr() and | ||
( | ||
e0 = ase.getRValue().(NewOrNewArrayExpr) or | ||
e0 = ase.getRValue().(NewOrNewArrayExpr).getEnclosingFunction().getACallToThisFunction() | ||
) and | ||
vro = ase.getLValue().(VariableAccess).getTarget() | ||
) | ||
or | ||
// `e0` is a `new` expression (or equivalent function call) assigned to the array element `vro` | ||
exists(AssignExpr ase | | ||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ase = vro.getAnAccess().(Qualifier).getEnclosingStmt().(ExprStmt).getExpr() and | ||
( | ||
e0 = ase.getRValue().(NewOrNewArrayExpr) or | ||
e0 = ase.getRValue().(NewOrNewArrayExpr).getEnclosingFunction().getACallToThisFunction() | ||
) and | ||
not ase.getLValue() instanceof VariableAccess and | ||
vro = ase.getLValue().getAPredecessor().(VariableAccess).getTarget() | ||
) | ||
) and | ||
// `e1` is a `new` expression (or equivalent function call) assigned to `vr` | ||
exists(AssignExpr ase | | ||
ihsinme marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ase = vr.getAnAccess().getEnclosingStmt().(ExprStmt).getExpr() and | ||
( | ||
e1 = ase.getRValue().(NewOrNewArrayExpr) or | ||
e1 = ase.getRValue().(NewOrNewArrayExpr).getEnclosingFunction().getACallToThisFunction() | ||
) and | ||
vr = ase.getLValue().(VariableAccess).getTarget() | ||
) and | ||
e0.getASuccessor*() = e1 and | ||
e0.getEnclosingStmt().getParentStmt*() = cb.getTryStmt().getStmt() and | ||
e1.getEnclosingStmt().getParentStmt*() = cb.getTryStmt().getStmt() and | ||
// `e2` is a `throw` (or a function call that may throw) that occurs in the `try` block before `e0` | ||
exists(Expr e2, ThrowExpr th | | ||
ihsinme marked this conversation as resolved.
Show resolved
Hide resolved
|
||
( | ||
e2 = th or | ||
e2 = th.getEnclosingFunction().getACallToThisFunction() | ||
) and | ||
e2.getEnclosingStmt().getParentStmt*() = cb.getTryStmt().getStmt() and | ||
e2.getASuccessor+() = e0 | ||
) | ||
) and | ||
// We exclude checking the value of a variable or its parent in the catch block. | ||
not exists(IfStmt ifst | | ||
ifst.getEnclosingStmt().getParentStmt*() = cb.getAStmt() and | ||
( | ||
ifst.getCondition().getAChild*().(VariableAccess).getTarget() = vr or | ||
ifst.getCondition().getAChild*().(VariableAccess).getTarget() = vro | ||
) | ||
) | ||
} | ||
|
||
/** Holds if `vro` may be released in the `catch`. */ | ||
pragma[inline] | ||
predicate newThrowDelete(CatchAnyBlock cb, Variable vro) { | ||
exists(Expr e0, AssignExpr ase, NewOrNewArrayExpr nae | | ||
ase = vro.getAnAccess().getEnclosingStmt().(ExprStmt).getExpr() and | ||
nae = ase.getRValue() and | ||
not nae.getAChild*().toString() = "nothrow" and | ||
( | ||
e0 = nae or | ||
e0 = nae.getEnclosingFunction().getACallToThisFunction() | ||
) and | ||
vro = ase.getLValue().(VariableAccess).getTarget() and | ||
e0.getEnclosingStmt().getParentStmt*() = cb.getTryStmt().getStmt() and | ||
not exists(AssignExpr ase1 | | ||
vro = ase1.getLValue().(VariableAccess).getTarget() and | ||
ase1.getRValue().getValue() = "0" and | ||
ase1.getASuccessor*() = e0 | ||
) | ||
) and | ||
not exists(Initializer it | | ||
vro.getInitializer() = it and | ||
it.getExpr().getValue() = "0" | ||
) and | ||
not exists(ConstructorFieldInit ci | vro = ci.getTarget()) | ||
} | ||
|
||
from CatchAnyBlock cb, string msg | ||
where | ||
exists(Variable vr, Variable vro, Expr exp | | ||
exp.getEnclosingStmt().getParentStmt*() = cb and | ||
exists(VariableAccess va | | ||
( | ||
( | ||
va = exp.(DeleteArrayExpr).getExpr().getAPredecessor+().(Qualifier) or | ||
va = exp.(DeleteArrayExpr).getExpr().getAPredecessor+() | ||
) and | ||
vr = exp.(DeleteArrayExpr).getExpr().(VariableAccess).getTarget() | ||
or | ||
( | ||
va = exp.(DeleteExpr).getExpr().getAPredecessor+().(Qualifier) or | ||
va = exp.(DeleteExpr).getExpr().getAPredecessor+() | ||
) and | ||
vr = exp.(DeleteExpr).getExpr().(VariableAccess).getTarget() | ||
) and | ||
va.getEnclosingStmt() = exp.getEnclosingStmt() and | ||
vro = va.getTarget() and | ||
vr != vro | ||
) and | ||
pointerDereference(cb, vr, vro) and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding of what's going on here is that we have a What is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are absolutely right if we are talking about deletion. for (size_t i = 0; i < _data->tileBuffers.size(); i++)
{
delete [] _data->tileBuffers[i]->buffer;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I understand. The |
||
msg = | ||
"it is possible to dereference a pointer when accessing a " + vr.getName() + | ||
", since it is possible to throw an exception before the memory for the " + vro.getName() + | ||
" is allocated" | ||
) | ||
or | ||
exists(Expr exp, Variable vr | | ||
( | ||
exp.(DeleteExpr).getEnclosingStmt().getParentStmt*() = cb and | ||
vr = exp.(DeleteExpr).getExpr().(VariableAccess).getTarget() | ||
or | ||
exp.(DeleteArrayExpr).getEnclosingStmt().getParentStmt*() = cb and | ||
vr = exp.(DeleteArrayExpr).getExpr().(VariableAccess).getTarget() | ||
) and | ||
doubleCallDelete(_, cb, vr) and | ||
msg = | ||
"This allocation may have been released in the try block or a previous catch block." + | ||
vr.getName() | ||
) | ||
or | ||
exists(Variable vro, Expr exp | | ||
exp.getEnclosingStmt().getParentStmt*() = cb and | ||
exists(VariableAccess va | | ||
( | ||
va = exp.(DeleteArrayExpr).getExpr() or | ||
va = exp.(DeleteExpr).getExpr() | ||
) and | ||
va.getEnclosingStmt() = exp.getEnclosingStmt() and | ||
vro = va.getTarget() | ||
) and | ||
newThrowDelete(cb, vro) and | ||
msg = | ||
"If the allocation in the try block fails, then an unallocated pointer " + vro.getName() + | ||
" will be freed in the catch block." | ||
) | ||
select cb, msg |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
| test.cpp:63:3:71:3 | { ... } | If the allocation in the try block fails, then an unallocated pointer bufMyData will be freed in the catch block. | | ||
| test.cpp:63:3:71:3 | { ... } | If the allocation in the try block fails, then an unallocated pointer buffer will be freed in the catch block. | | ||
| test.cpp:63:3:71:3 | { ... } | it is possible to dereference a pointer when accessing a buffer, since it is possible to throw an exception before the memory for the bufMyData is allocated | | ||
| test.cpp:91:3:100:3 | { ... } | If the allocation in the try block fails, then an unallocated pointer buffer will be freed in the catch block. | | ||
| test.cpp:120:3:128:3 | { ... } | If the allocation in the try block fails, then an unallocated pointer buffer will be freed in the catch block. | | ||
| test.cpp:143:3:151:3 | { ... } | If the allocation in the try block fails, then an unallocated pointer buffer will be freed in the catch block. | | ||
| test.cpp:181:3:183:3 | { ... } | This allocation may have been released in the try block or a previous catch block.valData | | ||
| test.cpp:219:3:221:3 | { ... } | This allocation may have been released in the try block or a previous catch block.valData | |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
experimental/Security/CWE/CWE-476/DangerousUseOfExceptionBlocks.ql |
Uh oh!
There was an error while loading. Please reload this page.