/* in clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h */
/// Declares a program state trait for type \p Type called \p Name, and /// introduce a type named \c NameTy. /// The macro should not be used inside namespaces. #define REGISTER_TRAIT_WITH_PROGRAMSTATE(Name, Type) \ namespace { \ class Name {}; \ using Name##Ty = Type; \ } \ namespace clang { \ namespace ento { \ template <> \ struct ProgramStateTrait<Name> : public ProgramStatePartialTrait<Name##Ty> { \ static void *GDMIndex() { \ static int Index; \ return &Index; \ } \ }; \ } \ ... ...
/// The macro should not be used inside namespaces, or for traits that must /// be accessible from more than one translation unit. #define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value) \ REGISTER_TRAIT_WITH_PROGRAMSTATE(Name, \ CLANG_ENTO_PROGRAMSTATE_MAP(Key, Value))
这样回过头就能看懂了示例代码了:
1 2 3 4 5
/* in SimpleStreamChecker.cpp */
/// The state of the checker is a map from tracked stream symbols to their /// state. Let's store it in the ProgramState. REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
/* in clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h */ using SymbolRef = const SymExpr *; ... ... /// Symbolic value. These values used to capture symbolic execution of /// the program. classSymExpr : public llvm::FoldingSetNode { virtual voidanchor();
再回到 PDF 中的例子:
1 2 3
SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); ProgramStateRef State = C.getState(); State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
voidSimpleStreamChecker::checkPostCall(const CallEvent &Call, CheckerContext &C)const { if (!Call.isGlobalCFunction("fopen")) return;
if (!OpenFn.matches(Call)) // 这里是什么意思? return;
// Get the symbolic value corresponding to the file handle. SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); if (!FileDesc) return; // 这里难道意思是 fopen 执行失败了,所以没返回值?
// Generate the next transition (an edge in the exploded graph). ProgramStateRef State = C.getState(); State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); C.addTransition(State); // 表示将新的结点添加到图中。 }
voidSimpleStreamChecker::checkPreCall(const CallEvent &Call, CheckerContext &C)const { if (!Call.isGlobalCFunction("fclose")) return;
if (!CloseFn.matches(Call)) return;
// Get the symbolic value corresponding to the file handle. SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); if (!FileDesc) return;
// Check if the stream has already been closed. ProgramStateRef State = C.getState(); const StreamState *SS = State->get<StreamMap>(FileDesc); if (SS && SS->isClosed()) { reportDoubleClose(FileDesc, Call, C); return; }
// Generate the next transition, in which the stream is closed. State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); C.addTransition(State); }
显然,如果在调用 fclose 时 file 已经关闭了,那么就报告多次关闭错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
voidSimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, const CallEvent &Call, CheckerContext &C)const { // We reached a bug, stop exploring the path here by generating a sink. ExplodedNode *ErrNode = C.generateErrorNode(); // If we've already reached this node on another path, return. if (!ErrNode) return;
// Generate the report. auto R = std::make_unique<PathSensitiveBugReport>( *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode); R->addRange(Call.getSourceRange()); R->markInteresting(FileDescSym); C.emitReport(std::move(R)); }
这里生成下沉结点被替换为了生成 ErrorNode。
4. 检查内存泄漏问题
这里引入新的概念:dead symbol,表示的是在该路径下再也不会被引用的变量。当一个变量成为 dead symbol 时,checker 会得到通知。
staticboolisLeaked(SymbolRef Sym, const StreamState &SS, bool IsSymDead, ProgramStateRef State) { if (IsSymDead && SS.isOpened()) { // If a symbol is NULL, assume that fopen failed on this path. // A symbol should only be considered leaked if it is non-null. ConstraintManager &CMgr = State->getConstraintManager(); ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); return !OpenFailed.isConstrainedTrue(); } returnfalse; }
这里如果确认 Symbol Dead,且文件状态是打开的,为什么不能直接确认泄漏呢?因为有可能文件指针 Sym 被约束为 NULL 了。要先确认其有可能不为 NULL,再报告漏洞。
1 2 3 4 5 6 7 8 9 10 11 12 13
voidSimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C, ExplodedNode *ErrNode)const { // Attach bug reports to the leak node. // TODO: Identify the leaked file descriptor. for (SymbolRef LeakedStream : LeakedStreams) { auto R = std::make_unique<PathSensitiveBugReport>( *LeakBugType, "Opened file is never closed; potential resource leak", ErrNode); R->markInteresting(LeakedStream); C.emitReport(std::move(R)); } }
四、测试这个栗子
这个 checker 在 clang 中已经自带了(至少 clang version 16.0.0 是这样的)。先写一个测试代码:
1 2 3 4 5 6 7 8 9 10
/* in /tmp/test_leak.c */ voidwriteCharToLog(char *Data) { FILE *F = fopen("mylog.txt", "w"); if (F != NULL) { if (!Data) return; fclose(F); } return; }