From 6ce8e0510f9e58bfa55e305ccafd354d1468950c Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 6 Nov 2023 13:00:44 +0100 Subject: [PATCH 1/3] Ruby: Adopt shared type tracking library --- config/identical-files.json | 6 +- .../TypeTrackingConsistency.ql | 8 + ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 17 +- .../lib/codeql/ruby/dataflow/FlowSummary.qll | 2 +- .../dataflow/internal/DataFlowDispatch.qll | 13 +- .../dataflow/internal/DataFlowPrivate.qll | 34 +- .../ruby/dataflow/internal/DataFlowPublic.qll | 35 +- .../lib/codeql/ruby/frameworks/XmlParsing.qll | 2 +- .../ruby/frameworks/rack/internal/App.qll | 2 +- .../frameworks/rack/internal/Response.qll | 2 +- .../ruby/regexp/internal/RegExpTracking.qll | 12 +- .../InsecureDownloadCustomizations.qll | 2 +- ruby/ql/lib/codeql/ruby/security/OpenSSL.qll | 2 +- .../StackTraceExposureCustomizations.qll | 9 +- .../UnsafeCodeConstructionCustomizations.qll | 11 +- .../UnsafeHtmlConstructionCustomizations.qll | 11 +- ...ShellCommandConstructionCustomizations.qll | 11 +- .../ruby/typetracking/ApiGraphShared.qll | 72 +-- .../codeql/ruby/typetracking/TypeTracker.qll | 104 +++-- .../ruby/typetracking/TypeTrackerSpecific.qll | 383 ++-------------- .../codeql/ruby/typetracking/TypeTracking.qll | 7 + .../internal/TypeTrackingImpl.qll | 430 ++++++++++++++++++ .../InlineTypeTrackingFlowTest.qll | 2 +- .../type-tracking-array-flow.expected | 1 - .../type-tracking-hash-flow.expected | 14 - .../dataflow/params/TypeTracker.expected | 95 ++-- .../dataflow/type-tracker/TypeTracker.ql | 2 +- 27 files changed, 696 insertions(+), 593 deletions(-) create mode 100644 ruby/ql/consistency-queries/TypeTrackingConsistency.ql create mode 100644 ruby/ql/lib/codeql/ruby/typetracking/TypeTracking.qll create mode 100644 ruby/ql/lib/codeql/ruby/typetracking/internal/TypeTrackingImpl.qll diff --git a/config/identical-files.json b/config/identical-files.json index 144031d5a686..53b286fb8a10 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -462,10 +462,6 @@ "ruby/ql/lib/codeql/ruby/security/internal/SensitiveDataHeuristics.qll", "swift/ql/lib/codeql/swift/security/internal/SensitiveDataHeuristics.qll" ], - "TypeTracker": [ - "python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll", - "ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll" - ], "SummaryTypeTracker": [ "python/ql/lib/semmle/python/dataflow/new/internal/SummaryTypeTracker.qll", "ruby/ql/lib/codeql/ruby/typetracking/internal/SummaryTypeTracker.qll" @@ -534,4 +530,4 @@ "python/ql/test/experimental/dataflow/model-summaries/InlineTaintTest.ext.yml", "python/ql/test/experimental/dataflow/model-summaries/NormalDataflowTest.ext.yml" ] -} +} \ No newline at end of file diff --git a/ruby/ql/consistency-queries/TypeTrackingConsistency.ql b/ruby/ql/consistency-queries/TypeTrackingConsistency.ql new file mode 100644 index 000000000000..a0b52db09e92 --- /dev/null +++ b/ruby/ql/consistency-queries/TypeTrackingConsistency.ql @@ -0,0 +1,8 @@ +import codeql.ruby.DataFlow +import codeql.ruby.typetracking.internal.TypeTrackingImpl + +private module ConsistencyChecksInput implements ConsistencyChecksInputSig { + predicate unreachableNodeExclude(DataFlow::Node n) { n instanceof DataFlow::PostUpdateNode } +} + +import ConsistencyChecks diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 6d00a1eaebb7..6a494845e2d8 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -8,8 +8,7 @@ private import codeql.ruby.AST private import codeql.ruby.DataFlow private import codeql.ruby.typetracking.ApiGraphShared -private import codeql.ruby.typetracking.TypeTracker -private import codeql.ruby.typetracking.TypeTrackerSpecific as TypeTrackerSpecific +private import codeql.ruby.typetracking.internal.TypeTrackingImpl private import codeql.ruby.controlflow.CfgNodes private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch @@ -1050,9 +1049,9 @@ module API { /** INTERNAL USE ONLY. */ module Internal { - private module Shared = ApiGraphShared; + private module MkShared = ApiGraphShared; - import Shared + import MkShared /** Gets the API node corresponding to the module/class object for `mod`. */ bindingset[mod] @@ -1093,7 +1092,7 @@ module API { private predicate needsSinkNode(DataFlow::Node node) { node instanceof DataFlowPrivate::ArgumentNode or - TypeTrackerSpecific::basicStoreStep(node, _, _) + TypeTrackingInput::storeStep(node, _, _) or node = any(DataFlow::CallableNode callable).getAReturnNode() or @@ -1203,10 +1202,8 @@ module API { cached predicate contentEdge(Node pred, DataFlow::Content content, Node succ) { - exists( - DataFlow::Node object, DataFlow::Node value, TypeTrackerSpecific::TypeTrackerContent c - | - TypeTrackerSpecific::basicLoadStep(object, value, c) and + exists(DataFlow::Node object, DataFlow::Node value, DataFlow::ContentSet c | + TypeTrackingInput::loadStep(object, value, c) and content = c.getAStoreContent() and not c.isSingleton(any(DataFlow::Content::AttributeNameContent k)) and // `x -> x.foo` with content "foo" @@ -1214,7 +1211,7 @@ module API { succ = getForwardStartNode(value) or // Based on `object.c = value` generate `object -> value` with content `c` - TypeTrackerSpecific::basicStoreStep(value, object, c) and + TypeTrackingInput::storeStep(value, object, c) and content = c.getAStoreContent() and pred = getForwardOrBackwardEndNode(getALocalSourceStrict(object)) and succ = MkSinkNode(value) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll index 7e7004c6e619..ae625fd44eeb 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll @@ -2,7 +2,7 @@ import codeql.ruby.AST private import codeql.ruby.CFG -private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.TypeTracking import codeql.ruby.DataFlow private import internal.FlowSummaryImpl as Impl private import internal.DataFlowDispatch diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll index 0668b6a357fb..7bec16f2c378 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll @@ -1,8 +1,7 @@ private import codeql.ruby.AST private import codeql.ruby.CFG private import DataFlowPrivate -private import codeql.ruby.typetracking.TypeTracker -private import codeql.ruby.typetracking.TypeTrackerSpecific as TypeTrackerSpecific +private import codeql.ruby.typetracking.internal.TypeTrackingImpl private import codeql.ruby.ast.internal.Module private import FlowSummaryImpl as FlowSummaryImpl private import FlowSummaryImplSpecific as FlowSummaryImplSpecific @@ -657,7 +656,7 @@ private module TrackInstanceInput implements CallGraphConstruction::InputSig { predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { // We exclude steps into `self` parameters. For those, we instead rely on the type of // the enclosing module - StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary) and + smallStepNoCall(nodeFrom, nodeTo, summary) and isNotSelf(nodeTo) or // We exclude steps into type checked variables. For those, we instead rely on the @@ -667,7 +666,7 @@ private module TrackInstanceInput implements CallGraphConstruction::InputSig { } predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { - StepSummary::smallstepCall(nodeFrom, nodeTo, summary) + smallStepCall(nodeFrom, nodeTo, summary) } class StateProj = Unit; @@ -941,7 +940,7 @@ private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruct RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, CfgNodes::ExprCfgNode nodeFromPreExpr | - TypeTrackerSpecific::callStep(call, arg, p) and + callStep(call, arg, p) and nodeTo.getPreUpdateNode() = arg and summary.toString() = "return" and ( @@ -965,13 +964,13 @@ private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruct } predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { - StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary) + smallStepNoCall(nodeFrom, nodeTo, summary) or localFlowStep(nodeFrom, nodeTo, summary) } predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) { - StepSummary::smallstepCall(nodeFrom, nodeTo, summary) + smallStepCall(nodeFrom, nodeTo, summary) or paramReturnFlow(nodeFrom, nodeTo, summary) } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index 1341ed667926..70cd41c8001a 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -113,7 +113,7 @@ module LocalFlow { SsaImpl::lastRefBeforeRedefExt(def, bb, i, next.getDefinitionExt()) and exprFrom = bb.getNode(i) and exprFrom.getExpr() instanceof VariableReadAccess and - exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()] + exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNodeImpl).getPreUpdateNode().asExpr()] ) } @@ -159,7 +159,7 @@ module LocalFlow { firstReadExt(def, nodeTo.asExpr()) or // Flow from post-update read to next read - localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo) + localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNodeImpl).getPreUpdateNode(), nodeTo) or // Flow into phi (read) SSA definition node from def localFlowSsaInputFromDef(nodeFrom, def, nodeTo) @@ -456,7 +456,7 @@ private predicate splatArgumentAt(CfgNodes::ExprNodes::CallCfgNode c, int pos) { /** A collection of cached types and predicates to be evaluated in the same stage. */ cached private module Cached { - private import codeql.ruby.typetracking.TypeTrackerSpecific as TypeTrackerSpecific + private import codeql.ruby.typetracking.internal.TypeTrackingImpl cached newtype TNode = @@ -575,14 +575,10 @@ private module Cached { VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo) } + /** Holds if `n` wraps an SSA definition without ingoing flow. */ private predicate entrySsaDefinition(SsaDefinitionExtNode n) { - n = LocalFlow::getParameterDefNode(_) - or - exists(SsaImpl::DefinitionExt def | def = n.getDefinitionExt() | - def instanceof Ssa::SelfDefinition - or - def instanceof Ssa::CapturedEntryDefinition - ) + n.getDefinitionExt() = + any(SsaImpl::WriteDefinition def | not def.(Ssa::WriteDefinition).assigns(_)) } pragma[nomagic] @@ -599,6 +595,16 @@ private module Cached { ) } + private predicate isStoreTargetNode(Node n) { + TypeTrackingInput::storeStep(_, n, _) + or + TypeTrackingInput::loadStoreStep(_, n, _, _) + or + TypeTrackingInput::withContentStepImpl(_, n, _) + or + TypeTrackingInput::withoutContentStepImpl(_, n, _) + } + cached predicate isLocalSourceNode(Node n) { n instanceof TSourceParameterNode @@ -614,11 +620,9 @@ private module Cached { entrySsaDefinition(n) and not LocalFlow::localFlowSsaParamInput(_, n) or - TypeTrackerSpecific::basicStoreStep(_, n, _) - or - TypeTrackerSpecific::basicLoadStep(_, n, _) + isStoreTargetNode(n) or - TypeTrackerSpecific::basicLoadStoreStep(_, n, _, _) + TypeTrackingInput::loadStep(_, n, _) } cached @@ -633,7 +637,7 @@ private module Cached { TElementContentOfTypeContent(string type, Boolean includeUnknown) { type = any(Content::KnownElementContent content).getIndex().getValueType() } or - TNoContentSet() // Only used by type-tracking + deprecated TNoContentSet() // Only used by type-tracking cached class TContentSet = diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll index 1e3077f5bd41..075375990bf1 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -2,10 +2,9 @@ private import codeql.ruby.AST private import DataFlowDispatch private import DataFlowPrivate private import codeql.ruby.CFG -private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.internal.TypeTrackingImpl private import codeql.ruby.dataflow.SSA private import FlowSummaryImpl as FlowSummaryImpl -private import SsaImpl as SsaImpl private import codeql.ruby.ApiGraphs /** @@ -245,7 +244,7 @@ class LocalSourceNode extends Node { /** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */ pragma[inline] - predicate flowsTo(Node nodeTo) { hasLocalSource(nodeTo, this) } + predicate flowsTo(Node nodeTo) { flowsTo(this, nodeTo) } /** * Gets a node that this node may flow to using one heap and/or interprocedural step. @@ -261,14 +260,14 @@ class LocalSourceNode extends Node { * See `TypeBackTracker` for more details about how to use this. */ pragma[inline] - LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) } + LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t = t2.step(result, this) } /** * Gets a node to which data may flow from this node in zero or * more local data-flow steps. */ pragma[inline] - Node getALocalUse() { hasLocalSource(result, this) } + Node getALocalUse() { flowsTo(this, result) } /** Gets a method call where this node flows to the receiver. */ CallNode getAMethodCall() { Cached::hasMethodCall(this, result, _) } @@ -354,21 +353,21 @@ class PostUpdateNode extends Node instanceof PostUpdateNodeImpl { Node getPreUpdateNode() { result = super.getPreUpdateNode() } } +/** An SSA definition, viewed as a node in a data flow graph. */ +class SsaDefinitionNode extends Node instanceof SsaDefinitionExtNode { + Ssa::Definition def; + + SsaDefinitionNode() { this = TSsaDefinitionExtNode(def) } + + /** Gets the underlying SSA definition. */ + Ssa::Definition getDefinition() { result = def } + + /** Gets the underlying variable. */ + Variable getVariable() { result = def.getSourceVariable() } +} + cached private module Cached { - cached - predicate hasLocalSource(Node sink, Node source) { - // Declaring `source` to be a `SourceNode` currently causes a redundant check in the - // recursive case, so instead we check it explicitly here. - source = sink and - source instanceof LocalSourceNode - or - exists(Node mid | - hasLocalSource(mid, source) and - localFlowStepTypeTracker(mid, sink) - ) - } - cached predicate hasMethodCall(LocalSourceNode source, CallNode call, string name) { source.flowsTo(call.getReceiver()) and diff --git a/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll b/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll index 74d783fbbc5d..91dc0ce5efad 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/XmlParsing.qll @@ -5,7 +5,7 @@ private import codeql.ruby.Concepts private import codeql.ruby.AST private import codeql.ruby.DataFlow -private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.TypeTracking private import codeql.ruby.ApiGraphs private import codeql.ruby.controlflow.CfgNodes as CfgNodes diff --git a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll index 41df3ac0e642..94c4a6fb0373 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll @@ -6,7 +6,7 @@ private import codeql.ruby.AST private import codeql.ruby.ApiGraphs private import codeql.ruby.Concepts private import codeql.ruby.DataFlow -private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.TypeTracking private import Response::Private as RP /** diff --git a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Response.qll b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Response.qll index 5f5b16014537..495a0b1b6da1 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Response.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Response.qll @@ -7,7 +7,7 @@ private import codeql.ruby.ApiGraphs private import codeql.ruby.Concepts private import codeql.ruby.controlflow.CfgNodes::ExprNodes private import codeql.ruby.DataFlow -private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.TypeTracking private import App as A /** Contains implementation details for modeling `Rack::Response`. */ diff --git a/ruby/ql/lib/codeql/ruby/regexp/internal/RegExpTracking.qll b/ruby/ql/lib/codeql/ruby/regexp/internal/RegExpTracking.qll index d4f8f17db347..5d70b7d164a2 100644 --- a/ruby/ql/lib/codeql/ruby/regexp/internal/RegExpTracking.qll +++ b/ruby/ql/lib/codeql/ruby/regexp/internal/RegExpTracking.qll @@ -17,7 +17,7 @@ private import codeql.ruby.AST as Ast private import codeql.ruby.CFG private import codeql.ruby.DataFlow private import codeql.ruby.controlflow.CfgNodes -private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.internal.TypeTrackingImpl private import codeql.ruby.ApiGraphs private import codeql.ruby.Concepts private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate @@ -67,7 +67,7 @@ private signature module TypeTrackInputSig { * Provides a version of type tracking where we first prune for reachable nodes, * before doing the type tracking computation. */ -private module TypeTrack { +private module PrunedTypeTrack { private predicate additionalStep( DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo ) { @@ -130,10 +130,10 @@ private module TypeTrack { TypeTracker t, DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo ) { exists(StepSummary summary | - StepSummary::step(nodeFrom, nodeTo, summary) and + step(nodeFrom, nodeTo, summary) and reached(nodeFrom, t) and reached(nodeTo, result) and - result = t.append(summary) + result = append(t, summary) ) or additionalStep(nodeFrom, nodeTo) and @@ -195,7 +195,7 @@ private module StringTypeTrackInput implements TypeTrackInputSig { * This is used to figure out where `start` is evaluated as a regular expression against an input string, * or where `start` is compiled into a regular expression. */ -private predicate trackStrings = TypeTrack::track/2; +private predicate trackStrings = PrunedTypeTrack::track/2; /** Holds if `strConst` flows to a regex compilation (tracked by `t`), where the resulting regular expression is stored in `reg`. */ pragma[nomagic] @@ -222,7 +222,7 @@ private module RegTypeTrackInput implements TypeTrackInputSig { * Gets a node that has been tracked from the regular expression `start` to some node. * This is used to figure out where `start` is executed against an input string. */ -private predicate trackRegs = TypeTrack::track/2; +private predicate trackRegs = PrunedTypeTrack::track/2; /** Gets a node that references a regular expression. */ private DataFlow::LocalSourceNode trackRegexpType(TypeTracker t) { diff --git a/ruby/ql/lib/codeql/ruby/security/InsecureDownloadCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/InsecureDownloadCustomizations.qll index cbd4dd88e7a4..0ac51d38ed69 100644 --- a/ruby/ql/lib/codeql/ruby/security/InsecureDownloadCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/InsecureDownloadCustomizations.qll @@ -7,7 +7,7 @@ private import codeql.ruby.AST private import codeql.ruby.DataFlow private import codeql.ruby.Concepts -private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.TypeTracking private import codeql.ruby.frameworks.Files private import codeql.ruby.frameworks.core.IO diff --git a/ruby/ql/lib/codeql/ruby/security/OpenSSL.qll b/ruby/ql/lib/codeql/ruby/security/OpenSSL.qll index cbabdc8782a0..0b57184bda9a 100644 --- a/ruby/ql/lib/codeql/ruby/security/OpenSSL.qll +++ b/ruby/ql/lib/codeql/ruby/security/OpenSSL.qll @@ -7,7 +7,7 @@ private import internal.CryptoAlgorithmNames private import codeql.ruby.Concepts private import codeql.ruby.DataFlow private import codeql.ruby.ApiGraphs -private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.TypeTracking bindingset[algorithmString] private string algorithmRegex(string algorithmString) { diff --git a/ruby/ql/lib/codeql/ruby/security/StackTraceExposureCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/StackTraceExposureCustomizations.qll index 636a4ca5ac47..7ada32e549d8 100644 --- a/ruby/ql/lib/codeql/ruby/security/StackTraceExposureCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/StackTraceExposureCustomizations.qll @@ -6,6 +6,7 @@ private import codeql.ruby.AST private import codeql.ruby.Concepts private import codeql.ruby.DataFlow +private import codeql.ruby.dataflow.SSA private import codeql.ruby.controlflow.CfgNodes private import codeql.ruby.frameworks.core.Kernel @@ -29,10 +30,10 @@ module StackTraceExposure { */ class BacktraceCall extends Source, DataFlow::CallNode { BacktraceCall() { - exists(DataFlow::LocalSourceNode varAccess | - varAccess.asExpr().(ExprNodes::VariableReadAccessCfgNode).getExpr().getVariable() = - any(RescueClause rc).getVariableExpr().(VariableAccess).getVariable() and - varAccess.flowsTo(this.getReceiver()) + exists(DataFlow::SsaDefinitionNode ssaDef | + ssaDef.getDefinition().(Ssa::WriteDefinition).getWriteAccess().getAstNode() = + any(RescueClause rc).getVariableExpr() and + ssaDef.(DataFlow::LocalSourceNode).flowsTo(this.getReceiver()) ) and this.getMethodName() = ["backtrace", "backtrace_locations"] } diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeCodeConstructionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeCodeConstructionCustomizations.qll index 1324f5ec5541..746a380e62cf 100644 --- a/ruby/ql/lib/codeql/ruby/security/UnsafeCodeConstructionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/UnsafeCodeConstructionCustomizations.qll @@ -8,6 +8,7 @@ private import ruby private import codeql.ruby.ApiGraphs private import codeql.ruby.frameworks.core.Gem::Gem as Gem private import codeql.ruby.Concepts as Concepts +private import codeql.ruby.typetracking.TypeTracking /** * Module containing sources, sinks, and sanitizers for code constructed from library input. @@ -39,22 +40,20 @@ module UnsafeCodeConstruction { /** Gets a node that is eventually executed as code at `codeExec`. */ DataFlow::Node getANodeExecutedAsCode(Concepts::CodeExecution codeExec) { - result = getANodeExecutedAsCode(TypeTracker::TypeBackTracker::end(), codeExec) + result = getANodeExecutedAsCode(TypeBackTracker::end(), codeExec) } - import codeql.ruby.typetracking.TypeTracker as TypeTracker + deprecated import codeql.ruby.typetracking.TypeTracker as TypeTracker /** Gets a node that is eventually executed as code at `codeExec`, type-tracked with `t`. */ private DataFlow::LocalSourceNode getANodeExecutedAsCode( - TypeTracker::TypeBackTracker t, Concepts::CodeExecution codeExec + TypeBackTracker t, Concepts::CodeExecution codeExec ) { t.start() and result = codeExec.getCode().getALocalSource() and codeExec.runsArbitraryCode() // methods like `Object.send` is benign here, because of the string-construction the attacker cannot control the entire method name or - exists(TypeTracker::TypeBackTracker t2 | - result = getANodeExecutedAsCode(t2, codeExec).backtrack(t2, t) - ) + exists(TypeBackTracker t2 | result = getANodeExecutedAsCode(t2, codeExec).backtrack(t2, t)) } /** diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeHtmlConstructionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeHtmlConstructionCustomizations.qll index 1be524ad1aec..45a1c00befef 100644 --- a/ruby/ql/lib/codeql/ruby/security/UnsafeHtmlConstructionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/UnsafeHtmlConstructionCustomizations.qll @@ -7,6 +7,7 @@ private import ruby private import codeql.ruby.ApiGraphs private import codeql.ruby.frameworks.core.Gem::Gem as Gem +private import codeql.ruby.typetracking.TypeTracking /** * Module containing sources, sinks, and sanitizers for HTML constructed from library input. @@ -37,21 +38,17 @@ module UnsafeHtmlConstruction { /** Gets a node that eventually ends up in the XSS `sink`. */ private DataFlow::Node getANodeThatEndsInXssSink(ReflectedXss::Sink sink) { - result = getANodeThatEndsInXssSink(TypeTracker::TypeBackTracker::end(), sink) + result = getANodeThatEndsInXssSink(TypeBackTracker::end(), sink) } - private import codeql.ruby.typetracking.TypeTracker as TypeTracker - /** Gets a node that is eventually ends up in the XSS `sink`, type-tracked with `t`. */ private DataFlow::LocalSourceNode getANodeThatEndsInXssSink( - TypeTracker::TypeBackTracker t, ReflectedXss::Sink sink + TypeBackTracker t, ReflectedXss::Sink sink ) { t.start() and result = sink.getALocalSource() or - exists(TypeTracker::TypeBackTracker t2 | - result = getANodeThatEndsInXssSink(t2, sink).backtrack(t2, t) - ) + exists(TypeBackTracker t2 | result = getANodeThatEndsInXssSink(t2, sink).backtrack(t2, t)) } /** diff --git a/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll index b114095cdbd5..be57768c1418 100644 --- a/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/UnsafeShellCommandConstructionCustomizations.qll @@ -9,6 +9,7 @@ private import codeql.ruby.DataFlow private import codeql.ruby.ApiGraphs private import codeql.ruby.frameworks.core.Gem::Gem as Gem private import codeql.ruby.Concepts as Concepts +import codeql.ruby.typetracking.TypeTracking /** * Module containing sources, sinks, and sanitizers for shell command constructed from library input. @@ -44,20 +45,18 @@ module UnsafeShellCommandConstruction { /** Holds if the string constructed at `source` is executed at `shellExec` */ predicate isUsedAsShellCommand(DataFlow::Node source, Concepts::SystemCommandExecution shellExec) { - source = backtrackShellExec(TypeTracker::TypeBackTracker::end(), shellExec) + source = backtrackShellExec(TypeBackTracker::end(), shellExec) } - import codeql.ruby.typetracking.TypeTracker as TypeTracker + deprecated import codeql.ruby.typetracking.TypeTracker as TypeTracker private DataFlow::LocalSourceNode backtrackShellExec( - TypeTracker::TypeBackTracker t, Concepts::SystemCommandExecution shellExec + TypeBackTracker t, Concepts::SystemCommandExecution shellExec ) { t.start() and result = any(DataFlow::Node n | shellExec.isShellInterpreted(n)).getALocalSource() or - exists(TypeTracker::TypeBackTracker t2 | - result = backtrackShellExec(t2, shellExec).backtrack(t2, t) - ) + exists(TypeBackTracker t2 | result = backtrackShellExec(t2, shellExec).backtrack(t2, t)) } /** diff --git a/ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll b/ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll index 7fc0efeaad04..7215116e8ef3 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/ApiGraphShared.qll @@ -5,8 +5,8 @@ */ private import codeql.Locations -private import codeql.ruby.typetracking.TypeTracker -private import TypeTrackerSpecific +private import codeql.ruby.DataFlow +private import codeql.ruby.typetracking.internal.TypeTrackingImpl /** * The signature to use when instantiating `ApiGraphShared`. @@ -37,14 +37,14 @@ signature module ApiGraphSharedSig { * * This node will have outgoing epsilon edges to its type-tracking successors. */ - ApiNode getForwardNode(TypeTrackingNode node, TypeTracker t); + ApiNode getForwardNode(DataFlow::LocalSourceNode node, TypeTracker t); /** * Gets the backward node with the given type-tracking state. * * This node will have outgoing epsilon edges to its type-tracking predecessors. */ - ApiNode getBackwardNode(TypeTrackingNode node, TypeTracker t); + ApiNode getBackwardNode(DataFlow::LocalSourceNode node, TypeTracker t); /** * Gets the sink node corresponding to `node`. @@ -55,7 +55,7 @@ signature module ApiGraphSharedSig { * * Sink nodes have outgoing epsilon edges to the backward nodes corresponding to their local sources. */ - ApiNode getSinkNode(Node node); + ApiNode getSinkNode(DataFlow::Node node); /** * Holds if a language-specific epsilon edge `pred -> succ` should be generated. @@ -72,7 +72,9 @@ module ApiGraphShared { /** Gets a local source of `node`. */ bindingset[node] pragma[inline_late] - TypeTrackingNode getALocalSourceStrict(Node node) { result = node.getALocalSource() } + DataFlow::LocalSourceNode getALocalSourceStrict(DataFlow::Node node) { + result = node.getALocalSource() + } cached private module Cached { @@ -85,23 +87,21 @@ module ApiGraphShared { cached predicate epsilonEdge(ApiNode pred, ApiNode succ) { exists( - StepSummary summary, TypeTrackingNode predNode, TypeTracker predState, - TypeTrackingNode succNode, TypeTracker succState + StepSummary summary, DataFlow::LocalSourceNode predNode, TypeTracker predState, + DataFlow::LocalSourceNode succNode, TypeTracker succState | - StepSummary::stepCall(predNode, succNode, summary) - or - StepSummary::stepNoCall(predNode, succNode, summary) + step(predNode, succNode, summary) | pred = getForwardNode(predNode, predState) and - succState = StepSummary::append(predState, summary) and + succState = append(predState, summary) and succ = getForwardNode(succNode, succState) or succ = getBackwardNode(predNode, predState) and // swap order for backward flow - succState = StepSummary::append(predState, summary) and + succState = append(predState, summary) and pred = getBackwardNode(succNode, succState) // swap order for backward flow ) or - exists(Node sink, TypeTrackingNode localSource | + exists(DataFlow::Node sink, DataFlow::LocalSourceNode localSource | pred = getSinkNode(sink) and localSource = getALocalSourceStrict(sink) and succ = getBackwardStartNode(localSource) @@ -121,39 +121,39 @@ module ApiGraphShared { /** Gets the API node to use when starting forward flow from `source` */ cached - ApiNode forwardStartNode(TypeTrackingNode source) { - result = getForwardNode(source, TypeTracker::end(false)) + ApiNode forwardStartNode(DataFlow::LocalSourceNode source) { + result = getForwardNode(source, noContentTypeTracker(false)) } /** Gets the API node to use when starting backward flow from `sink` */ cached - ApiNode backwardStartNode(TypeTrackingNode sink) { + ApiNode backwardStartNode(DataFlow::LocalSourceNode sink) { // There is backward flow A->B iff there is forward flow B->A. // The starting point of backward flow corresponds to the end of a forward flow, and vice versa. - result = getBackwardNode(sink, TypeTracker::end(_)) + result = getBackwardNode(sink, noContentTypeTracker(_)) } /** Gets `node` as a data flow source. */ cached - TypeTrackingNode asSourceCached(ApiNode node) { node = forwardEndNode(result) } + DataFlow::LocalSourceNode asSourceCached(ApiNode node) { node = forwardEndNode(result) } /** Gets `node` as a data flow sink. */ cached - Node asSinkCached(ApiNode node) { node = getSinkNode(result) } + DataFlow::Node asSinkCached(ApiNode node) { node = getSinkNode(result) } } private import Cached /** Gets an API node corresponding to the end of forward-tracking to `localSource`. */ pragma[nomagic] - private ApiNode forwardEndNode(TypeTrackingNode localSource) { - result = getForwardNode(localSource, TypeTracker::end(_)) + private ApiNode forwardEndNode(DataFlow::LocalSourceNode localSource) { + result = getForwardNode(localSource, noContentTypeTracker(_)) } /** Gets an API node corresponding to the end of backtracking to `localSource`. */ pragma[nomagic] - private ApiNode backwardEndNode(TypeTrackingNode localSource) { - result = getBackwardNode(localSource, TypeTracker::end(false)) + private ApiNode backwardEndNode(DataFlow::LocalSourceNode localSource) { + result = getBackwardNode(localSource, noContentTypeTracker(false)) } /** Gets a node reachable from `node` by zero or more epsilon edges, including `node` itself. */ @@ -164,18 +164,18 @@ module ApiGraphShared { /** Gets `node` as a data flow sink. */ bindingset[node] pragma[inline_late] - Node asSinkInline(ApiNode node) { result = asSinkCached(node) } + DataFlow::Node asSinkInline(ApiNode node) { result = asSinkCached(node) } /** Gets `node` as a data flow source. */ bindingset[node] pragma[inline_late] - TypeTrackingNode asSourceInline(ApiNode node) { result = asSourceCached(node) } + DataFlow::LocalSourceNode asSourceInline(ApiNode node) { result = asSourceCached(node) } /** Gets a value reachable from `source`. */ bindingset[source] pragma[inline_late] - Node getAValueReachableFromSourceInline(ApiNode source) { - exists(TypeTrackingNode src | + DataFlow::Node getAValueReachableFromSourceInline(ApiNode source) { + exists(DataFlow::LocalSourceNode src | src = asSourceInline(getAnEpsilonSuccessorInline(source)) and src.flowsTo(pragma[only_bind_into](result)) ) @@ -184,7 +184,7 @@ module ApiGraphShared { /** Gets a value that can reach `sink`. */ bindingset[sink] pragma[inline_late] - Node getAValueReachingSinkInline(ApiNode sink) { + DataFlow::Node getAValueReachingSinkInline(ApiNode sink) { backwardStartNode(result) = getAnEpsilonSuccessorInline(sink) } @@ -195,7 +195,7 @@ module ApiGraphShared { */ bindingset[node] pragma[inline_late] - ApiNode getForwardStartNode(Node node) { result = forwardStartNode(node) } + ApiNode getForwardStartNode(DataFlow::Node node) { result = forwardStartNode(node) } /** * Gets the starting point of backtracking from `node`. @@ -204,7 +204,7 @@ module ApiGraphShared { */ bindingset[node] pragma[inline_late] - ApiNode getBackwardStartNode(Node node) { result = backwardStartNode(node) } + ApiNode getBackwardStartNode(DataFlow::Node node) { result = backwardStartNode(node) } /** * Gets a possible ending point of forward-tracking at `node`. @@ -216,7 +216,7 @@ module ApiGraphShared { */ bindingset[node] pragma[inline_late] - ApiNode getForwardEndNode(Node node) { result = forwardEndNode(node) } + ApiNode getForwardEndNode(DataFlow::Node node) { result = forwardEndNode(node) } /** * Gets a possible ending point backtracking to `node`. @@ -228,7 +228,7 @@ module ApiGraphShared { */ bindingset[node] pragma[inline_late] - ApiNode getBackwardEndNode(Node node) { result = backwardEndNode(node) } + ApiNode getBackwardEndNode(DataFlow::Node node) { result = backwardEndNode(node) } /** * Gets a possible eding point of forward or backward tracking at `node`. @@ -237,19 +237,19 @@ module ApiGraphShared { */ bindingset[node] pragma[inline_late] - ApiNode getForwardOrBackwardEndNode(Node node) { + ApiNode getForwardOrBackwardEndNode(DataFlow::Node node) { result = getForwardEndNode(node) or result = getBackwardEndNode(node) } /** Gets an API node for tracking forward starting at `node`. This is the implementation of `DataFlow::LocalSourceNode.track()` */ bindingset[node] pragma[inline_late] - ApiNode getNodeForForwardTracking(Node node) { result = forwardStartNode(node) } + ApiNode getNodeForForwardTracking(DataFlow::Node node) { result = forwardStartNode(node) } /** Gets an API node for backtracking starting at `node`. The implementation of `DataFlow::Node.backtrack()`. */ bindingset[node] pragma[inline_late] - ApiNode getNodeForBacktracking(Node node) { + ApiNode getNodeForBacktracking(DataFlow::Node node) { result = getBackwardStartNode(getALocalSourceStrict(node)) } diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index 001375b4dc54..97e15b4b9f54 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -8,22 +8,22 @@ private module Cached { * A description of a step on an inter-procedural data flow path. */ cached - newtype TStepSummary = + deprecated newtype TStepSummary = LevelStep() or CallStep() or ReturnStep() or - StoreStep(TypeTrackerContent content) { basicStoreStep(_, _, content) } or - LoadStep(TypeTrackerContent content) { basicLoadStep(_, _, content) } or - LoadStoreStep(TypeTrackerContent load, TypeTrackerContent store) { + deprecated StoreStep(TypeTrackerContent content) { basicStoreStep(_, _, content) } or + deprecated LoadStep(TypeTrackerContent content) { basicLoadStep(_, _, content) } or + deprecated LoadStoreStep(TypeTrackerContent load, TypeTrackerContent store) { basicLoadStoreStep(_, _, load, store) } or - WithContent(ContentFilter filter) { basicWithContentStep(_, _, filter) } or - WithoutContent(ContentFilter filter) { basicWithoutContentStep(_, _, filter) } or + deprecated WithContent(ContentFilter filter) { basicWithContentStep(_, _, filter) } or + deprecated WithoutContent(ContentFilter filter) { basicWithoutContentStep(_, _, filter) } or JumpStep() cached - newtype TTypeTracker = - MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) { + deprecated newtype TTypeTracker = + deprecated MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) { content = noContent() or // Restrict `content` to those that might eventually match a load. @@ -40,8 +40,8 @@ private module Cached { } cached - newtype TTypeBackTracker = - MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) { + deprecated newtype TTypeBackTracker = + deprecated MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) { content = noContent() or // As in MkTypeTracker, restrict `content` to those that might eventually match a store. @@ -57,11 +57,13 @@ private module Cached { /** Gets a type tracker with no content and the call bit set to the given value. */ cached - TypeTracker noContentTypeTracker(boolean hasCall) { result = MkTypeTracker(hasCall, noContent()) } + deprecated TypeTracker noContentTypeTracker(boolean hasCall) { + result = MkTypeTracker(hasCall, noContent()) + } /** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */ cached - TypeTracker append(TypeTracker tt, StepSummary step) { + deprecated TypeTracker append(TypeTracker tt, StepSummary step) { exists(Boolean hasCall, OptionalTypeTrackerContent currentContents | tt = MkTypeTracker(hasCall, currentContents) | @@ -108,13 +110,13 @@ private module Cached { } pragma[nomagic] - private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) { + deprecated private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) { result = MkTypeBackTracker(hasReturn, noContent()) } /** Gets the summary resulting from prepending `step` to this type-tracking summary. */ cached - TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) { + deprecated TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) { exists(Boolean hasReturn, OptionalTypeTrackerContent content | tbt = MkTypeBackTracker(hasReturn, content) | @@ -167,7 +169,9 @@ private module Cached { * Steps contained in this predicate should _not_ depend on the call graph. */ cached - predicate stepNoCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + deprecated predicate stepNoCall( + TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary + ) { exists(Node mid | nodeFrom.flowsTo(mid) and smallstepNoCall(mid, nodeTo, summary)) } @@ -176,12 +180,14 @@ private module Cached { * inter-procedural step from `nodeFrom` to `nodeTo`. */ cached - predicate stepCall(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + deprecated predicate stepCall( + TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary + ) { exists(Node mid | nodeFrom.flowsTo(mid) and smallstepCall(mid, nodeTo, summary)) } cached - predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + deprecated predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { jumpStep(nodeFrom, nodeTo) and summary = JumpStep() or @@ -210,7 +216,7 @@ private module Cached { } cached - predicate smallstepCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { + deprecated predicate smallstepCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { callStep(nodeFrom, nodeTo) and summary = CallStep() or returnStep(nodeFrom, nodeTo) and @@ -223,25 +229,27 @@ private module Cached { private import Cached -private predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { +deprecated private predicate step( + TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary +) { stepNoCall(nodeFrom, nodeTo, summary) or stepCall(nodeFrom, nodeTo, summary) } pragma[nomagic] -private predicate stepProj(TypeTrackingNode nodeFrom, StepSummary summary) { +deprecated private predicate stepProj(TypeTrackingNode nodeFrom, StepSummary summary) { step(nodeFrom, _, summary) } -private predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { +deprecated private predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) { smallstepNoCall(nodeFrom, nodeTo, summary) or smallstepCall(nodeFrom, nodeTo, summary) } pragma[nomagic] -private predicate smallstepProj(Node nodeFrom, StepSummary summary) { +deprecated private predicate smallstepProj(Node nodeFrom, StepSummary summary) { smallstep(nodeFrom, _, summary) } @@ -270,7 +278,7 @@ private predicate smallstepProj(Node nodeFrom, StepSummary summary) { * function. This means we will track the fact that `x.attr` can have the type of `y` into the * assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called. */ -private predicate flowsToStoreStep( +deprecated private predicate flowsToStoreStep( Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContent content ) { exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content)) @@ -279,7 +287,7 @@ private predicate flowsToStoreStep( /** * Holds if `loadContent` is loaded from `nodeFrom` and written to `storeContent` of `nodeTo`. */ -private predicate flowsToLoadStoreStep( +deprecated private predicate flowsToLoadStoreStep( Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContent loadContent, TypeTrackerContent storeContent ) { @@ -293,7 +301,7 @@ private predicate flowsToLoadStoreStep( * * A description of a step on an inter-procedural data flow path. */ -class StepSummary extends TStepSummary { +deprecated class StepSummary extends TStepSummary { /** Gets a textual representation of this step summary. */ string toString() { this instanceof LevelStep and result = "level" @@ -316,7 +324,7 @@ class StepSummary extends TStepSummary { } /** Provides predicates for updating step summaries (`StepSummary`s). */ -module StepSummary { +deprecated module StepSummary { predicate append = Cached::append/2; /** @@ -437,7 +445,7 @@ module StepSummary { * `t = t2.step(myType(t2), result)`. If you additionally want to track individual * intra-procedural steps, use `t = t2.smallstep(myCallback(t2), result)`. */ -class TypeTracker extends TTypeTracker { +deprecated class TypeTracker extends TTypeTracker { Boolean hasCall; OptionalTypeTrackerContent content; @@ -565,7 +573,7 @@ class TypeTracker extends TTypeTracker { } /** Provides predicates for implementing custom `TypeTracker`s. */ -module TypeTracker { +deprecated module TypeTracker { /** * Gets a valid end point of type tracking. */ @@ -580,11 +588,11 @@ module TypeTracker { } pragma[nomagic] -private predicate backStepProj(TypeTrackingNode nodeTo, StepSummary summary) { +deprecated private predicate backStepProj(TypeTrackingNode nodeTo, StepSummary summary) { step(_, nodeTo, summary) } -private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSummary summary) { +deprecated private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSummary summary) { smallstep(_, nodeTo, summary) } @@ -618,7 +626,7 @@ private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSummary summary * `t2 = t.step(result, myCallback(t2))`. If you additionally want to track individual * intra-procedural steps, use `t2 = t.smallstep(result, myCallback(t2))`. */ -class TypeBackTracker extends TTypeBackTracker { +deprecated class TypeBackTracker extends TTypeBackTracker { Boolean hasReturn; OptionalTypeTrackerContent content; @@ -747,7 +755,7 @@ class TypeBackTracker extends TTypeBackTracker { } /** Provides predicates for implementing custom `TypeBackTracker`s. */ -module TypeBackTracker { +deprecated module TypeBackTracker { /** * Gets a valid end point of type back-tracking. */ @@ -768,14 +776,14 @@ module TypeBackTracker { * `stepCall` relation (`stepNoCall` not being recursive, can be join-ordered in the * same way as in `stepInlineLate`). */ -module CallGraphConstruction { +deprecated module CallGraphConstruction { /** The input to call graph construction. */ signature module InputSig { /** A state to track during type tracking. */ class State; /** Holds if type tracking should start at `start` in state `state`. */ - predicate start(Node start, State state); + deprecated predicate start(Node start, State state); /** * Holds if type tracking should use the step from `nodeFrom` to `nodeTo`, @@ -784,7 +792,7 @@ module CallGraphConstruction { * Implementing this predicate using `StepSummary::[small]stepNoCall` yields * standard type tracking. */ - predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary); + deprecated predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary); /** * Holds if type tracking should use the step from `nodeFrom` to `nodeTo`, @@ -793,7 +801,7 @@ module CallGraphConstruction { * Implementing this predicate using `StepSummary::[small]stepCall` yields * standard type tracking. */ - predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary); + deprecated predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary); /** A projection of an element from the state space. */ class StateProj; @@ -802,25 +810,25 @@ module CallGraphConstruction { StateProj stateProj(State state); /** Holds if type tracking should stop at `n` when we are tracking projected state `stateProj`. */ - predicate filter(Node n, StateProj stateProj); + deprecated predicate filter(Node n, StateProj stateProj); } /** Provides the `track` predicate for use in call graph construction. */ module Make { pragma[nomagic] - private predicate stepNoCallProj(Node nodeFrom, StepSummary summary) { + deprecated private predicate stepNoCallProj(Node nodeFrom, StepSummary summary) { Input::stepNoCall(nodeFrom, _, summary) } pragma[nomagic] - private predicate stepCallProj(Node nodeFrom, StepSummary summary) { + deprecated private predicate stepCallProj(Node nodeFrom, StepSummary summary) { Input::stepCall(nodeFrom, _, summary) } bindingset[nodeFrom, t] pragma[inline_late] pragma[noopt] - private TypeTracker stepNoCallInlineLate( + deprecated private TypeTracker stepNoCallInlineLate( TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo ) { exists(StepSummary summary | @@ -837,7 +845,7 @@ module CallGraphConstruction { } pragma[nomagic] - private Node track(Input::State state, TypeTracker t) { + deprecated private Node track(Input::State state, TypeTracker t) { t.start() and Input::start(result, state) or exists(Input::StateProj stateProj | @@ -855,12 +863,12 @@ module CallGraphConstruction { bindingset[t, summary] pragma[inline_late] - private TypeTracker appendInlineLate(TypeTracker t, StepSummary summary) { + deprecated private TypeTracker appendInlineLate(TypeTracker t, StepSummary summary) { result = t.append(summary) } pragma[nomagic] - private Node trackCall(Input::State state, TypeTracker t, StepSummary summary) { + deprecated private Node trackCall(Input::State state, TypeTracker t, StepSummary summary) { exists(TypeTracker t2 | // non-linear recursion result = track(state, t2) and @@ -871,7 +879,7 @@ module CallGraphConstruction { /** Gets a node that can be reached from _some_ start node in state `state`. */ pragma[nomagic] - Node track(Input::State state) { result = track(state, TypeTracker::end()) } + deprecated Node track(Input::State state) { result = track(state, TypeTracker::end()) } } /** A simple version of `CallGraphConstruction` that uses standard type tracking. */ @@ -882,15 +890,15 @@ module CallGraphConstruction { class State; /** Holds if type tracking should start at `start` in state `state`. */ - predicate start(Node start, State state); + deprecated predicate start(Node start, State state); /** Holds if type tracking should stop at `n`. */ - predicate filter(Node n); + deprecated predicate filter(Node n); } /** Provides the `track` predicate for use in call graph construction. */ module Make { - private module I implements CallGraphConstruction::InputSig { + deprecated private module I implements CallGraphConstruction::InputSig { private import codeql.util.Unit class State = Input::State; @@ -915,7 +923,7 @@ module CallGraphConstruction { } } - import CallGraphConstruction::Make + deprecated import CallGraphConstruction::Make } } } diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index d0ca089f6bc5..39b188753d1b 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -9,21 +9,19 @@ private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatc private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific private import codeql.ruby.dataflow.internal.AccessPathSyntax +private import internal.TypeTrackingImpl as TypeTrackingImpl +import codeql.util.Boolean -class Node = DataFlowPublic::Node; +deprecated class Node = DataFlowPublic::Node; -class TypeTrackingNode = DataFlowPublic::LocalSourceNode; +deprecated class TypeTrackingNode = DataFlowPublic::LocalSourceNode; -class TypeTrackerContent = DataFlowPublic::ContentSet; - -private module SCS = SummaryComponentStack; - -private module SC = SummaryComponent; +deprecated class TypeTrackerContent = DataFlowPublic::ContentSet; /** * An optional content set, that is, a `ContentSet` or the special "no content set" value. */ -class OptionalTypeTrackerContent extends DataFlowPrivate::TOptionalContentSet { +deprecated class OptionalTypeTrackerContent extends DataFlowPrivate::TOptionalContentSet { /** Gets a textual representation of this content set. */ string toString() { this instanceof DataFlowPrivate::TNoContentSet and @@ -33,178 +31,45 @@ class OptionalTypeTrackerContent extends DataFlowPrivate::TOptionalContentSet { } } -private newtype TContentFilter = MkElementFilter() - /** * A label to use for `WithContent` and `WithoutContent` steps, restricting * which `ContentSet` may pass through. */ -class ContentFilter extends TContentFilter { - /** Gets a string representation of this content filter. */ - string toString() { this = MkElementFilter() and result = "elements" } - - /** Gets the content of a type-tracker that matches this filter. */ - TypeTrackerContent getAMatchingContent() { - this = MkElementFilter() and - result.getAReadContent() instanceof DataFlow::Content::ElementContent - } -} +deprecated class ContentFilter = TypeTrackingImpl::TypeTrackingInput::ContentFilter; /** Module for getting `ContentFilter` values. */ -module ContentFilter { +deprecated module ContentFilter { /** Gets the filter that only allow element contents. */ - ContentFilter hasElements() { result = MkElementFilter() } + ContentFilter hasElements() { any() } } /** * Holds if a value stored with `storeContents` can be read back with `loadContents`. */ pragma[inline] -predicate compatibleContents(TypeTrackerContent storeContents, TypeTrackerContent loadContents) { +deprecated predicate compatibleContents( + TypeTrackerContent storeContents, TypeTrackerContent loadContents +) { storeContents.getAStoreContent() = loadContents.getAReadContent() } /** Gets the "no content set" value to use for a type tracker not inside any content. */ -OptionalTypeTrackerContent noContent() { result = DataFlowPrivate::TNoContentSet() } +deprecated OptionalTypeTrackerContent noContent() { result = DataFlowPrivate::TNoContentSet() } /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ -predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2; +deprecated predicate simpleLocalFlowStep = + TypeTrackingImpl::TypeTrackingInput::simpleLocalSmallStep/2; /** * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. */ -predicate jumpStep = DataFlowPrivate::jumpStep/2; - -/** Holds if there is direct flow from `param` to a return. */ -pragma[nomagic] -private predicate flowThrough(DataFlowPublic::ParameterNode param) { - exists(DataFlowPrivate::SourceReturnNode returnNode, DataFlowDispatch::ReturnKind rk | - param.flowsTo(returnNode) and - returnNode.hasKind(rk, param.(DataFlowPrivate::NodeImpl).getCfgScope()) - | - rk instanceof DataFlowDispatch::NormalReturnKind - or - rk instanceof DataFlowDispatch::BreakReturnKind - ) -} - -/** Holds if there is flow from `arg` to `p` via the call `call`, not counting `new -> initialize` call steps. */ -pragma[nomagic] -predicate callStepNoInitialize( - ExprNodes::CallCfgNode call, Node arg, DataFlowPrivate::ParameterNodeImpl p -) { - exists(DataFlowDispatch::ParameterPosition pos | - argumentPositionMatch(call, arg, pos) and - p.isSourceParameterOf(DataFlowDispatch::getTarget(call), pos) - ) -} +deprecated predicate jumpStep = TypeTrackingImpl::TypeTrackingInput::jumpStep/2; /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */ -pragma[nomagic] -predicate levelStepCall(Node nodeFrom, Node nodeTo) { - exists(DataFlowPublic::ParameterNode param | - flowThrough(param) and - callStepNoInitialize(nodeTo.asExpr(), nodeFrom, param) - ) -} +deprecated predicate levelStepCall = TypeTrackingImpl::TypeTrackingInput::levelStepCall/2; /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */ -pragma[nomagic] -predicate levelStepNoCall(Node nodeFrom, Node nodeTo) { - TypeTrackerSummaryFlow::levelStepNoCall(nodeFrom, nodeTo) - or - localFieldStep(nodeFrom, nodeTo) -} - -/** - * Gets a method of `mod`, with `instance` indicating if this is an instance method. - * - * Does not take inheritance or the various forms of inclusion into account. - */ -pragma[nomagic] -private MethodBase getAMethod(ModuleBase mod, boolean instance) { - not mod instanceof SingletonClass and - result = mod.getAMethod() and - if result instanceof SingletonMethod then instance = false else instance = true - or - exists(SingletonClass cls | - cls.getValue().(SelfVariableAccess).getVariable().getDeclaringScope() = mod and - result = cls.getAMethod().(Method) and - instance = false - ) -} - -/** - * Gets a value flowing into `field` in `mod`, with `instance` indicating if it's - * a field on an instance of `mod` (as opposed to the module object itself). - */ -pragma[nomagic] -private Node fieldPredecessor(ModuleBase mod, boolean instance, string field) { - exists(InstanceVariableWriteAccess access, AssignExpr assign | - access.getReceiver().getVariable().getDeclaringScope() = getAMethod(mod, instance) and - field = access.getVariable().getName() and - assign.getLeftOperand() = access and - result.asExpr().getExpr() = assign.getRightOperand() - ) -} - -/** - * Gets a reference to `field` in `mod`, with `instance` indicating if it's - * a field on an instance of `mod` (as opposed to the module object itself). - */ -pragma[nomagic] -private Node fieldSuccessor(ModuleBase mod, boolean instance, string field) { - exists(InstanceVariableReadAccess access | - access.getReceiver().getVariable().getDeclaringScope() = getAMethod(mod, instance) and - result.asExpr().getExpr() = access and - field = access.getVariable().getName() - ) -} - -/** - * Holds if `pred -> succ` should be used a level step, from a field assignment to - * a read within the same class. - */ -private predicate localFieldStep(Node pred, Node succ) { - exists(ModuleBase mod, boolean instance, string field | - pred = fieldPredecessor(mod, instance, field) and - succ = fieldSuccessor(mod, instance, field) - ) -} - -pragma[noinline] -private predicate argumentPositionMatch( - ExprNodes::CallCfgNode call, DataFlowPrivate::ArgumentNode arg, - DataFlowDispatch::ParameterPosition ppos -) { - exists(DataFlowDispatch::ArgumentPosition apos | - arg.sourceArgumentOf(call, apos) and - not apos.isLambdaSelf() and - DataFlowDispatch::parameterMatch(ppos, apos) - ) -} - -pragma[noinline] -private predicate viableParam( - ExprNodes::CallCfgNode call, DataFlowPrivate::ParameterNodeImpl p, - DataFlowDispatch::ParameterPosition ppos -) { - exists(Cfg::CfgScope callable | - DataFlowDispatch::getTarget(call) = callable or - DataFlowDispatch::getInitializeTarget(call) = callable - | - p.isSourceParameterOf(callable, ppos) - ) -} - -/** Holds if there is flow from `arg` to `p` via the call `call`. */ -pragma[nomagic] -predicate callStep(ExprNodes::CallCfgNode call, Node arg, DataFlowPrivate::ParameterNodeImpl p) { - exists(DataFlowDispatch::ParameterPosition pos | - argumentPositionMatch(call, arg, pos) and - viableParam(call, p, pos) - ) -} +deprecated predicate levelStepNoCall = TypeTrackingImpl::TypeTrackingInput::levelStepNoCall/2; /** * Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. @@ -213,7 +78,7 @@ predicate callStep(ExprNodes::CallCfgNode call, Node arg, DataFlowPrivate::Param * recursion (or, at best, terrible performance), since identifying calls to library * methods is done using API graphs (which uses type tracking). */ -predicate callStep(Node nodeFrom, Node nodeTo) { callStep(_, nodeFrom, nodeTo) } +deprecated predicate callStep = TypeTrackingImpl::TypeTrackingInput::callStep/2; /** * Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. @@ -222,17 +87,7 @@ predicate callStep(Node nodeFrom, Node nodeTo) { callStep(_, nodeFrom, nodeTo) } * recursion (or, at best, terrible performance), since identifying calls to library * methods is done using API graphs (which uses type tracking). */ -predicate returnStep(Node nodeFrom, Node nodeTo) { - exists(ExprNodes::CallCfgNode call | - nodeFrom instanceof DataFlowPrivate::ReturnNode and - not nodeFrom instanceof DataFlowPrivate::InitializeReturnNode and - nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and - // deliberately do not include `getInitializeTarget`, since calls to `new` should not - // get the return value from `initialize`. Any fields being set in the initializer - // will reach all reads via `callStep` and `localFieldStep`. - nodeTo.asExpr().getAstNode() = call.getAstNode() - ) -} +deprecated predicate returnStep = TypeTrackingImpl::TypeTrackingInput::returnStep/2; /** * Holds if `nodeFrom` is being written to the `contents` of the object @@ -265,211 +120,25 @@ predicate returnStep(Node nodeFrom, Node nodeTo) { * to `z` inside `bar`, even though this content write happens _after_ `bar` is * called. */ -predicate basicStoreStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) { - storeStepIntoSourceNode(nodeFrom, nodeTo, contents) - or - TypeTrackerSummaryFlow::basicStoreStep(nodeFrom, nodeTo, contents) -} - -/** - * Holds if a store step `nodeFrom -> nodeTo` with `contents` exists, where the destination node - * is a post-update node that should be treated as a local source node. - */ -private predicate storeStepIntoSourceNode(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) { - // TODO: support SetterMethodCall inside TuplePattern - exists(ExprNodes::MethodCallCfgNode call | - contents - .isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr() - .(Ast::SetterMethodCall) - .getTargetName())) and - nodeTo.(DataFlowPublic::PostUpdateNode).getPreUpdateNode().asExpr() = call.getReceiver() and - call.getExpr() instanceof Ast::SetterMethodCall and - call.getArgument(call.getNumberOfArguments() - 1) = - nodeFrom.(DataFlowPublic::ExprNode).getExprNode() - ) - or - DataFlowPrivate::storeStepCommon(nodeFrom, contents, nodeTo) -} +deprecated predicate basicStoreStep = TypeTrackingImpl::TypeTrackingInput::storeStep/3; /** * Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`. */ -predicate basicLoadStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) { - readStepIntoSourceNode(nodeFrom, nodeTo, contents) - or - exists(ExprNodes::MethodCallCfgNode call | - call.getExpr().getNumberOfArguments() = 0 and - contents.isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr().getMethodName())) and - nodeFrom.asExpr() = call.getReceiver() and - nodeTo.asExpr() = call - ) - or - TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, contents) -} - -/** - * Holds if a read step `nodeFrom -> nodeTo` with `contents` exists, where the destination node - * should be treated as a local source node. - */ -private predicate readStepIntoSourceNode(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) { - DataFlowPrivate::readStepCommon(nodeFrom, contents, nodeTo) -} +deprecated predicate basicLoadStep = TypeTrackingImpl::TypeTrackingInput::loadStep/3; /** * Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`. */ -predicate basicLoadStoreStep( - Node nodeFrom, Node nodeTo, DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent -) { - readStoreStepIntoSourceNode(nodeFrom, nodeTo, loadContent, storeContent) - or - TypeTrackerSummaryFlow::basicLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent) -} - -/** - * Holds if a read+store step `nodeFrom -> nodeTo` exists, where the destination node - * should be treated as a local source node. - */ -private predicate readStoreStepIntoSourceNode( - Node nodeFrom, Node nodeTo, DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent -) { - exists(DataFlowPrivate::SynthSplatParameterShiftNode shift | - shift.readFrom(nodeFrom, loadContent) and - shift.storeInto(nodeTo, storeContent) - ) - or - exists(DataFlowPrivate::SynthSplatArgumentShiftNode shift | - shift.readFrom(nodeFrom, loadContent) and - shift.storeInto(nodeTo, storeContent) - ) -} +deprecated predicate basicLoadStoreStep = TypeTrackingImpl::TypeTrackingInput::loadStoreStep/4; /** * Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here. */ -predicate basicWithoutContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) { - TypeTrackerSummaryFlow::basicWithoutContentStep(nodeFrom, nodeTo, filter) -} +deprecated predicate basicWithoutContentStep = + TypeTrackingImpl::TypeTrackingInput::withoutContentStep/3; /** * Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a content matched by `filter`. */ -predicate basicWithContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) { - TypeTrackerSummaryFlow::basicWithContentStep(nodeFrom, nodeTo, filter) -} - -/** - * A utility class that is equivalent to `boolean` but does not require type joining. - */ -class Boolean extends boolean { - Boolean() { this = true or this = false } -} - -private import SummaryComponentStack - -/** - * Holds if the given component can't be evaluated by `evaluateSummaryComponentStackLocal`. - */ -pragma[nomagic] -predicate isNonLocal(SummaryComponent component) { - component = SC::content(_) - or - component = SC::withContent(_) -} - -private import internal.SummaryTypeTracker as SummaryTypeTracker -private import codeql.ruby.dataflow.FlowSummary as FlowSummary - -private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input { - // Dataflow nodes - class Node = DataFlow::Node; - - // Content - class TypeTrackerContent = DataFlowPublic::ContentSet; - - class TypeTrackerContentFilter = ContentFilter; - - TypeTrackerContentFilter getFilterFromWithoutContentStep(TypeTrackerContent content) { - ( - content.isAnyElement() - or - content.isElementLowerBoundOrUnknown(_) - or - content.isElementOfTypeOrUnknown(_) - or - content.isSingleton(any(DataFlow::Content::UnknownElementContent c)) - ) and - result = MkElementFilter() - } - - TypeTrackerContentFilter getFilterFromWithContentStep(TypeTrackerContent content) { - ( - content.isAnyElement() - or - content.isElementLowerBound(_) - or - content.isElementLowerBoundOrUnknown(_) - or - content.isElementOfType(_) - or - content.isElementOfTypeOrUnknown(_) - or - content.isSingleton(any(DataFlow::Content::ElementContent c)) - ) and - result = MkElementFilter() - } - - // Summaries and their stacks - class SummaryComponent = FlowSummary::SummaryComponent; - - class SummaryComponentStack = FlowSummary::SummaryComponentStack; - - predicate singleton = FlowSummary::SummaryComponentStack::singleton/1; - - predicate push = FlowSummary::SummaryComponentStack::push/2; - - // Relating content to summaries - predicate content = FlowSummary::SummaryComponent::content/1; - - predicate withoutContent = FlowSummary::SummaryComponent::withoutContent/1; - - predicate withContent = FlowSummary::SummaryComponent::withContent/1; - - predicate return = FlowSummary::SummaryComponent::return/0; - - // Callables - class SummarizedCallable = FlowSummary::SummarizedCallable; - - // Relating nodes to summaries - Node argumentOf(Node call, SummaryComponent arg, boolean isPostUpdate) { - exists(DataFlowDispatch::ParameterPosition pos, DataFlowPrivate::ArgumentNode n | - arg = SummaryComponent::argument(pos) and - argumentPositionMatch(call.asExpr(), n, pos) - | - isPostUpdate = false and result = n - or - isPostUpdate = true and result.(DataFlowPublic::PostUpdateNode).getPreUpdateNode() = n - ) - } - - Node parameterOf(Node callable, SummaryComponent param) { - exists(DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos | - param = SummaryComponent::parameter(apos) and - DataFlowDispatch::parameterMatch(ppos, apos) and - result - .(DataFlowPrivate::ParameterNodeImpl) - .isSourceParameterOf(callable.asExpr().getExpr(), ppos) - ) - } - - Node returnOf(Node callable, SummaryComponent return) { - return = SummaryComponent::return() and - result.(DataFlowPrivate::ReturnNode).(DataFlowPrivate::NodeImpl).getCfgScope() = - callable.asExpr().getExpr() - } - - // Relating callables to nodes - Node callTo(SummarizedCallable callable) { result.asExpr().getExpr() = callable.getACallSimple() } -} - -private module TypeTrackerSummaryFlow = SummaryTypeTracker::SummaryFlow; +deprecated predicate basicWithContentStep = TypeTrackingImpl::TypeTrackingInput::withContentStep/3; diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracking.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracking.qll new file mode 100644 index 000000000000..67f09a45a370 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracking.qll @@ -0,0 +1,7 @@ +/** + * Provides classes and predicates for simple data-flow reachability suitable + * for tracking types. + */ + +private import codeql.ruby.typetracking.internal.TypeTrackingImpl as Impl +import Impl::Shared::TypeTracking diff --git a/ruby/ql/lib/codeql/ruby/typetracking/internal/TypeTrackingImpl.qll b/ruby/ql/lib/codeql/ruby/typetracking/internal/TypeTrackingImpl.qll new file mode 100644 index 000000000000..b50140a7b583 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/typetracking/internal/TypeTrackingImpl.qll @@ -0,0 +1,430 @@ +import codeql.typetracking.TypeTracking as Shared +import codeql.typetracking.internal.TypeTrackingImpl as SharedImpl +private import codeql.ruby.AST +private import codeql.ruby.CFG as Cfg +private import Cfg::CfgNodes +private import SummaryTypeTracker as SummaryTypeTracker +private import codeql.ruby.DataFlow +private import codeql.ruby.dataflow.FlowSummary as FlowSummary +private import codeql.ruby.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon +private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlowPublic +private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate +private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch +private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl +private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific +private import codeql.ruby.dataflow.internal.AccessPathSyntax + +/** Holds if there is direct flow from `param` to a return. */ +pragma[nomagic] +private predicate flowThrough(DataFlowPublic::ParameterNode param) { + exists(DataFlowPrivate::SourceReturnNode returnNode, DataFlowDispatch::ReturnKind rk | + param.flowsTo(returnNode) and + returnNode.hasKind(rk, param.(DataFlowPrivate::NodeImpl).getCfgScope()) + | + rk instanceof DataFlowDispatch::NormalReturnKind + or + rk instanceof DataFlowDispatch::BreakReturnKind + ) +} + +/** Holds if there is flow from `arg` to `p` via the call `call`, not counting `new -> initialize` call steps. */ +pragma[nomagic] +private predicate callStepNoInitialize( + ExprNodes::CallCfgNode call, DataFlow::Node arg, DataFlowPrivate::ParameterNodeImpl p +) { + exists(DataFlowDispatch::ParameterPosition pos | + argumentPositionMatch(call, arg, pos) and + p.isSourceParameterOf(DataFlowDispatch::getTarget(call), pos) + ) +} + +/** + * Gets a method of `mod`, with `instance` indicating if this is an instance method. + * + * Does not take inheritance or the various forms of inclusion into account. + */ +pragma[nomagic] +private MethodBase getAMethod(ModuleBase mod, boolean instance) { + not mod instanceof SingletonClass and + result = mod.getAMethod() and + if result instanceof SingletonMethod then instance = false else instance = true + or + exists(SingletonClass cls | + cls.getValue().(SelfVariableAccess).getVariable().getDeclaringScope() = mod and + result = cls.getAMethod().(Method) and + instance = false + ) +} + +/** + * Gets a value flowing into `field` in `mod`, with `instance` indicating if it's + * a field on an instance of `mod` (as opposed to the module object itself). + */ +pragma[nomagic] +private DataFlow::Node fieldPredecessor(ModuleBase mod, boolean instance, string field) { + exists(InstanceVariableWriteAccess access, AssignExpr assign | + access.getReceiver().getVariable().getDeclaringScope() = getAMethod(mod, instance) and + field = access.getVariable().getName() and + assign.getLeftOperand() = access and + result.asExpr().getExpr() = assign.getRightOperand() + ) +} + +/** + * Gets a reference to `field` in `mod`, with `instance` indicating if it's + * a field on an instance of `mod` (as opposed to the module object itself). + */ +pragma[nomagic] +private DataFlow::Node fieldSuccessor(ModuleBase mod, boolean instance, string field) { + exists(InstanceVariableReadAccess access | + access.getReceiver().getVariable().getDeclaringScope() = getAMethod(mod, instance) and + result.asExpr().getExpr() = access and + field = access.getVariable().getName() + ) +} + +/** + * Holds if `pred -> succ` should be used a level step, from a field assignment to + * a read within the same class. + */ +private predicate localFieldStep(DataFlow::Node pred, DataFlow::Node succ) { + exists(ModuleBase mod, boolean instance, string field | + pred = fieldPredecessor(mod, instance, field) and + succ = fieldSuccessor(mod, instance, field) + ) +} + +pragma[noinline] +private predicate argumentPositionMatch( + ExprNodes::CallCfgNode call, DataFlowPrivate::ArgumentNode arg, + DataFlowDispatch::ParameterPosition ppos +) { + exists(DataFlowDispatch::ArgumentPosition apos | + arg.sourceArgumentOf(call, apos) and + not apos.isLambdaSelf() and + DataFlowDispatch::parameterMatch(ppos, apos) + ) +} + +pragma[noinline] +private predicate viableParam( + ExprNodes::CallCfgNode call, DataFlowPrivate::ParameterNodeImpl p, + DataFlowDispatch::ParameterPosition ppos +) { + exists(Cfg::CfgScope callable | + DataFlowDispatch::getTarget(call) = callable or + DataFlowDispatch::getInitializeTarget(call) = callable + | + p.isSourceParameterOf(callable, ppos) + ) +} + +/** Holds if there is flow from `arg` to `p` via the call `call`. */ +pragma[nomagic] +predicate callStep( + ExprNodes::CallCfgNode call, DataFlow::Node arg, DataFlowPrivate::ParameterNodeImpl p +) { + exists(DataFlowDispatch::ParameterPosition pos | + argumentPositionMatch(call, arg, pos) and + viableParam(call, p, pos) + ) +} + +private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input { + // Dataflow nodes + class Node = DataFlow::Node; + + // Content + class TypeTrackerContent = DataFlowPublic::ContentSet; + + class TypeTrackerContentFilter = TypeTrackingInput::ContentFilter; + + TypeTrackerContentFilter getFilterFromWithoutContentStep(TypeTrackerContent content) { + ( + content.isAnyElement() + or + content.isElementLowerBoundOrUnknown(_) + or + content.isElementOfTypeOrUnknown(_) + or + content.isSingleton(any(DataFlow::Content::UnknownElementContent c)) + ) and + result = MkElementFilter() + } + + TypeTrackerContentFilter getFilterFromWithContentStep(TypeTrackerContent content) { + ( + content.isAnyElement() + or + content.isElementLowerBound(_) + or + content.isElementLowerBoundOrUnknown(_) + or + content.isElementOfType(_) + or + content.isElementOfTypeOrUnknown(_) + or + content.isSingleton(any(DataFlow::Content::ElementContent c)) + ) and + result = MkElementFilter() + } + + // Summaries and their stacks + class SummaryComponent = FlowSummary::SummaryComponent; + + class SummaryComponentStack = FlowSummary::SummaryComponentStack; + + predicate singleton = FlowSummary::SummaryComponentStack::singleton/1; + + predicate push = FlowSummary::SummaryComponentStack::push/2; + + // Relating content to summaries + predicate content = FlowSummary::SummaryComponent::content/1; + + predicate withoutContent = FlowSummary::SummaryComponent::withoutContent/1; + + predicate withContent = FlowSummary::SummaryComponent::withContent/1; + + predicate return = FlowSummary::SummaryComponent::return/0; + + // Callables + class SummarizedCallable = FlowSummary::SummarizedCallable; + + // Relating nodes to summaries + Node argumentOf(Node call, SummaryComponent arg, boolean isPostUpdate) { + exists(DataFlowDispatch::ParameterPosition pos, DataFlowPrivate::ArgumentNode n | + arg = FlowSummary::SummaryComponent::argument(pos) and + argumentPositionMatch(call.asExpr(), n, pos) + | + isPostUpdate = false and result = n + or + isPostUpdate = true and result.(DataFlowPublic::PostUpdateNode).getPreUpdateNode() = n + ) + } + + Node parameterOf(Node callable, SummaryComponent param) { + exists(DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos | + param = FlowSummary::SummaryComponent::parameter(apos) and + DataFlowDispatch::parameterMatch(ppos, apos) and + result + .(DataFlowPrivate::ParameterNodeImpl) + .isSourceParameterOf(callable.asExpr().getExpr(), ppos) + ) + } + + Node returnOf(Node callable, SummaryComponent return) { + return = FlowSummary::SummaryComponent::return() and + result.(DataFlowPrivate::ReturnNode).(DataFlowPrivate::NodeImpl).getCfgScope() = + callable.asExpr().getExpr() + } + + // Relating callables to nodes + Node callTo(SummarizedCallable callable) { result.asExpr().getExpr() = callable.getACallSimple() } +} + +private module TypeTrackerSummaryFlow = SummaryTypeTracker::SummaryFlow; + +private newtype TContentFilter = MkElementFilter() + +module TypeTrackingInput implements Shared::TypeTrackingInput { + class Node = DataFlowPublic::Node; + + class LocalSourceNode = DataFlowPublic::LocalSourceNode; + + class Content = DataFlowPublic::ContentSet; + + /** + * A label to use for `WithContent` and `WithoutContent` steps, restricting + * which `ContentSet` may pass through. + */ + class ContentFilter extends TContentFilter { + /** Gets a string representation of this content filter. */ + string toString() { this = MkElementFilter() and result = "elements" } + + /** Gets the content of a type-tracker that matches this filter. */ + Content getAMatchingContent() { + this = MkElementFilter() and + result.getAReadContent() instanceof DataFlow::Content::ElementContent + } + } + + /** + * Holds if a value stored with `storeContents` can be read back with `loadContents`. + */ + pragma[inline] + predicate compatibleContents(Content storeContents, Content loadContents) { + storeContents.getAStoreContent() = loadContents.getAReadContent() + } + + /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ + predicate simpleLocalSmallStep = DataFlowPrivate::localFlowStepTypeTracker/2; + + /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */ + pragma[nomagic] + predicate levelStepNoCall(Node nodeFrom, LocalSourceNode nodeTo) { + TypeTrackerSummaryFlow::levelStepNoCall(nodeFrom, nodeTo) + or + localFieldStep(nodeFrom, nodeTo) + } + + /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */ + pragma[nomagic] + predicate levelStepCall(Node nodeFrom, LocalSourceNode nodeTo) { + exists(DataFlowPublic::ParameterNode param | + flowThrough(param) and + callStepNoInitialize(nodeTo.asExpr(), nodeFrom, param) + ) + } + + /** + * Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. + * + * Flow into summarized library methods is not included, as that will lead to negative + * recursion (or, at best, terrible performance), since identifying calls to library + * methods is done using API graphs (which uses type tracking). + */ + predicate callStep(Node nodeFrom, LocalSourceNode nodeTo) { callStep(_, nodeFrom, nodeTo) } + + /** + * Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. + * + * Flow out of summarized library methods is not included, as that will lead to negative + * recursion (or, at best, terrible performance), since identifying calls to library + * methods is done using API graphs (which uses type tracking). + */ + predicate returnStep(Node nodeFrom, LocalSourceNode nodeTo) { + exists(ExprNodes::CallCfgNode call | + nodeFrom instanceof DataFlowPrivate::ReturnNode and + not nodeFrom instanceof DataFlowPrivate::InitializeReturnNode and + nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and + // deliberately do not include `getInitializeTarget`, since calls to `new` should not + // get the return value from `initialize`. Any fields being set in the initializer + // will reach all reads via `callStep` and `localFieldStep`. + nodeTo.asExpr().getAstNode() = call.getAstNode() + ) + } + + /** + * Holds if `nodeFrom` is being written to the `contents` of the object + * in `nodeTo`. + * + * Note that the choice of `nodeTo` does not have to make sense + * "chronologically". All we care about is whether the `contents` of + * `nodeTo` can have a specific type, and the assumption is that if a specific + * type appears here, then any access of that particular content can yield + * something of that particular type. + * + * Thus, in an example such as + * + * ```rb + * def foo(y) + * x = Foo.new + * bar(x) + * x.content = y + * baz(x) + * end + * + * def bar(x) + * z = x.content + * end + * ``` + * for the content write `x.content = y`, we will have `contents` being the + * literal string `"content"`, `nodeFrom` will be `y`, and `nodeTo` will be the + * `Foo` object created on the first line of the function. This means we will + * track the fact that `x.content` can have the type of `y` into the assignment + * to `z` inside `bar`, even though this content write happens _after_ `bar` is + * called. + */ + predicate storeStep(Node nodeFrom, Node nodeTo, Content contents) { + // TODO: support SetterMethodCall inside TuplePattern + exists(ExprNodes::MethodCallCfgNode call | + contents + .isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr() + .(SetterMethodCall) + .getTargetName())) and + nodeTo.(DataFlowPublic::PostUpdateNode).getPreUpdateNode().asExpr() = call.getReceiver() and + call.getExpr() instanceof SetterMethodCall and + call.getArgument(call.getNumberOfArguments() - 1) = + nodeFrom.(DataFlowPublic::ExprNode).getExprNode() + ) + or + DataFlowPrivate::storeStepCommon(nodeFrom, contents, nodeTo) + or + TypeTrackerSummaryFlow::basicStoreStep(nodeFrom, nodeTo, contents) + } + + /** + * Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`. + */ + predicate loadStep(Node nodeFrom, LocalSourceNode nodeTo, Content contents) { + DataFlowPrivate::readStepCommon(nodeFrom, contents, nodeTo) + or + exists(ExprNodes::MethodCallCfgNode call | + call.getExpr().getNumberOfArguments() = 0 and + contents + .isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr().getMethodName())) and + nodeFrom.asExpr() = call.getReceiver() and + nodeTo.asExpr() = call + ) + or + TypeTrackerSummaryFlow::basicLoadStep(nodeFrom, nodeTo, contents) + } + + /** + * Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`. + */ + predicate loadStoreStep(Node nodeFrom, Node nodeTo, Content loadContent, Content storeContent) { + exists(DataFlowPrivate::SynthSplatParameterShiftNode shift | + shift.readFrom(nodeFrom, loadContent) and + shift.storeInto(nodeTo, storeContent) + ) + or + exists(DataFlowPrivate::SynthSplatArgumentShiftNode shift | + shift.readFrom(nodeFrom, loadContent) and + shift.storeInto(nodeTo, storeContent) + ) + or + TypeTrackerSummaryFlow::basicLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent) + } + + /** + * Same as `withContentStep`, but `nodeTo` has type `Node` instead of `LocalSourceNode`, + * which allows for it by used in the definition of `LocalSourceNode`. + */ + additional predicate withContentStepImpl(Node nodeFrom, Node nodeTo, ContentFilter filter) { + TypeTrackerSummaryFlow::basicWithContentStep(nodeFrom, nodeTo, filter) + } + + /** + * Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a content matched by `filter`. + */ + predicate withContentStep(Node nodeFrom, LocalSourceNode nodeTo, ContentFilter filter) { + withContentStepImpl(nodeFrom, nodeTo, filter) + } + + /** + * Same as `withoutContentStep`, but `nodeTo` has type `Node` instead of `LocalSourceNode`, + * which allows for it by used in the definition of `LocalSourceNode`. + */ + additional predicate withoutContentStepImpl(Node nodeFrom, Node nodeTo, ContentFilter filter) { + TypeTrackerSummaryFlow::basicWithoutContentStep(nodeFrom, nodeTo, filter) + } + + /** + * Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here. + */ + predicate withoutContentStep(Node nodeFrom, LocalSourceNode nodeTo, ContentFilter filter) { + withoutContentStepImpl(nodeFrom, nodeTo, filter) + } + + /** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ + predicate jumpStep(Node nodeFrom, LocalSourceNode nodeTo) { + DataFlowPrivate::jumpStep(nodeFrom, nodeTo) + } + + predicate hasFeatureBacktrackStoreTarget() { none() } +} + +import SharedImpl::TypeTracking diff --git a/ruby/ql/test/TestUtilities/InlineTypeTrackingFlowTest.qll b/ruby/ql/test/TestUtilities/InlineTypeTrackingFlowTest.qll index 6d584ef31dcd..fdbd40968de6 100644 --- a/ruby/ql/test/TestUtilities/InlineTypeTrackingFlowTest.qll +++ b/ruby/ql/test/TestUtilities/InlineTypeTrackingFlowTest.qll @@ -1,7 +1,7 @@ import ruby import TestUtilities.InlineExpectationsTest import TestUtilities.InlineFlowTestUtil -private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.TypeTracking private DataFlow::LocalSourceNode track(TypeTracker t, DataFlow::CallNode source) { t.start() and diff --git a/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected b/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected index 31c5b4a28666..bbb3be53b548 100644 --- a/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected +++ b/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected @@ -32,7 +32,6 @@ testFailures | array_flow.rb:940:18:940:78 | # $ hasValueFlow=91.1 $ hasValueFlow=91.2 $ hasValueFlow=91.3 | Missing result:hasValueFlow=91.3 | | array_flow.rb:957:28:957:46 | # $ hasValueFlow=93 | Missing result:hasValueFlow=93 | | array_flow.rb:958:28:958:46 | # $ hasValueFlow=93 | Missing result:hasValueFlow=93 | -| array_flow.rb:1018:16:1018:36 | # $ hasValueFlow=99.2 | Missing result:hasValueFlow=99.2 | | array_flow.rb:1099:10:1099:13 | ...[...] | Unexpected result: hasValueFlow=105.2 | | array_flow.rb:1100:10:1100:13 | ...[...] | Unexpected result: hasValueFlow=105.3 | | array_flow.rb:1110:10:1110:13 | ...[...] | Unexpected result: hasValueFlow=105.2 | diff --git a/ruby/ql/test/library-tests/dataflow/hash-flow/type-tracking-hash-flow.expected b/ruby/ql/test/library-tests/dataflow/hash-flow/type-tracking-hash-flow.expected index 153b8f32d667..f7a5c7cc1609 100644 --- a/ruby/ql/test/library-tests/dataflow/hash-flow/type-tracking-hash-flow.expected +++ b/ruby/ql/test/library-tests/dataflow/hash-flow/type-tracking-hash-flow.expected @@ -9,30 +9,16 @@ testFailures | hash_flow.rb:219:27:219:47 | # $ hasValueFlow=14.2 | Missing result:hasValueFlow=14.2 | | hash_flow.rb:291:10:291:14 | ...[...] | Unexpected result: hasValueFlow=19.1 | | hash_flow.rb:294:10:294:14 | ...[...] | Unexpected result: hasValueFlow=19.3 | -| hash_flow.rb:453:22:453:42 | # $ hasValueFlow=27.3 | Missing result:hasValueFlow=27.3 | -| hash_flow.rb:455:22:455:42 | # $ hasValueFlow=27.4 | Missing result:hasValueFlow=27.4 | | hash_flow.rb:467:16:467:36 | # $ hasValueFlow=28.1 | Missing result:hasValueFlow=28.1 | -| hash_flow.rb:513:22:513:42 | # $ hasValueFlow=31.1 | Missing result:hasValueFlow=31.1 | | hash_flow.rb:515:10:515:20 | ( ... ) | Unexpected result: hasValueFlow=31.3 | -| hash_flow.rb:515:22:515:42 | # $ hasValueFlow=31.2 | Missing result:hasValueFlow=31.2 | | hash_flow.rb:559:17:559:57 | # $ hasValueFlow=34.1 $ hasValueFlow=34.2 | Missing result:hasValueFlow=34.1 | | hash_flow.rb:559:17:559:57 | # $ hasValueFlow=34.1 $ hasValueFlow=34.2 | Missing result:hasValueFlow=34.2 | | hash_flow.rb:571:18:571:38 | # $ hasValueFlow=35.1 | Missing result:hasValueFlow=35.1 | | hash_flow.rb:591:20:591:60 | # $ hasValueFlow=36.1 $ hasValueFlow=36.2 | Missing result:hasValueFlow=36.1 | | hash_flow.rb:591:20:591:60 | # $ hasValueFlow=36.1 $ hasValueFlow=36.2 | Missing result:hasValueFlow=36.2 | | hash_flow.rb:673:10:673:19 | ( ... ) | Unexpected result: hasValueFlow=41.1 | -| hash_flow.rb:704:22:704:42 | # $ hasValueFlow=42.3 | Missing result:hasValueFlow=42.3 | -| hash_flow.rb:706:22:706:42 | # $ hasValueFlow=42.4 | Missing result:hasValueFlow=42.4 | | hash_flow.rb:776:10:776:14 | ...[...] | Unexpected result: hasValueFlow=46.1 | | hash_flow.rb:779:10:779:14 | ...[...] | Unexpected result: hasValueFlow=46.3 | | hash_flow.rb:781:10:781:17 | ...[...] | Unexpected result: hasValueFlow=46.1 | | hash_flow.rb:784:10:784:17 | ...[...] | Unexpected result: hasValueFlow=46.3 | -| hash_flow.rb:841:22:841:42 | # $ hasValueFlow=48.3 | Missing result:hasValueFlow=48.3 | -| hash_flow.rb:843:22:843:42 | # $ hasValueFlow=48.4 | Missing result:hasValueFlow=48.4 | -| hash_flow.rb:903:22:903:42 | # $ hasValueFlow=50.3 | Missing result:hasValueFlow=50.3 | -| hash_flow.rb:905:22:905:42 | # $ hasValueFlow=50.4 | Missing result:hasValueFlow=50.4 | -| hash_flow.rb:933:22:933:42 | # $ hasValueFlow=51.3 | Missing result:hasValueFlow=51.3 | -| hash_flow.rb:935:22:935:42 | # $ hasValueFlow=51.4 | Missing result:hasValueFlow=51.4 | -| hash_flow.rb:963:22:963:42 | # $ hasValueFlow=52.3 | Missing result:hasValueFlow=52.3 | -| hash_flow.rb:965:22:965:42 | # $ hasValueFlow=52.4 | Missing result:hasValueFlow=52.4 | failures diff --git a/ruby/ql/test/library-tests/dataflow/params/TypeTracker.expected b/ruby/ql/test/library-tests/dataflow/params/TypeTracker.expected index ff3363a0ea37..6a99eaa763a2 100644 --- a/ruby/ql/test/library-tests/dataflow/params/TypeTracker.expected +++ b/ruby/ql/test/library-tests/dataflow/params/TypeTracker.expected @@ -2580,52 +2580,52 @@ track | params_flow.rb:120:1:126:3 | destruct | type tracker without call steps | params_flow.rb:120:1:126:3 | destruct | | params_flow.rb:120:1:126:3 | self in destruct | type tracker with call steps | params_flow.rb:5:1:7:3 | self in sink | | params_flow.rb:120:1:126:3 | self in destruct | type tracker without call steps | params_flow.rb:120:1:126:3 | self in destruct | +| params_flow.rb:120:15:120:15 | a | type tracker with call steps | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:15:120:15 | a | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | +| params_flow.rb:120:15:120:15 | a | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | | params_flow.rb:120:15:120:15 | a | type tracker without call steps | params_flow.rb:120:15:120:15 | a | +| params_flow.rb:120:15:120:15 | a | type tracker without call steps | params_flow.rb:120:15:120:15 | a | +| params_flow.rb:120:15:120:15 | a | type tracker without call steps with content splat position 0 | params_flow.rb:121:5:121:10 | synthetic splat argument | +| params_flow.rb:120:17:120:17 | b | type tracker with call steps | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:17:120:17 | b | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | +| params_flow.rb:120:17:120:17 | b | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | +| params_flow.rb:120:17:120:17 | b | type tracker without call steps | params_flow.rb:120:17:120:17 | b | | params_flow.rb:120:17:120:17 | b | type tracker without call steps | params_flow.rb:120:17:120:17 | b | +| params_flow.rb:120:17:120:17 | b | type tracker without call steps with content splat position 0 | params_flow.rb:122:5:122:10 | synthetic splat argument | +| params_flow.rb:120:22:120:22 | c | type tracker with call steps | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:22:120:22 | c | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | +| params_flow.rb:120:22:120:22 | c | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | +| params_flow.rb:120:22:120:22 | c | type tracker without call steps | params_flow.rb:120:22:120:22 | c | | params_flow.rb:120:22:120:22 | c | type tracker without call steps | params_flow.rb:120:22:120:22 | c | +| params_flow.rb:120:22:120:22 | c | type tracker without call steps with content splat position 0 | params_flow.rb:123:5:123:10 | synthetic splat argument | +| params_flow.rb:120:25:120:25 | d | type tracker with call steps | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:25:120:25 | d | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | +| params_flow.rb:120:25:120:25 | d | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | | params_flow.rb:120:25:120:25 | d | type tracker without call steps | params_flow.rb:120:25:120:25 | d | +| params_flow.rb:120:25:120:25 | d | type tracker without call steps | params_flow.rb:120:25:120:25 | d | +| params_flow.rb:120:25:120:25 | d | type tracker without call steps with content splat position 0 | params_flow.rb:124:5:124:10 | synthetic splat argument | +| params_flow.rb:120:27:120:27 | e | type tracker with call steps | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:27:120:27 | e | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | +| params_flow.rb:120:27:120:27 | e | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | +| params_flow.rb:120:27:120:27 | e | type tracker without call steps | params_flow.rb:120:27:120:27 | e | | params_flow.rb:120:27:120:27 | e | type tracker without call steps | params_flow.rb:120:27:120:27 | e | +| params_flow.rb:120:27:120:27 | e | type tracker without call steps with content splat position 0 | params_flow.rb:125:5:125:10 | synthetic splat argument | | params_flow.rb:121:5:121:10 | call to sink | type tracker without call steps | params_flow.rb:121:5:121:10 | call to sink | | params_flow.rb:121:5:121:10 | synthetic splat argument | type tracker with call steps | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:121:5:121:10 | synthetic splat argument | type tracker without call steps | params_flow.rb:121:5:121:10 | synthetic splat argument | -| params_flow.rb:121:10:121:10 | a | type tracker with call steps | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:121:10:121:10 | a | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | -| params_flow.rb:121:10:121:10 | a | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | -| params_flow.rb:121:10:121:10 | a | type tracker without call steps | params_flow.rb:121:10:121:10 | a | -| params_flow.rb:121:10:121:10 | a | type tracker without call steps with content splat position 0 | params_flow.rb:121:5:121:10 | synthetic splat argument | | params_flow.rb:122:5:122:10 | call to sink | type tracker without call steps | params_flow.rb:122:5:122:10 | call to sink | | params_flow.rb:122:5:122:10 | synthetic splat argument | type tracker with call steps | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:122:5:122:10 | synthetic splat argument | type tracker without call steps | params_flow.rb:122:5:122:10 | synthetic splat argument | -| params_flow.rb:122:10:122:10 | b | type tracker with call steps | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:122:10:122:10 | b | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | -| params_flow.rb:122:10:122:10 | b | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | -| params_flow.rb:122:10:122:10 | b | type tracker without call steps | params_flow.rb:122:10:122:10 | b | -| params_flow.rb:122:10:122:10 | b | type tracker without call steps with content splat position 0 | params_flow.rb:122:5:122:10 | synthetic splat argument | | params_flow.rb:123:5:123:10 | call to sink | type tracker without call steps | params_flow.rb:123:5:123:10 | call to sink | | params_flow.rb:123:5:123:10 | synthetic splat argument | type tracker with call steps | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:123:5:123:10 | synthetic splat argument | type tracker without call steps | params_flow.rb:123:5:123:10 | synthetic splat argument | -| params_flow.rb:123:10:123:10 | c | type tracker with call steps | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:123:10:123:10 | c | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | -| params_flow.rb:123:10:123:10 | c | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | -| params_flow.rb:123:10:123:10 | c | type tracker without call steps | params_flow.rb:123:10:123:10 | c | -| params_flow.rb:123:10:123:10 | c | type tracker without call steps with content splat position 0 | params_flow.rb:123:5:123:10 | synthetic splat argument | | params_flow.rb:124:5:124:10 | call to sink | type tracker without call steps | params_flow.rb:124:5:124:10 | call to sink | | params_flow.rb:124:5:124:10 | synthetic splat argument | type tracker with call steps | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:124:5:124:10 | synthetic splat argument | type tracker without call steps | params_flow.rb:124:5:124:10 | synthetic splat argument | -| params_flow.rb:124:10:124:10 | d | type tracker with call steps | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:124:10:124:10 | d | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | -| params_flow.rb:124:10:124:10 | d | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | -| params_flow.rb:124:10:124:10 | d | type tracker without call steps | params_flow.rb:124:10:124:10 | d | -| params_flow.rb:124:10:124:10 | d | type tracker without call steps with content splat position 0 | params_flow.rb:124:5:124:10 | synthetic splat argument | | params_flow.rb:125:5:125:10 | call to sink | type tracker without call steps | params_flow.rb:125:5:125:10 | call to sink | | params_flow.rb:125:5:125:10 | call to sink | type tracker without call steps | params_flow.rb:128:1:128:61 | call to destruct | | params_flow.rb:125:5:125:10 | synthetic splat argument | type tracker with call steps | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:125:5:125:10 | synthetic splat argument | type tracker without call steps | params_flow.rb:125:5:125:10 | synthetic splat argument | -| params_flow.rb:125:10:125:10 | e | type tracker with call steps | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:125:10:125:10 | e | type tracker with call steps with content splat position 0 | params_flow.rb:5:1:7:3 | synthetic splat parameter | -| params_flow.rb:125:10:125:10 | e | type tracker with call steps with content splat position 0 | params_flow.rb:6:5:6:10 | synthetic splat argument | -| params_flow.rb:125:10:125:10 | e | type tracker without call steps | params_flow.rb:125:10:125:10 | e | -| params_flow.rb:125:10:125:10 | e | type tracker without call steps with content splat position 0 | params_flow.rb:125:5:125:10 | synthetic splat argument | | params_flow.rb:128:1:128:61 | call to destruct | type tracker without call steps | params_flow.rb:128:1:128:61 | call to destruct | | params_flow.rb:128:1:128:61 | synthetic splat argument | type tracker without call steps | params_flow.rb:128:1:128:61 | synthetic splat argument | | params_flow.rb:128:10:128:31 | Array | type tracker without call steps | params_flow.rb:128:10:128:31 | Array | @@ -5297,47 +5297,52 @@ trackEnd | params_flow.rb:120:1:126:3 | self in destruct | params_flow.rb:123:5:123:10 | self | | params_flow.rb:120:1:126:3 | self in destruct | params_flow.rb:124:5:124:10 | self | | params_flow.rb:120:1:126:3 | self in destruct | params_flow.rb:125:5:125:10 | self | +| params_flow.rb:120:15:120:15 | a | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:15:120:15 | a | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:15:120:15 | a | params_flow.rb:6:10:6:10 | x | | params_flow.rb:120:15:120:15 | a | params_flow.rb:120:15:120:15 | a | +| params_flow.rb:120:15:120:15 | a | params_flow.rb:120:15:120:15 | a | +| params_flow.rb:120:15:120:15 | a | params_flow.rb:121:10:121:10 | a | +| params_flow.rb:120:17:120:17 | b | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:17:120:17 | b | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:17:120:17 | b | params_flow.rb:6:10:6:10 | x | +| params_flow.rb:120:17:120:17 | b | params_flow.rb:120:17:120:17 | b | | params_flow.rb:120:17:120:17 | b | params_flow.rb:120:17:120:17 | b | +| params_flow.rb:120:17:120:17 | b | params_flow.rb:122:10:122:10 | b | +| params_flow.rb:120:22:120:22 | c | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:22:120:22 | c | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:22:120:22 | c | params_flow.rb:6:10:6:10 | x | +| params_flow.rb:120:22:120:22 | c | params_flow.rb:120:22:120:22 | c | | params_flow.rb:120:22:120:22 | c | params_flow.rb:120:22:120:22 | c | +| params_flow.rb:120:22:120:22 | c | params_flow.rb:123:10:123:10 | c | +| params_flow.rb:120:25:120:25 | d | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:25:120:25 | d | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:25:120:25 | d | params_flow.rb:6:10:6:10 | x | | params_flow.rb:120:25:120:25 | d | params_flow.rb:120:25:120:25 | d | +| params_flow.rb:120:25:120:25 | d | params_flow.rb:120:25:120:25 | d | +| params_flow.rb:120:25:120:25 | d | params_flow.rb:124:10:124:10 | d | +| params_flow.rb:120:27:120:27 | e | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:27:120:27 | e | params_flow.rb:5:10:5:10 | x | +| params_flow.rb:120:27:120:27 | e | params_flow.rb:6:10:6:10 | x | +| params_flow.rb:120:27:120:27 | e | params_flow.rb:120:27:120:27 | e | | params_flow.rb:120:27:120:27 | e | params_flow.rb:120:27:120:27 | e | +| params_flow.rb:120:27:120:27 | e | params_flow.rb:125:10:125:10 | e | | params_flow.rb:121:5:121:10 | call to sink | params_flow.rb:121:5:121:10 | call to sink | | params_flow.rb:121:5:121:10 | synthetic splat argument | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:121:5:121:10 | synthetic splat argument | params_flow.rb:121:5:121:10 | synthetic splat argument | -| params_flow.rb:121:10:121:10 | a | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:121:10:121:10 | a | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:121:10:121:10 | a | params_flow.rb:6:10:6:10 | x | -| params_flow.rb:121:10:121:10 | a | params_flow.rb:121:10:121:10 | a | | params_flow.rb:122:5:122:10 | call to sink | params_flow.rb:122:5:122:10 | call to sink | | params_flow.rb:122:5:122:10 | synthetic splat argument | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:122:5:122:10 | synthetic splat argument | params_flow.rb:122:5:122:10 | synthetic splat argument | -| params_flow.rb:122:10:122:10 | b | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:122:10:122:10 | b | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:122:10:122:10 | b | params_flow.rb:6:10:6:10 | x | -| params_flow.rb:122:10:122:10 | b | params_flow.rb:122:10:122:10 | b | | params_flow.rb:123:5:123:10 | call to sink | params_flow.rb:123:5:123:10 | call to sink | | params_flow.rb:123:5:123:10 | synthetic splat argument | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:123:5:123:10 | synthetic splat argument | params_flow.rb:123:5:123:10 | synthetic splat argument | -| params_flow.rb:123:10:123:10 | c | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:123:10:123:10 | c | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:123:10:123:10 | c | params_flow.rb:6:10:6:10 | x | -| params_flow.rb:123:10:123:10 | c | params_flow.rb:123:10:123:10 | c | | params_flow.rb:124:5:124:10 | call to sink | params_flow.rb:124:5:124:10 | call to sink | | params_flow.rb:124:5:124:10 | synthetic splat argument | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:124:5:124:10 | synthetic splat argument | params_flow.rb:124:5:124:10 | synthetic splat argument | -| params_flow.rb:124:10:124:10 | d | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:124:10:124:10 | d | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:124:10:124:10 | d | params_flow.rb:6:10:6:10 | x | -| params_flow.rb:124:10:124:10 | d | params_flow.rb:124:10:124:10 | d | | params_flow.rb:125:5:125:10 | call to sink | params_flow.rb:125:5:125:10 | call to sink | | params_flow.rb:125:5:125:10 | call to sink | params_flow.rb:128:1:128:61 | call to destruct | | params_flow.rb:125:5:125:10 | synthetic splat argument | params_flow.rb:5:1:7:3 | synthetic splat parameter | | params_flow.rb:125:5:125:10 | synthetic splat argument | params_flow.rb:125:5:125:10 | synthetic splat argument | -| params_flow.rb:125:10:125:10 | e | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:125:10:125:10 | e | params_flow.rb:5:10:5:10 | x | -| params_flow.rb:125:10:125:10 | e | params_flow.rb:6:10:6:10 | x | -| params_flow.rb:125:10:125:10 | e | params_flow.rb:125:10:125:10 | e | | params_flow.rb:128:1:128:61 | call to destruct | params_flow.rb:128:1:128:61 | call to destruct | | params_flow.rb:128:1:128:61 | synthetic splat argument | params_flow.rb:128:1:128:61 | synthetic splat argument | | params_flow.rb:128:10:128:31 | Array | params_flow.rb:128:10:128:31 | Array | diff --git a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql index a39710997a4f..2d6892629aab 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql @@ -1,6 +1,6 @@ import codeql.ruby.AST import codeql.ruby.DataFlow -import codeql.ruby.typetracking.TypeTracker +import codeql.ruby.typetracking.TypeTracking class LocalSourceNode extends DataFlow::LocalSourceNode { LocalSourceNode() { this.getLocation().getFile().getExtension() = "rb" } From 12359ba7332619f9af9e33af0b43c8eedc2f8a8f Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 21 Nov 2023 11:46:15 +0100 Subject: [PATCH 2/3] Add change note --- ruby/ql/lib/change-notes/2023-11-21-new-type-tracking-lib.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ruby/ql/lib/change-notes/2023-11-21-new-type-tracking-lib.md diff --git a/ruby/ql/lib/change-notes/2023-11-21-new-type-tracking-lib.md b/ruby/ql/lib/change-notes/2023-11-21-new-type-tracking-lib.md new file mode 100644 index 000000000000..c03804e59750 --- /dev/null +++ b/ruby/ql/lib/change-notes/2023-11-21-new-type-tracking-lib.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Ruby now makes use of the shared type tracking library, exposed as `codeql.ruby.typetracking.TypeTracking`. The existing type tracking library, `codeql.ruby.typetracking.TypeTracker`, has consequently been deprecated. \ No newline at end of file From c6e805faefabd68dce1dc8d1ff3e450328b20622 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 5 Dec 2023 14:57:15 +0100 Subject: [PATCH 3/3] Ruby: Add more deprecation comments --- ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index 97e15b4b9f54..8d0c6d6b543c 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -1,4 +1,8 @@ -/** Step Summaries and Type Tracking */ +/** + * DEPRECATED: Use `codeql.ruby.typetracking.TypeTracking` instead. + * + * Step Summaries and Type Tracking + */ private import TypeTrackerSpecific @@ -419,6 +423,8 @@ deprecated module StepSummary { } /** + * DEPRECATED: Use `codeql.ruby.typetracking.TypeTracking` instead. + * * A summary of the steps needed to track a value to a given dataflow node. * * This can be used to track objects that implement a certain API in order to @@ -597,6 +603,8 @@ deprecated private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSumm } /** + * DEPRECATED: Use `codeql.ruby.typetracking.TypeTracking` instead. + * * A summary of the steps needed to back-track a use of a value to a given dataflow node. * * This can for example be used to track callbacks that are passed to a certain API,