Sitelet https://web.archive.org/web/20260503002716/https://github.com/github/codeql/pull/7807
Skip to content

Python: Dataflow improvements#7807

Merged
yoff merged 39 commits intogithub:mainfrom
RasmusWL:dataflow-improvements
Feb 28, 2022
Merged

Python: Dataflow improvements#7807
yoff merged 39 commits intogithub:mainfrom
RasmusWL:dataflow-improvements

Conversation

@RasmusWL
Copy link
Copy Markdown
Member

@RasmusWL RasmusWL commented Feb 2, 2022 •

This turned out to be a bit of random bag of things, and the first many commits is refactoring.

Will strongly recommend reading this PR commit by commit.

The main goal was to improve field-flow for attributes, such that we use AttrRead/AttrWrite modeling, and clear content when writing new value to attribute.

Ended up improving a few of the json library modeling, since I added post-update nodes to arguments of calls we can't resolve as well 👍

Before we didn't show how we treated the value _after_ the check. But we
do actually handle this nicely 💪
All the same tests are present in `fieldflow/test.py`
I deleted the old tests, so it's very clear what tests to look for
I went with NormalDataflowTest to signify that if you don't know what
you're looking for, this is probably the one. I did not want to just
call it DataflowTest, since that becomes a big vague when there are also
`FlowTest.qll` and `MaximalFlowTest.qll` -- I'm open to renaming this
though 👍
I had to rewrite the SINK1-SINK7 definitions, since this new requirement
complained that we had to add this `MISSING: flow` annotation :D

Doing this implementation also revealed that there was a bug, since I
did not compare files when checking for these `MISSING:` annotations. So
fixed that up in the implementation for inline taint tests as well.

(extra whitespace in argumentPassing.py to avoid changing line numbers
for other tests)
I checked to see that the tests still works. If I deleted the `arg5`
annotation, it got failures:

```diff
diff --git a/python/ql/test/experimental/dataflow/coverage/argumentPassing.py b/python/ql/test/experimental/dataflow/coverage/argumentPassing.py
index e218bdd..71816c1e01 100644
--- a/python/ql/test/experimental/dataflow/coverage/argumentPassing.py
+++ b/python/ql/test/experimental/dataflow/coverage/argumentPassing.py
@@ -46,7 +46,7 @@ def argument_passing(
     c,
     d=arg4,  #$ arg4 func=argument_passing
     *,
-    e=arg5,  #$ arg5 func=argument_passing
+    e=arg5,
     f,
     **g,
 ):
diff --git a/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected b/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected
index e69de29..22037a40c3 100644
--- a/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected
+++ b/python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.expected
@@ -0,0 +1,2 @@
+| argumentPassing.py:49:7:49:10 | ControlFlowNode for arg5 | Unexpected result: arg5= |
+| argumentPassing.py:49:7:49:10 | ControlFlowNode for arg5 | Unexpected result: func=argument_passing |
```
I feel like they don't bring any value anymore, since we have the nice
inline expectation tests. If I'm wrong, happy to revert this commit
though.
@github-actions github-actions Bot added the Python label Feb 2, 2022
Note that this doesn't actually add the desired flow from setattr, due
to missing post-update note. This will be fixed in later commit.
Notice the strange thing with treating `mypkg.foo(42)` as a ClassCall,
but completely ignoring `mypkg.subpkg.bar(43)` -- due to having the two
`ClassValue`s:

- `Missing module attribute mypkg.foo`
- `Missing module attribute mypkg.subpkg`

But not `Missing module attribute mypkg.subpkg` with the current import
structure.
This means that DataFlowCall is only for resolvable calls, which might not seem
like a big thing in itself, but enables the next commit to actually work :P
@RasmusWL RasmusWL force-pushed the dataflow-improvements branch from 47db18d to dd3e94d Compare February 3, 2022 13:59
Besides solving the problem with `setattr`, it also solved some old
problems with json library modeling (yay).
I went with `minorAnalysis` instead of `majorAnalysis`, since I don't
think the impact of this change will be major (but that's just my gut
feeling).
@RasmusWL RasmusWL force-pushed the dataflow-improvements branch from dd3e94d to 14a1aa0 Compare February 4, 2022 11:01
@RasmusWL RasmusWL marked this pull request as ready for review February 4, 2022 11:57
@RasmusWL RasmusWL requested a review from a team as a code owner February 4, 2022 11:57
@RasmusWL RasmusWL requested a review from yoff February 4, 2022 11:57
@RasmusWL
Copy link
Copy Markdown
Member Author

RasmusWL commented Feb 8, 2022

Performance looks ok. There are some changes in py/meta/alerts/remote-flow-sources-reach, but they all seem to be due to the change in 5774459 (LHS of assign no longer an AttrRead, so taint doesn't flow the LHS any more)

Copy link
Copy Markdown
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many good things in here. It is great to see a clean-up of the test files in addition to the data-flow improvements 💪
(also nice trick with whitespaces)

Comment on lines -37 to -38
s = TAINTED_STRING
ensure_tainted(s) # $ tainted
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure these are without value. I think they check that the barrier guard did not conclude the entire block (everything dominated by the true edge) taint-free..

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I think the division between the tests in that file and test_logical is not super clear, but I've expanded the test in both files a bit, so everything should be covered with tests now 👍

@@ -1,59 +0,0 @@
from python.ql.test.experimental.dataflow.testDefinitions import *
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests in this file are not quite duplicates of those in test.py, since here it all happens in the global scope. It is possible that the tests could be arranged better, but just deleting these is perhaps not the best way.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does field-flow work in a different way when things are on the global scope?

I must admit I did not consider if global scope could play a part here, but if the only difference is that things are in a global scope vs. defined in functions, I don't see the extra value from having both.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global variables are distinct from local ones in our analysis, so I think we need to test both contexts.

Comment thread python/ql/test/experimental/dataflow/validTest.py
Comment thread python/ql/test/experimental/dataflow/validTest.py
Comment on lines -51 to -52
case 42 as x:
SINK(x) #$ flow="SOURCE, l:-2 -> x" flow="42, l:-1 -> x"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand why this is preferred. In fact, it might be good to not just test strings all the time? (42 is a documented source)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because you're matching against SOURCE, which is the string source -- so without this change, running validTest.py fails 😬

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha!

obj.foo = x


def test_example1():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The names here used to refer to the docs (but I can see there were no link in the code), but it might be time to move on from that...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Unless you're feeling very strongly about it, I think the new names are a bit more easy 😊

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, you have expanded the tests beyond those examples, so telling names are more valuable now.

Comment thread python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll Outdated
Comment thread python/ql/test/experimental/dataflow/coverage/argumentRoutingTest.ql Outdated
Copy link
Copy Markdown
Member Author

@RasmusWL RasmusWL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have been able to answer/fix all the review comments

Comment on lines -51 to -52
case 42 as x:
SINK(x) #$ flow="SOURCE, l:-2 -> x" flow="42, l:-1 -> x"
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because you're matching against SOURCE, which is the string source -- so without this change, running validTest.py fails 😬

Comment on lines +15 to +17
override predicate hasActualResult(Location location, string element, string tag, string value) {
super.hasActualResult(location, element, tag, value)
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, that was a leftover from being able to quick-eval it 😄 I've removed it again

@@ -1 +0,0 @@
import semmle.python.dataflow.new.internal.DataFlowImplConsistency::Consistency
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I've re-added it. I looked into adding consistency queries to our codeql test run setup, so that these would be part of ALL tests, but I ended up not doing this -- but that's why they were deleted.

obj.foo = x


def test_example1():
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Unless you're feeling very strongly about it, I think the new names are a bit more easy 😊

@@ -1,59 +0,0 @@
from python.ql.test.experimental.dataflow.testDefinitions import *
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does field-flow work in a different way when things are on the global scope?

I must admit I did not consider if global scope could play a part here, but if the only difference is that things are in a global scope vs. defined in functions, I don't see the extra value from having both.

Comment on lines -37 to -38
s = TAINTED_STRING
ensure_tainted(s) # $ tainted
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I think the division between the tests in that file and test_logical is not super clear, but I've expanded the test in both files a bit, so everything should be covered with tests now 👍

@RasmusWL RasmusWL requested a review from yoff February 18, 2022 13:14
Copy link
Copy Markdown
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nearly there. I do think that testing in both local and global scope is necessary, due to how different we treat those two contexts (separate classes for local and global variables, different behaviour with respect to import time and runtime). I agree, though, that this distinction is not at all clear in the original test-files. I look forward to seeing this merged :-)

I thought it was interesting that it did not propagate flow to the uses
inside the functions :O
Notice that these tests don't pass, to show how they differ in the next
commit!
@RasmusWL RasmusWL requested a review from yoff February 24, 2022 14:08
Copy link
Copy Markdown
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for organising the missing bits. I am happy to merge this once the tests pass 👍

@RasmusWL
Copy link
Copy Markdown
Member Author

Ohhh... I had not accounted for validTest.py actually running those tests 😂 I'll fix that up 👍

By giving all variables unique names

I also added a comment with the function name from the normal tests, so
its' easily visible what these tests are testing
Since that apparently impacts call graph resolution with points-to :O

Also interesting that global flow was only not working for those cases
because of the tricky ifs... still need to 100% figure out how those ifs
are messing up the analysis :|
TL;DR; we used a too low value for `--max-import-depth` :(
I think we should write our tests in a way that puts points-to in the
best condition to resolve calls. Although this specific change did not
change much, it should help set us up for success in the future 👍
@RasmusWL RasmusWL requested a review from yoff February 28, 2022 10:20
Apparently there are minor differences with `test-6-max-import-depth-2`
where under Python 2 `isfile_no_problem.py` still works as before
Copy link
Copy Markdown
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, quite a twist at the end. Do you want to leave the investigations in this open state with all the monitoring in place, or would you rather put it on a branch until it is concluded? I feel it could take some time to sort out, and that we might not want to invest too much effort on the points-to implementation, so perhaps it is indeed better close it off and just leave the evidence/monitoring as is...

@RasmusWL
Copy link
Copy Markdown
Member Author

Wow, quite a twist at the end. Do you want to leave the investigations in this open state with all the monitoring in place, or would you rather put it on a branch until it is concluded? I feel it could take some time to sort out, and that we might not want to invest too much effort on the points-to implementation, so perhaps it is indeed better close it off and just leave the evidence/monitoring as is...

My intention was not to do any more investigation, but just leave the "evidence" as is 😊

@RasmusWL RasmusWL requested a review from yoff February 28, 2022 13:57
Copy link
Copy Markdown
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the ride :-)

@yoff yoff merged commit d953382 into github:main Feb 28, 2022
@RasmusWL RasmusWL deleted the dataflow-improvements branch February 28, 2022 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants