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/BugReporter/BugType.h"
     16 #include "clang/StaticAnalyzer/Core/Checker.h"
     17 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
     18 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.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 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 FunctionDecl *FD = C.getCalleeDecl(CE);
     66   if (!FD)
     67     return false;
     68 
     69   ASTContext &Ctx = C.getASTContext();
     70   if (!II_chroot)
     71     II_chroot = &Ctx.Idents.get("chroot");
     72   if (!II_chdir)
     73     II_chdir = &Ctx.Idents.get("chdir");
     74 
     75   if (FD->getIdentifier() == II_chroot) {
     76     Chroot(C, CE);
     77     return true;
     78   }
     79   if (FD->getIdentifier() == II_chdir) {
     80     Chdir(C, CE);
     81     return true;
     82   }
     83 
     84   return false;
     85 }
     86 
     87 void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const {
     88   ProgramStateRef state = C.getState();
     89   ProgramStateManager &Mgr = state->getStateManager();
     90 
     91   // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
     92   // the GDM.
     93   state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
     94   C.addTransition(state);
     95 }
     96 
     97 void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const {
     98   ProgramStateRef state = C.getState();
     99   ProgramStateManager &Mgr = state->getStateManager();
    100 
    101   // If there are no jail state in the GDM, just return.
    102   const void *k = state->FindGDM(ChrootChecker::getTag());
    103   if (!k)
    104     return;
    105 
    106   // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
    107   const Expr *ArgExpr = CE->getArg(0);
    108   SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext());
    109 
    110   if (const MemRegion *R = ArgVal.getAsRegion()) {
    111     R = R->StripCasts();
    112     if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
    113       const StringLiteral* Str = StrRegion->getStringLiteral();
    114       if (Str->getString() == "/")
    115         state = Mgr.addGDM(state, ChrootChecker::getTag(),
    116                            (void*) JAIL_ENTERED);
    117     }
    118   }
    119 
    120   C.addTransition(state);
    121 }
    122 
    123 // Check the jail state before any function call except chroot and chdir().
    124 void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
    125   const FunctionDecl *FD = C.getCalleeDecl(CE);
    126   if (!FD)
    127     return;
    128 
    129   ASTContext &Ctx = C.getASTContext();
    130   if (!II_chroot)
    131     II_chroot = &Ctx.Idents.get("chroot");
    132   if (!II_chdir)
    133     II_chdir = &Ctx.Idents.get("chdir");
    134 
    135   // Ingnore chroot and chdir.
    136   if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir)
    137     return;
    138 
    139   // If jail state is ROOT_CHANGED, generate BugReport.
    140   void *const* k = C.getState()->FindGDM(ChrootChecker::getTag());
    141   if (k)
    142     if (isRootChanged((intptr_t) *k))
    143       if (ExplodedNode *N = C.addTransition()) {
    144         if (!BT_BreakJail)
    145           BT_BreakJail.reset(new BuiltinBug("Break out of jail",
    146                                         "No call of chdir(\"/\") immediately "
    147                                         "after chroot"));
    148         BugReport *R = new BugReport(*BT_BreakJail,
    149                                      BT_BreakJail->getDescription(), N);
    150         C.emitReport(R);
    151       }
    152 
    153   return;
    154 }
    155 
    156 void ento::registerChrootChecker(CheckerManager &mgr) {
    157   mgr.registerChecker<ChrootChecker>();
    158 }
    159