-
Notifications
You must be signed in to change notification settings - Fork 1.8k
C/C++ : memory may not be freed on loop #9053
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
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,96 @@ | ||
| import cpp | ||
| import Critical.MemoryFreed | ||
|
|
||
| predicate mayCallFunction(Expr call, Function f) { | ||
| call.(FunctionCall).getTarget() = f or | ||
| call.(VariableCall).getVariable().getAnAssignedValue().getAChild*().(FunctionAccess).getTarget() = | ||
| f | ||
| } | ||
|
|
||
| predicate assignedToFieldOrGlobal(StackVariable v, Expr e) { | ||
| // assigned to anything except a StackVariable | ||
| // (typically a field or global, but for example also *ptr = v) | ||
| e.(Assignment).getRValue() = v.getAnAccess() and | ||
| not e.(Assignment).getLValue().(VariableAccess).getTarget() instanceof StackVariable | ||
| or | ||
| exists(Expr midExpr, Function mid, int arg | | ||
| // indirect assignment | ||
| e.(FunctionCall).getArgument(arg) = v.getAnAccess() and | ||
| mayCallFunction(e, mid) and | ||
| midExpr.getEnclosingFunction() = mid and | ||
| assignedToFieldOrGlobal(mid.getParameter(arg), midExpr) | ||
| ) | ||
| or | ||
| // assigned to a field via constructor field initializer | ||
| e.(ConstructorFieldInit).getExpr() = v.getAnAccess() | ||
| } | ||
|
|
||
| predicate allocCallOrIndirect(Expr e) { | ||
| // direct alloc call | ||
| e.(AllocationExpr).requiresDealloc() and | ||
| // We are only interested in alloc calls that are | ||
| // actually freed somehow, as MemoryNeverFreed | ||
| // will catch those that aren't. | ||
| allocMayBeFreed(e) | ||
| or | ||
| exists(ReturnStmt rtn | | ||
| // indirect alloc call | ||
| mayCallFunction(e, rtn.getEnclosingFunction()) and | ||
| ( | ||
| // return alloc | ||
| allocCallOrIndirect(rtn.getExpr()) | ||
| or | ||
| // return variable assigned with alloc | ||
| exists(Variable v | | ||
| v = rtn.getExpr().(VariableAccess).getTarget() and | ||
| allocCallOrIndirect(v.getAnAssignedValue()) and | ||
| not assignedToFieldOrGlobal(v, _) | ||
| ) | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| predicate verifiedRealloc(FunctionCall reallocCall, Variable v, ControlFlowNode verified) { | ||
| reallocCall.(AllocationExpr).getReallocPtr() = v.getAnAccess() and | ||
| ( | ||
| exists(Variable newV, ControlFlowNode node | | ||
| // a realloc followed by a null check at 'node' (return the non-null | ||
| // successor, i.e. where the realloc is confirmed to have succeeded) | ||
| newV.getAnAssignedValue() = reallocCall and | ||
| node.(AnalysedExpr).getNonNullSuccessor(newV) = verified and | ||
| // note: this case uses naive flow logic (getAnAssignedValue). | ||
| // special case: if the result of the 'realloc' is assigned to the | ||
| // same variable, we don't descriminate properly between the old | ||
| // and the new allocation; better to not consider this a free at | ||
| // all in that case. | ||
| newV != v | ||
| ) | ||
| or | ||
| // a realloc(ptr, 0), which always succeeds and frees | ||
| // (return the realloc itself) | ||
| reallocCall.(AllocationExpr).getReallocPtr().getValue() = "0" and | ||
| verified = reallocCall | ||
| ) | ||
| } | ||
|
|
||
| predicate allocationDefinition(StackVariable v, ControlFlowNode def) { | ||
| exists(Expr expr | exprDefinition(v, def, expr) and allocCallOrIndirect(expr)) | ||
| } | ||
|
|
||
| predicate freeCallOrIndirect(ControlFlowNode n, Variable v) { | ||
| // direct free call | ||
| n.(DeallocationExpr).getFreedExpr() = v.getAnAccess() and | ||
| not exists(n.(AllocationExpr).getReallocPtr()) | ||
| or | ||
| // verified realloc call | ||
| verifiedRealloc(_, v, n) | ||
| or | ||
| exists(FunctionCall midcall, Function mid, int arg | | ||
| // indirect free call | ||
| n.(Call).getArgument(arg) = v.getAnAccess() and | ||
| mayCallFunction(n, mid) and | ||
| midcall.getEnclosingFunction() = mid and | ||
| freeCallOrIndirect(midcall, mid.getParameter(arg)) | ||
| ) | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| for(int i = 0; i < 10; i++) { | ||
| char* free = malloc(0x100); | ||
| char* notfree = malloc(0x100); | ||
| if(i == 5) { | ||
| free(free); | ||
| break; | ||
| } | ||
| free(free); | ||
| free(notfree); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| <!DOCTYPE qhelp PUBLIC | ||
| "-//Semmle//qhelp//EN" | ||
| "qhelp.dtd"> | ||
| <qhelp> | ||
|
|
||
|
|
||
| <overview> | ||
| <p> | ||
| This rule looks for loops that allocate memory, but may break/continue without freeing it. This causes the function to leak memory and may eventually lead to software failure. | ||
| </p> | ||
| </overview> | ||
| <recommendation> | ||
| <p>Ensure that the function frees all dynamically allocated memory it has acquired in all circumstances, unless that memory is returned to the caller.</p> | ||
|
|
||
| </recommendation> | ||
| <example> | ||
| <sample src="MemoryMayNotBeFreedOnLoop.cpp" /> | ||
|
|
||
| <p>In this example, on the iteration with <code>i == 5</code>, the memory allocated into <code>notfree</code> will not be freed. To fix this memory leak, a free call should be added for <code>notfree</code> in the if block.</p> | ||
| </example> | ||
| </qhelp> | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,54 @@ | ||||||
| /** | ||||||
| * @name Memory May Not Be Freed On Loop | ||||||
| * @description A loop may break/continue before freeing memory that was allocated in the loop. A free/delete call should be added before the break/continue. | ||||||
| * @kind problem | ||||||
| * @id cpp/memory-may-not-be-freed-on-loop | ||||||
| * @problem.severity critical | ||||||
| * @tags efficiency | ||||||
| * security | ||||||
| */ | ||||||
|
|
||||||
| import cpp | ||||||
| import semmle.code.cpp.dataflow.DataFlow | ||||||
| import AllocAndFree | ||||||
|
|
||||||
| predicate sameLoop(BlockStmt b, Stmt is) { | ||||||
| if is instanceof Loop then is.getAChild*() = b else sameLoop(b, is.getParent()) | ||||||
| } | ||||||
|
|
||||||
| from StackVariable v, Expr def, JumpStmt b, BlockStmt bs, IfStmt is, ReturnStmt rt | ||||||
| where | ||||||
| allocationDefinition(v, def) and | ||||||
| (b instanceof BreakStmt or b instanceof ContinueStmt) 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. Do you think we'd get good results if we included |
||||||
| def.getParent() instanceof Initializer and | ||||||
| bs.getParent*() instanceof Loop and | ||||||
| exists(int i | i in [0 .. bs.getNumStmt() - 1] | | ||||||
| bs.getChild(i) = def.getEnclosingStmt() and | ||||||
| //Checking if the variable is not copied or used in a function call. If so, that might be normal if the variable is not freed in the loop. | ||||||
| not exists(int y | y in [0 .. bs.getNumStmt() - 1] | | ||||||
| bs.getChild(y) instanceof ExprStmt and | ||||||
| bs.getChild(y).(ExprStmt).getExpr() instanceof AssignExpr and | ||||||
| bs.getChild(y).(ExprStmt).getExpr().(Assignment).getRValue*() = v.getAnAccess() | ||||||
| ) and | ||||||
Yonah125 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| exists(int y | y in [i .. bs.getNumStmt() - 1] | | ||||||
| (bs.getChild(y) = is or bs.getChild(y).(IfStmt).getAChild*() = is) and | ||||||
| sameLoop(bs, is) 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. Is checking |
||||||
| ( | ||||||
| is.getAChild() = b or | ||||||
| is.getThen().getAChild() = b or | ||||||
|
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.
Suggested change
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. Friendly ping @Yonah125 could you do these changes ? |
||||||
| is.getAChild() = rt or | ||||||
| is.getThen().getAChild() = rt | ||||||
|
||||||
| ) and | ||||||
| not is.getCondition().getAChild*() = v.getAnAccess() 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. I think this line is redundant, given the line below. |
||||||
| not is.getParent*().(IfStmt).getCondition().getAChild*() = v.getAnAccess() and | ||||||
| not exists(int p | p in [i .. y] | freeCallOrIndirect(bs.getChild(p).(ExprStmt).getExpr(), v)) and | ||||||
| not exists(int a | a in [0 .. is.getThen().(BlockStmt).getNumStmt()] | | ||||||
| freeCallOrIndirect(is.getThen().getChild(a).(ExprStmt).getExpr(), v) | ||||||
| ) and | ||||||
| exists(int a | a in [y .. bs.getNumStmt() - 1] | | ||||||
| freeCallOrIndirect(bs.getChild(a).(ExprStmt).getExpr(), v) or | ||||||
| freeCallOrIndirect(bs.getChild(a).(IfStmt).getAChild(), v) | ||||||
| ) | ||||||
| ) | ||||||
| ) | ||||||
| select def, "The memory allocated here may not be released at $@.", b, "this point" | ||||||
Uh oh!
There was an error while loading. Please reload this page.