223 lines · cpp
1//===- VforkChecker.cpp -------- Vfork usage checks --------------*- 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 vfork checker which checks for dangerous uses of vfork.10// Vforked process shares memory (including stack) with parent so it's11// range of actions is significantly limited: can't write variables,12// can't call functions not in the allowed list, etc. For more details, see13// http://man7.org/linux/man-pages/man2/vfork.2.html14//15// This checker checks for prohibited constructs in vforked process.16// The state transition diagram:17// PARENT ---(vfork() == 0)--> CHILD18// |19// --(*p = ...)--> bug20// |21// --foo()--> bug22// |23// --return--> bug24//25//===----------------------------------------------------------------------===//26 27#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"28#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"29#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"30#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"31#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"32#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"33#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"34#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"35#include "clang/StaticAnalyzer/Core/Checker.h"36#include "clang/StaticAnalyzer/Core/CheckerManager.h"37#include "clang/AST/ParentMap.h"38#include <optional>39 40using namespace clang;41using namespace ento;42 43namespace {44 45class VforkChecker : public Checker<check::PreCall, check::PostCall,46 check::Bind, check::PreStmt<ReturnStmt>> {47 const BugType BT{this, "Dangerous construct in a vforked process"};48 mutable llvm::SmallPtrSet<const IdentifierInfo *, 10> VforkAllowlist;49 mutable const IdentifierInfo *II_vfork = nullptr;50 51 static bool isChildProcess(const ProgramStateRef State);52 53 bool isVforkCall(const Decl *D, CheckerContext &C) const;54 bool isCallExplicitelyAllowed(const IdentifierInfo *II,55 CheckerContext &C) const;56 57 void reportBug(const char *What, CheckerContext &C,58 const char *Details = nullptr) const;59 60public:61 VforkChecker() = default;62 63 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;64 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;65 void checkBind(SVal L, SVal V, const Stmt *S, bool AtDeclInit,66 CheckerContext &C) const;67 void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;68};69 70} // end anonymous namespace71 72// This trait holds region of variable that is assigned with vfork's73// return value (this is the only region child is allowed to write).74// VFORK_RESULT_INVALID means that we are in parent process.75// VFORK_RESULT_NONE means that vfork's return value hasn't been assigned.76// Other values point to valid regions.77REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion, const void *)78#define VFORK_RESULT_INVALID 079#define VFORK_RESULT_NONE ((void *)(uintptr_t)1)80 81bool VforkChecker::isChildProcess(const ProgramStateRef State) {82 return State->get<VforkResultRegion>() != VFORK_RESULT_INVALID;83}84 85bool VforkChecker::isVforkCall(const Decl *D, CheckerContext &C) const {86 auto FD = dyn_cast_or_null<FunctionDecl>(D);87 if (!FD || !C.isCLibraryFunction(FD))88 return false;89 90 if (!II_vfork) {91 ASTContext &AC = C.getASTContext();92 II_vfork = &AC.Idents.get("vfork");93 }94 95 return FD->getIdentifier() == II_vfork;96}97 98// Returns true iff ok to call function after successful vfork.99bool VforkChecker::isCallExplicitelyAllowed(const IdentifierInfo *II,100 CheckerContext &C) const {101 if (VforkAllowlist.empty()) {102 // According to manpage.103 const char *ids[] = {104 "_Exit",105 "_exit",106 "execl",107 "execle",108 "execlp",109 "execv",110 "execve",111 "execvp",112 "execvpe",113 nullptr114 };115 116 ASTContext &AC = C.getASTContext();117 for (const char **id = ids; *id; ++id)118 VforkAllowlist.insert(&AC.Idents.get(*id));119 }120 121 return VforkAllowlist.count(II);122}123 124void VforkChecker::reportBug(const char *What, CheckerContext &C,125 const char *Details) const {126 if (ExplodedNode *N = C.generateErrorNode(C.getState())) {127 SmallString<256> buf;128 llvm::raw_svector_ostream os(buf);129 130 os << What << " is prohibited after a successful vfork";131 132 if (Details)133 os << "; " << Details;134 135 auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);136 // TODO: mark vfork call in BugReportVisitor137 C.emitReport(std::move(Report));138 }139}140 141// Detect calls to vfork and split execution appropriately.142void VforkChecker::checkPostCall(const CallEvent &Call,143 CheckerContext &C) const {144 // We can't call vfork in child so don't bother145 // (corresponding warning has already been emitted in checkPreCall).146 ProgramStateRef State = C.getState();147 if (isChildProcess(State))148 return;149 150 if (!isVforkCall(Call.getDecl(), C))151 return;152 153 // Get return value of vfork.154 SVal VforkRetVal = Call.getReturnValue();155 std::optional<DefinedOrUnknownSVal> DVal =156 VforkRetVal.getAs<DefinedOrUnknownSVal>();157 if (!DVal)158 return;159 160 // Get assigned variable.161 const ParentMap &PM = C.getLocationContext()->getParentMap();162 const Stmt *P = PM.getParentIgnoreParenCasts(Call.getOriginExpr());163 const VarDecl *LhsDecl;164 std::tie(LhsDecl, std::ignore) = parseAssignment(P);165 166 // Get assigned memory region.167 MemRegionManager &M = C.getStoreManager().getRegionManager();168 const MemRegion *LhsDeclReg =169 LhsDecl170 ? M.getVarRegion(LhsDecl, C.getLocationContext())171 : (const MemRegion *)VFORK_RESULT_NONE;172 173 // Parent branch gets nonzero return value (according to manpage).174 ProgramStateRef ParentState, ChildState;175 std::tie(ParentState, ChildState) = C.getState()->assume(*DVal);176 C.addTransition(ParentState);177 ChildState = ChildState->set<VforkResultRegion>(LhsDeclReg);178 C.addTransition(ChildState);179}180 181// Prohibit calls to functions in child process which are not explicitly182// allowed.183void VforkChecker::checkPreCall(const CallEvent &Call,184 CheckerContext &C) const {185 ProgramStateRef State = C.getState();186 if (isChildProcess(State) &&187 !isCallExplicitelyAllowed(Call.getCalleeIdentifier(), C))188 reportBug("This function call", C);189}190 191// Prohibit writes in child process (except for vfork's lhs).192void VforkChecker::checkBind(SVal L, SVal V, const Stmt *S, bool AtDeclInit,193 CheckerContext &C) const {194 ProgramStateRef State = C.getState();195 if (!isChildProcess(State))196 return;197 198 const MemRegion *VforkLhs =199 static_cast<const MemRegion *>(State->get<VforkResultRegion>());200 const MemRegion *MR = L.getAsRegion();201 202 // Child is allowed to modify only vfork's lhs.203 if (!MR || MR == VforkLhs)204 return;205 206 reportBug("This assignment", C);207}208 209// Prohibit return from function in child process.210void VforkChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const {211 ProgramStateRef State = C.getState();212 if (isChildProcess(State))213 reportBug("Return", C, "call _exit() instead");214}215 216void ento::registerVforkChecker(CheckerManager &mgr) {217 mgr.registerChecker<VforkChecker>();218}219 220bool ento::shouldRegisterVforkChecker(const CheckerManager &mgr) {221 return true;222}223