forked from thezdi/PoC
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds MySQL/codeql/ and MySQL/clang_checker
MindShaRE blog part 3 on MySQL Cluster Taint Analysis https://www.zerodayinitiative.com/blog/2022/2/22/clang-checkers-and-codeql-queries-for-detecting-untrusted-pointer-derefs-and-tainted-loop-conditions
- Loading branch information
ZDI
committed
Feb 23, 2022
1 parent
ec6ae36
commit 33100cb
Showing
4 changed files
with
396 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
#include "Taint.h" | ||
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" | ||
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" | ||
#include "clang/StaticAnalyzer/Core/Checker.h" | ||
#include "clang/StaticAnalyzer/Core/CheckerManager.h" | ||
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" | ||
#include "clang/AST/ParentMap.h" | ||
|
||
using namespace clang; | ||
using namespace ento; | ||
using namespace taint; | ||
|
||
namespace { | ||
|
||
class TaintedLoopChecker : public Checker<check::BranchCondition> { | ||
|
||
mutable std::unique_ptr<BugType> BT; | ||
|
||
public: | ||
void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const; | ||
bool IsLoop(const Stmt *Stmt, CheckerContext &Ctx) const; | ||
void CheckLoopCondition(const Stmt *stmt, CheckerContext &Ctx) const; | ||
bool CheckTaintedStmt(const Stmt *stmt, CheckerContext &Ctx) const; | ||
bool isArgUnConstrained(SVal Arg, SValBuilder &builder, ProgramStateRef state) const; | ||
void reportBug(CheckerContext &C) const; | ||
}; | ||
} | ||
|
||
void TaintedLoopChecker::reportBug(CheckerContext &Ctx) const { | ||
|
||
if (!BT) | ||
BT.reset(new BuiltinBug(this, "Tainted Loop Condition")); | ||
|
||
ExplodedNode *N = Ctx.generateNonFatalErrorNode(Ctx.getState()); | ||
|
||
if (!N) | ||
return; | ||
|
||
auto report = std::make_unique<PathSensitiveBugReport>(*BT, "Tainted Branch Condition in Loop Construct", N); | ||
|
||
Ctx.emitReport(std::move(report)); | ||
} | ||
|
||
// Following code is from isArgUnConstrained by Andrew Ruef written for analyzing OpenSSL bug | ||
// https://blog.trailofbits.com/2014/04/27/using-static-analysis-and-clang-to-find-heartbleed | ||
|
||
bool TaintedLoopChecker::isArgUnConstrained(SVal Arg, SValBuilder &builder, ProgramStateRef state) const { | ||
|
||
bool result = false; | ||
|
||
llvm::APInt V(32, 0x10000); | ||
SVal Val = builder.makeIntVal(V, false); | ||
|
||
Optional<NonLoc> NLVal = Val.getAs<NonLoc>(); | ||
|
||
if (!NLVal) { | ||
return result; | ||
} | ||
|
||
Optional<NonLoc> NLArg = Arg.getAs<NonLoc>(); | ||
|
||
if (!NLArg) { | ||
return result; | ||
} | ||
|
||
SVal cmprLT = builder.evalBinOp(state, | ||
BO_GT, | ||
*NLArg, | ||
*NLVal, | ||
builder.getConditionType()); | ||
|
||
Optional<DefinedOrUnknownSVal> NLcmprLT = cmprLT.getAs<DefinedOrUnknownSVal>(); | ||
|
||
if (!NLcmprLT) { | ||
return result; | ||
} | ||
|
||
std::pair<ProgramStateRef,ProgramStateRef> p = | ||
state->assume(*NLcmprLT); | ||
|
||
ProgramStateRef trueState = p.first; | ||
|
||
if (trueState) { | ||
result = true; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
|
||
bool TaintedLoopChecker::IsLoop(const Stmt *stmt, CheckerContext &Ctx) const { | ||
|
||
const ParentMap &PM = Ctx.getLocationContext()->getParentMap(); | ||
const Stmt *current_stmt = stmt; | ||
|
||
while (PM.hasParent(current_stmt)) { | ||
|
||
unsigned int StmtClass = current_stmt->getStmtClass(); | ||
current_stmt = PM.getParent(current_stmt); | ||
|
||
if (StmtClass == Stmt::CompoundStmtClass) | ||
return false; | ||
else if (StmtClass == Stmt::WhileStmtClass) | ||
return true; | ||
else if (StmtClass == Stmt::DoStmtClass) | ||
return true; | ||
else if (StmtClass == Stmt::ForStmtClass) | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
bool TaintedLoopChecker::CheckTaintedStmt(const Stmt *stmt, CheckerContext &Ctx) const { | ||
|
||
ProgramStateRef state = Ctx.getState(); | ||
|
||
if (isTainted(state, Ctx.getSVal(stmt))) | ||
return true; | ||
|
||
Stmt::const_child_iterator child = stmt->child_begin(); | ||
|
||
while (child != stmt->child_end()) { | ||
|
||
if (isTainted(state, Ctx.getSVal(*child))) { | ||
return true; | ||
} | ||
|
||
++child; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
void TaintedLoopChecker::CheckLoopCondition(const Stmt *Condition, CheckerContext &Ctx) const { | ||
|
||
ProgramStateRef state = Ctx.getState(); | ||
SValBuilder &svalBuilder = Ctx.getSValBuilder(); | ||
SVal LoopVarVal; | ||
|
||
if (const BinaryOperator *BinOp = dyn_cast<BinaryOperator>(Condition)) { | ||
|
||
if (BinOp->isComparisonOp()) { | ||
|
||
const Expr *RHS = BinOp->getRHS(); | ||
const Expr *LHS = BinOp->getLHS(); | ||
|
||
SVal RHSVal = Ctx.getSVal(RHS); | ||
|
||
if (RHSVal.isZeroConstant()) | ||
Condition = LHS; | ||
else | ||
Condition = RHS; | ||
|
||
if (TaintedLoopChecker::CheckTaintedStmt(Condition, Ctx)) { | ||
|
||
LoopVarVal = Ctx.getSVal(Condition); | ||
|
||
if (TaintedLoopChecker::isArgUnConstrained(LoopVarVal, svalBuilder, state)) | ||
reportBug(Ctx); | ||
} | ||
} | ||
|
||
} else if (TaintedLoopChecker::CheckTaintedStmt(Condition, Ctx)) { | ||
|
||
// handle possible implicit boolean conversions | ||
if (dyn_cast<Expr>(Condition)->isKnownToHaveBooleanValue()) { | ||
|
||
if (const ImplicitCastExpr *IE = dyn_cast<ImplicitCastExpr>(Condition)) { | ||
Condition = IE->getSubExpr(); | ||
} | ||
} | ||
|
||
if (const UnaryOperator *UnOp = dyn_cast<UnaryOperator>(Condition)) { | ||
Condition = UnOp->getSubExpr(); | ||
} | ||
|
||
LoopVarVal = Ctx.getSVal(Condition); | ||
|
||
if (TaintedLoopChecker::isArgUnConstrained(LoopVarVal, svalBuilder, state)) | ||
reportBug(Ctx); | ||
} | ||
} | ||
|
||
void TaintedLoopChecker::checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const { | ||
|
||
if (IsLoop(Condition, Ctx)) | ||
CheckLoopCondition(Condition, Ctx); | ||
} | ||
|
||
void ento::registerTaintedLoopChecker(CheckerManager &mgr) { | ||
mgr.registerChecker<TaintedLoopChecker>(); | ||
} | ||
|
||
bool ento::shouldRegisterTaintedLoopChecker(const CheckerManager &mgr) { | ||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
#include "Taint.h" | ||
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" | ||
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" | ||
#include "clang/StaticAnalyzer/Core/Checker.h" | ||
#include "clang/StaticAnalyzer/Core/CheckerManager.h" | ||
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" | ||
#include "clang/AST/ParentMap.h" | ||
|
||
using namespace clang; | ||
using namespace ento; | ||
using namespace taint; | ||
|
||
namespace { | ||
|
||
class TaintedPointerChecker : public Checker<check::Location> { | ||
|
||
mutable std::unique_ptr<BugType> BT; | ||
|
||
public: | ||
void checkLocation(SVal L, bool IsLoad, const Stmt *S, CheckerContext &Ctx) const; | ||
void reportBug(CheckerContext &C) const; | ||
}; | ||
} | ||
|
||
void TaintedPointerChecker::reportBug(CheckerContext &Ctx) const { | ||
|
||
if (!BT) | ||
BT.reset(new BuiltinBug(this, "Tainted Pointer Load")); | ||
|
||
ExplodedNode *N = Ctx.generateNonFatalErrorNode(Ctx.getState()); | ||
|
||
if (!N) | ||
return; | ||
|
||
auto report = std::make_unique<PathSensitiveBugReport>(*BT, "Pointer Loaded From Tainted Source", N); | ||
|
||
Ctx.emitReport(std::move(report)); | ||
} | ||
|
||
void TaintedPointerChecker::checkLocation(SVal L, bool IsLoad, const Stmt *stmt, CheckerContext &Ctx) const { | ||
|
||
if (!IsLoad) | ||
return; | ||
|
||
if (isTainted(Ctx.getState(), L)) { | ||
const Expr *expr = dyn_cast<Expr>(stmt)->IgnoreImplicitAsWritten(); | ||
if (expr && expr->getType()->isPointerType()) | ||
reportBug(Ctx); | ||
} | ||
} | ||
|
||
void ento::registerTaintedPointerChecker(CheckerManager &mgr) { | ||
mgr.registerChecker<TaintedPointerChecker>(); | ||
} | ||
|
||
bool ento::shouldRegisterTaintedPointerChecker(const CheckerManager &mgr) { | ||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* @id cpp/untrusted-pointer-dereference | ||
* @kind path-problem | ||
* @problem.severity error | ||
*/ | ||
|
||
import cpp | ||
import DataFlow::PathGraph | ||
import semmle.code.cpp.dataflow.TaintTracking | ||
import semmle.code.cpp.controlflow.Guards | ||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering | ||
import semmle.code.cpp.ir.IR | ||
import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasedSSA | ||
|
||
//ref https://msrc-blog.microsoft.com/2019/03/19/vulnerability-hunting-with-semmle-ql-part-2/ | ||
|
||
class SystemCfg extends TaintTracking::Configuration { | ||
SystemCfg() { this = "SystemCfg" } | ||
|
||
override predicate isSource(DataFlow::Node node) { | ||
exists (FieldAccess va | | ||
node.asExpr() = va | ||
and va.getTarget().hasName("theData") | ||
) | ||
} | ||
|
||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { | ||
exists(Expr e, FieldAccess fa | | ||
pred.asExpr() = e | ||
and fa.getQualifier*() = e | ||
and succ.asExpr() = fa | ||
) | ||
} | ||
|
||
override predicate isSink(DataFlow::Node node) { | ||
exists(Instruction ir, string var, VariableAccess va | | ||
ir instanceof LoadInstruction | ||
and ir.getResultIRType() instanceof IRAddressType | ||
and ir.(LoadInstruction).getSourceValueOperand().isDefinitionInexact() | ||
and node.asExpr() = ir.getAST().(Expr) | ||
// Check type info of virtual variable to filter results. Very specific to MySQL Cluster, rewrite as necessary | ||
and va.getEnclosingFunction().getName() = ir.getEnclosingFunction().getName() | ||
and var = getOperandMemoryLocation(ir.(LoadInstruction).getSourceValueOperand()).getVirtualVariable().toString().replaceAll("*", "") | ||
and va.getTarget().toString() = var | ||
and va.getTarget().getType().toString().matches("%Signal%") | ||
) | ||
} | ||
|
||
override predicate isSanitizer(DataFlow::Node node) { | ||
exists(MacroInvocation mi | | ||
mi.getMacroName().matches("%ptrCheckGuard%") | ||
and mi.getExpr() = node.asExpr() | ||
) or | ||
|
||
exists(MacroInvocation mi | | ||
mi.getMacroName().matches("%arrGuard%") | ||
and mi.getExpr() = node.asExpr() | ||
) or | ||
|
||
exists( IfStmt aif, RelationalOperation rop | | ||
node.asExpr().(VariableAccess).getTarget().getAnAccess() = aif.getControllingExpr().getAChild*() | ||
and aif.getASuccessor+() = node.asExpr() | ||
and not ( node.asExpr() = aif.getControllingExpr().getAChild*() ) | ||
and rop = aif.getControllingExpr().getAChild*() | ||
and not rop.getRightOperand().getValue().toInt() = 0 // ignore check against 0 | ||
) | ||
} | ||
} | ||
|
||
from DataFlow::PathNode sink, DataFlow::PathNode source, SystemCfg cfg | ||
where cfg.hasFlowPath(source, sink) | ||
select sink.getNode(), source, sink, sink.getNode().getLocation().toString() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/** | ||
* @id cpp/untrusted-loop | ||
* @kind path-problem | ||
* @problem.severity warning | ||
*/ | ||
|
||
import cpp | ||
import DataFlow::PathGraph | ||
import semmle.code.cpp.dataflow.TaintTracking | ||
import semmle.code.cpp.controlflow.Guards | ||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering | ||
|
||
//ref https://msrc-blog.microsoft.com/2019/03/19/vulnerability-hunting-with-semmle-ql-part-2/ | ||
|
||
class SystemCfg extends TaintTracking::Configuration { | ||
SystemCfg() { this = "SystemCfg" } | ||
|
||
override predicate isSource(DataFlow::Node node) { | ||
exists (FieldAccess va | | ||
node.asExpr() = va | ||
and va.getTarget().hasName("theData") | ||
) | ||
} | ||
|
||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { | ||
exists(Expr e, FieldAccess fa | | ||
pred.asExpr() = e | ||
and fa.getQualifier*() = e | ||
and succ.asExpr() = fa | ||
) | ||
} | ||
|
||
override predicate isSink(DataFlow::Node node) { | ||
exists(Loop lp, Expr cexpr | | ||
cexpr = lp.getControllingExpr() and ( | ||
( | ||
cexpr.(ComparisonOperation).getRightOperand().getValue().toInt() = 0 | ||
and node.asExpr() = cexpr.(ComparisonOperation).getLeftOperand() | ||
) | ||
or node.asExpr() = cexpr.(ComparisonOperation).getRightOperand() | ||
or node.asExpr() = cexpr.(UnaryOperation).getOperand() | ||
) | ||
) | ||
} | ||
|
||
override predicate isSanitizer(DataFlow::Node node) { | ||
exists(MacroInvocation mi | | ||
mi.getMacroName().matches("%ptrCheckGuard%") | ||
and mi.getExpr() = node.asExpr() | ||
) or | ||
|
||
exists(MacroInvocation mi | | ||
mi.getMacroName().matches("%arrGuard%") | ||
and mi.getExpr() = node.asExpr() | ||
) or | ||
|
||
exists( IfStmt aif, RelationalOperation rop | | ||
node.asExpr().(VariableAccess).getTarget().getAnAccess() = aif.getControllingExpr().getAChild*() | ||
and aif.getASuccessor+() = node.asExpr() | ||
and not ( node.asExpr() = aif.getControllingExpr().getAChild*() ) | ||
and rop = aif.getControllingExpr().getAChild*() | ||
and not rop.getRightOperand().getValue().toInt() = 0 // ignore check against 0 | ||
) | ||
} | ||
} | ||
|
||
from DataFlow::PathNode sink, DataFlow::PathNode source, SystemCfg cfg | ||
where cfg.hasFlowPath(source, sink) | ||
select sink.getNode(), source, sink, sink.getNode().getLocation().toString() |