ChrootChecker.cpp 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. #include "ClangSACheckers.h"
  14. #include "clang/StaticAnalyzer/Core/BugReporter/BugType.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/PathSensitive/ProgramState.h"
  19. #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
  20. #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
  21. using namespace clang;
  22. using namespace ento;
  23. namespace {
  24. // enum value that represent the jail state
  25. enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
  26. bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
  27. //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
  28. // This checker checks improper use of chroot.
  29. // The state transition:
  30. // NO_CHROOT ---chroot(path)--> ROOT_CHANGED ---chdir(/) --> JAIL_ENTERED
  31. // | |
  32. // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)--
  33. // | |
  34. // bug<--foo()-- JAIL_ENTERED<--foo()--
  35. class ChrootChecker : public Checker<eval::Call, check::PreStmt<CallExpr> > {
  36. mutable IdentifierInfo *II_chroot, *II_chdir;
  37. // This bug refers to possibly break out of a chroot() jail.
  38. mutable std::unique_ptr<BuiltinBug> BT_BreakJail;
  39. public:
  40. ChrootChecker() : II_chroot(nullptr), II_chdir(nullptr) {}
  41. static void *getTag() {
  42. static int x;
  43. return &x;
  44. }
  45. bool evalCall(const CallExpr *CE, CheckerContext &C) const;
  46. void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
  47. private:
  48. void Chroot(CheckerContext &C, const CallExpr *CE) const;
  49. void Chdir(CheckerContext &C, const CallExpr *CE) const;
  50. };
  51. } // end anonymous namespace
  52. bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
  53. const FunctionDecl *FD = C.getCalleeDecl(CE);
  54. if (!FD)
  55. return false;
  56. ASTContext &Ctx = C.getASTContext();
  57. if (!II_chroot)
  58. II_chroot = &Ctx.Idents.get("chroot");
  59. if (!II_chdir)
  60. II_chdir = &Ctx.Idents.get("chdir");
  61. if (FD->getIdentifier() == II_chroot) {
  62. Chroot(C, CE);
  63. return true;
  64. }
  65. if (FD->getIdentifier() == II_chdir) {
  66. Chdir(C, CE);
  67. return true;
  68. }
  69. return false;
  70. }
  71. void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const {
  72. ProgramStateRef state = C.getState();
  73. ProgramStateManager &Mgr = state->getStateManager();
  74. // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
  75. // the GDM.
  76. state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
  77. C.addTransition(state);
  78. }
  79. void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const {
  80. ProgramStateRef state = C.getState();
  81. ProgramStateManager &Mgr = state->getStateManager();
  82. // If there are no jail state in the GDM, just return.
  83. const void *k = state->FindGDM(ChrootChecker::getTag());
  84. if (!k)
  85. return;
  86. // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
  87. const Expr *ArgExpr = CE->getArg(0);
  88. SVal ArgVal = C.getSVal(ArgExpr);
  89. if (const MemRegion *R = ArgVal.getAsRegion()) {
  90. R = R->StripCasts();
  91. if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
  92. const StringLiteral* Str = StrRegion->getStringLiteral();
  93. if (Str->getString() == "/")
  94. state = Mgr.addGDM(state, ChrootChecker::getTag(),
  95. (void*) JAIL_ENTERED);
  96. }
  97. }
  98. C.addTransition(state);
  99. }
  100. // Check the jail state before any function call except chroot and chdir().
  101. void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
  102. const FunctionDecl *FD = C.getCalleeDecl(CE);
  103. if (!FD)
  104. return;
  105. ASTContext &Ctx = C.getASTContext();
  106. if (!II_chroot)
  107. II_chroot = &Ctx.Idents.get("chroot");
  108. if (!II_chdir)
  109. II_chdir = &Ctx.Idents.get("chdir");
  110. // Ingnore chroot and chdir.
  111. if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir)
  112. return;
  113. // If jail state is ROOT_CHANGED, generate BugReport.
  114. void *const* k = C.getState()->FindGDM(ChrootChecker::getTag());
  115. if (k)
  116. if (isRootChanged((intptr_t) *k))
  117. if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
  118. if (!BT_BreakJail)
  119. BT_BreakJail.reset(new BuiltinBug(
  120. this, "Break out of jail", "No call of chdir(\"/\") immediately "
  121. "after chroot"));
  122. C.emitReport(llvm::make_unique<BugReport>(
  123. *BT_BreakJail, BT_BreakJail->getDescription(), N));
  124. }
  125. }
  126. void ento::registerChrootChecker(CheckerManager &mgr) {
  127. mgr.registerChecker<ChrootChecker>();
  128. }