Home | History | Annotate | Download | only in Checkers
      1 //===- Chrootchecker.cpp -------- Basic security checks ----------*- C++ -*-==//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 //
     10 //  This file defines chroot checker, which checks improper use of chroot.
     11 //
     12 //===----------------------------------------------------------------------===//
     13 
     14 #include "ClangSACheckers.h"
     15 #include "clang/StaticAnalyzer/Core/Checker.h"
     16 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
     17 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
     18 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
     19 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
     20 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
     21 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
     22 #include "llvm/ADT/ImmutableMap.h"
     23 using namespace clang;
     24 using namespace ento;
     25 
     26 namespace {
     27 
     28 // enum value that represent the jail state
     29 enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
     30 
     31 bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
     32 //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
     33 
     34 // This checker checks improper use of chroot.
     35 // The state transition:
     36 // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
     37 //                                  |                               |
     38 //         ROOT_CHANGED<--chdir(..)--      JAIL_ENTERED<--chdir(..)--
     39 //                                  |                               |
     40 //                      bug<--foo()--          JAIL_ENTERED<--foo()--
     41 class ChrootChecker : public Checker<eval::Call, check::PreStmt<CallExpr> > {
     42   mutable IdentifierInfo *II_chroot, *II_chdir;
     43   // This bug refers to possibly break out of a chroot() jail.
     44   mutable llvm::OwningPtr<BuiltinBug> BT_BreakJail;
     45 
     46 public:
     47   ChrootChecker() : II_chroot(0), II_chdir(0) {}
     48 
     49   static void *getTag() {
     50     static int x;
     51     return &x;
     52   }
     53 
     54   bool evalCall(const CallExpr *CE, CheckerContext &C) const;
     55   void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
     56 
     57 private:
     58   void Chroot(CheckerContext &C, const CallExpr *CE) const;
     59   void Chdir(CheckerContext &C, const CallExpr *CE) const;
     60 };
     61 
     62 } // end anonymous namespace
     63 
     64 bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
     65   const ProgramState *state = C.getState();
     66   const Expr *Callee = CE->getCallee();
     67   SVal L = state->getSVal(Callee);
     68   const FunctionDecl *FD = L.getAsFunctionDecl();
     69   if (!FD)
     70     return false;
     71 
     72   ASTContext &Ctx = C.getASTContext();
     73   if (!II_chroot)
     74     II_chroot = &Ctx.Idents.get("chroot");
     75   if (!II_chdir)
     76     II_chdir = &Ctx.Idents.get("chdir");
     77 
     78   if (FD->getIdentifier() == II_chroot) {
     79     Chroot(C, CE);
     80     return true;
     81   }
     82   if (FD->getIdentifier() == II_chdir) {
     83     Chdir(C, CE);
     84     return true;
     85   }
     86 
     87   return false;
     88 }
     89 
     90 void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const {
     91   const ProgramState *state = C.getState();
     92   ProgramStateManager &Mgr = state->getStateManager();
     93 
     94   // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
     95   // the GDM.
     96   state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
     97   C.addTransition(state);
     98 }
     99 
    100 void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const {
    101   const ProgramState *state = C.getState();
    102   ProgramStateManager &Mgr = state->getStateManager();
    103 
    104   // If there are no jail state in the GDM, just return.
    105   const void *k = state->FindGDM(ChrootChecker::getTag());
    106   if (!k)
    107     return;
    108 
    109   // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
    110   const Expr *ArgExpr = CE->getArg(0);
    111   SVal ArgVal = state->getSVal(ArgExpr);
    112 
    113   if (const MemRegion *R = ArgVal.getAsRegion()) {
    114     R = R->StripCasts();
    115     if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
    116       const StringLiteral* Str = StrRegion->getStringLiteral();
    117       if (Str->getString() == "/")
    118         state = Mgr.addGDM(state, ChrootChecker::getTag(),
    119                            (void*) JAIL_ENTERED);
    120     }
    121   }
    122 
    123   C.addTransition(state);
    124 }
    125 
    126 // Check the jail state before any function call except chroot and chdir().
    127 void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
    128   const ProgramState *state = C.getState();
    129   const Expr *Callee = CE->getCallee();
    130   SVal L = state->getSVal(Callee);
    131   const FunctionDecl *FD = L.getAsFunctionDecl();
    132   if (!FD)
    133     return;
    134 
    135   ASTContext &Ctx = C.getASTContext();
    136   if (!II_chroot)
    137     II_chroot = &Ctx.Idents.get("chroot");
    138   if (!II_chdir)
    139     II_chdir = &Ctx.Idents.get("chdir");
    140 
    141   // Ingnore chroot and chdir.
    142   if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir)
    143     return;
    144 
    145   // If jail state is ROOT_CHANGED, generate BugReport.
    146   void *const* k = state->FindGDM(ChrootChecker::getTag());
    147   if (k)
    148     if (isRootChanged((intptr_t) *k))
    149       if (ExplodedNode *N = C.generateNode()) {
    150         if (!BT_BreakJail)
    151           BT_BreakJail.reset(new BuiltinBug("Break out of jail",
    152                                         "No call of chdir(\"/\") immediately "
    153                                         "after chroot"));
    154         BugReport *R = new BugReport(*BT_BreakJail,
    155                                      BT_BreakJail->getDescription(), N);
    156         C.EmitReport(R);
    157       }
    158 
    159   return;
    160 }
    161 
    162 void ento::registerChrootChecker(CheckerManager &mgr) {
    163   mgr.registerChecker<ChrootChecker>();
    164 }
    165