Files
codeql/python/ql/lib/semmle/python/frameworks/PEP249.qll
Rasmus Wriedt Larsen 7b3f710e91 Python: Model aiosqlite
2023-03-22 15:51:47 +01:00

328 lines
12 KiB
Plaintext

/**
* Provides classes modeling PEP 249.
* See https://www.python.org/dev/peps/pep-0249/.
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
/**
* Provides classes modeling database interfaces following PEP 249.
* See https://www.python.org/dev/peps/pep-0249/.
*/
module PEP249 {
/**
* An API graph node representing a module that implements PEP 249.
*/
abstract class PEP249ModuleApiNode extends API::Node {
/** Gets a string representation of this element. */
override string toString() { result = this.(API::Node).toString() }
}
/**
* An API graph node representing a database connection.
*/
abstract class DatabaseConnection extends API::Node {
/** Gets a string representation of this element. */
override string toString() { result = this.(API::Node).toString() }
}
private class DefaultDatabaseConnection extends DatabaseConnection {
DefaultDatabaseConnection() {
this = any(PEP249ModuleApiNode mod).getMember("connect").getReturn()
}
}
/**
* An API graph node representing a database cursor.
*/
abstract class DatabaseCursor extends API::Node {
/** Gets a string representation of this element. */
override string toString() { result = this.(API::Node).toString() }
}
private class DefaultDatabaseCursor extends DatabaseCursor {
DefaultDatabaseCursor() { this = any(DatabaseConnection conn).getMember("cursor").getReturn() }
}
private string getSqlKwargName() {
result in ["sql", "statement", "operation", "query", "query_string", "sql_script"]
}
private string getExecuteMethodName() {
result in ["execute", "executemany", "executescript", "execute_insert", "execute_fetchall"]
}
/**
* A call to an execute method on a database cursor or a connection, such as `execute`
* or `executemany`.
*
* See
* - https://peps.python.org/pep-0249/#execute
* - https://peps.python.org/pep-0249/#executemany
*
* Note: While `execute` method on a connection is not part of PEP249, if it is used, we
* recognize it as an alias for constructing a cursor and calling `execute` on it.
*/
private class ExecuteMethodCall extends SqlExecution::Range, API::CallNode {
ExecuteMethodCall() {
exists(API::Node start |
start instanceof DatabaseCursor or start instanceof DatabaseConnection
|
this = start.getMember(getExecuteMethodName()).getACall()
)
}
override DataFlow::Node getSql() {
result in [this.getArg(0), this.getArgByName(getSqlKwargName()),]
}
}
// ---------------------------------------------------------------------------
// asyncio implementations
// ---------------------------------------------------------------------------
//
// we differentiate between normal and asyncio implementations, since we model the
// `execute` call differently -- as a SqlExecution vs SqlConstruction, since the SQL
// is only executed in asyncio after being awaited (which might happen in something
// like `asyncio.gather`)
/**
* An API graph node representing a module that implements PEP 249 using asyncio.
*/
abstract class AsyncPEP249ModuleApiNode extends API::Node {
/** Gets a string representation of this element. */
override string toString() { result = this.(API::Node).toString() }
}
/**
* An API graph node representing a asyncio database connection (after being awaited).
*/
abstract class AsyncDatabaseConnection extends API::Node {
/** Gets a string representation of this element. */
override string toString() { result = this.(API::Node).toString() }
}
private class DefaultAsyncDatabaseConnection extends AsyncDatabaseConnection {
DefaultAsyncDatabaseConnection() {
this = any(AsyncPEP249ModuleApiNode mod).getMember("connect").getReturn().getAwaited()
}
}
/**
* An API graph node representing a asyncio database cursor (after being awaited).
*/
abstract class AsyncDatabaseCursor extends API::Node {
/** Gets a string representation of this element. */
override string toString() { result = this.(API::Node).toString() }
}
private class DefaultAsyncDatabaseCursor extends AsyncDatabaseCursor {
DefaultAsyncDatabaseCursor() {
this = any(AsyncDatabaseConnection conn).getMember("cursor").getReturn().getAwaited()
}
}
/**
* A call to an execute method on an asyncio database cursor or an asyncio connection,
* such as `execute` or `executemany`.
*
* (This is not an SqlExecution, since that only happens when the coroutine is
* awaited)
*
* See ExecuteMethodCall for more details.
*/
private class AsyncExecuteMethodCall extends SqlConstruction::Range, API::CallNode {
AsyncExecuteMethodCall() {
exists(API::Node start |
start instanceof AsyncDatabaseCursor or start instanceof AsyncDatabaseConnection
|
this = start.getMember(getExecuteMethodName()).getACall()
)
}
override DataFlow::Node getSql() {
result in [this.getArg(0), this.getArgByName(getSqlKwargName()),]
}
}
/** Actual execution of the AsyncExecuteMethodCall coroutine. */
private class AwaitedAsyncExecuteMethodCall extends SqlExecution::Range {
AsyncExecuteMethodCall execute;
AwaitedAsyncExecuteMethodCall() { this = execute.getReturn().getAwaited().asSource() }
override DataFlow::Node getSql() { result = execute.getSql() }
}
// ---------------------------------------------------------------------------
// old impl
// ---------------------------------------------------------------------------
// the goal is to deprecate it in favour of the API graph version, but currently this
// requires a rewrite of the Peewee modeling, which depends on rewriting the
// instance/instance-source stuff to use API graphs instead.
// so is postponed for now.
/** Gets a reference to the `connect` function of a module that implements PEP 249. */
DataFlow::Node connect() {
result = any(PEP249ModuleApiNode a).getMember("connect").getAValueReachableFromSource()
}
/**
* Provides models for database connections (following PEP 249).
*
* See https://www.python.org/dev/peps/pep-0249/#connection-objects.
*/
module Connection {
/**
* A source of database connections (following PEP 249), extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by external
* libraries.
*
* Use the predicate `Connection::instance()` to get references to database connections (following PEP 249).
*
* Extend this class if the module implementing PEP 249 offers more direct ways to obtain
* a connection than going through `connect`.
*/
abstract class InstanceSource extends DataFlow::Node { }
/** A call to the `connect` function of a module that implements PEP 249. */
private class ConnectCall extends InstanceSource, DataFlow::CallCfgNode {
ConnectCall() { this.getFunction() = connect() }
}
/** Gets a reference to a database connection (following PEP 249). */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to a database connection (following PEP 249). */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
}
/**
* Provides models for database cursors (following PEP 249).
*
* These are returned by the `cursor` method on a database connection.
* See https://www.python.org/dev/peps/pep-0249/#cursor.
*/
module Cursor {
/**
* A source of database cursors (following PEP 249), extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by external
* libraries.
*
* Use the predicate `Cursor::instance()` to get references to database cursors (following PEP 249).
*
* Extend this class if the module implementing PEP 249 offers more direct ways to obtain
* a connection than going through `connect`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
/** Gets a reference to a database cursor. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to a database cursor. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/** Gets a reference to the `cursor` method on a database connection. */
private DataFlow::TypeTrackingNode methodRef(DataFlow::TypeTracker t) {
t.startInAttr("cursor") and
result = Connection::instance()
or
exists(DataFlow::TypeTracker t2 | result = methodRef(t2).track(t2, t))
}
/** Gets a reference to the `cursor` method on a database connection. */
DataFlow::Node methodRef() { methodRef(DataFlow::TypeTracker::end()).flowsTo(result) }
/** A call to the `cursor` method on a database connection */
private class CursorCall extends InstanceSource, DataFlow::CallCfgNode {
CursorCall() { this.getFunction() = methodRef() }
}
}
/**
* Gets a reference to the `execute` method on a cursor (or on a connection).
*
* Note: while `execute` method on a connection is not part of PEP249, if it is used, we
* recognize it as an alias for constructing a cursor and calling `execute` on it.
*
* See https://peps.python.org/pep-0249/#execute.
*/
private DataFlow::TypeTrackingNode execute(DataFlow::TypeTracker t) {
t.startInAttr("execute") and
result in [Cursor::instance(), Connection::instance()]
or
exists(DataFlow::TypeTracker t2 | result = execute(t2).track(t2, t))
}
/**
* Gets a reference to the `execute` method on a cursor (or on a connection).
*
* Note: while `execute` method on a connection is not part of PEP249, if it is used, we
* recognize it as an alias for constructing a cursor and calling `execute` on it.
*
* See https://peps.python.org/pep-0249/#execute.
*/
DataFlow::Node execute() { execute(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* A call to the `execute` method on a cursor or a connection.
*
* See https://peps.python.org/pep-0249/#execute
*
* Note: While `execute` method on a connection is not part of PEP249, if it is used, we
* recognize it as an alias for constructing a cursor and calling `execute` on it.
*/
private class ExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {
ExecuteCall() {
this.getFunction() = execute() and
not this instanceof ExecuteMethodCall
}
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] }
}
private DataFlow::TypeTrackingNode executemany(DataFlow::TypeTracker t) {
t.startInAttr("executemany") and
result in [Cursor::instance(), Connection::instance()]
or
exists(DataFlow::TypeTracker t2 | result = executemany(t2).track(t2, t))
}
private DataFlow::Node executemany() { executemany(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* A call to the `executemany` method on a cursor or a connection.
*
* See https://peps.python.org/pep-0249/#executemany
*
* Note: While `executemany` method on a connection is not part of PEP249, if it is used, we
* recognize it as an alias for constructing a cursor and calling `executemany` on it.
*/
private class ExecutemanyCall extends SqlExecution::Range, DataFlow::CallCfgNode {
ExecutemanyCall() {
this.getFunction() = executemany() and
not this instanceof ExecuteMethodCall
}
override DataFlow::Node getSql() {
result in [this.getArg(0), this.getArgByName(getSqlKwargName())]
}
}
}