From 33100cb80d9a41ff7d557876ce27c6a86e7b5eb0 Mon Sep 17 00:00:00 2001 From: ZDI Date: Wed, 23 Feb 2022 10:41:24 -0600 Subject: [PATCH] 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 --- MySQL/clang_checker/TaintedLoopChecker.cpp | 197 ++++++++++++++++++ MySQL/clang_checker/TaintedPointerChecker.cpp | 58 ++++++ MySQL/codeql/iruntrusted.ql | 72 +++++++ MySQL/codeql/loop.ql | 69 ++++++ 4 files changed, 396 insertions(+) create mode 100644 MySQL/clang_checker/TaintedLoopChecker.cpp create mode 100644 MySQL/clang_checker/TaintedPointerChecker.cpp create mode 100644 MySQL/codeql/iruntrusted.ql create mode 100644 MySQL/codeql/loop.ql diff --git a/MySQL/clang_checker/TaintedLoopChecker.cpp b/MySQL/clang_checker/TaintedLoopChecker.cpp new file mode 100644 index 0000000..da2df42 --- /dev/null +++ b/MySQL/clang_checker/TaintedLoopChecker.cpp @@ -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 { + + mutable std::unique_ptr 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(*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 NLVal = Val.getAs(); + + if (!NLVal) { + return result; + } + + Optional NLArg = Arg.getAs(); + + if (!NLArg) { + return result; + } + + SVal cmprLT = builder.evalBinOp(state, + BO_GT, + *NLArg, + *NLVal, + builder.getConditionType()); + + Optional NLcmprLT = cmprLT.getAs(); + + if (!NLcmprLT) { + return result; + } + + std::pair 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(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(Condition)->isKnownToHaveBooleanValue()) { + + if (const ImplicitCastExpr *IE = dyn_cast(Condition)) { + Condition = IE->getSubExpr(); + } + } + + if (const UnaryOperator *UnOp = dyn_cast(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(); +} + +bool ento::shouldRegisterTaintedLoopChecker(const CheckerManager &mgr) { + return true; +} diff --git a/MySQL/clang_checker/TaintedPointerChecker.cpp b/MySQL/clang_checker/TaintedPointerChecker.cpp new file mode 100644 index 0000000..7564cb8 --- /dev/null +++ b/MySQL/clang_checker/TaintedPointerChecker.cpp @@ -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 { + + mutable std::unique_ptr 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(*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(stmt)->IgnoreImplicitAsWritten(); + if (expr && expr->getType()->isPointerType()) + reportBug(Ctx); + } +} + +void ento::registerTaintedPointerChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterTaintedPointerChecker(const CheckerManager &mgr) { + return true; +} diff --git a/MySQL/codeql/iruntrusted.ql b/MySQL/codeql/iruntrusted.ql new file mode 100644 index 0000000..a8cd1d3 --- /dev/null +++ b/MySQL/codeql/iruntrusted.ql @@ -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() diff --git a/MySQL/codeql/loop.ql b/MySQL/codeql/loop.ql new file mode 100644 index 0000000..8c5ccf8 --- /dev/null +++ b/MySQL/codeql/loop.ql @@ -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()