//=== ErrnoModeling.cpp -----------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This defines a checker `ErrnoModeling`, which is used to make the system // value 'errno' available to other checkers. // The 'errno' value is stored at a special memory region that is accessible // through the `errno_modeling` namespace. The memory region is either the // region of `errno` itself if it is a variable, otherwise an artifically // created region (in the system memory space). If `errno` is defined by using // a function which returns the address of it (this is always the case if it is // not a variable) this function is recognized and evaluated. In this way // `errno` becomes visible to the analysis and checkers can change its value. // //===----------------------------------------------------------------------===// #include "ErrnoModeling.h" #include "clang/AST/ParentMapContext.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/FormatVariadic.h" #include using namespace clang; using namespace ento; namespace { // Name of the "errno" variable. // FIXME: Is there a system where it is not called "errno" but is a variable? const char *ErrnoVarName = "errno"; // Names of functions that return a location of the "errno" value. // FIXME: Are there other similar function names? const char *ErrnoLocationFuncNames[] = {"__errno_location", "___errno", "__errno", "_errno", "__error"}; class ErrnoModeling : public Checker, check::BeginFunction, check::LiveSymbols, eval::Call> { public: void checkASTDecl(const TranslationUnitDecl *D, AnalysisManager &Mgr, BugReporter &BR) const; void checkBeginFunction(CheckerContext &C) const; void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; bool evalCall(const CallEvent &Call, CheckerContext &C) const; // The declaration of an "errno" variable or "errno location" function. mutable const Decl *ErrnoDecl = nullptr; private: // FIXME: Names from `ErrnoLocationFuncNames` are used to build this set. CallDescriptionSet ErrnoLocationCalls{{{"__errno_location"}, 0, 0}, {{"___errno"}, 0, 0}, {{"__errno"}, 0, 0}, {{"_errno"}, 0, 0}, {{"__error"}, 0, 0}}; }; } // namespace /// Store a MemRegion that contains the 'errno' integer value. /// The value is null if the 'errno' value was not recognized in the AST. REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrnoRegion, const MemRegion *) REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrnoState, errno_modeling::ErrnoCheckState) /// Search for a variable called "errno" in the AST. /// Return nullptr if not found. static const VarDecl *getErrnoVar(ASTContext &ACtx) { IdentifierInfo &II = ACtx.Idents.get(ErrnoVarName); auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II); auto Found = llvm::find_if(LookupRes, [&ACtx](const Decl *D) { if (auto *VD = dyn_cast(D)) return ACtx.getSourceManager().isInSystemHeader(VD->getLocation()) && VD->hasExternalStorage() && VD->getType().getCanonicalType() == ACtx.IntTy; return false; }); if (Found == LookupRes.end()) return nullptr; return cast(*Found); } /// Search for a function with a specific name that is used to return a pointer /// to "errno". /// Return nullptr if no such function was found. static const FunctionDecl *getErrnoFunc(ASTContext &ACtx) { SmallVector LookupRes; for (StringRef ErrnoName : ErrnoLocationFuncNames) { IdentifierInfo &II = ACtx.Idents.get(ErrnoName); llvm::append_range(LookupRes, ACtx.getTranslationUnitDecl()->lookup(&II)); } auto Found = llvm::find_if(LookupRes, [&ACtx](const Decl *D) { if (auto *FD = dyn_cast(D)) return ACtx.getSourceManager().isInSystemHeader(FD->getLocation()) && FD->isExternC() && FD->getNumParams() == 0 && FD->getReturnType().getCanonicalType() == ACtx.getPointerType(ACtx.IntTy); return false; }); if (Found == LookupRes.end()) return nullptr; return cast(*Found); } void ErrnoModeling::checkASTDecl(const TranslationUnitDecl *D, AnalysisManager &Mgr, BugReporter &BR) const { // Try to find an usable `errno` value. // It can be an external variable called "errno" or a function that returns a // pointer to the "errno" value. This function can have different names. // The actual case is dependent on the C library implementation, we // can only search for a match in one of these variations. // We assume that exactly one of these cases might be true. ErrnoDecl = getErrnoVar(Mgr.getASTContext()); if (!ErrnoDecl) ErrnoDecl = getErrnoFunc(Mgr.getASTContext()); } void ErrnoModeling::checkBeginFunction(CheckerContext &C) const { if (!C.inTopFrame()) return; ASTContext &ACtx = C.getASTContext(); ProgramStateRef State = C.getState(); if (const auto *ErrnoVar = dyn_cast_or_null(ErrnoDecl)) { // There is an external 'errno' variable. // Use its memory region. // The memory region for an 'errno'-like variable is allocated in system // space by MemRegionManager. const MemRegion *ErrnoR = State->getRegion(ErrnoVar, C.getLocationContext()); assert(ErrnoR && "Memory region should exist for the 'errno' variable."); State = State->set(ErrnoR); State = errno_modeling::setErrnoValue(State, C, 0, errno_modeling::Irrelevant); C.addTransition(State); } else if (ErrnoDecl) { assert(isa(ErrnoDecl) && "Invalid errno location function."); // There is a function that returns the location of 'errno'. // We must create a memory region for it in system space. // Currently a symbolic region is used with an artifical symbol. // FIXME: It is better to have a custom (new) kind of MemRegion for such // cases. SValBuilder &SVB = C.getSValBuilder(); MemRegionManager &RMgr = C.getStateManager().getRegionManager(); const MemSpaceRegion *GlobalSystemSpace = RMgr.getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind); // Create an artifical symbol for the region. // It is not possible to associate a statement or expression in this case. const SymbolConjured *Sym = SVB.conjureSymbol( nullptr, C.getLocationContext(), ACtx.getLValueReferenceType(ACtx.IntTy), C.blockCount(), &ErrnoDecl); // The symbolic region is untyped, create a typed sub-region in it. // The ElementRegion is used to make the errno region a typed region. const MemRegion *ErrnoR = RMgr.getElementRegion( ACtx.IntTy, SVB.makeZeroArrayIndex(), RMgr.getSymbolicRegion(Sym, GlobalSystemSpace), C.getASTContext()); State = State->set(ErrnoR); State = errno_modeling::setErrnoValue(State, C, 0, errno_modeling::Irrelevant); C.addTransition(State); } } bool ErrnoModeling::evalCall(const CallEvent &Call, CheckerContext &C) const { // Return location of "errno" at a call to an "errno address returning" // function. if (ErrnoLocationCalls.contains(Call)) { ProgramStateRef State = C.getState(); const MemRegion *ErrnoR = State->get(); if (!ErrnoR) return false; State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), loc::MemRegionVal{ErrnoR}); C.addTransition(State); return true; } return false; } void ErrnoModeling::checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const { // The special errno region should never garbage collected. if (const auto *ErrnoR = State->get()) SR.markLive(ErrnoR); } namespace clang { namespace ento { namespace errno_modeling { std::optional getErrnoValue(ProgramStateRef State) { const MemRegion *ErrnoR = State->get(); if (!ErrnoR) return {}; QualType IntTy = State->getAnalysisManager().getASTContext().IntTy; return State->getSVal(ErrnoR, IntTy); } ProgramStateRef setErrnoValue(ProgramStateRef State, const LocationContext *LCtx, SVal Value, ErrnoCheckState EState) { const MemRegion *ErrnoR = State->get(); if (!ErrnoR) return State; // First set the errno value, the old state is still available at 'checkBind' // or 'checkLocation' for errno value. State = State->bindLoc(loc::MemRegionVal{ErrnoR}, Value, LCtx); return State->set(EState); } ProgramStateRef setErrnoValue(ProgramStateRef State, CheckerContext &C, uint64_t Value, ErrnoCheckState EState) { const MemRegion *ErrnoR = State->get(); if (!ErrnoR) return State; State = State->bindLoc( loc::MemRegionVal{ErrnoR}, C.getSValBuilder().makeIntVal(Value, C.getASTContext().IntTy), C.getLocationContext()); return State->set(EState); } std::optional getErrnoLoc(ProgramStateRef State) { const MemRegion *ErrnoR = State->get(); if (!ErrnoR) return {}; return loc::MemRegionVal{ErrnoR}; } ErrnoCheckState getErrnoState(ProgramStateRef State) { return State->get(); } ProgramStateRef setErrnoState(ProgramStateRef State, ErrnoCheckState EState) { return State->set(EState); } ProgramStateRef clearErrnoState(ProgramStateRef State) { return setErrnoState(State, Irrelevant); } bool isErrno(const Decl *D) { if (const auto *VD = dyn_cast_or_null(D)) if (const IdentifierInfo *II = VD->getIdentifier()) return II->getName() == ErrnoVarName; if (const auto *FD = dyn_cast_or_null(D)) if (const IdentifierInfo *II = FD->getIdentifier()) return llvm::is_contained(ErrnoLocationFuncNames, II->getName()); return false; } const NoteTag *getErrnoNoteTag(CheckerContext &C, const std::string &Message) { return C.getNoteTag([Message](PathSensitiveBugReport &BR) -> std::string { const MemRegion *ErrnoR = BR.getErrorNode()->getState()->get(); if (ErrnoR && BR.isInteresting(ErrnoR)) { BR.markNotInteresting(ErrnoR); return Message; } return ""; }); } ProgramStateRef setErrnoForStdSuccess(ProgramStateRef State, CheckerContext &C) { return setErrnoState(State, MustNotBeChecked); } ProgramStateRef setErrnoForStdFailure(ProgramStateRef State, CheckerContext &C, NonLoc ErrnoSym) { SValBuilder &SVB = C.getSValBuilder(); NonLoc ZeroVal = SVB.makeZeroVal(C.getASTContext().IntTy).castAs(); DefinedOrUnknownSVal Cond = SVB.evalBinOp(State, BO_NE, ErrnoSym, ZeroVal, SVB.getConditionType()) .castAs(); State = State->assume(Cond, true); if (!State) return nullptr; return setErrnoValue(State, C.getLocationContext(), ErrnoSym, Irrelevant); } ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State, CheckerContext &C, const Expr *InvalE) { const MemRegion *ErrnoR = State->get(); if (!ErrnoR) return State; State = State->invalidateRegions(ErrnoR, InvalE, C.blockCount(), C.getLocationContext(), false); if (!State) return nullptr; return setErrnoState(State, MustBeChecked); } } // namespace errno_modeling } // namespace ento } // namespace clang void ento::registerErrnoModeling(CheckerManager &mgr) { mgr.registerChecker(); } bool ento::shouldRegisterErrnoModeling(const CheckerManager &mgr) { return true; }