363 lines · cpp
1//== InvalidPtrChecker.cpp ------------------------------------- -*- C++ -*--=//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines InvalidPtrChecker which finds usages of possibly10// invalidated pointer.11// CERT SEI Rules ENV31-C and ENV34-C12// For more information see:13// https://wiki.sei.cmu.edu/confluence/x/8tYxBQ14// https://wiki.sei.cmu.edu/confluence/x/5NUxBQ15//===----------------------------------------------------------------------===//16 17#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"18#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"19#include "clang/StaticAnalyzer/Core/Checker.h"20#include "clang/StaticAnalyzer/Core/CheckerManager.h"21#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"22#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"23#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"24 25using namespace clang;26using namespace ento;27 28namespace {29 30class InvalidPtrChecker31 : public Checker<check::Location, check::BeginFunction, check::PostCall> {32private:33 // For accurate emission of NoteTags, the BugType of this checker should have34 // a unique address.35 BugType InvalidPtrBugType{this, "Use of invalidated pointer",36 categories::MemoryError};37 38 void EnvpInvalidatingCall(const CallEvent &Call, CheckerContext &C) const;39 40 using HandlerFn = void (InvalidPtrChecker::*)(const CallEvent &Call,41 CheckerContext &C) const;42 43 // SEI CERT ENV31-C44 45 // If set to true, consider getenv calls as invalidating operations on the46 // environment variable buffer. This is implied in the standard, but in47 // practice does not cause problems (in the commonly used environments).48 bool InvalidatingGetEnv = false;49 50 // GetEnv can be treated invalidating and non-invalidating as well.51 const CallDescription GetEnvCall{CDM::CLibrary, {"getenv"}, 1};52 53 const CallDescriptionMap<HandlerFn> EnvpInvalidatingFunctions = {54 {{CDM::CLibrary, {"setenv"}, 3},55 &InvalidPtrChecker::EnvpInvalidatingCall},56 {{CDM::CLibrary, {"unsetenv"}, 1},57 &InvalidPtrChecker::EnvpInvalidatingCall},58 {{CDM::CLibrary, {"putenv"}, 1},59 &InvalidPtrChecker::EnvpInvalidatingCall},60 {{CDM::CLibrary, {"_putenv_s"}, 2},61 &InvalidPtrChecker::EnvpInvalidatingCall},62 {{CDM::CLibrary, {"_wputenv_s"}, 2},63 &InvalidPtrChecker::EnvpInvalidatingCall},64 };65 66 void postPreviousReturnInvalidatingCall(const CallEvent &Call,67 CheckerContext &C) const;68 69 // SEI CERT ENV34-C70 const CallDescriptionMap<HandlerFn> PreviousCallInvalidatingFunctions = {71 {{CDM::CLibrary, {"setlocale"}, 2},72 &InvalidPtrChecker::postPreviousReturnInvalidatingCall},73 {{CDM::CLibrary, {"strerror"}, 1},74 &InvalidPtrChecker::postPreviousReturnInvalidatingCall},75 {{CDM::CLibrary, {"localeconv"}, 0},76 &InvalidPtrChecker::postPreviousReturnInvalidatingCall},77 {{CDM::CLibrary, {"asctime"}, 1},78 &InvalidPtrChecker::postPreviousReturnInvalidatingCall},79 };80 81 // The private members of this checker corresponding to commandline options82 // are set in this function.83 friend void ento::registerInvalidPtrChecker(CheckerManager &);84 85public:86 // Obtain the environment pointer from 'main()' (if present).87 void checkBeginFunction(CheckerContext &C) const;88 89 // Handle functions in EnvpInvalidatingFunctions, that invalidate environment90 // pointer from 'main()'91 // Handle functions in PreviousCallInvalidatingFunctions.92 // Also, check if invalidated region is passed to a93 // conservatively evaluated function call as an argument.94 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;95 96 // Check if invalidated region is being dereferenced.97 void checkLocation(SVal l, bool isLoad, const Stmt *S,98 CheckerContext &C) const;99 100private:101 const NoteTag *createEnvInvalidationNote(CheckerContext &C,102 ProgramStateRef State,103 StringRef FunctionName) const;104};105 106} // namespace107 108// Set of memory regions that were invalidated109REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions, const MemRegion *)110 111// Stores the region of the environment pointer of 'main' (if present).112REGISTER_TRAIT_WITH_PROGRAMSTATE(MainEnvPtrRegion, const MemRegion *)113 114// Stores the regions of environments returned by getenv calls.115REGISTER_SET_WITH_PROGRAMSTATE(GetenvEnvPtrRegions, const MemRegion *)116 117// Stores key-value pairs, where key is function declaration and value is118// pointer to memory region returned by previous call of this function119REGISTER_MAP_WITH_PROGRAMSTATE(PreviousCallResultMap, const FunctionDecl *,120 const MemRegion *)121 122const NoteTag *InvalidPtrChecker::createEnvInvalidationNote(123 CheckerContext &C, ProgramStateRef State, StringRef FunctionName) const {124 125 const MemRegion *MainRegion = State->get<MainEnvPtrRegion>();126 const auto GetenvRegions = State->get<GetenvEnvPtrRegions>();127 128 return C.getNoteTag([this, MainRegion, GetenvRegions,129 FunctionName = std::string{FunctionName}](130 PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {131 // Only handle the BugType of this checker.132 if (&BR.getBugType() != &InvalidPtrBugType)133 return;134 135 // Mark all regions that were interesting before as NOT interesting now136 // to avoid extra notes coming from invalidation points higher up the137 // bugpath. This ensures that only the last invalidation point is marked138 // with a note tag.139 llvm::SmallVector<std::string, 2> InvalidLocationNames;140 if (BR.isInteresting(MainRegion)) {141 BR.markNotInteresting(MainRegion);142 InvalidLocationNames.push_back("the environment parameter of 'main'");143 }144 bool InterestingGetenvFound = false;145 for (const MemRegion *MR : GetenvRegions) {146 if (BR.isInteresting(MR)) {147 BR.markNotInteresting(MR);148 if (!InterestingGetenvFound) {149 InterestingGetenvFound = true;150 InvalidLocationNames.push_back(151 "the environment returned by 'getenv'");152 }153 }154 }155 156 // Emit note tag message.157 if (InvalidLocationNames.size() >= 1)158 Out << '\'' << FunctionName << "' call may invalidate "159 << InvalidLocationNames[0];160 if (InvalidLocationNames.size() == 2)161 Out << ", and " << InvalidLocationNames[1];162 });163}164 165void InvalidPtrChecker::EnvpInvalidatingCall(const CallEvent &Call,166 CheckerContext &C) const {167 // This callevent invalidates all previously generated pointers to the168 // environment.169 ProgramStateRef State = C.getState();170 if (const MemRegion *MainEnvPtr = State->get<MainEnvPtrRegion>())171 State = State->add<InvalidMemoryRegions>(MainEnvPtr);172 for (const MemRegion *EnvPtr : State->get<GetenvEnvPtrRegions>())173 State = State->add<InvalidMemoryRegions>(EnvPtr);174 175 StringRef FunctionName = Call.getCalleeIdentifier()->getName();176 const NoteTag *InvalidationNote =177 createEnvInvalidationNote(C, State, FunctionName);178 179 C.addTransition(State, InvalidationNote);180}181 182void InvalidPtrChecker::postPreviousReturnInvalidatingCall(183 const CallEvent &Call, CheckerContext &C) const {184 ProgramStateRef State = C.getState();185 186 const NoteTag *Note = nullptr;187 const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());188 // Invalidate the region of the previously returned pointer - if there was189 // one.190 if (const MemRegion *const *Reg = State->get<PreviousCallResultMap>(FD)) {191 const MemRegion *PrevReg = *Reg;192 State = State->add<InvalidMemoryRegions>(PrevReg);193 Note = C.getNoteTag([this, PrevReg, FD](PathSensitiveBugReport &BR,194 llvm::raw_ostream &Out) {195 if (!BR.isInteresting(PrevReg) || &BR.getBugType() != &InvalidPtrBugType)196 return;197 Out << '\'';198 FD->getNameForDiagnostic(Out, FD->getASTContext().getLangOpts(), true);199 Out << "' call may invalidate the result of the previous " << '\'';200 FD->getNameForDiagnostic(Out, FD->getASTContext().getLangOpts(), true);201 Out << '\'';202 });203 }204 205 const LocationContext *LCtx = C.getLocationContext();206 const auto *CE = cast<CallExpr>(Call.getOriginExpr());207 208 // Function call will return a pointer to the new symbolic region.209 DefinedOrUnknownSVal RetVal =210 C.getSValBuilder().conjureSymbolVal(Call, C.blockCount());211 State = State->BindExpr(CE, LCtx, RetVal);212 213 const auto *SymRegOfRetVal =214 dyn_cast_or_null<SymbolicRegion>(RetVal.getAsRegion());215 if (!SymRegOfRetVal)216 return;217 218 // Remember to this region.219 const MemRegion *MR = SymRegOfRetVal->getBaseRegion();220 State = State->set<PreviousCallResultMap>(FD, MR);221 222 ExplodedNode *Node = C.addTransition(State, Note);223 const NoteTag *PreviousCallNote = C.getNoteTag(224 [this, MR](PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {225 if (!BR.isInteresting(MR) || &BR.getBugType() != &InvalidPtrBugType)226 return;227 Out << "previous function call was here";228 });229 230 C.addTransition(State, Node, PreviousCallNote);231}232 233// TODO: This seems really ugly. Simplify this.234static const MemRegion *findInvalidatedSymbolicBase(ProgramStateRef State,235 const MemRegion *Reg) {236 while (Reg) {237 if (State->contains<InvalidMemoryRegions>(Reg))238 return Reg;239 const auto *SymBase = Reg->getSymbolicBase();240 if (!SymBase)241 break;242 const auto *SRV = dyn_cast<SymbolRegionValue>(SymBase->getSymbol());243 if (!SRV)244 break;245 Reg = SRV->getRegion();246 if (const auto *VarReg = dyn_cast<VarRegion>(SRV->getRegion()))247 Reg = VarReg;248 }249 return nullptr;250}251 252// Handle functions in EnvpInvalidatingFunctions, that invalidate environment253// pointer from 'main()' Also, check if invalidated region is passed to a254// function call as an argument.255void InvalidPtrChecker::checkPostCall(const CallEvent &Call,256 CheckerContext &C) const {257 258 ProgramStateRef State = C.getState();259 260 // Model 'getenv' calls261 if (GetEnvCall.matches(Call)) {262 const MemRegion *Region = Call.getReturnValue().getAsRegion();263 if (Region) {264 State = State->add<GetenvEnvPtrRegions>(Region);265 C.addTransition(State);266 }267 }268 269 // Check if function invalidates 'envp' argument of 'main'270 if (const auto *Handler = EnvpInvalidatingFunctions.lookup(Call))271 (this->**Handler)(Call, C);272 273 // Check if function invalidates the result of previous call274 if (const auto *Handler = PreviousCallInvalidatingFunctions.lookup(Call))275 (this->**Handler)(Call, C);276 277 // If pedantic mode is on, regard 'getenv' calls invalidating as well278 if (InvalidatingGetEnv && GetEnvCall.matches(Call))279 postPreviousReturnInvalidatingCall(Call, C);280 281 // Check if one of the arguments of the function call is invalidated282 283 // If call was inlined, don't report invalidated argument284 if (C.wasInlined)285 return;286 287 for (unsigned I = 0, NumArgs = Call.getNumArgs(); I < NumArgs; ++I) {288 289 if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(290 Call.getArgSVal(I).getAsRegion())) {291 if (const MemRegion *InvalidatedSymbolicBase =292 findInvalidatedSymbolicBase(State, SR)) {293 ExplodedNode *ErrorNode = C.generateNonFatalErrorNode();294 if (!ErrorNode)295 return;296 297 SmallString<256> Msg;298 llvm::raw_svector_ostream Out(Msg);299 Out << "use of invalidated pointer '";300 Call.getArgExpr(I)->printPretty(Out, /*Helper=*/nullptr,301 C.getASTContext().getPrintingPolicy());302 Out << "' in a function call";303 304 auto Report = std::make_unique<PathSensitiveBugReport>(305 InvalidPtrBugType, Out.str(), ErrorNode);306 Report->markInteresting(InvalidatedSymbolicBase);307 Report->addRange(Call.getArgSourceRange(I));308 C.emitReport(std::move(Report));309 }310 }311 }312}313 314// Obtain the environment pointer from 'main()', if present.315void InvalidPtrChecker::checkBeginFunction(CheckerContext &C) const {316 if (!C.inTopFrame())317 return;318 319 const auto *FD = dyn_cast<FunctionDecl>(C.getLocationContext()->getDecl());320 if (!FD || FD->param_size() != 3 || !FD->isMain())321 return;322 323 ProgramStateRef State = C.getState();324 const MemRegion *EnvpReg =325 State->getRegion(FD->parameters()[2], C.getLocationContext());326 327 // Save the memory region pointed by the environment pointer parameter of328 // 'main'.329 C.addTransition(State->set<MainEnvPtrRegion>(EnvpReg));330}331 332// Check if invalidated region is being dereferenced.333void InvalidPtrChecker::checkLocation(SVal Loc, bool isLoad, const Stmt *S,334 CheckerContext &C) const {335 ProgramStateRef State = C.getState();336 337 // Ignore memory operations involving 'non-invalidated' locations.338 const MemRegion *InvalidatedSymbolicBase =339 findInvalidatedSymbolicBase(State, Loc.getAsRegion());340 if (!InvalidatedSymbolicBase)341 return;342 343 ExplodedNode *ErrorNode = C.generateNonFatalErrorNode();344 if (!ErrorNode)345 return;346 347 auto Report = std::make_unique<PathSensitiveBugReport>(348 InvalidPtrBugType, "dereferencing an invalid pointer", ErrorNode);349 Report->markInteresting(InvalidatedSymbolicBase);350 C.emitReport(std::move(Report));351}352 353void ento::registerInvalidPtrChecker(CheckerManager &Mgr) {354 auto *Checker = Mgr.registerChecker<InvalidPtrChecker>();355 Checker->InvalidatingGetEnv =356 Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker,357 "InvalidatingGetEnv");358}359 360bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) {361 return true;362}363