diff --git a/go/ql/lib/change-notes/2026-08-17-deprecate-condition-guard-node.md b/go/ql/lib/change-notes/2026-08-17-deprecate-condition-guard-node.md new file mode 100644 index 000000000000..e31683264859 --- /dev/null +++ b/go/ql/lib/change-notes/2026-08-17-deprecate-condition-guard-node.md @@ -0,0 +1,4 @@ +--- +category: deprecated +--- +* `ControlFlow::ConditionGuardNode` is now deprecated and has no instances. Use the API from `semmle.go.controlflow.Guards` instead. \ No newline at end of file diff --git a/go/ql/lib/change-notes/2026-08-17-shared-guards.md b/go/ql/lib/change-notes/2026-08-17-shared-guards.md new file mode 100644 index 000000000000..e51d9758043b --- /dev/null +++ b/go/ql/lib/change-notes/2026-08-17-shared-guards.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added `semmle.go.controlflow.Guards`, including the `Guard` and `GuardValue` classes and the `guardEnsures`, `guardEnsuresEq`, `guardEnsuresNeq`, and `guardEnsuresLeq` predicates. diff --git a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll index 37b81d0f4fe7..80287d7c02b9 100644 --- a/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll +++ b/go/ql/lib/semmle/go/controlflow/ControlFlowGraph.qll @@ -258,10 +258,12 @@ module ControlFlow { } /** + * DEPRECATED: Use `Guard` from `semmle.go.controlflow.Guards` instead. + * * A control-flow node recording the fact that a certain expression has a known * Boolean value at this point in the program. */ - class ConditionGuardNode extends IR::Instruction { + deprecated class ConditionGuardNode extends IR::Instruction { Expr cond; boolean outcome; @@ -292,43 +294,69 @@ module ControlFlow { b = false } - /** Holds if this guard ensures that the result of `nd` is `b`. */ - predicate ensures(DataFlow::Node nd, boolean b) { + /** + * DEPRECATED: Use `Guard.controls` from `semmle.go.controlflow.Guards` + * instead. + * + * Holds if this guard ensures that the result of `nd` is `b`. + */ + deprecated predicate ensures(DataFlow::Node nd, boolean b) { this.ensuresAux(any(Expr e | nd = DataFlow::exprNode(e)), b) } - /** Holds if this guard ensures that `lesser <= greater + bias` holds. */ - predicate ensuresLeq(DataFlow::Node lesser, DataFlow::Node greater, int bias) { + /** + * DEPRECATED: Use `guardEnsuresLeq` from `semmle.go.controlflow.Guards` + * instead. + * + * Holds if this guard ensures that `lesser <= greater + bias` holds. + */ + deprecated predicate ensuresLeq(DataFlow::Node lesser, DataFlow::Node greater, int bias) { exists(DataFlow::RelationalComparisonNode rel, boolean b | - this.ensures(rel, b) and + this.ensuresAux(rel.asExpr(), b) and rel.leq(b, lesser, greater, bias) ) or - this.ensuresEq(lesser, greater) and + exists(DataFlow::EqualityTestNode eq, boolean b | + this.ensuresAux(eq.asExpr(), b) and + eq.eq(b, lesser, greater) + ) and bias = 0 } - /** Holds if this guard ensures that `i = j` holds. */ - predicate ensuresEq(DataFlow::Node i, DataFlow::Node j) { + /** + * DEPRECATED: Use `guardEnsuresEq` from `semmle.go.controlflow.Guards` + * instead. + * + * Holds if this guard ensures that `i = j` holds. + */ + deprecated predicate ensuresEq(DataFlow::Node i, DataFlow::Node j) { exists(DataFlow::EqualityTestNode eq, boolean b | - this.ensures(eq, b) and + this.ensuresAux(eq.asExpr(), b) and eq.eq(b, i, j) ) } - /** Holds if this guard ensures that `i != j` holds. */ - predicate ensuresNeq(DataFlow::Node i, DataFlow::Node j) { + /** + * DEPRECATED: Use `guardEnsuresNeq` from `semmle.go.controlflow.Guards` + * instead. + * + * Holds if this guard ensures that `i != j` holds. + */ + deprecated predicate ensuresNeq(DataFlow::Node i, DataFlow::Node j) { exists(DataFlow::EqualityTestNode eq, boolean b | - this.ensures(eq, b.booleanNot()) and + this.ensuresAux(eq.asExpr(), b.booleanNot()) and eq.eq(b, i, j) ) } /** + * DEPRECATED: Use `Guard.controls` from `semmle.go.controlflow.Guards` + * instead. + * * Holds if this guard dominates basic block `bb`, that is, the guard * is known to hold at `bb`. */ - predicate dominates(ReachableBasicBlock bb) { + deprecated predicate dominates(ReachableBasicBlock bb) { this = bb.getANode() or this.dominates(bb.getImmediateDominator()) } diff --git a/go/ql/lib/semmle/go/controlflow/Guards.qll b/go/ql/lib/semmle/go/controlflow/Guards.qll new file mode 100644 index 000000000000..fdffe654dedb --- /dev/null +++ b/go/ql/lib/semmle/go/controlflow/Guards.qll @@ -0,0 +1,546 @@ +/** + * Provides classes and predicates for reasoning about guards and the control + * flow elements controlled by those guards. + * + * This is an instantiation of the shared guards library for Go. + */ +overlay[local?] +module; + +private import go +private import semmle.go.controlflow.ControlFlowGraphImpl +private import semmle.go.dataflow.SSA as GoSsa +private import semmle.go.dataflow.SsaImpl as SsaImpl +private import semmle.go.dataflow.internal.DataFlowUtil as DataFlowUtil +private import codeql.controlflow.Guards as SharedGuards +private import codeql.controlflow.SuccessorType + +private module GuardsInput implements + SharedGuards::InputSig +{ + private import go as G + + class NormalExitNode = CfgImpl::ControlFlow::NormalExitNode; + + class AstNode = G::AstNode; + + class Expr extends G::Expr { + /** Gets the associated control flow node. */ + CfgImpl::Cfg::ControlFlowNode getControlFlowNode() { result = IR::evalExprInstruction(this) } + + /** Gets the basic block containing this expression. */ + CfgImpl::Cfg::BasicBlock getBasicBlock() { result = this.getControlFlowNode().getBasicBlock() } + } + + predicate booleanOutcomeBlock(Expr guard, CfgImpl::Cfg::BasicBlock outcomeBlock, boolean branch) { + exists(CfgImpl::Cfg::ControlFlowNode outcomeNode | + branch = true and outcomeNode.isAfterTrue(guard) + or + branch = false and outcomeNode.isAfterFalse(guard) + | + outcomeBlock = outcomeNode.getBasicBlock() + ) + } + + private newtype TConstantValue = TStringValue(string s) { s = any(G::Expr e).getStringValue() } + + class ConstantValue extends TConstantValue { + /** Gets a textual representation of this constant value. */ + string toString() { this = TStringValue(result) } + } + + abstract class ConstantExpr extends Expr { + predicate isNull() { none() } + + boolean asBooleanValue() { none() } + + int asIntegerValue() { none() } + + ConstantValue asConstantValue() { none() } + } + + private class NilConstant extends ConstantExpr { + NilConstant() { + exprRefersToNil(this) + or + exprRefersToNil(this.(G::ConversionExpr).getOperand().stripParens()) + } + + override predicate isNull() { any() } + } + + private class BooleanConstant extends ConstantExpr { + BooleanConstant() { exists(this.getBoolValue()) } + + override boolean asBooleanValue() { result = this.getBoolValue() } + } + + private class IntegerConstant extends ConstantExpr { + IntegerConstant() { exists(this.getIntValue()) } + + override int asIntegerValue() { result = this.getIntValue() } + } + + private class StringConstant extends ConstantExpr { + StringConstant() { exists(this.getStringValue()) } + + override ConstantValue asConstantValue() { result = TStringValue(this.getStringValue()) } + } + + /** + * An expression that is known not to be `nil`. + */ + class NonNullExpr extends Expr { + NonNullExpr() { + this instanceof G::CompositeLit + or + this instanceof G::FuncLit + or + this instanceof G::AddressExpr + or + DataFlowUtil::isCertainlyNotNil(DataFlow::exprNode(this)) + } + } + + /** + * A case clause in a tagged expression `switch` statement, or a case expression in an + * expressionless `switch` statement. + */ + class Case extends AstNode { + G::ExpressionSwitchStmt switch; + + Case() { + exists(switch.getExpr()) and + this = switch.getACase() + or + not exists(switch.getExpr()) and + ( + this = switch.getANonDefaultCase().getAnExpr() + or + this = switch.getDefault() + ) + } + + Expr getSwitchExpr() { + result = switch.getExpr() + or + not exists(switch.getExpr()) and result = this + } + + predicate isDefaultCase() { this = switch.getDefault() } + + ConstantExpr asConstantCase() { + exists(G::CaseClause cc | + this = cc and + cc.getNumExpr() = 1 and + result = cc.getExpr(0) + ) + } + + predicate matchEdge(CfgImpl::Cfg::BasicBlock bb1, CfgImpl::Cfg::BasicBlock bb2) { + exists(Expr caseExpr | + caseExpr = this.(G::CaseClause).getAnExpr() + or + caseExpr = this + | + caseExpressionBranch(caseExpr, bb1, + any(MatchingSuccessor successor | + bb1.getASuccessor(successor) = bb2 and successor.getValue() = true + )) + ) + } + + predicate nonMatchEdge(CfgImpl::Cfg::BasicBlock bb1, CfgImpl::Cfg::BasicBlock bb2) { + exists(G::CaseClause cc, int last, Expr caseExpr | + cc = this and + last = max(int i | exists(cc.getExpr(i))) and + caseExpr = cc.getExpr(last) + | + caseExpressionBranch(caseExpr, bb1, + any(MatchingSuccessor successor | + bb1.getASuccessor(successor) = bb2 and successor.getValue() = false + )) + ) + or + caseExpressionBranch(this.(Expr), bb1, + any(MatchingSuccessor successor | + bb1.getASuccessor(successor) = bb2 and successor.getValue() = false + )) + } + } + + predicate caseOutcomeBlock(Case guard, CfgImpl::Cfg::BasicBlock outcomeBlock, boolean branch) { + branch = true and + guard.isDefaultCase() and + outcomeBlock = + any(CfgImpl::Cfg::ControlFlowNode outcomeNode | + outcomeNode + .isAfterValue(guard, any(MatchingSuccessor successor | successor.getValue() = true)) + ).getBasicBlock() + } + + additional predicate caseExpressionBranch( + Expr caseExpr, CfgImpl::Cfg::BasicBlock bb, MatchingSuccessor successor + ) { + exists(G::CaseClause cc, G::ExpressionSwitchStmt switch | + cc = switch.getACase() and + caseExpr = cc.getAnExpr() and + bb.getLastNode() = caseExpr.getControlFlowNode() and + exists(bb.getASuccessor(successor)) + ) + } + + predicate equalityBranchEdge( + Expr left, Expr right, CfgImpl::Cfg::BasicBlock bb1, CfgImpl::Cfg::BasicBlock bb2, boolean equal + ) { + exists(G::CaseClause cc, G::ExpressionSwitchStmt switch | + cc = switch.getACase() and + left = switch.getExpr() and + right = cc.getAnExpr() and + caseExpressionBranch(right, bb1, + any(MatchingSuccessor successor | + bb1.getASuccessor(successor) = bb2 and successor.getValue() = equal + )) + ) + } + + class AndExpr extends Expr instanceof G::LandExpr { + /** Gets an operand of this expression. */ + Expr getAnOperand() { result = super.getAnOperand() } + } + + class OrExpr extends Expr instanceof G::LorExpr { + /** Gets an operand of this expression. */ + Expr getAnOperand() { result = super.getAnOperand() } + } + + class NotExpr extends Expr instanceof G::NotExpr { + /** Gets the operand of this expression. */ + Expr getOperand() { result = super.getOperand() } + } + + private predicate sameNumericTypeFamily(G::NumericType source, G::NumericType target) { + source instanceof G::SignedIntegerType and target instanceof G::SignedIntegerType + or + source instanceof G::UnsignedIntegerType and target instanceof G::UnsignedIntegerType + or + source instanceof G::FloatType and target instanceof G::FloatType + or + source instanceof G::ComplexType and target instanceof G::ComplexType + } + + private predicate isUpcast(G::ConversionExpr conversion) { + conversion.getOperand().getType().getUnderlyingType() = conversion.getType().getUnderlyingType() + or + exists(G::NumericType source, G::NumericType target | + source = conversion.getOperand().getType().getUnderlyingType() and + target = conversion.getType().getUnderlyingType() and + sameNumericTypeFamily(source, target) and + source.getSize() <= target.getSize() + ) + } + + /** + * An expression that has the same value as a specific sub-expression, that + * is, a parenthesized expression or an upcast. + */ + class IdExpr extends Expr { + IdExpr() { this instanceof G::ParenExpr or isUpcast(this) } + + Expr getEqualChildExpr() { + result = this.(G::ParenExpr).getExpr() + or + result = this.(G::ConversionExpr).getOperand() + } + } + + /** + * Holds if `eqtest` is an equality or inequality test between `left` and + * `right`. The `polarity` indicates whether this is an equality test (true) + * or inequality test (false). + */ + pragma[nomagic] + predicate equalityTest(Expr eqtest, Expr left, Expr right, boolean polarity) { + exists(G::EqualityTestExpr eq | eq = eqtest | + left = eq.getLeftOperand() and + right = eq.getRightOperand() and + polarity = eq.getPolarity() + ) + } + + /** + * A conditional expression. Go has no such expression, so this class is + * empty. + */ + class ConditionalExpr extends Expr { + ConditionalExpr() { none() } + + /** Gets the condition of this expression. */ + Expr getCondition() { none() } + + /** Gets the true branch of this expression. */ + Expr getThen() { none() } + + /** Gets the false branch of this expression. */ + Expr getElse() { none() } + } + + class Parameter = G::Parameter; + + private int parameterPosition() { result = any(Parameter p).getIndex() } + + /** A parameter position represented by an integer. */ + class ParameterPosition extends int { + ParameterPosition() { this = parameterPosition() } + } + + /** An argument position represented by an integer. */ + class ArgumentPosition extends int { + ArgumentPosition() { this = parameterPosition() } + } + + /** Holds if arguments at position `apos` match parameters at position `ppos`. */ + pragma[inline] + predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos } + + final private class FinalFunction = G::Function; + + /** + * A declared function or concrete method. + * + * Calls are restricted separately to calls whose syntactic target is this + * function or method, excluding interface dispatch. + */ + class NonOverridableMethod extends FinalFunction { + NonOverridableMethod() { + exists(super.getFuncDecl()) and + super.getNumResult() <= 1 + } + + Parameter getParameter(ParameterPosition ppos) { result = super.getParameter(ppos) } + + /** Gets an expression being returned by this function. */ + Expr getAReturnExpr() { + exists(G::ReturnStmt ret | + ret.getEnclosingFunction() = super.getFuncDecl() and + result = ret.getExpr() + ) + } + } + + private predicate nonOverridableCall(G::CallExpr call, NonOverridableMethod m) { + call.getTarget() = m + } + + private predicate hasExplicitReceiverArgument(G::CallExpr call) { + call.getTarget() instanceof G::Method and + call.getCalleeExpr().(G::SelectorExpr).getBase() instanceof G::TypeExpr + } + + /** + * Gets the receiver argument when the selector base is passed directly, + * without promotion or an implicit address/dereference conversion. + */ + private Expr getDirectReceiverArgument(G::CallExpr call, NonOverridableMethod method) { + exists(G::SelectorExpr sel, IR::MethodReadInstruction read | + sel = call.getCalleeExpr() and + read.getExpr() = sel and + read.getReceiver() = IR::evalExprInstruction(result) and + result = sel.getBase() and + result.getType() = method.getParameter(-1).getType() + ) + } + + class NonOverridableMethodCall extends Expr instanceof G::CallExpr { + NonOverridableMethodCall() { nonOverridableCall(this, _) } + + NonOverridableMethod getMethod() { nonOverridableCall(this, result) } + + Expr getArgument(ArgumentPosition apos) { + ( + not hasExplicitReceiverArgument(this) and + ( + apos = -1 and + result = getDirectReceiverArgument(this, this.getMethod()) + or + apos != -1 and + result = super.getArgument(apos) + ) + or + hasExplicitReceiverArgument(this) and + result = super.getArgument(apos + 1) + ) and + not ( + super.hasImplicitVarargs() and + apos = this.getMethod().getNumParameter() - 1 + ) + } + } +} + +private module GuardsImpl = SharedGuards::Make; + +private module LogicInput implements GuardsImpl::LogicInputSig { + final private class FinalSsaDefinition = GoSsa::SsaDefinition; + + class SsaDefinition extends FinalSsaDefinition { + GuardsInput::Expr getARead() { + result = super.getVariable().getAUse().(IR::EvalInstruction).getExpr() + } + } + + class SsaExplicitWrite extends SsaDefinition instanceof GoSsa::SsaExplicitDefinition { + GuardsInput::Expr getValue() { result = super.getRhs().(IR::EvalInstruction).getExpr() } + } + + class SsaPhiDefinition extends SsaDefinition instanceof GoSsa::SsaPhiNode { + /** Holds if `inp` is an input to the phi node along the edge originating in `bb`. */ + predicate hasInputFromBlock(SsaDefinition inp, BasicBlock bb) { + SsaImpl::phiHasInputFromBlock(this, inp, bb) + } + } + + class SsaParameterInit extends SsaDefinition { + SsaParameterInit() { + this.(GoSsa::SsaExplicitDefinition).getInstruction() instanceof IR::InitParameterInstruction + } + + GuardsInput::Parameter getParameter() { + this.(GoSsa::SsaExplicitDefinition).getInstruction() = IR::initParamInstruction(result) + } + } + + predicate implicitReturnDefinition(GuardsInput::NonOverridableMethod method, SsaDefinition def) { + exists(IR::ReadResultInstruction read | + method.getNumResult() = 1 and + read.reads(method.getResult(0)) and + def.getVariable().getAUse() = read + ) + } + + predicate additionalSsaDefinitionValue(SsaDefinition def, GuardValue value) { + exists(IR::EvalImplicitInitInstruction init | + def.(GoSsa::SsaExplicitDefinition).getInstruction() = init and + value.asBooleanValue() = init.getBoolValue() + ) + } + + /** + * Holds if `rel` evaluating to `branch` ensures that `lesser` is less than + * `greater`, strictly if `strict` is true. + */ + private predicate comparison( + RelationalComparisonExpr rel, boolean branch, GuardsInput::Expr lesser, + GuardsInput::Expr greater, boolean strict + ) { + branch = true and + lesser = rel.getLesserOperand() and + greater = rel.getGreaterOperand() and + (if rel.isStrict() then strict = true else strict = false) + or + branch = false and + lesser = rel.getGreaterOperand() and + greater = rel.getLesserOperand() and + (if rel.isStrict() then strict = false else strict = true) + } + + /** + * Holds if `guard` evaluating to `val` ensures that: + * `e <= k` when `upper = true` + * `e >= k` when `upper = false` + */ + predicate rangeGuard( + GuardsImpl::PreGuard guard, GuardValue val, GuardsInput::Expr e, int k, boolean upper + ) { + exists( + RelationalComparisonExpr rel, boolean branch, GuardsInput::Expr lesser, + GuardsInput::Expr greater, boolean strict, int strictnessAdjustment + | + guard = rel and + val.asBooleanValue() = branch and + comparison(rel, branch, lesser, greater, strict) and + (if strict = true then strictnessAdjustment = 1 else strictnessAdjustment = 0) + | + // `e < k` or `e <= k` + e = lesser and + upper = true and + k = greater.getIntValue() - strictnessAdjustment + or + // `k < e` or `k <= e` + e = greater and + upper = false and + k = lesser.getIntValue() + strictnessAdjustment + ) + } +} + +/** An abstract value that a `Guard` may evaluate to. */ +class GuardValue = GuardsImpl::GuardValue; + +private module GuardsLogic = GuardsImpl::Logic; + +/** + * A guard. This is an expression whose value, or a switch case whose match outcome, determines + * subsequent control flow. + */ +final class Guard extends GuardsLogic::Guard { + /** Gets the innermost function or file to which this guard belongs. */ + ControlFlow::Root getRoot() { result.isRootOf(this) } +} + +/** + * Holds if `caseExpr` is a case expression in a tagged switch and its matching edge controls + * `block`. + */ +predicate caseExpressionMatchControls(Expr caseExpr, BasicBlock block) { + exists(BasicBlock guard, MatchingSuccessor successor | + GuardsInput::caseExpressionBranch(caseExpr, guard, successor) and + successor.getValue() = true and + guard.edgeDominates(block, successor) + ) +} + +/** + * Provides a set of barrier nodes for a guard that validates an expression. + */ +module ValidationWrapper { + import GuardsLogic::ValidationWrapper +} + +/** + * Holds if `bb` can only be reached when the expression `e` evaluates to `b`. + * + * This is the replacement for the old + * `ConditionGuardNode.ensures(e, b) and ConditionGuardNode.dominates(bb)` + * idiom. + */ +pragma[inline] +predicate guardEnsures(Expr e, boolean b, BasicBlock bb) { e.(Guard).controls(bb, b) } + +/** Holds if `guard` evaluating to `branch` ensures that `i = j` holds. */ +predicate guardEnsuresEq(Guard guard, boolean branch, DataFlow::Node i, DataFlow::Node j) { + guard.isEquality(i.asExpr(), j.asExpr(), branch) +} + +/** Holds if `guard` evaluating to `branch` ensures that `i != j` holds. */ +predicate guardEnsuresNeq(Guard guard, boolean branch, DataFlow::Node i, DataFlow::Node j) { + exists(boolean eqval | + guard.isEquality(i.asExpr(), j.asExpr(), eqval) and + branch = eqval.booleanNot() + ) +} + +/** + * Holds if `guard` evaluating to `branch` ensures that `lesser <= greater + bias` + * holds. + */ +predicate guardEnsuresLeq( + Guard guard, boolean branch, DataFlow::Node lesser, DataFlow::Node greater, int bias +) { + exists(DataFlow::RelationalComparisonNode rel | + guard = rel.asExpr() and + rel.leq(branch, lesser, greater, bias) + ) + or + guardEnsuresEq(guard, branch, lesser, greater) and bias = 0 +} diff --git a/go/ql/lib/semmle/go/controlflow/IR.qll b/go/ql/lib/semmle/go/controlflow/IR.qll index a87f1464de12..7e9ace19f4c1 100644 --- a/go/ql/lib/semmle/go/controlflow/IR.qll +++ b/go/ql/lib/semmle/go/controlflow/IR.qll @@ -32,22 +32,6 @@ module IR { n.isAfterValue(cc, any(MatchingSuccessor t | t.isMatch())) } - /** - * Holds if `n` records a boolean outcome, or the matching outcome of an - * expressionless switch case condition. - */ - private predicate isConditionGuardNode(ControlFlow::Node n) { - n.isAfterTrue(_) - or - n.isAfterFalse(_) - or - exists(Expr condition, MatchingSuccessor successor | - condition = - any(ExpressionSwitchStmt switch | not exists(switch.getExpr())).getACase().getAnExpr() and - n.isAfterValue(condition, successor) - ) - } - /** Gets the CFG node representing a basic literal, function literal, or plain identifier reference. */ cached private ControlFlow::Node leafEvaluation(Expr leaf) { @@ -72,17 +56,17 @@ module IR { or this.isAdditional(_, _) or - isConditionGuardNode(this) - or // The successful-match node of a type-switch case that binds an implicit // variable hosts that variable's declaration/assignment (see // `TypeSwitchImplicitVariableInstruction`). typeSwitchCaseMatch(this, _) or // `NotExpr` and `LogicalBinaryExpr` are not in `postOrInOrder`, so they - // have no `isIn` node. Use their combined after-node as the value-producing - // instruction, but not a value-specific after-node, which is already a - // `ConditionGuardInstruction`. + // have no `isIn` node. When such an expression is not in a conditional + // context (so it has a single combined after-node rather than per-branch + // value-after-nodes), use that after-node as the value-producing + // instruction. In conditional contexts the value is already split + // across branches, so no separate value instruction is needed. exists(Expr e | (e instanceof NotExpr or e instanceof LogicalBinaryExpr) and this.isAfter(e) and @@ -182,8 +166,6 @@ module IR { or this instanceof GoInstruction and result = "go" or - this instanceof ConditionGuardInstruction and result = "condition guard" - or this instanceof ReturnInstruction and result = "return" or this instanceof WriteResultInstruction and result = "result write" @@ -207,11 +189,6 @@ module IR { } } - /** A condition guard instruction, representing a known boolean outcome for a condition. */ - private class ConditionGuardInstruction extends Instruction { - ConditionGuardInstruction() { isConditionGuardNode(this) } - } - /** * An IR instruction representing the evaluation of an expression. */ diff --git a/go/ql/lib/semmle/go/dataflow/ExternalFlow.qll b/go/ql/lib/semmle/go/dataflow/ExternalFlow.qll index f0dc0cf0ca2b..40e82507d70d 100644 --- a/go/ql/lib/semmle/go/dataflow/ExternalFlow.qll +++ b/go/ql/lib/semmle/go/dataflow/ExternalFlow.qll @@ -104,6 +104,7 @@ overlay[local?] module; private import go +private import semmle.go.controlflow.Guards private import internal.ExternalFlowExtensions::Extensions as Extensions private import FlowSummary as FlowSummary private import internal.DataFlowPrivate @@ -459,24 +460,23 @@ private module Cached { private newtype TKindModelPair = TMkPair(string kind, string model) { isBarrierGuardNode(_, _, kind, model) } - private boolean convertAcceptingValue(Public::AcceptingValue av) { - av.isTrue() and result = true + private GuardValue convertAcceptingValue(Public::AcceptingValue av) { + av.isTrue() and result.asBooleanValue() = true or - av.isFalse() and result = false - // Remaining cases are not supported yet, they depend on the shared Guards library. - // or - // av.isNoException() and result.getDualValue().isThrowsException() - // or - // av.isZero() and result.asIntValue() = 0 - // or - // av.isNotZero() and result.getDualValue().asIntValue() = 0 - // or - // av.isNull() and result.isNullValue() - // or - // av.isNotNull() and result.isNonNullValue() + av.isFalse() and result.asBooleanValue() = false + or + av.isNoException() and result.getDualValue().isThrowsException() + or + av.isZero() and result.asIntValue() = 0 + or + av.isNotZero() and result.getDualValue().asIntValue() = 0 + or + av.isNull() and result.isNullValue() + or + av.isNotNull() and result.isNonNullValue() } - private predicate barrierGuardChecks(DataFlow::Node g, Expr e, boolean gv, TKindModelPair kmp) { + private predicate barrierGuardChecks(Guard g, Expr e, GuardValue gv, TKindModelPair kmp) { exists( SourceSinkInterpretationInput::InterpretNode n, Public::AcceptingValue acceptingValue, string kind, string model @@ -486,7 +486,7 @@ private module Cached { kmp = TMkPair(kind, model) and gv = convertAcceptingValue(acceptingValue) | - g.asExpr().(CallExpr).getAnArgument() = e // TODO: qualifier? + g.(CallExpr).getAnArgument() = e // TODO: qualifier? ) } @@ -500,7 +500,7 @@ private module Cached { isBarrierNode(n, kind, model) and n.asNode() = node ) or - DataFlow::ParameterizedBarrierGuard::getABarrierNode(TMkPair(kind, + DataFlow::ParameterizedBarrierGuardValue::getABarrierNode(TMkPair(kind, model)) = node } } diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll index e65b2493dd9b..056d8edd5d9f 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll @@ -2,6 +2,7 @@ overlay[local?] module; private import go +private import semmle.go.controlflow.Guards private import DataFlowUtil private import DataFlowImplCommon private import ContainerFlow @@ -390,19 +391,21 @@ private class ConstantBooleanArgumentNode extends ArgumentNode, ExprNode { } /** - * Returns a guard that will certainly not hold in calling context `call`. + * Holds if `guard` evaluating to `branch` will certainly not happen in calling + * context `call`. * * In particular it does not hold because it checks that `param` has value `b`, but * in context `call` it is known to have value `!b`. Note this is `noinline`d in order * to avoid a bad join order in `isUnreachableInCall`. */ pragma[noinline] -private ControlFlow::ConditionGuardNode getAFalsifiedGuard(DataFlowCall call) { +private predicate falsifiedGuard(DataFlowCall call, Guard guard, boolean branch) { exists(SsaParameterNode param, ConstantBooleanArgumentNode arg | // get constant bool argument and parameter for this call viableParamArg(call, pragma[only_bind_into](param), pragma[only_bind_into](arg)) and // which is used in a guard controlling `n` with the opposite value of `arg` - result.ensures(param.getAUse(), arg.getBooleanValue().booleanNot()) + guard = param.getAUse().asExpr() and + branch = arg.getBooleanValue().booleanNot() ) } @@ -416,7 +419,10 @@ class NodeRegion instanceof BasicBlock { * Holds if the nodes in `nr` are unreachable when the call context is `call`. */ predicate isUnreachableInCall(NodeRegion nr, DataFlowCall call) { - getAFalsifiedGuard(call).dominates(nr) + exists(Guard guard, boolean branch | + falsifiedGuard(call, guard, branch) and + guard.controls(nr, branch) + ) } /** diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll index 98e7d90667e0..ad6c5f3a92de 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll @@ -5,6 +5,7 @@ overlay[local?] module; private import go +private import semmle.go.controlflow.Guards private import semmle.go.dataflow.FunctionInputsAndOutputs private import semmle.go.dataflow.ExternalFlow private import DataFlowPrivate @@ -355,6 +356,39 @@ private module WithParam { signature predicate guardChecksSig(Node g, Expr e, boolean branch, P param); } +/** + * Holds if the guard `g` validates the expression `e` upon evaluating to `value`. + * + * The expression `e` is expected to be a syntactic part of the guard `g`. + */ +signature predicate valueGuardChecksSig(Guard g, Expr e, GuardValue value); + +bindingset[this] +private signature class ValueParamSig; + +private module WithValueParam { + /** + * Holds if the guard `g` validates the expression `e` upon evaluating to `value`. + * + * The expression `e` is expected to be a syntactic part of the guard `g`. + */ + signature predicate guardChecksSig(Guard g, Expr e, GuardValue value, P param); +} + +/** + * Provides a set of barrier nodes for a guard that validates an expression. + */ +module BarrierGuardValue { + private predicate guardChecksWithParam(Guard g, Expr e, GuardValue value, Unit param) { + guardChecks(g, e, value) and exists(param) + } + + private module B = ParameterizedBarrierGuardValue; + + /** Gets a node that is safely guarded by the given guard check. */ + Node getABarrierNode() { result = B::getABarrierNode(_) } +} + /** * Provides a set of barrier nodes for a guard that validates an expression. * @@ -362,20 +396,25 @@ private module WithParam { * in data flow and taint tracking. */ module BarrierGuard { - private predicate guardChecks(Node g, Expr e, boolean branch, Unit param) { - guardChecks(g, e, branch) and exists(param) + private predicate guardChecksValue(Guard g, Expr e, GuardValue value) { + guardChecks(DataFlow::exprNode(g), e, value.asBooleanValue()) } - private module B = ParameterizedBarrierGuard; + private module B = BarrierGuardValue; /** Gets a node that is safely guarded by the given guard check. */ - Node getABarrierNode() { result = B::getABarrierNode(_) } + Node getABarrierNode() { result = B::getABarrierNode() } /** * Gets a node that is safely guarded by the given guard check. */ Node getABarrierNodeForGuard(Node guardCheck) { - result = B::getABarrierNodeForGuard(guardCheck, _) + result = + ParameterizedBarrierGuard::getABarrierNodeForGuard(guardCheck, _) + } + + private predicate guardChecksWithParam(Node g, Expr e, boolean branch, Unit param) { + guardChecks(g, e, branch) and exists(param) } } @@ -385,14 +424,16 @@ module BarrierGuard { * This is expected to be used in `isBarrier`/`isSanitizer` definitions * in data flow and taint tracking. */ -module ParameterizedBarrierGuard::guardChecksSig/4 guardChecks> { +module ParameterizedBarrierGuardValue< + ValueParamSig P, WithValueParam

::guardChecksSig/4 guardChecks> +{ /** Gets a node that is safely guarded by the given guard check. */ Node getABarrierNode(P param) { - exists(ControlFlow::ConditionGuardNode guard, SsaWithFields var | + exists(Guard guard, GuardValue value, SsaWithFields var | result = pragma[only_bind_out](var).getAUse() | - guards(_, guard, _, var, param) and - pragma[only_bind_out](guard).dominates(result.getBasicBlock()) + guards(_, guard, value, _, var, param) and + pragma[only_bind_out](guard).valueControls(result.getBasicBlock(), value) ) } @@ -400,39 +441,36 @@ module ParameterizedBarrierGuard::guardChecksSig/4 guar * Gets a node that is safely guarded by the given guard check. */ Node getABarrierNodeForGuard(Node guardCheck, P param) { - exists(ControlFlow::ConditionGuardNode guard, SsaWithFields var | result = var.getAUse() | - guards(guardCheck, guard, _, var, param) and - guard.dominates(result.getBasicBlock()) + exists(Guard guard, GuardValue value, SsaWithFields var | result = var.getAUse() | + guards(guardCheck, guard, value, _, var, param) and + guard.valueControls(result.getBasicBlock(), value) ) } /** - * Holds if `guard` marks a point in the control-flow graph where `g` - * is known to validate `nd`, which is represented by `ap`. + * Holds if `guard` evaluating to `branch` marks a point in the control-flow + * graph where `g` is known to validate `nd`, which is represented by `ap`. * * This predicate exists to enforce a good join order in `getAGuardedNode`. */ pragma[noinline] - private predicate guards( - Node g, ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields ap, P param - ) { - guards(g, guard, nd, param) and nd = ap.getAUse() + private predicate guards(Node g, Guard guard, GuardValue value, Node nd, SsaWithFields ap, P param) { + guards(g, guard, value, nd, param) and nd = ap.getAUse() } /** - * Holds if `guard` marks a point in the control-flow graph where `g` - * is known to validate `nd`. + * Holds if `guard` evaluating to `branch` marks a point in the control-flow + * graph where `g` is known to validate `nd`. */ - private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd, P param) { - exists(boolean branch | - guardChecks(g, nd.asExpr(), branch, param) and - guard.ensures(g, branch) - ) + private predicate guards(Node g, Guard guard, GuardValue value, Node nd, P param) { + guardChecks(g.asExpr(), nd.asExpr(), value, param) and + guard = g.asExpr() or - exists(DataFlow::Property p, Node resNode, Node check, boolean outcome | + exists(DataFlow::Property p, Node resNode, Node check, boolean branch | guardingCall(g, _, _, _, p, _, nd, resNode, param) and - p.checkOn(check, outcome, resNode) and - guard.ensures(pragma[only_bind_into](check), outcome) + p.checkOn(check, branch, resNode) and + value.asBooleanValue() = branch and + guard = pragma[only_bind_into](check).asExpr() ) } @@ -487,9 +525,9 @@ module ParameterizedBarrierGuard::guardChecksSig/4 guar localFlow(inp.getExitNode(fd), pragma[only_bind_out](arg)) and ( // Case: a function like "if someBarrierGuard(arg) { return true } else { return false }" - exists(ControlFlow::ConditionGuardNode guard | - guards(g, pragma[only_bind_out](guard), arg, param) and - guard.dominates(pragma[only_bind_out](ret).getBasicBlock()) + exists(Guard guard, GuardValue value | + guards(g, pragma[only_bind_out](guard), value, arg, param) and + guard.valueControls(pragma[only_bind_out](ret).getBasicBlock(), value) | onlyPossibleReturnSatisfyingProperty(fd, outp, ret, p) ) @@ -498,7 +536,8 @@ module ParameterizedBarrierGuard::guardChecksSig/4 guar // or "return !someBarrierGuard(arg) && otherCond(...)" exists(boolean outcome | ret = getUniqueOutputNode(fd, outp) and - guardChecks(g, arg.asExpr(), outcome, param) and + guardChecks(g.asExpr(), arg.asExpr(), + any(GuardValue value | value.asBooleanValue() = outcome), param) and // This predicate's contract is (p holds of ret ==> arg is checked), // (and we have (this has outcome ==> arg is checked)) // but p.checkOn(ret, outcome, this) gives us (ret has outcome ==> p holds of this), @@ -530,6 +569,25 @@ module ParameterizedBarrierGuard::guardChecksSig/4 guar } } +/** + * Provides a set of barrier nodes for a Boolean guard that validates an expression. + */ +module ParameterizedBarrierGuard::guardChecksSig/4 guardChecks> { + private predicate guardChecksValue(Guard g, Expr e, GuardValue value, P param) { + guardChecks(DataFlow::exprNode(g), e, value.asBooleanValue(), param) + } + + private module B = ParameterizedBarrierGuardValue; + + /** Gets a node that is safely guarded by the given guard check. */ + Node getABarrierNode(P param) { result = B::getABarrierNode(param) } + + /** Gets a node that is safely guarded by the given guard check. */ + Node getABarrierNodeForGuard(Node guardCheck, P param) { + result = B::getABarrierNodeForGuard(guardCheck, param) + } +} + DataFlow::Node getUniqueOutputNode(FuncDecl fd, FunctionOutput outp) { result = unique(DataFlow::Node n | n = outp.getEntryNode(fd) | n) } @@ -579,7 +637,7 @@ private predicate onlyPossibleReturnOfNonNil(FuncDecl fd, FunctionOutput res, No /** * Holds if function `f`'s result `output`, which must be a return value, cannot be nil. */ -private predicate certainlyReturnsNonNil(Function f, FunctionOutput output) { +predicate certainlyReturnsNonNil(Function f, FunctionOutput output) { output.isResult(_) and ( f.hasQualifiedName("errors", "New") @@ -597,7 +655,7 @@ private predicate certainlyReturnsNonNil(Function f, FunctionOutput output) { /** * Holds if `node` cannot be `nil`. */ -private predicate isCertainlyNotNil(DataFlow::Node node) { +predicate isCertainlyNotNil(DataFlow::Node node) { node instanceof DataFlow::AddressOperationNode or exists(DataFlow::CallNode c, FunctionOutput output | output.getExitNode(c) = node | diff --git a/go/ql/lib/semmle/go/security/InsecureFeatureFlag.qll b/go/ql/lib/semmle/go/security/InsecureFeatureFlag.qll index 293f78507cc3..506d44077a04 100644 --- a/go/ql/lib/semmle/go/security/InsecureFeatureFlag.qll +++ b/go/ql/lib/semmle/go/security/InsecureFeatureFlag.qll @@ -3,6 +3,7 @@ */ import go +private import semmle.go.controlflow.Guards /** * Provides classes and predicates relating to flags that may indicate security expectations. @@ -114,9 +115,30 @@ module InsecureFeatureFlag { } /** - * Gets a control-flow node that represents a (likely) security feature-flag check + * Holds if `block` is controlled by a flag of kind `flagKind`. + * + * For a switch case expression, only the matching branch is controlled by that flag. Other + * branches, including the default case, are reached when the flag does not match. */ - ControlFlow::ConditionGuardNode getASecurityFeatureFlagCheck() { - result.ensures(any(SecurityFeatureFlag f).getAFlag().getANode(), _) + predicate flagControls(FlagKind flagKind, BasicBlock block) { + exists(GVN flag, Guard guard | + flag = flagKind.getAFlag() and + guard = flag.getANode().asExpr() and + ( + exists(CaseClause cc, ExpressionSwitchStmt switch | + guard.getParent() = cc and + cc = switch.getACase() and + exists(switch.getExpr()) and + caseExpressionMatchControls(guard, block) + ) + or + not exists(CaseClause cc, ExpressionSwitchStmt switch | + guard.getParent() = cc and + cc = switch.getACase() and + exists(switch.getExpr()) + ) and + guard.controls(block, _) + ) + ) } } diff --git a/go/ql/src/InconsistentCode/ConstantLengthComparison.ql b/go/ql/src/InconsistentCode/ConstantLengthComparison.ql index d0bcec7a89cb..6b722d887ae6 100644 --- a/go/ql/src/InconsistentCode/ConstantLengthComparison.ql +++ b/go/ql/src/InconsistentCode/ConstantLengthComparison.ql @@ -13,10 +13,11 @@ */ import go +private import semmle.go.controlflow.Guards from - ForStmt fs, Variable i, DataFlow::ElementReadNode idx, GVN a, - ControlFlow::ConditionGuardNode cond, DataFlow::CallNode lenA + ForStmt fs, Variable i, DataFlow::ElementReadNode idx, GVN a, Guard cond, boolean branch, + DataFlow::CallNode lenA where // `i` is incremented in `fs` fs.getPost().(IncStmt).getOperand() = i.getAReference() and @@ -27,11 +28,11 @@ where lenA.getArgument(0) = a.getANode() and // and is checked against a constant exists(DataFlow::Node const | exists(const.getIntValue()) | - cond.ensuresNeq(lenA, const) or - cond.ensuresLeq(const, lenA, _) + guardEnsuresNeq(cond, branch, lenA, const) or + guardEnsuresLeq(cond, branch, const, lenA, _) ) and - cond.dominates(idx.getBasicBlock()) and + cond.controls(idx.getBasicBlock(), branch) and // and that check happens inside the loop body - cond.getCondition().getParent+() = fs -select cond.getCondition(), "This checks the length against a constant, but it $@.", idx, + cond.(Expr).getParent+() = fs +select cond, "This checks the length against a constant, but it $@.", idx, "is indexed using a variable" diff --git a/go/ql/src/InconsistentCode/LengthComparisonOffByOne.ql b/go/ql/src/InconsistentCode/LengthComparisonOffByOne.ql index b36177fdc823..f7b197b362b0 100644 --- a/go/ql/src/InconsistentCode/LengthComparisonOffByOne.ql +++ b/go/ql/src/InconsistentCode/LengthComparisonOffByOne.ql @@ -13,6 +13,7 @@ */ import go +private import semmle.go.controlflow.Guards newtype TIndex = VariableIndex(DataFlow::SsaNode v) { v.getAUse() = any(DataFlow::ElementReadNode e).getIndex() } or @@ -41,28 +42,29 @@ DataFlow::CallNode arrayLen(DataFlow::SsaNode array) { } /** - * Gets a condition that checks that `index` is less than or equal to `array.length`. + * Holds if `guard` evaluating to `branch` checks that `index` is less than or equal to + * `array.length`. */ -ControlFlow::ConditionGuardNode getLengthLEGuard(Index index, DataFlow::SsaNode array) { - result.ensuresLeq(getAUse(index), arrayLen(array), 0) +predicate lengthLeGuard(Guard guard, boolean branch, Index index, DataFlow::SsaNode array) { + guardEnsuresLeq(guard, branch, getAUse(index), arrayLen(array), 0) or exists(int i, int bias | index = ConstantIndex(i) | - result.ensuresLeq(getAUse(ConstantIndex(i + bias)), arrayLen(array), bias) + guardEnsuresLeq(guard, branch, getAUse(ConstantIndex(i + bias)), arrayLen(array), bias) ) } -predicate isDominatingLengthLEGuard( - ControlFlow::ConditionGuardNode guard, Index index, DataFlow::SsaNode array, BasicBlock bb +predicate isControllingLengthLeGuard( + Guard guard, boolean branch, Index index, DataFlow::SsaNode array, BasicBlock bb ) { - guard = getLengthLEGuard(index, array) and - guard.dominates(bb) + lengthLeGuard(guard, branch, index, array) and + guard.controls(bb, branch) } /** - * Gets a condition that checks that `index` is not equal to `array.length`. + * Holds if `guard` evaluating to `branch` checks that `index` is not equal to `array.length`. */ -ControlFlow::ConditionGuardNode getLengthNEGuard(Index index, DataFlow::SsaNode array) { - result.ensuresNeq(getAUse(index), arrayLen(array)) +predicate lengthNeGuard(Guard guard, boolean branch, Index index, DataFlow::SsaNode array) { + guardEnsuresNeq(guard, branch, getAUse(index), arrayLen(array)) } /** @@ -85,26 +87,27 @@ predicate isRegexpMethodCall(DataFlow::MethodCallNode c) { } from - ControlFlow::ConditionGuardNode cond, DataFlow::SsaNode array, Index index, - DataFlow::ElementReadNode ea, BasicBlock bb + Guard cond, boolean branch, DataFlow::SsaNode array, Index index, DataFlow::ElementReadNode ea, + BasicBlock bb where // there is a read from `array[index]` elementRead(ea, array, index, bb) and // and it is guarded by a comparison `index <= len(array)` - isDominatingLengthLEGuard(cond, index, array, bb) and + isControllingLengthLeGuard(cond, branch, index, array, bb) and // and report the innermost guard that establishes the comparison - not exists(ControlFlow::ConditionGuardNode innerCond | - isDominatingLengthLEGuard(innerCond, index, array, bb) and - innerCond.getCondition().getParent+() = cond.getCondition() + not exists(Guard innerCond, boolean innerBranch | + isControllingLengthLeGuard(innerCond, innerBranch, index, array, bb) and + innerCond.(Expr).getParent+() = cond.(Expr) ) and // but the read is not guarded by another check that `index != len(array)` - not getLengthNEGuard(index, array).dominates(bb) and + not exists(Guard ne, boolean neBranch | + lengthNeGuard(ne, neBranch, index, array) and ne.controls(bb, neBranch) + ) and // and it is not additionally guarded by a stronger index check - not exists(Index index2, int i, int i2 | + not exists(Index index2, int i, int i2, Guard g2, boolean b2 | index = ConstantIndex(i) and index2 = ConstantIndex(i2) and i < i2 | - isDominatingLengthLEGuard(_, index2, array, bb) + isControllingLengthLeGuard(g2, b2, index2, array, bb) ) and not isRegexpMethodCall(array.getInit()) -select cond.getCondition(), - "Off-by-one index comparison against length may lead to out-of-bounds $@.", ea, "read" +select cond, "Off-by-one index comparison against length may lead to out-of-bounds $@.", ea, "read" diff --git a/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql b/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql index a6321b7d7cb3..76b694e3bfe9 100644 --- a/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql +++ b/go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql @@ -13,6 +13,7 @@ */ import go +private import semmle.go.controlflow.Guards /** * Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`, @@ -70,12 +71,12 @@ predicate regexpGuardsHandler(RegexpPattern regexp, Http::RequestHandler handler /** Holds if `regexp` guards an HTTP error write. */ predicate regexpGuardsError(RegexpPattern regexp) { - exists(ControlFlow::ConditionGuardNode cond, RegexpMatchFunction match, DataFlow::CallNode call | + exists(Guard cond, RegexpMatchFunction match, DataFlow::CallNode call | call.getTarget() = match and match.getRegexp(call) = regexp | - cond.ensures(match.getResult().getNode(call).getASuccessor*(), true) and - cond.dominates(any(ReachableBasicBlock b | writesHttpError(b))) + cond = match.getResult().getNode(call).getASuccessor*().asExpr() and + cond.controls(any(ReachableBasicBlock b | writesHttpError(b)), true) ) } diff --git a/go/ql/src/Security/CWE-209/StackTraceExposure.ql b/go/ql/src/Security/CWE-209/StackTraceExposure.ql index 45d58f442c32..4be0867b33f5 100644 --- a/go/ql/src/Security/CWE-209/StackTraceExposure.ql +++ b/go/ql/src/Security/CWE-209/StackTraceExposure.ql @@ -15,6 +15,7 @@ import go import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag +private import semmle.go.controlflow.Guards /** * A flag indicating the program is in debug or development mode, or that stack @@ -56,11 +57,7 @@ module StackTraceExposureConfig implements DataFlow::ConfigSig { // Sanitize everything controlled by an is-debug-mode check. // Imprecision: I don't try to guess which arm of a branch is intended // to mean debug mode, and which is production mode. - exists(ControlFlow::ConditionGuardNode cgn | - cgn.ensures(any(DebugModeFlag f).getAFlag().getANode(), _) - | - cgn.dominates(node.getBasicBlock()) - ) + flagControls(any(DebugModeFlag f), node.getBasicBlock()) } predicate observeDiffInformedIncrementalMode() { any() } diff --git a/go/ql/src/Security/CWE-295/DisabledCertificateCheck.ql b/go/ql/src/Security/CWE-295/DisabledCertificateCheck.ql index bc05c8cf4aa8..80b19196cc1b 100644 --- a/go/ql/src/Security/CWE-295/DisabledCertificateCheck.ql +++ b/go/ql/src/Security/CWE-295/DisabledCertificateCheck.ql @@ -24,6 +24,7 @@ import go import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag +private import semmle.go.controlflow.Guards /** * Holds if `part` becomes a part of `whole`, either by (local) data flow or by being incorporated @@ -49,13 +50,6 @@ class InsecureCertificateFlag extends FlagKind { } } -/** - * Gets a control-flow node that represents a (likely) flag controlling an insecure certificate setup. - */ -ControlFlow::ConditionGuardNode getAnInsecureCertificateCheck() { - result.ensures(any(InsecureCertificateFlag f).getAFlag().getANode(), _) -} - /** * Returns flag kinds relevant to this query: a generic security feature flag, or one * specifically controlling insecure certificate configuration. @@ -80,7 +74,7 @@ where f.hasQualifiedName("crypto/tls", "Config", "InsecureSkipVerify") and rhs.getBoolValue() = true and // exclude writes guarded by a feature flag - not [getASecurityFeatureFlagCheck(), getAnInsecureCertificateCheck()].dominatesNode(w) and + not flagControls(securityOrTlsVersionFlag(), w.getBasicBlock()) and // exclude results in functions whose name documents the insecurity not exists(FuncDef fn | fn = w.getRoot() | isSecurityOrCertificateConfigFlag(fn.getEnclosingFunction*().getName()) diff --git a/go/ql/src/Security/CWE-327/InsecureTLS.ql b/go/ql/src/Security/CWE-327/InsecureTLS.ql index b5d8a81f3d82..559af3060c1d 100644 --- a/go/ql/src/Security/CWE-327/InsecureTLS.ql +++ b/go/ql/src/Security/CWE-327/InsecureTLS.ql @@ -13,6 +13,7 @@ import go import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag +private import semmle.go.controlflow.Guards /** * Holds if it is insecure to assign TLS version `val` named `name` to `tls.Config` field `fieldName`. @@ -246,13 +247,6 @@ class LegacyTlsVersionFlag extends FlagKind { override string getAFlagName() { result.regexpMatch("(?i).*(old|intermediate|legacy).*") } } -/** - * Gets a control-flow node that represents a (likely) flag controlling TLS version selection. - */ -ControlFlow::ConditionGuardNode getALegacyTlsVersionCheck() { - result.ensures(any(LegacyTlsVersionFlag f).getAFlag().getANode(), _) -} - /** * Returns flag kinds relevant to this query: a generic security feature flag, or one * specifically controlling TLS version selection. @@ -275,8 +269,7 @@ where isInsecureTlsCipherFlow(source.asPathNode2(), sink.asPathNode2(), message) ) and // Exclude sources or sinks guarded by a feature or legacy flag - not [getASecurityFeatureFlagCheck(), getALegacyTlsVersionCheck()] - .dominatesNode([source, sink].getNode().asInstruction()) and + not flagControls(securityOrTlsVersionFlag(), [source, sink].getNode().getBasicBlock()) and // Exclude sources or sinks that occur lexically within a block related to a feature or legacy flag not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), securityOrTlsVersionFlag()) and // Exclude results in functions whose name documents insecurity diff --git a/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.ql b/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.ql index 554e271492e4..7770b05f1e22 100644 --- a/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.ql +++ b/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.ql @@ -14,20 +14,18 @@ import go import SensitiveConditionBypass +private import semmle.go.controlflow.Guards from - ControlFlow::ConditionGuardNode guard, DataFlow::Node sensitiveSink, - SensitiveExpr::Classification classification, DataFlow::Node source, DataFlow::Node operand, - ComparisonExpr comp + DataFlow::Node sensitiveSink, SensitiveExpr::Classification classification, DataFlow::Node source, + DataFlow::Node operand, ComparisonExpr comp where // there should be a flow between source and the operand sink Flow::flow(source, operand) and // both the operand should belong to the same comparison expression operand.asExpr() = comp.getAnOperand() and - // get the ConditionGuardNode corresponding to the comparison expr. - guard.getCondition() = comp and // the sink `sensitiveSink` should be sensitive, isSensitive(sensitiveSink, classification) and - // the guard should control the sink - guard.dominates(sensitiveSink.getBasicBlock()) + // the comparison should control the sink + comp.(Guard).controls(sensitiveSink.getBasicBlock(), _) select comp, "This sensitive comparision check can potentially be bypassed." diff --git a/go/ql/src/experimental/CWE-942/CorsMisconfiguration.ql b/go/ql/src/experimental/CWE-942/CorsMisconfiguration.ql index d0ef8514d5f9..2fada12af834 100644 --- a/go/ql/src/experimental/CWE-942/CorsMisconfiguration.ql +++ b/go/ql/src/experimental/CWE-942/CorsMisconfiguration.ql @@ -14,6 +14,7 @@ import go import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag +private import semmle.go.controlflow.Guards /** * A flag indicating a check for satisfied permissions or test configuration. @@ -59,11 +60,7 @@ module UntrustedToAllowOriginHeaderConfig implements DataFlow::ConfigSig { } predicate isBarrier(DataFlow::Node node) { - exists(ControlFlow::ConditionGuardNode cgn | - cgn.ensures(any(AllowedFlag f).getAFlag().getANode(), _) - | - cgn.dominates(node.getBasicBlock()) - ) + flagControls(any(AllowedFlag f), node.getBasicBlock()) } predicate isSink(DataFlow::Node sink) { isSinkHW(sink, _) } @@ -171,9 +168,9 @@ class MapRead extends DataFlow::ElementReadNode { module FromUntrustedConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource } - predicate isSink(DataFlow::Node sink) { isSinkCgn(sink, _) } + predicate isSink(DataFlow::Node sink) { isSinkGuard(sink, _) } - additional predicate isSinkCgn(DataFlow::Node sink, ControlFlow::ConditionGuardNode cgn) { + additional predicate isSinkGuard(DataFlow::Node sink, Guard guard) { exists(IfStmt ifs | exists(Expr operand | operand = ifs.getCondition().getAChildExpr*() and @@ -202,7 +199,7 @@ module FromUntrustedConfig implements DataFlow::ConfigSig { ) ) | - cgn.getCondition() = ifs.getCondition() + guard = ifs.getCondition() ) } } @@ -217,10 +214,10 @@ module FromUntrustedFlow = TaintTracking::Global; * Holds if the provided `allowOriginHW` is also destination of a `ActiveThreatModelSource`. */ predicate flowsToGuardedByCheckOnUntrusted(DataFlow::ExprNode allowOriginHW) { - exists(DataFlow::Node sink, ControlFlow::ConditionGuardNode cgn | - FromUntrustedFlow::flowTo(sink) and FromUntrustedConfig::isSinkCgn(sink, cgn) + exists(DataFlow::Node sink, Guard guard | + FromUntrustedFlow::flowTo(sink) and FromUntrustedConfig::isSinkGuard(sink, guard) | - cgn.dominates(allowOriginHW.getBasicBlock()) + guard.controls(allowOriginHW.getBasicBlock(), _) ) } @@ -233,9 +230,5 @@ where allowOriginIsNull(allowOriginHW, message) ) and not flowsToGuardedByCheckOnUntrusted(allowOriginHW) and - not exists(ControlFlow::ConditionGuardNode cgn | - cgn.ensures(any(AllowedFlag f).getAFlag().getANode(), _) - | - cgn.dominates(allowOriginHW.getBasicBlock()) - ) + not flagControls(any(AllowedFlag f), allowOriginHW.getBasicBlock()) select allowOriginHW, message diff --git a/go/ql/src/experimental/IntegerOverflow/RangeAnalysis.qll b/go/ql/src/experimental/IntegerOverflow/RangeAnalysis.qll index 2d7e249fbc03..615ea485ac42 100644 --- a/go/ql/src/experimental/IntegerOverflow/RangeAnalysis.qll +++ b/go/ql/src/experimental/IntegerOverflow/RangeAnalysis.qll @@ -1,4 +1,5 @@ import go +private import semmle.go.controlflow.Guards Expr getAUse(SsaDefinition def) { result = def.getVariable().getAUse().(IR::EvalInstruction).getExpr() @@ -40,12 +41,12 @@ float getAnUpperBound(Expr expr) { if //if a condition expression exists before and one of the operand happens to be the identifier, we use this condition expression to narrow down the range. exists( - ControlFlow::ConditionGuardNode n, DataFlow::Node lesser, DataFlow::Node greater, + Guard n, boolean branch, DataFlow::Node lesser, DataFlow::Node greater, ReachableBasicBlock bb | - n.ensuresLeq(lesser, greater, _) and + guardEnsuresLeq(n, branch, lesser, greater, _) and IR::evalExprInstruction(lesser.asExpr()) = v.getAUse() and - n.dominates(bb) and + n.controls(bb, branch) and bb.getANode() = IR::evalExprInstruction(identifier) and not exists(Expr e | e = v.getAUse().(IR::EvalInstruction).getExpr() and @@ -54,12 +55,12 @@ float getAnUpperBound(Expr expr) { ) then exists( - ControlFlow::ConditionGuardNode n, ReachableBasicBlock bb, DataFlow::Node lesser, + Guard n, boolean branch, ReachableBasicBlock bb, DataFlow::Node lesser, DataFlow::Node greater, int bias | - n.dominates(bb) and + n.controls(bb, branch) and bb.getANode() = IR::evalExprInstruction(identifier) and - n.ensuresLeq(lesser, greater, bias) and + guardEnsuresLeq(n, branch, lesser, greater, bias) and v.getAUse() = IR::evalExprInstruction(lesser.asExpr()) and not exists(Expr e | e = v.getAUse().(IR::EvalInstruction).getExpr() and @@ -191,12 +192,12 @@ float getALowerBound(Expr expr) { //if exists a condition expression before this identifier if exists( - ControlFlow::ConditionGuardNode n, DataFlow::Node greater, DataFlow::Node lesser, + Guard n, boolean branch, DataFlow::Node greater, DataFlow::Node lesser, ReachableBasicBlock bb | - n.ensuresLeq(lesser, greater, _) and + guardEnsuresLeq(n, branch, lesser, greater, _) and IR::evalExprInstruction(greater.asExpr()) = v.getAUse() and - n.dominates(bb) and + n.controls(bb, branch) and bb.getANode() = IR::evalExprInstruction(identifier) and not exists(Expr e | e = v.getAUse().(IR::EvalInstruction).getExpr() and @@ -205,12 +206,12 @@ float getALowerBound(Expr expr) { ) then exists( - ControlFlow::ConditionGuardNode n, ReachableBasicBlock bb, DataFlow::Node lesser, + Guard n, boolean branch, ReachableBasicBlock bb, DataFlow::Node lesser, DataFlow::Node greater, int bias, float lbs | - n.dominates(bb) and + n.controls(bb, branch) and bb.getANode() = IR::evalExprInstruction(identifier) and - n.ensuresLeq(lesser, greater, bias) and + guardEnsuresLeq(n, branch, lesser, greater, bias) and v.getAUse() = IR::evalExprInstruction(greater.asExpr()) and not exists(Expr e | e = v.getAUse().(IR::EvalInstruction).getExpr() and diff --git a/go/ql/test/experimental/CWE-942/CONSISTENCY/DataFlowConsistency.expected b/go/ql/test/experimental/CWE-942/CONSISTENCY/DataFlowConsistency.expected index 2bd9c1d6185a..20e32467bcbb 100644 --- a/go/ql/test/experimental/CWE-942/CONSISTENCY/DataFlowConsistency.expected +++ b/go/ql/test/experimental/CWE-942/CONSISTENCY/DataFlowConsistency.expected @@ -13,3 +13,4 @@ reverseRead | CorsMisconfiguration.go:170:14:170:16 | implicit-deref req | Origin of readStep is missing a PostUpdateNode. | | CorsMisconfiguration.go:194:17:194:19 | implicit-deref req | Origin of readStep is missing a PostUpdateNode. | | CorsMisconfiguration.go:206:14:206:16 | implicit-deref req | Origin of readStep is missing a PostUpdateNode. | +| CorsMisconfiguration.go:219:14:219:16 | implicit-deref req | Origin of readStep is missing a PostUpdateNode. | diff --git a/go/ql/test/experimental/CWE-942/CorsMisconfiguration.expected b/go/ql/test/experimental/CWE-942/CorsMisconfiguration.expected index 442324639216..2e53a2b710cd 100644 --- a/go/ql/test/experimental/CWE-942/CorsMisconfiguration.expected +++ b/go/ql/test/experimental/CWE-942/CorsMisconfiguration.expected @@ -7,6 +7,7 @@ | CorsMisconfiguration.go:53:4:53:44 | call to Set | access-control-allow-origin header is set to a user-defined value, and access-control-allow-credentials is set to `true` | | CorsMisconfiguration.go:60:4:60:56 | call to Set | access-control-allow-origin header is set to a user-defined value, and access-control-allow-credentials is set to `true` | | CorsMisconfiguration.go:67:5:67:57 | call to Set | access-control-allow-origin header is set to a user-defined value, and access-control-allow-credentials is set to `true` | +| CorsMisconfiguration.go:224:5:224:57 | call to Set | access-control-allow-origin header is set to a user-defined value, and access-control-allow-credentials is set to `true` | | RsCors.go:11:21:11:59 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` | | RsCors.go:31:23:31:61 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` | | RsCors.go:59:20:59:58 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` | diff --git a/go/ql/test/experimental/CWE-942/CorsMisconfiguration.go b/go/ql/test/experimental/CWE-942/CorsMisconfiguration.go index 506db53a2621..5f8c80083cc1 100644 --- a/go/ql/test/experimental/CWE-942/CorsMisconfiguration.go +++ b/go/ql/test/experimental/CWE-942/CorsMisconfiguration.go @@ -215,6 +215,16 @@ func main() { w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-Token, X-Client") w.Header().Set("Access-Control-Allow-Credentials", "true") }) + http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + origin := req.Header.Get("origin") + switch req.Method { + case "allowed": + w.Header().Set("Access-Control-Allow-Origin", origin) + default: + w.Header().Set("Access-Control-Allow-Origin", origin) // $ Alert + } + w.Header().Set("Access-Control-Allow-Credentials", "true") + }) } } diff --git a/go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.expected b/go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.expected new file mode 100644 index 000000000000..91c519f464be --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.expected @@ -0,0 +1,273 @@ +controlsResult +| guards.go:12:7:12:15 | ...<... | negative | true | +| guards.go:12:7:12:15 | ...<... | positive | false | +| guards.go:12:7:12:15 | ...<... | zero | false | +| guards.go:14:7:14:16 | ...==... | positive | false | +| guards.go:14:7:14:16 | ...==... | zero | true | +| guards.go:16:2:17:18 | case clause | positive | true | +| guards.go:23:2:24:21 | case clause | tagged one or two | false | +| guards.go:23:2:24:21 | case clause | tagged other | false | +| guards.go:23:2:24:21 | case clause | tagged zero | true | +| guards.go:25:2:26:27 | case clause | tagged other | false | +| guards.go:27:2:28:22 | case clause | tagged other | true | +| guards.go:34:2:35:30 | case clause | tagged false boolean | true | +| guards.go:34:7:34:11 | value | tagged false boolean | false | +| guards.go:38:2:39:29 | case clause | tagged true boolean | true | +| guards.go:38:7:38:11 | value | tagged true boolean | true | +| guards.go:42:2:43:33 | case clause | tagged false comparison | true | +| guards.go:42:7:42:17 | ...<... | tagged false comparison | false | +| guards.go:48:5:48:5 | a | compound true | true | +| guards.go:48:5:48:17 | ...&&... | compound false | false | +| guards.go:48:5:48:17 | ...&&... | compound true | true | +| guards.go:48:11:48:16 | ...\|\|... | compound true | true | +| guards.go:56:5:56:20 | ...==... | upcast | true | +| guards.go:59:5:59:19 | ...==... | downcast | true | +| guards.go:62:5:62:22 | ...==... | upcast narrow | true | +| guards.go:68:5:68:15 | ...<=... | above ten | false | +| guards.go:68:5:68:15 | ...<=... | at most ten | true | +| guards.go:73:5:73:15 | ...<=... | at least ten | true | +| guards.go:73:5:73:15 | ...<=... | below ten | false | +| guards.go:81:10:81:14 | value | ssa copy | true | +| guards.go:82:5:82:8 | copy | ssa copy | true | +| guards.go:87:5:87:9 | value | ssa phi | true | +| guards.go:90:5:90:7 | phi | ssa phi | true | +| guards.go:96:5:96:18 | ...==... | nil pointer | true | +| guards.go:96:5:96:18 | ...==... | non-nil pointer | false | +| guards.go:102:5:102:26 | ...==... | typed nil pointer | true | +| guards.go:106:5:106:21 | ...==... | new result | true | +| guards.go:110:5:110:27 | ...==... | make result | true | +| guards.go:114:5:114:24 | ...==... | composite literal | true | +| guards.go:118:5:118:32 | ...==... | errors new | true | +| guards.go:122:5:122:32 | ...==... | fmt errorf | true | +| guards.go:126:5:126:28 | ...==... | source non-nil return | true | +| guards.go:137:2:138:26 | case clause | default case | false | +| guards.go:137:2:138:26 | case clause | non-default case | true | +| guards.go:139:2:140:22 | case clause | default case | true | +| guards.go:146:2:147:29 | case clause | default before case | true | +| guards.go:148:2:149:28 | case clause | case after default | true | +| guards.go:148:2:149:28 | case clause | default before case | false | +| guards.go:155:2:156:22 | case clause | default only | true | +| guards.go:162:7:162:11 | value | expressionless case | true | +| guards.go:162:7:162:11 | value | expressionless default | false | +| guards.go:164:2:165:32 | case clause | expressionless default | true | +| guards.go:183:5:183:21 | call to isNotNil | non-variadic wrapper | true | +| guards.go:194:5:194:32 | call to namedResultIsNotNil | named result wrapper | true | +| guards.go:208:5:208:43 | call to conditionalNamedResultIsNotNil | conditional named result wrapper | true | +| guards.go:224:5:224:31 | call to isNotNil | method wrapper | true | +| guards.go:230:5:230:50 | call to isNotNil | method expression wrapper | true | +| guards.go:236:5:236:31 | call to isNotNil | interface method wrapper | true | +| guards.go:248:5:248:24 | call to isNotNil | receiver method wrapper | true | +| guards.go:254:5:254:44 | call to isNotNil | receiver method expression wrapper | true | +| guards.go:264:5:264:24 | call to isNotNil | promoted receiver method wrapper | true | +| guards.go:270:5:270:24 | call to isNotNil | implicit address receiver method wrapper | true | +| guards.go:282:5:282:22 | call to isTrue | implicit dereference receiver method wrapper | true | +| guards.go:296:5:296:18 | call to check | indirect wrapper | true | +| guards.go:306:5:306:20 | call to hasArgs | implicit variadic wrapper | true | +| guards.go:309:5:309:24 | call to hasArgs | explicit variadic wrapper | true | +valueControlsResult +| guards.go:12:7:12:11 | value | negative | Upper bound -1 | +| guards.go:12:7:12:11 | value | positive | Lower bound 0 | +| guards.go:12:7:12:11 | value | zero | Lower bound 0 | +| guards.go:12:7:12:15 | ...<... | negative | true | +| guards.go:12:7:12:15 | ...<... | positive | false | +| guards.go:12:7:12:15 | ...<... | zero | false | +| guards.go:14:7:14:11 | value | positive | not 0 | +| guards.go:14:7:14:11 | value | zero | 0 | +| guards.go:14:7:14:16 | ...==... | positive | false | +| guards.go:14:7:14:16 | ...==... | zero | true | +| guards.go:16:2:17:18 | case clause | positive | true | +| guards.go:22:9:22:13 | value | tagged one or two | not 0 | +| guards.go:22:9:22:13 | value | tagged other | not 0 | +| guards.go:22:9:22:13 | value | tagged other | not 1 | +| guards.go:22:9:22:13 | value | tagged other | not 2 | +| guards.go:22:9:22:13 | value | tagged zero | 0 | +| guards.go:23:2:24:21 | case clause | tagged one or two | false | +| guards.go:23:2:24:21 | case clause | tagged other | false | +| guards.go:23:2:24:21 | case clause | tagged zero | true | +| guards.go:25:2:26:27 | case clause | tagged other | false | +| guards.go:27:2:28:22 | case clause | tagged other | true | +| guards.go:34:2:35:30 | case clause | tagged false boolean | true | +| guards.go:34:7:34:11 | value | tagged false boolean | false | +| guards.go:38:2:39:29 | case clause | tagged true boolean | true | +| guards.go:38:7:38:11 | value | tagged true boolean | true | +| guards.go:42:2:43:33 | case clause | tagged false comparison | true | +| guards.go:42:7:42:12 | number | tagged false comparison | Lower bound 10 | +| guards.go:42:7:42:17 | ...<... | tagged false comparison | false | +| guards.go:48:5:48:5 | a | compound true | true | +| guards.go:48:5:48:17 | ...&&... | compound false | false | +| guards.go:48:5:48:17 | ...&&... | compound true | true | +| guards.go:48:11:48:16 | ...\|\|... | compound true | true | +| guards.go:56:5:56:15 | type conversion | upcast | 0 | +| guards.go:56:5:56:20 | ...==... | upcast | true | +| guards.go:56:11:56:14 | wide | upcast | 0 | +| guards.go:59:5:59:14 | type conversion | downcast | 0 | +| guards.go:59:5:59:19 | ...==... | downcast | true | +| guards.go:62:5:62:17 | type conversion | upcast narrow | 0 | +| guards.go:62:5:62:22 | ...==... | upcast narrow | true | +| guards.go:62:11:62:16 | narrow | upcast narrow | 0 | +| guards.go:68:5:68:9 | value | above ten | Lower bound 11 | +| guards.go:68:5:68:9 | value | at most ten | Upper bound 10 | +| guards.go:68:5:68:15 | ...<=... | above ten | false | +| guards.go:68:5:68:15 | ...<=... | at most ten | true | +| guards.go:73:5:73:15 | ...<=... | at least ten | true | +| guards.go:73:5:73:15 | ...<=... | below ten | false | +| guards.go:73:11:73:15 | value | at least ten | Lower bound 10 | +| guards.go:73:11:73:15 | value | below ten | Upper bound 9 | +| guards.go:81:10:81:14 | value | ssa copy | true | +| guards.go:82:5:82:8 | copy | ssa copy | true | +| guards.go:87:5:87:9 | value | ssa phi | true | +| guards.go:90:5:90:7 | phi | ssa phi | true | +| guards.go:96:5:96:11 | pointer | nil pointer | null | +| guards.go:96:5:96:11 | pointer | non-nil pointer | not null | +| guards.go:96:5:96:18 | ...==... | nil pointer | true | +| guards.go:96:5:96:18 | ...==... | non-nil pointer | false | +| guards.go:102:5:102:11 | pointer | typed nil pointer | null | +| guards.go:102:5:102:26 | ...==... | typed nil pointer | true | +| guards.go:106:5:106:9 | value | new result | not null | +| guards.go:106:5:106:21 | ...==... | new result | true | +| guards.go:110:5:110:9 | value | make result | not null | +| guards.go:110:5:110:27 | ...==... | make result | true | +| guards.go:110:14:110:27 | call to make | composite literal | no exception | +| guards.go:110:14:110:27 | call to make | errors new | no exception | +| guards.go:110:14:110:27 | call to make | fmt errorf | no exception | +| guards.go:110:14:110:27 | call to make | make result | no exception | +| guards.go:110:14:110:27 | call to make | source non-nil return | no exception | +| guards.go:114:5:114:9 | value | composite literal | not null | +| guards.go:114:5:114:24 | ...==... | composite literal | true | +| guards.go:118:5:118:9 | value | errors new | not null | +| guards.go:118:5:118:32 | ...==... | errors new | true | +| guards.go:118:14:118:32 | call to New | errors new | no exception | +| guards.go:118:14:118:32 | call to New | fmt errorf | no exception | +| guards.go:118:14:118:32 | call to New | source non-nil return | no exception | +| guards.go:122:5:122:9 | value | fmt errorf | not null | +| guards.go:122:5:122:32 | ...==... | fmt errorf | true | +| guards.go:122:14:122:32 | call to Errorf | fmt errorf | no exception | +| guards.go:122:14:122:32 | call to Errorf | source non-nil return | no exception | +| guards.go:126:5:126:9 | value | source non-nil return | not null | +| guards.go:126:5:126:28 | ...==... | source non-nil return | true | +| guards.go:126:14:126:28 | call to returnsNonNil | source non-nil return | no exception | +| guards.go:136:9:136:13 | value | default case | not 0 | +| guards.go:136:9:136:13 | value | non-default case | 0 | +| guards.go:137:2:138:26 | case clause | default case | false | +| guards.go:137:2:138:26 | case clause | non-default case | true | +| guards.go:139:2:140:22 | case clause | default case | true | +| guards.go:145:9:145:13 | value | case after default | 0 | +| guards.go:145:9:145:13 | value | default before case | not 0 | +| guards.go:146:2:147:29 | case clause | default before case | true | +| guards.go:148:2:149:28 | case clause | case after default | true | +| guards.go:148:2:149:28 | case clause | default before case | false | +| guards.go:155:2:156:22 | case clause | default only | true | +| guards.go:162:7:162:11 | value | expressionless case | true | +| guards.go:162:7:162:11 | value | expressionless default | false | +| guards.go:164:2:165:32 | case clause | expressionless default | true | +| guards.go:183:5:183:21 | call to isNotNil | non-variadic wrapper | no exception | +| guards.go:183:5:183:21 | call to isNotNil | non-variadic wrapper | true | +| guards.go:183:14:183:20 | pointer | non-variadic wrapper | not null | +| guards.go:194:5:194:32 | call to namedResultIsNotNil | named result wrapper | no exception | +| guards.go:194:5:194:32 | call to namedResultIsNotNil | named result wrapper | true | +| guards.go:194:25:194:31 | pointer | named result wrapper | not null | +| guards.go:208:5:208:43 | call to conditionalNamedResultIsNotNil | conditional named result wrapper | no exception | +| guards.go:208:5:208:43 | call to conditionalNamedResultIsNotNil | conditional named result wrapper | true | +| guards.go:208:36:208:42 | pointer | conditional named result wrapper | not null | +| guards.go:224:5:224:31 | call to isNotNil | method wrapper | no exception | +| guards.go:224:5:224:31 | call to isNotNil | method wrapper | true | +| guards.go:224:24:224:30 | pointer | method wrapper | not null | +| guards.go:230:5:230:50 | call to isNotNil | method expression wrapper | no exception | +| guards.go:230:5:230:50 | call to isNotNil | method expression wrapper | true | +| guards.go:230:43:230:49 | pointer | method expression wrapper | not null | +| guards.go:236:5:236:31 | call to isNotNil | interface method wrapper | no exception | +| guards.go:236:5:236:31 | call to isNotNil | interface method wrapper | true | +| guards.go:248:5:248:13 | validator | receiver method wrapper | not null | +| guards.go:248:5:248:24 | call to isNotNil | receiver method wrapper | no exception | +| guards.go:248:5:248:24 | call to isNotNil | receiver method wrapper | true | +| guards.go:254:5:254:44 | call to isNotNil | receiver method expression wrapper | no exception | +| guards.go:254:5:254:44 | call to isNotNil | receiver method expression wrapper | true | +| guards.go:254:35:254:43 | validator | receiver method expression wrapper | not null | +| guards.go:264:5:264:24 | call to isNotNil | promoted receiver method wrapper | no exception | +| guards.go:264:5:264:24 | call to isNotNil | promoted receiver method wrapper | true | +| guards.go:270:5:270:24 | call to isNotNil | implicit address receiver method wrapper | no exception | +| guards.go:270:5:270:24 | call to isNotNil | implicit address receiver method wrapper | true | +| guards.go:282:5:282:22 | call to isTrue | implicit dereference receiver method wrapper | no exception | +| guards.go:282:5:282:22 | call to isTrue | implicit dereference receiver method wrapper | true | +| guards.go:296:5:296:18 | call to check | indirect wrapper | no exception | +| guards.go:296:5:296:18 | call to check | indirect wrapper | true | +| guards.go:306:5:306:20 | call to hasArgs | implicit variadic wrapper | no exception | +| guards.go:306:5:306:20 | call to hasArgs | implicit variadic wrapper | true | +| guards.go:309:5:309:24 | call to hasArgs | explicit variadic wrapper | no exception | +| guards.go:309:5:309:24 | call to hasArgs | explicit variadic wrapper | true | +| guards.go:309:13:309:20 | pointers | explicit variadic wrapper | not null | +| guards.go:321:2:321:22 | call to ensureNotNil | after assertion | no exception | +| guards.go:321:15:321:21 | pointer | after assertion | not null | +ensuresEqResult +| guards.go:14:7:14:16 | ...==... | 0 = value | true | +| guards.go:14:7:14:16 | ...==... | value = 0 | true | +| guards.go:23:2:24:21 | case clause | 0 = value | true | +| guards.go:23:2:24:21 | case clause | value = 0 | true | +| guards.go:56:5:56:20 | ...==... | 0 = type conversion | true | +| guards.go:56:5:56:20 | ...==... | type conversion = 0 | true | +| guards.go:59:5:59:19 | ...==... | 0 = type conversion | true | +| guards.go:59:5:59:19 | ...==... | type conversion = 0 | true | +| guards.go:62:5:62:22 | ...==... | 0 = type conversion | true | +| guards.go:62:5:62:22 | ...==... | type conversion = 0 | true | +| guards.go:96:5:96:18 | ...==... | nil = pointer | true | +| guards.go:96:5:96:18 | ...==... | pointer = nil | true | +| guards.go:102:5:102:26 | ...==... | pointer = type conversion | true | +| guards.go:102:5:102:26 | ...==... | type conversion = pointer | true | +| guards.go:106:5:106:21 | ...==... | call to new = value | true | +| guards.go:106:5:106:21 | ...==... | value = call to new | true | +| guards.go:110:5:110:27 | ...==... | call to make = value | true | +| guards.go:110:5:110:27 | ...==... | value = call to make | true | +| guards.go:114:5:114:24 | ...==... | &... = value | true | +| guards.go:114:5:114:24 | ...==... | value = &... | true | +| guards.go:118:5:118:32 | ...==... | call to New = value | true | +| guards.go:118:5:118:32 | ...==... | value = call to New | true | +| guards.go:122:5:122:32 | ...==... | call to Errorf = value | true | +| guards.go:122:5:122:32 | ...==... | value = call to Errorf | true | +| guards.go:126:5:126:28 | ...==... | call to returnsNonNil = value | true | +| guards.go:126:5:126:28 | ...==... | value = call to returnsNonNil | true | +| guards.go:137:2:138:26 | case clause | 0 = value | true | +| guards.go:137:2:138:26 | case clause | value = 0 | true | +| guards.go:148:2:149:28 | case clause | 0 = value | true | +| guards.go:148:2:149:28 | case clause | value = 0 | true | +| guards.go:171:2:172:13 | case clause | 0 = value | true | +| guards.go:171:2:172:13 | case clause | value = 0 | true | +| guards.go:179:9:179:22 | ...!=... | nil = pointer | false | +| guards.go:179:9:179:22 | ...!=... | pointer = nil | false | +| guards.go:189:10:189:23 | ...!=... | nil = pointer | false | +| guards.go:189:10:189:23 | ...!=... | pointer = nil | false | +| guards.go:200:5:200:18 | ...==... | nil = pointer | true | +| guards.go:200:5:200:18 | ...==... | pointer = nil | true | +| guards.go:220:9:220:22 | ...!=... | nil = pointer | false | +| guards.go:220:9:220:22 | ...!=... | pointer = nil | false | +| guards.go:244:9:244:24 | ...!=... | nil = validator | false | +| guards.go:244:9:244:24 | ...!=... | validator = nil | false | +| guards.go:278:9:278:25 | ...==... | true = validator | true | +| guards.go:278:9:278:25 | ...==... | validator = true | true | +| guards.go:302:9:302:21 | ...!=... | nil = values | false | +| guards.go:302:9:302:21 | ...!=... | values = nil | false | +| guards.go:315:5:315:18 | ...==... | nil = pointer | true | +| guards.go:315:5:315:18 | ...==... | pointer = nil | true | +| guards.go:328:9:328:22 | ...!=... | nil = pointer | false | +| guards.go:328:9:328:22 | ...!=... | pointer = nil | false | +| guards.go:353:5:353:32 | ...==... | 0 = call to modelZeroGuard | true | +| guards.go:353:5:353:32 | ...==... | call to modelZeroGuard = 0 | true | +| guards.go:356:5:356:35 | ...!=... | 0 = call to modelNotZeroGuard | false | +| guards.go:356:5:356:35 | ...!=... | call to modelNotZeroGuard = 0 | false | +| guards.go:359:5:359:34 | ...==... | call to modelNullGuard = nil | true | +| guards.go:359:5:359:34 | ...==... | nil = call to modelNullGuard | true | +| guards.go:362:5:362:37 | ...!=... | call to modelNotNullGuard = nil | false | +| guards.go:362:5:362:37 | ...!=... | nil = call to modelNotNullGuard | false | +defaultCaseResult +| guards.go:16:2:17:18 | case clause | positive | true | +| guards.go:27:2:28:22 | case clause | tagged other | true | +| guards.go:139:2:140:22 | case clause | default case | true | +| guards.go:146:2:147:29 | case clause | default before case | true | +| guards.go:155:2:156:22 | case clause | default only | true | +| guards.go:164:2:165:32 | case clause | expressionless default | true | +modelBarrierResult +| guards.go:351:27:351:33 | pointer | model bool | model-bool | +| guards.go:354:27:354:33 | pointer | model zero | model-zero | +| guards.go:357:31:357:37 | pointer | model not zero | model-not-zero | +| guards.go:360:27:360:33 | pointer | model null | model-null | +| guards.go:363:31:363:37 | pointer | model not null | model-not-null | +| guards.go:366:34:366:40 | pointer | model no exception | model-no-exception | diff --git a/go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.ext.yml b/go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.ext.yml new file mode 100644 index 000000000000..4635146b411c --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.ext.yml @@ -0,0 +1,11 @@ +extensions: + - addsTo: + pack: codeql/go-all + extensible: barrierGuardModel + data: + - ["github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/Guards", "", False, "modelBoolGuard", "", "", "Argument[0]", "true", "model-bool", "manual"] + - ["github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/Guards", "", False, "modelZeroGuard", "", "", "Argument[0]", "zero", "model-zero", "manual"] + - ["github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/Guards", "", False, "modelNotZeroGuard", "", "", "Argument[0]", "not-zero", "model-not-zero", "manual"] + - ["github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/Guards", "", False, "modelNullGuard", "", "", "Argument[0]", "null", "model-null", "manual"] + - ["github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/Guards", "", False, "modelNotNullGuard", "", "", "Argument[0]", "not-null", "model-not-null", "manual"] + - ["github.com/github/codeql-go/ql/test/library-tests/semmle/go/controlflow/Guards", "", False, "modelExceptionGuard", "", "", "Argument[0]", "no-exception", "model-no-exception", "manual"] diff --git a/go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.ql b/go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.ql new file mode 100644 index 000000000000..6deebb5bc379 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.ql @@ -0,0 +1,51 @@ +import go +import semmle.go.controlflow.Guards +private import semmle.go.dataflow.ExternalFlow + +predicate sinkCall(DataFlow::CallNode call, string label) { + call.getTarget().getName() = "sink" and + label = call.getArgument(0).getExactValue() +} + +query predicate controlsResult(Guard guard, string label, string outcome) { + exists(DataFlow::CallNode call, boolean branch | + sinkCall(call, label) and + guard.controls(call.getBasicBlock(), branch) and + outcome = branch.toString() + ) +} + +query predicate valueControlsResult(Guard guard, string label, string outcome) { + exists(DataFlow::CallNode call, GuardValue value | + sinkCall(call, label) and + guard.valueControls(call.getBasicBlock(), value) and + outcome = value.toString() + ) +} + +query predicate ensuresEqResult(Guard guard, string label, string outcome) { + exists(boolean branch, DataFlow::Node left, DataFlow::Node right | + guardEnsuresEq(guard, branch, left, right) and + label = left.toString() + " = " + right.toString() and + outcome = branch.toString() + ) +} + +query predicate defaultCaseResult(Guard guard, string label, string outcome) { + exists(DataFlow::CallNode call, GuardValue value, CaseClause defaultCase | + sinkCall(call, label) and + defaultCase = any(ExpressionSwitchStmt switch).getDefault() and + guard = defaultCase and + guard.valueControls(call.getBasicBlock(), value) and + outcome = value.toString() + ) +} + +query predicate modelBarrierResult(DataFlow::Node node, string label, string kind) { + exists(DataFlow::CallNode call | + call.getTarget().getName() = "valueSink" and + label = call.getArgument(0).getExactValue() and + node = call.getArgument(1) and + barrierNode(node, kind, _) + ) +} diff --git a/go/ql/test/library-tests/semmle/go/controlflow/Guards/guards.go b/go/ql/test/library-tests/semmle/go/controlflow/Guards/guards.go new file mode 100644 index 000000000000..c2ebd2c9ed43 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/controlflow/Guards/guards.go @@ -0,0 +1,367 @@ +package guards + +import ( + "errors" + "fmt" +) + +func sink(string) {} + +func taglessSwitch(value int) { + switch { + case value < 0: + sink("negative") + case value == 0: + sink("zero") + default: + sink("positive") + } +} + +func taggedSwitch(value int) { + switch value { + case 0: + sink("tagged zero") + case 1, 2: + sink("tagged one or two") + default: + sink("tagged other") + } +} + +func taggedBooleanSwitch(value bool, number int) { + switch false { + case value: + sink("tagged false boolean") + } + switch true { + case value: + sink("tagged true boolean") + } + switch false { + case number < 10: + sink("tagged false comparison") + } +} + +func compoundCondition(a, b, c bool) { + if a && (b || c) { + sink("compound true") + } else { + sink("compound false") + } +} + +func conversions(wide int16, narrow int8) { + if int32(wide) == 0 { + sink("upcast") + } + if int8(wide) == 0 { + sink("downcast") + } + if int16(narrow) == 0 { + sink("upcast narrow") + } +} + +func nonStrictComparisons(value int) { + if value <= 10 { + sink("at most ten") + } else { + sink("above ten") + } + if 10 <= value { + sink("at least ten") + } else { + sink("below ten") + } +} + +func ssaGuards(value bool) { + copy := value + if copy { + sink("ssa copy") + } + + phi := false + if value { + phi = true + } + if phi { + sink("ssa phi") + } +} + +func nilGuards(pointer *int, value any) { + if pointer == nil { + sink("nil pointer") + } else { + sink("non-nil pointer") + } + + if pointer == (*int)(nil) { + sink("typed nil pointer") // pointer is null + } + + if value == new(int) { + sink("new result") // value is not null + } + + if value == make(chan int) { + sink("make result") // value is not null + } + + if value == &struct{}{} { + sink("composite literal") + } + + if value == errors.New("error") { + sink("errors new") // value is not null + } + + if value == fmt.Errorf("error") { + sink("fmt errorf") // value is not null + } + + if value == returnsNonNil() { + sink("source non-nil return") // value is not null + } +} + +func returnsNonNil() *int { + return new(int) +} + +func defaultCase(value int) { + switch value { + case 0: + sink("non-default case") + default: + sink("default case") // default case matches + } +} + +func defaultBeforeCase(value int) { + switch value { + default: + sink("default before case") // default case matches + case 0: + sink("case after default") + } +} + +func defaultOnly(value int) { + switch value { + default: + sink("default only") // default case matches + } +} + +func expressionlessDefault(value bool) { + switch { + case value: + sink("expressionless case") + default: + sink("expressionless default") // default case matches + } +} + +func fallthroughToDefault(value int) { + switch value { + case 0: + fallthrough + default: + sink("fallthrough default") // No default-match fact: this is also reached by fallthrough. + } +} + +func isNotNil(pointer *int) bool { + return pointer != nil +} + +func wrapperGuard(pointer *int) { + if isNotNil(pointer) { + sink("non-variadic wrapper") + } +} + +func namedResultIsNotNil(pointer *int) (valid bool) { + valid = pointer != nil + return +} + +func namedResultWrapper(pointer *int) { + if namedResultIsNotNil(pointer) { + sink("named result wrapper") // pointer is not null + } +} + +func conditionalNamedResultIsNotNil(pointer *int) (valid bool) { + if pointer == nil { + return + } + valid = true + return +} + +func conditionalNamedResultWrapper(pointer *int) { + if conditionalNamedResultIsNotNil(pointer) { + sink("conditional named result wrapper") // pointer is not null + } +} + +type pointerValidator interface { + isNotNil(*int) bool +} + +type concreteValidator struct{} + +func (concreteValidator) isNotNil(pointer *int) bool { + return pointer != nil +} + +func methodWrapper(validator concreteValidator, pointer *int) { + if validator.isNotNil(pointer) { + sink("method wrapper") // pointer is not null + } +} + +func methodExpressionWrapper(validator concreteValidator, pointer *int) { + if concreteValidator.isNotNil(validator, pointer) { + sink("method expression wrapper") // pointer is not null + } +} + +func interfaceMethodWrapper(validator pointerValidator, pointer *int) { + if validator.isNotNil(pointer) { + sink("interface method wrapper") // No pointer fact: the concrete target is unknown. + } +} + +type receiverValidator struct{} + +func (validator *receiverValidator) isNotNil() bool { + return validator != nil +} + +func receiverMethodWrapper(validator *receiverValidator) { + if validator.isNotNil() { + sink("receiver method wrapper") // validator is not null + } +} + +func receiverMethodExpressionWrapper(validator *receiverValidator) { + if (*receiverValidator).isNotNil(validator) { + sink("receiver method expression wrapper") // validator is not null + } +} + +type promotedReceiverValidator struct { + *receiverValidator +} + +func promotedReceiverMethodWrapper(validator promotedReceiverValidator) { + if validator.isNotNil() { + sink("promoted receiver method wrapper") // No fact for the outer validator. + } +} + +func implicitAddressReceiverMethodWrapper(validator receiverValidator) { + if validator.isNotNil() { + sink("implicit address receiver method wrapper") // No fact for the addressed value. + } +} + +type booleanReceiverValidator bool + +func (validator booleanReceiverValidator) isTrue() bool { + return validator == true +} + +func implicitDereferenceReceiverMethodWrapper(validator *booleanReceiverValidator) { + if validator.isTrue() { + sink("implicit dereference receiver method wrapper") // No Boolean fact for the pointer. + } +} + +func alwaysTrue(*int) bool { + return true +} + +func indirectWrapper(pointer *int, useValidation bool) { + check := alwaysTrue + if useValidation { + check = isNotNil + } + if check(pointer) { + sink("indirect wrapper") // No pointer fact: the possible target may not validate it. + } +} + +func hasArgs(values ...*int) bool { + return values != nil +} + +func variadicWrapper(pointer *int, pointers []*int) { + if hasArgs(pointer) { + sink("implicit variadic wrapper") // No pointer fact: only the synthetic slice is non-null. + } + if hasArgs(pointers...) { + sink("explicit variadic wrapper") + } +} + +func ensureNotNil(pointer *int) { + if pointer == nil { + panic("nil pointer") + } +} + +func exceptionGuard(pointer *int) { + ensureNotNil(pointer) + sink("after assertion") // pointer is not null +} + +func valueSink(string, any) {} + +func modelBoolGuard(pointer *int) bool { + return pointer != nil +} + +func modelZeroGuard(pointer *int) int { + return 0 +} + +func modelNotZeroGuard(pointer *int) int { + return 1 +} + +func modelNullGuard(pointer *int) *int { + return nil +} + +func modelNotNullGuard(pointer *int) *int { + return pointer +} + +func modelExceptionGuard(pointer *int) {} + +func modelGuards(pointer *int) { + if modelBoolGuard(pointer) { + valueSink("model bool", pointer) + } + if modelZeroGuard(pointer) == 0 { + valueSink("model zero", pointer) // model-zero barrier + } + if modelNotZeroGuard(pointer) != 0 { + valueSink("model not zero", pointer) // model-not-zero barrier + } + if modelNullGuard(pointer) == nil { + valueSink("model null", pointer) // model-null barrier + } + if modelNotNullGuard(pointer) != nil { + valueSink("model not null", pointer) // model-not-null barrier + } + modelExceptionGuard(pointer) + valueSink("model no exception", pointer) // model-no-exception barrier +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go b/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go index a7a595509f9c..8f7fa74bc0a7 100644 --- a/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go +++ b/go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go @@ -842,7 +842,7 @@ func test() { s := source() isValid := !guardBool(s) if isValid { - sink(s) // $ SPURIOUS: hasValueFlow="s" + sink(s) } else { sink(s) // $ hasValueFlow="s" } diff --git a/go/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected b/go/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected index 732b9cd5caed..a16e9e115283 100644 --- a/go/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected +++ b/go/ql/test/query-tests/Security/CWE-209/StackTraceExposure.expected @@ -1,8 +1,13 @@ +#select +| test.go:18:10:18:12 | buf | test.go:15:28:15:30 | buf [postupdate] | test.go:18:10:18:12 | buf | HTTP response depends on $@ and may be exposed to an external user. | test.go:15:28:15:30 | buf [postupdate] | stack trace information | +| test.go:46:11:46:13 | buf | test.go:15:28:15:30 | buf [postupdate] | test.go:46:11:46:13 | buf | HTTP response depends on $@ and may be exposed to an external user. | test.go:15:28:15:30 | buf [postupdate] | stack trace information | edges | test.go:15:28:15:30 | buf [postupdate] | test.go:18:10:18:12 | buf | provenance | | +| test.go:15:28:15:30 | buf [postupdate] | test.go:21:29:21:31 | buf | provenance | | +| test.go:21:29:21:31 | buf | test.go:46:11:46:13 | buf | provenance | | nodes | test.go:15:28:15:30 | buf [postupdate] | semmle.label | buf [postupdate] | | test.go:18:10:18:12 | buf | semmle.label | buf | +| test.go:21:29:21:31 | buf | semmle.label | buf | +| test.go:46:11:46:13 | buf | semmle.label | buf | subpaths -#select -| test.go:18:10:18:12 | buf | test.go:15:28:15:30 | buf [postupdate] | test.go:18:10:18:12 | buf | HTTP response depends on $@ and may be exposed to an external user. | test.go:15:28:15:30 | buf [postupdate] | stack trace information | diff --git a/go/ql/test/query-tests/Security/CWE-209/test.go b/go/ql/test/query-tests/Security/CWE-209/test.go index 6a1b6c298ba2..94231dd62b22 100644 --- a/go/ql/test/query-tests/Security/CWE-209/test.go +++ b/go/ql/test/query-tests/Security/CWE-209/test.go @@ -39,4 +39,10 @@ func handlePanic(w http.ResponseWriter, r *http.Request) { if printStackTrace { w.Write(buf) } + switch "production" { + case "debug": + w.Write(buf) + default: + w.Write(buf) // $ Alert + } } diff --git a/go/ql/test/query-tests/Security/CWE-295/DisabledCertificateCheck/main.go b/go/ql/test/query-tests/Security/CWE-295/DisabledCertificateCheck/main.go index 152ece5ba466..ece238eb8933 100644 --- a/go/ql/test/query-tests/Security/CWE-295/DisabledCertificateCheck/main.go +++ b/go/ql/test/query-tests/Security/CWE-295/DisabledCertificateCheck/main.go @@ -83,3 +83,16 @@ func good3(i int) *http.Transport { } return nil } + +func reusedFeatureFlag(cfg *tls.Config, enableSecurity bool) { + switch true { + case enableSecurity: + _ = enableSecurity + } + + if enableSecurity { + _ = cfg + } else { + cfg.InsecureSkipVerify = true // OK + } +} diff --git a/java/ql/lib/semmle/code/java/controlflow/Guards.qll b/java/ql/lib/semmle/code/java/controlflow/Guards.qll index 56dc9aa55e50..a4a864ed60d9 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Guards.qll @@ -342,6 +342,12 @@ private module LogicInput_v3 implements GuardsImpl::LogicInputSig { private import semmle.code.java.dataflow.IntegerGuards as IntegerGuards import LogicInput_v2 + predicate implicitReturnDefinition(GuardsInput::NonOverridableMethod method, SsaDefinition def) { + none() + } + + predicate additionalSsaDefinitionValue(SsaDefinition def, GuardValue value) { none() } + predicate rangeGuard(GuardsImpl::PreGuard guard, GuardValue val, Expr e, int k, boolean upper) { IntegerGuards::rangeGuard(guard, val.asBooleanValue(), e, k, upper) } diff --git a/shared/controlflow/codeql/controlflow/Guards.qll b/shared/controlflow/codeql/controlflow/Guards.qll index e12535b4b328..a4f42c750923 100644 --- a/shared/controlflow/codeql/controlflow/Guards.qll +++ b/shared/controlflow/codeql/controlflow/Guards.qll @@ -78,6 +78,14 @@ signature module InputSig; @@ -1263,6 +1358,13 @@ module Make< ) { validReturnInCustomGuardToRank(maxRank(result), result, ppos, retval, val) or + exists(SsaDefinition ret, SsaParameterInit param, Guard guard, GuardValue guardVal | + implicitReturnDefinition(result, ret) and + param.getParameter() = result.getParameter(ppos) and + ssaImpliesGuard(ret, retval, guard, guardVal) and + ReturnImplies::ssaControls(param, val, guard, guardVal) + ) + or exists(SsaParameterInit param, Guard g0, GuardValue v0 | param.getParameter() = result.getParameter(ppos) and guardDirectlyControlsExit(g0, v0) and @@ -1362,6 +1464,13 @@ module Make< validReturnInValidationWrapper(ret, ppos, retval, par) ) or + exists(SsaDefinition ret, SsaParameterInit param, Guard guard, GuardValue guardVal | + implicitReturnDefinition(result, ret) and + param.getParameter() = result.getParameter(ppos) and + ssaImpliesGuard(ret, retval, guard, guardVal) and + guardChecksDef(guard, param, guardVal, par) + ) + or exists(SsaParameterInit param, BasicBlock bb, Guard guard, GuardValue val | param.getParameter() = result.getParameter(ppos) and guardChecksDef(guard, param, val, par) and @@ -1427,6 +1536,16 @@ module Make< dominatingEdge(guard, succ) and succ.dominates(bb) ) + or + exists(BasicBlock outcomeBlock | + booleanOutcomeBlock(this, outcomeBlock, v.asBooleanValue()) and + outcomeBlock.dominates(bb) + ) + or + exists(BasicBlock outcomeBlock | + caseOutcomeBlock(this, outcomeBlock, v.asBooleanValue()) and + outcomeBlock.dominates(bb) + ) } /**