diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp')
| -rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp | 141 | 
1 files changed, 141 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp new file mode 100644 index 000000000000..7a41a7b6b216 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -0,0 +1,141 @@ +//===-- ChrootChecker.cpp - chroot usage checks ---------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +//  This file defines chroot checker, which checks improper use of chroot. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" + +using namespace clang; +using namespace ento; + +namespace { + +// enum value that represent the jail state +enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED }; + +bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; } +//bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; } + +// This checker checks improper use of chroot. +// The state transition: +// NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED +//                                  |                               | +//         ROOT_CHANGED<--chdir(..)--      JAIL_ENTERED<--chdir(..)-- +//                                  |                               | +//                      bug<--foo()--          JAIL_ENTERED<--foo()-- +class ChrootChecker : public Checker<eval::Call, check::PreCall> { +  // This bug refers to possibly break out of a chroot() jail. +  mutable std::unique_ptr<BuiltinBug> BT_BreakJail; + +  const CallDescription Chroot{"chroot", 1}, Chdir{"chdir", 1}; + +public: +  ChrootChecker() {} + +  static void *getTag() { +    static int x; +    return &x; +  } + +  bool evalCall(const CallEvent &Call, CheckerContext &C) const; +  void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + +private: +  void evalChroot(const CallEvent &Call, CheckerContext &C) const; +  void evalChdir(const CallEvent &Call, CheckerContext &C) const; +}; + +} // end anonymous namespace + +bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { +  if (Call.isCalled(Chroot)) { +    evalChroot(Call, C); +    return true; +  } +  if (Call.isCalled(Chdir)) { +    evalChdir(Call, C); +    return true; +  } + +  return false; +} + +void ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const { +  ProgramStateRef state = C.getState(); +  ProgramStateManager &Mgr = state->getStateManager(); + +  // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in +  // the GDM. +  state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED); +  C.addTransition(state); +} + +void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const { +  ProgramStateRef state = C.getState(); +  ProgramStateManager &Mgr = state->getStateManager(); + +  // If there are no jail state in the GDM, just return. +  const void *k = state->FindGDM(ChrootChecker::getTag()); +  if (!k) +    return; + +  // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED. +  const Expr *ArgExpr = Call.getArgExpr(0); +  SVal ArgVal = C.getSVal(ArgExpr); + +  if (const MemRegion *R = ArgVal.getAsRegion()) { +    R = R->StripCasts(); +    if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) { +      const StringLiteral* Str = StrRegion->getStringLiteral(); +      if (Str->getString() == "/") +        state = Mgr.addGDM(state, ChrootChecker::getTag(), +                           (void*) JAIL_ENTERED); +    } +  } + +  C.addTransition(state); +} + +// Check the jail state before any function call except chroot and chdir(). +void ChrootChecker::checkPreCall(const CallEvent &Call, +                                 CheckerContext &C) const { +  // Ignore chroot and chdir. +  if (Call.isCalled(Chroot) || Call.isCalled(Chdir)) +    return; + +  // If jail state is ROOT_CHANGED, generate BugReport. +  void *const* k = C.getState()->FindGDM(ChrootChecker::getTag()); +  if (k) +    if (isRootChanged((intptr_t) *k)) +      if (ExplodedNode *N = C.generateNonFatalErrorNode()) { +        if (!BT_BreakJail) +          BT_BreakJail.reset(new BuiltinBug( +              this, "Break out of jail", "No call of chdir(\"/\") immediately " +                                         "after chroot")); +        C.emitReport(std::make_unique<PathSensitiveBugReport>( +            *BT_BreakJail, BT_BreakJail->getDescription(), N)); +      } +} + +void ento::registerChrootChecker(CheckerManager &mgr) { +  mgr.registerChecker<ChrootChecker>(); +} + +bool ento::shouldRegisterChrootChecker(const LangOptions &LO) { +  return true; +}  | 
