Skip to content

Commit 94d12ed

Browse files
authored
Merge pull request github#16759 from michaelnebel/modelgen/sourcesinkmodelgen
C#/Java: Introduce source and sink model generation sanitisers.
2 parents a1743aa + aa962f9 commit 94d12ed

File tree

10 files changed

+170
-22
lines changed

10 files changed

+170
-22
lines changed

csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ private class TaintStore extends TaintState, TTaintStore {
145145
*
146146
* This can be used to generate Flow summaries for APIs from parameter to return.
147147
*/
148-
module ThroughFlowConfig implements DataFlow::StateConfigSig {
148+
module PropagateFlowConfig implements DataFlow::StateConfigSig {
149149
class FlowState = TaintState;
150150

151151
predicate isSource(DataFlow::Node source, FlowState state) {
@@ -190,14 +190,14 @@ module ThroughFlowConfig implements DataFlow::StateConfigSig {
190190
}
191191
}
192192

193-
private module ThroughFlow = TaintTracking::GlobalWithState<ThroughFlowConfig>;
193+
private module PropagateFlow = TaintTracking::GlobalWithState<PropagateFlowConfig>;
194194

195195
/**
196196
* Gets the summary model(s) of `api`, if there is flow from parameters to return value or parameter.
197197
*/
198198
string captureThroughFlow(DataFlowTargetApi api) {
199199
exists(DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt, string input, string output |
200-
ThroughFlow::flow(p, returnNodeExt) and
200+
PropagateFlow::flow(p, returnNodeExt) and
201201
returnNodeExt.(DataFlow::Node).getEnclosingCallable() = api and
202202
input = parameterNodeAsInput(p) and
203203
output = returnNodeExt.getOutput() and
@@ -213,8 +213,13 @@ string captureThroughFlow(DataFlowTargetApi api) {
213213
* This can be used to generate Source summaries for an API, if the API expose an already known source
214214
* via its return (then the API itself becomes a source).
215215
*/
216-
module FromSourceConfig implements DataFlow::ConfigSig {
217-
predicate isSource(DataFlow::Node source) { ExternalFlow::sourceNode(source, _) }
216+
module PropagateFromSourceConfig implements DataFlow::ConfigSig {
217+
predicate isSource(DataFlow::Node source) {
218+
exists(string kind |
219+
isRelevantSourceKind(kind) and
220+
ExternalFlow::sourceNode(source, kind)
221+
)
222+
}
218223

219224
predicate isSink(DataFlow::Node sink) {
220225
exists(DataFlowTargetApi c |
@@ -225,22 +230,26 @@ module FromSourceConfig implements DataFlow::ConfigSig {
225230

226231
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSinkCallContext }
227232

233+
predicate isBarrier(DataFlow::Node n) {
234+
exists(Type t | t = n.getType() and not isRelevantType(t))
235+
}
236+
228237
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
229238
isRelevantTaintStep(node1, node2)
230239
}
231240
}
232241

233-
private module FromSource = TaintTracking::Global<FromSourceConfig>;
242+
private module PropagateFromSource = TaintTracking::Global<PropagateFromSourceConfig>;
234243

235244
/**
236245
* Gets the source model(s) of `api`, if there is flow from an existing known source to the return of `api`.
237246
*/
238247
string captureSource(DataFlowTargetApi api) {
239248
exists(DataFlow::Node source, ReturnNodeExt sink, string kind |
240-
FromSource::flow(source, sink) and
249+
PropagateFromSource::flow(source, sink) and
241250
ExternalFlow::sourceNode(source, kind) and
242251
api = sink.getEnclosingCallable() and
243-
isRelevantSourceKind(kind) and
252+
not irrelevantSourceSinkApi(source.getEnclosingCallable(), api) and
244253
result = ModelPrinting::asSourceModel(api, sink.getOutput(), kind)
245254
)
246255
}
@@ -255,9 +264,15 @@ string captureSource(DataFlowTargetApi api) {
255264
module PropagateToSinkConfig implements DataFlow::ConfigSig {
256265
predicate isSource(DataFlow::Node source) { apiSource(source) }
257266

258-
predicate isSink(DataFlow::Node sink) { ExternalFlow::sinkNode(sink, _) }
267+
predicate isSink(DataFlow::Node sink) {
268+
exists(string kind | isRelevantSinkKind(kind) and ExternalFlow::sinkNode(sink, kind))
269+
}
259270

260-
predicate isBarrier(DataFlow::Node node) { sinkModelSanitizer(node) }
271+
predicate isBarrier(DataFlow::Node node) {
272+
exists(Type t | t = node.getType() and not isRelevantType(t))
273+
or
274+
sinkModelSanitizer(node)
275+
}
261276

262277
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
263278

@@ -276,7 +291,6 @@ string captureSink(DataFlowTargetApi api) {
276291
PropagateToSink::flow(src, sink) and
277292
ExternalFlow::sinkNode(sink, kind) and
278293
api = src.getEnclosingCallable() and
279-
isRelevantSinkKind(kind) and
280294
result = ModelPrinting::asSinkModel(api, asInputArgument(src), kind)
281295
)
282296
}

csharp/ql/src/utils/modelgenerator/internal/CaptureModelsSpecific.qll

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,29 @@ predicate apiSource(DataFlow::Node source) {
257257
)
258258
}
259259

260+
private predicate uniquelyCalls(DataFlowCallable dc1, DataFlowCallable dc2) {
261+
exists(DataFlowCall call |
262+
dc1 = call.getEnclosingCallable() and
263+
dc2 = unique(DataFlowCallable dc0 | dc0 = viableCallable(call) | dc0)
264+
)
265+
}
266+
267+
bindingset[dc1, dc2]
268+
private predicate uniquelyCallsPlus(DataFlowCallable dc1, DataFlowCallable dc2) =
269+
fastTC(uniquelyCalls/2)(dc1, dc2)
270+
271+
/**
272+
* Holds if it is not relevant to generate a source model for `api`, even
273+
* if flow is detected from a node within `source` to a sink within `api`.
274+
*/
275+
bindingset[sourceEnclosing, api]
276+
predicate irrelevantSourceSinkApi(Callable sourceEnclosing, TargetApiSpecific api) {
277+
not exists(DataFlowCallable dc1, DataFlowCallable dc2 | uniquelyCallsPlus(dc1, dc2) or dc1 = dc2 |
278+
dc1.getUnderlyingCallable() = api and
279+
dc2.getUnderlyingCallable() = sourceEnclosing
280+
)
281+
}
282+
260283
/**
261284
* Gets the MaD input string representation of `source`.
262285
*/
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/csharp-all
4+
extensible: sinkModel
5+
data:
6+
- [ "Sinks", "NewSinks", False, "Sink", "(System.Object)", "", "Argument[0]", "test-sink", "manual"]

csharp/ql/test/utils/modelgenerator/dataflow/Sinks.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ public class NewSinks
1212
public string TaintedProp { get; set; }
1313
public string PrivateSetTaintedProp { get; private set; }
1414

15+
// Sink defined in the extensible file next to the test.
16+
// neutral=Sinks;NewSinks;Sink;(System.Object);summary;df-generated
17+
public void Sink(object o) => throw null;
18+
1519
// New sink
1620
// sink=Sinks;NewSinks;false;WrapResponseWrite;(System.Object);;Argument[0];html-injection;df-generated
1721
// neutral=Sinks;NewSinks;WrapResponseWrite;(System.Object);summary;df-generated
@@ -78,6 +82,14 @@ public void WrapPropPrivateSetResponseWriteFile()
7882
var response = new HttpResponse();
7983
response.WriteFile(PrivateSetTaintedProp);
8084
}
85+
86+
// Not a new sink because a simple type is used in an intermediate step
87+
// neutral=Sinks;NewSinks;WrapResponseWriteFileSimpleType;(System.String);summary;df-generated
88+
public void WrapResponseWriteFileSimpleType(string s)
89+
{
90+
var r = s == "hello";
91+
Sink(r);
92+
}
8193
}
8294

8395
public class CompoundSinks

csharp/ql/test/utils/modelgenerator/dataflow/Sources.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,34 @@ public ConsoleKeyInfo WrapConsoleReadKey()
3434
{
3535
return Console.ReadKey();
3636
}
37+
38+
// Not a new source because a simple type is used in an intermediate step
39+
// neutral=Sources;NewSources;WrapConsoleReadLineGetBool;();summary;df-generated
40+
public bool WrapConsoleReadLineGetBool()
41+
{
42+
var s = Console.ReadLine();
43+
return s == "hello";
44+
}
45+
46+
public class MyConsoleReader
47+
{
48+
// source=Sources;NewSources+MyConsoleReader;false;ToString;();;ReturnValue;local;df-generated
49+
// neutral=Sources;NewSources+MyConsoleReader;ToString;();summary;df-generated
50+
public override string ToString()
51+
{
52+
return Console.ReadLine();
53+
}
54+
}
55+
56+
57+
public class MyContainer<T>
58+
{
59+
public T Value { get; set; }
60+
61+
// summary=Sources;NewSources+MyContainer<T>;false;Read;();;Argument[this];ReturnValue;taint;df-generated
62+
public string Read()
63+
{
64+
return Value.ToString();
65+
}
66+
}
3767
}

java/ql/src/utils/modelgenerator/internal/CaptureModels.qll

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ private class TaintStore extends TaintState, TTaintStore {
145145
*
146146
* This can be used to generate Flow summaries for APIs from parameter to return.
147147
*/
148-
module ThroughFlowConfig implements DataFlow::StateConfigSig {
148+
module PropagateFlowConfig implements DataFlow::StateConfigSig {
149149
class FlowState = TaintState;
150150

151151
predicate isSource(DataFlow::Node source, FlowState state) {
@@ -190,14 +190,14 @@ module ThroughFlowConfig implements DataFlow::StateConfigSig {
190190
}
191191
}
192192

193-
private module ThroughFlow = TaintTracking::GlobalWithState<ThroughFlowConfig>;
193+
private module PropagateFlow = TaintTracking::GlobalWithState<PropagateFlowConfig>;
194194

195195
/**
196196
* Gets the summary model(s) of `api`, if there is flow from parameters to return value or parameter.
197197
*/
198198
string captureThroughFlow(DataFlowTargetApi api) {
199199
exists(DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt, string input, string output |
200-
ThroughFlow::flow(p, returnNodeExt) and
200+
PropagateFlow::flow(p, returnNodeExt) and
201201
returnNodeExt.(DataFlow::Node).getEnclosingCallable() = api and
202202
input = parameterNodeAsInput(p) and
203203
output = returnNodeExt.getOutput() and
@@ -213,8 +213,13 @@ string captureThroughFlow(DataFlowTargetApi api) {
213213
* This can be used to generate Source summaries for an API, if the API expose an already known source
214214
* via its return (then the API itself becomes a source).
215215
*/
216-
module FromSourceConfig implements DataFlow::ConfigSig {
217-
predicate isSource(DataFlow::Node source) { ExternalFlow::sourceNode(source, _) }
216+
module PropagateFromSourceConfig implements DataFlow::ConfigSig {
217+
predicate isSource(DataFlow::Node source) {
218+
exists(string kind |
219+
isRelevantSourceKind(kind) and
220+
ExternalFlow::sourceNode(source, kind)
221+
)
222+
}
218223

219224
predicate isSink(DataFlow::Node sink) {
220225
exists(DataFlowTargetApi c |
@@ -225,22 +230,26 @@ module FromSourceConfig implements DataFlow::ConfigSig {
225230

226231
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSinkCallContext }
227232

233+
predicate isBarrier(DataFlow::Node n) {
234+
exists(Type t | t = n.getType() and not isRelevantType(t))
235+
}
236+
228237
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
229238
isRelevantTaintStep(node1, node2)
230239
}
231240
}
232241

233-
private module FromSource = TaintTracking::Global<FromSourceConfig>;
242+
private module PropagateFromSource = TaintTracking::Global<PropagateFromSourceConfig>;
234243

235244
/**
236245
* Gets the source model(s) of `api`, if there is flow from an existing known source to the return of `api`.
237246
*/
238247
string captureSource(DataFlowTargetApi api) {
239248
exists(DataFlow::Node source, ReturnNodeExt sink, string kind |
240-
FromSource::flow(source, sink) and
249+
PropagateFromSource::flow(source, sink) and
241250
ExternalFlow::sourceNode(source, kind) and
242251
api = sink.getEnclosingCallable() and
243-
isRelevantSourceKind(kind) and
252+
not irrelevantSourceSinkApi(source.getEnclosingCallable(), api) and
244253
result = ModelPrinting::asSourceModel(api, sink.getOutput(), kind)
245254
)
246255
}
@@ -255,9 +264,15 @@ string captureSource(DataFlowTargetApi api) {
255264
module PropagateToSinkConfig implements DataFlow::ConfigSig {
256265
predicate isSource(DataFlow::Node source) { apiSource(source) }
257266

258-
predicate isSink(DataFlow::Node sink) { ExternalFlow::sinkNode(sink, _) }
267+
predicate isSink(DataFlow::Node sink) {
268+
exists(string kind | isRelevantSinkKind(kind) and ExternalFlow::sinkNode(sink, kind))
269+
}
259270

260-
predicate isBarrier(DataFlow::Node node) { sinkModelSanitizer(node) }
271+
predicate isBarrier(DataFlow::Node node) {
272+
exists(Type t | t = node.getType() and not isRelevantType(t))
273+
or
274+
sinkModelSanitizer(node)
275+
}
261276

262277
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
263278

@@ -276,7 +291,6 @@ string captureSink(DataFlowTargetApi api) {
276291
PropagateToSink::flow(src, sink) and
277292
ExternalFlow::sinkNode(sink, kind) and
278293
api = src.getEnclosingCallable() and
279-
isRelevantSinkKind(kind) and
280294
result = ModelPrinting::asSinkModel(api, asInputArgument(src), kind)
281295
)
282296
}

java/ql/src/utils/modelgenerator/internal/CaptureModelsSpecific.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,12 @@ predicate apiSource(DataFlow::Node source) {
278278
)
279279
}
280280

281+
/**
282+
* Holds if it is not relevant to generate a source model for `api`, even
283+
* if flow is detected from a node within `source` to a sink within `api`.
284+
*/
285+
predicate irrelevantSourceSinkApi(Callable source, TargetApiSpecific api) { none() }
286+
281287
/**
282288
* Gets the MaD input string representation of `source`.
283289
*/
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
extensions:
2+
3+
- addsTo:
4+
pack: codeql/java-all
5+
extensible: sourceModel
6+
data:
7+
- [ "p", "Sources", False, "source", "()", "", "ReturnValue", "test-source", "manual" ]

java/ql/test/utils/modelgenerator/dataflow/p/Sinks.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,11 @@ public void hasManualSinkNeutral(Object o) {
6464
public void compoundPropgate(Sinks s) {
6565
s.fieldSink();
6666
}
67+
68+
// Not a new sink because a simple type is used in an intermediate step
69+
// neutral=p;Sinks;wrapSinkSimpleType;(String);summary;df-generated
70+
public void wrapSinkSimpleType(String s) {
71+
Boolean b = s == "hello";
72+
sink(b);
73+
}
6774
}

java/ql/test/utils/modelgenerator/dataflow/p/Sources.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
public class Sources {
1010

11+
// Defined as a source in the model file next to the test.
12+
// neutral=p;Sources;source;();summary;df-generated
13+
public String source() {
14+
return "";
15+
}
16+
1117
// source=p;Sources;true;readUrl;(URL);;ReturnValue;remote;df-generated
1218
// sink=p;Sources;true;readUrl;(URL);;Argument[0];request-forgery;df-generated
1319
// neutral=p;Sources;readUrl;(URL);summary;df-generated
@@ -37,4 +43,27 @@ public void sourceToParameter(InputStream[] streams, List<InputStream> otherStre
3743
streams[0] = socket.accept().getInputStream();
3844
otherStreams.add(socket.accept().getInputStream());
3945
}
46+
47+
// Not a new source because a simple type is used in an intermediate step
48+
// neutral=p;Sources;wrapSourceGetBool;();summary;df-generated
49+
public Boolean wrapSourceGetBool() {
50+
String s = source();
51+
return s == "hello";
52+
}
53+
54+
public class SourceReader {
55+
@Override
56+
public String toString() {
57+
return source();
58+
}
59+
}
60+
61+
public class MyContainer<T> {
62+
private T value;
63+
64+
// neutral=p;Sources$MyContainer;read;();summary;df-generated
65+
public String read() {
66+
return value.toString();
67+
}
68+
}
4069
}

0 commit comments

Comments
 (0)