зеркало из https://github.com/microsoft/clang-1.git
[analyzer] Report bugs when freeing memory with offset pointer
The malloc checker will now catch the case when a previously malloc'ed region is freed, but the pointer passed to free does not point to the start of the allocated memory. For example: int *p1 = malloc(sizeof(int)); p1++; free(p1); // warn From the "memory.LeakPtrValChanged enhancement to unix.Malloc" entry in the list of potential checkers. A patch by Branden Archer! git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@174678 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Родитель
233e26acc0
Коммит
118aa750c5
|
@ -129,6 +129,7 @@ class MallocChecker : public Checker<check::DeadSymbols,
|
|||
mutable OwningPtr<BugType> BT_Leak;
|
||||
mutable OwningPtr<BugType> BT_UseFree;
|
||||
mutable OwningPtr<BugType> BT_BadFree;
|
||||
mutable OwningPtr<BugType> BT_OffsetFree;
|
||||
mutable IdentifierInfo *II_malloc, *II_free, *II_realloc, *II_calloc,
|
||||
*II_valloc, *II_reallocf, *II_strndup, *II_strdup;
|
||||
|
||||
|
@ -225,6 +226,7 @@ private:
|
|||
static bool SummarizeValue(raw_ostream &os, SVal V);
|
||||
static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR);
|
||||
void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange range) const;
|
||||
void ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range)const;
|
||||
|
||||
/// Find the location of the allocation for Sym on the path leading to the
|
||||
/// exploded node N.
|
||||
|
@ -710,43 +712,55 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
|
|||
ReportBadFree(C, ArgVal, ArgExpr->getSourceRange());
|
||||
return 0;
|
||||
}
|
||||
|
||||
const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(R);
|
||||
|
||||
const SymbolicRegion *SrBase = dyn_cast<SymbolicRegion>(R->getBaseRegion());
|
||||
// Various cases could lead to non-symbol values here.
|
||||
// For now, ignore them.
|
||||
if (!SR)
|
||||
if (!SrBase)
|
||||
return 0;
|
||||
|
||||
SymbolRef Sym = SR->getSymbol();
|
||||
const RefState *RS = State->get<RegionState>(Sym);
|
||||
SymbolRef SymBase = SrBase->getSymbol();
|
||||
const RefState *RsBase = State->get<RegionState>(SymBase);
|
||||
SymbolRef PreviousRetStatusSymbol = 0;
|
||||
|
||||
// Check double free.
|
||||
if (RS &&
|
||||
(RS->isReleased() || RS->isRelinquished()) &&
|
||||
!didPreviousFreeFail(State, Sym, PreviousRetStatusSymbol)) {
|
||||
if (RsBase &&
|
||||
(RsBase->isReleased() || RsBase->isRelinquished()) &&
|
||||
!didPreviousFreeFail(State, SymBase, PreviousRetStatusSymbol)) {
|
||||
|
||||
if (ExplodedNode *N = C.generateSink()) {
|
||||
if (!BT_DoubleFree)
|
||||
BT_DoubleFree.reset(
|
||||
new BugType("Double free", "Memory Error"));
|
||||
BugReport *R = new BugReport(*BT_DoubleFree,
|
||||
(RS->isReleased() ? "Attempt to free released memory" :
|
||||
"Attempt to free non-owned memory"), N);
|
||||
BugReport *R = new BugReport(*BT_DoubleFree,
|
||||
(RsBase->isReleased() ? "Attempt to free released memory"
|
||||
: "Attempt to free non-owned memory"),
|
||||
N);
|
||||
R->addRange(ArgExpr->getSourceRange());
|
||||
R->markInteresting(Sym);
|
||||
R->markInteresting(SymBase);
|
||||
if (PreviousRetStatusSymbol)
|
||||
R->markInteresting(PreviousRetStatusSymbol);
|
||||
R->addVisitor(new MallocBugVisitor(Sym));
|
||||
R->addVisitor(new MallocBugVisitor(SymBase));
|
||||
C.emitReport(R);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReleasedAllocated = (RS != 0);
|
||||
// Check if the memory location being freed is the actual location
|
||||
// allocated, or an offset.
|
||||
RegionOffset Offset = R->getAsOffset();
|
||||
if (RsBase && RsBase->isAllocated() &&
|
||||
Offset.isValid() &&
|
||||
!Offset.hasSymbolicOffset() &&
|
||||
Offset.getOffset() != 0) {
|
||||
ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange());
|
||||
return 0;
|
||||
}
|
||||
|
||||
ReleasedAllocated = (RsBase != 0);
|
||||
|
||||
// Clean out the info on previous call to free return info.
|
||||
State = State->remove<FreeReturnValue>(Sym);
|
||||
State = State->remove<FreeReturnValue>(SymBase);
|
||||
|
||||
// Keep track of the return value. If it is NULL, we will know that free
|
||||
// failed.
|
||||
|
@ -754,15 +768,17 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
|
|||
SVal RetVal = C.getSVal(ParentExpr);
|
||||
SymbolRef RetStatusSymbol = RetVal.getAsSymbol();
|
||||
if (RetStatusSymbol) {
|
||||
C.getSymbolManager().addSymbolDependency(Sym, RetStatusSymbol);
|
||||
State = State->set<FreeReturnValue>(Sym, RetStatusSymbol);
|
||||
C.getSymbolManager().addSymbolDependency(SymBase, RetStatusSymbol);
|
||||
State = State->set<FreeReturnValue>(SymBase, RetStatusSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
// Normal free.
|
||||
if (Hold)
|
||||
return State->set<RegionState>(Sym, RefState::getRelinquished(ParentExpr));
|
||||
return State->set<RegionState>(Sym, RefState::getReleased(ParentExpr));
|
||||
if (Hold) {
|
||||
return State->set<RegionState>(SymBase,
|
||||
RefState::getRelinquished(ParentExpr));
|
||||
}
|
||||
return State->set<RegionState>(SymBase, RefState::getReleased(ParentExpr));
|
||||
}
|
||||
|
||||
bool MallocChecker::SummarizeValue(raw_ostream &os, SVal V) {
|
||||
|
@ -891,6 +907,41 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
|
|||
}
|
||||
}
|
||||
|
||||
void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal,
|
||||
SourceRange Range) const {
|
||||
ExplodedNode *N = C.generateSink();
|
||||
if (N == NULL)
|
||||
return;
|
||||
|
||||
if (!BT_OffsetFree)
|
||||
BT_OffsetFree.reset(new BugType("Offset free", "Memory Error"));
|
||||
|
||||
SmallString<100> buf;
|
||||
llvm::raw_svector_ostream os(buf);
|
||||
|
||||
const MemRegion *MR = ArgVal.getAsRegion();
|
||||
assert(MR && "Only MemRegion based symbols can have offset free errors");
|
||||
|
||||
RegionOffset Offset = MR->getAsOffset();
|
||||
assert((Offset.isValid() &&
|
||||
!Offset.hasSymbolicOffset() &&
|
||||
Offset.getOffset() != 0) &&
|
||||
"Only symbols with a valid offset can have offset free errors");
|
||||
|
||||
int offsetBytes = Offset.getOffset() / C.getASTContext().getCharWidth();
|
||||
|
||||
os << "Argument to free() is offset by "
|
||||
<< offsetBytes
|
||||
<< " "
|
||||
<< ((abs(offsetBytes) > 1) ? "bytes" : "byte")
|
||||
<< " from the start of memory allocated by malloc()";
|
||||
|
||||
BugReport *R = new BugReport(*BT_OffsetFree, os.str(), N);
|
||||
R->markInteresting(MR->getBaseRegion());
|
||||
R->addRange(Range);
|
||||
C.emitReport(R);
|
||||
}
|
||||
|
||||
ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C,
|
||||
const CallExpr *CE,
|
||||
bool FreesOnFail) const {
|
||||
|
|
|
@ -1090,4 +1090,104 @@ void testPassToSystemHeaderFunctionIndirectlyStruct() {
|
|||
fakeSystemHeaderCall(&ss);
|
||||
} // missing leak
|
||||
|
||||
int *testOffsetAllocate(size_t size) {
|
||||
int *memoryBlock = (int *)malloc(size + sizeof(int));
|
||||
return &memoryBlock[1]; // no-warning
|
||||
}
|
||||
|
||||
void testOffsetDeallocate(int *memoryBlock) {
|
||||
free(&memoryBlock[-1]); // no-warning
|
||||
}
|
||||
|
||||
void testOffsetOfRegionFreed() {
|
||||
__int64_t * array = malloc(sizeof(__int64_t)*2);
|
||||
array += 1;
|
||||
free(&array[0]); // expected-warning{{Argument to free() is offset by 8 bytes from the start of memory allocated by malloc()}}
|
||||
}
|
||||
|
||||
void testOffsetOfRegionFreed2() {
|
||||
__int64_t *p = malloc(sizeof(__int64_t)*2);
|
||||
p += 1;
|
||||
free(p); // expected-warning{{Argument to free() is offset by 8 bytes from the start of memory allocated by malloc()}}
|
||||
}
|
||||
|
||||
void testOffsetOfRegionFreed3() {
|
||||
char *r = malloc(sizeof(char));
|
||||
r = r - 10;
|
||||
free(r); // expected-warning {{Argument to free() is offset by -10 bytes from the start of memory allocated by malloc()}}
|
||||
}
|
||||
|
||||
void testOffsetOfRegionFreedAfterFunctionCall() {
|
||||
int *p = malloc(sizeof(int)*2);
|
||||
p += 1;
|
||||
myfoo(p);
|
||||
free(p); // no-warning
|
||||
}
|
||||
|
||||
void testFixManipulatedPointerBeforeFree() {
|
||||
int * array = malloc(sizeof(int)*2);
|
||||
array += 1;
|
||||
free(&array[-1]); // no-warning
|
||||
}
|
||||
|
||||
void testFixManipulatedPointerBeforeFree2() {
|
||||
char *r = malloc(sizeof(char));
|
||||
r = r + 10;
|
||||
free(r-10); // no-warning
|
||||
}
|
||||
|
||||
void freeOffsetPointerPassedToFunction() {
|
||||
__int64_t *p = malloc(sizeof(__int64_t)*2);
|
||||
p[1] = 0;
|
||||
p += 1;
|
||||
myfooint(*p); // not passing the pointer, only a value pointed by pointer
|
||||
free(p); // expected-warning {{Argument to free() is offset by 8 bytes from the start of memory allocated by malloc()}}
|
||||
}
|
||||
|
||||
int arbitraryInt();
|
||||
void freeUnknownOffsetPointer() {
|
||||
char *r = malloc(sizeof(char));
|
||||
r = r + arbitraryInt(); // unable to reason about what the offset might be
|
||||
free(r); // no-warning
|
||||
}
|
||||
|
||||
void testFreeNonMallocPointerWithNoOffset() {
|
||||
char c;
|
||||
char *r = &c;
|
||||
r = r + 10;
|
||||
free(r-10); // expected-warning {{Argument to free() is the address of the local variable 'c', which is not memory allocated by malloc()}}
|
||||
}
|
||||
|
||||
void testFreeNonMallocPointerWithOffset() {
|
||||
char c;
|
||||
char *r = &c;
|
||||
free(r+1); // expected-warning {{Argument to free() is the address of the local variable 'c', which is not memory allocated by malloc()}}
|
||||
}
|
||||
|
||||
void testOffsetZeroDoubleFree() {
|
||||
int *array = malloc(sizeof(int)*2);
|
||||
int *p = &array[0];
|
||||
free(p);
|
||||
free(&array[0]); // expected-warning{{Attempt to free released memory}}
|
||||
}
|
||||
|
||||
void testOffsetPassedToStrlen() {
|
||||
char * string = malloc(sizeof(char)*10);
|
||||
string += 1;
|
||||
int length = strlen(string); // expected-warning {{Memory is never released; potential leak of memory pointed to by 'string'}}
|
||||
}
|
||||
|
||||
void testOffsetPassedToStrlenThenFree() {
|
||||
char * string = malloc(sizeof(char)*10);
|
||||
string += 1;
|
||||
int length = strlen(string);
|
||||
free(string); // expected-warning {{Argument to free() is offset by 1 byte from the start of memory allocated by malloc()}}
|
||||
}
|
||||
|
||||
void testOffsetPassedAsConst() {
|
||||
char * string = malloc(sizeof(char)*10);
|
||||
string += 1;
|
||||
passConstPtr(string);
|
||||
free(string); // expected-warning {{Argument to free() is offset by 1 byte from the start of memory allocated by malloc()}}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче