Skip to content

JS: Derive type-tracking steps from flow summaries #18125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
1 change: 1 addition & 0 deletions javascript/ql/lib/qlpack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies:
codeql/ssa: ${workspace}
codeql/threat-models: ${workspace}
codeql/tutorial: ${workspace}
codeql/typetracking: ${workspace}
codeql/util: ${workspace}
codeql/xml: ${workspace}
codeql/yaml: ${workspace}
Expand Down
5 changes: 2 additions & 3 deletions javascript/ql/lib/semmle/javascript/dataflow/FlowSummary.qll
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@ abstract class SummarizedCallable extends LibraryCallable, Impl::Public::Summari
bindingset[this]
SummarizedCallable() { any() }

// TODO: rename 'propagatesFlowExt' and/or override 'propagatesFlow' directly
/**
* Holds if data may flow from `input` to `output` through this callable.
*
* `preservesValue` indicates whether this is a value-preserving step or a taint-step.
*/
pragma[nomagic]
predicate propagatesFlowExt(string input, string output, boolean preservesValue) { none() }
predicate propagatesFlow(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
this.propagatesFlow(input, output, preservesValue) and model = this
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import javascript
private import semmle.javascript.dataflow.TypeTracking
private import semmle.javascript.internal.CachedStages
private import semmle.javascript.dataflow.internal.Contents as Contents
private import sharedlib.SummaryTypeTracker as SummaryTypeTracker
private import FlowSteps

cached
Expand Down Expand Up @@ -29,6 +31,8 @@ private module Cached {
SharedTypeTrackingStep::loadStoreStep(_, _, _, this)
or
this = DataFlow::PseudoProperties::arrayLikeElement()
or
this instanceof Contents::Private::PropertyName
}
}

Expand All @@ -46,6 +50,12 @@ private module Cached {
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
SharedTypeTrackingStep::loadStoreStep(_, _, fromProp, toProp)
or
exists(DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent |
SummaryTypeTracker::basicLoadStoreStep(_, _, loadContent, storeContent) and
fromProp = loadContent.asPropertyName() and
toProp = storeContent.asPropertyName()
)
or
summarizedLoadStoreStep(_, _, fromProp, toProp)
} or
WithoutPropStep(PropertySet props) { SharedTypeTrackingStep::withoutPropStep(_, _, props) }
Expand Down Expand Up @@ -205,6 +215,21 @@ private module Cached {
succ = getACallbackSource(parameter).getParameter(i) and
summary = ReturnStep()
)
or
SummaryTypeTracker::levelStepNoCall(pred, succ) and summary = LevelStep()
or
exists(DataFlow::ContentSet content |
SummaryTypeTracker::basicLoadStep(pred, succ, content) and
summary = LoadStep(content.asPropertyName())
or
SummaryTypeTracker::basicStoreStep(pred, succ, content) and
summary = StoreStep(content.asPropertyName())
)
or
exists(DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent |
SummaryTypeTracker::basicLoadStoreStep(pred, succ, loadContent, storeContent) and
summary = LoadStoreStep(loadContent.asPropertyName(), storeContent.asPropertyName())
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
private import semmle.javascript.Locations
private import codeql.typetracking.internal.SummaryTypeTracker
private import semmle.javascript.dataflow.internal.DataFlowPrivate as DataFlowPrivate
private import semmle.javascript.dataflow.FlowSummary as FlowSummary
private import FlowSummaryImpl as FlowSummaryImpl
private import DataFlowArg

private module SummaryFlowConfig implements Input {
import JSDataFlow
import FlowSummaryImpl::Public
import FlowSummaryImpl::Private
import FlowSummaryImpl::Private::SummaryComponent

class Content = DataFlow::ContentSet;

class ContentFilter extends Unit {
ContentFilter() { none() }
}

ContentFilter getFilterFromWithoutContentStep(Content content) { none() }

ContentFilter getFilterFromWithContentStep(Content content) { none() }

predicate singleton = SummaryComponentStack::singleton/1;

predicate push = SummaryComponentStack::push/2;

SummaryComponent return() {
result = SummaryComponent::return(DataFlowPrivate::MkNormalReturnKind())
}

Node argumentOf(Node call, SummaryComponent arg, boolean isPostUpdate) {
// Note: we cannot rely on DataFlowPrivate::DataFlowCall here because that depends on the call graph.
exists(ArgumentPosition apos, ParameterPosition ppos, Node argNode |
arg = argument(ppos) and
parameterMatch(ppos, apos) and
(
argNode = call.(DataFlow::InvokeNode).getArgument(apos.asPositional())
or
apos.isThis() and
argNode = call.(DataFlow::CallNode).getReceiver()
)
|
isPostUpdate = true and result = argNode.getPostUpdateNode()
or
isPostUpdate = false and result = argNode
)
}

Node parameterOf(Node callable, SummaryComponent param) {
exists(ArgumentPosition apos, ParameterPosition ppos, DataFlow::FunctionNode function |
param = parameter(apos) and
parameterMatch(ppos, apos) and
callable = function
|
result = function.getParameter(ppos.asPositional())
or
ppos.isThis() and
result = function.getReceiver()
)
}

Node returnOf(Node callable, SummaryComponent return) {
return = return() and
result = callable.(DataFlow::FunctionNode).getReturnNode()
}

class SummarizedCallable instanceof SummarizedCallableImpl {
predicate propagatesFlow(
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
) {
super.propagatesFlow(input, output, preservesValue, _)
}

string toString() { result = super.toString() }
}

Node callTo(SummarizedCallable callable) {
result = callable.(FlowSummary::SummarizedCallable).getACallSimple()
}
}

import SummaryFlow<SummaryFlowConfig>
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class At extends SummarizedCallable {

override InstanceCall getACallSimple() { result.getMethodName() = "at" }

override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this].ArrayElement" and
output = "ReturnValue"
Expand All @@ -45,7 +45,7 @@ class Concat extends SummarizedCallable {

override InstanceCall getACallSimple() { result.getMethodName() = "concat" }

override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this,0..].ArrayElement" and
output = "ReturnValue.ArrayElement"
Expand All @@ -61,7 +61,7 @@ class Slice extends SummarizedCallable {

override InstanceCall getACallSimple() { result.getMethodName() = "slice" }

override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this].ArrayElement" and
output = "ReturnValue.ArrayElement"
Expand All @@ -80,7 +80,7 @@ class Entries extends SummarizedCallable {
result.getNumArgument() = 0
}

override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
(
input = "Argument[this]." + ["MapKey", "SetElement"] and
Expand All @@ -97,7 +97,7 @@ class ForEach extends SummarizedCallable {

override InstanceCall getACallSimple() { result.getMethodName() = "forEach" }

override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
/*
* array.forEach(callbackfn, thisArg)
Expand Down Expand Up @@ -128,7 +128,7 @@ class Keys extends SummarizedCallable {
result.getNumArgument() = 0
}

override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this]." + ["MapKey", "SetElement"] and
output = "ReturnValue.IteratorElement"
Expand All @@ -143,7 +143,7 @@ class Values extends SummarizedCallable {
result.getNumArgument() = 0
}

override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this]." + ["ArrayElement", "SetElement", "MapValue"] and
output = "ReturnValue.IteratorElement"
Expand Down
Loading
Loading