Merge branch 'main' into django-rest-framework

This commit is contained in:
Rasmus Wriedt Larsen
2021-11-02 14:33:16 +01:00
197 changed files with 5026 additions and 1618 deletions

View File

@@ -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

View File

@@ -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
)
}

View File

@@ -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() }
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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.
*

View File

@@ -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(_) }
}

View File

@@ -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. */

View File

@@ -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
}
}

View File

@@ -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]

View File

@@ -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()
}
}

View File

@@ -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()
)

View 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() }
}
}
}

View 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)
}
}
}
}

View 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"
}
}
}
}

View File

@@ -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
}
}
}
// ---------------------------------------------------------------------------

View File

@@ -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")
}

View File

@@ -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 */

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}