JS: Support for dynamic args to flow summaries

This commit is contained in:
Asger F
2024-08-12 14:38:19 +02:00
parent 53a2a66dd0
commit acdc896c04
7 changed files with 59 additions and 28 deletions

View File

@@ -24,6 +24,8 @@ private module ConsistencyConfig implements InputSig<Location, JSDataFlow> {
or or
n instanceof FlowSummaryIntermediateAwaitStoreNode n instanceof FlowSummaryIntermediateAwaitStoreNode
or or
n instanceof FlowSummaryDynamicParameterArrayNode
or
n instanceof GenericSynthesizedNode n instanceof GenericSynthesizedNode
or or
n = DataFlow::globalAccessPathRootPseudoNode() n = DataFlow::globalAccessPathRootPseudoNode()
@@ -43,6 +45,10 @@ private module ConsistencyConfig implements InputSig<Location, JSDataFlow> {
node instanceof TStaticArgumentArrayNode or node instanceof TStaticArgumentArrayNode or
node instanceof TDynamicArgumentArrayNode node instanceof TDynamicArgumentArrayNode
} }
predicate reverseReadExclude(DataFlow::Node node) {
node instanceof FlowSummaryDynamicParameterArrayNode
}
} }
module Consistency = MakeConsistency<Location, JSDataFlow, JSTaintFlow, ConsistencyConfig>; module Consistency = MakeConsistency<Location, JSDataFlow, JSTaintFlow, ConsistencyConfig>;

View File

@@ -80,6 +80,7 @@ private module Cached {
TConstructorThisArgumentNode(InvokeExpr e) { e instanceof NewExpr or e instanceof SuperCall } or TConstructorThisArgumentNode(InvokeExpr e) { e instanceof NewExpr or e instanceof SuperCall } or
TConstructorThisPostUpdate(Constructor ctor) or TConstructorThisPostUpdate(Constructor ctor) or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TFlowSummaryDynamicParameterArrayNode(FlowSummaryImpl::Public::SummarizedCallable callable) or
TFlowSummaryIntermediateAwaitStoreNode(FlowSummaryImpl::Private::SummaryNode sn) { TFlowSummaryIntermediateAwaitStoreNode(FlowSummaryImpl::Private::SummaryNode sn) {
// NOTE: This dependency goes through the 'Steps' module whose instantiation depends on the call graph, // 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. // but the specific predicate we're referering to does not use that information.

View File

@@ -30,6 +30,19 @@ class FlowSummaryNode extends DataFlow::Node, TFlowSummaryNode {
override string toString() { result = this.getSummaryNode().toString() } override string toString() { result = this.getSummaryNode().toString() }
} }
class FlowSummaryDynamicParameterArrayNode extends DataFlow::Node,
TFlowSummaryDynamicParameterArrayNode
{
private FlowSummaryImpl::Public::SummarizedCallable callable;
FlowSummaryDynamicParameterArrayNode() { this = TFlowSummaryDynamicParameterArrayNode(callable) }
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = callable }
cached
override string toString() { result = "[dynamic parameter array] " + callable }
}
class FlowSummaryIntermediateAwaitStoreNode extends DataFlow::Node, class FlowSummaryIntermediateAwaitStoreNode extends DataFlow::Node,
TFlowSummaryIntermediateAwaitStoreNode TFlowSummaryIntermediateAwaitStoreNode
{ {
@@ -342,6 +355,12 @@ private predicate isParameterNodeImpl(Node p, DataFlowCallable c, ParameterPosit
FlowSummaryImpl::Private::summaryParameterNode(summaryNode.getSummaryNode(), pos) and FlowSummaryImpl::Private::summaryParameterNode(summaryNode.getSummaryNode(), pos) and
c.asLibraryCallable() = summaryNode.getSummarizedCallable() c.asLibraryCallable() = summaryNode.getSummarizedCallable()
) )
or
exists(FlowSummaryImpl::Public::SummarizedCallable callable |
c.asLibraryCallable() = callable and
pos.isDynamicArgumentArray() and
p = TFlowSummaryDynamicParameterArrayNode(callable)
)
} }
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) { predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
@@ -410,6 +429,8 @@ DataFlowCallable nodeGetEnclosingCallable(Node node) {
or or
result.asLibraryCallable() = node.(FlowSummaryNode).getSummarizedCallable() result.asLibraryCallable() = node.(FlowSummaryNode).getSummarizedCallable()
or or
result.asLibraryCallable() = node.(FlowSummaryDynamicParameterArrayNode).getSummarizedCallable()
or
result.asLibraryCallable() = node.(FlowSummaryIntermediateAwaitStoreNode).getSummarizedCallable() result.asLibraryCallable() = node.(FlowSummaryIntermediateAwaitStoreNode).getSummarizedCallable()
or or
node = TGenericSynthesizedNode(_, _, result) node = TGenericSynthesizedNode(_, _, result)
@@ -1118,6 +1139,17 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
storeContent.isUnknownArrayElement() storeContent.isUnknownArrayElement()
else storeContent.asArrayIndex() = n + c.asArrayIndex() else storeContent.asArrayIndex() = n + c.asArrayIndex()
) )
or
exists(FlowSummaryNode parameter, ParameterPosition pos |
FlowSummaryImpl::Private::summaryParameterNode(parameter.getSummaryNode(), pos) and
node1 = TFlowSummaryDynamicParameterArrayNode(parameter.getSummarizedCallable()) and
node2 = parameter and
(
c.asArrayIndex() = pos.asPositional()
or
c = ContentSet::arrayElementLowerBound(pos.asPositionalLowerBound())
)
)
} }
/** Gets the post-update node for which `node` is the corresponding pre-update node. */ /** Gets the post-update node for which `node` is the corresponding pre-update node. */

View File

@@ -35,8 +35,6 @@ private predicate positionName(ParameterPosition pos, string operand) {
or or
pos.isFunctionSelfReference() and operand = "function" pos.isFunctionSelfReference() and operand = "function"
or or
pos.isDynamicArgumentArray() and operand = "arguments-array" // TODO: remove and handle automatically
or
operand = pos.asPositionalLowerBound() + ".." operand = pos.asPositionalLowerBound() + ".."
} }

View File

@@ -101,17 +101,11 @@ class ArrayConstructorSummary extends SummarizedCallable {
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and preservesValue = true and
(
input = "Argument[0..]" and input = "Argument[0..]" and
output = "ReturnValue.ArrayElement" output = "ReturnValue.ArrayElement"
or or
input = "Argument[arguments-array].WithArrayElement" and
output = "ReturnValue"
)
or
// TODO: workaround for WithArrayElement not being converted to a taint step
preservesValue = false and preservesValue = false and
input = "Argument[arguments-array]" and input = "Argument[0..]" and
output = "ReturnValue" output = "ReturnValue"
} }
} }
@@ -437,8 +431,7 @@ class PushLike extends SummarizedCallable {
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and preservesValue = true and
// TODO: make it so `arguments-array` is handled without needing to reference it explicitly in every flow-summary input = "Argument[0..]" and
input = ["Argument[0..]", "Argument[arguments-array].ArrayElement"] and
output = "Argument[this].ArrayElement" output = "Argument[this].ArrayElement"
} }
} }

View File

@@ -1,7 +0,0 @@
| library-tests/FlowSummary/tst.js:278 | expected an alert, but found none | NOT OK | ConsistencyConfig |
| library-tests/FlowSummary/tst.js:282 | expected an alert, but found none | NOT OK | ConsistencyConfig |
| library-tests/FlowSummary/tst.js:283 | expected an alert, but found none | NOT OK | ConsistencyConfig |
| library-tests/FlowSummary/tst.js:286 | expected an alert, but found none | NOT OK | ConsistencyConfig |
| library-tests/FlowSummary/tst.js:287 | expected an alert, but found none | NOT OK | ConsistencyConfig |
| library-tests/FlowSummary/tst.js:290 | expected an alert, but found none | NOT OK | ConsistencyConfig |
| library-tests/FlowSummary/tst.js:291 | expected an alert, but found none | NOT OK | ConsistencyConfig |

View File

@@ -275,19 +275,27 @@ function m18() {
const dynamicParam0 = mkSummary("Argument[0..]", "ReturnValue"); const dynamicParam0 = mkSummary("Argument[0..]", "ReturnValue");
const dynamicParam1 = mkSummary("Argument[1..]", "ReturnValue"); const dynamicParam1 = mkSummary("Argument[1..]", "ReturnValue");
sink(staticParam0(...source())); // NOT OK sink(staticParam0(...[source()])); // NOT OK
sink(staticParam0("safe", ...source())); // OK sink(staticParam0(...["safe", source()])); // OK
sink(staticParam0(...[source(), "safe", ])); // NOT OK
sink(staticParam0("safe", ...[source()])); // OK
sink(staticParam0(source(), ...["safe"])); // NOT OK sink(staticParam0(source(), ...["safe"])); // NOT OK
sink(staticParam1(...source())); // NOT OK sink(staticParam1(...[source()])); // OK
sink(staticParam1("safe", ...source())); // NOT OK sink(staticParam1(...["safe", source()])); // NOT OK
sink(staticParam1(...[source(), "safe", ])); // OK
sink(staticParam1("safe", ...[source()])); // NOT OK
sink(staticParam1(source(), ...["safe"])); // OK sink(staticParam1(source(), ...["safe"])); // OK
sink(dynamicParam0(...source())); // NOT OK sink(dynamicParam0(...[source()])); // NOT OK
sink(dynamicParam0("safe", ...source())); // NOT OK sink(dynamicParam0(...["safe", source()])); // NOT OK
sink(dynamicParam0(...[source(), "safe", ])); // NOT OK
sink(dynamicParam0("safe", ...[source()])); // NOT OK
sink(dynamicParam0(source(), ...["safe"])); // NOT OK sink(dynamicParam0(source(), ...["safe"])); // NOT OK
sink(dynamicParam1(...source())); // NOT OK sink(dynamicParam1(...[source()])); // OK
sink(dynamicParam1("safe", ...source())); // NOT OK sink(dynamicParam1(...["safe", source()])); // NOT OK
sink(dynamicParam1(...[source(), "safe", ])); // OK
sink(dynamicParam1("safe", ...[source()])); // NOT OK
sink(dynamicParam1(source(), ...["safe"])); // OK sink(dynamicParam1(source(), ...["safe"])); // OK
} }