Skip to content

Commit 94cb47e

Browse files
AnnaZaksdevincoughlin
authored andcommitted
[analyzer] Fix false positives in Keychain API checker
The checker has several false positives that this patch addresses: - Do not check if the return status has been compared to error (or no error) at the time when leaks are reported since the status symbol might no longer be alive. Instead, pattern match on the assume and stop tracking allocated symbols on error paths. - The checker used to report error when an unknown symbol was freed. This could lead to false positives, let's not repot those. This leads to loss of coverage in double frees. - Do not enforce that we should only call free if we are sure that error was not returned and the pointer is not null. That warning is too noisy and we received several false positive reports about it. (I removed: "Only call free if a valid (non-NULL) buffer was returned") - Use !isDead instead of isLive in leak reporting. Otherwise, we report leaks for objects we loose track of. This change triggered change #1. This also adds checker specific dump to the state. Differential Revision: https://reviews.llvm.org/D28330 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@291866 91177308-0d34-0410-b5e6-96231b3b80d8 (cherry picked from commit 434e02b58246b87259c1e58b1430a0517522a2f8) apple-llvm-split-commit: 45ed842634265cd2ca00f1daebaccb49641941bf apple-llvm-split-dir: clang/
1 parent c900f7f commit 94cb47e

File tree

2 files changed

+134
-100
lines changed

2 files changed

+134
-100
lines changed

clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp

Lines changed: 89 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ using namespace ento;
2828
namespace {
2929
class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>,
3030
check::PostStmt<CallExpr>,
31-
check::DeadSymbols> {
31+
check::DeadSymbols,
32+
eval::Assume> {
3233
mutable std::unique_ptr<BugType> BT;
3334

3435
public:
@@ -57,6 +58,10 @@ class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>,
5758
void checkPreStmt(const CallExpr *S, CheckerContext &C) const;
5859
void checkPostStmt(const CallExpr *S, CheckerContext &C) const;
5960
void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
61+
ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond,
62+
bool Assumption) const;
63+
void printState(raw_ostream &Out, ProgramStateRef State,
64+
const char *NL, const char *Sep) const;
6065

6166
private:
6267
typedef std::pair<SymbolRef, const AllocationState*> AllocationPair;
@@ -106,19 +111,6 @@ class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>,
106111
std::unique_ptr<BugReport> generateAllocatedDataNotReleasedReport(
107112
const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const;
108113

109-
/// Check if RetSym evaluates to an error value in the current state.
110-
bool definitelyReturnedError(SymbolRef RetSym,
111-
ProgramStateRef State,
112-
SValBuilder &Builder,
113-
bool noError = false) const;
114-
115-
/// Check if RetSym evaluates to a NoErr value in the current state.
116-
bool definitelyDidnotReturnError(SymbolRef RetSym,
117-
ProgramStateRef State,
118-
SValBuilder &Builder) const {
119-
return definitelyReturnedError(RetSym, State, Builder, true);
120-
}
121-
122114
/// Mark an AllocationPair interesting for diagnostic reporting.
123115
void markInteresting(BugReport *R, const AllocationPair &AP) const {
124116
R->markInteresting(AP.first);
@@ -221,24 +213,6 @@ static SymbolRef getAsPointeeSymbol(const Expr *Expr,
221213
return nullptr;
222214
}
223215

224-
// When checking for error code, we need to consider the following cases:
225-
// 1) noErr / [0]
226-
// 2) someErr / [1, inf]
227-
// 3) unknown
228-
// If noError, returns true iff (1).
229-
// If !noError, returns true iff (2).
230-
bool MacOSKeychainAPIChecker::definitelyReturnedError(SymbolRef RetSym,
231-
ProgramStateRef State,
232-
SValBuilder &Builder,
233-
bool noError) const {
234-
DefinedOrUnknownSVal NoErrVal = Builder.makeIntVal(NoErr,
235-
Builder.getSymbolManager().getType(RetSym));
236-
DefinedOrUnknownSVal NoErr = Builder.evalEQ(State, NoErrVal,
237-
nonloc::SymbolVal(RetSym));
238-
ProgramStateRef ErrState = State->assume(NoErr, noError);
239-
return ErrState == State;
240-
}
241-
242216
// Report deallocator mismatch. Remove the region from tracking - reporting a
243217
// missing free error after this one is redundant.
244218
void MacOSKeychainAPIChecker::
@@ -289,27 +263,25 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
289263
const Expr *ArgExpr = CE->getArg(paramIdx);
290264
if (SymbolRef V = getAsPointeeSymbol(ArgExpr, C))
291265
if (const AllocationState *AS = State->get<AllocatedData>(V)) {
292-
if (!definitelyReturnedError(AS->Region, State, C.getSValBuilder())) {
293-
// Remove the value from the state. The new symbol will be added for
294-
// tracking when the second allocator is processed in checkPostStmt().
295-
State = State->remove<AllocatedData>(V);
296-
ExplodedNode *N = C.generateNonFatalErrorNode(State);
297-
if (!N)
298-
return;
299-
initBugType();
300-
SmallString<128> sbuf;
301-
llvm::raw_svector_ostream os(sbuf);
302-
unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
303-
os << "Allocated data should be released before another call to "
304-
<< "the allocator: missing a call to '"
305-
<< FunctionsToTrack[DIdx].Name
306-
<< "'.";
307-
auto Report = llvm::make_unique<BugReport>(*BT, os.str(), N);
308-
Report->addVisitor(llvm::make_unique<SecKeychainBugVisitor>(V));
309-
Report->addRange(ArgExpr->getSourceRange());
310-
Report->markInteresting(AS->Region);
311-
C.emitReport(std::move(Report));
312-
}
266+
// Remove the value from the state. The new symbol will be added for
267+
// tracking when the second allocator is processed in checkPostStmt().
268+
State = State->remove<AllocatedData>(V);
269+
ExplodedNode *N = C.generateNonFatalErrorNode(State);
270+
if (!N)
271+
return;
272+
initBugType();
273+
SmallString<128> sbuf;
274+
llvm::raw_svector_ostream os(sbuf);
275+
unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
276+
os << "Allocated data should be released before another call to "
277+
<< "the allocator: missing a call to '"
278+
<< FunctionsToTrack[DIdx].Name
279+
<< "'.";
280+
auto Report = llvm::make_unique<BugReport>(*BT, os.str(), N);
281+
Report->addVisitor(llvm::make_unique<SecKeychainBugVisitor>(V));
282+
Report->addRange(ArgExpr->getSourceRange());
283+
Report->markInteresting(AS->Region);
284+
C.emitReport(std::move(Report));
313285
}
314286
return;
315287
}
@@ -344,13 +316,12 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
344316

345317
// Is the argument to the call being tracked?
346318
const AllocationState *AS = State->get<AllocatedData>(ArgSM);
347-
if (!AS && FunctionsToTrack[idx].Kind != ValidAPI) {
319+
if (!AS)
348320
return;
349-
}
350-
// If trying to free data which has not been allocated yet, report as a bug.
351-
// TODO: We might want a more precise diagnostic for double free
321+
322+
// TODO: We might want to report double free here.
352323
// (that would involve tracking all the freed symbols in the checker state).
353-
if (!AS || RegionArgIsBad) {
324+
if (RegionArgIsBad) {
354325
// It is possible that this is a false positive - the argument might
355326
// have entered as an enclosing function parameter.
356327
if (isEnclosingFunctionParam(ArgExpr))
@@ -418,23 +389,6 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
418389
return;
419390
}
420391

421-
// If the buffer can be null and the return status can be an error,
422-
// report a bad call to free.
423-
if (State->assume(ArgSVal.castAs<DefinedSVal>(), false) &&
424-
!definitelyDidnotReturnError(AS->Region, State, C.getSValBuilder())) {
425-
ExplodedNode *N = C.generateNonFatalErrorNode(State);
426-
if (!N)
427-
return;
428-
initBugType();
429-
auto Report = llvm::make_unique<BugReport>(
430-
*BT, "Only call free if a valid (non-NULL) buffer was returned.", N);
431-
Report->addVisitor(llvm::make_unique<SecKeychainBugVisitor>(ArgSM));
432-
Report->addRange(ArgExpr->getSourceRange());
433-
Report->markInteresting(AS->Region);
434-
C.emitReport(std::move(Report));
435-
return;
436-
}
437-
438392
C.addTransition(State);
439393
}
440394

@@ -540,27 +494,63 @@ MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport(
540494
return Report;
541495
}
542496

497+
/// If the return symbol is assumed to be error, remove the allocated info
498+
/// from consideration.
499+
ProgramStateRef MacOSKeychainAPIChecker::evalAssume(ProgramStateRef State,
500+
SVal Cond,
501+
bool Assumption) const {
502+
AllocatedDataTy AMap = State->get<AllocatedData>();
503+
if (AMap.isEmpty())
504+
return State;
505+
506+
auto *CondBSE = dyn_cast_or_null<BinarySymExpr>(Cond.getAsSymExpr());
507+
if (!CondBSE)
508+
return State;
509+
BinaryOperator::Opcode OpCode = CondBSE->getOpcode();
510+
if (OpCode != BO_EQ && OpCode != BO_NE)
511+
return State;
512+
513+
// Match for a restricted set of patterns for cmparison of error codes.
514+
// Note, the comparisons of type '0 == st' are transformed into SymIntExpr.
515+
SymbolRef ReturnSymbol = nullptr;
516+
if (auto *SIE = dyn_cast<SymIntExpr>(CondBSE)) {
517+
const llvm::APInt &RHS = SIE->getRHS();
518+
bool ErrorIsReturned = (OpCode == BO_EQ && RHS != NoErr) ||
519+
(OpCode == BO_NE && RHS == NoErr);
520+
if (!Assumption)
521+
ErrorIsReturned = !ErrorIsReturned;
522+
if (ErrorIsReturned)
523+
ReturnSymbol = SIE->getLHS();
524+
}
525+
526+
if (ReturnSymbol)
527+
for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) {
528+
if (ReturnSymbol == I->second.Region)
529+
State = State->remove<AllocatedData>(I->first);
530+
}
531+
532+
return State;
533+
}
534+
543535
void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR,
544536
CheckerContext &C) const {
545537
ProgramStateRef State = C.getState();
546-
AllocatedDataTy ASet = State->get<AllocatedData>();
547-
if (ASet.isEmpty())
538+
AllocatedDataTy AMap = State->get<AllocatedData>();
539+
if (AMap.isEmpty())
548540
return;
549541

550542
bool Changed = false;
551543
AllocationPairVec Errors;
552-
for (AllocatedDataTy::iterator I = ASet.begin(), E = ASet.end(); I != E; ++I) {
553-
if (SR.isLive(I->first))
544+
for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) {
545+
if (!SR.isDead(I->first))
554546
continue;
555547

556548
Changed = true;
557549
State = State->remove<AllocatedData>(I->first);
558-
// If the allocated symbol is null or if the allocation call might have
559-
// returned an error, do not report.
550+
// If the allocated symbol is null do not report.
560551
ConstraintManager &CMgr = State->getConstraintManager();
561552
ConditionTruthVal AllocFailed = CMgr.isNull(State, I.getKey());
562-
if (AllocFailed.isConstrainedTrue() ||
563-
definitelyReturnedError(I->second.Region, State, C.getSValBuilder()))
553+
if (AllocFailed.isConstrainedTrue())
564554
continue;
565555
Errors.push_back(std::make_pair(I->first, &I->second));
566556
}
@@ -612,6 +602,22 @@ MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode(
612602
"Data is allocated here.");
613603
}
614604

605+
void MacOSKeychainAPIChecker::printState(raw_ostream &Out,
606+
ProgramStateRef State,
607+
const char *NL,
608+
const char *Sep) const {
609+
610+
AllocatedDataTy AMap = State->get<AllocatedData>();
611+
612+
if (!AMap.isEmpty()) {
613+
Out << Sep << "KeychainAPIChecker :" << NL;
614+
for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) {
615+
I.getKey()->dumpToStream(Out);
616+
}
617+
}
618+
}
619+
620+
615621
void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) {
616622
mgr.registerChecker<MacOSKeychainAPIChecker>();
617623
}

clang/test/Analysis/keychainAPI.m

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
// RUN: %clang_cc1 -analyze -analyzer-checker=osx.SecKeychainAPI %s -verify
1+
// RUN: %clang_cc1 -analyze -analyzer-checker=osx.SecKeychainAPI -fblocks %s -verify
2+
3+
#include "Inputs/system-header-simulator-objc.h"
24

35
// Fake typedefs.
46
typedef unsigned int OSStatus;
57
typedef unsigned int SecKeychainAttributeList;
68
typedef unsigned int SecKeychainItemRef;
79
typedef unsigned int SecItemClass;
810
typedef unsigned int UInt32;
9-
typedef unsigned int CFTypeRef;
10-
typedef unsigned int UInt16;
1111
typedef unsigned int SecProtocolType;
1212
typedef unsigned int SecAuthenticationType;
1313
typedef unsigned int SecKeychainAttributeInfo;
@@ -77,7 +77,7 @@ void errRetVal() {
7777
void *outData;
7878
st = SecKeychainItemCopyContent(2, ptr, ptr, &length, &outData);
7979
if (st == GenericError)
80-
SecKeychainItemFreeContent(ptr, outData); // expected-warning{{Only call free if a valid (non-NULL) buffer was returned}}
80+
SecKeychainItemFreeContent(ptr, outData);
8181
} // expected-warning{{Allocated data is not released: missing a call to 'SecKeychainItemFreeContent'}}
8282

8383
// If null is passed in, the data is not allocated, so no need for the matching free.
@@ -101,14 +101,6 @@ void doubleAlloc() {
101101
SecKeychainItemFreeContent(ptr, outData);
102102
}
103103

104-
void fooOnlyFree() {
105-
unsigned int *ptr = 0;
106-
OSStatus st = 0;
107-
UInt32 length;
108-
void *outData = &length;
109-
SecKeychainItemFreeContent(ptr, outData);// expected-warning{{Trying to free data which has not been allocated}}
110-
}
111-
112104
// Do not warn if undefined value is passed to a function.
113105
void fooOnlyFreeUndef() {
114106
unsigned int *ptr = 0;
@@ -220,11 +212,27 @@ int foo(CFTypeRef keychainOrArray, SecProtocolType protocol,
220212
if (st == noErr)
221213
SecKeychainItemFreeContent(ptr, outData[3]);
222214
}
223-
if (length) { // expected-warning{{Allocated data is not released: missing a call to 'SecKeychainItemFreeContent'}}
215+
if (length) { // TODO: We do not report a warning here since the symbol is no longer live, but it's not marked as dead.
224216
length++;
225217
}
226218
return 0;
227-
}// no-warning
219+
}
220+
221+
int testErrorCodeAsLHS(CFTypeRef keychainOrArray, SecProtocolType protocol,
222+
SecAuthenticationType authenticationType, SecKeychainItemRef *itemRef) {
223+
unsigned int *ptr = 0;
224+
OSStatus st = 0;
225+
UInt32 length;
226+
void *outData;
227+
st = SecKeychainFindInternetPassword(keychainOrArray,
228+
16, "server", 16, "domain", 16, "account",
229+
16, "path", 222, protocol, authenticationType,
230+
&length, &outData, itemRef);
231+
if (noErr == st)
232+
SecKeychainItemFreeContent(ptr, outData);
233+
234+
return 0;
235+
}
228236

229237
void free(void *ptr);
230238
void deallocateWithFree() {
@@ -251,7 +259,6 @@ void deallocateWithFree() {
251259
extern const CFAllocatorRef kCFAllocatorNull;
252260
extern const CFAllocatorRef kCFAllocatorUseContext;
253261
CFStringRef CFStringCreateWithBytesNoCopy(CFAllocatorRef alloc, const uint8_t *bytes, CFIndex numBytes, CFStringEncoding encoding, Boolean externalFormat, CFAllocatorRef contentsDeallocator);
254-
extern void CFRelease(CFStringRef cf);
255262

256263
void DellocWithCFStringCreate1(CFAllocatorRef alloc) {
257264
unsigned int *ptr = 0;
@@ -333,11 +340,11 @@ void radar10508828() {
333340
SecKeychainItemFreeContent(0, pwdBytes);
334341
}
335342

336-
void radar10508828_2() {
343+
void radar10508828_20092614() {
337344
UInt32 pwdLen = 0;
338345
void* pwdBytes = 0;
339346
OSStatus rc = SecKeychainFindGenericPassword(0, 3, "foo", 3, "bar", &pwdLen, &pwdBytes, 0);
340-
SecKeychainItemFreeContent(0, pwdBytes); // expected-warning {{Only call free if a valid (non-NULL) buffer was returned}}
347+
SecKeychainItemFreeContent(0, pwdBytes);
341348
}
342349

343350
//Example from bug 10797.
@@ -426,3 +433,24 @@ void allocAndFree3(void *attrList) {
426433
SecKeychainItemFreeContent(attrList, outData);
427434
}
428435

436+
typedef struct AuthorizationValue {
437+
int length;
438+
void *data;
439+
} AuthorizationValue;
440+
typedef struct AuthorizationCallback {
441+
OSStatus (*SetContextVal)(AuthorizationValue *inValue);
442+
} AuthorizationCallback;
443+
static AuthorizationCallback cb;
444+
int radar_19196494() {
445+
@autoreleasepool {
446+
AuthorizationValue login_password = {};
447+
UInt32 passwordLength;
448+
void *passwordData = 0;
449+
OSStatus err = SecKeychainFindGenericPassword(0, 0, "", 0, "", (UInt32 *)&login_password.length, (void**)&login_password.data, 0);
450+
cb.SetContextVal(&login_password);
451+
if (err == noErr) {
452+
SecKeychainItemFreeContent(0, login_password.data);
453+
}
454+
}
455+
return 0;
456+
}

0 commit comments

Comments
 (0)