Migrate to shared FlowSummary library

This commit is contained in:
Asger F
2024-06-25 14:20:56 +02:00
parent dd7aff555d
commit 6b35a766a6
7 changed files with 72 additions and 1603 deletions

View File

@@ -2,81 +2,25 @@
private import javascript
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImpl as Impl
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImplSpecific
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowImplCommon as DataFlowImplCommon
private import semmle.javascript.dataflow.internal.DataFlowPrivate
class SummaryComponent = Impl::Public::SummaryComponent;
/** Provides predicates for constructing summary components. */
module SummaryComponent {
private import Impl::Public::SummaryComponent as SC
predicate parameter = SC::parameter/1;
predicate argument = SC::argument/1;
predicate content = SC::content/1;
predicate withoutContent = SC::withoutContent/1;
predicate withContent = SC::withContent/1;
class SyntheticGlobal = SC::SyntheticGlobal;
/** Gets a summary component that represents a receiver. */
SummaryComponent receiver() { result = argument(MkThisParameter()) }
/** Gets a summary component that represents the return value of a call. */
SummaryComponent return() { result = SC::return(MkNormalReturnKind()) }
/** Gets a summary component that represents the exception thrown from a call. */
SummaryComponent exceptionalReturn() { result = SC::return(MkExceptionalReturnKind()) }
}
class SummaryComponentStack = Impl::Public::SummaryComponentStack;
/** Provides predicates for constructing stacks of summary components. */
module SummaryComponentStack {
private import Impl::Public::SummaryComponentStack as SCS
predicate singleton = SCS::singleton/1;
predicate push = SCS::push/2;
predicate argument = SCS::argument/1;
/** Gets a singleton stack representing a receiver. */
SummaryComponentStack receiver() { result = singleton(SummaryComponent::receiver()) }
/** Gets a singleton stack representing the return value of a call. */
SummaryComponentStack return() { result = singleton(SummaryComponent::return()) }
/** Gets a singleton stack representing the exception thrown from a call. */
SummaryComponentStack exceptionalReturn() {
result = singleton(SummaryComponent::exceptionalReturn())
}
}
/** A callable with a flow summary, identified by a unique string. */
abstract class SummarizedCallable extends LibraryCallable, Impl::Public::SummarizedCallable {
bindingset[this]
SummarizedCallable() { any() }
/**
* Same as
*
* ```ql
* propagatesFlow(
* SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
* )
* ```
*
* but uses an external (string) representation of the input and output stacks.
*/
// TODO: rename 'propagatesFlowExt' and/or override 'propagatesFlow' directly
pragma[nomagic]
predicate propagatesFlowExt(string input, string output, boolean preservesValue) { none() }
override predicate propagatesFlow(
string input, string output, boolean preservesValue, string model
) {
this.propagatesFlowExt(input, output, preservesValue) and model = this
}
/**
* Gets the synthesized parameter that results from an input specification
* that starts with `Argument[s]` for this library callable.
@@ -88,5 +32,3 @@ abstract class SummarizedCallable extends LibraryCallable, Impl::Public::Summari
)
}
}
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;

View File

@@ -10,6 +10,7 @@ private import semmle.javascript.dataflow.internal.Contents::Private
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowImplCommon as DataFlowImplCommon
private import semmle.javascript.dataflow.internal.DataFlowPrivate as DataFlowPrivate
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImpl as FlowSummaryImpl
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate as FlowSummaryPrivate
private import semmle.javascript.dataflow.internal.VariableCapture as VariableCapture
cached
@@ -58,7 +59,10 @@ private module Cached {
TConstructorThisPostUpdate(Constructor ctor) or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TFlowSummaryIntermediateAwaitStoreNode(FlowSummaryImpl::Private::SummaryNode sn) {
FlowSummaryImpl::Private::Steps::summaryStoreStep(sn, MkAwaited(), _)
// NOTE: This dependency goes through the 'Steps' module whose instantiation depends on the call graph,
// but the specific predicate we're referering to does not use that information.
// So it doesn't cause negative recursion but it might look a bit surprising.
FlowSummaryPrivate::Steps::summaryStoreStep(sn, MkAwaited(), _)
} or
TSynthCaptureNode(VariableCapture::VariableCaptureOutput::SynthesizedCaptureNode node) or
TGenericSynthesizedNode(AstNode node, string tag, DataFlowPrivate::DataFlowCallable container) {

View File

@@ -8,6 +8,8 @@ private import semmle.javascript.dataflow.internal.VariableCapture
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowImplCommon as DataFlowImplCommon
private import semmle.javascript.internal.flow_summaries.AllFlowSummaries
private import sharedlib.FlowSummaryImpl as FlowSummaryImpl
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate as FlowSummaryPrivate
private import semmle.javascript.dataflow.FlowSummary as FlowSummary
private import semmle.javascript.dataflow.internal.BarrierGuards
class DataFlowSecondLevelScope = Unit;
@@ -117,7 +119,8 @@ private DataFlow::Node getAnOutNodeImpl(DataFlowCall call, ReturnKind kind) {
or
kind = MkNormalReturnKind() and result = call.asAccessorCall().(DataFlow::PropRead)
or
FlowSummaryImpl::Private::summaryOutNode(call, result.(FlowSummaryNode).getSummaryNode(), kind)
FlowSummaryImpl::Private::summaryOutNode(call.(SummaryCall).getReceiver(),
result.(FlowSummaryNode).getSummaryNode(), kind)
}
class ReturnNode extends DataFlow::Node {
@@ -275,7 +278,8 @@ private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition
// argument to setter (TODO: this has no post-update node)
pos.asPositional() = 0 and n = call.asAccessorCall().(DataFlow::PropWrite).getRhs()
or
FlowSummaryImpl::Private::summaryArgumentNode(call, n.(FlowSummaryNode).getSummaryNode(), pos)
FlowSummaryImpl::Private::summaryArgumentNode(call.(SummaryCall).getReceiver(),
n.(FlowSummaryNode).getSummaryNode(), pos)
}
predicate isArgumentNode(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) {
@@ -802,8 +806,8 @@ private predicate valuePreservingStep(Node node1, Node node2) {
or
node2 = FlowSteps::getThrowTarget(node1)
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(node1.(FlowSummaryNode).getSummaryNode(),
node2.(FlowSummaryNode).getSummaryNode(), true)
FlowSummaryPrivate::Steps::summaryLocalStep(node1.(FlowSummaryNode).getSummaryNode(),
node2.(FlowSummaryNode).getSummaryNode(), true, _) // TODO: preserve 'model'
or
// Step from post-update nodes to local sources of the pre-update node. This emulates how JS usually tracks side effects.
exists(PostUpdateNode postUpdate |
@@ -828,7 +832,7 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
nodeGetEnclosingCallable(pragma[only_bind_out](node2))
or
exists(FlowSummaryImpl::Private::SummaryNode input, FlowSummaryImpl::Private::SummaryNode output |
FlowSummaryImpl::Private::Steps::summaryStoreStep(input, MkAwaited(), output) and
FlowSummaryPrivate::Steps::summaryStoreStep(input, MkAwaited(), output) and
node1 = TFlowSummaryNode(input) and
(
node2 = TFlowSummaryNode(output) and
@@ -837,7 +841,7 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
node2 = TFlowSummaryIntermediateAwaitStoreNode(input)
)
or
FlowSummaryImpl::Private::Steps::summaryReadStep(input, MkAwaited(), output) and
FlowSummaryPrivate::Steps::summaryReadStep(input, MkAwaited(), output) and
node1 = TFlowSummaryNode(input) and
node2 = TFlowSummaryNode(output)
)
@@ -859,7 +863,7 @@ predicate jumpStep(Node node1, Node node2) {
valuePreservingStep(node1, node2) and
node1.getContainer() != node2.getContainer()
or
FlowSummaryImpl::Private::Steps::summaryJumpStep(node1.(FlowSummaryNode).getSummaryNode(),
FlowSummaryPrivate::Steps::summaryJumpStep(node1.(FlowSummaryNode).getSummaryNode(),
node2.(FlowSummaryNode).getSummaryNode())
or
DataFlow::AdditionalFlowStep::jumpStep(node1, node2)
@@ -882,8 +886,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
)
or
exists(ContentSet contentSet |
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(),
contentSet, node2.(FlowSummaryNode).getSummaryNode())
FlowSummaryPrivate::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), contentSet,
node2.(FlowSummaryNode).getSummaryNode())
|
not isSpecialContentSet(contentSet) and
c = contentSet
@@ -894,7 +898,7 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
or
// For deep reads, generate read edges with a self-loop
exists(Node origin, ContentSet contentSet |
FlowSummaryImpl::Private::Steps::summaryReadStep(origin.(FlowSummaryNode).getSummaryNode(),
FlowSummaryPrivate::Steps::summaryReadStep(origin.(FlowSummaryNode).getSummaryNode(),
contentSet, node2.(FlowSummaryNode).getSummaryNode()) and
node1 = [origin, node2]
|
@@ -938,13 +942,13 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
node2 = tryGetPostUpdate(write.getBase())
)
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
FlowSummaryPrivate::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
node2.(FlowSummaryNode).getSummaryNode()) and
not isSpecialContentSet(c)
or
// Store into Awaited
exists(FlowSummaryImpl::Private::SummaryNode input, FlowSummaryImpl::Private::SummaryNode output |
FlowSummaryImpl::Private::Steps::summaryStoreStep(input, MkAwaited(), output) and
FlowSummaryPrivate::Steps::summaryStoreStep(input, MkAwaited(), output) and
node1 = TFlowSummaryIntermediateAwaitStoreNode(input) and
node2 = TFlowSummaryNode(output) and
c = ContentSet::promiseValue()
@@ -964,15 +968,14 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
* in `x.f = newValue`.
*/
predicate clearsContent(Node n, ContentSet c) {
FlowSummaryImpl::Private::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
FlowSummaryPrivate::Steps::summaryClearsContent(n.(FlowSummaryNode).getSummaryNode(), c)
or
// Clear promise content before storing into promise value, to avoid creating nested promises
n = TFlowSummaryIntermediateAwaitStoreNode(_) and
c = MkPromiseFilter()
or
// After reading from Awaited, the output must not be stored in a promise content
FlowSummaryImpl::Private::Steps::summaryReadStep(_, MkAwaited(),
n.(FlowSummaryNode).getSummaryNode()) and
FlowSummaryPrivate::Steps::summaryReadStep(_, MkAwaited(), n.(FlowSummaryNode).getSummaryNode()) and
c = MkPromiseFilter()
or
any(AdditionalFlowInternal flow).clearsContent(n, c)
@@ -998,12 +1001,11 @@ predicate clearsContent(Node n, ContentSet c) {
* at node `n`.
*/
predicate expectsContent(Node n, ContentSet c) {
FlowSummaryImpl::Private::Steps::summaryExpectsContent(n.(FlowSummaryNode).getSummaryNode(), c)
FlowSummaryPrivate::Steps::summaryExpectsContent(n.(FlowSummaryNode).getSummaryNode(), c)
or
// After storing into Awaited, the result must be stored in a promise-content.
// There is a value step from the input directly to this node, hence the need for expectsContent.
FlowSummaryImpl::Private::Steps::summaryStoreStep(_, MkAwaited(),
n.(FlowSummaryNode).getSummaryNode()) and
FlowSummaryPrivate::Steps::summaryStoreStep(_, MkAwaited(), n.(FlowSummaryNode).getSummaryNode()) and
c = MkPromiseFilter()
or
any(AdditionalFlowInternal flow).expectsContent(n, c)
@@ -1035,7 +1037,10 @@ int accessPathLimit() { result = 2 }
* by default as a heuristic.
*/
predicate allowParameterReturnInSelf(ParameterNode p) {
FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p)
exists(DataFlowCallable callable, ParameterPosition pos |
isParameterNodeImpl(p, callable, pos) and
FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(callable.asLibraryCallable(), pos)
)
or
exists(Function f |
VariableCaptureOutput::heuristicAllowInstanceParameterReturnInSelf(f) and

View File

@@ -70,11 +70,6 @@ DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) {
result = TAnyType() and exists(t) and exists(rk)
}
/** Gets the type of synthetic global `sg`. */
DataFlowType getSyntheticGlobalType(SummaryComponent::SyntheticGlobal sg) {
result = TAnyType() and exists(sg)
}
/**
* Holds if an external flow summary exists for `c` with input specification
* `input`, output specification `output`, kind `kind`, and provenance `provenance`.
@@ -97,21 +92,21 @@ predicate summaryElement(
predicate neutralSummaryElement(FlowSummary::SummarizedCallable c, string provenance) { none() }
pragma[inline]
private SummaryComponent makeContentComponents(
private Private::SummaryComponent makeContentComponents(
Private::AccessPathToken token, string name, ContentSet contents
) {
token.getName() = name and
result = FlowSummary::SummaryComponent::content(contents)
result = Private::SummaryComponent::content(contents)
or
token.getName() = "With" + name and
result = FlowSummary::SummaryComponent::withContent(contents)
result = Private::SummaryComponent::withContent(contents)
or
token.getName() = "Without" + name and
result = FlowSummary::SummaryComponent::withoutContent(contents)
result = Private::SummaryComponent::withoutContent(contents)
}
pragma[inline]
private SummaryComponent makePropertyContentComponents(
private Private::SummaryComponent makePropertyContentComponents(
Private::AccessPathToken token, string name, PropertyName content
) {
result = makeContentComponents(token, name, ContentSet::property(content))
@@ -160,12 +155,12 @@ private ParameterPosition parsePosition(string operand) {
*
* This covers all the JS-specific components of a flow summary.
*/
SummaryComponent interpretComponentSpecific(Private::AccessPathToken c) {
Private::SummaryComponent interpretComponentSpecific(Private::AccessPathToken c) {
c.getName() = "Argument" and
result = FlowSummary::SummaryComponent::argument(parsePosition(c.getAnArgument()))
result = Private::SummaryComponent::argument(parsePosition(c.getAnArgument()))
or
c.getName() = "Parameter" and
result = FlowSummary::SummaryComponent::parameter(parsePosition(c.getAnArgument()))
result = Private::SummaryComponent::parameter(parsePosition(c.getAnArgument()))
or
result = makePropertyContentComponents(c, "Member", c.getAnArgument())
or
@@ -210,20 +205,20 @@ SummaryComponent interpretComponentSpecific(Private::AccessPathToken c) {
or
c.getName() = "ReturnValue" and
c.getAnArgument() = "exception" and
result = SummaryComponent::return(MkExceptionalReturnKind())
result = Private::SummaryComponent::return(MkExceptionalReturnKind())
or
// Awaited is mapped down to a combination steps that handle coercion and promise-flattening.
c.getName() = "Awaited" and
c.getNumArgument() = 0 and
result = SummaryComponent::content(MkAwaited())
result = Private::SummaryComponent::content(MkAwaited())
or
c.getName() = "AnyMemberDeep" and
c.getNumArgument() = 0 and
result = SummaryComponent::content(MkAnyPropertyDeep())
result = Private::SummaryComponent::content(MkAnyPropertyDeep())
or
c.getName() = "ArrayElementDeep" and
c.getNumArgument() = 0 and
result = SummaryComponent::content(MkArrayElementDeep())
result = Private::SummaryComponent::content(MkArrayElementDeep())
}
private string getMadStringFromContentSetAux(ContentSet cs) {
@@ -272,13 +267,14 @@ private string getMadStringFromContentSet(ContentSet cs) {
}
/** Gets the textual representation of a summary component in the format used for MaD models. */
string getMadRepresentationSpecific(SummaryComponent sc) {
string getMadRepresentationSpecific(Private::SummaryComponent sc) {
exists(ContentSet cs |
sc = Private::TContentSummaryComponent(cs) and result = getMadStringFromContentSet(cs)
sc = Private::SummaryComponent::content(cs) and
result = getMadStringFromContentSet(cs)
)
or
exists(ReturnKind rk |
sc = Private::TReturnSummaryComponent(rk) and
sc = Private::SummaryComponent::return(rk) and
not rk = getReturnValueKind() and
result = "ReturnValue[" + rk + "]"
)
@@ -368,3 +364,13 @@ bindingset[s]
ParameterPosition parseArgBody(string s) {
result = parseParamBody(s) // Currently these are identical
}
private module FlowSummaryStepInput implements Private::StepsInputSig {
DataFlowCall getACall(SummarizedCallable sc) {
exists(LibraryCallable callable | callable = sc |
result.asOrdinaryCall() = [callable.getACall(), callable.getACallSimple()]
)
}
}
module Steps = Private::Steps<FlowSummaryStepInput>;

View File

@@ -2,20 +2,21 @@ private import javascript
private import semmle.javascript.dataflow.internal.DataFlowPrivate
private import semmle.javascript.dataflow.internal.Contents::Public
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImpl as FlowSummaryImpl
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate as FlowSummaryPrivate
private import semmle.javascript.dataflow.internal.BarrierGuards
cached
predicate defaultAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
TaintTracking::AdditionalTaintStep::step(node1, node2)
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(node1.(FlowSummaryNode).getSummaryNode(),
node2.(FlowSummaryNode).getSummaryNode(), false)
FlowSummaryPrivate::Steps::summaryLocalStep(node1.(FlowSummaryNode).getSummaryNode(),
node2.(FlowSummaryNode).getSummaryNode(), false, _) // TODO: preserve 'model' parameter
or
// Convert steps into and out of array elements to plain taint steps
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(),
FlowSummaryPrivate::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(),
ContentSet::arrayElement(), node2.(FlowSummaryNode).getSummaryNode())
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(),
FlowSummaryPrivate::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(),
ContentSet::arrayElement(), node2.(FlowSummaryNode).getSummaryNode())
}

View File

@@ -1 +0,0 @@
import semmle.javascript.dataflow.internal.FlowSummaryPrivate