10b57cec5SDimitry Andric //===- VforkChecker.cpp -------- Vfork usage checks --------------*- C++ -*-==//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // This file defines vfork checker which checks for dangerous uses of vfork.
100b57cec5SDimitry Andric // Vforked process shares memory (including stack) with parent so it's
110b57cec5SDimitry Andric // range of actions is significantly limited: can't write variables,
12349cc55cSDimitry Andric // can't call functions not in the allowed list, etc. For more details, see
130b57cec5SDimitry Andric // http://man7.org/linux/man-pages/man2/vfork.2.html
140b57cec5SDimitry Andric //
150b57cec5SDimitry Andric // This checker checks for prohibited constructs in vforked process.
160b57cec5SDimitry Andric // The state transition diagram:
170b57cec5SDimitry Andric // PARENT ---(vfork() == 0)--> CHILD
180b57cec5SDimitry Andric // |
190b57cec5SDimitry Andric // --(*p = ...)--> bug
200b57cec5SDimitry Andric // |
210b57cec5SDimitry Andric // --foo()--> bug
220b57cec5SDimitry Andric // |
230b57cec5SDimitry Andric // --return--> bug
240b57cec5SDimitry Andric //
250b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
260b57cec5SDimitry Andric
270b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
280b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
290b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
300b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
310b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
320b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
330b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
340b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
350b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h"
360b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/CheckerManager.h"
370b57cec5SDimitry Andric #include "clang/AST/ParentMap.h"
38bdd1243dSDimitry Andric #include <optional>
390b57cec5SDimitry Andric
400b57cec5SDimitry Andric using namespace clang;
410b57cec5SDimitry Andric using namespace ento;
420b57cec5SDimitry Andric
430b57cec5SDimitry Andric namespace {
440b57cec5SDimitry Andric
450b57cec5SDimitry Andric class VforkChecker : public Checker<check::PreCall, check::PostCall,
460b57cec5SDimitry Andric check::Bind, check::PreStmt<ReturnStmt>> {
47*647cbc5dSDimitry Andric const BugType BT{this, "Dangerous construct in a vforked process"};
48349cc55cSDimitry Andric mutable llvm::SmallSet<const IdentifierInfo *, 10> VforkAllowlist;
495f757f3fSDimitry Andric mutable const IdentifierInfo *II_vfork = nullptr;
500b57cec5SDimitry Andric
510b57cec5SDimitry Andric static bool isChildProcess(const ProgramStateRef State);
520b57cec5SDimitry Andric
530b57cec5SDimitry Andric bool isVforkCall(const Decl *D, CheckerContext &C) const;
54349cc55cSDimitry Andric bool isCallExplicitelyAllowed(const IdentifierInfo *II,
55349cc55cSDimitry Andric CheckerContext &C) const;
560b57cec5SDimitry Andric
570b57cec5SDimitry Andric void reportBug(const char *What, CheckerContext &C,
580b57cec5SDimitry Andric const char *Details = nullptr) const;
590b57cec5SDimitry Andric
600b57cec5SDimitry Andric public:
615f757f3fSDimitry Andric VforkChecker() = default;
620b57cec5SDimitry Andric
630b57cec5SDimitry Andric void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
640b57cec5SDimitry Andric void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
650b57cec5SDimitry Andric void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const;
660b57cec5SDimitry Andric void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;
670b57cec5SDimitry Andric };
680b57cec5SDimitry Andric
690b57cec5SDimitry Andric } // end anonymous namespace
700b57cec5SDimitry Andric
710b57cec5SDimitry Andric // This trait holds region of variable that is assigned with vfork's
720b57cec5SDimitry Andric // return value (this is the only region child is allowed to write).
730b57cec5SDimitry Andric // VFORK_RESULT_INVALID means that we are in parent process.
740b57cec5SDimitry Andric // VFORK_RESULT_NONE means that vfork's return value hasn't been assigned.
750b57cec5SDimitry Andric // Other values point to valid regions.
REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion,const void *)760b57cec5SDimitry Andric REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion, const void *)
770b57cec5SDimitry Andric #define VFORK_RESULT_INVALID 0
780b57cec5SDimitry Andric #define VFORK_RESULT_NONE ((void *)(uintptr_t)1)
790b57cec5SDimitry Andric
800b57cec5SDimitry Andric bool VforkChecker::isChildProcess(const ProgramStateRef State) {
810b57cec5SDimitry Andric return State->get<VforkResultRegion>() != VFORK_RESULT_INVALID;
820b57cec5SDimitry Andric }
830b57cec5SDimitry Andric
isVforkCall(const Decl * D,CheckerContext & C) const840b57cec5SDimitry Andric bool VforkChecker::isVforkCall(const Decl *D, CheckerContext &C) const {
850b57cec5SDimitry Andric auto FD = dyn_cast_or_null<FunctionDecl>(D);
860b57cec5SDimitry Andric if (!FD || !C.isCLibraryFunction(FD))
870b57cec5SDimitry Andric return false;
880b57cec5SDimitry Andric
890b57cec5SDimitry Andric if (!II_vfork) {
900b57cec5SDimitry Andric ASTContext &AC = C.getASTContext();
910b57cec5SDimitry Andric II_vfork = &AC.Idents.get("vfork");
920b57cec5SDimitry Andric }
930b57cec5SDimitry Andric
940b57cec5SDimitry Andric return FD->getIdentifier() == II_vfork;
950b57cec5SDimitry Andric }
960b57cec5SDimitry Andric
970b57cec5SDimitry Andric // Returns true iff ok to call function after successful vfork.
isCallExplicitelyAllowed(const IdentifierInfo * II,CheckerContext & C) const98349cc55cSDimitry Andric bool VforkChecker::isCallExplicitelyAllowed(const IdentifierInfo *II,
990b57cec5SDimitry Andric CheckerContext &C) const {
100349cc55cSDimitry Andric if (VforkAllowlist.empty()) {
1010b57cec5SDimitry Andric // According to manpage.
1020b57cec5SDimitry Andric const char *ids[] = {
1030b57cec5SDimitry Andric "_Exit",
1045ffd83dbSDimitry Andric "_exit",
1050b57cec5SDimitry Andric "execl",
1060b57cec5SDimitry Andric "execle",
1075ffd83dbSDimitry Andric "execlp",
1080b57cec5SDimitry Andric "execv",
1095ffd83dbSDimitry Andric "execve",
1100b57cec5SDimitry Andric "execvp",
1110b57cec5SDimitry Andric "execvpe",
1120b57cec5SDimitry Andric nullptr
1130b57cec5SDimitry Andric };
1140b57cec5SDimitry Andric
1150b57cec5SDimitry Andric ASTContext &AC = C.getASTContext();
1160b57cec5SDimitry Andric for (const char **id = ids; *id; ++id)
117349cc55cSDimitry Andric VforkAllowlist.insert(&AC.Idents.get(*id));
1180b57cec5SDimitry Andric }
1190b57cec5SDimitry Andric
120349cc55cSDimitry Andric return VforkAllowlist.count(II);
1210b57cec5SDimitry Andric }
1220b57cec5SDimitry Andric
reportBug(const char * What,CheckerContext & C,const char * Details) const1230b57cec5SDimitry Andric void VforkChecker::reportBug(const char *What, CheckerContext &C,
1240b57cec5SDimitry Andric const char *Details) const {
1250b57cec5SDimitry Andric if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
1260b57cec5SDimitry Andric SmallString<256> buf;
1270b57cec5SDimitry Andric llvm::raw_svector_ostream os(buf);
1280b57cec5SDimitry Andric
1290b57cec5SDimitry Andric os << What << " is prohibited after a successful vfork";
1300b57cec5SDimitry Andric
1310b57cec5SDimitry Andric if (Details)
1320b57cec5SDimitry Andric os << "; " << Details;
1330b57cec5SDimitry Andric
134*647cbc5dSDimitry Andric auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);
1350b57cec5SDimitry Andric // TODO: mark vfork call in BugReportVisitor
1360b57cec5SDimitry Andric C.emitReport(std::move(Report));
1370b57cec5SDimitry Andric }
1380b57cec5SDimitry Andric }
1390b57cec5SDimitry Andric
1400b57cec5SDimitry Andric // Detect calls to vfork and split execution appropriately.
checkPostCall(const CallEvent & Call,CheckerContext & C) const1410b57cec5SDimitry Andric void VforkChecker::checkPostCall(const CallEvent &Call,
1420b57cec5SDimitry Andric CheckerContext &C) const {
1430b57cec5SDimitry Andric // We can't call vfork in child so don't bother
1440b57cec5SDimitry Andric // (corresponding warning has already been emitted in checkPreCall).
1450b57cec5SDimitry Andric ProgramStateRef State = C.getState();
1460b57cec5SDimitry Andric if (isChildProcess(State))
1470b57cec5SDimitry Andric return;
1480b57cec5SDimitry Andric
1490b57cec5SDimitry Andric if (!isVforkCall(Call.getDecl(), C))
1500b57cec5SDimitry Andric return;
1510b57cec5SDimitry Andric
1520b57cec5SDimitry Andric // Get return value of vfork.
1530b57cec5SDimitry Andric SVal VforkRetVal = Call.getReturnValue();
154bdd1243dSDimitry Andric std::optional<DefinedOrUnknownSVal> DVal =
1550b57cec5SDimitry Andric VforkRetVal.getAs<DefinedOrUnknownSVal>();
1560b57cec5SDimitry Andric if (!DVal)
1570b57cec5SDimitry Andric return;
1580b57cec5SDimitry Andric
1590b57cec5SDimitry Andric // Get assigned variable.
1600b57cec5SDimitry Andric const ParentMap &PM = C.getLocationContext()->getParentMap();
1610b57cec5SDimitry Andric const Stmt *P = PM.getParentIgnoreParenCasts(Call.getOriginExpr());
1620b57cec5SDimitry Andric const VarDecl *LhsDecl;
1630b57cec5SDimitry Andric std::tie(LhsDecl, std::ignore) = parseAssignment(P);
1640b57cec5SDimitry Andric
1650b57cec5SDimitry Andric // Get assigned memory region.
1660b57cec5SDimitry Andric MemRegionManager &M = C.getStoreManager().getRegionManager();
1670b57cec5SDimitry Andric const MemRegion *LhsDeclReg =
1680b57cec5SDimitry Andric LhsDecl
1690b57cec5SDimitry Andric ? M.getVarRegion(LhsDecl, C.getLocationContext())
1700b57cec5SDimitry Andric : (const MemRegion *)VFORK_RESULT_NONE;
1710b57cec5SDimitry Andric
1720b57cec5SDimitry Andric // Parent branch gets nonzero return value (according to manpage).
1730b57cec5SDimitry Andric ProgramStateRef ParentState, ChildState;
1740b57cec5SDimitry Andric std::tie(ParentState, ChildState) = C.getState()->assume(*DVal);
1750b57cec5SDimitry Andric C.addTransition(ParentState);
1760b57cec5SDimitry Andric ChildState = ChildState->set<VforkResultRegion>(LhsDeclReg);
1770b57cec5SDimitry Andric C.addTransition(ChildState);
1780b57cec5SDimitry Andric }
1790b57cec5SDimitry Andric
180349cc55cSDimitry Andric // Prohibit calls to functions in child process which are not explicitly
181349cc55cSDimitry Andric // allowed.
checkPreCall(const CallEvent & Call,CheckerContext & C) const1820b57cec5SDimitry Andric void VforkChecker::checkPreCall(const CallEvent &Call,
1830b57cec5SDimitry Andric CheckerContext &C) const {
1840b57cec5SDimitry Andric ProgramStateRef State = C.getState();
185349cc55cSDimitry Andric if (isChildProcess(State) &&
186349cc55cSDimitry Andric !isCallExplicitelyAllowed(Call.getCalleeIdentifier(), C))
1870b57cec5SDimitry Andric reportBug("This function call", C);
1880b57cec5SDimitry Andric }
1890b57cec5SDimitry Andric
1900b57cec5SDimitry Andric // Prohibit writes in child process (except for vfork's lhs).
checkBind(SVal L,SVal V,const Stmt * S,CheckerContext & C) const1910b57cec5SDimitry Andric void VforkChecker::checkBind(SVal L, SVal V, const Stmt *S,
1920b57cec5SDimitry Andric CheckerContext &C) const {
1930b57cec5SDimitry Andric ProgramStateRef State = C.getState();
1940b57cec5SDimitry Andric if (!isChildProcess(State))
1950b57cec5SDimitry Andric return;
1960b57cec5SDimitry Andric
1970b57cec5SDimitry Andric const MemRegion *VforkLhs =
1980b57cec5SDimitry Andric static_cast<const MemRegion *>(State->get<VforkResultRegion>());
1990b57cec5SDimitry Andric const MemRegion *MR = L.getAsRegion();
2000b57cec5SDimitry Andric
2010b57cec5SDimitry Andric // Child is allowed to modify only vfork's lhs.
2020b57cec5SDimitry Andric if (!MR || MR == VforkLhs)
2030b57cec5SDimitry Andric return;
2040b57cec5SDimitry Andric
2050b57cec5SDimitry Andric reportBug("This assignment", C);
2060b57cec5SDimitry Andric }
2070b57cec5SDimitry Andric
2080b57cec5SDimitry Andric // Prohibit return from function in child process.
checkPreStmt(const ReturnStmt * RS,CheckerContext & C) const2090b57cec5SDimitry Andric void VforkChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const {
2100b57cec5SDimitry Andric ProgramStateRef State = C.getState();
2110b57cec5SDimitry Andric if (isChildProcess(State))
2120b57cec5SDimitry Andric reportBug("Return", C, "call _exit() instead");
2130b57cec5SDimitry Andric }
2140b57cec5SDimitry Andric
registerVforkChecker(CheckerManager & mgr)2150b57cec5SDimitry Andric void ento::registerVforkChecker(CheckerManager &mgr) {
2160b57cec5SDimitry Andric mgr.registerChecker<VforkChecker>();
2170b57cec5SDimitry Andric }
2180b57cec5SDimitry Andric
shouldRegisterVforkChecker(const CheckerManager & mgr)2195ffd83dbSDimitry Andric bool ento::shouldRegisterVforkChecker(const CheckerManager &mgr) {
2200b57cec5SDimitry Andric return true;
2210b57cec5SDimitry Andric }
222