-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathFileNotAlwaysClosedQuery.qll
More file actions
136 lines (115 loc) · 4.34 KB
/
FileNotAlwaysClosedQuery.qll
File metadata and controls
136 lines (115 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/** Definitions for reasoning about whether files are closed. */
import python
import semmle.python.dataflow.new.internal.DataFlowDispatch
import semmle.python.ApiGraphs
/** A CFG node where a file is opened. */
abstract class FileOpenSource extends DataFlow::CfgNode { }
/** A call to the builtin `open` or `os.open`. */
class FileOpenCall extends FileOpenSource {
FileOpenCall() {
this = [API::builtin("open").getACall(), API::moduleImport("os").getMember("open").getACall()]
}
}
private DataFlow::TypeTrackingNode fileOpenInstance(DataFlow::TypeTracker t) {
t.start() and
result instanceof FileOpenSource
or
exists(DataFlow::TypeTracker t2 | result = fileOpenInstance(t2).track(t2, t))
}
/** A call that returns an instance of an open file object. */
class FileOpen extends DataFlow::CallCfgNode {
FileOpen() { fileOpenInstance(DataFlow::TypeTracker::end()).flowsTo(this) }
/** Gets the local source of this file object, through any wrapper calls. */
FileOpen getLocalSource() {
if this instanceof FileWrapperCall
then result = this.(FileWrapperCall).getWrapped().getLocalSource()
else result = this
}
}
/** A call that may wrap a file object in a wrapper class or `os.fdopen`. */
class FileWrapperCall extends FileOpenSource, DataFlow::CallCfgNode {
FileOpen wrapped;
FileWrapperCall() {
wrapped = this.getArg(_).getALocalSource() and
this.getFunction() = classTracker(_)
or
wrapped = this.getArg(0) and
this = API::moduleImport("os").getMember("fdopen").getACall()
}
/** Gets the file that this call wraps. */
FileOpen getWrapped() { result = wrapped }
}
/** A node where a file is closed. */
abstract class FileClose extends DataFlow::CfgNode {
/** Holds if this file close will occur if an exception is thrown at `e`. */
predicate guardsExceptions(Expr e) {
exists(Try try |
e = try.getAStmt().getAChildNode*() and
(
this.asExpr() = try.getAHandler().getAChildNode*()
or
this.asExpr() = try.getAFinalstmt().getAChildNode*()
)
)
}
}
/** A call to the `.close()` method of a file object. */
class FileCloseCall extends FileClose {
FileCloseCall() { exists(DataFlow::MethodCallNode mc | mc.calls(this, "close")) }
}
/** A call to `os.close`. */
class OsCloseCall extends FileClose {
OsCloseCall() { this = API::moduleImport("os").getMember("close").getACall().getArg(0) }
}
/** A `with` statement. */
class WithStatement extends FileClose {
With w;
WithStatement() { this.asExpr() = w.getContextExpr() }
override predicate guardsExceptions(Expr e) {
super.guardsExceptions(e)
or
e = w.getAStmt().getAChildNode*()
}
}
/** Holds if an exception may be raised at `node` if it is a file object. */
private predicate mayRaiseWithFile(DataFlow::CfgNode node) {
// Currently just consider any method called on `node`; e.g. `file.write()`; as potentially raising an exception
exists(DataFlow::MethodCallNode mc | node = mc.getObject()) and
not node instanceof FileOpen and
not node instanceof FileClose
}
/** Holds if the file opened at `fo` is closed. */
predicate fileIsClosed(FileOpen fo) { exists(FileClose fc | DataFlow::localFlow(fo, fc)) }
/** Holds if the file opened at `fo` is returned to the caller. This makes the caller responsible for closing the file. */
predicate fileIsReturned(FileOpen fo) {
exists(Return ret, Expr retVal |
(
retVal = ret.getValue()
or
retVal = ret.getValue().(List).getAnElt()
or
retVal = ret.getValue().(Tuple).getAnElt()
) and
DataFlow::localFlow(fo, DataFlow::exprNode(retVal))
)
}
/** Holds if the file opened at `fo` is stored in a field. We assume that another method is then responsible for closing the file. */
predicate fileIsStoredInField(FileOpen fo) {
exists(DataFlow::AttrWrite aw | DataFlow::localFlow(fo, aw.getValue()))
}
/** Holds if the file opened at `fo` is not closed, and is expected to be closed. */
predicate fileNotClosed(FileOpen fo) {
not fileIsClosed(fo) and
not fileIsReturned(fo) and
not fileIsStoredInField(fo) and
not exists(FileWrapperCall fwc | fo = fwc.getWrapped())
}
predicate fileMayNotBeClosedOnException(FileOpen fo, DataFlow::Node raises) {
fileIsClosed(fo) and
mayRaiseWithFile(raises) and
DataFlow::localFlow(fo, raises) and
not exists(FileClose fc |
DataFlow::localFlow(fo, fc) and
fc.guardsExceptions(raises.asExpr())
)
}