Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,9 @@ Moved checkers
original checkers were implemented only using AST matching and make more
sense as a single clang-tidy check.

- The checker ``alpha.unix.Chroot`` was modernized, improved and moved from
alpha to a main Unix family checker.

.. _release-notes-sanitizers:

Sanitizers
Expand Down
30 changes: 15 additions & 15 deletions clang/docs/analyzer/checkers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1750,6 +1750,21 @@ Critical section handling functions modeled by this checker:
}
}
.. _unix-Chroot:
unix.Chroot (C)
"""""""""""""""""""""
Check improper use of chroot.
.. code-block:: c
void f();
void test() {
chroot("/usr/local");
f(); // warn: no call of chdir("/") immediately after chroot
}
.. _unix-Errno:
unix.Errno (C)
Expand Down Expand Up @@ -3275,21 +3290,6 @@ SEI CERT checkers which tries to find errors based on their `C coding rules <htt
alpha.unix
^^^^^^^^^^
.. _alpha-unix-Chroot:
alpha.unix.Chroot (C)
"""""""""""""""""""""
Check improper use of chroot.
.. code-block:: c
void f();
void test() {
chroot("/usr/local");
f(); // warn: no call of chdir("/") immediately after chroot
}
.. _alpha-unix-PthreadLock:
alpha.unix.PthreadLock (C)
Expand Down
8 changes: 4 additions & 4 deletions clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
Original file line number Diff line number Diff line change
Expand Up @@ -598,14 +598,14 @@ def VforkChecker : Checker<"Vfork">,
HelpText<"Check for proper usage of vfork">,
Documentation<HasDocumentation>;

} // end "unix"

let ParentPackage = UnixAlpha in {

def ChrootChecker : Checker<"Chroot">,
HelpText<"Check improper use of chroot">,
Documentation<HasDocumentation>;

} // end "unix"

let ParentPackage = UnixAlpha in {

def PthreadLockChecker : Checker<"PthreadLock">,
HelpText<"Simple lock -> unlock checker">,
Dependencies<[PthreadLockBase]>,
Expand Down
149 changes: 117 additions & 32 deletions clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
//
//===----------------------------------------------------------------------===//

#include "clang/AST/ASTContext.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
Expand All @@ -24,21 +26,30 @@
using namespace clang;
using namespace ento;

namespace {

// enum value that represent the jail state
enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
enum ChrootKind { NO_CHROOT, ROOT_CHANGED, ROOT_CHANGE_FAILED, JAIL_ENTERED };

bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
//bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
// Track chroot state changes for success, failure, state change
// and "jail"
REGISTER_TRAIT_WITH_PROGRAMSTATE(ChrootState, ChrootKind)

// Track the call expression to chroot for accurate
// warning messages
REGISTER_TRAIT_WITH_PROGRAMSTATE(ChrootCall, const Expr *)

namespace {

// This checker checks improper use of chroot.
// The state transition:
// The state transitions
//
// -> ROOT_CHANGE_FAILED
// |
// 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.
const BugType BT_BreakJail{this, "Break out of jail"};
Expand All @@ -49,20 +60,17 @@ class ChrootChecker : public Checker<eval::Call, check::PreCall> {
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
/// Searches for the ExplodedNode where chroot was called.
static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N,
CheckerContext &C);
};

bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
if (Chroot.matches(Call)) {
Expand All @@ -80,19 +88,53 @@ bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
void ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const {
ProgramStateRef state = C.getState();
ProgramStateManager &Mgr = state->getStateManager();
const TargetInfo &TI = C.getASTContext().getTargetInfo();
SValBuilder &SVB = C.getSValBuilder();
BasicValueFactory &BVF = SVB.getBasicValueFactory();
ConstraintManager &CM = Mgr.getConstraintManager();

// 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);
const QualType sIntTy = C.getASTContext().getIntTypeForBitwidth(
/*DestWidth=*/TI.getIntWidth(), /*Signed=*/true);

const Expr *ChrootCE = Call.getOriginExpr();
if (!ChrootCE)
return;
const auto *CE = cast<CallExpr>(Call.getOriginExpr());

const LocationContext *LCtx = C.getLocationContext();
NonLoc RetVal =
C.getSValBuilder()
.conjureSymbolVal(nullptr, ChrootCE, LCtx, sIntTy, C.blockCount())
.castAs<NonLoc>();

ProgramStateRef StateChrootFailed, StateChrootSuccess;
std::tie(StateChrootFailed, StateChrootSuccess) = state->assume(RetVal);

const llvm::APSInt &Zero = BVF.getValue(0, sIntTy);
const llvm::APSInt &Minus1 = BVF.getValue(-1, sIntTy);

if (StateChrootFailed) {
StateChrootFailed = CM.assumeInclusiveRange(StateChrootFailed, RetVal,
Minus1, Minus1, true);
StateChrootFailed = StateChrootFailed->set<ChrootState>(ROOT_CHANGE_FAILED);
StateChrootFailed = StateChrootFailed->set<ChrootCall>(ChrootCE);
C.addTransition(StateChrootFailed->BindExpr(CE, LCtx, RetVal));
}

if (StateChrootSuccess) {
StateChrootSuccess =
CM.assumeInclusiveRange(StateChrootSuccess, RetVal, Zero, Zero, true);
StateChrootSuccess = StateChrootSuccess->set<ChrootState>(ROOT_CHANGED);
StateChrootSuccess = StateChrootSuccess->set<ChrootCall>(ChrootCE);
C.addTransition(StateChrootSuccess->BindExpr(CE, LCtx, RetVal));
}
}

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 there are no jail state, just return.
const ChrootKind k = C.getState()->get<ChrootState>();
if (!k)
return;

Expand All @@ -104,15 +146,35 @@ void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const {
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);
if (Str->getString() == "/") {
state = state->set<ChrootState>(JAIL_ENTERED);
}
}
}

C.addTransition(state);
}

const ExplodedNode *ChrootChecker::getAcquisitionSite(const ExplodedNode *N,
CheckerContext &C) {
ProgramStateRef State = N->getState();
// When bug type is resource leak, exploded node N may not have state info
// for leaked file descriptor, but predecessor should have it.
if (!State->get<ChrootCall>())
N = N->getFirstPred();

const ExplodedNode *Pred = N;
while (N) {
State = N->getState();
if (!State->get<ChrootCall>())
return Pred;
Pred = N;
N = N->getFirstPred();
}

return nullptr;
}

// Check the jail state before any function call except chroot and chdir().
void ChrootChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
Expand All @@ -121,17 +183,40 @@ void ChrootChecker::checkPreCall(const CallEvent &Call,
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()) {
constexpr llvm::StringLiteral Msg =
"No call of chdir(\"/\") immediately after chroot";
C.emitReport(
std::make_unique<PathSensitiveBugReport>(BT_BreakJail, Msg, N));
}
const ChrootKind k = C.getState()->get<ChrootState>();
if (k == ROOT_CHANGED) {
ExplodedNode *Err =
C.generateNonFatalErrorNode(C.getState(), C.getPredecessor());
if (!Err)
return;
const Expr *ChrootExpr = C.getState()->get<ChrootCall>();

const ExplodedNode *ChrootCallNode = getAcquisitionSite(Err, C);
assert(ChrootCallNode && "Could not find place of stream opening.");

PathDiagnosticLocation LocUsedForUniqueing;
if (const Stmt *ChrootStmt = ChrootCallNode->getStmtForDiagnostics())
LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
ChrootStmt, C.getSourceManager(),
ChrootCallNode->getLocationContext());

std::unique_ptr<PathSensitiveBugReport> R =
std::make_unique<PathSensitiveBugReport>(
BT_BreakJail, "No call of chdir(\"/\") immediately after chroot",
Err, LocUsedForUniqueing,
ChrootCallNode->getLocationContext()->getDecl());

R->addNote("chroot called here",
PathDiagnosticLocation::create(ChrootCallNode->getLocation(),
C.getSourceManager()),
{ChrootExpr->getSourceRange()});

C.emitReport(std::move(R));
}
}

} // namespace

void ento::registerChrootChecker(CheckerManager &mgr) {
mgr.registerChecker<ChrootChecker>();
}
Expand Down
1 change: 1 addition & 0 deletions clang/test/Analysis/analyzer-enabled-checkers.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
// CHECK-NEXT: security.insecureAPI.vfork
// CHECK-NEXT: unix.API
// CHECK-NEXT: unix.BlockInCriticalSection
// CHECK-NEXT: unix.Chroot
// CHECK-NEXT: unix.cstring.CStringModeling
// CHECK-NEXT: unix.DynamicMemoryModeling
// CHECK-NEXT: unix.Errno
Expand Down
36 changes: 33 additions & 3 deletions clang/test/Analysis/chroot.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.unix.Chroot -verify %s
// RUN: %clang_analyze_cc1 -analyzer-checker=unix.Chroot -verify %s

extern int chroot(const char* path);
extern int chdir(const char* path);
Expand All @@ -7,7 +7,7 @@ void foo(void) {
}

void f1(void) {
chroot("/usr/local"); // root changed.
chroot("/usr/local"); // expected-note {{chroot called here}}
foo(); // expected-warning {{No call of chdir("/") immediately after chroot}}
}

Expand All @@ -18,7 +18,37 @@ void f2(void) {
}

void f3(void) {
chroot("/usr/local"); // root changed.
chroot("/usr/local"); // expected-note {{chroot called here}}
chdir("../"); // change working directory, still out of jail.
foo(); // expected-warning {{No call of chdir("/") immediately after chroot}}
}

void f4(void) {
if (chroot("/usr/local") == 0) {
chdir("../"); // change working directory, still out of jail.
}
}

void f5(void) {
int v = chroot("/usr/local");
if (v == -1) {
foo(); // no warning, chroot failed
chdir("../"); // change working directory, still out of jail.
}
}

void f6(void) {
if (chroot("/usr/local") == -1) {
chdir("../"); // change working directory, still out of jail.
}
}

void f7(void) {
int v = chroot("/usr/local"); // expected-note {{chroot called here}}
if (v == -1) {
foo(); // no warning, chroot failed
chdir("../"); // change working directory, still out of jail.
} else {
foo(); // expected-warning {{No call of chdir("/") immediately after chroot}}
}
}
9 changes: 0 additions & 9 deletions clang/test/Analysis/show-checker-list.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@
// RUN: -analyzer-checker-help-developer \
// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-STABLE-ALPHA-DEVELOPER

// CHECK-STABLE-NOT: alpha.unix.Chroot
// CHECK-DEVELOPER-NOT: alpha.unix.Chroot
// CHECK-ALPHA: alpha.unix.Chroot

// Note that alpha.cplusplus.IteratorModeling is not only an alpha, but also a
// hidden checker. In this case, we'd only like to see it in the developer list.
// CHECK-ALPHA-NOT: alpha.cplusplus.IteratorModeling
Expand All @@ -42,10 +38,6 @@
// CHECK-ALPHA-NOT: debug.ConfigDumper


// CHECK-STABLE-ALPHA: alpha.unix.Chroot
// CHECK-DEVELOPER-ALPHA: alpha.unix.Chroot
// CHECK-STABLE-DEVELOPER-NOT: alpha.unix.Chroot

// CHECK-STABLE-ALPHA: core.DivideZero
// CHECK-DEVELOPER-ALPHA-NOT: core.DivideZero
// CHECK-STABLE-DEVELOPER: core.DivideZero
Expand All @@ -55,6 +47,5 @@
// CHECK-STABLE-DEVELOPER: debug.ConfigDumper


// CHECK-STABLE-ALPHA-DEVELOPER: alpha.unix.Chroot
// CHECK-STABLE-ALPHA-DEVELOPER: core.DivideZero
// CHECK-STABLE-ALPHA-DEVELOPER: debug.ConfigDumper
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
// CHECK-NEXT: security.insecureAPI.vfork
// CHECK-NEXT: unix.API
// CHECK-NEXT: unix.BlockInCriticalSection
// CHECK-NEXT: unix.Chroot
// CHECK-NEXT: unix.cstring.CStringModeling
// CHECK-NEXT: unix.DynamicMemoryModeling
// CHECK-NEXT: unix.Errno
Expand Down