| 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-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file defines InvalidPtrChecker which finds usages of possibly |
| 10 | // invalidated pointer. |
| 11 | // CERT SEI Rules ENV31-C and ENV34-C |
| 12 | // For more information see: |
| 13 | // https://wiki.sei.cmu.edu/confluence/x/8tYxBQ |
| 14 | // https://wiki.sei.cmu.edu/confluence/x/5NUxBQ |
| 15 | //===----------------------------------------------------------------------===// |
| 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 | |
| 25 | using namespace clang; |
| 26 | using namespace ento; |
| 27 | |
| 28 | namespace { |
| 29 | |
| 30 | class InvalidPtrChecker |
| 31 | : public Checker<check::Location, check::BeginFunction, check::PostCall> { |
| 32 | private: |
| 33 | // For accurate emission of NoteTags, the BugType of this checker should have |
| 34 | // 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-C |
| 44 | |
| 45 | // If set to true, consider getenv calls as invalidating operations on the |
| 46 | // environment variable buffer. This is implied in the standard, but in |
| 47 | // 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-C |
| 70 | 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 options |
| 82 | // are set in this function. |
| 83 | friend void ento::registerInvalidPtrChecker(CheckerManager &); |
| 84 | |
| 85 | public: |
| 86 | // Obtain the environment pointer from 'main()' (if present). |
| 87 | void checkBeginFunction(CheckerContext &C) const; |
| 88 | |
| 89 | // Handle functions in EnvpInvalidatingFunctions, that invalidate environment |
| 90 | // pointer from 'main()' |
| 91 | // Handle functions in PreviousCallInvalidatingFunctions. |
| 92 | // Also, check if invalidated region is passed to a |
| 93 | // 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 | |
| 100 | private: |
| 101 | const NoteTag *createEnvInvalidationNote(CheckerContext &C, |
| 102 | ProgramStateRef State, |
| 103 | StringRef FunctionName) const; |
| 104 | }; |
| 105 | |
| 106 | } // namespace |
| 107 | |
| 108 | // Set of memory regions that were invalidated |
| 109 | REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions, const MemRegion *) |
| 110 | |
| 111 | // Stores the region of the environment pointer of 'main' (if present). |
| 112 | REGISTER_TRAIT_WITH_PROGRAMSTATE(MainEnvPtrRegion, const MemRegion *) |
| 113 | |
| 114 | // Stores the regions of environments returned by getenv calls. |
| 115 | REGISTER_SET_WITH_PROGRAMSTATE(GetenvEnvPtrRegions, const MemRegion *) |
| 116 | |
| 117 | // Stores key-value pairs, where key is function declaration and value is |
| 118 | // pointer to memory region returned by previous call of this function |
| 119 | REGISTER_MAP_WITH_PROGRAMSTATE(PreviousCallResultMap, const FunctionDecl *, |
| 120 | const MemRegion *) |
| 121 | |
| 122 | const 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(Cb: [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 now |
| 136 | // to avoid extra notes coming from invalidation points higher up the |
| 137 | // bugpath. This ensures that only the last invalidation point is marked |
| 138 | // with a note tag. |
| 139 | llvm::SmallVector<std::string, 2> InvalidLocationNames; |
| 140 | if (BR.isInteresting(R: MainRegion)) { |
| 141 | BR.markNotInteresting(R: MainRegion); |
| 142 | InvalidLocationNames.push_back(Elt: "the environment parameter of 'main'" ); |
| 143 | } |
| 144 | bool InterestingGetenvFound = false; |
| 145 | for (const MemRegion *MR : GetenvRegions) { |
| 146 | if (BR.isInteresting(R: MR)) { |
| 147 | BR.markNotInteresting(R: MR); |
| 148 | if (!InterestingGetenvFound) { |
| 149 | InterestingGetenvFound = true; |
| 150 | InvalidLocationNames.push_back( |
| 151 | Elt: "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 | |
| 165 | void InvalidPtrChecker::EnvpInvalidatingCall(const CallEvent &Call, |
| 166 | CheckerContext &C) const { |
| 167 | // This callevent invalidates all previously generated pointers to the |
| 168 | // environment. |
| 169 | ProgramStateRef State = C.getState(); |
| 170 | if (const MemRegion *MainEnvPtr = State->get<MainEnvPtrRegion>()) |
| 171 | State = State->add<InvalidMemoryRegions>(K: MainEnvPtr); |
| 172 | for (const MemRegion *EnvPtr : State->get<GetenvEnvPtrRegions>()) |
| 173 | State = State->add<InvalidMemoryRegions>(K: EnvPtr); |
| 174 | |
| 175 | StringRef FunctionName = Call.getCalleeIdentifier()->getName(); |
| 176 | const NoteTag *InvalidationNote = |
| 177 | createEnvInvalidationNote(C, State, FunctionName); |
| 178 | |
| 179 | C.addTransition(State, Tag: InvalidationNote); |
| 180 | } |
| 181 | |
| 182 | void 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>(Val: Call.getDecl()); |
| 188 | // Invalidate the region of the previously returned pointer - if there was |
| 189 | // one. |
| 190 | if (const MemRegion *const *Reg = State->get<PreviousCallResultMap>(key: FD)) { |
| 191 | const MemRegion *PrevReg = *Reg; |
| 192 | State = State->add<InvalidMemoryRegions>(K: PrevReg); |
| 193 | Note = C.getNoteTag(Cb: [this, PrevReg, FD](PathSensitiveBugReport &BR, |
| 194 | llvm::raw_ostream &Out) { |
| 195 | if (!BR.isInteresting(R: PrevReg) || &BR.getBugType() != &InvalidPtrBugType) |
| 196 | return; |
| 197 | Out << '\''; |
| 198 | FD->getNameForDiagnostic(OS&: Out, Policy: FD->getASTContext().getLangOpts(), Qualified: true); |
| 199 | Out << "' call may invalidate the result of the previous " << '\''; |
| 200 | FD->getNameForDiagnostic(OS&: Out, Policy: FD->getASTContext().getLangOpts(), Qualified: true); |
| 201 | Out << '\''; |
| 202 | }); |
| 203 | } |
| 204 | |
| 205 | const LocationContext *LCtx = C.getLocationContext(); |
| 206 | const auto *CE = cast<CallExpr>(Val: Call.getOriginExpr()); |
| 207 | |
| 208 | // Function call will return a pointer to the new symbolic region. |
| 209 | DefinedOrUnknownSVal RetVal = |
| 210 | C.getSValBuilder().conjureSymbolVal(call: Call, visitCount: C.blockCount()); |
| 211 | State = State->BindExpr(S: CE, LCtx, V: RetVal); |
| 212 | |
| 213 | const auto *SymRegOfRetVal = |
| 214 | dyn_cast_or_null<SymbolicRegion>(Val: RetVal.getAsRegion()); |
| 215 | if (!SymRegOfRetVal) |
| 216 | return; |
| 217 | |
| 218 | // Remember to this region. |
| 219 | const MemRegion *MR = SymRegOfRetVal->getBaseRegion(); |
| 220 | State = State->set<PreviousCallResultMap>(K: FD, E: MR); |
| 221 | |
| 222 | ExplodedNode *Node = C.addTransition(State, Tag: Note); |
| 223 | const NoteTag *PreviousCallNote = C.getNoteTag( |
| 224 | Cb: [this, MR](PathSensitiveBugReport &BR, llvm::raw_ostream &Out) { |
| 225 | if (!BR.isInteresting(R: MR) || &BR.getBugType() != &InvalidPtrBugType) |
| 226 | return; |
| 227 | Out << "previous function call was here" ; |
| 228 | }); |
| 229 | |
| 230 | C.addTransition(State, Pred: Node, Tag: PreviousCallNote); |
| 231 | } |
| 232 | |
| 233 | // TODO: This seems really ugly. Simplify this. |
| 234 | static const MemRegion *findInvalidatedSymbolicBase(ProgramStateRef State, |
| 235 | const MemRegion *Reg) { |
| 236 | while (Reg) { |
| 237 | if (State->contains<InvalidMemoryRegions>(key: Reg)) |
| 238 | return Reg; |
| 239 | const auto *SymBase = Reg->getSymbolicBase(); |
| 240 | if (!SymBase) |
| 241 | break; |
| 242 | const auto *SRV = dyn_cast<SymbolRegionValue>(Val: SymBase->getSymbol()); |
| 243 | if (!SRV) |
| 244 | break; |
| 245 | Reg = SRV->getRegion(); |
| 246 | if (const auto *VarReg = dyn_cast<VarRegion>(Val: SRV->getRegion())) |
| 247 | Reg = VarReg; |
| 248 | } |
| 249 | return nullptr; |
| 250 | } |
| 251 | |
| 252 | // Handle functions in EnvpInvalidatingFunctions, that invalidate environment |
| 253 | // pointer from 'main()' Also, check if invalidated region is passed to a |
| 254 | // function call as an argument. |
| 255 | void InvalidPtrChecker::checkPostCall(const CallEvent &Call, |
| 256 | CheckerContext &C) const { |
| 257 | |
| 258 | ProgramStateRef State = C.getState(); |
| 259 | |
| 260 | // Model 'getenv' calls |
| 261 | if (GetEnvCall.matches(Call)) { |
| 262 | const MemRegion *Region = Call.getReturnValue().getAsRegion(); |
| 263 | if (Region) { |
| 264 | State = State->add<GetenvEnvPtrRegions>(K: 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 call |
| 274 | if (const auto *Handler = PreviousCallInvalidatingFunctions.lookup(Call)) |
| 275 | (this->**Handler)(Call, C); |
| 276 | |
| 277 | // If pedantic mode is on, regard 'getenv' calls invalidating as well |
| 278 | if (InvalidatingGetEnv && GetEnvCall.matches(Call)) |
| 279 | postPreviousReturnInvalidatingCall(Call, C); |
| 280 | |
| 281 | // Check if one of the arguments of the function call is invalidated |
| 282 | |
| 283 | // If call was inlined, don't report invalidated argument |
| 284 | 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 | Val: Call.getArgSVal(Index: I).getAsRegion())) { |
| 291 | if (const MemRegion *InvalidatedSymbolicBase = |
| 292 | findInvalidatedSymbolicBase(State, Reg: 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(Index: I)->printPretty(OS&: Out, /*Helper=*/nullptr, |
| 301 | Policy: C.getASTContext().getPrintingPolicy()); |
| 302 | Out << "' in a function call" ; |
| 303 | |
| 304 | auto Report = std::make_unique<PathSensitiveBugReport>( |
| 305 | args: InvalidPtrBugType, args: Out.str(), args&: ErrorNode); |
| 306 | Report->markInteresting(R: InvalidatedSymbolicBase); |
| 307 | Report->addRange(R: Call.getArgSourceRange(Index: I)); |
| 308 | C.emitReport(R: std::move(Report)); |
| 309 | } |
| 310 | } |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | // Obtain the environment pointer from 'main()', if present. |
| 315 | void InvalidPtrChecker::checkBeginFunction(CheckerContext &C) const { |
| 316 | if (!C.inTopFrame()) |
| 317 | return; |
| 318 | |
| 319 | const auto *FD = dyn_cast<FunctionDecl>(Val: 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(D: FD->parameters()[2], LC: C.getLocationContext()); |
| 326 | |
| 327 | // Save the memory region pointed by the environment pointer parameter of |
| 328 | // 'main'. |
| 329 | C.addTransition(State: State->set<MainEnvPtrRegion>(EnvpReg)); |
| 330 | } |
| 331 | |
| 332 | // Check if invalidated region is being dereferenced. |
| 333 | void 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, Reg: 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 | args: InvalidPtrBugType, args: "dereferencing an invalid pointer" , args&: ErrorNode); |
| 349 | Report->markInteresting(R: InvalidatedSymbolicBase); |
| 350 | C.emitReport(R: std::move(Report)); |
| 351 | } |
| 352 | |
| 353 | void ento::registerInvalidPtrChecker(CheckerManager &Mgr) { |
| 354 | auto *Checker = Mgr.registerChecker<InvalidPtrChecker>(); |
| 355 | Checker->InvalidatingGetEnv = |
| 356 | Mgr.getAnalyzerOptions().getCheckerBooleanOption(C: Checker, |
| 357 | OptionName: "InvalidatingGetEnv" ); |
| 358 | } |
| 359 | |
| 360 | bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) { |
| 361 | return true; |
| 362 | } |
| 363 | |