xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp (revision e034c4ef7b52635bb9cc78b6d3f97a4af5002f92)
1 //===-- ChrootChecker.cpp - chroot usage checks ---------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 //  This file defines chroot checker, which checks improper use of chroot.
10 //  This is described by the SEI Cert C rule POS05-C.
11 //  The checker is a warning not a hard failure since it only checks for a
12 //  recommended rule.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #include "clang/AST/ASTContext.h"
17 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19 #include "clang/StaticAnalyzer/Core/Checker.h"
20 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
22 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
23 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
24 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
25 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
26 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
27 
28 using namespace clang;
29 using namespace ento;
30 
31 namespace {
32 enum ChrootKind { NO_CHROOT, ROOT_CHANGED, ROOT_CHANGE_FAILED, JAIL_ENTERED };
33 } // namespace
34 
35 // Track chroot state changes for success, failure, state change
36 // and "jail"
37 REGISTER_TRAIT_WITH_PROGRAMSTATE(ChrootState, ChrootKind)
38 namespace {
39 
40 // This checker checks improper use of chroot.
41 // The state transitions
42 //
43 //                          -> ROOT_CHANGE_FAILED
44 //                          |
45 // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
46 //                                  |                               |
47 //         ROOT_CHANGED<--chdir(..)--      JAIL_ENTERED<--chdir(..)--
48 //                                  |                               |
49 //                      bug<--foo()--          JAIL_ENTERED<--foo()--
50 //
51 class ChrootChecker final : public Checker<eval::Call, check::PreCall> {
52 public:
53   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
54   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
55 
56 private:
57   bool evalChroot(const CallEvent &Call, CheckerContext &C) const;
58   bool evalChdir(const CallEvent &Call, CheckerContext &C) const;
59 
60   const BugType BreakJailBug{this, "Break out of jail"};
61   const CallDescription Chroot{CDM::CLibrary, {"chroot"}, 1};
62   const CallDescription Chdir{CDM::CLibrary, {"chdir"}, 1};
63 };
64 
65 bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
66   if (Chroot.matches(Call))
67     return evalChroot(Call, C);
68 
69   if (Chdir.matches(Call))
70     return evalChdir(Call, C);
71 
72   return false;
73 }
74 
75 bool ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const {
76   BasicValueFactory &BVF = C.getSValBuilder().getBasicValueFactory();
77   const LocationContext *LCtx = C.getLocationContext();
78   ProgramStateRef State = C.getState();
79   const auto *CE = cast<CallExpr>(Call.getOriginExpr());
80 
81   const QualType IntTy = C.getASTContext().IntTy;
82   SVal Zero = nonloc::ConcreteInt{BVF.getValue(0, IntTy)};
83   SVal Minus1 = nonloc::ConcreteInt{BVF.getValue(-1, IntTy)};
84 
85   ProgramStateRef ChrootFailed = State->BindExpr(CE, LCtx, Minus1);
86   C.addTransition(ChrootFailed->set<ChrootState>(ROOT_CHANGE_FAILED));
87 
88   ProgramStateRef ChrootSucceeded = State->BindExpr(CE, LCtx, Zero);
89   C.addTransition(ChrootSucceeded->set<ChrootState>(ROOT_CHANGED));
90   return true;
91 }
92 
93 bool ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const {
94   ProgramStateRef State = C.getState();
95 
96   // If there are no jail state, just return.
97   if (State->get<ChrootState>() == NO_CHROOT)
98     return false;
99 
100   // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
101   SVal ArgVal = Call.getArgSVal(0);
102 
103   if (const MemRegion *R = ArgVal.getAsRegion()) {
104     R = R->StripCasts();
105     if (const auto *StrRegion = dyn_cast<StringRegion>(R)) {
106       if (StrRegion->getStringLiteral()->getString() == "/") {
107         C.addTransition(State->set<ChrootState>(JAIL_ENTERED));
108         return true;
109       }
110     }
111   }
112   return false;
113 }
114 
115 class ChrootInvocationVisitor final : public BugReporterVisitor {
116 public:
117   explicit ChrootInvocationVisitor(const CallDescription &Chroot)
118       : Chroot{Chroot} {}
119 
120   PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
121                                    BugReporterContext &BRC,
122                                    PathSensitiveBugReport &BR) override {
123     if (Satisfied)
124       return nullptr;
125 
126     auto StmtP = N->getLocation().getAs<StmtPoint>();
127     if (!StmtP)
128       return nullptr;
129 
130     const CallExpr *Call = StmtP->getStmtAs<CallExpr>();
131     if (!Call)
132       return nullptr;
133 
134     if (!Chroot.matchesAsWritten(*Call))
135       return nullptr;
136 
137     Satisfied = true;
138     PathDiagnosticLocation Pos(Call, BRC.getSourceManager(),
139                                N->getLocationContext());
140     return std::make_shared<PathDiagnosticEventPiece>(Pos, "chroot called here",
141                                                       /*addPosRange=*/true);
142   }
143 
144   void Profile(llvm::FoldingSetNodeID &ID) const override {
145     static bool Tag;
146     ID.AddPointer(&Tag);
147   }
148 
149 private:
150   const CallDescription &Chroot;
151   bool Satisfied = false;
152 };
153 
154 // Check the jail state before any function call except chroot and chdir().
155 void ChrootChecker::checkPreCall(const CallEvent &Call,
156                                  CheckerContext &C) const {
157   // Ignore chroot and chdir.
158   if (matchesAny(Call, Chroot, Chdir))
159     return;
160 
161   // If jail state is not ROOT_CHANGED just return.
162   if (C.getState()->get<ChrootState>() != ROOT_CHANGED)
163     return;
164 
165   // Generate bug report.
166   ExplodedNode *Err =
167       C.generateNonFatalErrorNode(C.getState(), C.getPredecessor());
168   if (!Err)
169     return;
170 
171   auto R = std::make_unique<PathSensitiveBugReport>(
172       BreakJailBug, R"(No call of chdir("/") immediately after chroot)", Err);
173   R->addVisitor<ChrootInvocationVisitor>(Chroot);
174   C.emitReport(std::move(R));
175 }
176 
177 } // namespace
178 
179 void ento::registerChrootChecker(CheckerManager &Mgr) {
180   Mgr.registerChecker<ChrootChecker>();
181 }
182 
183 bool ento::shouldRegisterChrootChecker(const CheckerManager &) { return true; }
184