clang API Documentation

ChrootChecker.cpp
Go to the documentation of this file.
00001 //===- Chrootchecker.cpp -------- Basic security checks ----------*- C++ -*-==//
00002 //
00003 //                     The LLVM Compiler Infrastructure
00004 //
00005 // This file is distributed under the University of Illinois Open Source
00006 // License. See LICENSE.TXT for details.
00007 //
00008 //===----------------------------------------------------------------------===//
00009 //
00010 //  This file defines chroot checker, which checks improper use of chroot.
00011 //
00012 //===----------------------------------------------------------------------===//
00013 
00014 #include "ClangSACheckers.h"
00015 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
00016 #include "clang/StaticAnalyzer/Core/Checker.h"
00017 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
00018 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
00019 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
00020 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
00021 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
00022 #include "llvm/ADT/ImmutableMap.h"
00023 using namespace clang;
00024 using namespace ento;
00025 
00026 namespace {
00027 
00028 // enum value that represent the jail state
00029 enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
00030   
00031 bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
00032 //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
00033 
00034 // This checker checks improper use of chroot.
00035 // The state transition:
00036 // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
00037 //                                  |                               |
00038 //         ROOT_CHANGED<--chdir(..)--      JAIL_ENTERED<--chdir(..)--
00039 //                                  |                               |
00040 //                      bug<--foo()--          JAIL_ENTERED<--foo()--
00041 class ChrootChecker : public Checker<eval::Call, check::PreStmt<CallExpr> > {
00042   mutable IdentifierInfo *II_chroot, *II_chdir;
00043   // This bug refers to possibly break out of a chroot() jail.
00044   mutable std::unique_ptr<BuiltinBug> BT_BreakJail;
00045 
00046 public:
00047   ChrootChecker() : II_chroot(nullptr), II_chdir(nullptr) {}
00048 
00049   static void *getTag() {
00050     static int x;
00051     return &x;
00052   }
00053   
00054   bool evalCall(const CallExpr *CE, CheckerContext &C) const;
00055   void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
00056 
00057 private:
00058   void Chroot(CheckerContext &C, const CallExpr *CE) const;
00059   void Chdir(CheckerContext &C, const CallExpr *CE) const;
00060 };
00061 
00062 } // end anonymous namespace
00063 
00064 bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
00065   const FunctionDecl *FD = C.getCalleeDecl(CE);
00066   if (!FD)
00067     return false;
00068 
00069   ASTContext &Ctx = C.getASTContext();
00070   if (!II_chroot)
00071     II_chroot = &Ctx.Idents.get("chroot");
00072   if (!II_chdir)
00073     II_chdir = &Ctx.Idents.get("chdir");
00074 
00075   if (FD->getIdentifier() == II_chroot) {
00076     Chroot(C, CE);
00077     return true;
00078   }
00079   if (FD->getIdentifier() == II_chdir) {
00080     Chdir(C, CE);
00081     return true;
00082   }
00083 
00084   return false;
00085 }
00086 
00087 void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const {
00088   ProgramStateRef state = C.getState();
00089   ProgramStateManager &Mgr = state->getStateManager();
00090   
00091   // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in 
00092   // the GDM.
00093   state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
00094   C.addTransition(state);
00095 }
00096 
00097 void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const {
00098   ProgramStateRef state = C.getState();
00099   ProgramStateManager &Mgr = state->getStateManager();
00100 
00101   // If there are no jail state in the GDM, just return.
00102   const void *k = state->FindGDM(ChrootChecker::getTag());
00103   if (!k)
00104     return;
00105 
00106   // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
00107   const Expr *ArgExpr = CE->getArg(0);
00108   SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext());
00109   
00110   if (const MemRegion *R = ArgVal.getAsRegion()) {
00111     R = R->StripCasts();
00112     if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
00113       const StringLiteral* Str = StrRegion->getStringLiteral();
00114       if (Str->getString() == "/")
00115         state = Mgr.addGDM(state, ChrootChecker::getTag(),
00116                            (void*) JAIL_ENTERED);
00117     }
00118   }
00119 
00120   C.addTransition(state);
00121 }
00122 
00123 // Check the jail state before any function call except chroot and chdir().
00124 void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
00125   const FunctionDecl *FD = C.getCalleeDecl(CE);
00126   if (!FD)
00127     return;
00128 
00129   ASTContext &Ctx = C.getASTContext();
00130   if (!II_chroot)
00131     II_chroot = &Ctx.Idents.get("chroot");
00132   if (!II_chdir)
00133     II_chdir = &Ctx.Idents.get("chdir");
00134 
00135   // Ingnore chroot and chdir.
00136   if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir)
00137     return;
00138   
00139   // If jail state is ROOT_CHANGED, generate BugReport.
00140   void *const* k = C.getState()->FindGDM(ChrootChecker::getTag());
00141   if (k)
00142     if (isRootChanged((intptr_t) *k))
00143       if (ExplodedNode *N = C.addTransition()) {
00144         if (!BT_BreakJail)
00145           BT_BreakJail.reset(new BuiltinBug(
00146               this, "Break out of jail", "No call of chdir(\"/\") immediately "
00147                                          "after chroot"));
00148         BugReport *R = new BugReport(*BT_BreakJail, 
00149                                      BT_BreakJail->getDescription(), N);
00150         C.emitReport(R);
00151       }
00152   
00153   return;
00154 }
00155 
00156 void ento::registerChrootChecker(CheckerManager &mgr) {
00157   mgr.registerChecker<ChrootChecker>();
00158 }