xref: /freebsd-src/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp (revision 647cbc5de815c5651677bf8582797f716ec7b48d)
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