diff --git a/javascript/ql/lib/qlpack.yml b/javascript/ql/lib/qlpack.yml index a7baed0056b6..78f26bc98701 100644 --- a/javascript/ql/lib/qlpack.yml +++ b/javascript/ql/lib/qlpack.yml @@ -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} diff --git a/javascript/ql/lib/semmle/javascript/dataflow/FlowSummary.qll b/javascript/ql/lib/semmle/javascript/dataflow/FlowSummary.qll index 9f619a3058e6..1520ff22f88a 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/FlowSummary.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/FlowSummary.qll @@ -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 } /** diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/StepSummary.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/StepSummary.qll index 435d4d82ed57..2bcd89130a9c 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/StepSummary.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/StepSummary.qll @@ -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 @@ -29,6 +31,8 @@ private module Cached { SharedTypeTrackingStep::loadStoreStep(_, _, _, this) or this = DataFlow::PseudoProperties::arrayLikeElement() + or + this instanceof Contents::Private::PropertyName } } @@ -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) } @@ -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()) + ) } } diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/sharedlib/SummaryTypeTracker.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/sharedlib/SummaryTypeTracker.qll new file mode 100644 index 000000000000..c9acd77db1a4 --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/sharedlib/SummaryTypeTracker.qll @@ -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 diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/AmbiguousCoreMethods.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/AmbiguousCoreMethods.qll index 9c74cc7e33fe..95981a6fb95d 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/AmbiguousCoreMethods.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/AmbiguousCoreMethods.qll @@ -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" @@ -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" @@ -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" @@ -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 @@ -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) @@ -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" @@ -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" diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Arrays.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Arrays.qll index 9381ca98dd72..702936b46c3c 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Arrays.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Arrays.qll @@ -99,7 +99,7 @@ class ArrayConstructorSummary extends SummarizedCallable { result = arrayConstructorRef().getAnInvocation() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0..]" and output = "ReturnValue.ArrayElement" @@ -123,7 +123,7 @@ class Join extends SummarizedCallable { result.getNumArgument() = [0, 1] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = false and input = "Argument[this].ArrayElement" and output = "ReturnValue" @@ -135,7 +135,7 @@ class CopyWithin extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "copyWithin" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[this].WithArrayElement" and output = "ReturnValue" @@ -154,7 +154,7 @@ class FlowIntoCallback extends SummarizedCallable { result.getMethodName() = ["every", "findIndex", "findLastIndex", "some"] } - 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 @@ -171,7 +171,7 @@ class Filter extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "filter" } - 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 @@ -198,7 +198,7 @@ class Fill extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "fill" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0..]" and output = ["ReturnValue.ArrayElement", "Argument[this].ArrayElement"] @@ -210,7 +210,7 @@ class FindLike extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = ["find", "findLast"] } - 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 @@ -229,7 +229,7 @@ class FindLibrary extends SummarizedCallable { result = DataFlow::moduleImport(["array.prototype.find", "array-find"]).getACall() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0].ArrayElement" and @@ -257,7 +257,7 @@ class Flat extends SummarizedCallable { ) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[this]" + concat(int n | n in [0 .. depth] | ".ArrayElement") @@ -277,7 +277,7 @@ class FlatMap extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "flatMap" } - 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 @@ -309,7 +309,7 @@ class From1Arg extends SummarizedCallable { result = arrayFromCall() and result.getNumArgument() = 1 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0].WithArrayElement" and @@ -346,7 +346,7 @@ class FromManyArg extends SummarizedCallable { result.getNumArgument() > 1 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0]." + ["ArrayElement", "SetElement", "IteratorElement"] and @@ -380,7 +380,7 @@ class Map extends SummarizedCallable { result.getMethodName() = "map" } - 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 @@ -405,7 +405,7 @@ class Of extends SummarizedCallable { result = arrayConstructorRef().getAMemberCall("of") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0..]" and output = "ReturnValue.ArrayElement" @@ -417,7 +417,7 @@ class Pop extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "pop" } - 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" @@ -429,7 +429,7 @@ class PushLike extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = ["push", "unshift"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0..]" and output = "Argument[this].ArrayElement" @@ -441,7 +441,7 @@ class ReduceLike extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = ["reduce", "reduceRight"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and /* * Signatures: @@ -470,7 +470,7 @@ class Reverse extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = ["reverse", "toReversed"] } - 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" @@ -482,7 +482,7 @@ class Shift extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "shift" } - 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[0]" and output = "ReturnValue" @@ -500,7 +500,7 @@ class Sort extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = ["sort", "toSorted"] } - 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 @@ -517,7 +517,7 @@ class Splice extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "splice" } - 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 @@ -534,7 +534,7 @@ class ToSpliced extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "toSpliced" } - 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 @@ -551,7 +551,7 @@ class ArrayCoercionPackage extends FunctionalPackageSummary { override string getAPackageName() { result = ["arrify", "array-ify"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0].WithArrayElement" and @@ -573,7 +573,7 @@ class ArrayCopyingPackage extends FunctionalPackageSummary { override string getAPackageName() { result = ["array-union", "array-uniq", "uniq"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0..].ArrayElement" and output = "ReturnValue.ArrayElement" @@ -587,7 +587,7 @@ class ArrayFlatteningPackage extends FunctionalPackageSummary { result = ["array-flatten", "arr-flatten", "flatten", "array.prototype.flat"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // TODO: properly support these. For the moment we're just adding parity with the old model preservesValue = false and input = "Argument[0..]" and diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll index c81dadadfb64..252baab207bb 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll @@ -40,7 +40,7 @@ private class ExceptionFlowSummary extends SummarizedCallable, LibraryCallableIn isCallback(result.getAnArgument().getALocalSource()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0..].ReturnValue[exception]" and output = "ReturnValue[exception]" diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Iterators.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Iterators.qll index 94afac527873..e9937363c01d 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Iterators.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Iterators.qll @@ -16,7 +16,7 @@ class IteratorNext 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].IteratorElement" and diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/JsonStringify.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/JsonStringify.qll index 86779b8e7ecb..ecd2dcdfc79b 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/JsonStringify.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/JsonStringify.qll @@ -12,7 +12,7 @@ private class JsonStringifySummary extends SummarizedCallable { override DataFlow::InvokeNode getACall() { result instanceof JsonStringifyCall } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = false and input = ["Argument[0]", "Argument[0].AnyMemberDeep"] and output = "ReturnValue" diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Maps.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Maps.qll index c80bee19aaa7..3adc145d1a1a 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Maps.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Maps.qll @@ -15,7 +15,7 @@ class MapConstructor extends SummarizedCallable { result = mapConstructorRef().getAnInstantiation() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0]." + ["ArrayElement", "SetElement", "IteratorElement"] + ".Member[0]" and @@ -87,7 +87,7 @@ class MapGet extends SummarizedCallable { result.getNumArgument() = 1 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[this].MapValue" and output = "ReturnValue" @@ -102,7 +102,7 @@ class MapSet extends SummarizedCallable { result.getNumArgument() = 2 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = ["Argument[this].WithMapKey", "Argument[this].WithMapValue"] and output = "ReturnValue" diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll index aa9398f14bb3..33299a3f5c09 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll @@ -29,7 +29,7 @@ private class PromiseConstructor extends SummarizedCallable { none() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( // TODO: when FlowSummaryImpl.qll supports these summaries, remove the workaround in PromiseConstructorWorkaround @@ -58,7 +58,7 @@ module PromiseConstructorWorkaround { promiseConstructorRef().getAnInstantiation().getCallback(0).getParameter(0).getACall() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0]" and output = "Argument[function].Member[resolve-value]" @@ -73,7 +73,7 @@ module PromiseConstructorWorkaround { promiseConstructorRef().getAnInstantiation().getCallback(0).getParameter(1).getACall() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0]" and output = "Argument[function].Member[reject-value]" @@ -87,7 +87,7 @@ module PromiseConstructorWorkaround { result = promiseConstructorRef().getAnInstantiation() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0].Parameter[0].Member[resolve-value]" and @@ -111,7 +111,7 @@ private class PromiseThen2Arguments extends SummarizedCallable { result.getNumArgument() = 2 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0,1].ReturnValue" and output = "ReturnValue.Awaited" @@ -133,7 +133,7 @@ private class PromiseThen1Argument extends SummarizedCallable { result.getNumArgument() = 1 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0].ReturnValue" and output = "ReturnValue.Awaited" @@ -152,7 +152,7 @@ private class PromiseCatch extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "catch" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0].ReturnValue" and output = "ReturnValue.Awaited" @@ -171,7 +171,7 @@ private class PromiseFinally extends SummarizedCallable { override InstanceCall getACallSimple() { result.getMethodName() = "finally" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0].ReturnValue.Awaited[error]" and output = "ReturnValue.Awaited[error]" @@ -190,7 +190,7 @@ private class PromiseResolve extends SummarizedCallable { result = promiseConstructorRef().getAMemberCall("resolve") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0]" and output = "ReturnValue.Awaited" @@ -204,7 +204,7 @@ private class PromiseReject extends SummarizedCallable { result = promiseConstructorRef().getAMemberCall("reject") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0]" and output = "ReturnValue.Awaited[error]" @@ -218,7 +218,7 @@ private class PromiseAll extends SummarizedCallable { result = promiseConstructorRef().getAMemberCall("all") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and exists(string content | content = getAnArrayContent() | input = "Argument[0]." + content + ".Awaited" and @@ -242,7 +242,7 @@ private class PromiseAnyLike extends SummarizedCallable { result = promiseConstructorRef().getAMemberCall(["any", "race", "firstFulfilled"]) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0].ArrayElement" and output = "ReturnValue.Awaited" @@ -258,7 +258,7 @@ private class PromiseAllSettled extends SummarizedCallable { result = DataFlow::moduleImport("promise.allsettled").getACall() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and exists(string content | content = getAnArrayContent() | input = "Argument[0]." + content + ".Awaited" and @@ -277,7 +277,7 @@ private class BluebirdMapSeries extends SummarizedCallable { result = promiseConstructorRef().getAMemberCall("mapSeries") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0].Awaited.ArrayElement.Awaited" and @@ -310,7 +310,7 @@ private class PromiseWithResolversLike extends SummarizedCallable { result = promiseConstructorRef().getAMemberCall(["withResolver", "withResolvers", "defer"]) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( // TODO: not currently supported by FlowSummaryImpl.qll diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Sets.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Sets.qll index 1880eb569bf5..34f7d222df8b 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Sets.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Sets.qll @@ -15,7 +15,7 @@ class SetConstructor extends SummarizedCallable { result = setConstructorRef().getAnInstantiation() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[0]." + ["ArrayElement", "SetElement", "IteratorElement"] and @@ -38,7 +38,7 @@ class SetAdd extends SummarizedCallable { result.getNumArgument() = 1 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and input = "Argument[0]" and output = "Argument[this].SetElement" diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Strings.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Strings.qll index 9267ab598fb8..154668cde080 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Strings.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Strings.qll @@ -15,7 +15,7 @@ private class StringReplaceNoWildcard extends SummarizedCallable { override StringReplaceCall getACall() { not result.hasRegExpContainingWildcard() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = false and ( input = "Argument[this]" and @@ -39,7 +39,7 @@ private class StringReplaceWithWildcard extends SummarizedCallable { override StringReplaceCall getACall() { result.hasRegExpContainingWildcard() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = false and ( input = "Argument[this]" and @@ -60,7 +60,7 @@ class StringSplit extends SummarizedCallable { not result.getArgument(0).getStringValue() = ["#", "?"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = false and input = "Argument[this]" and output = "ReturnValue.ArrayElement" @@ -85,7 +85,7 @@ class StringSplitHashOrQuestionMark extends SummarizedCallable { result.getArgument(0).getStringValue() = ["#", "?"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = false and ( input = "Argument[this].OptionalBarrier[split-url-suffix]" and diff --git a/javascript/ql/test/library-tests/FlowSummary/test.ql b/javascript/ql/test/library-tests/FlowSummary/test.ql index 3b300bbe19b1..7d0d0db7ee2d 100644 --- a/javascript/ql/test/library-tests/FlowSummary/test.ql +++ b/javascript/ql/test/library-tests/FlowSummary/test.ql @@ -1,6 +1,6 @@ import javascript import testUtilities.ConsistencyChecking -import Summaries +import testUtilities.InlineSummaries DataFlow::CallNode getACall(string name) { result.getCalleeName() = name diff --git a/javascript/ql/test/library-tests/TypeTracking2/DependencyRestriction.expected b/javascript/ql/test/library-tests/TypeTracking2/DependencyRestriction.expected new file mode 100644 index 000000000000..e1481d55a807 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeTracking2/DependencyRestriction.expected @@ -0,0 +1 @@ +| pass | diff --git a/javascript/ql/test/library-tests/TypeTracking2/DependencyRestriction.ql b/javascript/ql/test/library-tests/TypeTracking2/DependencyRestriction.ql new file mode 100644 index 000000000000..51ed46ac6554 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeTracking2/DependencyRestriction.ql @@ -0,0 +1,19 @@ +/** + * Test that fails with a compilation error if `getACallSimple` depends on the call graph. + * To do this, we add a negative dependency from the call graph to `getACallSimple`. + */ + +import javascript +import semmle.javascript.dataflow.internal.StepSummary +import semmle.javascript.dataflow.FlowSummary + +class NegativeDependency extends DataFlow::SharedTypeTrackingStep { + override predicate step(DataFlow::Node node1, DataFlow::Node node2) { + exists(SummarizedCallable callable | + not exists(callable.getACallSimple()) and + node1 = node2 + ) + } +} + +select "pass" diff --git a/javascript/ql/test/library-tests/TypeTracking2/summaries.js b/javascript/ql/test/library-tests/TypeTracking2/summaries.js new file mode 100644 index 000000000000..1550ded19f42 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeTracking2/summaries.js @@ -0,0 +1,55 @@ +function m0() { + const x = source("m0.1"); + sink(x); // $ track=m0.1 +} + +function m1() { + const fn = mkSummary("Argument[0]", "ReturnValue"); + const obj = source("m1.1"); + sink(fn(obj)); // $ track=m1.1 + sink(fn(obj.p)); + sink(fn(obj).p); + sink(fn({ p: obj })); + sink(fn({ p: obj }).q); +} + +function m2() { + const fn = mkSummary("Argument[0].Member[p]", "ReturnValue"); + const obj = source("m2.1"); + sink(fn(obj)); + sink(fn(obj.p)); + sink(fn(obj).p); + sink(fn({ p: obj })); // $ track=m2.1 + sink(fn({ p: obj }).q); +} + +function m3() { + const fn = mkSummary("Argument[0]", "ReturnValue.Member[p]"); + const obj = source("m3.1"); + sink(fn(obj)); + sink(fn(obj.p)); + sink(fn(obj).p); // $ track=m3.1 + sink(fn({ p: obj })); + sink(fn({ p: obj }).q); +} + + +function m4() { + const fn = mkSummary("Argument[0].Member[p]", "ReturnValue.Member[q]"); + const obj = source("m4.1"); + sink(fn(obj)); + sink(fn(obj.p)); + sink(fn(obj).p); + sink(fn({ p: obj })); + sink(fn({ p: obj }).q); // $ track=m4.1 +} + +function m5() { + // Store and read to a property that isn't mentioned anywhere in the source code. + const store = mkSummary("Argument[0]", "ReturnValue.Member[propOnlyMentionedInSummary]"); + const read = mkSummary("Argument[0].Member[propOnlyMentionedInSummary]", "ReturnValue"); + sink(read(store(source("m5.1")))); // $ track=m5.1 + sink(read(source("m5.1"))); + sink(store(source("m5.1"))); + sink(store(read(source("m5.1")))); +} diff --git a/javascript/ql/test/library-tests/TypeTracking2/test.expected b/javascript/ql/test/library-tests/TypeTracking2/test.expected new file mode 100644 index 000000000000..8ec8033d086e --- /dev/null +++ b/javascript/ql/test/library-tests/TypeTracking2/test.expected @@ -0,0 +1,2 @@ +testFailures +failures diff --git a/javascript/ql/test/library-tests/TypeTracking2/test.ql b/javascript/ql/test/library-tests/TypeTracking2/test.ql new file mode 100644 index 000000000000..dcc3ff1b57b8 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeTracking2/test.ql @@ -0,0 +1,39 @@ +import javascript +import testUtilities.InlineSummaries +import testUtilities.InlineExpectationsTest + +private DataFlow::SourceNode typeTrack(DataFlow::TypeTracker t, string name) { + t.start() and + exists(DataFlow::CallNode call | + call.getCalleeName() = "source" and + name = call.getArgument(0).getStringValue() and + result = call + ) + or + exists(DataFlow::TypeTracker t2 | result = typeTrack(t2, name).track(t2, t)) +} + +DataFlow::SourceNode typeTrack(string name) { + result = typeTrack(DataFlow::TypeTracker::end(), name) +} + +module TestConfig implements TestSig { + string getARelevantTag() { result = "track" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + element = "" and + tag = "track" and + exists(DataFlow::CallNode call, DataFlow::Node arg | + call.getCalleeName() = "sink" and + arg = call.getArgument(0) and + typeTrack(value).flowsTo(arg) and + location = arg.getLocation() + ) + } + + predicate hasOptionalResult(Location location, string element, string tag, string value) { + none() + } +} + +import MakeTest diff --git a/javascript/ql/test/library-tests/FlowSummary/Summaries.qll b/javascript/ql/test/testUtilities/InlineSummaries.qll similarity index 86% rename from javascript/ql/test/library-tests/FlowSummary/Summaries.qll rename to javascript/ql/test/testUtilities/InlineSummaries.qll index e6037cb814b9..559f13609778 100644 --- a/javascript/ql/test/library-tests/FlowSummary/Summaries.qll +++ b/javascript/ql/test/testUtilities/InlineSummaries.qll @@ -11,11 +11,11 @@ class MkSummary extends SummarizedCallable { mkSummary.getLocation().getStartLine() } - override DataFlow::InvokeNode getACall() { + override DataFlow::InvokeNode getACallSimple() { result = mkSummary.flow().(DataFlow::CallNode).getAnInvocation() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( // mkSummary(input, output)