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