From a91c1dc7159e587a9ae2648f4d5266d0bc2d191b Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 17 Dec 2024 13:15:54 +0100 Subject: [PATCH] C#: Move external api declarations to the library pack. --- .../code/csharp/telemetry/ExternalApi.qll | 163 +++++++++++++++++ .../code/csharp/telemetry/TestLibrary.qll | 16 ++ csharp/ql/src/Telemetry/ExternalApi.qll | 165 +----------------- .../ql/src/Telemetry/ExternalLibraryUsage.ql | 2 +- .../ql/src/Telemetry/SupportedExternalApis.ql | 2 +- .../src/Telemetry/SupportedExternalSinks.ql | 2 +- .../src/Telemetry/SupportedExternalSources.ql | 2 +- .../src/Telemetry/SupportedExternalTaint.ql | 2 +- csharp/ql/src/Telemetry/TestLibrary.qll | 18 +- .../src/Telemetry/UnsupportedExternalAPIs.ql | 2 +- .../frameworks/UnsupportedExternalAPIs.ql | 2 +- .../ql/src/utils/modeleditor/ModelEditor.qll | 2 +- 12 files changed, 193 insertions(+), 185 deletions(-) create mode 100644 csharp/ql/lib/semmle/code/csharp/telemetry/ExternalApi.qll create mode 100644 csharp/ql/lib/semmle/code/csharp/telemetry/TestLibrary.qll diff --git a/csharp/ql/lib/semmle/code/csharp/telemetry/ExternalApi.qll b/csharp/ql/lib/semmle/code/csharp/telemetry/ExternalApi.qll new file mode 100644 index 000000000000..270db8b0d19c --- /dev/null +++ b/csharp/ql/lib/semmle/code/csharp/telemetry/ExternalApi.qll @@ -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 { + 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() + } +} diff --git a/csharp/ql/lib/semmle/code/csharp/telemetry/TestLibrary.qll b/csharp/ql/lib/semmle/code/csharp/telemetry/TestLibrary.qll new file mode 100644 index 000000000000..f157dd27f92e --- /dev/null +++ b/csharp/ql/lib/semmle/code/csharp/telemetry/TestLibrary.qll @@ -0,0 +1,16 @@ +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()) } +} diff --git a/csharp/ql/src/Telemetry/ExternalApi.qll b/csharp/ql/src/Telemetry/ExternalApi.qll index 270db8b0d19c..288f635022fb 100644 --- a/csharp/ql/src/Telemetry/ExternalApi.qll +++ b/csharp/ql/src/Telemetry/ExternalApi.qll @@ -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 { - 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 diff --git a/csharp/ql/src/Telemetry/ExternalLibraryUsage.ql b/csharp/ql/src/Telemetry/ExternalLibraryUsage.ql index 9d6d733adef6..86c500790650 100644 --- a/csharp/ql/src/Telemetry/ExternalLibraryUsage.ql +++ b/csharp/ql/src/Telemetry/ExternalLibraryUsage.ql @@ -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 = diff --git a/csharp/ql/src/Telemetry/SupportedExternalApis.ql b/csharp/ql/src/Telemetry/SupportedExternalApis.ql index 6d6270fde4ef..e98023c77e14 100644 --- a/csharp/ql/src/Telemetry/SupportedExternalApis.ql +++ b/csharp/ql/src/Telemetry/SupportedExternalApis.ql @@ -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() } diff --git a/csharp/ql/src/Telemetry/SupportedExternalSinks.ql b/csharp/ql/src/Telemetry/SupportedExternalSinks.ql index 312647587888..6a0ef5ff345f 100644 --- a/csharp/ql/src/Telemetry/SupportedExternalSinks.ql +++ b/csharp/ql/src/Telemetry/SupportedExternalSinks.ql @@ -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() } diff --git a/csharp/ql/src/Telemetry/SupportedExternalSources.ql b/csharp/ql/src/Telemetry/SupportedExternalSources.ql index d3878941d551..635f143fc47e 100644 --- a/csharp/ql/src/Telemetry/SupportedExternalSources.ql +++ b/csharp/ql/src/Telemetry/SupportedExternalSources.ql @@ -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() } diff --git a/csharp/ql/src/Telemetry/SupportedExternalTaint.ql b/csharp/ql/src/Telemetry/SupportedExternalTaint.ql index b4d4dc7019c3..0ae3cc03ac0d 100644 --- a/csharp/ql/src/Telemetry/SupportedExternalTaint.ql +++ b/csharp/ql/src/Telemetry/SupportedExternalTaint.ql @@ -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() } diff --git a/csharp/ql/src/Telemetry/TestLibrary.qll b/csharp/ql/src/Telemetry/TestLibrary.qll index f157dd27f92e..0fcafb0a1d76 100644 --- a/csharp/ql/src/Telemetry/TestLibrary.qll +++ b/csharp/ql/src/Telemetry/TestLibrary.qll @@ -1,16 +1,4 @@ -private import csharp +// Use `semmle.code.csharp.telemetry.TestLibrary` instead. +deprecated module; -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()) } -} +import semmle.code.csharp.telemetry.TestLibrary diff --git a/csharp/ql/src/Telemetry/UnsupportedExternalAPIs.ql b/csharp/ql/src/Telemetry/UnsupportedExternalAPIs.ql index 99b8eaf74d97..f3f2fede827e 100644 --- a/csharp/ql/src/Telemetry/UnsupportedExternalAPIs.ql +++ b/csharp/ql/src/Telemetry/UnsupportedExternalAPIs.ql @@ -7,7 +7,7 @@ */ private import csharp -private import ExternalApi +private import semmle.code.csharp.telemetry.ExternalApi private predicate relevant(ExternalApi api) { not api.isSupported() } diff --git a/csharp/ql/src/meta/frameworks/UnsupportedExternalAPIs.ql b/csharp/ql/src/meta/frameworks/UnsupportedExternalAPIs.ql index 56299f82d9c0..9a36e2f76140 100644 --- a/csharp/ql/src/meta/frameworks/UnsupportedExternalAPIs.ql +++ b/csharp/ql/src/meta/frameworks/UnsupportedExternalAPIs.ql @@ -9,7 +9,7 @@ */ private import csharp -private import Telemetry.ExternalApi +private import semmle.code.csharp.telemetry.ExternalApi from Call c, ExternalApi api where diff --git a/csharp/ql/src/utils/modeleditor/ModelEditor.qll b/csharp/ql/src/utils/modeleditor/ModelEditor.qll index c6635d4cd802..db026daf7eaf 100644 --- a/csharp/ql/src/utils/modeleditor/ModelEditor.qll +++ b/csharp/ql/src/utils/modeleditor/ModelEditor.qll @@ -5,7 +5,7 @@ private import semmle.code.csharp.dataflow.FlowSummary private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl private import semmle.code.csharp.dataflow.internal.ExternalFlow private import semmle.code.csharp.frameworks.Test -private import Telemetry.TestLibrary +private import semmle.code.csharp.telemetry.TestLibrary /** Holds if the given callable is not worth supporting. */ private predicate isUninteresting(Callable c) {