Skip to content
Merged
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
163 changes: 163 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/telemetry/ExternalApi.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/** Provides classes and predicates related to handling APIs from external libraries. */

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.dataflow.FlowSummary
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate
private import semmle.code.csharp.security.dataflow.flowsources.ApiSources as ApiSources
private import semmle.code.csharp.security.dataflow.flowsinks.ApiSinks as ApiSinks
private import TestLibrary

/** Holds if the given callable is not worth supporting. */
private predicate isUninteresting(Callable c) {
c.getDeclaringType() instanceof TestLibrary
or
c.(Constructor).isParameterless()
or
// The data flow library uses read/store steps for properties, so we don't need to model them,
// if both a getter and a setter exist.
c.(Accessor).getDeclaration().(Property).isReadWrite()
}

/**
* An external API from either the C# Standard Library or a 3rd party library.
*/
class ExternalApi extends Callable {
ExternalApi() {
this.isUnboundDeclaration() and
this.fromLibrary() and
this.(Modifiable).isEffectivelyPublic() and
not isUninteresting(this)
}

/**
* Gets the unbound type, name and parameter types of this API.
*/
bindingset[this]
private string getSignature() {
result =
nestedName(this.getDeclaringType().getUnboundDeclaration()) + "." + this.getName() + "(" +
parameterQualifiedTypeNamesToString(this) + ")"
}

/**
* Gets the namespace of this API.
*/
bindingset[this]
string getNamespace() { this.getDeclaringType().hasFullyQualifiedName(result, _) }

/**
* Gets the namespace and signature of this API.
*/
bindingset[this]
string getApiName() { result = this.getNamespace() + "#" + this.getSignature() }

/** Gets a node that is an input to a call to this API. */
private ArgumentNode getAnInput() {
result
.getCall()
.(DataFlowDispatch::NonDelegateDataFlowCall)
.getATarget(_)
.getUnboundDeclaration() = this
}

/** Gets a node that is an output from a call to this API. */
private DataFlow::Node getAnOutput() {
exists(Call c, DataFlowDispatch::NonDelegateDataFlowCall dc |
dc.getDispatchCall().getCall() = c and
c.getTarget().getUnboundDeclaration() = this
|
result = DataFlowDispatch::getAnOutNode(dc, _)
)
}

/** Holds if this API has a supported summary. */
pragma[nomagic]
predicate hasSummary() {
this instanceof SummarizedCallable
or
defaultAdditionalTaintStep(this.getAnInput(), _, _)
}

/** Holds if this API is a known source. */
pragma[nomagic]
predicate isSource() { this.getAnOutput() instanceof ApiSources::SourceNode }

/** Holds if this API is a known sink. */
pragma[nomagic]
predicate isSink() { this.getAnInput() instanceof ApiSinks::SinkNode }

/** Holds if this API is a known neutral. */
pragma[nomagic]
predicate isNeutral() { this instanceof FlowSummaryImpl::Public::NeutralCallable }

/**
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
* recognized source, sink or neutral or it has a flow summary.
*/
predicate isSupported() {
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
}
}

/**
* Gets the nested name of the type `t`.
*
* If the type is not a nested type, the result is the same as \`getName()\`.
* Otherwise the name of the nested type is prefixed with a \`+\` and appended to
* the name of the enclosing type, which might be a nested type as well.
*/
private string nestedName(Type t) {
not exists(t.getDeclaringType().getUnboundDeclaration()) and
result = t.getName()
or
nestedName(t.getDeclaringType().getUnboundDeclaration()) + "+" + t.getName() = result
}

/**
* Gets the limit for the number of results produced by a telemetry query.
*/
int resultLimit() { result = 100 }

/**
* Holds if it is relevant to count usages of `api`.
*/
signature predicate relevantApi(ExternalApi api);

/**
* Given a predicate to count relevant API usages, this module provides a predicate
* for restricting the number or returned results based on a certain limit.
*/
module Results<relevantApi/1 getRelevantUsages> {
private int getUsages(string apiName) {
result =
strictcount(Call c, ExternalApi api |
c.getTarget().getUnboundDeclaration() = api and
apiName = api.getApiName() and
getRelevantUsages(api) and
c.fromSource()
)
}

private int getOrder(string apiName) {
apiName =
rank[result](string name, int usages |
usages = getUsages(name)
|
name order by usages desc, name
)
}

/**
* Holds if there exists an API with `apiName` that is being used `usages` times
* and if it is in the top results (guarded by resultLimit).
*/
predicate restrict(string apiName, int usages) {
usages = getUsages(apiName) and
getOrder(apiName) <= resultLimit()
}
}
18 changes: 18 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/telemetry/TestLibrary.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** Provides classes and predicates related to handling test libraries. */

private import csharp

pragma[nomagic]
private predicate isTestNamespace(Namespace ns) {
ns.getFullName()
.matches([
"NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%"
])
}

/**
* A test library.
*/
class TestLibrary extends RefType {
TestLibrary() { isTestNamespace(this.getNamespace()) }
}
165 changes: 3 additions & 162 deletions csharp/ql/src/Telemetry/ExternalApi.qll
Original file line number Diff line number Diff line change
@@ -1,163 +1,4 @@
/** Provides classes and predicates related to handling APIs from external libraries. */
// Use `semmle.code.csharp.telemetry.ExternalApi` instead.
deprecated module;

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.dataflow.FlowSummary
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate
private import semmle.code.csharp.security.dataflow.flowsources.ApiSources as ApiSources
private import semmle.code.csharp.security.dataflow.flowsinks.ApiSinks as ApiSinks
private import TestLibrary

/** Holds if the given callable is not worth supporting. */
private predicate isUninteresting(Callable c) {
c.getDeclaringType() instanceof TestLibrary
or
c.(Constructor).isParameterless()
or
// The data flow library uses read/store steps for properties, so we don't need to model them,
// if both a getter and a setter exist.
c.(Accessor).getDeclaration().(Property).isReadWrite()
}

/**
* An external API from either the C# Standard Library or a 3rd party library.
*/
class ExternalApi extends Callable {
ExternalApi() {
this.isUnboundDeclaration() and
this.fromLibrary() and
this.(Modifiable).isEffectivelyPublic() and
not isUninteresting(this)
}

/**
* Gets the unbound type, name and parameter types of this API.
*/
bindingset[this]
private string getSignature() {
result =
nestedName(this.getDeclaringType().getUnboundDeclaration()) + "." + this.getName() + "(" +
parameterQualifiedTypeNamesToString(this) + ")"
}

/**
* Gets the namespace of this API.
*/
bindingset[this]
string getNamespace() { this.getDeclaringType().hasFullyQualifiedName(result, _) }

/**
* Gets the namespace and signature of this API.
*/
bindingset[this]
string getApiName() { result = this.getNamespace() + "#" + this.getSignature() }

/** Gets a node that is an input to a call to this API. */
private ArgumentNode getAnInput() {
result
.getCall()
.(DataFlowDispatch::NonDelegateDataFlowCall)
.getATarget(_)
.getUnboundDeclaration() = this
}

/** Gets a node that is an output from a call to this API. */
private DataFlow::Node getAnOutput() {
exists(Call c, DataFlowDispatch::NonDelegateDataFlowCall dc |
dc.getDispatchCall().getCall() = c and
c.getTarget().getUnboundDeclaration() = this
|
result = DataFlowDispatch::getAnOutNode(dc, _)
)
}

/** Holds if this API has a supported summary. */
pragma[nomagic]
predicate hasSummary() {
this instanceof SummarizedCallable
or
defaultAdditionalTaintStep(this.getAnInput(), _, _)
}

/** Holds if this API is a known source. */
pragma[nomagic]
predicate isSource() { this.getAnOutput() instanceof ApiSources::SourceNode }

/** Holds if this API is a known sink. */
pragma[nomagic]
predicate isSink() { this.getAnInput() instanceof ApiSinks::SinkNode }

/** Holds if this API is a known neutral. */
pragma[nomagic]
predicate isNeutral() { this instanceof FlowSummaryImpl::Public::NeutralCallable }

/**
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
* recognized source, sink or neutral or it has a flow summary.
*/
predicate isSupported() {
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
}
}

/**
* Gets the nested name of the type `t`.
*
* If the type is not a nested type, the result is the same as \`getName()\`.
* Otherwise the name of the nested type is prefixed with a \`+\` and appended to
* the name of the enclosing type, which might be a nested type as well.
*/
private string nestedName(Type t) {
not exists(t.getDeclaringType().getUnboundDeclaration()) and
result = t.getName()
or
nestedName(t.getDeclaringType().getUnboundDeclaration()) + "+" + t.getName() = result
}

/**
* Gets the limit for the number of results produced by a telemetry query.
*/
int resultLimit() { result = 100 }

/**
* Holds if it is relevant to count usages of `api`.
*/
signature predicate relevantApi(ExternalApi api);

/**
* Given a predicate to count relevant API usages, this module provides a predicate
* for restricting the number or returned results based on a certain limit.
*/
module Results<relevantApi/1 getRelevantUsages> {
private int getUsages(string apiName) {
result =
strictcount(Call c, ExternalApi api |
c.getTarget().getUnboundDeclaration() = api and
apiName = api.getApiName() and
getRelevantUsages(api) and
c.fromSource()
)
}

private int getOrder(string apiName) {
apiName =
rank[result](string name, int usages |
usages = getUsages(name)
|
name order by usages desc, name
)
}

/**
* Holds if there exists an API with `apiName` that is being used `usages` times
* and if it is in the top results (guarded by resultLimit).
*/
predicate restrict(string apiName, int usages) {
usages = getUsages(apiName) and
getOrder(apiName) <= resultLimit()
}
}
import semmle.code.csharp.telemetry.ExternalApi
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/ExternalLibraryUsage.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate getRelevantUsages(string namespace, int usages) {
usages =
Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/SupportedExternalApis.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate relevant(ExternalApi api) { api.isSupported() }

Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/SupportedExternalSinks.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate relevant(ExternalApi api) { api.isSink() }

Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/SupportedExternalSources.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate relevant(ExternalApi api) { api.isSource() }

Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/SupportedExternalTaint.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate relevant(ExternalApi api) { api.hasSummary() }

Expand Down
Loading
Loading