diff --git a/csharp/ql/lib/change-notes/2026-08-15-additional-taint-step.md b/csharp/ql/lib/change-notes/2026-08-15-additional-taint-step.md new file mode 100644 index 000000000000..6b87eef63b3a --- /dev/null +++ b/csharp/ql/lib/change-notes/2026-08-15-additional-taint-step.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added the `AdditionalTaintStep` extension point (`semmle.code.csharp.dataflow.FlowSteps`). Extend this class to add additional taint steps that apply to all taint-tracking configurations. diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSteps.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSteps.qll new file mode 100644 index 000000000000..6be7cd936ff1 --- /dev/null +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/FlowSteps.qll @@ -0,0 +1,20 @@ +/** + * Provides classes representing various flow steps for taint tracking. + */ + +private import codeql.util.Unit +private import semmle.code.csharp.dataflow.DataFlow + +/** + * A unit class for adding additional taint steps. + * + * Extend this class to add additional taint steps that should apply to all + * taint configurations. + */ +class AdditionalTaintStep extends Unit { + /** + * Holds if the step from `node1` to `node2` should be considered a taint + * step for all configurations. + */ + abstract predicate step(DataFlow::Node node1, DataFlow::Node node2); +} diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll index 2c90aa678050..238ecab13461 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll @@ -2,6 +2,7 @@ private import csharp private import TaintTrackingPublic private import FlowSummaryImpl as FlowSummaryImpl private import semmle.code.csharp.Caching +private import semmle.code.csharp.dataflow.FlowSteps private import semmle.code.csharp.dataflow.internal.DataFlowDispatch private import semmle.code.csharp.dataflow.internal.DataFlowPrivate private import semmle.code.csharp.dispatch.Dispatch @@ -172,6 +173,8 @@ private module Cached { model = "" or FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false, model) + or + any(AdditionalTaintStep a).step(nodeFrom, nodeTo) and model = "AdditionalTaintStep" } } diff --git a/csharp/ql/test/library-tests/dataflow/additional-taint-step/AdditionalTaintStep.expected b/csharp/ql/test/library-tests/dataflow/additional-taint-step/AdditionalTaintStep.expected new file mode 100644 index 000000000000..acd241d386f5 --- /dev/null +++ b/csharp/ql/test/library-tests/dataflow/additional-taint-step/AdditionalTaintStep.expected @@ -0,0 +1 @@ +| Test.cs:13:35:13:45 | access to parameter taintSource | Test.cs:13:23:13:46 | call to method Step | AdditionalTaintStep | diff --git a/csharp/ql/test/library-tests/dataflow/additional-taint-step/AdditionalTaintStep.ql b/csharp/ql/test/library-tests/dataflow/additional-taint-step/AdditionalTaintStep.ql new file mode 100644 index 000000000000..39d9e154b6f7 --- /dev/null +++ b/csharp/ql/test/library-tests/dataflow/additional-taint-step/AdditionalTaintStep.ql @@ -0,0 +1,24 @@ +import csharp +import semmle.code.csharp.dataflow.FlowSteps +import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate + +/** + * A test-only additional taint step that treats calls to `Marker.Step` as + * propagating taint from the argument to the call result, to verify that + * `AdditionalTaintStep` subclasses are picked up by `defaultAdditionalTaintStep`. + */ +private class MarkerStepTaintStep extends AdditionalTaintStep { + override predicate step(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodCall mc | + mc.getTarget().hasName("Step") and + mc.getTarget().getDeclaringType().hasName("Marker") + | + node1.asExpr() = mc.getArgument(0) and + node2.asExpr() = mc + ) + } +} + +from DataFlow::Node src, DataFlow::Node sink, string model +where defaultAdditionalTaintStep(src, sink, model) and model = "AdditionalTaintStep" +select src, sink, model diff --git a/csharp/ql/test/library-tests/dataflow/additional-taint-step/Test.cs b/csharp/ql/test/library-tests/dataflow/additional-taint-step/Test.cs new file mode 100644 index 000000000000..051793e2c855 --- /dev/null +++ b/csharp/ql/test/library-tests/dataflow/additional-taint-step/Test.cs @@ -0,0 +1,18 @@ +class Marker +{ + // A stand-in for a framework method that isn't otherwise understood by the + // taint-tracking library, whose taint behaviour is modelled by a test-only + // `AdditionalTaintStep` subclass instead. + public static object Step(object x) => new object(); +} + +class Test +{ + void M(object taintSource) + { + var tainted = Marker.Step(taintSource); + Sink(tainted); + } + + static void Sink(object o) { } +}