xref: /netbsd-src/external/apache2/llvm/dist/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp (revision e038c9c4676b0f19b1b7dd08a940c6ed64a6d5ae)
17330f729Sjoerg //===-- ChrootChecker.cpp - chroot usage checks ---------------------------===//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg //  This file defines chroot checker, which checks improper use of chroot.
107330f729Sjoerg //
117330f729Sjoerg //===----------------------------------------------------------------------===//
127330f729Sjoerg 
137330f729Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
147330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
157330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
167330f729Sjoerg #include "clang/StaticAnalyzer/Core/CheckerManager.h"
177330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
187330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
197330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
207330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
217330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
227330f729Sjoerg 
237330f729Sjoerg using namespace clang;
247330f729Sjoerg using namespace ento;
257330f729Sjoerg 
267330f729Sjoerg namespace {
277330f729Sjoerg 
287330f729Sjoerg // enum value that represent the jail state
297330f729Sjoerg enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
307330f729Sjoerg 
isRootChanged(intptr_t k)317330f729Sjoerg bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
327330f729Sjoerg //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
337330f729Sjoerg 
347330f729Sjoerg // This checker checks improper use of chroot.
357330f729Sjoerg // The state transition:
367330f729Sjoerg // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
377330f729Sjoerg //                                  |                               |
387330f729Sjoerg //         ROOT_CHANGED<--chdir(..)--      JAIL_ENTERED<--chdir(..)--
397330f729Sjoerg //                                  |                               |
407330f729Sjoerg //                      bug<--foo()--          JAIL_ENTERED<--foo()--
417330f729Sjoerg class ChrootChecker : public Checker<eval::Call, check::PreCall> {
427330f729Sjoerg   // This bug refers to possibly break out of a chroot() jail.
437330f729Sjoerg   mutable std::unique_ptr<BuiltinBug> BT_BreakJail;
447330f729Sjoerg 
457330f729Sjoerg   const CallDescription Chroot{"chroot", 1}, Chdir{"chdir", 1};
467330f729Sjoerg 
477330f729Sjoerg public:
ChrootChecker()487330f729Sjoerg   ChrootChecker() {}
497330f729Sjoerg 
getTag()507330f729Sjoerg   static void *getTag() {
517330f729Sjoerg     static int x;
527330f729Sjoerg     return &x;
537330f729Sjoerg   }
547330f729Sjoerg 
557330f729Sjoerg   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
567330f729Sjoerg   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
577330f729Sjoerg 
587330f729Sjoerg private:
597330f729Sjoerg   void evalChroot(const CallEvent &Call, CheckerContext &C) const;
607330f729Sjoerg   void evalChdir(const CallEvent &Call, CheckerContext &C) const;
617330f729Sjoerg };
627330f729Sjoerg 
637330f729Sjoerg } // end anonymous namespace
647330f729Sjoerg 
evalCall(const CallEvent & Call,CheckerContext & C) const657330f729Sjoerg bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
667330f729Sjoerg   if (Call.isCalled(Chroot)) {
677330f729Sjoerg     evalChroot(Call, C);
687330f729Sjoerg     return true;
697330f729Sjoerg   }
707330f729Sjoerg   if (Call.isCalled(Chdir)) {
717330f729Sjoerg     evalChdir(Call, C);
727330f729Sjoerg     return true;
737330f729Sjoerg   }
747330f729Sjoerg 
757330f729Sjoerg   return false;
767330f729Sjoerg }
777330f729Sjoerg 
evalChroot(const CallEvent & Call,CheckerContext & C) const787330f729Sjoerg void ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const {
797330f729Sjoerg   ProgramStateRef state = C.getState();
807330f729Sjoerg   ProgramStateManager &Mgr = state->getStateManager();
817330f729Sjoerg 
827330f729Sjoerg   // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
837330f729Sjoerg   // the GDM.
847330f729Sjoerg   state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
857330f729Sjoerg   C.addTransition(state);
867330f729Sjoerg }
877330f729Sjoerg 
evalChdir(const CallEvent & Call,CheckerContext & C) const887330f729Sjoerg void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const {
897330f729Sjoerg   ProgramStateRef state = C.getState();
907330f729Sjoerg   ProgramStateManager &Mgr = state->getStateManager();
917330f729Sjoerg 
927330f729Sjoerg   // If there are no jail state in the GDM, just return.
937330f729Sjoerg   const void *k = state->FindGDM(ChrootChecker::getTag());
947330f729Sjoerg   if (!k)
957330f729Sjoerg     return;
967330f729Sjoerg 
977330f729Sjoerg   // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
987330f729Sjoerg   const Expr *ArgExpr = Call.getArgExpr(0);
997330f729Sjoerg   SVal ArgVal = C.getSVal(ArgExpr);
1007330f729Sjoerg 
1017330f729Sjoerg   if (const MemRegion *R = ArgVal.getAsRegion()) {
1027330f729Sjoerg     R = R->StripCasts();
1037330f729Sjoerg     if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
1047330f729Sjoerg       const StringLiteral* Str = StrRegion->getStringLiteral();
1057330f729Sjoerg       if (Str->getString() == "/")
1067330f729Sjoerg         state = Mgr.addGDM(state, ChrootChecker::getTag(),
1077330f729Sjoerg                            (void*) JAIL_ENTERED);
1087330f729Sjoerg     }
1097330f729Sjoerg   }
1107330f729Sjoerg 
1117330f729Sjoerg   C.addTransition(state);
1127330f729Sjoerg }
1137330f729Sjoerg 
1147330f729Sjoerg // Check the jail state before any function call except chroot and chdir().
checkPreCall(const CallEvent & Call,CheckerContext & C) const1157330f729Sjoerg void ChrootChecker::checkPreCall(const CallEvent &Call,
1167330f729Sjoerg                                  CheckerContext &C) const {
1177330f729Sjoerg   // Ignore chroot and chdir.
1187330f729Sjoerg   if (Call.isCalled(Chroot) || Call.isCalled(Chdir))
1197330f729Sjoerg     return;
1207330f729Sjoerg 
1217330f729Sjoerg   // If jail state is ROOT_CHANGED, generate BugReport.
1227330f729Sjoerg   void *const* k = C.getState()->FindGDM(ChrootChecker::getTag());
1237330f729Sjoerg   if (k)
1247330f729Sjoerg     if (isRootChanged((intptr_t) *k))
1257330f729Sjoerg       if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
1267330f729Sjoerg         if (!BT_BreakJail)
1277330f729Sjoerg           BT_BreakJail.reset(new BuiltinBug(
1287330f729Sjoerg               this, "Break out of jail", "No call of chdir(\"/\") immediately "
1297330f729Sjoerg                                          "after chroot"));
1307330f729Sjoerg         C.emitReport(std::make_unique<PathSensitiveBugReport>(
1317330f729Sjoerg             *BT_BreakJail, BT_BreakJail->getDescription(), N));
1327330f729Sjoerg       }
1337330f729Sjoerg }
1347330f729Sjoerg 
registerChrootChecker(CheckerManager & mgr)1357330f729Sjoerg void ento::registerChrootChecker(CheckerManager &mgr) {
1367330f729Sjoerg   mgr.registerChecker<ChrootChecker>();
1377330f729Sjoerg }
1387330f729Sjoerg 
shouldRegisterChrootChecker(const CheckerManager & mgr)139*e038c9c4Sjoerg bool ento::shouldRegisterChrootChecker(const CheckerManager &mgr) {
1407330f729Sjoerg   return true;
1417330f729Sjoerg }
142