diff --git a/python/ql/src/experimental/dataflow/internal/DataFlowPrivate.qll b/python/ql/src/experimental/dataflow/internal/DataFlowPrivate.qll index 93efad8be21f..3f9762bbf83e 100644 --- a/python/ql/src/experimental/dataflow/internal/DataFlowPrivate.qll +++ b/python/ql/src/experimental/dataflow/internal/DataFlowPrivate.qll @@ -504,6 +504,29 @@ predicate jumpStep(Node nodeFrom, Node nodeTo) { or // Module variable write nodeFrom = nodeTo.(ModuleVariableNode).getAWrite() + or + // Read of module attribute: + exists(AttrRead r, ModuleValue mv | + r.getObject().asCfgNode().pointsTo(mv) and + module_export(mv.getScope(), r.getAttributeName(), nodeFrom) and + nodeTo = r + ) +} + +/** + * Holds if the module `m` defines a name `name` by assigning `defn` to it. This is an + * overapproximation, as `name` may not in fact be exported (e.g. by defining an `__all__` that does + * not include `name`). + */ +private predicate module_export(Module m, string name, CfgNode defn) { + exists(EssaVariable v | + v.getName() = name and + v.getAUse() = m.getANormalExit() + | + defn.getNode() = v.getDefinition().(AssignmentDefinition).getValue() + or + defn.getNode() = v.getDefinition().(ArgumentRefinement).getArgument() + ) } //-------- diff --git a/python/ql/test/experimental/dataflow/pep_328/__init__.py b/python/ql/test/experimental/dataflow/pep_328/__init__.py new file mode 100644 index 000000000000..2ae28399f5fd --- /dev/null +++ b/python/ql/test/experimental/dataflow/pep_328/__init__.py @@ -0,0 +1 @@ +pass diff --git a/python/ql/test/experimental/dataflow/pep_328/package/__init__.py b/python/ql/test/experimental/dataflow/pep_328/package/__init__.py new file mode 100644 index 000000000000..2e96e096cc76 --- /dev/null +++ b/python/ql/test/experimental/dataflow/pep_328/package/__init__.py @@ -0,0 +1 @@ +bar = "bar" diff --git a/python/ql/test/experimental/dataflow/pep_328/package/moduleA.py b/python/ql/test/experimental/dataflow/pep_328/package/moduleA.py new file mode 100644 index 000000000000..8c4ff6a2557b --- /dev/null +++ b/python/ql/test/experimental/dataflow/pep_328/package/moduleA.py @@ -0,0 +1 @@ +foo = "foo" diff --git a/python/ql/test/experimental/dataflow/pep_328/package/subpackage1/__init__.py b/python/ql/test/experimental/dataflow/pep_328/package/subpackage1/__init__.py new file mode 100644 index 000000000000..2b8c529b95e3 --- /dev/null +++ b/python/ql/test/experimental/dataflow/pep_328/package/subpackage1/__init__.py @@ -0,0 +1,16 @@ +from .moduleY import spam +from .moduleY import spam as ham +from . import moduleY +from ..subpackage1 import moduleY +from ..subpackage2.moduleZ import eggs +from ..moduleA import foo + +try: + from ...package import bar +except Exception as e: + print(e) + +try: + from ...sys import path +except Exception as e: + print(e) diff --git a/python/ql/test/experimental/dataflow/pep_328/package/subpackage1/moduleX.py b/python/ql/test/experimental/dataflow/pep_328/package/subpackage1/moduleX.py new file mode 100644 index 000000000000..2b8c529b95e3 --- /dev/null +++ b/python/ql/test/experimental/dataflow/pep_328/package/subpackage1/moduleX.py @@ -0,0 +1,16 @@ +from .moduleY import spam +from .moduleY import spam as ham +from . import moduleY +from ..subpackage1 import moduleY +from ..subpackage2.moduleZ import eggs +from ..moduleA import foo + +try: + from ...package import bar +except Exception as e: + print(e) + +try: + from ...sys import path +except Exception as e: + print(e) diff --git a/python/ql/test/experimental/dataflow/pep_328/package/subpackage1/moduleY.py b/python/ql/test/experimental/dataflow/pep_328/package/subpackage1/moduleY.py new file mode 100644 index 000000000000..5fd18b654965 --- /dev/null +++ b/python/ql/test/experimental/dataflow/pep_328/package/subpackage1/moduleY.py @@ -0,0 +1 @@ +spam = "spam" diff --git a/python/ql/test/experimental/dataflow/pep_328/package/subpackage2/__init__.py b/python/ql/test/experimental/dataflow/pep_328/package/subpackage2/__init__.py new file mode 100644 index 000000000000..2ae28399f5fd --- /dev/null +++ b/python/ql/test/experimental/dataflow/pep_328/package/subpackage2/__init__.py @@ -0,0 +1 @@ +pass diff --git a/python/ql/test/experimental/dataflow/pep_328/package/subpackage2/moduleZ.py b/python/ql/test/experimental/dataflow/pep_328/package/subpackage2/moduleZ.py new file mode 100644 index 000000000000..d1de6fe8df34 --- /dev/null +++ b/python/ql/test/experimental/dataflow/pep_328/package/subpackage2/moduleZ.py @@ -0,0 +1 @@ +eggs = "eggs" diff --git a/python/ql/test/experimental/dataflow/pep_328/start.py b/python/ql/test/experimental/dataflow/pep_328/start.py new file mode 100644 index 000000000000..0b214430e29f --- /dev/null +++ b/python/ql/test/experimental/dataflow/pep_328/start.py @@ -0,0 +1 @@ +import package.subpackage1.moduleX diff --git a/python/ql/test/experimental/dataflow/regression/dataflow.expected b/python/ql/test/experimental/dataflow/regression/dataflow.expected index e7b3b140e505..2fe69282e378 100644 --- a/python/ql/test/experimental/dataflow/regression/dataflow.expected +++ b/python/ql/test/experimental/dataflow/regression/dataflow.expected @@ -1,3 +1,7 @@ +| module.py:1:13:1:18 | ControlFlowNode for SOURCE | test.py:89:10:89:10 | ControlFlowNode for t | +| module.py:1:13:1:18 | ControlFlowNode for SOURCE | test.py:106:10:106:14 | ControlFlowNode for Attribute | +| module.py:1:13:1:18 | ControlFlowNode for SOURCE | test.py:111:10:111:12 | ControlFlowNode for Attribute | +| module.py:1:13:1:18 | ControlFlowNode for SOURCE | test.py:156:6:156:11 | ControlFlowNode for unsafe | | module.py:6:12:6:17 | ControlFlowNode for SOURCE | test.py:101:10:101:10 | ControlFlowNode for t | | test.py:3:10:3:15 | ControlFlowNode for SOURCE | test.py:3:10:3:15 | ControlFlowNode for SOURCE | | test.py:6:9:6:14 | ControlFlowNode for SOURCE | test.py:7:10:7:10 | ControlFlowNode for s | diff --git a/python/ql/test/experimental/dataflow/regression/test.py b/python/ql/test/experimental/dataflow/regression/test.py index dbcfd4c4584e..aa620b6ba5f3 100644 --- a/python/ql/test/experimental/dataflow/regression/test.py +++ b/python/ql/test/experimental/dataflow/regression/test.py @@ -86,7 +86,7 @@ def test12(): def test13(): t = module.dangerous - SINK(t) # Flow not found + SINK(t) def test14(): t = module.safe @@ -108,13 +108,13 @@ def x_sink(arg): def test17(): t = C() t.x = module.dangerous - SINK(t.x) # Flow not found + SINK(t.x) def test18(): t = C() t.x = module.dangerous t = hub(t) - x_sink(t) # Flow not found + x_sink(t) def test19(): t = CUSTOM_SOURCE @@ -153,7 +153,7 @@ def test22(cond): SINK(t) from module import dangerous as unsafe -SINK(unsafe) # Flow not found +SINK(unsafe) def test23(): with SOURCE as t: diff --git a/python/ql/test/experimental/dataflow/typetracking/mymodule.py b/python/ql/test/experimental/dataflow/typetracking/mymodule.py index ed4ca954c43f..86bd891efd7a 100644 --- a/python/ql/test/experimental/dataflow/typetracking/mymodule.py +++ b/python/ql/test/experimental/dataflow/typetracking/mymodule.py @@ -2,3 +2,6 @@ def func(): return tracked # $tracked + +z = tracked # $tracked +some_func(z) # $tracked diff --git a/python/ql/test/experimental/dataflow/typetracking/test.py b/python/ql/test/experimental/dataflow/typetracking/test.py index 60ef0b64470d..6fbf4cfabb16 100644 --- a/python/ql/test/experimental/dataflow/typetracking/test.py +++ b/python/ql/test/experimental/dataflow/typetracking/test.py @@ -51,9 +51,10 @@ def global_var_write_test(): def test_import(): import mymodule - mymodule.x # $f-:tracked + mymodule.x # $tracked y = mymodule.func() # $tracked y # $tracked + mymodule.z # $tracked # ------------------------------------------------------------------------------