17330f729Sjoerg //===- VforkChecker.cpp -------- Vfork usage checks --------------*- C++ -*-==//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg // This file defines vfork checker which checks for dangerous uses of vfork.
107330f729Sjoerg // Vforked process shares memory (including stack) with parent so it's
117330f729Sjoerg // range of actions is significantly limited: can't write variables,
127330f729Sjoerg // can't call functions not in whitelist, etc. For more details, see
137330f729Sjoerg // http://man7.org/linux/man-pages/man2/vfork.2.html
147330f729Sjoerg //
157330f729Sjoerg // This checker checks for prohibited constructs in vforked process.
167330f729Sjoerg // The state transition diagram:
177330f729Sjoerg // PARENT ---(vfork() == 0)--> CHILD
187330f729Sjoerg // |
197330f729Sjoerg // --(*p = ...)--> bug
207330f729Sjoerg // |
217330f729Sjoerg // --foo()--> bug
227330f729Sjoerg // |
237330f729Sjoerg // --return--> bug
247330f729Sjoerg //
257330f729Sjoerg //===----------------------------------------------------------------------===//
267330f729Sjoerg
277330f729Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
287330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
297330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
307330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
317330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
327330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
337330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
347330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
357330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
367330f729Sjoerg #include "clang/StaticAnalyzer/Core/CheckerManager.h"
377330f729Sjoerg #include "clang/AST/ParentMap.h"
387330f729Sjoerg
397330f729Sjoerg using namespace clang;
407330f729Sjoerg using namespace ento;
417330f729Sjoerg
427330f729Sjoerg namespace {
437330f729Sjoerg
447330f729Sjoerg class VforkChecker : public Checker<check::PreCall, check::PostCall,
457330f729Sjoerg check::Bind, check::PreStmt<ReturnStmt>> {
467330f729Sjoerg mutable std::unique_ptr<BuiltinBug> BT;
477330f729Sjoerg mutable llvm::SmallSet<const IdentifierInfo *, 10> VforkWhitelist;
487330f729Sjoerg mutable const IdentifierInfo *II_vfork;
497330f729Sjoerg
507330f729Sjoerg static bool isChildProcess(const ProgramStateRef State);
517330f729Sjoerg
527330f729Sjoerg bool isVforkCall(const Decl *D, CheckerContext &C) const;
537330f729Sjoerg bool isCallWhitelisted(const IdentifierInfo *II, CheckerContext &C) const;
547330f729Sjoerg
557330f729Sjoerg void reportBug(const char *What, CheckerContext &C,
567330f729Sjoerg const char *Details = nullptr) const;
577330f729Sjoerg
587330f729Sjoerg public:
VforkChecker()597330f729Sjoerg VforkChecker() : II_vfork(nullptr) {}
607330f729Sjoerg
617330f729Sjoerg void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
627330f729Sjoerg void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
637330f729Sjoerg void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const;
647330f729Sjoerg void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;
657330f729Sjoerg };
667330f729Sjoerg
677330f729Sjoerg } // end anonymous namespace
687330f729Sjoerg
697330f729Sjoerg // This trait holds region of variable that is assigned with vfork's
707330f729Sjoerg // return value (this is the only region child is allowed to write).
717330f729Sjoerg // VFORK_RESULT_INVALID means that we are in parent process.
727330f729Sjoerg // VFORK_RESULT_NONE means that vfork's return value hasn't been assigned.
737330f729Sjoerg // Other values point to valid regions.
REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion,const void *)747330f729Sjoerg REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion, const void *)
757330f729Sjoerg #define VFORK_RESULT_INVALID 0
767330f729Sjoerg #define VFORK_RESULT_NONE ((void *)(uintptr_t)1)
777330f729Sjoerg
787330f729Sjoerg bool VforkChecker::isChildProcess(const ProgramStateRef State) {
797330f729Sjoerg return State->get<VforkResultRegion>() != VFORK_RESULT_INVALID;
807330f729Sjoerg }
817330f729Sjoerg
isVforkCall(const Decl * D,CheckerContext & C) const827330f729Sjoerg bool VforkChecker::isVforkCall(const Decl *D, CheckerContext &C) const {
837330f729Sjoerg auto FD = dyn_cast_or_null<FunctionDecl>(D);
847330f729Sjoerg if (!FD || !C.isCLibraryFunction(FD))
857330f729Sjoerg return false;
867330f729Sjoerg
877330f729Sjoerg if (!II_vfork) {
887330f729Sjoerg ASTContext &AC = C.getASTContext();
897330f729Sjoerg II_vfork = &AC.Idents.get("vfork");
907330f729Sjoerg }
917330f729Sjoerg
927330f729Sjoerg return FD->getIdentifier() == II_vfork;
937330f729Sjoerg }
947330f729Sjoerg
957330f729Sjoerg // Returns true iff ok to call function after successful vfork.
isCallWhitelisted(const IdentifierInfo * II,CheckerContext & C) const967330f729Sjoerg bool VforkChecker::isCallWhitelisted(const IdentifierInfo *II,
977330f729Sjoerg CheckerContext &C) const {
987330f729Sjoerg if (VforkWhitelist.empty()) {
997330f729Sjoerg // According to manpage.
1007330f729Sjoerg const char *ids[] = {
1017330f729Sjoerg "_Exit",
102*e038c9c4Sjoerg "_exit",
1037330f729Sjoerg "execl",
1047330f729Sjoerg "execle",
105*e038c9c4Sjoerg "execlp",
1067330f729Sjoerg "execv",
107*e038c9c4Sjoerg "execve",
1087330f729Sjoerg "execvp",
1097330f729Sjoerg "execvpe",
1107330f729Sjoerg nullptr
1117330f729Sjoerg };
1127330f729Sjoerg
1137330f729Sjoerg ASTContext &AC = C.getASTContext();
1147330f729Sjoerg for (const char **id = ids; *id; ++id)
1157330f729Sjoerg VforkWhitelist.insert(&AC.Idents.get(*id));
1167330f729Sjoerg }
1177330f729Sjoerg
1187330f729Sjoerg return VforkWhitelist.count(II);
1197330f729Sjoerg }
1207330f729Sjoerg
reportBug(const char * What,CheckerContext & C,const char * Details) const1217330f729Sjoerg void VforkChecker::reportBug(const char *What, CheckerContext &C,
1227330f729Sjoerg const char *Details) const {
1237330f729Sjoerg if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
1247330f729Sjoerg if (!BT)
1257330f729Sjoerg BT.reset(new BuiltinBug(this,
1267330f729Sjoerg "Dangerous construct in a vforked process"));
1277330f729Sjoerg
1287330f729Sjoerg SmallString<256> buf;
1297330f729Sjoerg llvm::raw_svector_ostream os(buf);
1307330f729Sjoerg
1317330f729Sjoerg os << What << " is prohibited after a successful vfork";
1327330f729Sjoerg
1337330f729Sjoerg if (Details)
1347330f729Sjoerg os << "; " << Details;
1357330f729Sjoerg
1367330f729Sjoerg auto Report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N);
1377330f729Sjoerg // TODO: mark vfork call in BugReportVisitor
1387330f729Sjoerg C.emitReport(std::move(Report));
1397330f729Sjoerg }
1407330f729Sjoerg }
1417330f729Sjoerg
1427330f729Sjoerg // Detect calls to vfork and split execution appropriately.
checkPostCall(const CallEvent & Call,CheckerContext & C) const1437330f729Sjoerg void VforkChecker::checkPostCall(const CallEvent &Call,
1447330f729Sjoerg CheckerContext &C) const {
1457330f729Sjoerg // We can't call vfork in child so don't bother
1467330f729Sjoerg // (corresponding warning has already been emitted in checkPreCall).
1477330f729Sjoerg ProgramStateRef State = C.getState();
1487330f729Sjoerg if (isChildProcess(State))
1497330f729Sjoerg return;
1507330f729Sjoerg
1517330f729Sjoerg if (!isVforkCall(Call.getDecl(), C))
1527330f729Sjoerg return;
1537330f729Sjoerg
1547330f729Sjoerg // Get return value of vfork.
1557330f729Sjoerg SVal VforkRetVal = Call.getReturnValue();
1567330f729Sjoerg Optional<DefinedOrUnknownSVal> DVal =
1577330f729Sjoerg VforkRetVal.getAs<DefinedOrUnknownSVal>();
1587330f729Sjoerg if (!DVal)
1597330f729Sjoerg return;
1607330f729Sjoerg
1617330f729Sjoerg // Get assigned variable.
1627330f729Sjoerg const ParentMap &PM = C.getLocationContext()->getParentMap();
1637330f729Sjoerg const Stmt *P = PM.getParentIgnoreParenCasts(Call.getOriginExpr());
1647330f729Sjoerg const VarDecl *LhsDecl;
1657330f729Sjoerg std::tie(LhsDecl, std::ignore) = parseAssignment(P);
1667330f729Sjoerg
1677330f729Sjoerg // Get assigned memory region.
1687330f729Sjoerg MemRegionManager &M = C.getStoreManager().getRegionManager();
1697330f729Sjoerg const MemRegion *LhsDeclReg =
1707330f729Sjoerg LhsDecl
1717330f729Sjoerg ? M.getVarRegion(LhsDecl, C.getLocationContext())
1727330f729Sjoerg : (const MemRegion *)VFORK_RESULT_NONE;
1737330f729Sjoerg
1747330f729Sjoerg // Parent branch gets nonzero return value (according to manpage).
1757330f729Sjoerg ProgramStateRef ParentState, ChildState;
1767330f729Sjoerg std::tie(ParentState, ChildState) = C.getState()->assume(*DVal);
1777330f729Sjoerg C.addTransition(ParentState);
1787330f729Sjoerg ChildState = ChildState->set<VforkResultRegion>(LhsDeclReg);
1797330f729Sjoerg C.addTransition(ChildState);
1807330f729Sjoerg }
1817330f729Sjoerg
1827330f729Sjoerg // Prohibit calls to non-whitelist functions in child process.
checkPreCall(const CallEvent & Call,CheckerContext & C) const1837330f729Sjoerg void VforkChecker::checkPreCall(const CallEvent &Call,
1847330f729Sjoerg CheckerContext &C) const {
1857330f729Sjoerg ProgramStateRef State = C.getState();
1867330f729Sjoerg if (isChildProcess(State)
1877330f729Sjoerg && !isCallWhitelisted(Call.getCalleeIdentifier(), C))
1887330f729Sjoerg reportBug("This function call", C);
1897330f729Sjoerg }
1907330f729Sjoerg
1917330f729Sjoerg // Prohibit writes in child process (except for vfork's lhs).
checkBind(SVal L,SVal V,const Stmt * S,CheckerContext & C) const1927330f729Sjoerg void VforkChecker::checkBind(SVal L, SVal V, const Stmt *S,
1937330f729Sjoerg CheckerContext &C) const {
1947330f729Sjoerg ProgramStateRef State = C.getState();
1957330f729Sjoerg if (!isChildProcess(State))
1967330f729Sjoerg return;
1977330f729Sjoerg
1987330f729Sjoerg const MemRegion *VforkLhs =
1997330f729Sjoerg static_cast<const MemRegion *>(State->get<VforkResultRegion>());
2007330f729Sjoerg const MemRegion *MR = L.getAsRegion();
2017330f729Sjoerg
2027330f729Sjoerg // Child is allowed to modify only vfork's lhs.
2037330f729Sjoerg if (!MR || MR == VforkLhs)
2047330f729Sjoerg return;
2057330f729Sjoerg
2067330f729Sjoerg reportBug("This assignment", C);
2077330f729Sjoerg }
2087330f729Sjoerg
2097330f729Sjoerg // Prohibit return from function in child process.
checkPreStmt(const ReturnStmt * RS,CheckerContext & C) const2107330f729Sjoerg void VforkChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const {
2117330f729Sjoerg ProgramStateRef State = C.getState();
2127330f729Sjoerg if (isChildProcess(State))
2137330f729Sjoerg reportBug("Return", C, "call _exit() instead");
2147330f729Sjoerg }
2157330f729Sjoerg
registerVforkChecker(CheckerManager & mgr)2167330f729Sjoerg void ento::registerVforkChecker(CheckerManager &mgr) {
2177330f729Sjoerg mgr.registerChecker<VforkChecker>();
2187330f729Sjoerg }
2197330f729Sjoerg
shouldRegisterVforkChecker(const CheckerManager & mgr)220*e038c9c4Sjoerg bool ento::shouldRegisterVforkChecker(const CheckerManager &mgr) {
2217330f729Sjoerg return true;
2227330f729Sjoerg }
223