Merge pull request #20961 from aschackmull/dataflow/flowfrom

Dataflow: Add flowFrom predicates to mirror flowTo.
This commit is contained in:
Anders Schack-Mulligen
2025-12-04 10:09:29 +01:00
committed by GitHub
42 changed files with 77 additions and 53 deletions

View File

@@ -10,7 +10,7 @@ import ExternalAPIsSpecific
/** A node representing untrusted data being passed to an external API. */
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flow(_, this) }
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flowTo(this) }
/** Gets a source of untrusted data which is passed to this external API data node. */
DataFlow::Node getAnUntrustedSource() { UntrustedDataToExternalApiFlow::flow(result, this) }

View File

@@ -10,7 +10,7 @@ import ExternalAPIsSpecific
/** A node representing untrusted data being passed to an external API. */
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flow(_, this) }
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flowTo(this) }
/** Gets a source of untrusted data which is passed to this external API data node. */
DataFlow::Node getAnUntrustedSource() { UntrustedDataToExternalApiFlow::flow(result, this) }

View File

@@ -263,7 +263,7 @@ module FromSensitiveFlow = TaintTracking::Global<FromSensitiveConfig>;
* A taint flow configuration for flow from a sensitive expression to an encryption operation.
*/
module ToEncryptionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { FromSensitiveFlow::flow(source, _) }
predicate isSource(DataFlow::Node source) { FromSensitiveFlow::flowFrom(source) }
predicate isSink(DataFlow::Node sink) { isSinkEncrypt(sink, _) }
@@ -311,7 +311,7 @@ where
FromSensitiveFlow::flowPath(source, sink) and
isSinkSendRecv(sink.getNode(), networkSendRecv) and
// no flow from sensitive -> evidence of encryption
not ToEncryptionFlow::flow(source.getNode(), _) and
not ToEncryptionFlow::flowFrom(source.getNode()) and
not FromEncryptionFlow::flowTo(sink.getNode()) and
// construct result
if networkSendRecv instanceof NetworkSend

View File

@@ -129,7 +129,7 @@ module PointerArithmeticToDerefFlow = DataFlow::Global<PointerArithmeticToDerefC
predicate pointerArithOverflow(PointerArithmeticInstruction pai, int delta) {
pointerArithOverflow0(pai, delta) and
PointerArithmeticToDerefFlow::flow(DataFlow::instructionNode(pai), _)
PointerArithmeticToDerefFlow::flowFrom(DataFlow::instructionNode(pai))
}
bindingset[v]

View File

@@ -52,7 +52,7 @@ class IDbCommandConstructionSqlExpr extends SqlExpr, ObjectCreation {
class DapperCommandDefinitionMethodCallSqlExpr extends SqlExpr, ObjectCreation {
DapperCommandDefinitionMethodCallSqlExpr() {
this.getObjectType() instanceof Dapper::CommandDefinitionStruct and
DapperCommandDefinitionMethodCallSql::flow(DataFlow::exprNode(this), _)
DapperCommandDefinitionMethodCallSql::flowFromExpr(this)
}
override Expr getSql() { result = this.getArgumentForName("commandText") }

View File

@@ -85,7 +85,7 @@ module RemoteSourceToExternalApi = TaintTracking::Global<RemoteSourceToExternalA
/** A node representing untrusted data being passed to an external API. */
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
UntrustedExternalApiDataNode() { RemoteSourceToExternalApi::flow(_, this) }
UntrustedExternalApiDataNode() { RemoteSourceToExternalApi::flowTo(this) }
/** Gets a source of untrusted data which is passed to this external API data node. */
DataFlow::Node getAnUntrustedSource() { RemoteSourceToExternalApi::flow(result, this) }

View File

@@ -91,7 +91,7 @@ class ExponentialRegexSink extends DataFlow::ExprNode, Sink {
ExponentialRegexSink() {
exists(RegexOperation regexOperation |
// Exponential regex flows to the pattern argument
ExponentialRegexDataFlow::flow(_, DataFlow::exprNode(regexOperation.getPattern()))
ExponentialRegexDataFlow::flowToExpr(regexOperation.getPattern())
|
// This is used as an input for this pattern
this.getExpr() = regexOperation.getInput() and

View File

@@ -53,7 +53,7 @@ where
// JsonConvert static method call, but with additional unsafe typename tracking
exists(DataFlow::Node settingsCallArg |
JsonConvertTracking::flowPath(userInput.asPathNode3(), deserializeCallArg.asPathNode3()) and
TypeNameTracking::flow(_, settingsCallArg) and
TypeNameTracking::flowTo(settingsCallArg) and
sameParent(deserializeCallArg.getNode(), settingsCallArg)
)
select deserializeCallArg, userInput, deserializeCallArg, "$@ flows to unsafe deserializer.",

View File

@@ -46,10 +46,7 @@ predicate insecureCookieOptionsCreation(ObjectCreation oc) {
// `Secure` property in `CookieOptions` passed to IResponseCookies.Append(...) wasn't set
oc.getType() instanceof MicrosoftAspNetCoreHttpCookieOptions and
secureFalseOrNotSet(oc) and
exists(DataFlow::Node creation |
CookieOptionsTracking::flow(creation, _) and
creation.asExpr() = oc
)
CookieOptionsTracking::flowFromExpr(oc)
}
predicate insecureCookieAppend(Expr sink) {

View File

@@ -27,7 +27,7 @@ module AllocationSizeOverflow {
private module FindLargeLensFlow = TaintTracking::Global<FindLargeLensConfig>;
private DataFlow::CallNode getALargeLenCall() {
exists(DataFlow::Node lenArg | FindLargeLensFlow::flow(_, lenArg) |
exists(DataFlow::Node lenArg | FindLargeLensFlow::flowTo(lenArg) |
result.getArgument(0) = lenArg
)
}

View File

@@ -211,7 +211,7 @@ module UntrustedDataToUnknownExternalApiFlow =
/** A node representing untrusted data being passed to an external API. */
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flow(_, this) }
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flowTo(this) }
/** Gets a source of untrusted data which is passed to this external API data node. */
DataFlow::Node getAnUntrustedSource() { UntrustedDataToExternalApiFlow::flow(result, this) }

View File

@@ -15,7 +15,7 @@ module MissingJwtSignatureCheck {
module Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source instanceof Source and
not SafeParse::flow(source, _)
not SafeParse::flowFrom(source)
}
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }

View File

@@ -32,7 +32,7 @@ module UnsafeUnzipSymlink {
* Holds if `node` is an archive header field read that flows to a `path/filepath.EvalSymlinks` call.
*/
private predicate symlinksEvald(DataFlow::Node node) {
EvalSymlinksFlow::flow(getASimilarReadNode(node), _)
EvalSymlinksFlow::flowFrom(getASimilarReadNode(node))
}
private module Config implements DataFlow::ConfigSig {

View File

@@ -81,5 +81,5 @@ module Config implements DataFlow::ConfigSig {
module Flow = DataFlow::Global<Config>;
from DataFlow::Node source, string msg
where Flow::flow(source, _) and Config::isSourceString(source, msg)
where Flow::flowFrom(source) and Config::isSourceString(source, msg)
select source, msg

View File

@@ -154,7 +154,7 @@ module FlowToPrintFlow = DataFlow::Global<FlowToPrintConfig>;
/** Holds if the provided `CallNode`'s result flows to an argument of a printer call. */
predicate resultFlowsToPrinter(DataFlow::CallNode authCodeUrlCall) {
FlowToPrintFlow::flow(authCodeUrlCall.getResult(), _)
FlowToPrintFlow::flowFrom(authCodeUrlCall.getResult())
}
/** Get a data-flow node that reads the value of `os.Stdin`. */

View File

@@ -21,6 +21,6 @@ where
OpenUrlRedirect::Flow::flowPath(source, sink) and
// this excludes flow from safe parts of request URLs, for example the full URL when the
// doing a redirect from `http://<path>` to `https://<path>`
not SafeUrlFlow::Flow::flow(_, sink.getNode())
not SafeUrlFlow::Flow::flowTo(sink.getNode())
select sink.getNode(), source, sink, "This path to an untrusted URL redirection depends on a $@.",
source.getNode(), "user-provided value"

View File

@@ -21,6 +21,6 @@ where
RequestForgery::Flow::flowPath(source, sink) and
request = sink.getNode().(RequestForgery::Sink).getARequest() and
// this excludes flow from safe parts of request URLs, for example the full URL
not SafeUrlFlow::Flow::flow(_, sink.getNode())
not SafeUrlFlow::Flow::flowTo(sink.getNode())
select request, source, sink, "The $@ of this request depends on a $@.", sink.getNode(),
sink.getNode().(RequestForgery::Sink).getKind(), source, "user-provided value"

View File

@@ -70,5 +70,6 @@ module PamStartToAuthenticateFlow = TaintTracking::Global<PamStartToAuthenticate
from DataFlow::Node source, DataFlow::Node sink
where
not isInTestFile(source.asExpr()) and
(PamStartToAuthenticateFlow::flow(source, sink) and not PamStartToAcctMgmtFlow::flow(source, _))
PamStartToAuthenticateFlow::flow(source, sink) and
not PamStartToAcctMgmtFlow::flowFrom(source)
select source, "This Pam transaction may not be secure."

View File

@@ -24,7 +24,7 @@ module JwtParseWithConstantKeyConfig implements DataFlow::ConfigSig {
or
n = fd.(FuncDecl).getFunction().getARead()
|
GolangJwtKeyFunc::flow(n, _) and
GolangJwtKeyFunc::flowFrom(n) and
sink = rn and
rn.getRoot() = fd and
rn.getIndex() = 0

View File

@@ -26,9 +26,7 @@ private module TypeLiteralToParseAsFlowConfig implements DataFlow::ConfigSig {
private module TypeLiteralToParseAsFlow = DataFlow::Global<TypeLiteralToParseAsFlowConfig>;
private TypeLiteral getSourceWithFlowToParseAs() {
TypeLiteralToParseAsFlow::flow(DataFlow::exprNode(result), _)
}
private TypeLiteral getSourceWithFlowToParseAs() { TypeLiteralToParseAsFlow::flowFromExpr(result) }
/** A field that is deserialized by `HttpResponse.parseAs`. */
class HttpResponseParseAsDeserializableField extends DeserializableField {

View File

@@ -110,7 +110,7 @@ private module TypeLiteralToJacksonDatabindFlow =
DataFlow::Global<TypeLiteralToJacksonDatabindFlowConfig>;
private TypeLiteral getSourceWithFlowToJacksonDatabind() {
TypeLiteralToJacksonDatabindFlow::flow(DataFlow::exprNode(result), _)
TypeLiteralToJacksonDatabindFlow::flowFromExpr(result)
}
/** A type whose values are explicitly deserialized in a call to a Jackson method. */

View File

@@ -164,7 +164,7 @@ private module RegexFlowConfig implements DataFlow::ConfigSig {
private module RegexFlow = DataFlow::Global<RegexFlowConfig>;
private predicate usedAsRegexImpl(StringLiteral regex, string mode, boolean match_full_string) {
RegexFlow::flow(DataFlow::exprNode(regex), _) and
RegexFlow::flowFromExpr(regex) and
mode = "None" and // TODO: proper mode detection
(if matchesFullString(regex) then match_full_string = true else match_full_string = false)
}

View File

@@ -51,7 +51,7 @@ private module VerifiedIntentFlow = DataFlow::Global<VerifiedIntentConfig>;
/** An `onReceive` method that doesn't verify the action of the intent it receives. */
private class UnverifiedOnReceiveMethod extends OnReceiveMethod {
UnverifiedOnReceiveMethod() {
not VerifiedIntentFlow::flow(DataFlow::parameterNode(this.getIntentParameter()), _) and
not VerifiedIntentFlow::flowFrom(DataFlow::parameterNode(this.getIntentParameter())) and
// Empty methods do not need to be verified since they do not perform any actions.
this.getBody().getNumStmt() > 0
}

View File

@@ -118,7 +118,7 @@ where
// implicit: no setAllowContentAccess(false)
exists(WebViewSource source |
source.asExpr() = e and
not WebViewDisallowContentAccessFlow::flow(source, _)
not WebViewDisallowContentAccessFlow::flowFrom(source)
)
select e,
"Sensitive information may be exposed via a malicious link due to access to content:// links being allowed in this WebView."

View File

@@ -85,7 +85,7 @@ private module JxBrowserFlow = DataFlow::Global<JxBrowserFlowConfig>;
deprecated query predicate problems(DataFlow::Node src, string message) {
JxBrowserFlowConfig::isSource(src) and
not JxBrowserFlow::flow(src, _) and
not JxBrowserFlow::flowFrom(src) and
not isSafeJxBrowserVersion() and
message = "This JxBrowser instance may not check HTTPS certificates."
}

View File

@@ -50,7 +50,7 @@ private Expr getAccessControlAllowOriginHeaderName() {
* A taint-tracking configuration for flow from a source node to CorsProbableCheckAccess methods.
*/
module CorsSourceReachesCheckConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { CorsOriginFlow::flow(source, _) }
predicate isSource(DataFlow::Node source) { CorsOriginFlow::flowFrom(source) }
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(CorsProbableCheckAccess check).getAnArgument()
@@ -86,7 +86,7 @@ deprecated query predicate problems(
string message1, DataFlow::Node sourceNode, string message2
) {
CorsOriginFlow::flowPath(source, sink) and
not CorsSourceReachesCheckFlow::flow(sourceNode, _) and
not CorsSourceReachesCheckFlow::flowFrom(sourceNode) and
sinkNode = sink.getNode() and
message1 = "CORS header is being set using user controlled value $@." and
sourceNode = source.getNode() and

View File

@@ -17,7 +17,7 @@ deprecated import JwtAuth0 as JwtAuth0
deprecated module JwtDecodeConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource and
not FlowToJwtVerify::flow(source, _)
not FlowToJwtVerify::flowFrom(source)
}
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(JwtAuth0::GetPayload a) }

View File

@@ -25,9 +25,7 @@ module Config implements DataFlow::ConfigSig {
module Flow = DataFlow::Global<Config>;
predicate isSunk(StringLiteral sl) {
exists(DataFlow::Node source | Flow::flow(source, _) and sl = source.asExpr())
}
predicate isSunk(StringLiteral sl) { Flow::flowFromExpr(sl) }
query predicate shouldBeSunkButIsnt(ShouldBeSunk src) { not isSunk(src) }

View File

@@ -25,9 +25,7 @@ module Config implements DataFlow::ConfigSig {
module Flow = DataFlow::Global<Config>;
predicate isSunk(StringLiteral sl) {
exists(DataFlow::Node source | Flow::flow(source, _) and sl = source.asExpr())
}
predicate isSunk(StringLiteral sl) { Flow::flowFromExpr(sl) }
query predicate shouldBeSunkButIsnt(ShouldBeSunk src) { not isSunk(src) }

View File

@@ -20,7 +20,7 @@ module FlowStepTest implements TestSig {
predicate hasActualResult(Location l, string element, string tag, string value) {
tag = "taintReachesReturn" and
value = "" and
exists(DataFlow::Node source | Flow::flow(source, _) |
exists(DataFlow::Node source | Flow::flowFrom(source) |
l = source.getLocation() and
element = source.toString()
)

View File

@@ -20,7 +20,7 @@ module SinkTest implements TestSig {
predicate hasActualResult(Location l, string element, string tag, string value) {
tag = "taintReachesSink" and
value = "" and
exists(DataFlow::Node source | Flow::flow(source, _) |
exists(DataFlow::Node source | Flow::flowFrom(source) |
l = source.getLocation() and
element = source.toString()
)

View File

@@ -97,7 +97,7 @@ class ExternalApiDataNode extends DataFlow::Node instanceof Sink { }
/** A node representing untrusted data being passed to an external API. */
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
UntrustedExternalApiDataNode() { ExternalAPIUsedWithUntrustedDataFlow::flow(_, this) }
UntrustedExternalApiDataNode() { ExternalAPIUsedWithUntrustedDataFlow::flowTo(this) }
/** Gets a source of untrusted data which is passed to this external API data node. */
DataFlow::Node getAnUntrustedSource() { ExternalAPIUsedWithUntrustedDataFlow::flow(result, this) }
@@ -110,7 +110,7 @@ private newtype TExternalApi =
/** An external API sink with `name`. */
MkExternalApiNode(string name) {
exists(Sink sink |
ExternalAPIUsedWithUntrustedDataFlow::flow(_, sink) and
ExternalAPIUsedWithUntrustedDataFlow::flowTo(sink) and
name = sink.getApiName()
)
}

View File

@@ -36,6 +36,6 @@ import UnverifiedDecodeFlow::PathGraph
from UnverifiedDecodeFlow::PathNode source, UnverifiedDecodeFlow::PathNode sink
where
UnverifiedDecodeFlow::flowPath(source, sink) and
not VerifiedDecodeFlow::flow(source.getNode(), _)
not VerifiedDecodeFlow::flowFrom(source.getNode())
select source.getNode(), source, sink, "Decoding JWT $@.", sink.getNode(),
"without signature verification"

View File

@@ -32,5 +32,5 @@ class BasicBarrierGuard extends DataFlow::CallNode {
deprecated class ConsistencyConfig extends ConsistencyConfiguration {
ConsistencyConfig() { this = "ConsistencyConfig" }
override DataFlow::Node getAnAlert() { Flow::flow(_, result) }
override DataFlow::Node getAnAlert() { Flow::flowTo(result) }
}

View File

@@ -14,7 +14,7 @@ predicate passwordVarAssign(Variable v, DataFlow::Node nd) {
module PasswordFlow = DataFlow::Global<PasswordConfig>;
query predicate test_query17(DataFlow::Node sink, string res) {
exists(Variable v | PasswordFlow::flow(_, sink) and passwordVarAssign(v, sink) |
exists(Variable v | PasswordFlow::flowTo(sink) and passwordVarAssign(v, sink) |
res = "Password variable " + v.toString() + " is assigned a constant string."
)
}

View File

@@ -50,7 +50,7 @@ module FullServerSideRequestForgeryFlow = TaintTracking::Global<FullServerSideRe
*/
predicate fullyControlledRequest(Http::Client::Request request) {
forall(DataFlow::Node urlPart | urlPart = request.getAUrlPart() |
FullServerSideRequestForgeryFlow::flow(_, urlPart)
FullServerSideRequestForgeryFlow::flowTo(urlPart)
)
}

View File

@@ -182,7 +182,7 @@ module UntrustedDataToExternalApiFlow = TaintTracking::Global<UntrustedDataToExt
/** A node representing untrusted data being passed to an external API. */
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flow(_, this) }
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flowTo(this) }
/** Gets a source of untrusted data which is passed to this external API data node. */
DataFlow::Node getAnUntrustedSource() { UntrustedDataToExternalApiFlow::flow(result, this) }

View File

@@ -38,5 +38,5 @@ module RemoteFlowSourceReachConfig implements DataFlow::ConfigSig {
module RemoteFlowSourceReachFlow = TaintTracking::Global<RemoteFlowSourceReachConfig>;
from DataFlow::Node reachable
where RemoteFlowSourceReachFlow::flow(_, reachable)
where RemoteFlowSourceReachFlow::flowTo(reachable)
select reachable, prettyNode(reachable)

View File

@@ -28,5 +28,5 @@ private module BasicTaintConfig implements DataFlow::ConfigSig {
private module BasicTaintFlow = TaintTracking::Global<BasicTaintConfig>;
from DataFlow::Node node
where BasicTaintFlow::flow(_, node)
where BasicTaintFlow::flowTo(node)
select node, "Tainted node"

View File

@@ -88,5 +88,5 @@ import InsecureCookieFlow::PathGraph
from InsecureCookieFlow::PathNode sourceNode, InsecureCookieFlow::PathNode sinkNode
where
InsecureCookieFlow::flowPath(sourceNode, sinkNode) and
not PartitionedCookieFlow::flow(_, sinkNode.getNode())
not PartitionedCookieFlow::flowTo(sinkNode.getNode())
select sinkNode.getNode(), sourceNode, sinkNode, "Cookie attribute 'Secure' is not set to true."

View File

@@ -207,6 +207,28 @@ module MakeImpl<LocationSig Location, InputSig<Location> Lang> {
flowLocal(source, sink) and Config::observeOverlayInformedIncrementalMode()
}
/**
* Holds if data can flow from `source` to some sink.
* This is a local predicate that only has results local to the overlay/base database.
*/
predicate flowFromLocal(Node source) = forceLocal(Flow::flowFrom/1)(source)
/**
* Holds if data can flow from `source` to some sink.
*/
predicate flowFrom(Node source) {
Flow::flowFrom(source)
or
// If we are overlay informed (i.e. we are not diff-informed), we
// merge in the local results which includes the base database results.
flowFromLocal(source) and Config::observeOverlayInformedIncrementalMode()
}
/**
* Holds if data can flow from `source` to some sink.
*/
predicate flowFromExpr(Lang::DataFlowExpr source) { flowFrom(exprNode(source)) }
/**
* Holds if data can flow from some source to `sink`.
* This is a local predicate that only has results local to the overlay/base database.
@@ -3501,6 +3523,16 @@ module MakeImpl<LocationSig Location, InputSig<Location> Lang> {
)
}
/**
* Holds if data can flow from `source` to some sink.
*/
predicate flowFrom(Node source) { exists(PathNode n | n.isSource() and n.getNode() = source) }
/**
* Holds if data can flow from `source` to some sink.
*/
predicate flowFromExpr(Expr source) { flowFrom(exprNode(source)) }
/**
* Holds if data can flow from some source to `sink`.
*/

View File

@@ -87,7 +87,7 @@ private class UrlTransmittedSink extends CleartextTransmissionSink {
// exclude `tel:` and similar URLs. These URLs necessarily contain
// sensitive data which you expect to transmit only by making the
// phone call (or similar operation).
not ExcludeUrlFlow::flow(_, this)
not ExcludeUrlFlow::flowTo(this)
}
}