diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll index 2f6c1062768..649d08b8016 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll @@ -14,6 +14,11 @@ private module Cached { ReturnStep() or StoreStep(TypeTrackerContent content) { basicStoreStep(_, _, content) } or LoadStep(TypeTrackerContent content) { basicLoadStep(_, _, content) } or + LoadStoreStep(TypeTrackerContent load, TypeTrackerContent store) { + basicLoadStoreStep(_, _, load, store) + } or + WithContent(ContentFilter filter) { basicWithContentStep(_, _, filter) } or + WithoutContent(ContentFilter filter) { basicWithoutContentStep(_, _, filter) } or JumpStep() cached @@ -61,6 +66,14 @@ private module Cached { or step = JumpStep() and result = MkTypeTracker(false, currentContents) + or + exists(ContentFilter filter | result = tt | + step = WithContent(filter) and + currentContents = filter.getAMatchingContent() + or + step = WithoutContent(filter) and + not currentContents = filter.getAMatchingContent() + ) ) or exists(TypeTrackerContent storeContents, boolean hasCall | @@ -75,6 +88,16 @@ private module Cached { tt = noContentTypeTracker(hasCall) and result = MkTypeTracker(hasCall, storeContents) ) + or + exists( + TypeTrackerContent currentContent, TypeTrackerContent store, TypeTrackerContent load, + boolean hasCall + | + step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and + compatibleContents(pragma[only_bind_into](currentContent), load) and + tt = MkTypeTracker(pragma[only_bind_into](hasCall), currentContent) and + result = MkTypeTracker(pragma[only_bind_out](hasCall), store) + ) } pragma[nomagic] @@ -96,6 +119,14 @@ private module Cached { or step = JumpStep() and result = MkTypeBackTracker(false, content) + or + exists(ContentFilter filter | result = tbt | + step = WithContent(filter) and + content = filter.getAMatchingContent() + or + step = WithoutContent(filter) and + not content = filter.getAMatchingContent() + ) ) or exists(TypeTrackerContent loadContents, boolean hasReturn | @@ -110,6 +141,16 @@ private module Cached { tbt = noContentTypeBackTracker(hasReturn) and result = MkTypeBackTracker(hasReturn, loadContents) ) + or + exists( + TypeTrackerContent currentContent, TypeTrackerContent store, TypeTrackerContent load, + boolean hasCall + | + step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and + compatibleContents(store, pragma[only_bind_into](currentContent)) and + tbt = MkTypeBackTracker(pragma[only_bind_into](hasCall), currentContent) and + result = MkTypeBackTracker(pragma[only_bind_out](hasCall), load) + ) } /** @@ -146,6 +187,19 @@ private module Cached { or basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content) ) + or + exists(TypeTrackerContent loadContent, TypeTrackerContent storeContent | + flowsToLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent) and + summary = LoadStoreStep(loadContent, storeContent) + ) + or + exists(ContentFilter filter | + basicWithContentStep(nodeFrom, nodeTo, filter) and + summary = WithContent(filter) + or + basicWithoutContentStep(nodeFrom, nodeTo, filter) and + summary = WithoutContent(filter) + ) } cached @@ -190,6 +244,18 @@ private predicate flowsToStoreStep( exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content)) } +/** + * Holds if `loadContent` is loaded from `nodeFrom` and written to `storeContent` of `nodeTo`. + */ +predicate flowsToLoadStoreStep( + Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContent loadContent, + TypeTrackerContent storeContent +) { + exists(Node obj | + nodeTo.flowsTo(obj) and basicLoadStoreStep(nodeFrom, obj, loadContent, storeContent) + ) +} + /** * INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead. * @@ -208,6 +274,11 @@ class StepSummary extends TStepSummary { or exists(TypeTrackerContent content | this = LoadStep(content) | result = "load " + content) or + exists(TypeTrackerContent load, TypeTrackerContent store | + this = LoadStoreStep(load, store) and + result = "load-store " + load + " -> " + store + ) + or this instanceof JumpStep and result = "jump" } } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll index ecf7e4ccca1..05ad3cbdd7d 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll @@ -28,6 +28,14 @@ class TypeTrackerContent extends OptionalTypeTrackerContent { /** Gets the content string representing no value. */ OptionalTypeTrackerContent noContent() { result = "" } +/** + * A label to use for `WithContent` and `WithoutContent` steps, restricting + * which `ContentSet` may pass through. Not currently used in Python. + */ +class ContentFilter extends Unit { + TypeTrackerContent getAMatchingContent() { none() } +} + pragma[inline] predicate compatibleContents(TypeTrackerContent storeContent, TypeTrackerContent loadContent) { storeContent = loadContent @@ -110,6 +118,23 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, string content) { ) } +/** + * Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`. + */ +predicate basicLoadStoreStep(Node nodeFrom, Node nodeTo, string loadContent, string storeContent) { + none() +} + +/** + * Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here. + */ +predicate basicWithoutContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) { none() } + +/** + * Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a content matched by `filter`. + */ +predicate basicWithContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) { none() } + /** * A utility class that is equivalent to `boolean` but does not require type joining. */