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