mirror of
https://github.com/github/codeql.git
synced 2026-04-25 16:55:19 +02:00
Add case for exception flow
This commit is contained in:
@@ -13,63 +13,13 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
// import FileOpen
|
||||
import FileNotAlwaysClosedQuery
|
||||
|
||||
// /**
|
||||
// * Whether resource is opened and closed in in a matched pair of methods,
|
||||
// * either `__enter__` and `__exit__` or `__init__` and `__del__`
|
||||
// */
|
||||
// predicate opened_in_enter_closed_in_exit(ControlFlowNode open) {
|
||||
// file_not_closed_at_scope_exit(open) and
|
||||
// exists(FunctionValue entry, FunctionValue exit |
|
||||
// open.getScope() = entry.getScope() and
|
||||
// exists(ClassValue cls |
|
||||
// cls.declaredAttribute("__enter__") = entry and cls.declaredAttribute("__exit__") = exit
|
||||
// or
|
||||
// cls.declaredAttribute("__init__") = entry and cls.declaredAttribute("__del__") = exit
|
||||
// ) and
|
||||
// exists(AttrNode attr_open, AttrNode attrclose |
|
||||
// attr_open.getScope() = entry.getScope() and
|
||||
// attrclose.getScope() = exit.getScope() and
|
||||
// expr_is_open(attr_open.(DefinitionNode).getValue(), open) and
|
||||
// attr_open.getName() = attrclose.getName() and
|
||||
// close_method_call(_, attrclose)
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// predicate file_not_closed_at_scope_exit(ControlFlowNode open) {
|
||||
// exists(EssaVariable v |
|
||||
// BaseFlow::reaches_exit(v) and
|
||||
// var_is_open(v, open) and
|
||||
// not file_is_returned(v, open)
|
||||
// )
|
||||
// or
|
||||
// call_to_open(open) and
|
||||
// not exists(AssignmentDefinition def | def.getValue() = open) and
|
||||
// not exists(Return r | r.getValue() = open.getNode())
|
||||
// }
|
||||
// predicate file_not_closed_at_exception_exit(ControlFlowNode open, ControlFlowNode exit) {
|
||||
// exists(EssaVariable v |
|
||||
// exit.(RaisingNode).viableExceptionalExit(_, _) and
|
||||
// not closes_arg(exit, v.getSourceVariable()) and
|
||||
// not close_method_call(exit, v.getAUse().(NameNode)) and
|
||||
// var_is_open(v, open) and
|
||||
// v.getAUse() = exit.getAChild*()
|
||||
// )
|
||||
// }
|
||||
/* Check to see if a file is opened but not closed or returned */
|
||||
// from ControlFlowNode defn, string message
|
||||
// where
|
||||
// not opened_in_enter_closed_in_exit(defn) and
|
||||
// (
|
||||
// file_not_closed_at_scope_exit(defn) and message = "File is opened but is not closed."
|
||||
// or
|
||||
// not file_not_closed_at_scope_exit(defn) and
|
||||
// file_not_closed_at_exception_exit(defn, _) and
|
||||
// message = "File may not be closed if an exception is raised."
|
||||
// )
|
||||
// select defn.getNode(), message
|
||||
from FileOpen fo
|
||||
where fileNotAlwaysClosed(fo)
|
||||
select fo, "File is opened but is not closed."
|
||||
from FileOpen fo, string msg
|
||||
where
|
||||
fileNotClosed(fo) and
|
||||
msg = "File is opened but is not closed."
|
||||
or
|
||||
fileMayNotBeClosedOnException(fo, _) and
|
||||
msg = "File may not be closed if an exception is raised"
|
||||
select fo.getLocalSource(), msg
|
||||
|
||||
@@ -4,50 +4,133 @@ import python
|
||||
import semmle.python.dataflow.new.internal.DataFlowDispatch
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
abstract class FileOpen extends DataFlow::CfgNode { }
|
||||
/** A CFG node where a file is opened. */
|
||||
abstract class FileOpenSource extends DataFlow::CfgNode { }
|
||||
|
||||
class FileOpenCall extends FileOpen {
|
||||
FileOpenCall() { this = [API::builtin("open").getACall()] }
|
||||
/** 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()]
|
||||
}
|
||||
}
|
||||
|
||||
class FileWrapperClassCall extends FileOpen, DataFlow::CallCfgNode {
|
||||
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;
|
||||
|
||||
FileWrapperClassCall() {
|
||||
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 }
|
||||
}
|
||||
|
||||
abstract class FileClose extends DataFlow::CfgNode { }
|
||||
/** 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 {
|
||||
WithStatement() { exists(With w | this.asExpr() = w.getContextExpr()) }
|
||||
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 | DataFlow::localFlow(fo, DataFlow::exprNode(ret.getValue())))
|
||||
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()))
|
||||
}
|
||||
|
||||
predicate fileNotAlwaysClosed(FileOpen fo) {
|
||||
/** 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(FileWrapperClassCall fwc | fo = fwc.getWrapped())
|
||||
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())
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user