122b4164eSYury Gribov //===- VforkChecker.cpp -------- Vfork usage checks --------------*- C++ -*-==//
222b4164eSYury Gribov //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
622b4164eSYury Gribov //
722b4164eSYury Gribov //===----------------------------------------------------------------------===//
822b4164eSYury Gribov //
922b4164eSYury Gribov //  This file defines vfork checker which checks for dangerous uses of vfork.
1022b4164eSYury Gribov //  Vforked process shares memory (including stack) with parent so it's
1122b4164eSYury Gribov //  range of actions is significantly limited: can't write variables,
12*8659b241SZarko Todorovski //  can't call functions not in the allowed list, etc. For more details, see
1322b4164eSYury Gribov //  http://man7.org/linux/man-pages/man2/vfork.2.html
1422b4164eSYury Gribov //
1522b4164eSYury Gribov //  This checker checks for prohibited constructs in vforked process.
1622b4164eSYury Gribov //  The state transition diagram:
1722b4164eSYury Gribov //  PARENT ---(vfork() == 0)--> CHILD
1822b4164eSYury Gribov //                                   |
1922b4164eSYury Gribov //                                   --(*p = ...)--> bug
2022b4164eSYury Gribov //                                   |
2122b4164eSYury Gribov //                                   --foo()--> bug
2222b4164eSYury Gribov //                                   |
2322b4164eSYury Gribov //                                   --return--> bug
2422b4164eSYury Gribov //
2522b4164eSYury Gribov //===----------------------------------------------------------------------===//
2622b4164eSYury Gribov 
2776a21502SKristof Umann #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
2822b4164eSYury Gribov #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
2922b4164eSYury Gribov #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
3022b4164eSYury Gribov #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
3122b4164eSYury Gribov #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
3222b4164eSYury Gribov #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
3322b4164eSYury Gribov #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
3422b4164eSYury Gribov #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
3522b4164eSYury Gribov #include "clang/StaticAnalyzer/Core/Checker.h"
3622b4164eSYury Gribov #include "clang/StaticAnalyzer/Core/CheckerManager.h"
3722b4164eSYury Gribov #include "clang/AST/ParentMap.h"
3822b4164eSYury Gribov 
3922b4164eSYury Gribov using namespace clang;
4022b4164eSYury Gribov using namespace ento;
4122b4164eSYury Gribov 
4222b4164eSYury Gribov namespace {
4322b4164eSYury Gribov 
4422b4164eSYury Gribov class VforkChecker : public Checker<check::PreCall, check::PostCall,
4522b4164eSYury Gribov                                     check::Bind, check::PreStmt<ReturnStmt>> {
4622b4164eSYury Gribov   mutable std::unique_ptr<BuiltinBug> BT;
47*8659b241SZarko Todorovski   mutable llvm::SmallSet<const IdentifierInfo *, 10> VforkAllowlist;
4822b4164eSYury Gribov   mutable const IdentifierInfo *II_vfork;
4922b4164eSYury Gribov 
5022b4164eSYury Gribov   static bool isChildProcess(const ProgramStateRef State);
5122b4164eSYury Gribov 
5222b4164eSYury Gribov   bool isVforkCall(const Decl *D, CheckerContext &C) const;
53*8659b241SZarko Todorovski   bool isCallExplicitelyAllowed(const IdentifierInfo *II,
54*8659b241SZarko Todorovski                                 CheckerContext &C) const;
5522b4164eSYury Gribov 
5622b4164eSYury Gribov   void reportBug(const char *What, CheckerContext &C,
571660a5d2SEugene Zelenko                  const char *Details = nullptr) const;
5822b4164eSYury Gribov 
5922b4164eSYury Gribov public:
VforkChecker()601660a5d2SEugene Zelenko   VforkChecker() : II_vfork(nullptr) {}
6122b4164eSYury Gribov 
6222b4164eSYury Gribov   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
6322b4164eSYury Gribov   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
6422b4164eSYury Gribov   void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const;
6522b4164eSYury Gribov   void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;
6622b4164eSYury Gribov };
6722b4164eSYury Gribov 
6822b4164eSYury Gribov } // end anonymous namespace
6922b4164eSYury Gribov 
7022b4164eSYury Gribov // This trait holds region of variable that is assigned with vfork's
7122b4164eSYury Gribov // return value (this is the only region child is allowed to write).
7222b4164eSYury Gribov // VFORK_RESULT_INVALID means that we are in parent process.
7322b4164eSYury Gribov // VFORK_RESULT_NONE means that vfork's return value hasn't been assigned.
7422b4164eSYury Gribov // Other values point to valid regions.
REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion,const void *)7522b4164eSYury Gribov REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion, const void *)
7622b4164eSYury Gribov #define VFORK_RESULT_INVALID 0
7722b4164eSYury Gribov #define VFORK_RESULT_NONE ((void *)(uintptr_t)1)
7822b4164eSYury Gribov 
7922b4164eSYury Gribov bool VforkChecker::isChildProcess(const ProgramStateRef State) {
8022b4164eSYury Gribov   return State->get<VforkResultRegion>() != VFORK_RESULT_INVALID;
8122b4164eSYury Gribov }
8222b4164eSYury Gribov 
isVforkCall(const Decl * D,CheckerContext & C) const8322b4164eSYury Gribov bool VforkChecker::isVforkCall(const Decl *D, CheckerContext &C) const {
8422b4164eSYury Gribov   auto FD = dyn_cast_or_null<FunctionDecl>(D);
8522b4164eSYury Gribov   if (!FD || !C.isCLibraryFunction(FD))
8622b4164eSYury Gribov     return false;
8722b4164eSYury Gribov 
8822b4164eSYury Gribov   if (!II_vfork) {
8922b4164eSYury Gribov     ASTContext &AC = C.getASTContext();
9022b4164eSYury Gribov     II_vfork = &AC.Idents.get("vfork");
9122b4164eSYury Gribov   }
9222b4164eSYury Gribov 
9322b4164eSYury Gribov   return FD->getIdentifier() == II_vfork;
9422b4164eSYury Gribov }
9522b4164eSYury Gribov 
9622b4164eSYury Gribov // Returns true iff ok to call function after successful vfork.
isCallExplicitelyAllowed(const IdentifierInfo * II,CheckerContext & C) const97*8659b241SZarko Todorovski bool VforkChecker::isCallExplicitelyAllowed(const IdentifierInfo *II,
9822b4164eSYury Gribov                                             CheckerContext &C) const {
99*8659b241SZarko Todorovski   if (VforkAllowlist.empty()) {
10022b4164eSYury Gribov     // According to manpage.
10122b4164eSYury Gribov     const char *ids[] = {
10222b4164eSYury Gribov       "_Exit",
1035a11233aSArtem Dergachev       "_exit",
10422b4164eSYury Gribov       "execl",
10522b4164eSYury Gribov       "execle",
1065a11233aSArtem Dergachev       "execlp",
10722b4164eSYury Gribov       "execv",
1085a11233aSArtem Dergachev       "execve",
10922b4164eSYury Gribov       "execvp",
11022b4164eSYury Gribov       "execvpe",
1111660a5d2SEugene Zelenko       nullptr
11222b4164eSYury Gribov     };
11322b4164eSYury Gribov 
11422b4164eSYury Gribov     ASTContext &AC = C.getASTContext();
11522b4164eSYury Gribov     for (const char **id = ids; *id; ++id)
116*8659b241SZarko Todorovski       VforkAllowlist.insert(&AC.Idents.get(*id));
11722b4164eSYury Gribov   }
11822b4164eSYury Gribov 
119*8659b241SZarko Todorovski   return VforkAllowlist.count(II);
12022b4164eSYury Gribov }
12122b4164eSYury Gribov 
reportBug(const char * What,CheckerContext & C,const char * Details) const12222b4164eSYury Gribov void VforkChecker::reportBug(const char *What, CheckerContext &C,
12322b4164eSYury Gribov                              const char *Details) const {
12422b4164eSYury Gribov   if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
12522b4164eSYury Gribov     if (!BT)
12622b4164eSYury Gribov       BT.reset(new BuiltinBug(this,
12722b4164eSYury Gribov                               "Dangerous construct in a vforked process"));
12822b4164eSYury Gribov 
12922b4164eSYury Gribov     SmallString<256> buf;
13022b4164eSYury Gribov     llvm::raw_svector_ostream os(buf);
13122b4164eSYury Gribov 
13222b4164eSYury Gribov     os << What << " is prohibited after a successful vfork";
13322b4164eSYury Gribov 
13422b4164eSYury Gribov     if (Details)
13522b4164eSYury Gribov       os << "; " << Details;
13622b4164eSYury Gribov 
1372f169e7cSArtem Dergachev     auto Report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N);
13822b4164eSYury Gribov     // TODO: mark vfork call in BugReportVisitor
13922b4164eSYury Gribov     C.emitReport(std::move(Report));
14022b4164eSYury Gribov   }
14122b4164eSYury Gribov }
14222b4164eSYury Gribov 
14322b4164eSYury Gribov // Detect calls to vfork and split execution appropriately.
checkPostCall(const CallEvent & Call,CheckerContext & C) const14422b4164eSYury Gribov void VforkChecker::checkPostCall(const CallEvent &Call,
14522b4164eSYury Gribov                                  CheckerContext &C) const {
14622b4164eSYury Gribov   // We can't call vfork in child so don't bother
14722b4164eSYury Gribov   // (corresponding warning has already been emitted in checkPreCall).
14822b4164eSYury Gribov   ProgramStateRef State = C.getState();
14922b4164eSYury Gribov   if (isChildProcess(State))
15022b4164eSYury Gribov     return;
15122b4164eSYury Gribov 
15222b4164eSYury Gribov   if (!isVforkCall(Call.getDecl(), C))
15322b4164eSYury Gribov     return;
15422b4164eSYury Gribov 
15522b4164eSYury Gribov   // Get return value of vfork.
15622b4164eSYury Gribov   SVal VforkRetVal = Call.getReturnValue();
15722b4164eSYury Gribov   Optional<DefinedOrUnknownSVal> DVal =
15822b4164eSYury Gribov     VforkRetVal.getAs<DefinedOrUnknownSVal>();
15922b4164eSYury Gribov   if (!DVal)
16022b4164eSYury Gribov     return;
16122b4164eSYury Gribov 
16222b4164eSYury Gribov   // Get assigned variable.
16322b4164eSYury Gribov   const ParentMap &PM = C.getLocationContext()->getParentMap();
16422b4164eSYury Gribov   const Stmt *P = PM.getParentIgnoreParenCasts(Call.getOriginExpr());
16522b4164eSYury Gribov   const VarDecl *LhsDecl;
16622b4164eSYury Gribov   std::tie(LhsDecl, std::ignore) = parseAssignment(P);
16722b4164eSYury Gribov 
16822b4164eSYury Gribov   // Get assigned memory region.
16922b4164eSYury Gribov   MemRegionManager &M = C.getStoreManager().getRegionManager();
17022b4164eSYury Gribov   const MemRegion *LhsDeclReg =
17122b4164eSYury Gribov     LhsDecl
17222b4164eSYury Gribov       ? M.getVarRegion(LhsDecl, C.getLocationContext())
17322b4164eSYury Gribov       : (const MemRegion *)VFORK_RESULT_NONE;
17422b4164eSYury Gribov 
17522b4164eSYury Gribov   // Parent branch gets nonzero return value (according to manpage).
17622b4164eSYury Gribov   ProgramStateRef ParentState, ChildState;
17722b4164eSYury Gribov   std::tie(ParentState, ChildState) = C.getState()->assume(*DVal);
17822b4164eSYury Gribov   C.addTransition(ParentState);
17922b4164eSYury Gribov   ChildState = ChildState->set<VforkResultRegion>(LhsDeclReg);
18022b4164eSYury Gribov   C.addTransition(ChildState);
18122b4164eSYury Gribov }
18222b4164eSYury Gribov 
183*8659b241SZarko Todorovski // Prohibit calls to functions in child process which are not explicitly
184*8659b241SZarko Todorovski // allowed.
checkPreCall(const CallEvent & Call,CheckerContext & C) const18522b4164eSYury Gribov void VforkChecker::checkPreCall(const CallEvent &Call,
18622b4164eSYury Gribov                                 CheckerContext &C) const {
18722b4164eSYury Gribov   ProgramStateRef State = C.getState();
188*8659b241SZarko Todorovski   if (isChildProcess(State) &&
189*8659b241SZarko Todorovski       !isCallExplicitelyAllowed(Call.getCalleeIdentifier(), C))
19022b4164eSYury Gribov     reportBug("This function call", C);
19122b4164eSYury Gribov }
19222b4164eSYury Gribov 
19322b4164eSYury Gribov // Prohibit writes in child process (except for vfork's lhs).
checkBind(SVal L,SVal V,const Stmt * S,CheckerContext & C) const19422b4164eSYury Gribov void VforkChecker::checkBind(SVal L, SVal V, const Stmt *S,
19522b4164eSYury Gribov                              CheckerContext &C) const {
19622b4164eSYury Gribov   ProgramStateRef State = C.getState();
19722b4164eSYury Gribov   if (!isChildProcess(State))
19822b4164eSYury Gribov     return;
19922b4164eSYury Gribov 
20022b4164eSYury Gribov   const MemRegion *VforkLhs =
20122b4164eSYury Gribov     static_cast<const MemRegion *>(State->get<VforkResultRegion>());
20222b4164eSYury Gribov   const MemRegion *MR = L.getAsRegion();
20322b4164eSYury Gribov 
20422b4164eSYury Gribov   // Child is allowed to modify only vfork's lhs.
20522b4164eSYury Gribov   if (!MR || MR == VforkLhs)
20622b4164eSYury Gribov     return;
20722b4164eSYury Gribov 
20822b4164eSYury Gribov   reportBug("This assignment", C);
20922b4164eSYury Gribov }
21022b4164eSYury Gribov 
21122b4164eSYury Gribov // Prohibit return from function in child process.
checkPreStmt(const ReturnStmt * RS,CheckerContext & C) const21222b4164eSYury Gribov void VforkChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const {
21322b4164eSYury Gribov   ProgramStateRef State = C.getState();
21422b4164eSYury Gribov   if (isChildProcess(State))
21522b4164eSYury Gribov     reportBug("Return", C, "call _exit() instead");
21622b4164eSYury Gribov }
21722b4164eSYury Gribov 
registerVforkChecker(CheckerManager & mgr)21822b4164eSYury Gribov void ento::registerVforkChecker(CheckerManager &mgr) {
21922b4164eSYury Gribov   mgr.registerChecker<VforkChecker>();
22022b4164eSYury Gribov }
221058a7a45SKristof Umann 
shouldRegisterVforkChecker(const CheckerManager & mgr)222bda3dd0dSKirstóf Umann bool ento::shouldRegisterVforkChecker(const CheckerManager &mgr) {
223058a7a45SKristof Umann   return true;
224058a7a45SKristof Umann }
225