Skip to content

Commit 902b450

Browse files
committed
Python: Also model pathlib.Path().open().write()
And this transition to type-trackers also helped fix the missing path through function calls 👍
1 parent 39ec870 commit 902b450

File tree

3 files changed

+47
-13
lines changed

3 files changed

+47
-13
lines changed

python/ql/src/semmle/python/frameworks/Stdlib.qll

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -403,20 +403,49 @@ private module Stdlib {
403403
}
404404
}
405405

406+
/** Gets a reference to an open file. */
407+
private DataFlow::LocalSourceNode openFile(DataFlow::TypeTracker t, FileSystemAccess openCall) {
408+
t.start() and
409+
result = openCall and
410+
(
411+
openCall instanceof OpenCall
412+
or
413+
openCall instanceof PathLibOpenCall
414+
)
415+
or
416+
exists(DataFlow::TypeTracker t2 | result = openFile(t2, openCall).track(t2, t))
417+
}
418+
419+
/** Gets a reference to an open file. */
420+
private DataFlow::Node openFile(FileSystemAccess openCall) {
421+
openFile(DataFlow::TypeTracker::end(), openCall).flowsTo(result)
422+
}
423+
424+
/** Gets a reference to the `write` or `writelines` method on an open file. */
425+
private DataFlow::LocalSourceNode writeMethodOnOpenFile(
426+
DataFlow::TypeTracker t, FileSystemAccess openCall
427+
) {
428+
t.startInAttr(["write", "writelines"]) and
429+
result = openFile(openCall)
430+
or
431+
exists(DataFlow::TypeTracker t2 | result = writeMethodOnOpenFile(t2, openCall).track(t2, t))
432+
}
433+
434+
/** Gets a reference to the `write` or `writelines` method on an open file. */
435+
private DataFlow::Node writeMethodOnOpenFile(FileSystemAccess openCall) {
436+
writeMethodOnOpenFile(DataFlow::TypeTracker::end(), openCall).flowsTo(result)
437+
}
438+
406439
/** A call to the `write` or `writelines` method on an opened file, such as `open("foo", "w").write(...)`. */
407-
private class WriteOnOpenFile extends FileSystemWriteAccess::Range, DataFlow::CallCfgNode {
408-
WriteOnOpenFile() {
409-
this = getOpenFunctionRef().getReturn().getMember(["write", "writelines"]).getACall()
410-
}
440+
private class WriteCallOnOpenFile extends FileSystemWriteAccess::Range, DataFlow::CallCfgNode {
441+
FileSystemAccess openCall;
442+
443+
WriteCallOnOpenFile() { this.getFunction() = writeMethodOnOpenFile(openCall) }
411444

412445
override DataFlow::Node getAPathArgument() {
413-
// best effort attempt to give the path argument, that was initially given to the `open` call.
414-
exists(OpenCall openCall, DataFlow::AttrRead read |
415-
read.getAttributeName() in ["write", "writelines"] and
416-
openCall.flowsTo(read.getObject()) and
417-
read.(DataFlow::LocalSourceNode).flowsTo(this.getFunction()) and
418-
result = openCall.getAPathArgument()
419-
)
446+
// best effort attempt to give the path argument, that was initially given to the
447+
// `open` call.
448+
result = openCall.getAPathArgument()
420449
}
421450

422451
override DataFlow::Node getADataNode() { result in [this.getArg(0), this.getArgByName("data")] }
@@ -1051,6 +1080,11 @@ private module Stdlib {
10511080
override DataFlow::Node getADataNode() { result in [this.getArg(0), this.getArgByName("data")] }
10521081
}
10531082

1083+
/** A call to the `open` method on a `pathlib.Path` instance. */
1084+
private class PathLibOpenCall extends PathlibFileAccess {
1085+
PathLibOpenCall() { attrbuteName = "open" }
1086+
}
1087+
10541088
/** An additional taint steps for objects of type `pathlib.Path` */
10551089
private class PathlibPathTaintStep extends TaintTracking::AdditionalTaintStep {
10561090
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {

python/ql/test/library-tests/frameworks/stdlib-py3/FileSystemAccess.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
p.write_bytes(b"hello") # $ getAPathArgument=p fileWriteData=b"hello"
1515
p.write_text("hello") # $ getAPathArgument=p fileWriteData="hello"
16-
p.open("wt").write("hello") # $ getAPathArgument=p MISSING: fileWriteData="hello"
16+
p.open("wt").write("hello") # $ getAPathArgument=p fileWriteData="hello"
1717

1818
name = windows.parent.name
1919
o = open

python/ql/test/library-tests/frameworks/stdlib/FileSystemAccess.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@
2424

2525

2626
def through_function(open_file):
27-
open_file.write("foo") # $ fileWriteData="foo" MISSING: getAPathArgument="path"
27+
open_file.write("foo") # $ fileWriteData="foo" getAPathArgument="path"
2828

2929
through_function(f)

0 commit comments

Comments
 (0)