Merge pull request #4881 from ihsinme/main

CPP: Add query for CWE-401 memory leak on unsuccessful call to realloc function
This commit is contained in:
Geoffrey White
2021-01-13 10:58:09 +00:00
committed by GitHub
6 changed files with 406 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
// BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
// GOOD: this way we will exclude possible memory leak
unsigned char * tmp;
if (currentSize < newSize)
{
tmp = (unsigned char *)realloc(buffer, newSize);
}
if (tmp == NULL)
{
free(buffer);
}
else
buffer = tmp;

View File

@@ -0,0 +1,38 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Memory leak on failed call to realloc.
The expression <code>mem = realloc (mem, size)</code> is potentially dangerous, if the call fails, we will lose the pointer to the memory block.
An unsuccessful call is possible not only when trying to allocate a large amount of memory, but also when the process memory is strongly segmented.</p>
<p>False positives include code in which immediately after calling the realloc function, the pointer is manipulated without first checking for validity.
In this case, an exception will occur in the program and it will terminate.
But from the point of view of safe coding, these places require the attention of developers.
At this stage, false positives are also possible in situations where the exception handling is quite complicated and occurs outside the base block in which memory is redistributed.</p>
</overview>
<recommendation>
<p>We recommend storing the result in a temporary variable and eliminating memory leak.</p>
</recommendation>
<example>
<p>The following example demonstrates an erroneous and corrected use of the <code>realloc</code> function.</p>
<sample src="MemoryLeakOnFailedCallToRealloc.c" />
</example>
<references>
<li>
CERT C++ Coding Standard:
<a href="https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM51-CPP.+Properly+deallocate+dynamically+allocated+resources">MEM51-CPP. Properly deallocate dynamically allocated resources</a>.
</li>
<li>
CERT C Coding Standard:
<a href="https://wiki.sei.cmu.edu/confluence/display/c/WIN30-C.+Properly+pair+allocation+and+deallocation+functions">WIN30-C. Properly pair allocation and deallocation functions</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,69 @@
/**
* @name Memory leak on failed call to realloc
* @description The expression mem = realloc (mem, size) is potentially dangerous, if the call fails, we will lose the pointer to the memory block.
* We recommend storing the result in a temporary variable and eliminating memory leak.
* @kind problem
* @id cpp/memory-leak-on-failed-call-to-realloc
* @problem.severity warning
* @precision medium
* @tags correctness
* security
* external/cwe/cwe-401
*/
import cpp
/**
* A call to `realloc` of the form `v = realloc(v, size)`, for some variable `v`.
*/
class ReallocCallLeak extends FunctionCall {
Variable v;
ReallocCallLeak() {
exists(AssignExpr ex, VariableAccess va1, VariableAccess va2 |
this.getTarget().hasName("realloc") and
this = ex.getRValue() and
va1 = ex.getLValue() and
va2 = this.getArgument(0) and
va1 = v.getAnAccess() and
va2 = v.getAnAccess()
)
}
predicate isExistsIfWithExitCall() {
exists(IfStmt ifc |
this.getArgument(0) = v.getAnAccess() and
ifc.getCondition().getAChild*() = v.getAnAccess() and
ifc.getEnclosingFunction() = this.getEnclosingFunction() and
ifc.getLocation().getStartLine() >= this.getArgument(0).getLocation().getStartLine() and
exists(FunctionCall fc |
fc.getTarget().hasName("exit") and
fc.getEnclosingFunction() = this.getEnclosingFunction() and
(ifc.getThen().getAChild*() = fc or ifc.getElse().getAChild*() = fc)
)
or
exists(FunctionCall fc, FunctionCall ftmp1, FunctionCall ftmp2 |
ftmp1.getTarget().hasName("exit") and
ftmp2.(ControlFlowNode).getASuccessor*() = ftmp1 and
fc = ftmp2.getEnclosingFunction().getACallToThisFunction() and
fc.getEnclosingFunction() = this.getEnclosingFunction() and
(ifc.getThen().getAChild*() = fc or ifc.getElse().getAChild*() = fc)
)
)
}
predicate isExistsAssertWithArgumentCall() {
exists(FunctionCall fc |
fc.getTarget().hasName("__assert_fail") and
this.getEnclosingFunction() = fc.getEnclosingFunction() and
fc.getLocation().getStartLine() > this.getArgument(0).getLocation().getEndLine() and
fc.getArgument(0).toString().matches("%" + this.getArgument(0).toString() + "%")
)
}
}
from ReallocCallLeak rcl
where
not rcl.isExistsIfWithExitCall() and
not rcl.isExistsAssertWithArgumentCall()
select rcl, "possible loss of original pointer on unsuccessful call realloc"

View File

@@ -0,0 +1,4 @@
| test.c:34:29:34:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
| test.c:63:29:63:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
| test.c:139:29:139:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |
| test.c:186:29:186:35 | call to realloc | possible loss of original pointer on unsuccessful call realloc |

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-401/MemoryLeakOnFailedCallToRealloc.ql

View File

@@ -0,0 +1,274 @@
#define size_t int
#define NULL ((void*)0)
#define assert(x) if (!(x)) __assert_fail(#x,__FILE__,__LINE__)
void __assert_fail(const char *assertion, const char *file, int line) { }
void aFakeFailed_1(int file, int line)
{
}
void aFailed_1(int file, int line)
{
exit(0);
}
void aFailed_2(int file, int line, int ex)
{
if(ex == 1)
exit(0);
else
return;
}
#define F_NUM 1
#define myASSERT_1(expr) \
if (!(expr)) \
aFailed_1(F_NUM, __LINE__)
#define myASSERT_2(expr) \
if (!(expr)) \
aFailed_2(F_NUM, __LINE__, 1)
unsigned char * badResize_0(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
return buffer;
}
unsigned char * goodResize_0(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: this way we will exclude possible memory leak
unsigned char * tmp;
if (currentSize < newSize)
{
tmp = (unsigned char *)realloc(buffer, newSize);
}
if (tmp == NULL)
{
free(buffer);
return NULL;
}
else
buffer = tmp;
return buffer;
}
unsigned char * badResize_1_0(unsigned char * buffer,size_t currentSize,size_t newSize)
{
if(!buffer)
exit(0);
// BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
return buffer;
}
unsigned char * noBadResize_1_0(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
if(!buffer)
exit(0);
return buffer;
}
unsigned char * noBadResize_1_1(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
if(buffer)
return buffer;
else
exit(0);
}
unsigned char * noBadResize_1_2(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
if(buffer = (unsigned char *)realloc(buffer, newSize))
exit(0);
}
return buffer;
}
unsigned char * noBadResize_1_3(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
if(!buffer)
aFailed_1(1, 1);
return buffer;
}
unsigned char * noBadResize_1_4(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
if(buffer)
return buffer;
else
aFailed_1(1, 1);
}
unsigned char * noBadResize_1_5(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
if(buffer = (unsigned char *)realloc(buffer, newSize))
aFailed_1(1, 1);
}
return buffer;
}
unsigned char * badResize_1_1(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
if(!buffer)
aFakeFailed_1(1, 1);
return buffer;
}
unsigned char * noBadResize_1_6(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
if(!buffer)
aFailed_2(1, 1, 1);
return buffer;
}
unsigned char * noBadResize_1_7(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
if(buffer)
return buffer;
else
aFailed_2(1, 1, 1);
}
unsigned char * noBadResize_1_8(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
if(buffer = (unsigned char *)realloc(buffer, newSize))
aFailed_2(1, 1, 1);
}
return buffer;
}
unsigned char * badResize_2_0(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// BAD: on unsuccessful call to realloc, we will lose a pointer to a valid memory block
assert(buffer!=0);
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
return buffer;
}
unsigned char * noBadResize_2_0(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
assert(buffer!=0);
}
return buffer;
}
unsigned char * noBadResize2e_2_1(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
assert(buffer!=0);
return buffer;
}
unsigned char * noBadResize_2_2(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
assert(buffer);
}
return buffer;
}
unsigned char * noBadResize_2_3(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
assert(buffer);
return buffer;
}
unsigned char * noBadResize_2_4(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
myASSERT_1(buffer);
}
return buffer;
}
unsigned char * noBadResize_2_5(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
myASSERT_1(buffer);
return buffer;
}
unsigned char * noBadResize_2_6(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
myASSERT_2(buffer);
}
return buffer;
}
unsigned char * noBadResize_2_7(unsigned char * buffer,size_t currentSize,size_t newSize)
{
// GOOD: program to end
if (currentSize < newSize)
{
buffer = (unsigned char *)realloc(buffer, newSize);
}
myASSERT_2(buffer);
return buffer;
}