Skip to content

Commit 195ccb0

Browse files
authored
Merge pull request #16484 from michaelnebel/csharp/superimplmodelgen
C#: Lift models.
2 parents 4c97b0c + 5a25967 commit 195ccb0

File tree

5 files changed

+188
-41
lines changed

5 files changed

+188
-41
lines changed

csharp/ql/lib/semmle/code/csharp/dataflow/internal/ExternalFlow.qll

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ private import FlowSummaryImpl::Public
9494
private import FlowSummaryImpl::Private
9595
private import FlowSummaryImpl::Private::External
9696
private import semmle.code.csharp.commons.QualifiedName
97+
private import semmle.code.csharp.dispatch.OverridableCallable
98+
private import semmle.code.csharp.frameworks.System
9799
private import codeql.mad.ModelValidation as SharedModelVal
98100

99101
private predicate relevantNamespace(string namespace) {
@@ -442,20 +444,16 @@ predicate sourceNode(Node node, string kind) { sourceNode(node, kind, _) }
442444
*/
443445
predicate sinkNode(Node node, string kind) { sinkNode(node, kind, _) }
444446

445-
/** Holds if the summary should apply for all overrides of `c`. */
446-
predicate isBaseCallableOrPrototype(UnboundCallable c) {
447-
c.getDeclaringType() instanceof Interface
448-
or
449-
exists(Modifiable m | m = [c.(Modifiable), c.(Accessor).getDeclaration()] |
450-
m.isAbstract()
451-
or
452-
c.getDeclaringType().(Modifiable).isAbstract() and m.(Virtualizable).isVirtual()
447+
private predicate isOverridableCallable(OverridableCallable c) {
448+
not exists(Type t, Callable base | c.getOverridee+() = base and t = base.getDeclaringType() |
449+
t instanceof SystemObjectClass or
450+
t instanceof SystemValueTypeClass
453451
)
454452
}
455453

456454
/** Gets a string representing whether the summary should apply for all overrides of `c`. */
457455
private string getCallableOverride(UnboundCallable c) {
458-
if isBaseCallableOrPrototype(c) then result = "true" else result = "false"
456+
if isOverridableCallable(c) then result = "true" else result = "false"
459457
}
460458

461459
private module QualifiedNameInput implements QualifiedNameInputSig {

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

Lines changed: 90 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ private import semmle.code.csharp.commons.Collections as Collections
88
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch
99
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
1010
private import semmle.code.csharp.frameworks.system.linq.Expressions
11+
private import semmle.code.csharp.frameworks.System
1112
import semmle.code.csharp.dataflow.internal.ExternalFlow as ExternalFlow
1213
import semmle.code.csharp.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
1314
import semmle.code.csharp.dataflow.internal.DataFlowPrivate as DataFlowPrivate
@@ -19,10 +20,12 @@ module TaintTracking = CS::TaintTracking;
1920

2021
class Type = CS::Type;
2122

23+
class Callable = CS::Callable;
24+
2225
/**
2326
* Holds if any of the parameters of `api` are `System.Func<>`.
2427
*/
25-
private predicate isHigherOrder(CS::Callable api) {
28+
private predicate isHigherOrder(Callable api) {
2629
exists(Type t | t = api.getAParameter().getType().getUnboundDeclaration() |
2730
t instanceof SystemLinqExpressions::DelegateExtType
2831
)
@@ -32,23 +35,56 @@ private predicate irrelevantAccessor(CS::Accessor a) {
3235
a.getDeclaration().(CS::Property).isReadWrite()
3336
}
3437

35-
/**
36-
* Holds if it is relevant to generate models for `api`.
37-
*/
38-
private predicate isRelevantForModels(CS::Callable api) {
39-
[api.(CS::Modifiable), api.(CS::Accessor).getDeclaration()].isEffectivelyPublic() and
40-
api.getDeclaringType().getNamespace().getFullName() != "" and
41-
not api instanceof CS::ConversionOperator and
42-
not api instanceof Util::MainMethod and
43-
not api instanceof CS::Destructor and
44-
not api instanceof CS::AnonymousFunctionExpr and
45-
not api.(CS::Constructor).isParameterless() and
46-
// Disregard all APIs that have a manual model.
47-
not api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) and
48-
not api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) and
38+
private predicate isUninterestingForModels(Callable api) {
39+
api.getDeclaringType().getNamespace().getFullName() = ""
40+
or
41+
api instanceof CS::ConversionOperator
42+
or
43+
api instanceof Util::MainMethod
44+
or
45+
api instanceof CS::Destructor
46+
or
47+
api instanceof CS::AnonymousFunctionExpr
48+
or
49+
api.(CS::Constructor).isParameterless()
50+
or
51+
exists(Type decl | decl = api.getDeclaringType() |
52+
decl instanceof SystemObjectClass or
53+
decl instanceof SystemValueTypeClass
54+
)
55+
or
4956
// Disregard properties that have both a get and a set accessor,
5057
// which implicitly means auto implemented properties.
51-
not irrelevantAccessor(api)
58+
irrelevantAccessor(api)
59+
}
60+
61+
private predicate relevant(Callable api) {
62+
[api.(CS::Modifiable), api.(CS::Accessor).getDeclaration()].isEffectivelyPublic() and
63+
api.fromSource() and
64+
api.isUnboundDeclaration() and
65+
not isUninterestingForModels(api)
66+
}
67+
68+
private Callable getARelevantOverrideeOrImplementee(Overridable m) {
69+
m.overridesOrImplements(result) and relevant(result)
70+
}
71+
72+
/**
73+
* Gets the super implementation of `api` if it is relevant.
74+
* If such a super implementation does not exist, returns `api` if it is relevant.
75+
*/
76+
private Callable liftedImpl(Callable api) {
77+
(
78+
result = getARelevantOverrideeOrImplementee(api)
79+
or
80+
result = api and relevant(api)
81+
) and
82+
not exists(getARelevantOverrideeOrImplementee(result))
83+
}
84+
85+
private predicate hasManualModel(Callable api) {
86+
api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) or
87+
api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel())
5288
}
5389

5490
/**
@@ -66,23 +102,37 @@ predicate isUninterestingForDataFlowModels(CS::Callable api) { isHigherOrder(api
66102
predicate isUninterestingForTypeBasedFlowModels(CS::Callable api) { none() }
67103

68104
/**
69-
* A class of callables that are relevant generating summary, source and sinks models for.
105+
* A class of callables that are potentially relevant for generating summary, source, sink
106+
* and neutral models.
70107
*
71-
* In the Standard library and 3rd party libraries it the callables that can be called
72-
* from outside the library itself.
108+
* In the Standard library and 3rd party libraries it is the callables (or callables that have a
109+
* super implementation) that can be called from outside the library itself.
73110
*/
74-
class TargetApiSpecific extends CS::Callable {
111+
class TargetApiSpecific extends Callable {
112+
private Callable lift;
113+
75114
TargetApiSpecific() {
76-
this.fromSource() and
77-
this.isUnboundDeclaration() and
78-
isRelevantForModels(this)
115+
lift = liftedImpl(this) and
116+
not hasManualModel(lift)
79117
}
118+
119+
/**
120+
* Gets the callable that a model will be lifted to.
121+
*
122+
* The lifted callable is relevant in terms of model
123+
* generation (this is ensured by `liftedImpl`).
124+
*/
125+
Callable lift() { result = lift }
126+
127+
/**
128+
* Holds if `this` is relevant in terms of model generation.
129+
*/
130+
predicate isRelevant() { relevant(this) }
80131
}
81132

82-
predicate asPartialModel = ExternalFlow::asPartialModel/1;
133+
string asPartialModel(TargetApiSpecific api) { result = ExternalFlow::asPartialModel(api.lift()) }
83134

84-
/** Computes the first 4 columns for neutral CSV rows of `c`. */
85-
predicate asPartialNeutralModel = ExternalFlow::getSignature/1;
135+
string asPartialNeutralModel(TargetApiSpecific api) { result = ExternalFlow::getSignature(api) }
86136

87137
/**
88138
* Holds if `t` is a type that is generally used for bulk data in collection types.
@@ -151,7 +201,7 @@ string paramReturnNodeAsOutput(CS::Callable c, ParameterPosition pos) {
151201
/**
152202
* Gets the enclosing callable of `ret`.
153203
*/
154-
CS::Callable returnNodeEnclosingCallable(DataFlow::Node ret) {
204+
Callable returnNodeEnclosingCallable(DataFlow::Node ret) {
155205
result = DataFlowImplCommon::getNodeEnclosingCallable(ret).asCallable()
156206
}
157207

@@ -176,12 +226,24 @@ private predicate isRelevantMemberAccess(DataFlow::Node node) {
176226

177227
predicate sinkModelSanitizer(DataFlow::Node node) { none() }
178228

229+
private class ManualNeutralSinkCallable extends Callable {
230+
ManualNeutralSinkCallable() {
231+
this =
232+
any(FlowSummaryImpl::Public::NeutralCallable nc |
233+
nc.hasManualModel() and nc.getKind() = "sink"
234+
)
235+
}
236+
}
237+
179238
/**
180239
* Holds if `source` is an api entrypoint relevant for creating sink models.
181240
*/
182241
predicate apiSource(DataFlow::Node source) {
183242
(isRelevantMemberAccess(source) or source instanceof DataFlow::ParameterNode) and
184-
isRelevantForModels(source.getEnclosingCallable())
243+
exists(Callable enclosing | enclosing = source.getEnclosingCallable() |
244+
relevant(enclosing) and
245+
not enclosing instanceof ManualNeutralSinkCallable
246+
)
185247
}
186248

187249
/**

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,13 @@ string captureFlow(DataFlowTargetApi api) {
8181
}
8282

8383
/**
84-
* Gets the neutral model for `api`, if any.
85-
* A neutral model is generated, if there does not exist summary model.
84+
* Gets the neutral summary model for `api`, if any.
85+
* A neutral summary model is generated, if we are not generating
86+
* a summary model that applies to `api` and if it relevant to generate
87+
* a model for `api`.
8688
*/
8789
string captureNoFlow(DataFlowTargetApi api) {
88-
not exists(captureFlow(api)) and
90+
not exists(DataFlowTargetApi api0 | exists(captureFlow(api0)) and api0.lift() = api.lift()) and
91+
api.isRelevant() and
8992
result = ModelPrinting::asNeutralSummaryModel(api)
9093
}

csharp/ql/test/library-tests/dataflow/library/FlowSummariesFiltered.ql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
import shared.FlowSummaries
22
private import semmle.code.csharp.dataflow.internal.ExternalFlow
33

4+
/** Holds if `c` is a base callable or prototype. */
5+
private predicate isBaseCallableOrPrototype(UnboundCallable c) {
6+
c.getDeclaringType() instanceof Interface
7+
or
8+
exists(Modifiable m | m = [c.(Modifiable), c.(Accessor).getDeclaration()] |
9+
m.isAbstract()
10+
or
11+
c.getDeclaringType().(Modifiable).isAbstract() and m.(Virtualizable).isVirtual()
12+
)
13+
}
14+
415
class IncludeFilteredSummarizedCallable extends IncludeSummarizedCallable {
516
/**
617
* Holds if flow is propagated between `input` and `output` and

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

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ public string ReturnParam1(string input0, string input1)
245245

246246
public class DerivedClass2Flow : BaseClassFlow
247247
{
248-
// summary=Models;DerivedClass2Flow;false;ReturnParam;(System.Object);;Argument[0];ReturnValue;taint;df-generated
248+
// summary=Models;BaseClassFlow;true;ReturnParam;(System.Object);;Argument[0];ReturnValue;taint;df-generated
249249
public override object ReturnParam(object input)
250250
{
251251
return input;
@@ -490,3 +490,76 @@ public ParameterlessConstructor()
490490
IsInitialized = true;
491491
}
492492
}
493+
494+
public class Inheritance
495+
{
496+
public abstract class BasePublic
497+
{
498+
public abstract string Id(string x);
499+
}
500+
501+
public class AImplBasePublic : BasePublic
502+
{
503+
// summary=Models;Inheritance+BasePublic;true;Id;(System.String);;Argument[0];ReturnValue;taint;df-generated
504+
public override string Id(string x)
505+
{
506+
return x;
507+
}
508+
}
509+
510+
public interface IPublic1
511+
{
512+
string Id(string x);
513+
}
514+
515+
public interface IPublic2
516+
{
517+
string Id(string x);
518+
}
519+
520+
public abstract class B : IPublic1
521+
{
522+
public abstract string Id(string x);
523+
}
524+
525+
private abstract class C : IPublic2
526+
{
527+
public abstract string Id(string x);
528+
}
529+
530+
public class BImpl : B
531+
{
532+
// summary=Models;Inheritance+IPublic1;true;Id;(System.String);;Argument[0];ReturnValue;taint;df-generated
533+
public override string Id(string x)
534+
{
535+
return x;
536+
}
537+
}
538+
539+
private class CImpl : C
540+
{
541+
// summary=Models;Inheritance+IPublic2;true;Id;(System.String);;Argument[0];ReturnValue;taint;df-generated
542+
public override string Id(string x)
543+
{
544+
return x;
545+
}
546+
}
547+
548+
public interface IPublic3
549+
{
550+
string Prop { get; }
551+
}
552+
553+
public abstract class D : IPublic3
554+
{
555+
public abstract string Prop { get; }
556+
}
557+
558+
public class DImpl : D
559+
{
560+
private string tainted;
561+
562+
// summary=Models;Inheritance+IPublic3;true;get_Prop;();;Argument[this];ReturnValue;taint;df-generated
563+
public override string Prop { get { return tainted; } }
564+
}
565+
}

0 commit comments

Comments
 (0)