Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.frameworks.EntityFramework
private import semmle.code.csharp.frameworks.NHibernate
private import semmle.code.csharp.frameworks.Razor
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.frameworks.system.Collections
private import semmle.code.csharp.frameworks.system.threading.Tasks
private import codeql.util.Unit
Expand Down Expand Up @@ -841,6 +842,58 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
nodeTo = nodeFrom.(LocalFunctionCreationNode).getAnAccess(true)
}

module ApiTypeFlow {
private Callable getRelevantCallable(NonDelegateDataFlowCall call) {
exists(DispatchCall dc |
dc = call.getDispatchCall() and
dc.getCall().fromSource() and
(
result = dc.getADynamicTarget() or
result = dc.getAStaticTarget()
) and
result.fromLibrary()
)
}

/**
* Notes:
* (1) Make some test code (including a library that is called)
* (2) Make sure that flow doesn't go in- and out of out parameters.
* (3) Make sure that we only apply this to non source code calls
* (4) Make sure that we only apply this if there doesn't exist a summary or neutral.
* (5) Match input and out-out types.
* (6) Only consider "interesting" types (eg. non-simple types)
* (7) We should track taint from qualifier to the return (this could be fluent APIs)
* (8) Assume that the constructor call taints the object case a tainited non simple
* typed value is provided.
* (9) Respect neutrals (no flow if there exist any neutral)
* (10) Should we also handle properties?
*/
predicate taintStep(ArgumentNode nodeFrom, OutNode nodeTo) {
exists(DataFlowCall call, Callable c, ArgumentPosition apos |
call = nodeTo.getCall(_) and
nodeFrom.argumentOf(call, apos) and
c = getRelevantCallable(call) and
(
// r = new C(arg)
exists(apos.getPosition()) and c instanceof Constructor
or
// r = arg.Api(...)
apos.isQualifier() and not c instanceof Constructor
or
// r = c.Api(arg)
exists(Type t |
not t instanceof SimpleType and
not t instanceof SystemDateTimeStruct
|
t = nodeFrom.getType() and
t = nodeTo.getType()
)
)
)
}
}

/**
* Holds if `arg` is a `params` argument of `c`, for parameter `p`, and `arg` will
* be wrapped in an array by the C# compiler.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ private module Cached {
nodeTo.(FlowSummaryNode).getSummaryNode(), false)
or
nodeTo = nodeFrom.(DataFlow::NonLocalJumpNode).getAJumpSuccessor(false)
or
ApiTypeFlow::taintStep(nodeFrom, nodeTo)
}
}

Expand Down
140 changes: 140 additions & 0 deletions csharp/ql/test/library-tests/dataflow/typebased/TypeBased.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System;

namespace My.TypeBased
{
public class Test
{
public void M1()
{
var s1 = Source<string>(1);
MyApi.Api1(s1);
Sink(s1); // $ hasTaintFlow=1
}

public void M2()
{
var s2 = Source<string>(2);
var r = MyApi.Api2(s2);
Sink(r); // $ hasTaintFlow=2
}

public void M31()
{
var s3 = Source<string>(3);
var r = MyApi.Api3(s3, "hello");
Sink(r); // $ hasTaintFlow=3
}

public void M32()
{
var s3 = Source<string>(3);
var r = MyApi.Api3("hello", s3);
Sink(r); // $ hasTaintFlow=3
}

public void M41()
{
var s4 = Source<string>(4);
var o4 = new object();
var r = MyApi.Api4(s4, o4);
Sink(r); // $ hasTaintFlow=4
}

public void M42()
{
var o4 = Source<object>(4);
var r = MyApi.Api4("hello", o4);
Sink(r); // No flow
}

public void M51()
{
var s5 = Source<string>(5);
var o5 = new object();
var r = MyApi.Api5(s5, o5);
Sink(r); // No flow
}

public void M52()
{
var o5 = Source<object>(5);
var r = MyApi.Api5("hello", o5);
Sink(r); // $ hasTaintFlow=5
}

public void M6()
{
var s6 = Source<string>(6);
MyApi.Api6(s6, out var r);
Sink(r); // $ hasTaintFlow=6
}

public void M7()
{
var s7 = Source<string>(7);
var r2 = MyApi.Api7(s7, out var r1);
Sink(r1); // $ hasTaintFlow=7
Sink(r2); // $ hasTaintFlow=7
}

public void M8()
{
var o8 = Source<object>(8);
var i8 = Source<int>(8);
var r = MyApi.Api8(o8, i8);
Sink(r); // $ No flow
}

public void M9()
{
var d9 = Source<DateTime>(9);
var t9 = Source<TimeSpan>(9);
var r = MyApi.Api9(d9, t9);
Sink(r); // No flow
}

public void M10()
{
var b10 = Source<bool>(10);
var i10 = Source<int>(10);
var r = MyApi.Api10(b10, i10);
Sink(r); // No flow
}

public void M11()
{
var c = new MyClass("hello", 42);
var r = c.Api1(42);
Sink(r); // No flow
}

public void M12()
{
var s12 = Source<string>(12);
var c = new MyClass(s12, 0);
var r = c.Api1(0);
Sink(r); // $ hasTaintFlow=12
}

public void M13()
{
var s131 = Source<string>(131);
var s132 = Source<string>(132);
var c = new MyClass(s131, 0);
var r = c.Api2(s132);
Sink(r); // $ hasTaintFlow=131 hasTaintFlow=132
}

public void M14()
{
var s14 = Source<string>(14);
var c = new MyClass(s14, 0);
c.Api3(out var r);
Sink(r); // $ hasTaintFlow=14
}

public static void Sink(object o) { }

public static T Source<T>(object source) => throw null;
}
}
Loading