From a1f2b815669d92bcde42d6dc77c96d23f33d7ad5 Mon Sep 17 00:00:00 2001 From: hugo-syn Date: Wed, 19 Aug 2026 15:30:47 +0200 Subject: [PATCH 01/16] C#: add OData action-parameter taint modeling Adds semmle.code.csharp.frameworks.OData, following the WCF.qll/JsonNET.qll convention: values cast, as-converted, or type-tested out of an untyped ODataActionParameters dictionary, and entities tracked by Delta (via GetInstance/Patch/Put/CopyChangedValues/CopyUnchangedValues), have no static type relationship to the action method's own parameter types, so their members aren't picked up by the existing AspNetRemoteFlowSourceMember modeling. This adds a TaintedMember for those bound types (with the same nested-type/collection recursion as AspNetRemoteFlowSourceMember), plus two AdditionalTaintStep steps for the Delta method calls, which don't fit the member-read shape TaintedMember covers. Co-Authored-By: Claude Sonnet 5 --- .../2026-08-19-odata-taint-step.md | 4 + .../internal/TaintTrackingPrivate.qll | 1 + .../semmle/code/csharp/frameworks/OData.qll | 161 ++++++++++++++++++ .../library-tests/frameworks/OData/OData.cs | 125 ++++++++++++++ .../frameworks/OData/OData.expected | 9 + .../library-tests/frameworks/OData/OData.ql | 21 +++ 6 files changed, 321 insertions(+) create mode 100644 csharp/ql/lib/change-notes/2026-08-19-odata-taint-step.md create mode 100644 csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll create mode 100644 csharp/ql/test/library-tests/frameworks/OData/OData.cs create mode 100644 csharp/ql/test/library-tests/frameworks/OData/OData.expected create mode 100644 csharp/ql/test/library-tests/frameworks/OData/OData.ql diff --git a/csharp/ql/lib/change-notes/2026-08-19-odata-taint-step.md b/csharp/ql/lib/change-notes/2026-08-19-odata-taint-step.md new file mode 100644 index 000000000000..3d0a6cbeb8ae --- /dev/null +++ b/csharp/ql/lib/change-notes/2026-08-19-odata-taint-step.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added taint modeling for OData action parameter binding (`Microsoft.AspNet.OData`/`Microsoft.AspNetCore.OData`). Values cast, `as`-converted, or type-tested out of `ODataActionParameters`, and entities tracked by `Delta` (via `GetInstance`, `Patch`, `Put`, `CopyChangedValues`, and `CopyUnchangedValues`), now taint the members of the target type. 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 238ecab13461..418573cae8cc 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/TaintTrackingPrivate.qll @@ -9,6 +9,7 @@ private import semmle.code.csharp.dispatch.Dispatch private import semmle.code.csharp.commons.ComparisonTest // import `TaintedMember` definitions from other files to avoid potential reevaluation private import semmle.code.csharp.frameworks.JsonNET +private import semmle.code.csharp.frameworks.OData private import semmle.code.csharp.frameworks.WCF private import semmle.code.csharp.security.dataflow.flowsources.Remote diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll new file mode 100644 index 000000000000..899f1cbd036a --- /dev/null +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -0,0 +1,161 @@ +/** + * Provides taint modeling for `Microsoft.AspNet.OData`/`Microsoft.AspNetCore.OData` + * (and the older `System.Web.Http.OData`) OData action parameter binding. + * + * OData actions receive their untrusted payload in one of two shapes that + * bypass the usual "type used as an action-method parameter" taint modeling: + * + * - `ODataActionParameters`, an untyped `Dictionary` whose + * values are cast, `as`-converted, or type-tested to arbitrary model types + * by the action method body. + * - `Delta`, a change-tracking wrapper for PATCH/PUT requests, whose + * tracked property values are exposed via `GetInstance()` or copied onto an + * existing entity via `Patch`/`Put`/`CopyChangedValues`/`CopyUnchangedValues`. + * + * In both cases the type that ends up holding the client-controlled data has + * no static relationship to the action method's parameter types, so its + * members need to be taint-tracked explicitly. + */ + +private import csharp +private import semmle.code.csharp.commons.Collections +private import semmle.code.csharp.dataflow.FlowSteps +private import semmle.code.csharp.dataflow.TaintTracking +private import semmle.code.csharp.dataflow.internal.DataFlowPrivate + +/** The `ODataActionParameters` dictionary type, across OData library versions. */ +private class ODataActionParametersClass extends Class { + ODataActionParametersClass() { + this.hasFullyQualifiedName("Microsoft.AspNet.OData", "ODataActionParameters") or + this.hasFullyQualifiedName("Microsoft.AspNetCore.OData.Formatter", "ODataActionParameters") or + this.hasFullyQualifiedName("System.Web.Http.OData", "ODataActionParameters") + } +} + +/** An indexer read on an `ODataActionParameters` dictionary, e.g. `parameters["CabFile"]`. */ +private class ODataActionParameterRead extends ElementAccess { + ODataActionParameterRead() { this.getQualifier().getType() instanceof ODataActionParametersClass } +} + +/** Holds if `e` may (locally) hold the value of an `ODataActionParameters` entry. */ +private predicate isODataParameterValue(Expr e) { + TaintTracking::localExprTaint(any(ODataActionParameterRead r), e) +} + +/** The generic `Delta` change-tracking class, across OData library versions. */ +private class DeltaClass extends UnboundGenericClass { + DeltaClass() { + this.getNumberOfTypeParameters() = 1 and + ( + this.hasFullyQualifiedName("Microsoft.AspNet.OData", "Delta`1") or + this.hasFullyQualifiedName("Microsoft.AspNetCore.OData.Deltas", "Delta`1") + ) + } +} + +/** + * A type that a value read out of `ODataActionParameters` is cast, `as`-converted, + * or type-tested to -- directly, or wrapped in a collection (`List`, + * `IEnumerable`, arrays, ...) -- or a type that is tracked by a `Delta`. + */ +private class ODataBoundType extends ValueOrRefType { + ODataBoundType() { + exists(Cast c | isODataParameterValue(c.getExpr()) | + this = c.getTargetType() or + this = c.getTargetType().(CollectionType).getElementType() or + this = c.getTargetType().(ParamsCollectionType).getElementType() + ) + or + exists(IsExpr ie, Type t | + isODataParameterValue(ie.getExpr()) and + t = ie.getPattern().(TypePatternExpr).getCheckedType() + | + this = t or + this = t.(CollectionType).getElementType() or + this = t.(ParamsCollectionType).getElementType() + ) + or + this = any(ConstructedClass c | c.getUnboundGeneric() instanceof DeltaClass).getTypeArgument(0) + } +} + +private class CandidateODataMember extends Member { + CandidateODataMember() { + this.isPublic() and + not this.isStatic() and + ( + this = + any(Property p | + p.isAutoImplemented() and + p.getGetter().isPublic() and + p.getSetter().isPublic() + ) + or + this = any(Field f | f.isPublic()) + ) + } +} + +/** + * Taint members (transitively) on types used in + * 1. Casts, `as`-conversions, or type tests applied to `ODataActionParameters` values. + * 2. The type argument of a `Delta`. + * + * Note that this also impacts uses of such types in other contexts, the same + * trade-off `AspNetRemoteFlowSourceMember` (`Remote.qll`) makes for ASP.NET + * action-method parameters. + */ +private class ODataBoundMember extends TaintTracking::TaintedMember, CandidateODataMember { + ODataBoundMember() { + exists(Type t, Type t0 | t = this.getDeclaringType() | + (t = t0 or t = t0.(CollectionType).getElementType()) and + ( + t0 = any(ODataBoundMember m).getType() + or + t0 instanceof ODataBoundType + ) + ) + } +} + +/** The `Patch`, `Put`, `CopyChangedValues`, and `CopyUnchangedValues` methods on `Delta`. */ +private class DeltaMutatingMethod extends Method { + DeltaMutatingMethod() { + this.getDeclaringType() instanceof DeltaClass and + this.hasName(["Patch", "Put", "CopyChangedValues", "CopyUnchangedValues"]) + } +} + +/** + * A call to `Delta.Patch`/`Put`/`CopyChangedValues`/`CopyUnchangedValues` + * copies the changes tracked by the `Delta` receiver onto its `original` + * entity argument. + */ +private class DeltaMutatingCallTaintStep extends AdditionalTaintStep { + override predicate step(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodCall mc | + mc.getTarget().getUnboundDeclaration() instanceof DeltaMutatingMethod and + node1.asExpr() = mc.getQualifier() and + node2.(PostUpdateNode).getPreUpdateNode().asExpr() = mc.getArgument(0) + ) + } +} + +/** The `GetInstance` method on `Delta`. */ +private class DeltaGetInstanceMethod extends Method { + DeltaGetInstanceMethod() { + this.getDeclaringType() instanceof DeltaClass and + this.hasName("GetInstance") + } +} + +/** `Delta.GetInstance()` returns the tracked entity, carrying the same taint as the `Delta` itself. */ +private class DeltaGetInstanceTaintStep extends AdditionalTaintStep { + override predicate step(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodCall mc | + mc.getTarget().getUnboundDeclaration() instanceof DeltaGetInstanceMethod and + node1.asExpr() = mc.getQualifier() and + node2.asExpr() = mc + ) + } +} diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.cs b/csharp/ql/test/library-tests/frameworks/OData/OData.cs new file mode 100644 index 000000000000..ade9366f64d0 --- /dev/null +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; + +namespace Microsoft.AspNet.OData +{ + public class ODataActionParameters : Dictionary + { + } + + public class Delta where TStructuralType : class + { + private TStructuralType instance; + + public Delta() { instance = default(TStructuralType); } + + public TStructuralType GetInstance() => instance; + + public void Patch(TStructuralType original) { } + + public void Put(TStructuralType original) { } + + public void CopyChangedValues(TStructuralType original) { } + + public void CopyUnchangedValues(TStructuralType original) { } + } +} + +namespace Test +{ + using Microsoft.AspNet.OData; + using System.Collections.Generic; + + public class FileMetadata + { + public string Author { get; set; } + } + + public class UploadedFile + { + public string FileName { get; set; } + + public string FileContent { get; set; } + + public FileMetadata Metadata { get; set; } + + public List History { get; set; } + } + + public class SubscriptionRelation + { + public string EventName { get; set; } + + public string EventType { get; set; } + } + + public class Widget + { + public string Name { get; set; } + } + + public class UnrelatedType + { + // Never reached via an ODataActionParameters/Delta cast, so this + // member must stay untainted even though `UnrelatedType` itself is + // used elsewhere in the file. + public string Name { get; set; } + } + + public class OrderController + { + void Sink(object o) { } + + void CastFromDictionary(ODataActionParameters parameters) + { + var file = (UploadedFile)parameters["CabFile"]; + Sink(file); + Sink(file.FileName); + Sink(file.FileContent); + Sink(file.Metadata.Author); + foreach (var m in file.History) + { + Sink(m.Author); + } + } + + void IsAsFromDictionary(ODataActionParameters parameters) + { + if (parameters["NewEvents"] is IEnumerable relations1) + { + foreach (var item in relations1) + { + Sink(item.EventName); + } + } + + var relations2 = parameters["NewEvents"] as IEnumerable; + foreach (var item in relations2) + { + Sink(item.EventType); + } + } + + void DeltaPatch(Delta delta, Widget original) + { + delta.Patch(original); + Sink(original.Name); + } + + void DeltaGetInstance(Delta delta) + { + var w = delta.GetInstance(); + Sink(w.Name); + } + + void Untainted() + { + var w = new Widget(); + w.Name = "safe"; + Sink(w.Name); + + var u = new UnrelatedType(); + u.Name = "also safe"; + Sink(u.Name); + } + } +} diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.expected b/csharp/ql/test/library-tests/frameworks/OData/OData.expected new file mode 100644 index 000000000000..dbaffab69b1d --- /dev/null +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.expected @@ -0,0 +1,9 @@ +| OData.cs:72:55:72:64 | parameters | OData.cs:75:18:75:21 | access to local variable file | +| OData.cs:72:55:72:64 | parameters | OData.cs:76:18:76:30 | access to property FileName | +| OData.cs:72:55:72:64 | parameters | OData.cs:77:18:77:33 | access to property FileContent | +| OData.cs:72:55:72:64 | parameters | OData.cs:78:18:78:37 | access to property Author | +| OData.cs:72:55:72:64 | parameters | OData.cs:81:22:81:29 | access to property Author | +| OData.cs:85:55:85:64 | parameters | OData.cs:91:26:91:39 | access to property EventName | +| OData.cs:85:55:85:64 | parameters | OData.cs:98:22:98:35 | access to property EventType | +| OData.cs:102:39:102:43 | delta | OData.cs:105:18:105:30 | access to property Name | +| OData.cs:108:45:108:49 | delta | OData.cs:111:18:111:23 | access to property Name | diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.ql b/csharp/ql/test/library-tests/frameworks/OData/OData.ql new file mode 100644 index 000000000000..80d0f1d04dec --- /dev/null +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.ql @@ -0,0 +1,21 @@ +import csharp + +module TaintConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node n) { + exists(Parameter p | p = n.asParameter() | + p.getType().hasFullyQualifiedName("Microsoft.AspNet.OData", "ODataActionParameters") + or + p.getType().getUnboundDeclaration().hasFullyQualifiedName("Microsoft.AspNet.OData", "Delta`1") + ) + } + + predicate isSink(DataFlow::Node sink) { + exists(MethodCall c | c.getArgument(0) = sink.asExpr() and c.getTarget().hasName("Sink")) + } +} + +module Taint = TaintTracking::Global; + +from DataFlow::Node source, DataFlow::Node sink +where Taint::flow(source, sink) +select source, sink From e88e00388effca676a02f87fc8ce712905f725ee Mon Sep 17 00:00:00 2001 From: hugo-syn Date: Wed, 19 Aug 2026 15:45:00 +0200 Subject: [PATCH 02/16] C#: make OData.qll classes public, genericize test fixtures Match WCF.qll's convention: only the TaintedMember/AdditionalTaintStep wiring classes stay private, everything else that identifies a reusable OData domain concept (ODataActionParametersClass, DeltaClass, ODataBoundType, DeltaMutatingMethod, DeltaGetInstanceMethod) is public. Also renames the test fixtures to generic placeholder names. Co-Authored-By: Claude Sonnet 5 --- .../semmle/code/csharp/frameworks/OData.qll | 44 ++++++++--------- .../library-tests/frameworks/OData/OData.cs | 48 +++++++++---------- .../frameworks/OData/OData.expected | 14 +++--- 3 files changed, 53 insertions(+), 53 deletions(-) diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index 899f1cbd036a..406041f04e10 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -17,14 +17,14 @@ * members need to be taint-tracked explicitly. */ -private import csharp +import csharp private import semmle.code.csharp.commons.Collections private import semmle.code.csharp.dataflow.FlowSteps private import semmle.code.csharp.dataflow.TaintTracking private import semmle.code.csharp.dataflow.internal.DataFlowPrivate /** The `ODataActionParameters` dictionary type, across OData library versions. */ -private class ODataActionParametersClass extends Class { +class ODataActionParametersClass extends Class { ODataActionParametersClass() { this.hasFullyQualifiedName("Microsoft.AspNet.OData", "ODataActionParameters") or this.hasFullyQualifiedName("Microsoft.AspNetCore.OData.Formatter", "ODataActionParameters") or @@ -32,8 +32,8 @@ private class ODataActionParametersClass extends Class { } } -/** An indexer read on an `ODataActionParameters` dictionary, e.g. `parameters["CabFile"]`. */ -private class ODataActionParameterRead extends ElementAccess { +/** An indexer read on an `ODataActionParameters` dictionary, e.g. `parameters["Foo"]`. */ +class ODataActionParameterRead extends ElementAccess { ODataActionParameterRead() { this.getQualifier().getType() instanceof ODataActionParametersClass } } @@ -43,7 +43,7 @@ private predicate isODataParameterValue(Expr e) { } /** The generic `Delta` change-tracking class, across OData library versions. */ -private class DeltaClass extends UnboundGenericClass { +class DeltaClass extends UnboundGenericClass { DeltaClass() { this.getNumberOfTypeParameters() = 1 and ( @@ -58,7 +58,7 @@ private class DeltaClass extends UnboundGenericClass { * or type-tested to -- directly, or wrapped in a collection (`List`, * `IEnumerable`, arrays, ...) -- or a type that is tracked by a `Delta`. */ -private class ODataBoundType extends ValueOrRefType { +class ODataBoundType extends ValueOrRefType { ODataBoundType() { exists(Cast c | isODataParameterValue(c.getExpr()) | this = c.getTargetType() or @@ -79,6 +79,22 @@ private class ODataBoundType extends ValueOrRefType { } } +/** The `Patch`, `Put`, `CopyChangedValues`, and `CopyUnchangedValues` methods on `Delta`. */ +class DeltaMutatingMethod extends Method { + DeltaMutatingMethod() { + this.getDeclaringType() instanceof DeltaClass and + this.hasName(["Patch", "Put", "CopyChangedValues", "CopyUnchangedValues"]) + } +} + +/** The `GetInstance` method on `Delta`. */ +class DeltaGetInstanceMethod extends Method { + DeltaGetInstanceMethod() { + this.getDeclaringType() instanceof DeltaClass and + this.hasName("GetInstance") + } +} + private class CandidateODataMember extends Member { CandidateODataMember() { this.isPublic() and @@ -118,14 +134,6 @@ private class ODataBoundMember extends TaintTracking::TaintedMember, CandidateOD } } -/** The `Patch`, `Put`, `CopyChangedValues`, and `CopyUnchangedValues` methods on `Delta`. */ -private class DeltaMutatingMethod extends Method { - DeltaMutatingMethod() { - this.getDeclaringType() instanceof DeltaClass and - this.hasName(["Patch", "Put", "CopyChangedValues", "CopyUnchangedValues"]) - } -} - /** * A call to `Delta.Patch`/`Put`/`CopyChangedValues`/`CopyUnchangedValues` * copies the changes tracked by the `Delta` receiver onto its `original` @@ -141,14 +149,6 @@ private class DeltaMutatingCallTaintStep extends AdditionalTaintStep { } } -/** The `GetInstance` method on `Delta`. */ -private class DeltaGetInstanceMethod extends Method { - DeltaGetInstanceMethod() { - this.getDeclaringType() instanceof DeltaClass and - this.hasName("GetInstance") - } -} - /** `Delta.GetInstance()` returns the tracked entity, carrying the same taint as the `Delta` itself. */ private class DeltaGetInstanceTaintStep extends AdditionalTaintStep { override predicate step(DataFlow::Node node1, DataFlow::Node node2) { diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.cs b/csharp/ql/test/library-tests/frameworks/OData/OData.cs index ade9366f64d0..71a5c2218b68 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.cs +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.cs @@ -29,27 +29,27 @@ namespace Test using Microsoft.AspNet.OData; using System.Collections.Generic; - public class FileMetadata + public class EntityMetadata { - public string Author { get; set; } + public string Owner { get; set; } } - public class UploadedFile + public class BoundEntity { - public string FileName { get; set; } + public string Name { get; set; } - public string FileContent { get; set; } + public string Content { get; set; } - public FileMetadata Metadata { get; set; } + public EntityMetadata Metadata { get; set; } - public List History { get; set; } + public List Revisions { get; set; } } - public class SubscriptionRelation + public class RelatedItem { - public string EventName { get; set; } + public string Label { get; set; } - public string EventType { get; set; } + public string Category { get; set; } } public class Widget @@ -65,37 +65,37 @@ public class UnrelatedType public string Name { get; set; } } - public class OrderController + public class SampleController { void Sink(object o) { } void CastFromDictionary(ODataActionParameters parameters) { - var file = (UploadedFile)parameters["CabFile"]; - Sink(file); - Sink(file.FileName); - Sink(file.FileContent); - Sink(file.Metadata.Author); - foreach (var m in file.History) + var entity = (BoundEntity)parameters["Entity"]; + Sink(entity); + Sink(entity.Name); + Sink(entity.Content); + Sink(entity.Metadata.Owner); + foreach (var m in entity.Revisions) { - Sink(m.Author); + Sink(m.Owner); } } void IsAsFromDictionary(ODataActionParameters parameters) { - if (parameters["NewEvents"] is IEnumerable relations1) + if (parameters["Items"] is IEnumerable items1) { - foreach (var item in relations1) + foreach (var item in items1) { - Sink(item.EventName); + Sink(item.Label); } } - var relations2 = parameters["NewEvents"] as IEnumerable; - foreach (var item in relations2) + var items2 = parameters["Items"] as IEnumerable; + foreach (var item in items2) { - Sink(item.EventType); + Sink(item.Category); } } diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.expected b/csharp/ql/test/library-tests/frameworks/OData/OData.expected index dbaffab69b1d..a9a2796c0ca5 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.expected +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.expected @@ -1,9 +1,9 @@ -| OData.cs:72:55:72:64 | parameters | OData.cs:75:18:75:21 | access to local variable file | -| OData.cs:72:55:72:64 | parameters | OData.cs:76:18:76:30 | access to property FileName | -| OData.cs:72:55:72:64 | parameters | OData.cs:77:18:77:33 | access to property FileContent | -| OData.cs:72:55:72:64 | parameters | OData.cs:78:18:78:37 | access to property Author | -| OData.cs:72:55:72:64 | parameters | OData.cs:81:22:81:29 | access to property Author | -| OData.cs:85:55:85:64 | parameters | OData.cs:91:26:91:39 | access to property EventName | -| OData.cs:85:55:85:64 | parameters | OData.cs:98:22:98:35 | access to property EventType | +| OData.cs:72:55:72:64 | parameters | OData.cs:75:18:75:23 | access to local variable entity | +| OData.cs:72:55:72:64 | parameters | OData.cs:76:18:76:28 | access to property Name | +| OData.cs:72:55:72:64 | parameters | OData.cs:77:18:77:31 | access to property Content | +| OData.cs:72:55:72:64 | parameters | OData.cs:78:18:78:38 | access to property Owner | +| OData.cs:72:55:72:64 | parameters | OData.cs:81:22:81:28 | access to property Owner | +| OData.cs:85:55:85:64 | parameters | OData.cs:91:26:91:35 | access to property Label | +| OData.cs:85:55:85:64 | parameters | OData.cs:98:22:98:34 | access to property Category | | OData.cs:102:39:102:43 | delta | OData.cs:105:18:105:30 | access to property Name | | OData.cs:108:45:108:49 | delta | OData.cs:111:18:111:23 | access to property Name | From 3440d4a1eb5338c78c5faf7e40fdacd53c4f8a77 Mon Sep 17 00:00:00 2001 From: hugo-syn Date: Wed, 19 Aug 2026 15:53:23 +0200 Subject: [PATCH 03/16] C#: drop redundant TaintTracking import in OData.qll import csharp already publicly imports semmle.code.csharp.dataflow.TaintTracking (and DataFlow), same as WCF.qll/JsonNET.qll rely on implicitly. Co-Authored-By: Claude Sonnet 5 --- csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll | 1 - 1 file changed, 1 deletion(-) diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index 406041f04e10..80917322f441 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -20,7 +20,6 @@ import csharp private import semmle.code.csharp.commons.Collections private import semmle.code.csharp.dataflow.FlowSteps -private import semmle.code.csharp.dataflow.TaintTracking private import semmle.code.csharp.dataflow.internal.DataFlowPrivate /** The `ODataActionParameters` dictionary type, across OData library versions. */ From 22ad8c7dc130d7dbde025590985cd17aa315cb1f Mon Sep 17 00:00:00 2001 From: hugo-syn Date: Fri, 21 Aug 2026 09:51:56 +0200 Subject: [PATCH 04/16] C#: model Delta.GetInstance() as a MaD summary instead of QL Per review feedback on #22384, replace the hand-written DeltaGetInstanceMethod/DeltaGetInstanceTaintStep taint step with a Models-as-Data summaryModel row for both the Microsoft.AspNet.OData and Microsoft.AspNetCore.OData.Deltas variants of Delta.GetInstance(). --- .../lib/ext/Microsoft.AspNet.OData.model.yml | 7 +++++++ .../semmle/code/csharp/frameworks/OData.qll | 19 ------------------- 2 files changed, 7 insertions(+), 19 deletions(-) create mode 100644 csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml diff --git a/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml b/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml new file mode 100644 index 000000000000..ede706dd194a --- /dev/null +++ b/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml @@ -0,0 +1,7 @@ +extensions: + - addsTo: + pack: codeql/csharp-all + extensible: summaryModel + data: + - ["Microsoft.AspNet.OData", "Delta", True, "GetInstance", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] + - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "GetInstance", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index 80917322f441..4a366c12ce64 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -86,14 +86,6 @@ class DeltaMutatingMethod extends Method { } } -/** The `GetInstance` method on `Delta`. */ -class DeltaGetInstanceMethod extends Method { - DeltaGetInstanceMethod() { - this.getDeclaringType() instanceof DeltaClass and - this.hasName("GetInstance") - } -} - private class CandidateODataMember extends Member { CandidateODataMember() { this.isPublic() and @@ -147,14 +139,3 @@ private class DeltaMutatingCallTaintStep extends AdditionalTaintStep { ) } } - -/** `Delta.GetInstance()` returns the tracked entity, carrying the same taint as the `Delta` itself. */ -private class DeltaGetInstanceTaintStep extends AdditionalTaintStep { - override predicate step(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodCall mc | - mc.getTarget().getUnboundDeclaration() instanceof DeltaGetInstanceMethod and - node1.asExpr() = mc.getQualifier() and - node2.asExpr() = mc - ) - } -} From a44bfec092d2c71bedea0851567bd89a25f9b822 Mon Sep 17 00:00:00 2001 From: hugo-syn Date: Fri, 21 Aug 2026 09:53:09 +0200 Subject: [PATCH 05/16] C#: reuse CandidateMemberToTaint from Remote.qll in OData.qll Per review feedback on #22384, OData.qll's CandidateODataMember was an exact copy of CandidateMemberToTaint from Remote.qll. Make that class public and import it instead of duplicating it. --- .../semmle/code/csharp/frameworks/OData.qll | 20 ++----------------- .../security/dataflow/flowsources/Remote.qll | 3 ++- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index 4a366c12ce64..3065b7127cc5 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -21,6 +21,7 @@ import csharp private import semmle.code.csharp.commons.Collections private import semmle.code.csharp.dataflow.FlowSteps private import semmle.code.csharp.dataflow.internal.DataFlowPrivate +private import semmle.code.csharp.security.dataflow.flowsources.Remote /** The `ODataActionParameters` dictionary type, across OData library versions. */ class ODataActionParametersClass extends Class { @@ -86,23 +87,6 @@ class DeltaMutatingMethod extends Method { } } -private class CandidateODataMember extends Member { - CandidateODataMember() { - this.isPublic() and - not this.isStatic() and - ( - this = - any(Property p | - p.isAutoImplemented() and - p.getGetter().isPublic() and - p.getSetter().isPublic() - ) - or - this = any(Field f | f.isPublic()) - ) - } -} - /** * Taint members (transitively) on types used in * 1. Casts, `as`-conversions, or type tests applied to `ODataActionParameters` values. @@ -112,7 +96,7 @@ private class CandidateODataMember extends Member { * trade-off `AspNetRemoteFlowSourceMember` (`Remote.qll`) makes for ASP.NET * action-method parameters. */ -private class ODataBoundMember extends TaintTracking::TaintedMember, CandidateODataMember { +private class ODataBoundMember extends TaintTracking::TaintedMember, CandidateMemberToTaint { ODataBoundMember() { exists(Type t, Type t0 | t = this.getDeclaringType() | (t = t0 or t = t0.(CollectionType).getElementType()) and diff --git a/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll b/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll index 68c06a1828de..3eac08b1bd93 100644 --- a/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll +++ b/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsources/Remote.qll @@ -117,7 +117,8 @@ class AspNetServiceRemoteFlowSource extends AspNetRemoteFlowSource, DataFlow::Pa override string getSourceType() { result = "ASP.NET web service input" } } -private class CandidateMemberToTaint extends Member { +/** A public, non-static, auto-implemented property or field, candidate for taint-tracking. */ +class CandidateMemberToTaint extends Member { CandidateMemberToTaint() { this.isPublic() and not this.isStatic() and From 2c17dd2149fe884c18a08f82ebc136431c7016dc Mon Sep 17 00:00:00 2001 From: hugo-syn Date: Fri, 21 Aug 2026 09:53:28 +0200 Subject: [PATCH 06/16] C#: move OData test types into test/resources/stubs Per review feedback on #22384, keep the ODataActionParameters/Delta stub implementations out of the test .cs file and store them in test/resources/stubs instead, following the pattern used by other frameworks (e.g. JsonNET, Aws). The test now loads the stub project via an options file and relies on no .dll files. --- .../library-tests/frameworks/OData/OData.cs | 26 ------------------- .../frameworks/OData/OData.expected | 18 ++++++------- .../library-tests/frameworks/OData/options | 2 ++ .../7.7.5/Microsoft.AspNet.OData.cs | 19 ++++++++++++++ .../7.7.5/Microsoft.AspNet.OData.csproj | 12 +++++++++ 5 files changed, 42 insertions(+), 35 deletions(-) create mode 100644 csharp/ql/test/library-tests/frameworks/OData/options create mode 100644 csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs create mode 100644 csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.cs b/csharp/ql/test/library-tests/frameworks/OData/OData.cs index 71a5c2218b68..ab06179ab79a 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.cs +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.cs @@ -1,29 +1,3 @@ -using System.Collections.Generic; - -namespace Microsoft.AspNet.OData -{ - public class ODataActionParameters : Dictionary - { - } - - public class Delta where TStructuralType : class - { - private TStructuralType instance; - - public Delta() { instance = default(TStructuralType); } - - public TStructuralType GetInstance() => instance; - - public void Patch(TStructuralType original) { } - - public void Put(TStructuralType original) { } - - public void CopyChangedValues(TStructuralType original) { } - - public void CopyUnchangedValues(TStructuralType original) { } - } -} - namespace Test { using Microsoft.AspNet.OData; diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.expected b/csharp/ql/test/library-tests/frameworks/OData/OData.expected index a9a2796c0ca5..fad59d4a9a9b 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.expected +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.expected @@ -1,9 +1,9 @@ -| OData.cs:72:55:72:64 | parameters | OData.cs:75:18:75:23 | access to local variable entity | -| OData.cs:72:55:72:64 | parameters | OData.cs:76:18:76:28 | access to property Name | -| OData.cs:72:55:72:64 | parameters | OData.cs:77:18:77:31 | access to property Content | -| OData.cs:72:55:72:64 | parameters | OData.cs:78:18:78:38 | access to property Owner | -| OData.cs:72:55:72:64 | parameters | OData.cs:81:22:81:28 | access to property Owner | -| OData.cs:85:55:85:64 | parameters | OData.cs:91:26:91:35 | access to property Label | -| OData.cs:85:55:85:64 | parameters | OData.cs:98:22:98:34 | access to property Category | -| OData.cs:102:39:102:43 | delta | OData.cs:105:18:105:30 | access to property Name | -| OData.cs:108:45:108:49 | delta | OData.cs:111:18:111:23 | access to property Name | +| OData.cs:46:55:46:64 | parameters | OData.cs:49:18:49:23 | access to local variable entity | +| OData.cs:46:55:46:64 | parameters | OData.cs:50:18:50:28 | access to property Name | +| OData.cs:46:55:46:64 | parameters | OData.cs:51:18:51:31 | access to property Content | +| OData.cs:46:55:46:64 | parameters | OData.cs:52:18:52:38 | access to property Owner | +| OData.cs:46:55:46:64 | parameters | OData.cs:55:22:55:28 | access to property Owner | +| OData.cs:59:55:59:64 | parameters | OData.cs:65:26:65:35 | access to property Label | +| OData.cs:59:55:59:64 | parameters | OData.cs:72:22:72:34 | access to property Category | +| OData.cs:76:39:76:43 | delta | OData.cs:79:18:79:30 | access to property Name | +| OData.cs:82:45:82:49 | delta | OData.cs:85:18:85:23 | access to property Name | diff --git a/csharp/ql/test/library-tests/frameworks/OData/options b/csharp/ql/test/library-tests/frameworks/OData/options new file mode 100644 index 000000000000..357763232dbe --- /dev/null +++ b/csharp/ql/test/library-tests/frameworks/OData/options @@ -0,0 +1,2 @@ +semmle-extractor-options: /nostdlib /noconfig +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj diff --git a/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs b/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs new file mode 100644 index 000000000000..2f6673628748 --- /dev/null +++ b/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs @@ -0,0 +1,19 @@ +// This file contains auto-generated code. +// Generated from `Microsoft.AspNet.OData, Version=7.7.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`. +namespace Microsoft.AspNet.OData +{ + public class ODataActionParameters : System.Collections.Generic.Dictionary + { + public ODataActionParameters() => throw null; + } + + public class Delta where TStructuralType : class + { + public Delta() => throw null; + public TStructuralType GetInstance() => throw null; + public void Patch(TStructuralType original) => throw null; + public void Put(TStructuralType original) => throw null; + public void CopyChangedValues(TStructuralType original) => throw null; + public void CopyUnchangedValues(TStructuralType original) => throw null; + } +} diff --git a/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj b/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj new file mode 100644 index 000000000000..2be6995cd169 --- /dev/null +++ b/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj @@ -0,0 +1,12 @@ + + + net10.0 + true + bin\ + false + + + + + + From 9c3a9b48187dc8e12f517464372ff9ed5d5423d4 Mon Sep 17 00:00:00 2001 From: hugo-syn Date: Fri, 21 Aug 2026 09:53:39 +0200 Subject: [PATCH 07/16] C#: fix DeltaClass doc comment to not name a specific type parameter Per review feedback on #22384, the doc comment named the type parameter TStructuralType, but the AspNetCore variant of Delta names it T. Refer to the unbound generic as \`Delta\`1\`\` instead. --- csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index 3065b7127cc5..936a50d6cf30 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -42,7 +42,7 @@ private predicate isODataParameterValue(Expr e) { TaintTracking::localExprTaint(any(ODataActionParameterRead r), e) } -/** The generic `Delta` change-tracking class, across OData library versions. */ +/** The generic ``Delta`1`` change-tracking class, across OData library versions. */ class DeltaClass extends UnboundGenericClass { DeltaClass() { this.getNumberOfTypeParameters() = 1 and From 746b8067f15f8f9380fa1978acea6acbe79ce331 Mon Sep 17 00:00:00 2001 From: hugo-syn <61210734+hugo-syn@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:04:40 +0200 Subject: [PATCH 08/16] Update csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml Co-authored-by: Michael Nebel --- csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml b/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml index ede706dd194a..a2da2bee2dfe 100644 --- a/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml +++ b/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml @@ -4,4 +4,12 @@ extensions: extensible: summaryModel data: - ["Microsoft.AspNet.OData", "Delta", True, "GetInstance", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] + - ["Microsoft.AspNet.OData", "Delta", True, "Patch", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["Microsoft.AspNet.OData", "Delta", True, "Put", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["Microsoft.AspNet.OData", "Delta", True, "CopyChangedValues", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["Microsoft.AspNet.OData", "Delta", True, "CopyUnchangedValues", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "GetInstance", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] + - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "Patch", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "Put", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "CopyChangedValues", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "CopyUnchangedValues", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] From 131b6be091722116ab832e0e72ea54c00911a0ea Mon Sep 17 00:00:00 2001 From: hugo-syn <61210734+hugo-syn@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:04:52 +0200 Subject: [PATCH 09/16] Update csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll Co-authored-by: Michael Nebel --- csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll | 8 -------- 1 file changed, 8 deletions(-) diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index 936a50d6cf30..9989583fd6ae 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -79,14 +79,6 @@ class ODataBoundType extends ValueOrRefType { } } -/** The `Patch`, `Put`, `CopyChangedValues`, and `CopyUnchangedValues` methods on `Delta`. */ -class DeltaMutatingMethod extends Method { - DeltaMutatingMethod() { - this.getDeclaringType() instanceof DeltaClass and - this.hasName(["Patch", "Put", "CopyChangedValues", "CopyUnchangedValues"]) - } -} - /** * Taint members (transitively) on types used in * 1. Casts, `as`-conversions, or type tests applied to `ODataActionParameters` values. From de9eae2324310170a0218ac49435f91103439261 Mon Sep 17 00:00:00 2001 From: hugo-syn <61210734+hugo-syn@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:05:04 +0200 Subject: [PATCH 10/16] Update csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll Co-authored-by: Michael Nebel --- .../lib/semmle/code/csharp/frameworks/OData.qll | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index 9989583fd6ae..e5ca56a60e99 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -100,18 +100,3 @@ private class ODataBoundMember extends TaintTracking::TaintedMember, CandidateMe ) } } - -/** - * A call to `Delta.Patch`/`Put`/`CopyChangedValues`/`CopyUnchangedValues` - * copies the changes tracked by the `Delta` receiver onto its `original` - * entity argument. - */ -private class DeltaMutatingCallTaintStep extends AdditionalTaintStep { - override predicate step(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodCall mc | - mc.getTarget().getUnboundDeclaration() instanceof DeltaMutatingMethod and - node1.asExpr() = mc.getQualifier() and - node2.(PostUpdateNode).getPreUpdateNode().asExpr() = mc.getArgument(0) - ) - } -} From 93baa405ecae6ce83d0b90b877828e584f2bb4b4 Mon Sep 17 00:00:00 2001 From: hugo-syn Date: Tue, 25 Aug 2026 16:03:30 +0200 Subject: [PATCH 11/16] fix: add fix from review --- .../lib/ext/Microsoft.AspNet.OData.model.yml | 8 ++++ .../semmle/code/csharp/frameworks/OData.qll | 44 ++++++++++++++++--- .../library-tests/frameworks/OData/OData.cs | 40 +++++++++++++++++ .../frameworks/OData/OData.expected | 10 ++++- .../library-tests/frameworks/OData/OData.ql | 2 + .../library-tests/frameworks/OData/options | 1 + .../7.7.5/Microsoft.AspNet.OData.cs | 6 +-- .../resources/stubs/System.Web.Http.OData.cs | 15 +++++++ 8 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 csharp/ql/test/resources/stubs/System.Web.Http.OData.cs diff --git a/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml b/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml index a2da2bee2dfe..e27c4ccfe529 100644 --- a/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml +++ b/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml @@ -5,11 +5,19 @@ extensions: data: - ["Microsoft.AspNet.OData", "Delta", True, "GetInstance", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNet.OData", "Delta", True, "Patch", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["Microsoft.AspNet.OData", "Delta", True, "Patch", "(TStructuralType)", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNet.OData", "Delta", True, "Put", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - ["Microsoft.AspNet.OData", "Delta", True, "CopyChangedValues", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["Microsoft.AspNet.OData", "Delta", True, "CopyChangedValues", "(TStructuralType)", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNet.OData", "Delta", True, "CopyUnchangedValues", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "GetInstance", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "Patch", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "Patch", "(T)", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "Put", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "CopyChangedValues", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "CopyUnchangedValues", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["System.Web.Http.OData", "Delta", True, "GetEntity", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] + - ["System.Web.Http.OData", "Delta", True, "Patch", "(TEntityType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["System.Web.Http.OData", "Delta", True, "Put", "(TEntityType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["System.Web.Http.OData", "Delta", True, "CopyChangedValues", "(TEntityType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] + - ["System.Web.Http.OData", "Delta", True, "CopyUnchangedValues", "(TEntityType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index e5ca56a60e99..9571268c6b30 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -9,8 +9,9 @@ * values are cast, `as`-converted, or type-tested to arbitrary model types * by the action method body. * - `Delta`, a change-tracking wrapper for PATCH/PUT requests, whose - * tracked property values are exposed via `GetInstance()` or copied onto an - * existing entity via `Patch`/`Put`/`CopyChangedValues`/`CopyUnchangedValues`. + * tracked property values are exposed via `GetInstance()` (`GetEntity()` in + * the older `System.Web.Http.OData`) or copied onto an existing entity via + * `Patch`/`Put`/`CopyChangedValues`/`CopyUnchangedValues`. * * In both cases the type that ends up holding the client-controlled data has * no static relationship to the action method's parameter types, so its @@ -20,7 +21,6 @@ import csharp private import semmle.code.csharp.commons.Collections private import semmle.code.csharp.dataflow.FlowSteps -private import semmle.code.csharp.dataflow.internal.DataFlowPrivate private import semmle.code.csharp.security.dataflow.flowsources.Remote /** The `ODataActionParameters` dictionary type, across OData library versions. */ @@ -32,14 +32,43 @@ class ODataActionParametersClass extends Class { } } -/** An indexer read on an `ODataActionParameters` dictionary, e.g. `parameters["Foo"]`. */ +/** + * Holds if `e` is (or, via local flow -- e.g. an upcast to `IDictionary` + * -- may hold the value of) an `ODataActionParameters` dictionary. + */ +private predicate isODataActionParametersValue(Expr e) { + e.getType() instanceof ODataActionParametersClass + or + DataFlow::localExprFlow(any(Expr e0 | isODataActionParametersValue(e0)), e) +} + +/** + * An indexer read on an `ODataActionParameters` dictionary, e.g. `parameters["Foo"]` + * (including through an upcast to a base dictionary type/interface). + */ class ODataActionParameterRead extends ElementAccess { - ODataActionParameterRead() { this.getQualifier().getType() instanceof ODataActionParametersClass } + ODataActionParameterRead() { isODataActionParametersValue(this.getQualifier()) } +} + +/** + * A call to `TryGetValue` on an `ODataActionParameters` dictionary copies the value + * of the looked-up entry into the `out` argument. + */ +private class ODataActionParametersTryGetValueTaintStep extends AdditionalTaintStep { + override predicate step(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodCall mc, AssignableDefinitions::OutRefDefinition def | + mc.getTarget().hasName("TryGetValue") and + isODataActionParametersValue(mc.getQualifier()) and + node1.asExpr() = mc.getQualifier() and + def.getTargetAccess() = mc.getArgumentForName("value") and + node2 = DataFlow::assignableDefinitionNode(def) + ) + } } /** Holds if `e` may (locally) hold the value of an `ODataActionParameters` entry. */ private predicate isODataParameterValue(Expr e) { - TaintTracking::localExprTaint(any(ODataActionParameterRead r), e) + DataFlow::localExprFlow(any(ODataActionParameterRead r), e) } /** The generic ``Delta`1`` change-tracking class, across OData library versions. */ @@ -48,7 +77,8 @@ class DeltaClass extends UnboundGenericClass { this.getNumberOfTypeParameters() = 1 and ( this.hasFullyQualifiedName("Microsoft.AspNet.OData", "Delta`1") or - this.hasFullyQualifiedName("Microsoft.AspNetCore.OData.Deltas", "Delta`1") + this.hasFullyQualifiedName("Microsoft.AspNetCore.OData.Deltas", "Delta`1") or + this.hasFullyQualifiedName("System.Web.Http.OData", "Delta`1") ) } } diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.cs b/csharp/ql/test/library-tests/frameworks/OData/OData.cs index ab06179ab79a..bbce96d56fc9 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.cs +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.cs @@ -73,6 +73,22 @@ void IsAsFromDictionary(ODataActionParameters parameters) } } + void TryGetValueFromDictionary(ODataActionParameters parameters) + { + if (parameters.TryGetValue("Entity", out var value)) + { + var entity = (BoundEntity)value; + Sink(entity.Name); + } + } + + void UpcastThenIndex(ODataActionParameters parameters) + { + IDictionary dict = parameters; + var entity = (BoundEntity)dict["Entity"]; + Sink(entity.Name); + } + void DeltaPatch(Delta delta, Widget original) { delta.Patch(original); @@ -85,6 +101,30 @@ void DeltaGetInstance(Delta delta) Sink(w.Name); } + void DeltaPatchReturnValue(Delta delta, Widget original) + { + var updated = delta.Patch(original); + Sink(updated.Name); + } + + void DeltaCopyChangedValuesReturnValue(Delta delta, Widget original) + { + var updated = delta.CopyChangedValues(original); + Sink(updated.Name); + } + + void LegacyDeltaPatch(System.Web.Http.OData.Delta delta, Widget original) + { + delta.Patch(original); + Sink(original.Name); + } + + void LegacyDeltaGetEntity(System.Web.Http.OData.Delta delta) + { + var w = delta.GetEntity(); + Sink(w.Name); + } + void Untainted() { var w = new Widget(); diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.expected b/csharp/ql/test/library-tests/frameworks/OData/OData.expected index fad59d4a9a9b..290236df722e 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.expected +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.expected @@ -5,5 +5,11 @@ | OData.cs:46:55:46:64 | parameters | OData.cs:55:22:55:28 | access to property Owner | | OData.cs:59:55:59:64 | parameters | OData.cs:65:26:65:35 | access to property Label | | OData.cs:59:55:59:64 | parameters | OData.cs:72:22:72:34 | access to property Category | -| OData.cs:76:39:76:43 | delta | OData.cs:79:18:79:30 | access to property Name | -| OData.cs:82:45:82:49 | delta | OData.cs:85:18:85:23 | access to property Name | +| OData.cs:76:62:76:71 | parameters | OData.cs:81:22:81:32 | access to property Name | +| OData.cs:85:52:85:61 | parameters | OData.cs:89:18:89:28 | access to property Name | +| OData.cs:92:39:92:43 | delta | OData.cs:95:18:95:30 | access to property Name | +| OData.cs:98:45:98:49 | delta | OData.cs:101:18:101:23 | access to property Name | +| OData.cs:104:50:104:54 | delta | OData.cs:107:18:107:29 | access to property Name | +| OData.cs:110:62:110:66 | delta | OData.cs:113:18:113:29 | access to property Name | +| OData.cs:116:67:116:71 | delta | OData.cs:119:18:119:30 | access to property Name | +| OData.cs:122:71:122:75 | delta | OData.cs:125:18:125:23 | access to property Name | diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.ql b/csharp/ql/test/library-tests/frameworks/OData/OData.ql index 80d0f1d04dec..ed70740699e3 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.ql +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.ql @@ -6,6 +6,8 @@ module TaintConfig implements DataFlow::ConfigSig { p.getType().hasFullyQualifiedName("Microsoft.AspNet.OData", "ODataActionParameters") or p.getType().getUnboundDeclaration().hasFullyQualifiedName("Microsoft.AspNet.OData", "Delta`1") + or + p.getType().getUnboundDeclaration().hasFullyQualifiedName("System.Web.Http.OData", "Delta`1") ) } diff --git a/csharp/ql/test/library-tests/frameworks/OData/options b/csharp/ql/test/library-tests/frameworks/OData/options index 357763232dbe..e9ba768d29a7 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/options +++ b/csharp/ql/test/library-tests/frameworks/OData/options @@ -1,2 +1,3 @@ semmle-extractor-options: /nostdlib /noconfig semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj +semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.Http.OData.cs diff --git a/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs b/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs index 2f6673628748..da46281cf2d1 100644 --- a/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs +++ b/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs @@ -1,5 +1,3 @@ -// This file contains auto-generated code. -// Generated from `Microsoft.AspNet.OData, Version=7.7.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`. namespace Microsoft.AspNet.OData { public class ODataActionParameters : System.Collections.Generic.Dictionary @@ -11,9 +9,9 @@ public class Delta where TStructuralType : class { public Delta() => throw null; public TStructuralType GetInstance() => throw null; - public void Patch(TStructuralType original) => throw null; + public TStructuralType Patch(TStructuralType original) => throw null; public void Put(TStructuralType original) => throw null; - public void CopyChangedValues(TStructuralType original) => throw null; + public TStructuralType CopyChangedValues(TStructuralType original) => throw null; public void CopyUnchangedValues(TStructuralType original) => throw null; } } diff --git a/csharp/ql/test/resources/stubs/System.Web.Http.OData.cs b/csharp/ql/test/resources/stubs/System.Web.Http.OData.cs new file mode 100644 index 000000000000..e4b9775379c7 --- /dev/null +++ b/csharp/ql/test/resources/stubs/System.Web.Http.OData.cs @@ -0,0 +1,15 @@ +namespace System.Web.Http.OData +{ + public class ODataActionParameters : System.Collections.Generic.Dictionary + { + } + + public class Delta where TEntityType : class + { + public TEntityType GetEntity() => throw null; + public void Patch(TEntityType original) { } + public void Put(TEntityType original) { } + public void CopyChangedValues(TEntityType original) { } + public void CopyUnchangedValues(TEntityType original) { } + } +} From cf4c3d09e496d3486be40240f3465fffc1e35f07 Mon Sep 17 00:00:00 2001 From: hugo-syn Date: Wed, 26 Aug 2026 16:35:00 +0200 Subject: [PATCH 12/16] C#: address further OData review comments - Revert Patch/CopyChangedValues to void-only per michaelnebel (defer to maintainer over docs citation despite conflicting reflection evidence). - Move Microsoft.AspNet.OData.cs stub to a flat file, drop its wrapper project. - Drop the TryGetValue AdditionalTaintStep: it only added a taint step and didn't make cast targets recognized as ODataBoundType, so it doesn't fully address the underlying gap; left for a follow-up PR. - Convert OData.ql to a path-problem query for clearer test output. Co-Authored-By: Claude Sonnet 5 --- .../lib/ext/Microsoft.AspNet.OData.model.yml | 3 - .../semmle/code/csharp/frameworks/OData.qll | 17 ---- .../library-tests/frameworks/OData/OData.cs | 21 ---- .../frameworks/OData/OData.expected | 96 ++++++++++++++++--- .../library-tests/frameworks/OData/OData.ql | 11 ++- .../library-tests/frameworks/OData/options | 3 +- .../7.7.5 => }/Microsoft.AspNet.OData.cs | 4 +- .../7.7.5/Microsoft.AspNet.OData.csproj | 12 --- 8 files changed, 93 insertions(+), 74 deletions(-) rename csharp/ql/test/resources/stubs/{Microsoft.AspNet.OData/7.7.5 => }/Microsoft.AspNet.OData.cs (75%) delete mode 100644 csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj diff --git a/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml b/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml index e27c4ccfe529..86065b1ca2ea 100644 --- a/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml +++ b/csharp/ql/lib/ext/Microsoft.AspNet.OData.model.yml @@ -5,14 +5,11 @@ extensions: data: - ["Microsoft.AspNet.OData", "Delta", True, "GetInstance", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNet.OData", "Delta", True, "Patch", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - - ["Microsoft.AspNet.OData", "Delta", True, "Patch", "(TStructuralType)", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNet.OData", "Delta", True, "Put", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - ["Microsoft.AspNet.OData", "Delta", True, "CopyChangedValues", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - - ["Microsoft.AspNet.OData", "Delta", True, "CopyChangedValues", "(TStructuralType)", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNet.OData", "Delta", True, "CopyUnchangedValues", "(TStructuralType)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "GetInstance", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "Patch", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "Patch", "(T)", "", "Argument[this]", "ReturnValue", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "Put", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "CopyChangedValues", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] - ["Microsoft.AspNetCore.OData.Deltas", "Delta", True, "CopyUnchangedValues", "(T)", "", "Argument[this]", "Argument[0]", "taint", "manual"] diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index 9571268c6b30..16422fda1d8c 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -20,7 +20,6 @@ import csharp private import semmle.code.csharp.commons.Collections -private import semmle.code.csharp.dataflow.FlowSteps private import semmle.code.csharp.security.dataflow.flowsources.Remote /** The `ODataActionParameters` dictionary type, across OData library versions. */ @@ -50,22 +49,6 @@ class ODataActionParameterRead extends ElementAccess { ODataActionParameterRead() { isODataActionParametersValue(this.getQualifier()) } } -/** - * A call to `TryGetValue` on an `ODataActionParameters` dictionary copies the value - * of the looked-up entry into the `out` argument. - */ -private class ODataActionParametersTryGetValueTaintStep extends AdditionalTaintStep { - override predicate step(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodCall mc, AssignableDefinitions::OutRefDefinition def | - mc.getTarget().hasName("TryGetValue") and - isODataActionParametersValue(mc.getQualifier()) and - node1.asExpr() = mc.getQualifier() and - def.getTargetAccess() = mc.getArgumentForName("value") and - node2 = DataFlow::assignableDefinitionNode(def) - ) - } -} - /** Holds if `e` may (locally) hold the value of an `ODataActionParameters` entry. */ private predicate isODataParameterValue(Expr e) { DataFlow::localExprFlow(any(ODataActionParameterRead r), e) diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.cs b/csharp/ql/test/library-tests/frameworks/OData/OData.cs index bbce96d56fc9..a16a821da070 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.cs +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.cs @@ -73,15 +73,6 @@ void IsAsFromDictionary(ODataActionParameters parameters) } } - void TryGetValueFromDictionary(ODataActionParameters parameters) - { - if (parameters.TryGetValue("Entity", out var value)) - { - var entity = (BoundEntity)value; - Sink(entity.Name); - } - } - void UpcastThenIndex(ODataActionParameters parameters) { IDictionary dict = parameters; @@ -101,18 +92,6 @@ void DeltaGetInstance(Delta delta) Sink(w.Name); } - void DeltaPatchReturnValue(Delta delta, Widget original) - { - var updated = delta.Patch(original); - Sink(updated.Name); - } - - void DeltaCopyChangedValuesReturnValue(Delta delta, Widget original) - { - var updated = delta.CopyChangedValues(original); - Sink(updated.Name); - } - void LegacyDeltaPatch(System.Web.Http.OData.Delta delta, Widget original) { delta.Patch(original); diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.expected b/csharp/ql/test/library-tests/frameworks/OData/OData.expected index 290236df722e..a2e0614a51ec 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.expected +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.expected @@ -1,15 +1,81 @@ -| OData.cs:46:55:46:64 | parameters | OData.cs:49:18:49:23 | access to local variable entity | -| OData.cs:46:55:46:64 | parameters | OData.cs:50:18:50:28 | access to property Name | -| OData.cs:46:55:46:64 | parameters | OData.cs:51:18:51:31 | access to property Content | -| OData.cs:46:55:46:64 | parameters | OData.cs:52:18:52:38 | access to property Owner | -| OData.cs:46:55:46:64 | parameters | OData.cs:55:22:55:28 | access to property Owner | -| OData.cs:59:55:59:64 | parameters | OData.cs:65:26:65:35 | access to property Label | -| OData.cs:59:55:59:64 | parameters | OData.cs:72:22:72:34 | access to property Category | -| OData.cs:76:62:76:71 | parameters | OData.cs:81:22:81:32 | access to property Name | -| OData.cs:85:52:85:61 | parameters | OData.cs:89:18:89:28 | access to property Name | -| OData.cs:92:39:92:43 | delta | OData.cs:95:18:95:30 | access to property Name | -| OData.cs:98:45:98:49 | delta | OData.cs:101:18:101:23 | access to property Name | -| OData.cs:104:50:104:54 | delta | OData.cs:107:18:107:29 | access to property Name | -| OData.cs:110:62:110:66 | delta | OData.cs:113:18:113:29 | access to property Name | -| OData.cs:116:67:116:71 | delta | OData.cs:119:18:119:30 | access to property Name | -| OData.cs:122:71:122:75 | delta | OData.cs:125:18:125:23 | access to property Name | +edges +| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:48:26:48:58 | (...) ... : BoundEntity | provenance | | +| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:49:18:49:23 | access to local variable entity | provenance | | +| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:50:18:50:28 | access to property Name | provenance | | +| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:51:18:51:31 | access to property Content | provenance | | +| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:52:18:52:38 | access to property Owner | provenance | | +| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:55:22:55:28 | access to property Owner | provenance | | +| OData.cs:48:26:48:58 | (...) ... : BoundEntity | OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | provenance | | +| OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:65:26:65:35 | access to property Label | provenance | | +| OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:69:26:69:72 | ... as ... : IEnumerable | provenance | | +| OData.cs:69:17:69:22 | access to local variable items2 : IEnumerable | OData.cs:72:22:72:34 | access to property Category | provenance | | +| OData.cs:69:26:69:72 | ... as ... : IEnumerable | OData.cs:69:17:69:22 | access to local variable items2 : IEnumerable | provenance | | +| OData.cs:76:52:76:61 | parameters : ODataActionParameters | OData.cs:78:41:78:44 | access to local variable dict : ODataActionParameters | provenance | | +| OData.cs:78:41:78:44 | access to local variable dict : ODataActionParameters | OData.cs:79:26:79:52 | (...) ... : BoundEntity | provenance | | +| OData.cs:79:17:79:22 | access to local variable entity : BoundEntity | OData.cs:80:18:80:28 | access to property Name | provenance | | +| OData.cs:79:26:79:52 | (...) ... : BoundEntity | OData.cs:79:17:79:22 | access to local variable entity : BoundEntity | provenance | | +| OData.cs:83:39:83:43 | delta : Delta | OData.cs:85:13:85:17 | access to parameter delta : Delta | provenance | | +| OData.cs:85:13:85:17 | access to parameter delta : Delta | OData.cs:85:25:85:32 | [post] access to parameter original : Widget | provenance | MaD:143 | +| OData.cs:85:25:85:32 | [post] access to parameter original : Widget | OData.cs:86:18:86:30 | access to property Name | provenance | | +| OData.cs:89:45:89:49 | delta : Delta | OData.cs:91:21:91:25 | access to parameter delta : Delta | provenance | | +| OData.cs:91:17:91:17 | access to local variable w : Widget | OData.cs:92:18:92:23 | access to property Name | provenance | | +| OData.cs:91:21:91:25 | access to parameter delta : Delta | OData.cs:91:21:91:39 | call to method GetInstance : Widget | provenance | MaD:142 | +| OData.cs:91:21:91:39 | call to method GetInstance : Widget | OData.cs:91:17:91:17 | access to local variable w : Widget | provenance | | +| OData.cs:95:67:95:71 | delta : Delta | OData.cs:97:13:97:17 | access to parameter delta : Delta | provenance | | +| OData.cs:97:13:97:17 | access to parameter delta : Delta | OData.cs:97:25:97:32 | [post] access to parameter original : Widget | provenance | MaD:153 | +| OData.cs:97:25:97:32 | [post] access to parameter original : Widget | OData.cs:98:18:98:30 | access to property Name | provenance | | +| OData.cs:101:71:101:75 | delta : Delta | OData.cs:103:21:103:25 | access to parameter delta : Delta | provenance | | +| OData.cs:103:17:103:17 | access to local variable w : Widget | OData.cs:104:18:104:23 | access to property Name | provenance | | +| OData.cs:103:21:103:25 | access to parameter delta : Delta | OData.cs:103:21:103:37 | call to method GetEntity : Widget | provenance | MaD:152 | +| OData.cs:103:21:103:37 | call to method GetEntity : Widget | OData.cs:103:17:103:17 | access to local variable w : Widget | provenance | | +nodes +| OData.cs:46:55:46:64 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | +| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | semmle.label | access to local variable entity : BoundEntity | +| OData.cs:48:26:48:58 | (...) ... : BoundEntity | semmle.label | (...) ... : BoundEntity | +| OData.cs:49:18:49:23 | access to local variable entity | semmle.label | access to local variable entity | +| OData.cs:50:18:50:28 | access to property Name | semmle.label | access to property Name | +| OData.cs:51:18:51:31 | access to property Content | semmle.label | access to property Content | +| OData.cs:52:18:52:38 | access to property Owner | semmle.label | access to property Owner | +| OData.cs:55:22:55:28 | access to property Owner | semmle.label | access to property Owner | +| OData.cs:59:55:59:64 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | +| OData.cs:65:26:65:35 | access to property Label | semmle.label | access to property Label | +| OData.cs:69:17:69:22 | access to local variable items2 : IEnumerable | semmle.label | access to local variable items2 : IEnumerable | +| OData.cs:69:26:69:72 | ... as ... : IEnumerable | semmle.label | ... as ... : IEnumerable | +| OData.cs:72:22:72:34 | access to property Category | semmle.label | access to property Category | +| OData.cs:76:52:76:61 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | +| OData.cs:78:41:78:44 | access to local variable dict : ODataActionParameters | semmle.label | access to local variable dict : ODataActionParameters | +| OData.cs:79:17:79:22 | access to local variable entity : BoundEntity | semmle.label | access to local variable entity : BoundEntity | +| OData.cs:79:26:79:52 | (...) ... : BoundEntity | semmle.label | (...) ... : BoundEntity | +| OData.cs:80:18:80:28 | access to property Name | semmle.label | access to property Name | +| OData.cs:83:39:83:43 | delta : Delta | semmle.label | delta : Delta | +| OData.cs:85:13:85:17 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | +| OData.cs:85:25:85:32 | [post] access to parameter original : Widget | semmle.label | [post] access to parameter original : Widget | +| OData.cs:86:18:86:30 | access to property Name | semmle.label | access to property Name | +| OData.cs:89:45:89:49 | delta : Delta | semmle.label | delta : Delta | +| OData.cs:91:17:91:17 | access to local variable w : Widget | semmle.label | access to local variable w : Widget | +| OData.cs:91:21:91:25 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | +| OData.cs:91:21:91:39 | call to method GetInstance : Widget | semmle.label | call to method GetInstance : Widget | +| OData.cs:92:18:92:23 | access to property Name | semmle.label | access to property Name | +| OData.cs:95:67:95:71 | delta : Delta | semmle.label | delta : Delta | +| OData.cs:97:13:97:17 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | +| OData.cs:97:25:97:32 | [post] access to parameter original : Widget | semmle.label | [post] access to parameter original : Widget | +| OData.cs:98:18:98:30 | access to property Name | semmle.label | access to property Name | +| OData.cs:101:71:101:75 | delta : Delta | semmle.label | delta : Delta | +| OData.cs:103:17:103:17 | access to local variable w : Widget | semmle.label | access to local variable w : Widget | +| OData.cs:103:21:103:25 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | +| OData.cs:103:21:103:37 | call to method GetEntity : Widget | semmle.label | call to method GetEntity : Widget | +| OData.cs:104:18:104:23 | access to property Name | semmle.label | access to property Name | +subpaths +#select +| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:49:18:49:23 | access to local variable entity | $@ | OData.cs:49:18:49:23 | access to local variable entity | access to local variable entity | +| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:50:18:50:28 | access to property Name | $@ | OData.cs:50:18:50:28 | access to property Name | access to property Name | +| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:51:18:51:31 | access to property Content | $@ | OData.cs:51:18:51:31 | access to property Content | access to property Content | +| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:52:18:52:38 | access to property Owner | $@ | OData.cs:52:18:52:38 | access to property Owner | access to property Owner | +| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:55:22:55:28 | access to property Owner | $@ | OData.cs:55:22:55:28 | access to property Owner | access to property Owner | +| OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:65:26:65:35 | access to property Label | $@ | OData.cs:65:26:65:35 | access to property Label | access to property Label | +| OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:72:22:72:34 | access to property Category | $@ | OData.cs:72:22:72:34 | access to property Category | access to property Category | +| OData.cs:76:52:76:61 | parameters : ODataActionParameters | OData.cs:76:52:76:61 | parameters : ODataActionParameters | OData.cs:80:18:80:28 | access to property Name | $@ | OData.cs:80:18:80:28 | access to property Name | access to property Name | +| OData.cs:83:39:83:43 | delta : Delta | OData.cs:83:39:83:43 | delta : Delta | OData.cs:86:18:86:30 | access to property Name | $@ | OData.cs:86:18:86:30 | access to property Name | access to property Name | +| OData.cs:89:45:89:49 | delta : Delta | OData.cs:89:45:89:49 | delta : Delta | OData.cs:92:18:92:23 | access to property Name | $@ | OData.cs:92:18:92:23 | access to property Name | access to property Name | +| OData.cs:95:67:95:71 | delta : Delta | OData.cs:95:67:95:71 | delta : Delta | OData.cs:98:18:98:30 | access to property Name | $@ | OData.cs:98:18:98:30 | access to property Name | access to property Name | +| OData.cs:101:71:101:75 | delta : Delta | OData.cs:101:71:101:75 | delta : Delta | OData.cs:104:18:104:23 | access to property Name | $@ | OData.cs:104:18:104:23 | access to property Name | access to property Name | diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.ql b/csharp/ql/test/library-tests/frameworks/OData/OData.ql index ed70740699e3..b25ef30bc470 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.ql +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.ql @@ -1,4 +1,9 @@ +/** + * @kind path-problem + */ + import csharp +import Taint::PathGraph module TaintConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node n) { @@ -18,6 +23,6 @@ module TaintConfig implements DataFlow::ConfigSig { module Taint = TaintTracking::Global; -from DataFlow::Node source, DataFlow::Node sink -where Taint::flow(source, sink) -select source, sink +from Taint::PathNode source, Taint::PathNode sink +where Taint::flowPath(source, sink) +select source, source, sink, "$@", sink, sink.toString() diff --git a/csharp/ql/test/library-tests/frameworks/OData/options b/csharp/ql/test/library-tests/frameworks/OData/options index e9ba768d29a7..64d98a3ae94f 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/options +++ b/csharp/ql/test/library-tests/frameworks/OData/options @@ -1,3 +1,4 @@ semmle-extractor-options: /nostdlib /noconfig -semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj +semmle-extractor-options: ${testdir}/../../../resources/stubs/Microsoft.AspNet.OData.cs semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.Http.OData.cs diff --git a/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs b/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData.cs similarity index 75% rename from csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs rename to csharp/ql/test/resources/stubs/Microsoft.AspNet.OData.cs index da46281cf2d1..2ff03ab13994 100644 --- a/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.cs +++ b/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData.cs @@ -9,9 +9,9 @@ public class Delta where TStructuralType : class { public Delta() => throw null; public TStructuralType GetInstance() => throw null; - public TStructuralType Patch(TStructuralType original) => throw null; + public void Patch(TStructuralType original) => throw null; public void Put(TStructuralType original) => throw null; - public TStructuralType CopyChangedValues(TStructuralType original) => throw null; + public void CopyChangedValues(TStructuralType original) => throw null; public void CopyUnchangedValues(TStructuralType original) => throw null; } } diff --git a/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj b/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj deleted file mode 100644 index 2be6995cd169..000000000000 --- a/csharp/ql/test/resources/stubs/Microsoft.AspNet.OData/7.7.5/Microsoft.AspNet.OData.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - net10.0 - true - bin\ - false - - - - - - From 51a107eaac1676cceecb3990da365c9b4163b21d Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 27 Aug 2026 09:39:12 +0200 Subject: [PATCH 13/16] C#: Convert the OData test to use the Sink for the alert location and update test expected output. --- .../frameworks/OData/OData.expected | 24 +++++++++---------- .../library-tests/frameworks/OData/OData.ql | 3 ++- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.expected b/csharp/ql/test/library-tests/frameworks/OData/OData.expected index a2e0614a51ec..7cef6368a276 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.expected +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.expected @@ -67,15 +67,15 @@ nodes | OData.cs:104:18:104:23 | access to property Name | semmle.label | access to property Name | subpaths #select -| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:49:18:49:23 | access to local variable entity | $@ | OData.cs:49:18:49:23 | access to local variable entity | access to local variable entity | -| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:50:18:50:28 | access to property Name | $@ | OData.cs:50:18:50:28 | access to property Name | access to property Name | -| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:51:18:51:31 | access to property Content | $@ | OData.cs:51:18:51:31 | access to property Content | access to property Content | -| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:52:18:52:38 | access to property Owner | $@ | OData.cs:52:18:52:38 | access to property Owner | access to property Owner | -| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:55:22:55:28 | access to property Owner | $@ | OData.cs:55:22:55:28 | access to property Owner | access to property Owner | -| OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:65:26:65:35 | access to property Label | $@ | OData.cs:65:26:65:35 | access to property Label | access to property Label | -| OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:72:22:72:34 | access to property Category | $@ | OData.cs:72:22:72:34 | access to property Category | access to property Category | -| OData.cs:76:52:76:61 | parameters : ODataActionParameters | OData.cs:76:52:76:61 | parameters : ODataActionParameters | OData.cs:80:18:80:28 | access to property Name | $@ | OData.cs:80:18:80:28 | access to property Name | access to property Name | -| OData.cs:83:39:83:43 | delta : Delta | OData.cs:83:39:83:43 | delta : Delta | OData.cs:86:18:86:30 | access to property Name | $@ | OData.cs:86:18:86:30 | access to property Name | access to property Name | -| OData.cs:89:45:89:49 | delta : Delta | OData.cs:89:45:89:49 | delta : Delta | OData.cs:92:18:92:23 | access to property Name | $@ | OData.cs:92:18:92:23 | access to property Name | access to property Name | -| OData.cs:95:67:95:71 | delta : Delta | OData.cs:95:67:95:71 | delta : Delta | OData.cs:98:18:98:30 | access to property Name | $@ | OData.cs:98:18:98:30 | access to property Name | access to property Name | -| OData.cs:101:71:101:75 | delta : Delta | OData.cs:101:71:101:75 | delta : Delta | OData.cs:104:18:104:23 | access to property Name | $@ | OData.cs:104:18:104:23 | access to property Name | access to property Name | +| OData.cs:49:18:49:23 | access to local variable entity | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:49:18:49:23 | access to local variable entity | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | +| OData.cs:50:18:50:28 | access to property Name | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:50:18:50:28 | access to property Name | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | +| OData.cs:51:18:51:31 | access to property Content | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:51:18:51:31 | access to property Content | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | +| OData.cs:52:18:52:38 | access to property Owner | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:52:18:52:38 | access to property Owner | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | +| OData.cs:55:22:55:28 | access to property Owner | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:55:22:55:28 | access to property Owner | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | +| OData.cs:65:26:65:35 | access to property Label | OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:65:26:65:35 | access to property Label | This path depends on an $@. | OData.cs:59:55:59:64 | parameters | ODataParameters value | +| OData.cs:72:22:72:34 | access to property Category | OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:72:22:72:34 | access to property Category | This path depends on an $@. | OData.cs:59:55:59:64 | parameters | ODataParameters value | +| OData.cs:80:18:80:28 | access to property Name | OData.cs:76:52:76:61 | parameters : ODataActionParameters | OData.cs:80:18:80:28 | access to property Name | This path depends on an $@. | OData.cs:76:52:76:61 | parameters | ODataParameters value | +| OData.cs:86:18:86:30 | access to property Name | OData.cs:83:39:83:43 | delta : Delta | OData.cs:86:18:86:30 | access to property Name | This path depends on an $@. | OData.cs:83:39:83:43 | delta | ODataParameters value | +| OData.cs:92:18:92:23 | access to property Name | OData.cs:89:45:89:49 | delta : Delta | OData.cs:92:18:92:23 | access to property Name | This path depends on an $@. | OData.cs:89:45:89:49 | delta | ODataParameters value | +| OData.cs:98:18:98:30 | access to property Name | OData.cs:95:67:95:71 | delta : Delta | OData.cs:98:18:98:30 | access to property Name | This path depends on an $@. | OData.cs:95:67:95:71 | delta | ODataParameters value | +| OData.cs:104:18:104:23 | access to property Name | OData.cs:101:71:101:75 | delta : Delta | OData.cs:104:18:104:23 | access to property Name | This path depends on an $@. | OData.cs:101:71:101:75 | delta | ODataParameters value | diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.ql b/csharp/ql/test/library-tests/frameworks/OData/OData.ql index b25ef30bc470..f0636d555b51 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.ql +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.ql @@ -25,4 +25,5 @@ module Taint = TaintTracking::Global; from Taint::PathNode source, Taint::PathNode sink where Taint::flowPath(source, sink) -select source, source, sink, "$@", sink, sink.toString() +select sink.getNode(), source, sink, "This path depends on an $@.", source.getNode(), + "ODataParameters value" From 538d51f3f95aba0332c84b9869d50560a44ff416 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 27 Aug 2026 10:05:34 +0200 Subject: [PATCH 14/16] C#: Convert the test to use inline expectations and update expected test output. --- .../library-tests/frameworks/OData/OData.cs | 24 +++++++++---------- .../frameworks/OData/OData.expected | 14 +++++++---- .../library-tests/frameworks/OData/OData.ql | 9 +++---- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.cs b/csharp/ql/test/library-tests/frameworks/OData/OData.cs index a16a821da070..91d0440c3288 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.cs +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.cs @@ -46,13 +46,13 @@ void Sink(object o) { } void CastFromDictionary(ODataActionParameters parameters) { var entity = (BoundEntity)parameters["Entity"]; - Sink(entity); - Sink(entity.Name); - Sink(entity.Content); - Sink(entity.Metadata.Owner); + Sink(entity); // $ hasTaintFlow=line:46 + Sink(entity.Name); // $ hasTaintFlow=line:46 + Sink(entity.Content); // $ hasTaintFlow=line:46 + Sink(entity.Metadata.Owner); // $ hasTaintFlow=line:46 foreach (var m in entity.Revisions) { - Sink(m.Owner); + Sink(m.Owner); // $ hasTaintFlow=line:46 } } @@ -62,14 +62,14 @@ void IsAsFromDictionary(ODataActionParameters parameters) { foreach (var item in items1) { - Sink(item.Label); + Sink(item.Label); // $ hasTaintFlow=line:59 } } var items2 = parameters["Items"] as IEnumerable; foreach (var item in items2) { - Sink(item.Category); + Sink(item.Category); // $ hasTaintFlow=line:59 } } @@ -77,31 +77,31 @@ void UpcastThenIndex(ODataActionParameters parameters) { IDictionary dict = parameters; var entity = (BoundEntity)dict["Entity"]; - Sink(entity.Name); + Sink(entity.Name); // $ hasTaintFlow=line:76 } void DeltaPatch(Delta delta, Widget original) { delta.Patch(original); - Sink(original.Name); + Sink(original.Name); // $ hasTaintFlow=line:83 } void DeltaGetInstance(Delta delta) { var w = delta.GetInstance(); - Sink(w.Name); + Sink(w.Name); // $ hasTaintFlow=line:89 } void LegacyDeltaPatch(System.Web.Http.OData.Delta delta, Widget original) { delta.Patch(original); - Sink(original.Name); + Sink(original.Name); // $ hasTaintFlow=line:95 } void LegacyDeltaGetEntity(System.Web.Http.OData.Delta delta) { var w = delta.GetEntity(); - Sink(w.Name); + Sink(w.Name); // $ hasTaintFlow=line:101 } void Untainted() diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.expected b/csharp/ql/test/library-tests/frameworks/OData/OData.expected index 7cef6368a276..86f12b08f3c2 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.expected +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.expected @@ -1,3 +1,8 @@ +models +| 1 | Summary: Microsoft.AspNet.OData; Delta; true; GetInstance; (); ; Argument[this]; ReturnValue; taint; manual | +| 2 | Summary: Microsoft.AspNet.OData; Delta; true; Patch; (TStructuralType); ; Argument[this]; Argument[0]; taint; manual | +| 3 | Summary: System.Web.Http.OData; Delta; true; GetEntity; (); ; Argument[this]; ReturnValue; taint; manual | +| 4 | Summary: System.Web.Http.OData; Delta; true; Patch; (TEntityType); ; Argument[this]; Argument[0]; taint; manual | edges | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:48:26:48:58 | (...) ... : BoundEntity | provenance | | | OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:49:18:49:23 | access to local variable entity | provenance | | @@ -15,18 +20,18 @@ edges | OData.cs:79:17:79:22 | access to local variable entity : BoundEntity | OData.cs:80:18:80:28 | access to property Name | provenance | | | OData.cs:79:26:79:52 | (...) ... : BoundEntity | OData.cs:79:17:79:22 | access to local variable entity : BoundEntity | provenance | | | OData.cs:83:39:83:43 | delta : Delta | OData.cs:85:13:85:17 | access to parameter delta : Delta | provenance | | -| OData.cs:85:13:85:17 | access to parameter delta : Delta | OData.cs:85:25:85:32 | [post] access to parameter original : Widget | provenance | MaD:143 | +| OData.cs:85:13:85:17 | access to parameter delta : Delta | OData.cs:85:25:85:32 | [post] access to parameter original : Widget | provenance | MaD:2 | | OData.cs:85:25:85:32 | [post] access to parameter original : Widget | OData.cs:86:18:86:30 | access to property Name | provenance | | | OData.cs:89:45:89:49 | delta : Delta | OData.cs:91:21:91:25 | access to parameter delta : Delta | provenance | | | OData.cs:91:17:91:17 | access to local variable w : Widget | OData.cs:92:18:92:23 | access to property Name | provenance | | -| OData.cs:91:21:91:25 | access to parameter delta : Delta | OData.cs:91:21:91:39 | call to method GetInstance : Widget | provenance | MaD:142 | +| OData.cs:91:21:91:25 | access to parameter delta : Delta | OData.cs:91:21:91:39 | call to method GetInstance : Widget | provenance | MaD:1 | | OData.cs:91:21:91:39 | call to method GetInstance : Widget | OData.cs:91:17:91:17 | access to local variable w : Widget | provenance | | | OData.cs:95:67:95:71 | delta : Delta | OData.cs:97:13:97:17 | access to parameter delta : Delta | provenance | | -| OData.cs:97:13:97:17 | access to parameter delta : Delta | OData.cs:97:25:97:32 | [post] access to parameter original : Widget | provenance | MaD:153 | +| OData.cs:97:13:97:17 | access to parameter delta : Delta | OData.cs:97:25:97:32 | [post] access to parameter original : Widget | provenance | MaD:4 | | OData.cs:97:25:97:32 | [post] access to parameter original : Widget | OData.cs:98:18:98:30 | access to property Name | provenance | | | OData.cs:101:71:101:75 | delta : Delta | OData.cs:103:21:103:25 | access to parameter delta : Delta | provenance | | | OData.cs:103:17:103:17 | access to local variable w : Widget | OData.cs:104:18:104:23 | access to property Name | provenance | | -| OData.cs:103:21:103:25 | access to parameter delta : Delta | OData.cs:103:21:103:37 | call to method GetEntity : Widget | provenance | MaD:152 | +| OData.cs:103:21:103:25 | access to parameter delta : Delta | OData.cs:103:21:103:37 | call to method GetEntity : Widget | provenance | MaD:3 | | OData.cs:103:21:103:37 | call to method GetEntity : Widget | OData.cs:103:17:103:17 | access to local variable w : Widget | provenance | | nodes | OData.cs:46:55:46:64 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | @@ -66,6 +71,7 @@ nodes | OData.cs:103:21:103:37 | call to method GetEntity : Widget | semmle.label | call to method GetEntity : Widget | | OData.cs:104:18:104:23 | access to property Name | semmle.label | access to property Name | subpaths +testFailures #select | OData.cs:49:18:49:23 | access to local variable entity | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:49:18:49:23 | access to local variable entity | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | | OData.cs:50:18:50:28 | access to property Name | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:50:18:50:28 | access to property Name | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.ql b/csharp/ql/test/library-tests/frameworks/OData/OData.ql index f0636d555b51..df79cc58cdb4 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.ql +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.ql @@ -3,7 +3,8 @@ */ import csharp -import Taint::PathGraph +import utils.test.InlineFlowTest +import PathGraph module TaintConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node n) { @@ -21,9 +22,9 @@ module TaintConfig implements DataFlow::ConfigSig { } } -module Taint = TaintTracking::Global; +import TaintFlowTest -from Taint::PathNode source, Taint::PathNode sink -where Taint::flowPath(source, sink) +from PathNode source, PathNode sink +where flowPath(source, sink) select sink.getNode(), source, sink, "This path depends on an $@.", source.getNode(), "ODataParameters value" From 5b2568d4ea3c10aac18258378fb46658ecae3c16 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 27 Aug 2026 10:23:04 +0200 Subject: [PATCH 15/16] C#: Use unique types pr test case (otherwise they will interfere with each other in relation to identifying which members will be tainted). --- .../library-tests/frameworks/OData/OData.cs | 37 +++-- .../frameworks/OData/OData.expected | 156 +++++++++--------- 2 files changed, 100 insertions(+), 93 deletions(-) diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.cs b/csharp/ql/test/library-tests/frameworks/OData/OData.cs index 91d0440c3288..8e63a2dec7d4 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.cs +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.cs @@ -8,7 +8,7 @@ public class EntityMetadata public string Owner { get; set; } } - public class BoundEntity + public class BoundEntity1 { public string Name { get; set; } @@ -19,6 +19,11 @@ public class BoundEntity public List Revisions { get; set; } } + public class BoundEntity2 + { + public string Name { get; set; } + } + public class RelatedItem { public string Label { get; set; } @@ -45,14 +50,14 @@ void Sink(object o) { } void CastFromDictionary(ODataActionParameters parameters) { - var entity = (BoundEntity)parameters["Entity"]; - Sink(entity); // $ hasTaintFlow=line:46 - Sink(entity.Name); // $ hasTaintFlow=line:46 - Sink(entity.Content); // $ hasTaintFlow=line:46 - Sink(entity.Metadata.Owner); // $ hasTaintFlow=line:46 + var entity = (BoundEntity1)parameters["Entity"]; + Sink(entity); // $ hasTaintFlow=line:51 + Sink(entity.Name); // $ hasTaintFlow=line:51 + Sink(entity.Content); // $ hasTaintFlow=line:51 + Sink(entity.Metadata.Owner); // $ hasTaintFlow=line:51 foreach (var m in entity.Revisions) { - Sink(m.Owner); // $ hasTaintFlow=line:46 + Sink(m.Owner); // $ hasTaintFlow=line:51 } } @@ -62,46 +67,46 @@ void IsAsFromDictionary(ODataActionParameters parameters) { foreach (var item in items1) { - Sink(item.Label); // $ hasTaintFlow=line:59 + Sink(item.Label); // $ hasTaintFlow=line:64 } } var items2 = parameters["Items"] as IEnumerable; foreach (var item in items2) { - Sink(item.Category); // $ hasTaintFlow=line:59 + Sink(item.Category); // $ hasTaintFlow=line:64 } } void UpcastThenIndex(ODataActionParameters parameters) { - IDictionary dict = parameters; - var entity = (BoundEntity)dict["Entity"]; - Sink(entity.Name); // $ hasTaintFlow=line:76 + var dict = (IDictionary)parameters; + var entity = (BoundEntity2)dict["Entity"]; + Sink(entity.Name); // $ hasTaintFlow=line:81 } void DeltaPatch(Delta delta, Widget original) { delta.Patch(original); - Sink(original.Name); // $ hasTaintFlow=line:83 + Sink(original.Name); // $ hasTaintFlow=line:88 } void DeltaGetInstance(Delta delta) { var w = delta.GetInstance(); - Sink(w.Name); // $ hasTaintFlow=line:89 + Sink(w.Name); // $ hasTaintFlow=line:94 } void LegacyDeltaPatch(System.Web.Http.OData.Delta delta, Widget original) { delta.Patch(original); - Sink(original.Name); // $ hasTaintFlow=line:95 + Sink(original.Name); // $ hasTaintFlow=line:100 } void LegacyDeltaGetEntity(System.Web.Http.OData.Delta delta) { var w = delta.GetEntity(); - Sink(w.Name); // $ hasTaintFlow=line:101 + Sink(w.Name); // $ hasTaintFlow=line:106 } void Untainted() diff --git a/csharp/ql/test/library-tests/frameworks/OData/OData.expected b/csharp/ql/test/library-tests/frameworks/OData/OData.expected index 86f12b08f3c2..1c69969e8756 100644 --- a/csharp/ql/test/library-tests/frameworks/OData/OData.expected +++ b/csharp/ql/test/library-tests/frameworks/OData/OData.expected @@ -4,84 +4,86 @@ models | 3 | Summary: System.Web.Http.OData; Delta; true; GetEntity; (); ; Argument[this]; ReturnValue; taint; manual | | 4 | Summary: System.Web.Http.OData; Delta; true; Patch; (TEntityType); ; Argument[this]; Argument[0]; taint; manual | edges -| OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:48:26:48:58 | (...) ... : BoundEntity | provenance | | -| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:49:18:49:23 | access to local variable entity | provenance | | -| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:50:18:50:28 | access to property Name | provenance | | -| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:51:18:51:31 | access to property Content | provenance | | -| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:52:18:52:38 | access to property Owner | provenance | | -| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | OData.cs:55:22:55:28 | access to property Owner | provenance | | -| OData.cs:48:26:48:58 | (...) ... : BoundEntity | OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | provenance | | -| OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:65:26:65:35 | access to property Label | provenance | | -| OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:69:26:69:72 | ... as ... : IEnumerable | provenance | | -| OData.cs:69:17:69:22 | access to local variable items2 : IEnumerable | OData.cs:72:22:72:34 | access to property Category | provenance | | -| OData.cs:69:26:69:72 | ... as ... : IEnumerable | OData.cs:69:17:69:22 | access to local variable items2 : IEnumerable | provenance | | -| OData.cs:76:52:76:61 | parameters : ODataActionParameters | OData.cs:78:41:78:44 | access to local variable dict : ODataActionParameters | provenance | | -| OData.cs:78:41:78:44 | access to local variable dict : ODataActionParameters | OData.cs:79:26:79:52 | (...) ... : BoundEntity | provenance | | -| OData.cs:79:17:79:22 | access to local variable entity : BoundEntity | OData.cs:80:18:80:28 | access to property Name | provenance | | -| OData.cs:79:26:79:52 | (...) ... : BoundEntity | OData.cs:79:17:79:22 | access to local variable entity : BoundEntity | provenance | | -| OData.cs:83:39:83:43 | delta : Delta | OData.cs:85:13:85:17 | access to parameter delta : Delta | provenance | | -| OData.cs:85:13:85:17 | access to parameter delta : Delta | OData.cs:85:25:85:32 | [post] access to parameter original : Widget | provenance | MaD:2 | -| OData.cs:85:25:85:32 | [post] access to parameter original : Widget | OData.cs:86:18:86:30 | access to property Name | provenance | | -| OData.cs:89:45:89:49 | delta : Delta | OData.cs:91:21:91:25 | access to parameter delta : Delta | provenance | | -| OData.cs:91:17:91:17 | access to local variable w : Widget | OData.cs:92:18:92:23 | access to property Name | provenance | | -| OData.cs:91:21:91:25 | access to parameter delta : Delta | OData.cs:91:21:91:39 | call to method GetInstance : Widget | provenance | MaD:1 | -| OData.cs:91:21:91:39 | call to method GetInstance : Widget | OData.cs:91:17:91:17 | access to local variable w : Widget | provenance | | -| OData.cs:95:67:95:71 | delta : Delta | OData.cs:97:13:97:17 | access to parameter delta : Delta | provenance | | -| OData.cs:97:13:97:17 | access to parameter delta : Delta | OData.cs:97:25:97:32 | [post] access to parameter original : Widget | provenance | MaD:4 | -| OData.cs:97:25:97:32 | [post] access to parameter original : Widget | OData.cs:98:18:98:30 | access to property Name | provenance | | -| OData.cs:101:71:101:75 | delta : Delta | OData.cs:103:21:103:25 | access to parameter delta : Delta | provenance | | -| OData.cs:103:17:103:17 | access to local variable w : Widget | OData.cs:104:18:104:23 | access to property Name | provenance | | -| OData.cs:103:21:103:25 | access to parameter delta : Delta | OData.cs:103:21:103:37 | call to method GetEntity : Widget | provenance | MaD:3 | -| OData.cs:103:21:103:37 | call to method GetEntity : Widget | OData.cs:103:17:103:17 | access to local variable w : Widget | provenance | | +| OData.cs:51:55:51:64 | parameters : ODataActionParameters | OData.cs:53:26:53:59 | (...) ... : BoundEntity1 | provenance | | +| OData.cs:53:17:53:22 | access to local variable entity : BoundEntity1 | OData.cs:54:18:54:23 | access to local variable entity | provenance | | +| OData.cs:53:17:53:22 | access to local variable entity : BoundEntity1 | OData.cs:55:18:55:28 | access to property Name | provenance | | +| OData.cs:53:17:53:22 | access to local variable entity : BoundEntity1 | OData.cs:56:18:56:31 | access to property Content | provenance | | +| OData.cs:53:17:53:22 | access to local variable entity : BoundEntity1 | OData.cs:57:18:57:38 | access to property Owner | provenance | | +| OData.cs:53:17:53:22 | access to local variable entity : BoundEntity1 | OData.cs:60:22:60:28 | access to property Owner | provenance | | +| OData.cs:53:26:53:59 | (...) ... : BoundEntity1 | OData.cs:53:17:53:22 | access to local variable entity : BoundEntity1 | provenance | | +| OData.cs:64:55:64:64 | parameters : ODataActionParameters | OData.cs:70:26:70:35 | access to property Label | provenance | | +| OData.cs:64:55:64:64 | parameters : ODataActionParameters | OData.cs:74:26:74:72 | ... as ... : IEnumerable | provenance | | +| OData.cs:74:17:74:22 | access to local variable items2 : IEnumerable | OData.cs:77:22:77:34 | access to property Category | provenance | | +| OData.cs:74:26:74:72 | ... as ... : IEnumerable | OData.cs:74:17:74:22 | access to local variable items2 : IEnumerable | provenance | | +| OData.cs:81:52:81:61 | parameters : ODataActionParameters | OData.cs:83:24:83:62 | (...) ... : ODataActionParameters | provenance | | +| OData.cs:83:17:83:20 | access to local variable dict : ODataActionParameters | OData.cs:84:26:84:53 | (...) ... : BoundEntity2 | provenance | | +| OData.cs:83:24:83:62 | (...) ... : ODataActionParameters | OData.cs:83:17:83:20 | access to local variable dict : ODataActionParameters | provenance | | +| OData.cs:84:17:84:22 | access to local variable entity : BoundEntity2 | OData.cs:85:18:85:28 | access to property Name | provenance | | +| OData.cs:84:26:84:53 | (...) ... : BoundEntity2 | OData.cs:84:17:84:22 | access to local variable entity : BoundEntity2 | provenance | | +| OData.cs:88:39:88:43 | delta : Delta | OData.cs:90:13:90:17 | access to parameter delta : Delta | provenance | | +| OData.cs:90:13:90:17 | access to parameter delta : Delta | OData.cs:90:25:90:32 | [post] access to parameter original : Widget | provenance | MaD:2 | +| OData.cs:90:25:90:32 | [post] access to parameter original : Widget | OData.cs:91:18:91:30 | access to property Name | provenance | | +| OData.cs:94:45:94:49 | delta : Delta | OData.cs:96:21:96:25 | access to parameter delta : Delta | provenance | | +| OData.cs:96:17:96:17 | access to local variable w : Widget | OData.cs:97:18:97:23 | access to property Name | provenance | | +| OData.cs:96:21:96:25 | access to parameter delta : Delta | OData.cs:96:21:96:39 | call to method GetInstance : Widget | provenance | MaD:1 | +| OData.cs:96:21:96:39 | call to method GetInstance : Widget | OData.cs:96:17:96:17 | access to local variable w : Widget | provenance | | +| OData.cs:100:67:100:71 | delta : Delta | OData.cs:102:13:102:17 | access to parameter delta : Delta | provenance | | +| OData.cs:102:13:102:17 | access to parameter delta : Delta | OData.cs:102:25:102:32 | [post] access to parameter original : Widget | provenance | MaD:4 | +| OData.cs:102:25:102:32 | [post] access to parameter original : Widget | OData.cs:103:18:103:30 | access to property Name | provenance | | +| OData.cs:106:71:106:75 | delta : Delta | OData.cs:108:21:108:25 | access to parameter delta : Delta | provenance | | +| OData.cs:108:17:108:17 | access to local variable w : Widget | OData.cs:109:18:109:23 | access to property Name | provenance | | +| OData.cs:108:21:108:25 | access to parameter delta : Delta | OData.cs:108:21:108:37 | call to method GetEntity : Widget | provenance | MaD:3 | +| OData.cs:108:21:108:37 | call to method GetEntity : Widget | OData.cs:108:17:108:17 | access to local variable w : Widget | provenance | | nodes -| OData.cs:46:55:46:64 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | -| OData.cs:48:17:48:22 | access to local variable entity : BoundEntity | semmle.label | access to local variable entity : BoundEntity | -| OData.cs:48:26:48:58 | (...) ... : BoundEntity | semmle.label | (...) ... : BoundEntity | -| OData.cs:49:18:49:23 | access to local variable entity | semmle.label | access to local variable entity | -| OData.cs:50:18:50:28 | access to property Name | semmle.label | access to property Name | -| OData.cs:51:18:51:31 | access to property Content | semmle.label | access to property Content | -| OData.cs:52:18:52:38 | access to property Owner | semmle.label | access to property Owner | -| OData.cs:55:22:55:28 | access to property Owner | semmle.label | access to property Owner | -| OData.cs:59:55:59:64 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | -| OData.cs:65:26:65:35 | access to property Label | semmle.label | access to property Label | -| OData.cs:69:17:69:22 | access to local variable items2 : IEnumerable | semmle.label | access to local variable items2 : IEnumerable | -| OData.cs:69:26:69:72 | ... as ... : IEnumerable | semmle.label | ... as ... : IEnumerable | -| OData.cs:72:22:72:34 | access to property Category | semmle.label | access to property Category | -| OData.cs:76:52:76:61 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | -| OData.cs:78:41:78:44 | access to local variable dict : ODataActionParameters | semmle.label | access to local variable dict : ODataActionParameters | -| OData.cs:79:17:79:22 | access to local variable entity : BoundEntity | semmle.label | access to local variable entity : BoundEntity | -| OData.cs:79:26:79:52 | (...) ... : BoundEntity | semmle.label | (...) ... : BoundEntity | -| OData.cs:80:18:80:28 | access to property Name | semmle.label | access to property Name | -| OData.cs:83:39:83:43 | delta : Delta | semmle.label | delta : Delta | -| OData.cs:85:13:85:17 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | -| OData.cs:85:25:85:32 | [post] access to parameter original : Widget | semmle.label | [post] access to parameter original : Widget | -| OData.cs:86:18:86:30 | access to property Name | semmle.label | access to property Name | -| OData.cs:89:45:89:49 | delta : Delta | semmle.label | delta : Delta | -| OData.cs:91:17:91:17 | access to local variable w : Widget | semmle.label | access to local variable w : Widget | -| OData.cs:91:21:91:25 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | -| OData.cs:91:21:91:39 | call to method GetInstance : Widget | semmle.label | call to method GetInstance : Widget | -| OData.cs:92:18:92:23 | access to property Name | semmle.label | access to property Name | -| OData.cs:95:67:95:71 | delta : Delta | semmle.label | delta : Delta | -| OData.cs:97:13:97:17 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | -| OData.cs:97:25:97:32 | [post] access to parameter original : Widget | semmle.label | [post] access to parameter original : Widget | -| OData.cs:98:18:98:30 | access to property Name | semmle.label | access to property Name | -| OData.cs:101:71:101:75 | delta : Delta | semmle.label | delta : Delta | -| OData.cs:103:17:103:17 | access to local variable w : Widget | semmle.label | access to local variable w : Widget | -| OData.cs:103:21:103:25 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | -| OData.cs:103:21:103:37 | call to method GetEntity : Widget | semmle.label | call to method GetEntity : Widget | -| OData.cs:104:18:104:23 | access to property Name | semmle.label | access to property Name | +| OData.cs:51:55:51:64 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | +| OData.cs:53:17:53:22 | access to local variable entity : BoundEntity1 | semmle.label | access to local variable entity : BoundEntity1 | +| OData.cs:53:26:53:59 | (...) ... : BoundEntity1 | semmle.label | (...) ... : BoundEntity1 | +| OData.cs:54:18:54:23 | access to local variable entity | semmle.label | access to local variable entity | +| OData.cs:55:18:55:28 | access to property Name | semmle.label | access to property Name | +| OData.cs:56:18:56:31 | access to property Content | semmle.label | access to property Content | +| OData.cs:57:18:57:38 | access to property Owner | semmle.label | access to property Owner | +| OData.cs:60:22:60:28 | access to property Owner | semmle.label | access to property Owner | +| OData.cs:64:55:64:64 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | +| OData.cs:70:26:70:35 | access to property Label | semmle.label | access to property Label | +| OData.cs:74:17:74:22 | access to local variable items2 : IEnumerable | semmle.label | access to local variable items2 : IEnumerable | +| OData.cs:74:26:74:72 | ... as ... : IEnumerable | semmle.label | ... as ... : IEnumerable | +| OData.cs:77:22:77:34 | access to property Category | semmle.label | access to property Category | +| OData.cs:81:52:81:61 | parameters : ODataActionParameters | semmle.label | parameters : ODataActionParameters | +| OData.cs:83:17:83:20 | access to local variable dict : ODataActionParameters | semmle.label | access to local variable dict : ODataActionParameters | +| OData.cs:83:24:83:62 | (...) ... : ODataActionParameters | semmle.label | (...) ... : ODataActionParameters | +| OData.cs:84:17:84:22 | access to local variable entity : BoundEntity2 | semmle.label | access to local variable entity : BoundEntity2 | +| OData.cs:84:26:84:53 | (...) ... : BoundEntity2 | semmle.label | (...) ... : BoundEntity2 | +| OData.cs:85:18:85:28 | access to property Name | semmle.label | access to property Name | +| OData.cs:88:39:88:43 | delta : Delta | semmle.label | delta : Delta | +| OData.cs:90:13:90:17 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | +| OData.cs:90:25:90:32 | [post] access to parameter original : Widget | semmle.label | [post] access to parameter original : Widget | +| OData.cs:91:18:91:30 | access to property Name | semmle.label | access to property Name | +| OData.cs:94:45:94:49 | delta : Delta | semmle.label | delta : Delta | +| OData.cs:96:17:96:17 | access to local variable w : Widget | semmle.label | access to local variable w : Widget | +| OData.cs:96:21:96:25 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | +| OData.cs:96:21:96:39 | call to method GetInstance : Widget | semmle.label | call to method GetInstance : Widget | +| OData.cs:97:18:97:23 | access to property Name | semmle.label | access to property Name | +| OData.cs:100:67:100:71 | delta : Delta | semmle.label | delta : Delta | +| OData.cs:102:13:102:17 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | +| OData.cs:102:25:102:32 | [post] access to parameter original : Widget | semmle.label | [post] access to parameter original : Widget | +| OData.cs:103:18:103:30 | access to property Name | semmle.label | access to property Name | +| OData.cs:106:71:106:75 | delta : Delta | semmle.label | delta : Delta | +| OData.cs:108:17:108:17 | access to local variable w : Widget | semmle.label | access to local variable w : Widget | +| OData.cs:108:21:108:25 | access to parameter delta : Delta | semmle.label | access to parameter delta : Delta | +| OData.cs:108:21:108:37 | call to method GetEntity : Widget | semmle.label | call to method GetEntity : Widget | +| OData.cs:109:18:109:23 | access to property Name | semmle.label | access to property Name | subpaths testFailures #select -| OData.cs:49:18:49:23 | access to local variable entity | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:49:18:49:23 | access to local variable entity | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | -| OData.cs:50:18:50:28 | access to property Name | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:50:18:50:28 | access to property Name | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | -| OData.cs:51:18:51:31 | access to property Content | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:51:18:51:31 | access to property Content | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | -| OData.cs:52:18:52:38 | access to property Owner | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:52:18:52:38 | access to property Owner | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | -| OData.cs:55:22:55:28 | access to property Owner | OData.cs:46:55:46:64 | parameters : ODataActionParameters | OData.cs:55:22:55:28 | access to property Owner | This path depends on an $@. | OData.cs:46:55:46:64 | parameters | ODataParameters value | -| OData.cs:65:26:65:35 | access to property Label | OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:65:26:65:35 | access to property Label | This path depends on an $@. | OData.cs:59:55:59:64 | parameters | ODataParameters value | -| OData.cs:72:22:72:34 | access to property Category | OData.cs:59:55:59:64 | parameters : ODataActionParameters | OData.cs:72:22:72:34 | access to property Category | This path depends on an $@. | OData.cs:59:55:59:64 | parameters | ODataParameters value | -| OData.cs:80:18:80:28 | access to property Name | OData.cs:76:52:76:61 | parameters : ODataActionParameters | OData.cs:80:18:80:28 | access to property Name | This path depends on an $@. | OData.cs:76:52:76:61 | parameters | ODataParameters value | -| OData.cs:86:18:86:30 | access to property Name | OData.cs:83:39:83:43 | delta : Delta | OData.cs:86:18:86:30 | access to property Name | This path depends on an $@. | OData.cs:83:39:83:43 | delta | ODataParameters value | -| OData.cs:92:18:92:23 | access to property Name | OData.cs:89:45:89:49 | delta : Delta | OData.cs:92:18:92:23 | access to property Name | This path depends on an $@. | OData.cs:89:45:89:49 | delta | ODataParameters value | -| OData.cs:98:18:98:30 | access to property Name | OData.cs:95:67:95:71 | delta : Delta | OData.cs:98:18:98:30 | access to property Name | This path depends on an $@. | OData.cs:95:67:95:71 | delta | ODataParameters value | -| OData.cs:104:18:104:23 | access to property Name | OData.cs:101:71:101:75 | delta : Delta | OData.cs:104:18:104:23 | access to property Name | This path depends on an $@. | OData.cs:101:71:101:75 | delta | ODataParameters value | +| OData.cs:54:18:54:23 | access to local variable entity | OData.cs:51:55:51:64 | parameters : ODataActionParameters | OData.cs:54:18:54:23 | access to local variable entity | This path depends on an $@. | OData.cs:51:55:51:64 | parameters | ODataParameters value | +| OData.cs:55:18:55:28 | access to property Name | OData.cs:51:55:51:64 | parameters : ODataActionParameters | OData.cs:55:18:55:28 | access to property Name | This path depends on an $@. | OData.cs:51:55:51:64 | parameters | ODataParameters value | +| OData.cs:56:18:56:31 | access to property Content | OData.cs:51:55:51:64 | parameters : ODataActionParameters | OData.cs:56:18:56:31 | access to property Content | This path depends on an $@. | OData.cs:51:55:51:64 | parameters | ODataParameters value | +| OData.cs:57:18:57:38 | access to property Owner | OData.cs:51:55:51:64 | parameters : ODataActionParameters | OData.cs:57:18:57:38 | access to property Owner | This path depends on an $@. | OData.cs:51:55:51:64 | parameters | ODataParameters value | +| OData.cs:60:22:60:28 | access to property Owner | OData.cs:51:55:51:64 | parameters : ODataActionParameters | OData.cs:60:22:60:28 | access to property Owner | This path depends on an $@. | OData.cs:51:55:51:64 | parameters | ODataParameters value | +| OData.cs:70:26:70:35 | access to property Label | OData.cs:64:55:64:64 | parameters : ODataActionParameters | OData.cs:70:26:70:35 | access to property Label | This path depends on an $@. | OData.cs:64:55:64:64 | parameters | ODataParameters value | +| OData.cs:77:22:77:34 | access to property Category | OData.cs:64:55:64:64 | parameters : ODataActionParameters | OData.cs:77:22:77:34 | access to property Category | This path depends on an $@. | OData.cs:64:55:64:64 | parameters | ODataParameters value | +| OData.cs:85:18:85:28 | access to property Name | OData.cs:81:52:81:61 | parameters : ODataActionParameters | OData.cs:85:18:85:28 | access to property Name | This path depends on an $@. | OData.cs:81:52:81:61 | parameters | ODataParameters value | +| OData.cs:91:18:91:30 | access to property Name | OData.cs:88:39:88:43 | delta : Delta | OData.cs:91:18:91:30 | access to property Name | This path depends on an $@. | OData.cs:88:39:88:43 | delta | ODataParameters value | +| OData.cs:97:18:97:23 | access to property Name | OData.cs:94:45:94:49 | delta : Delta | OData.cs:97:18:97:23 | access to property Name | This path depends on an $@. | OData.cs:94:45:94:49 | delta | ODataParameters value | +| OData.cs:103:18:103:30 | access to property Name | OData.cs:100:67:100:71 | delta : Delta | OData.cs:103:18:103:30 | access to property Name | This path depends on an $@. | OData.cs:100:67:100:71 | delta | ODataParameters value | +| OData.cs:109:18:109:23 | access to property Name | OData.cs:106:71:106:75 | delta : Delta | OData.cs:109:18:109:23 | access to property Name | This path depends on an $@. | OData.cs:106:71:106:75 | delta | ODataParameters value | From 8519b913eed24e2022a3f79407320f60c61e00af Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 27 Aug 2026 11:25:03 +0200 Subject: [PATCH 16/16] C#: Remove recursion. --- csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll index 16422fda1d8c..bc0cbb431e3f 100644 --- a/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll +++ b/csharp/ql/lib/semmle/code/csharp/frameworks/OData.qll @@ -36,9 +36,9 @@ class ODataActionParametersClass extends Class { * -- may hold the value of) an `ODataActionParameters` dictionary. */ private predicate isODataActionParametersValue(Expr e) { - e.getType() instanceof ODataActionParametersClass - or - DataFlow::localExprFlow(any(Expr e0 | isODataActionParametersValue(e0)), e) + exists(ParameterAccess e0 | e0.getType() instanceof ODataActionParametersClass | + e0 = e or DataFlow::localExprFlow(e0, e) + ) } /**