| //=== 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 <optional> |
| |
| 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::ASTDecl<TranslationUnitDecl>, 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<VarDecl>(D)) |
| return ACtx.getSourceManager().isInSystemHeader(VD->getLocation()) && |
| VD->hasExternalStorage() && |
| VD->getType().getCanonicalType() == ACtx.IntTy; |
| return false; |
| }); |
| if (Found == LookupRes.end()) |
| return nullptr; |
| |
| return cast<VarDecl>(*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<const Decl *> 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<FunctionDecl>(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<FunctionDecl>(*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<VarDecl>(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<ErrnoRegion>(ErrnoR); |
| State = |
| errno_modeling::setErrnoValue(State, C, 0, errno_modeling::Irrelevant); |
| C.addTransition(State); |
| } else if (ErrnoDecl) { |
| assert(isa<FunctionDecl>(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<ErrnoRegion>(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<ErrnoRegion>(); |
| 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<ErrnoRegion>()) |
| SR.markLive(ErrnoR); |
| } |
| |
| namespace clang { |
| namespace ento { |
| namespace errno_modeling { |
| |
| std::optional<SVal> getErrnoValue(ProgramStateRef State) { |
| const MemRegion *ErrnoR = State->get<ErrnoRegion>(); |
| 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<ErrnoRegion>(); |
| 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<ErrnoState>(EState); |
| } |
| |
| ProgramStateRef setErrnoValue(ProgramStateRef State, CheckerContext &C, |
| uint64_t Value, ErrnoCheckState EState) { |
| const MemRegion *ErrnoR = State->get<ErrnoRegion>(); |
| if (!ErrnoR) |
| return State; |
| State = State->bindLoc( |
| loc::MemRegionVal{ErrnoR}, |
| C.getSValBuilder().makeIntVal(Value, C.getASTContext().IntTy), |
| C.getLocationContext()); |
| return State->set<ErrnoState>(EState); |
| } |
| |
| std::optional<Loc> getErrnoLoc(ProgramStateRef State) { |
| const MemRegion *ErrnoR = State->get<ErrnoRegion>(); |
| if (!ErrnoR) |
| return {}; |
| return loc::MemRegionVal{ErrnoR}; |
| } |
| |
| ErrnoCheckState getErrnoState(ProgramStateRef State) { |
| return State->get<ErrnoState>(); |
| } |
| |
| ProgramStateRef setErrnoState(ProgramStateRef State, ErrnoCheckState EState) { |
| return State->set<ErrnoState>(EState); |
| } |
| |
| ProgramStateRef clearErrnoState(ProgramStateRef State) { |
| return setErrnoState(State, Irrelevant); |
| } |
| |
| bool isErrno(const Decl *D) { |
| if (const auto *VD = dyn_cast_or_null<VarDecl>(D)) |
| if (const IdentifierInfo *II = VD->getIdentifier()) |
| return II->getName() == ErrnoVarName; |
| if (const auto *FD = dyn_cast_or_null<FunctionDecl>(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<ErrnoRegion>(); |
| 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<NonLoc>(); |
| DefinedOrUnknownSVal Cond = |
| SVB.evalBinOp(State, BO_NE, ErrnoSym, ZeroVal, SVB.getConditionType()) |
| .castAs<DefinedOrUnknownSVal>(); |
| 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<ErrnoRegion>(); |
| 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<ErrnoModeling>(); |
| } |
| |
| bool ento::shouldRegisterErrnoModeling(const CheckerManager &mgr) { |
| return true; |
| } |