mirror of
https://github.com/github/codeql.git
synced 2026-05-05 13:45:19 +02:00
Merge branch 'main' into django-rest-framework
This commit is contained in:
@@ -13,6 +13,7 @@ private import semmle.python.frameworks.Cryptography
|
||||
private import semmle.python.frameworks.Dill
|
||||
private import semmle.python.frameworks.Django
|
||||
private import semmle.python.frameworks.Fabric
|
||||
private import semmle.python.frameworks.FastApi
|
||||
private import semmle.python.frameworks.Flask
|
||||
private import semmle.python.frameworks.FlaskSqlAlchemy
|
||||
private import semmle.python.frameworks.Idna
|
||||
@@ -24,12 +25,14 @@ private import semmle.python.frameworks.Mysql
|
||||
private import semmle.python.frameworks.MySQLdb
|
||||
private import semmle.python.frameworks.Peewee
|
||||
private import semmle.python.frameworks.Psycopg2
|
||||
private import semmle.python.frameworks.Pydantic
|
||||
private import semmle.python.frameworks.PyMySQL
|
||||
private import semmle.python.frameworks.RestFramework
|
||||
private import semmle.python.frameworks.Rsa
|
||||
private import semmle.python.frameworks.RuamelYaml
|
||||
private import semmle.python.frameworks.Simplejson
|
||||
private import semmle.python.frameworks.SqlAlchemy
|
||||
private import semmle.python.frameworks.Starlette
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
private import semmle.python.frameworks.Tornado
|
||||
private import semmle.python.frameworks.Twisted
|
||||
|
||||
@@ -107,7 +107,7 @@ class SpecialMethodCallNode extends PotentialSpecialMethodCallNode {
|
||||
|
||||
SpecialMethodCallNode() {
|
||||
exists(SpecialMethod::Potential pot |
|
||||
this.(SpecialMethod::Potential) = pot and
|
||||
this = pot and
|
||||
pot.getSelf().pointsTo().getClass().lookup(pot.getSpecialMethodName()) = resolvedSpecialMethod
|
||||
)
|
||||
}
|
||||
|
||||
@@ -94,13 +94,13 @@ class AugAssign extends AugAssign_ {
|
||||
* Gets the target of this augmented assignment statement.
|
||||
* That is, the `a` in `a += b`.
|
||||
*/
|
||||
Expr getTarget() { result = this.getOperation().(BinaryExpr).getLeft() }
|
||||
Expr getTarget() { result = this.getOperation().getLeft() }
|
||||
|
||||
/**
|
||||
* Gets the value of this augmented assignment statement.
|
||||
* That is, the `b` in `a += b`.
|
||||
*/
|
||||
Expr getValue() { result = this.getOperation().(BinaryExpr).getRight() }
|
||||
Expr getValue() { result = this.getOperation().getRight() }
|
||||
|
||||
override Stmt getASubStatement() { none() }
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
|
||||
@@ -2,6 +2,42 @@ private import DataFlowImplSpecific::Private
|
||||
private import DataFlowImplSpecific::Public
|
||||
import Cached
|
||||
|
||||
module DataFlowImplCommonPublic {
|
||||
private newtype TFlowFeature =
|
||||
TFeatureHasSourceCallContext() or
|
||||
TFeatureHasSinkCallContext() or
|
||||
TFeatureEqualSourceSinkCallContext()
|
||||
|
||||
/** A flow configuration feature for use in `Configuration::getAFeature()`. */
|
||||
class FlowFeature extends TFlowFeature {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sources have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
|
||||
override string toString() { result = "FeatureHasSourceCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sinks have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
|
||||
override string toString() { result = "FeatureHasSinkCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that source-sink pairs have some
|
||||
* shared existing call context.
|
||||
*/
|
||||
class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
|
||||
override string toString() { result = "FeatureEqualSourceSinkCallContext" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
|
||||
*
|
||||
|
||||
@@ -1365,10 +1365,8 @@ module IterableUnpacking {
|
||||
}
|
||||
|
||||
/** A (possibly recursive) target of an unpacking assignment which is also a sequence. */
|
||||
class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget {
|
||||
UnpackingAssignmentSequenceTarget() { this instanceof SequenceNode }
|
||||
|
||||
ControlFlowNode getElement(int i) { result = this.(SequenceNode).getElement(i) }
|
||||
class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget instanceof SequenceNode {
|
||||
ControlFlowNode getElement(int i) { result = super.getElement(i) }
|
||||
|
||||
ControlFlowNode getAnElement() { result = this.getElement(_) }
|
||||
}
|
||||
|
||||
@@ -52,6 +52,24 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
|
||||
cached
|
||||
TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) {
|
||||
exists(Boolean hasReturn, string content | tbt = MkTypeBackTracker(hasReturn, content) |
|
||||
step = LevelStep() and result = tbt
|
||||
or
|
||||
step = CallStep() and hasReturn = false and result = tbt
|
||||
or
|
||||
step = ReturnStep() and result = MkTypeBackTracker(true, content)
|
||||
or
|
||||
exists(string p |
|
||||
step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
|
||||
)
|
||||
or
|
||||
step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or intra-procedural step from `nodeFrom` to `nodeTo`.
|
||||
@@ -365,19 +383,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
|
||||
|
||||
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
|
||||
TypeBackTracker prepend(StepSummary step) {
|
||||
step = LevelStep() and result = this
|
||||
or
|
||||
step = CallStep() and hasReturn = false and result = this
|
||||
or
|
||||
step = ReturnStep() and result = MkTypeBackTracker(true, content)
|
||||
or
|
||||
exists(string p |
|
||||
step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
|
||||
)
|
||||
or
|
||||
step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
|
||||
}
|
||||
TypeBackTracker prepend(StepSummary step) { result = prepend(this, step) }
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
string toString() {
|
||||
@@ -459,6 +465,19 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
this = result
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a forwards summary that is compatible with this backwards summary.
|
||||
* That is, if this summary describes the steps needed to back-track a value
|
||||
* from `sink` to `mid`, and the result is a valid summary of the steps needed
|
||||
* to track a value from `source` to `mid`, then the value from `source` may
|
||||
* also flow to `sink`.
|
||||
*/
|
||||
TypeTracker getACompatibleTypeTracker() {
|
||||
exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
|
||||
hasCall = false or this.hasReturn() = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for implementing custom `TypeBackTracker`s. */
|
||||
|
||||
@@ -639,16 +639,14 @@ module DataFlow {
|
||||
}
|
||||
}
|
||||
|
||||
deprecated private class ConfigurationAdapter extends TaintTracking::Configuration {
|
||||
ConfigurationAdapter() { this instanceof Configuration }
|
||||
|
||||
deprecated private class ConfigurationAdapter extends TaintTracking::Configuration instanceof Configuration {
|
||||
override predicate isSource(DataFlow::Node node, TaintKind kind) {
|
||||
this.(Configuration).isSource(node.asCfgNode()) and
|
||||
Configuration.super.isSource(node.asCfgNode()) and
|
||||
kind instanceof DataFlowType
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node node, TaintKind kind) {
|
||||
this.(Configuration).isSink(node.asCfgNode()) and
|
||||
Configuration.super.isSink(node.asCfgNode()) and
|
||||
kind instanceof DataFlowType
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,14 @@ string munge(File sourceFile, ExternalPackage package) {
|
||||
result = "/" + sourceFile.getRelativePath() + "<|>" + package.getName() + "<|>unknown"
|
||||
}
|
||||
|
||||
abstract class ExternalPackage extends Object {
|
||||
ExternalPackage() { this instanceof ModuleObject }
|
||||
|
||||
abstract class ExternalPackage extends Object instanceof ModuleObject {
|
||||
abstract string getName();
|
||||
|
||||
abstract string getVersion();
|
||||
|
||||
Object getAttribute(string name) { result = this.(ModuleObject).attr(name) }
|
||||
Object getAttribute(string name) { result = super.attr(name) }
|
||||
|
||||
PackageObject getPackage() { result = this.(ModuleObject).getPackage() }
|
||||
PackageObject getPackage() { result = super.getPackage() }
|
||||
}
|
||||
|
||||
bindingset[text]
|
||||
|
||||
@@ -221,7 +221,7 @@ class ModuleVariable extends SsaSourceVariable {
|
||||
pragma[noinline]
|
||||
ImportMemberNode global_variable_import() {
|
||||
result.getScope() = this.(GlobalVariable).getScope() and
|
||||
import_from_dot_in_init(result.(ImportMemberNode).getModule(this.getName()))
|
||||
import_from_dot_in_init(result.getModule(this.getName()))
|
||||
}
|
||||
|
||||
override ControlFlowNode getAnImplicitUse() {
|
||||
@@ -306,7 +306,7 @@ class EscapingGlobalVariable extends ModuleVariable {
|
||||
Scope scope_as_global_variable() { result = this.(GlobalVariable).getScope() }
|
||||
|
||||
override CallNode redefinedAtCallSite() {
|
||||
result.(CallNode).getScope().getScope*() = this.scope_as_global_variable()
|
||||
result.getScope().getScope*() = this.scope_as_global_variable()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ class SpecialSsaSourceVariable extends SsaSourceVariable {
|
||||
Scope scope_as_global_variable() { result = this.(GlobalVariable).getScope() }
|
||||
|
||||
override CallNode redefinedAtCallSite() {
|
||||
result.(CallNode).getScope().getScope*() = this.scope_as_global_variable()
|
||||
result.getScope().getScope*() = this.scope_as_global_variable()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -330,8 +330,8 @@ module AiohttpWebModel {
|
||||
exists(Await await, DataFlow::CallCfgNode call, DataFlow::AttrRead read |
|
||||
this.asExpr() = await
|
||||
|
|
||||
read.(DataFlow::AttrRead).getObject() = Request::instance() and
|
||||
read.(DataFlow::AttrRead).getAttributeName() = "post" and
|
||||
read.getObject() = Request::instance() and
|
||||
read.getAttributeName() = "post" and
|
||||
call.getFunction() = read and
|
||||
await.getValue() = call.asExpr()
|
||||
)
|
||||
|
||||
352
python/ql/lib/semmle/python/frameworks/FastApi.qll
Normal file
352
python/ql/lib/semmle/python/frameworks/FastApi.qll
Normal file
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `fastapi` PyPI package.
|
||||
* See https://fastapi.tiangolo.com/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.Pydantic
|
||||
private import semmle.python.frameworks.Starlette
|
||||
|
||||
/**
|
||||
* Provides models for the `fastapi` PyPI package.
|
||||
* See https://fastapi.tiangolo.com/.
|
||||
*/
|
||||
private module FastApi {
|
||||
/**
|
||||
* Provides models for FastAPI applications (an instance of `fastapi.FastAPI`).
|
||||
*/
|
||||
module App {
|
||||
/** Gets a reference to a FastAPI application (an instance of `fastapi.FastAPI`). */
|
||||
API::Node instance() { result = API::moduleImport("fastapi").getMember("FastAPI").getReturn() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fastapi.APIRouter` class
|
||||
*
|
||||
* See https://fastapi.tiangolo.com/tutorial/bigger-applications/.
|
||||
*/
|
||||
module APIRouter {
|
||||
/** Gets a reference to an instance of `fastapi.APIRouter`. */
|
||||
API::Node instance() {
|
||||
result = API::moduleImport("fastapi").getMember("APIRouter").getReturn()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// routing modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* A call to a method like `get` or `post` on a FastAPI application.
|
||||
*
|
||||
* See https://fastapi.tiangolo.com/tutorial/first-steps/#define-a-path-operation-decorator
|
||||
*/
|
||||
private class FastApiRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CallCfgNode {
|
||||
FastApiRouteSetup() {
|
||||
exists(string routeAddingMethod |
|
||||
routeAddingMethod = HTTP::httpVerbLower()
|
||||
or
|
||||
routeAddingMethod in ["api_route", "websocket"]
|
||||
|
|
||||
this = App::instance().getMember(routeAddingMethod).getACall()
|
||||
or
|
||||
this = APIRouter::instance().getMember(routeAddingMethod).getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override Parameter getARoutedParameter() {
|
||||
// this will need to be refined a bit, since you can add special parameters to
|
||||
// your request handler functions that are used to pass in the response. There
|
||||
// might be other special cases as well, but as a start this is not too far off
|
||||
// the mark.
|
||||
result = this.getARequestHandler().getArgByName(_) and
|
||||
// type-annotated with `Response`
|
||||
not any(Response::RequestHandlerParam src).asExpr() = result
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() {
|
||||
result in [this.getArg(0), this.getArgByName("path")]
|
||||
}
|
||||
|
||||
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
||||
|
||||
override string getFramework() { result = "FastAPI" }
|
||||
|
||||
/** Gets the argument specifying the response class to use, if any. */
|
||||
DataFlow::Node getResponseClassArg() { result = this.getArgByName("response_class") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter to a request handler that has a type-annotation with a class that is a
|
||||
* Pydantic model.
|
||||
*/
|
||||
private class PydanticModelRequestHandlerParam extends Pydantic::BaseModel::InstanceSource,
|
||||
DataFlow::ParameterNode {
|
||||
PydanticModelRequestHandlerParam() {
|
||||
this.getParameter().getAnnotation() = Pydantic::BaseModel::subclassRef().getAUse().asExpr() and
|
||||
any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Response modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* A parameter to a request handler that has a WebSocket type-annotation.
|
||||
*/
|
||||
private class WebSocketRequestHandlerParam extends Starlette::WebSocket::InstanceSource,
|
||||
DataFlow::ParameterNode {
|
||||
WebSocketRequestHandlerParam() {
|
||||
this.getParameter().getAnnotation() = Starlette::WebSocket::classRef().getAUse().asExpr() and
|
||||
any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fastapi.Response` class and subclasses.
|
||||
*
|
||||
* See https://fastapi.tiangolo.com/advanced/custom-response/#response.
|
||||
*/
|
||||
module Response {
|
||||
/**
|
||||
* Gets the `API::Node` for the manually modeled response classes called `name`.
|
||||
*/
|
||||
private API::Node getModeledResponseClass(string name) {
|
||||
name = "Response" and
|
||||
result = API::moduleImport("fastapi").getMember(name)
|
||||
or
|
||||
// see https://github.com/tiangolo/fastapi/blob/master/fastapi/responses.py
|
||||
name in [
|
||||
"Response", "HTMLResponse", "PlainTextResponse", "JSONResponse", "UJSONResponse",
|
||||
"ORJSONResponse", "RedirectResponse", "StreamingResponse", "FileResponse"
|
||||
] and
|
||||
result = API::moduleImport("fastapi").getMember("responses").getMember(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default MIME type for a FastAPI response class (defined with the
|
||||
* `media_type` class-attribute).
|
||||
*
|
||||
* Also models user-defined classes and tries to take inheritance into account.
|
||||
*
|
||||
* TODO: build easy way to solve problems like this, like we used to have the
|
||||
* `ClassValue.lookup` predicate.
|
||||
*/
|
||||
private string getDefaultMimeType(API::Node responseClass) {
|
||||
exists(string name | responseClass = getModeledResponseClass(name) |
|
||||
// no defaults for these.
|
||||
name in ["Response", "RedirectResponse", "StreamingResponse"] and
|
||||
none()
|
||||
or
|
||||
// For `FileResponse` the code will guess what mimetype
|
||||
// to use, or fall back to "text/plain", but claiming that all responses will
|
||||
// have "text/plain" per default is also highly inaccurate, so just going to not
|
||||
// do anything about this.
|
||||
name = "FileResponse" and
|
||||
none()
|
||||
or
|
||||
name = "HTMLResponse" and
|
||||
result = "text/html"
|
||||
or
|
||||
name = "PlainTextResponse" and
|
||||
result = "text/plain"
|
||||
or
|
||||
name in ["JSONResponse", "UJSONResponse", "ORJSONResponse"] and
|
||||
result = "application/json"
|
||||
)
|
||||
or
|
||||
// user-defined subclasses
|
||||
exists(Class cls, API::Node base |
|
||||
base = getModeledResponseClass(_).getASubclass*() and
|
||||
cls.getABase() = base.getAUse().asExpr() and
|
||||
responseClass.getAnImmediateUse().asExpr().(ClassExpr) = cls.getParent()
|
||||
|
|
||||
exists(Assign assign | assign = cls.getAStmt() |
|
||||
assign.getATarget().(Name).getId() = "media_type" and
|
||||
result = assign.getValue().(StrConst).getText()
|
||||
)
|
||||
or
|
||||
// TODO: this should use a proper MRO calculation instead
|
||||
not exists(Assign assign | assign = cls.getAStmt() |
|
||||
assign.getATarget().(Name).getId() = "media_type"
|
||||
) and
|
||||
result = getDefaultMimeType(base)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `fastapi.Response` and its' subclasses, extend this class to model new instances.
|
||||
*
|
||||
* This can include instantiations of the class, return values from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use the predicate `Response::instance()` to get references to instances of `fastapi.Response`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of a response class. */
|
||||
private class ResponseInstantiation extends InstanceSource, HTTP::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
API::Node baseApiNode;
|
||||
API::Node responseClass;
|
||||
|
||||
ResponseInstantiation() {
|
||||
baseApiNode = getModeledResponseClass(_) and
|
||||
responseClass = baseApiNode.getASubclass*() and
|
||||
this = responseClass.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() {
|
||||
not baseApiNode = getModeledResponseClass(["RedirectResponse", "FileResponse"]) and
|
||||
result in [this.getArg(0), this.getArgByName("content")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() {
|
||||
not baseApiNode = getModeledResponseClass("RedirectResponse") and
|
||||
result in [this.getArg(3), this.getArgByName("media_type")]
|
||||
}
|
||||
|
||||
override string getMimetypeDefault() { result = getDefaultMimeType(responseClass) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A direct instantiation of a redirect response.
|
||||
*/
|
||||
private class RedirectResponseInstantiation extends ResponseInstantiation,
|
||||
HTTP::Server::HttpRedirectResponse::Range {
|
||||
RedirectResponseInstantiation() { baseApiNode = getModeledResponseClass("RedirectResponse") }
|
||||
|
||||
override DataFlow::Node getRedirectLocation() {
|
||||
result in [this.getArg(0), this.getArgByName("url")]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implicit response from a return of FastAPI request handler.
|
||||
*/
|
||||
private class FastApiRequestHandlerReturn extends HTTP::Server::HttpResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
FastApiRouteSetup routeSetup;
|
||||
|
||||
FastApiRequestHandlerReturn() {
|
||||
node = routeSetup.getARequestHandler().getAReturnValueFlowNode()
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { result = this }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
|
||||
|
||||
override string getMimetypeDefault() {
|
||||
exists(API::Node responseClass |
|
||||
responseClass.getAUse() = routeSetup.getResponseClassArg() and
|
||||
result = getDefaultMimeType(responseClass)
|
||||
)
|
||||
or
|
||||
not exists(routeSetup.getResponseClassArg()) and
|
||||
result = "application/json"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implicit response from a return of FastAPI request handler, that has
|
||||
* `response_class` set to a `FileResponse`.
|
||||
*/
|
||||
private class FastApiRequestHandlerFileResponseReturn extends FastApiRequestHandlerReturn {
|
||||
FastApiRequestHandlerFileResponseReturn() {
|
||||
exists(API::Node responseClass |
|
||||
responseClass.getAUse() = routeSetup.getResponseClassArg() and
|
||||
responseClass = getModeledResponseClass("FileResponse").getASubclass*()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An implicit response from a return of FastAPI request handler, that has
|
||||
* `response_class` set to a `RedirectResponse`.
|
||||
*/
|
||||
private class FastApiRequestHandlerRedirectReturn extends FastApiRequestHandlerReturn,
|
||||
HTTP::Server::HttpRedirectResponse::Range {
|
||||
FastApiRequestHandlerRedirectReturn() {
|
||||
exists(API::Node responseClass |
|
||||
responseClass.getAUse() = routeSetup.getResponseClassArg() and
|
||||
responseClass = getModeledResponseClass("RedirectResponse").getASubclass*()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { none() }
|
||||
|
||||
override DataFlow::Node getRedirectLocation() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* A parameter to a FastAPI request-handler that has a `fastapi.Response`
|
||||
* type-annotation.
|
||||
*/
|
||||
class RequestHandlerParam extends InstanceSource, DataFlow::ParameterNode {
|
||||
RequestHandlerParam() {
|
||||
this.getParameter().getAnnotation() =
|
||||
getModeledResponseClass(_).getASubclass*().getAUse().asExpr() and
|
||||
any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `fastapi.Response`. */
|
||||
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `fastapi.Response`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* A call to `set_cookie` on a FastAPI Response.
|
||||
*/
|
||||
private class SetCookieCall extends HTTP::Server::CookieWrite::Range, DataFlow::MethodCallNode {
|
||||
SetCookieCall() { this.calls(instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("key")] }
|
||||
|
||||
override DataFlow::Node getValueArg() {
|
||||
result in [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `append` on a `headers` of a FastAPI Response, with the `Set-Cookie`
|
||||
* header-key.
|
||||
*/
|
||||
private class HeadersAppendCookie extends HTTP::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
HeadersAppendCookie() {
|
||||
exists(DataFlow::AttrRead headers, DataFlow::Node keyArg |
|
||||
headers.accesses(instance(), "headers") and
|
||||
this.calls(headers, "append") and
|
||||
keyArg in [this.getArg(0), this.getArgByName("key")] and
|
||||
keyArg.getALocalSource().asExpr().(StrConst).getText().toLowerCase() = "set-cookie"
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() {
|
||||
result in [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { none() }
|
||||
|
||||
override DataFlow::Node getValueArg() { none() }
|
||||
}
|
||||
}
|
||||
}
|
||||
108
python/ql/lib/semmle/python/frameworks/Pydantic.qll
Normal file
108
python/ql/lib/semmle/python/frameworks/Pydantic.qll
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `pydantic` PyPI package.
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/pydantic/
|
||||
* - https://pydantic-docs.helpmanual.io/
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for `pydantic` PyPI package.
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/pydantic/
|
||||
* - https://pydantic-docs.helpmanual.io/
|
||||
*/
|
||||
module Pydantic {
|
||||
/**
|
||||
* Provides models for `pydantic.BaseModel` subclasses (a pydantic model).
|
||||
*
|
||||
* See https://pydantic-docs.helpmanual.io/usage/models/.
|
||||
*/
|
||||
module BaseModel {
|
||||
/** Gets a reference to a `pydantic.BaseModel` subclass (a pydantic model). */
|
||||
API::Node subclassRef() {
|
||||
result = API::moduleImport("pydantic").getMember("BaseModel").getASubclass+()
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `pydantic.BaseModel` subclasses, extend this class to model new instances.
|
||||
*
|
||||
* This can include instantiations of the class, return values from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use the predicate `BaseModel::instance()` to get references to instances of `pydantic.BaseModel`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** Gets a reference to an instance of a `pydantic.BaseModel` subclass. */
|
||||
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
t.start() and
|
||||
instanceStepToPydanticModel(_, result)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of a `pydantic.BaseModel` subclass. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* A step from an instance of a `pydantic.BaseModel` subclass, that might result in
|
||||
* an instance of a `pydantic.BaseModel` subclass.
|
||||
*
|
||||
* NOTE: We currently overapproximate, and treat all attributes as containing
|
||||
* another pydantic model. For the code below, we _could_ limit this to `main_foo`
|
||||
* and members of `other_foos`. IF THIS IS CHANGED, YOU MUST CHANGE THE ADDITIONAL
|
||||
* TAINT STEPS BELOW, SUCH THAT SIMPLE ACCESS OF SOMETHIGN LIKE `str` IS STILL
|
||||
* TAINTED.
|
||||
*
|
||||
*
|
||||
* ```py
|
||||
* class MyComplexModel(BaseModel):
|
||||
* field: str
|
||||
* main_foo: Foo
|
||||
* other_foos: List[Foo]
|
||||
* ```
|
||||
*/
|
||||
private predicate instanceStepToPydanticModel(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// attributes (such as `model.foo`)
|
||||
nodeFrom = instance() and
|
||||
nodeTo.(DataFlow::AttrRead).getObject() = nodeFrom
|
||||
or
|
||||
// subscripts on attributes (such as `model.foo[0]`). This needs to handle nested
|
||||
// lists (such as `model.foo[0][0]`), and access being split into multiple
|
||||
// statements (such as `xs = model.foo; xs[0]`).
|
||||
//
|
||||
// To handle this we overapproximate which things are a Pydantic model, by
|
||||
// treating any subscript on anything that originates on a Pydantic model to also
|
||||
// be a Pydantic model. So `model[0]` will be an overapproximation, but should not
|
||||
// really cause problems (since we don't expect real code to contain such accesses)
|
||||
nodeFrom = instance() and
|
||||
nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra taint propagation for `pydantic.BaseModel` subclasses. (note that these could also be `pydantic.BaseModel` subclasses)
|
||||
*/
|
||||
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// NOTE: if `instanceStepToPydanticModel` is changed to be more precise, these
|
||||
// taint steps should be expanded, such that a field that has type `str` is
|
||||
// still tainted.
|
||||
instanceStepToPydanticModel(nodeFrom, nodeTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
162
python/ql/lib/semmle/python/frameworks/Starlette.qll
Normal file
162
python/ql/lib/semmle/python/frameworks/Starlette.qll
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `starlette` PyPI package.
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/starlette/
|
||||
* - https://www.starlette.io/
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for `starlette` PyPI package.
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/starlette/
|
||||
* - https://www.starlette.io/
|
||||
*/
|
||||
module Starlette {
|
||||
/**
|
||||
* Provides models for the `starlette.websockets.WebSocket` class
|
||||
*
|
||||
* See https://www.starlette.io/websockets/.
|
||||
*/
|
||||
module WebSocket {
|
||||
/** Gets a reference to the `starlette.websockets.WebSocket` class. */
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("starlette").getMember("websockets").getMember("WebSocket")
|
||||
or
|
||||
result = API::moduleImport("fastapi").getMember("WebSocket")
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `starlette.websockets.WebSocket`, extend this class to model new instances.
|
||||
*
|
||||
* This can include instantiations of the class, return values from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use the predicate `WebSocket::instance()` to get references to instances of `starlette.websockets.WebSocket`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of `starlette.websockets.WebSocket`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `starlette.websockets.WebSocket`. */
|
||||
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `starlette.websockets.WebSocket`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* Taint propagation for `starlette.websockets.WebSocket`.
|
||||
*/
|
||||
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
|
||||
InstanceTaintSteps() { this = "starlette.websockets.WebSocket" }
|
||||
|
||||
override DataFlow::Node getInstance() { result = instance() }
|
||||
|
||||
override string getAttributeName() { result in ["url", "headers", "query_params", "cookies"] }
|
||||
|
||||
override string getMethodName() { none() }
|
||||
|
||||
override string getAsyncMethodName() {
|
||||
result in [
|
||||
"receive", "receive_bytes", "receive_text", "receive_json", "iter_bytes", "iter_text",
|
||||
"iter_json"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** An attribute read on a `starlette.websockets.WebSocket` instance that is a `starlette.requests.URL` instance. */
|
||||
private class UrlInstances extends URL::InstanceSource {
|
||||
UrlInstances() {
|
||||
this.(DataFlow::AttrRead).getObject() = instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "url"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `starlette.requests.URL` class
|
||||
*
|
||||
* See the URL part of https://www.starlette.io/websockets/.
|
||||
*/
|
||||
module URL {
|
||||
/** Gets a reference to the `starlette.requests.URL` class. */
|
||||
private API::Node classRef() {
|
||||
result = API::moduleImport("starlette").getMember("requests").getMember("URL")
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `starlette.requests.URL`, extend this class to model new instances.
|
||||
*
|
||||
* This can include instantiations of the class, return values from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use the predicate `URL::instance()` to get references to instances of `starlette.requests.URL`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of `starlette.requests.URL`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `starlette.requests.URL`. */
|
||||
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `starlette.requests.URL`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* Taint propagation for `starlette.requests.URL`.
|
||||
*/
|
||||
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
|
||||
InstanceTaintSteps() { this = "starlette.requests.URL" }
|
||||
|
||||
override DataFlow::Node getInstance() { result = instance() }
|
||||
|
||||
override string getAttributeName() {
|
||||
result in [
|
||||
"components", "netloc", "path", "query", "fragment", "username", "password", "hostname",
|
||||
"port"
|
||||
]
|
||||
}
|
||||
|
||||
override string getMethodName() { none() }
|
||||
|
||||
override string getAsyncMethodName() { none() }
|
||||
}
|
||||
|
||||
/** An attribute read on a `starlette.requests.URL` instance that is a `urllib.parse.SplitResult` instance. */
|
||||
private class UrlSplitInstances extends Stdlib::SplitResult::InstanceSource instanceof DataFlow::AttrRead {
|
||||
UrlSplitInstances() {
|
||||
super.getObject() = instance() and
|
||||
super.getAttributeName() = "components"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,6 +167,74 @@ module Stdlib {
|
||||
override string getAsyncMethodName() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `urllib.parse.SplitResult` class
|
||||
*
|
||||
* See https://docs.python.org/3.9/library/urllib.parse.html#urllib.parse.SplitResult.
|
||||
*/
|
||||
module SplitResult {
|
||||
/** Gets a reference to the `urllib.parse.SplitResult` class. */
|
||||
private API::Node classRef() {
|
||||
result = API::moduleImport("urllib").getMember("parse").getMember("SplitResult")
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `urllib.parse.SplitResult`, extend this class to model new instances.
|
||||
*
|
||||
* This can include instantiations of the class, return values from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use the predicate `SplitResult::instance()` to get references to instances of `urllib.parse.SplitResult`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of `urllib.parse.SplitResult`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `urllib.parse.SplitResult`. */
|
||||
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `urllib.parse.SplitResult`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* Taint propagation for `urllib.parse.SplitResult`.
|
||||
*/
|
||||
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
|
||||
InstanceTaintSteps() { this = "urllib.parse.SplitResult" }
|
||||
|
||||
override DataFlow::Node getInstance() { result = instance() }
|
||||
|
||||
override string getAttributeName() {
|
||||
result in [
|
||||
"netloc", "path", "query", "fragment", "username", "password", "hostname", "port"
|
||||
]
|
||||
}
|
||||
|
||||
override string getMethodName() { none() }
|
||||
|
||||
override string getAsyncMethodName() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra taint propagation for `urllib.parse.SplitResult`, not covered by `InstanceTaintSteps`.
|
||||
*/
|
||||
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// TODO
|
||||
none()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1749,6 +1817,30 @@ private module StdlibPrivate {
|
||||
|
||||
override string getKind() { result = Escaping::getRegexKind() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// urllib
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* A call to `urllib.parse.urlsplit`
|
||||
*
|
||||
* See https://docs.python.org/3.9/library/urllib.parse.html#urllib.parse.urlsplit
|
||||
*/
|
||||
class UrllibParseUrlsplitCall extends Stdlib::SplitResult::InstanceSource, DataFlow::CallCfgNode {
|
||||
UrllibParseUrlsplitCall() {
|
||||
this = API::moduleImport("urllib").getMember("parse").getMember("urlsplit").getACall()
|
||||
}
|
||||
|
||||
/** Gets the argument that specifies the URL. */
|
||||
DataFlow::Node getUrl() { result in [this.getArg(0), this.getArgByName("url")] }
|
||||
}
|
||||
|
||||
/** Extra taint-step such that the result of `urllib.parse.urlsplit(tainted_string)` is tainted. */
|
||||
private class UrllibParseUrlsplitCallAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
nodeTo.(UrllibParseUrlsplitCall).getUrl() = nodeFrom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -248,7 +248,7 @@ class UnicodeObjectInternal extends ConstantObjectInternal, TUnicode {
|
||||
override ObjectInternal getClass() { result = TBuiltinClassObject(Builtin::special("unicode")) }
|
||||
|
||||
override Builtin getBuiltin() {
|
||||
result.(Builtin).strValue() = this.strValue() and
|
||||
result.strValue() = this.strValue() and
|
||||
result.getClass() = Builtin::special("unicode")
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ class BytesObjectInternal extends ConstantObjectInternal, TBytes {
|
||||
override ObjectInternal getClass() { result = TBuiltinClassObject(Builtin::special("bytes")) }
|
||||
|
||||
override Builtin getBuiltin() {
|
||||
result.(Builtin).strValue() = this.strValue() and
|
||||
result.strValue() = this.strValue() and
|
||||
result.getClass() = Builtin::special("bytes")
|
||||
}
|
||||
|
||||
|
||||
@@ -147,9 +147,7 @@ class Value extends TObject {
|
||||
* Class representing modules in the Python program
|
||||
* Each `ModuleValue` represents a module object in the Python program.
|
||||
*/
|
||||
class ModuleValue extends Value {
|
||||
ModuleValue() { this instanceof ModuleObjectInternal }
|
||||
|
||||
class ModuleValue extends Value instanceof ModuleObjectInternal {
|
||||
/**
|
||||
* Holds if this module "exports" name.
|
||||
* That is, does it define `name` in `__all__` or is
|
||||
@@ -159,7 +157,7 @@ class ModuleValue extends Value {
|
||||
predicate exports(string name) { PointsTo::moduleExports(this, name) }
|
||||
|
||||
/** Gets the scope for this module, provided that it is a Python module. */
|
||||
ModuleScope getScope() { result = this.(ModuleObjectInternal).getSourceModule() }
|
||||
ModuleScope getScope() { result = super.getSourceModule() }
|
||||
|
||||
/**
|
||||
* Gets the container path for this module. Will be the file for a Python module,
|
||||
@@ -181,7 +179,7 @@ class ModuleValue extends Value {
|
||||
predicate isPackage() { this instanceof PackageObjectInternal }
|
||||
|
||||
/** Whether the complete set of names "exported" by this module can be accurately determined */
|
||||
predicate hasCompleteExportInfo() { this.(ModuleObjectInternal).hasCompleteExportInfo() }
|
||||
predicate hasCompleteExportInfo() { super.hasCompleteExportInfo() }
|
||||
|
||||
/** Get a module that this module imports */
|
||||
ModuleValue getAnImportedModule() { result.importedAs(this.getScope().getAnImportedModuleName()) }
|
||||
@@ -452,23 +450,21 @@ class CallableValue extends Value {
|
||||
* Class representing bound-methods, such as `o.func`, where `o` is an instance
|
||||
* of a class that has a callable attribute `func`.
|
||||
*/
|
||||
class BoundMethodValue extends CallableValue {
|
||||
BoundMethodValue() { this instanceof BoundMethodObjectInternal }
|
||||
|
||||
class BoundMethodValue extends CallableValue instanceof BoundMethodObjectInternal {
|
||||
/**
|
||||
* Gets the callable that will be used when `this` is called.
|
||||
* The actual callable for `func` in `o.func`.
|
||||
*/
|
||||
CallableValue getFunction() { result = this.(BoundMethodObjectInternal).getFunction() }
|
||||
CallableValue getFunction() { result = super.getFunction() }
|
||||
|
||||
/**
|
||||
* Gets the value that will be used for the `self` parameter when `this` is called.
|
||||
* The value for `o` in `o.func`.
|
||||
*/
|
||||
Value getSelf() { result = this.(BoundMethodObjectInternal).getSelf() }
|
||||
Value getSelf() { result = super.getSelf() }
|
||||
|
||||
/** Gets the parameter node that will be used for `self`. */
|
||||
NameNode getSelfParameter() { result = this.(BoundMethodObjectInternal).getSelfParameter() }
|
||||
NameNode getSelfParameter() { result = super.getSelfParameter() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -831,12 +827,10 @@ class BuiltinMethodValue extends FunctionValue {
|
||||
/**
|
||||
* A class representing sequence objects with a length and tracked items.
|
||||
*/
|
||||
class SequenceValue extends Value {
|
||||
SequenceValue() { this instanceof SequenceObjectInternal }
|
||||
class SequenceValue extends Value instanceof SequenceObjectInternal {
|
||||
Value getItem(int n) { result = super.getItem(n) }
|
||||
|
||||
Value getItem(int n) { result = this.(SequenceObjectInternal).getItem(n) }
|
||||
|
||||
int length() { result = this.(SequenceObjectInternal).length() }
|
||||
int length() { result = super.length() }
|
||||
}
|
||||
|
||||
/** A class representing tuple objects */
|
||||
@@ -887,14 +881,12 @@ class NumericValue extends Value {
|
||||
* https://docs.python.org/3/howto/descriptor.html#properties
|
||||
* https://docs.python.org/3/library/functions.html#property
|
||||
*/
|
||||
class PropertyValue extends Value {
|
||||
PropertyValue() { this instanceof PropertyInternal }
|
||||
class PropertyValue extends Value instanceof PropertyInternal {
|
||||
CallableValue getGetter() { result = super.getGetter() }
|
||||
|
||||
CallableValue getGetter() { result = this.(PropertyInternal).getGetter() }
|
||||
CallableValue getSetter() { result = super.getSetter() }
|
||||
|
||||
CallableValue getSetter() { result = this.(PropertyInternal).getSetter() }
|
||||
|
||||
CallableValue getDeleter() { result = this.(PropertyInternal).getDeleter() }
|
||||
CallableValue getDeleter() { result = super.getDeleter() }
|
||||
}
|
||||
|
||||
/** A method-resolution-order sequence of classes */
|
||||
|
||||
@@ -144,12 +144,10 @@ class ReModulePointToExtension extends PointsToExtension {
|
||||
private predicate pointsTo_helper(Context context) { context.appliesTo(this) }
|
||||
}
|
||||
|
||||
deprecated private class BackwardCompatiblePointToExtension extends PointsToExtension {
|
||||
BackwardCompatiblePointToExtension() { this instanceof CustomPointsToFact }
|
||||
|
||||
deprecated private class BackwardCompatiblePointToExtension extends PointsToExtension instanceof CustomPointsToFact {
|
||||
override predicate pointsTo(Context context, ObjectInternal value, ControlFlowNode origin) {
|
||||
exists(Object obj, ClassObject cls |
|
||||
this.(CustomPointsToFact).pointsTo(context, obj, cls, origin)
|
||||
CustomPointsToFact.super.pointsTo(context, obj, cls, origin)
|
||||
|
|
||||
value.getBuiltin() = obj
|
||||
or
|
||||
|
||||
@@ -84,7 +84,7 @@ private predicate property_getter(CallNode decorated, FunctionObject getter) {
|
||||
private predicate property_setter(CallNode decorated, FunctionObject setter) {
|
||||
property_getter(decorated, _) and
|
||||
exists(CallNode setter_call, AttrNode prop_setter |
|
||||
prop_setter.getObject("setter").refersTo(decorated.(Object))
|
||||
prop_setter.getObject("setter").refersTo(decorated)
|
||||
|
|
||||
setter_call.getArg(0).refersTo(setter) and
|
||||
setter_call.getFunction() = prop_setter
|
||||
@@ -97,7 +97,7 @@ private predicate property_setter(CallNode decorated, FunctionObject setter) {
|
||||
private predicate property_deleter(CallNode decorated, FunctionObject deleter) {
|
||||
property_getter(decorated, _) and
|
||||
exists(CallNode deleter_call, AttrNode prop_deleter |
|
||||
prop_deleter.getObject("deleter").refersTo(decorated.(Object))
|
||||
prop_deleter.getObject("deleter").refersTo(decorated)
|
||||
|
|
||||
deleter_call.getArg(0).refersTo(deleter) and
|
||||
deleter_call.getFunction() = prop_deleter
|
||||
|
||||
@@ -78,5 +78,5 @@ private predicate tracking_step(ControlFlowNode src, ControlFlowNode dest) {
|
||||
or
|
||||
tracked_call_step(src, dest)
|
||||
or
|
||||
dest.refersTo(src.(Object))
|
||||
dest.refersTo(src)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user