mirror of
https://github.com/github/codeql.git
synced 2025-12-22 11:46:32 +01:00
Merge pull request #12636 from RasmusWL/sql-modeling
Python: Some more SQL modeling
This commit is contained in:
@@ -223,7 +223,9 @@ and the CodeQL library pack ``codeql/python-all`` (`changelog <https://github.co
|
|||||||
aioch, Database
|
aioch, Database
|
||||||
aiomysql, Database
|
aiomysql, Database
|
||||||
aiopg, Database
|
aiopg, Database
|
||||||
|
aiosqlite, Database
|
||||||
asyncpg, Database
|
asyncpg, Database
|
||||||
|
cassandra-driver, Database
|
||||||
clickhouse-driver, Database
|
clickhouse-driver, Database
|
||||||
cx_Oracle, Database
|
cx_Oracle, Database
|
||||||
mysql-connector-python, Database
|
mysql-connector-python, Database
|
||||||
@@ -233,9 +235,9 @@ and the CodeQL library pack ``codeql/python-all`` (`changelog <https://github.co
|
|||||||
oracledb, Database
|
oracledb, Database
|
||||||
phoenixdb, Database
|
phoenixdb, Database
|
||||||
psycopg2, Database
|
psycopg2, Database
|
||||||
pyodbc, Database
|
|
||||||
pymssql, Database
|
pymssql, Database
|
||||||
PyMySQL, Database
|
PyMySQL, Database
|
||||||
|
pyodbc, Database
|
||||||
sqlite3, Database
|
sqlite3, Database
|
||||||
Flask-SQLAlchemy, Database ORM
|
Flask-SQLAlchemy, Database ORM
|
||||||
peewee, Database ORM
|
peewee, Database ORM
|
||||||
@@ -276,4 +278,3 @@ and the CodeQL library pack ``codeql/ruby-all`` (`changelog <https://github.com/
|
|||||||
Ruby on Rails, Web framework
|
Ruby on Rails, Web framework
|
||||||
rubyzip, Compression library
|
rubyzip, Compression library
|
||||||
typhoeus, HTTP client
|
typhoeus, HTTP client
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
category: minorAnalysis
|
||||||
|
---
|
||||||
|
* Added modeling of SQL execution in the packages `sqlite3.dbapi2`, `cassandra-driver`, `aiosqlite`, and the functions `sqlite3.Connection.executescript`/`sqlite3.Cursor.executescript` and `asyncpg.connection.connect()`.
|
||||||
@@ -3,12 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// If you add modeling of a new framework/library, remember to add it to the docs in
|
// If you add modeling of a new framework/library, remember to add it to the docs in
|
||||||
// `docs/codeql/support/reusables/frameworks.rst`
|
// `docs/codeql/reusables/supported-frameworks.rst`
|
||||||
private import semmle.python.frameworks.Aioch
|
private import semmle.python.frameworks.Aioch
|
||||||
private import semmle.python.frameworks.Aiohttp
|
private import semmle.python.frameworks.Aiohttp
|
||||||
private import semmle.python.frameworks.Aiomysql
|
private import semmle.python.frameworks.Aiomysql
|
||||||
|
private import semmle.python.frameworks.Aiosqlite
|
||||||
private import semmle.python.frameworks.Aiopg
|
private import semmle.python.frameworks.Aiopg
|
||||||
private import semmle.python.frameworks.Asyncpg
|
private import semmle.python.frameworks.Asyncpg
|
||||||
|
private import semmle.python.frameworks.CassandraDriver
|
||||||
private import semmle.python.frameworks.ClickhouseDriver
|
private import semmle.python.frameworks.ClickhouseDriver
|
||||||
private import semmle.python.frameworks.Cryptodome
|
private import semmle.python.frameworks.Cryptodome
|
||||||
private import semmle.python.frameworks.Cryptography
|
private import semmle.python.frameworks.Cryptography
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ private import python
|
|||||||
private import semmle.python.dataflow.new.DataFlow
|
private import semmle.python.dataflow.new.DataFlow
|
||||||
private import semmle.python.Concepts
|
private import semmle.python.Concepts
|
||||||
private import semmle.python.ApiGraphs
|
private import semmle.python.ApiGraphs
|
||||||
|
private import semmle.python.frameworks.PEP249
|
||||||
|
|
||||||
/** Provides models for the `aiomysql` PyPI package. */
|
/** Provides models for the `aiomysql` PyPI package. */
|
||||||
private module Aiomysql {
|
private module Aiomysql {
|
||||||
private import semmle.python.internal.Awaited
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a `ConnectionPool` that is created when the result of `aiomysql.create_pool()` is awaited.
|
* Gets a `ConnectionPool` that is created when the result of `aiomysql.create_pool()` is awaited.
|
||||||
* See https://aiomysql.readthedocs.io/en/stable/pool.html
|
* See https://aiomysql.readthedocs.io/en/stable/pool.html
|
||||||
@@ -23,49 +22,29 @@ private module Aiomysql {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a `Connection` that is created when
|
* A Connection that is created when
|
||||||
* - the result of `aiomysql.connect()` is awaited.
|
* - the result of `aiomysql.connect()` is awaited.
|
||||||
* - the result of calling `acquire` on a `ConnectionPool` is awaited.
|
* - the result of calling `acquire` on a `ConnectionPool` is awaited.
|
||||||
* See https://aiomysql.readthedocs.io/en/stable/connection.html#connection
|
* See
|
||||||
|
* - https://aiomysql.readthedocs.io/en/stable/connection.html#connection
|
||||||
|
* - https://aiomysql.readthedocs.io/en/stable/pool.html#Pool.acquire
|
||||||
*/
|
*/
|
||||||
API::Node connection() {
|
class AiomysqlConnection extends PEP249::AsyncDatabaseConnection {
|
||||||
result = API::moduleImport("aiomysql").getMember("connect").getReturn().getAwaited()
|
AiomysqlConnection() {
|
||||||
or
|
this = API::moduleImport("aiomysql").getMember("connect").getReturn().getAwaited()
|
||||||
result = connectionPool().getMember("acquire").getReturn().getAwaited()
|
or
|
||||||
|
this = connectionPool().getMember("acquire").getReturn().getAwaited()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a `Cursor` that is created when
|
* An additional cursor, that is created when
|
||||||
* - the result of calling `cursor` on a `ConnectionPool` is awaited.
|
* - the result of calling `cursor` on a `ConnectionPool` is awaited.
|
||||||
* - the result of calling `cursor` on a `Connection` is awaited.
|
* See
|
||||||
* See https://aiomysql.readthedocs.io/en/stable/cursors.html
|
* - https://aiomysql.readthedocs.io/en/stable/pool.html##Pool.cursor
|
||||||
*/
|
*/
|
||||||
API::Node cursor() {
|
class AiomysqlCursor extends PEP249::AsyncDatabaseCursor {
|
||||||
result = connectionPool().getMember("cursor").getReturn().getAwaited()
|
AiomysqlCursor() { this = connectionPool().getMember("cursor").getReturn().getAwaited() }
|
||||||
or
|
|
||||||
result = connection().getMember("cursor").getReturn().getAwaited()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A query. Calling `execute` on a `Cursor` constructs a query.
|
|
||||||
* See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
|
|
||||||
*/
|
|
||||||
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
|
|
||||||
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
|
|
||||||
|
|
||||||
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").asSink() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An awaited query. Awaiting the result of calling `execute` executes the query.
|
|
||||||
* See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
|
|
||||||
*/
|
|
||||||
class AwaitedCursorExecuteCall extends SqlExecution::Range {
|
|
||||||
CursorExecuteCall executeCall;
|
|
||||||
|
|
||||||
AwaitedCursorExecuteCall() { this = executeCall.getReturn().getAwaited().asSource() }
|
|
||||||
|
|
||||||
override DataFlow::Node getSql() { result = executeCall.getSql() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ private import python
|
|||||||
private import semmle.python.dataflow.new.DataFlow
|
private import semmle.python.dataflow.new.DataFlow
|
||||||
private import semmle.python.Concepts
|
private import semmle.python.Concepts
|
||||||
private import semmle.python.ApiGraphs
|
private import semmle.python.ApiGraphs
|
||||||
|
private import semmle.python.frameworks.PEP249
|
||||||
|
|
||||||
/** Provides models for the `aiopg` PyPI package. */
|
/** Provides models for the `aiopg` PyPI package. */
|
||||||
private module Aiopg {
|
private module Aiopg {
|
||||||
private import semmle.python.internal.Awaited
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a `ConnectionPool` that is created when the result of `aiopg.create_pool()` is awaited.
|
* Gets a `ConnectionPool` that is created when the result of `aiopg.create_pool()` is awaited.
|
||||||
* See https://aiopg.readthedocs.io/en/stable/core.html#pool
|
* See https://aiopg.readthedocs.io/en/stable/core.html#pool
|
||||||
@@ -23,49 +22,29 @@ private module Aiopg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a `Connection` that is created when
|
* A Connection that is created when
|
||||||
* - the result of `aiopg.connect()` is awaited.
|
* - the result of `aiopg.connect()` is awaited.
|
||||||
* - the result of calling `acquire` on a `ConnectionPool` is awaited.
|
* - the result of calling `acquire` on a `ConnectionPool` is awaited.
|
||||||
* See https://aiopg.readthedocs.io/en/stable/core.html#connection
|
* See
|
||||||
|
* - https://aiopg.readthedocs.io/en/stable/core.html#connection
|
||||||
|
* - https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Pool.acquire
|
||||||
*/
|
*/
|
||||||
API::Node connection() {
|
class AiopgConnection extends PEP249::AsyncDatabaseConnection {
|
||||||
result = API::moduleImport("aiopg").getMember("connect").getReturn().getAwaited()
|
AiopgConnection() {
|
||||||
or
|
this = API::moduleImport("aiopg").getMember("connect").getReturn().getAwaited()
|
||||||
result = connectionPool().getMember("acquire").getReturn().getAwaited()
|
or
|
||||||
|
this = connectionPool().getMember("acquire").getReturn().getAwaited()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a `Cursor` that is created when
|
* An additional cursor, that is created when
|
||||||
* - the result of calling `cursor` on a `ConnectionPool` is awaited.
|
* - the result of calling `cursor` on a `ConnectionPool` is awaited.
|
||||||
* - the result of calling `cursor` on a `Connection` is awaited.
|
* See
|
||||||
* See https://aiopg.readthedocs.io/en/stable/core.html#cursor
|
* - https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Pool.cursor
|
||||||
*/
|
*/
|
||||||
API::Node cursor() {
|
class AiopgCursor extends PEP249::AsyncDatabaseCursor {
|
||||||
result = connectionPool().getMember("cursor").getReturn().getAwaited()
|
AiopgCursor() { this = connectionPool().getMember("cursor").getReturn().getAwaited() }
|
||||||
or
|
|
||||||
result = connection().getMember("cursor").getReturn().getAwaited()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A query. Calling `execute` on a `Cursor` constructs a query.
|
|
||||||
* See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
|
|
||||||
*/
|
|
||||||
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
|
|
||||||
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
|
|
||||||
|
|
||||||
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").asSink() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An awaited query. Awaiting the result of calling `execute` executes the query.
|
|
||||||
* See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
|
|
||||||
*/
|
|
||||||
class AwaitedCursorExecuteCall extends SqlExecution::Range {
|
|
||||||
CursorExecuteCall execute;
|
|
||||||
|
|
||||||
AwaitedCursorExecuteCall() { this = execute.getReturn().getAwaited().asSource() }
|
|
||||||
|
|
||||||
override DataFlow::Node getSql() { result = execute.getSql() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
39
python/ql/lib/semmle/python/frameworks/Aiosqlite.qll
Normal file
39
python/ql/lib/semmle/python/frameworks/Aiosqlite.qll
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Provides classes modeling security-relevant aspects of the `aiosqlite` PyPI package.
|
||||||
|
* See
|
||||||
|
* - https://pypi.org/project/aiosqlite/
|
||||||
|
*/
|
||||||
|
|
||||||
|
private import python
|
||||||
|
private import semmle.python.dataflow.new.DataFlow
|
||||||
|
private import semmle.python.Concepts
|
||||||
|
private import semmle.python.ApiGraphs
|
||||||
|
private import semmle.python.frameworks.PEP249
|
||||||
|
|
||||||
|
/** Provides models for the `aiosqlite` PyPI package. */
|
||||||
|
private module Aiosqlite {
|
||||||
|
/**
|
||||||
|
* A model of `aiosqlite` as a module that implements PEP 249 using asyncio, providing
|
||||||
|
* ways to execute SQL statements against a database.
|
||||||
|
*/
|
||||||
|
class AiosqlitePEP249 extends PEP249::AsyncPEP249ModuleApiNode {
|
||||||
|
AiosqlitePEP249() { this = API::moduleImport("aiosqlite") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An additional cursor, that is return from the coroutine Connection.execute,
|
||||||
|
* see https://aiosqlite.omnilib.dev/en/latest/api.html#aiosqlite.Connection.execute
|
||||||
|
*/
|
||||||
|
class AiosqliteCursor extends PEP249::AsyncDatabaseCursor {
|
||||||
|
AiosqliteCursor() {
|
||||||
|
this =
|
||||||
|
API::moduleImport("aiosqlite")
|
||||||
|
.getMember("connect")
|
||||||
|
.getReturn()
|
||||||
|
.getAwaited()
|
||||||
|
.getMember("execute")
|
||||||
|
.getReturn()
|
||||||
|
.getAwaited()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ private module Asyncpg {
|
|||||||
// * - the result of `asyncpg.connect()` is awaited.
|
// * - the result of `asyncpg.connect()` is awaited.
|
||||||
// * - the result of calling `acquire` on a `ConnectionPool` is awaited.
|
// * - the result of calling `acquire` on a `ConnectionPool` is awaited.
|
||||||
"asyncpg.Connection;asyncpg;Member[connect].ReturnValue.Awaited",
|
"asyncpg.Connection;asyncpg;Member[connect].ReturnValue.Awaited",
|
||||||
|
"asyncpg.Connection;asyncpg;Member[connection].Member[connect].ReturnValue.Awaited",
|
||||||
"asyncpg.Connection;asyncpg.ConnectionPool;Member[acquire].ReturnValue.Awaited",
|
"asyncpg.Connection;asyncpg.ConnectionPool;Member[acquire].ReturnValue.Awaited",
|
||||||
// Creating an internal `~Connection` type that contains both `Connection` and `ConnectionPool`.
|
// Creating an internal `~Connection` type that contains both `Connection` and `ConnectionPool`.
|
||||||
"asyncpg.~Connection;asyncpg.Connection;", //
|
"asyncpg.~Connection;asyncpg.Connection;", //
|
||||||
|
|||||||
61
python/ql/lib/semmle/python/frameworks/CassandraDriver.qll
Normal file
61
python/ql/lib/semmle/python/frameworks/CassandraDriver.qll
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Provides classes modeling security-relevant aspects of the `cassandra-driver` PyPI package.
|
||||||
|
* See https://pypi.org/project/cassandra-driver/
|
||||||
|
*/
|
||||||
|
|
||||||
|
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
|
||||||
|
private import semmle.python.frameworks.PEP249
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides models for the `cassandra-driver` PyPI package.
|
||||||
|
* See https://pypi.org/project/cassandra-driver/
|
||||||
|
*/
|
||||||
|
private module CassandraDriver {
|
||||||
|
/**
|
||||||
|
* A cassandra cluster session.
|
||||||
|
*
|
||||||
|
* see
|
||||||
|
* - https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Cluster.connect
|
||||||
|
* - https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Session
|
||||||
|
*/
|
||||||
|
API::Node session() {
|
||||||
|
result =
|
||||||
|
API::moduleImport("cassandra")
|
||||||
|
.getMember("cluster")
|
||||||
|
.getMember("Cluster")
|
||||||
|
.getReturn()
|
||||||
|
.getMember("connect")
|
||||||
|
.getReturn()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* see https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Session.execute
|
||||||
|
*/
|
||||||
|
class CassandraSessionExecuteCall extends SqlExecution::Range, API::CallNode {
|
||||||
|
CassandraSessionExecuteCall() { this = session().getMember("execute").getACall() }
|
||||||
|
|
||||||
|
override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* see https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Session.execute_async
|
||||||
|
*/
|
||||||
|
class CassandraSessionExecuteAsyncCall extends SqlConstruction::Range, API::CallNode {
|
||||||
|
CassandraSessionExecuteAsyncCall() { this = session().getMember("execute_async").getACall() }
|
||||||
|
|
||||||
|
override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* see https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Session.prepare
|
||||||
|
*/
|
||||||
|
class CassandraSessionPrepareCall extends SqlConstruction::Range, API::CallNode {
|
||||||
|
CassandraSessionPrepareCall() { this = session().getMember("prepare").getACall() }
|
||||||
|
|
||||||
|
override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -561,8 +561,8 @@ module PrivateDjango {
|
|||||||
API::Node connection() { result = db().getMember("connection") }
|
API::Node connection() { result = db().getMember("connection") }
|
||||||
|
|
||||||
/** A `django.db.connection` is a PEP249 compliant DB connection. */
|
/** A `django.db.connection` is a PEP249 compliant DB connection. */
|
||||||
class DjangoDbConnection extends PEP249::Connection::InstanceSource {
|
class DjangoDbConnection extends PEP249::DatabaseConnection {
|
||||||
DjangoDbConnection() { this = connection().asSource() }
|
DjangoDbConnection() { this = connection() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -22,6 +22,148 @@ module PEP249 {
|
|||||||
override string toString() { result = this.(API::Node).toString() }
|
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. */
|
/** Gets a reference to the `connect` function of a module that implements PEP 249. */
|
||||||
DataFlow::Node connect() {
|
DataFlow::Node connect() {
|
||||||
result = any(PEP249ModuleApiNode a).getMember("connect").getAValueReachableFromSource()
|
result = any(PEP249ModuleApiNode a).getMember("connect").getAValueReachableFromSource()
|
||||||
@@ -147,7 +289,10 @@ module PEP249 {
|
|||||||
* recognize it as an alias for constructing a cursor and calling `execute` on it.
|
* recognize it as an alias for constructing a cursor and calling `execute` on it.
|
||||||
*/
|
*/
|
||||||
private class ExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {
|
private class ExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {
|
||||||
ExecuteCall() { this.getFunction() = execute() }
|
ExecuteCall() {
|
||||||
|
this.getFunction() = execute() and
|
||||||
|
not this instanceof ExecuteMethodCall
|
||||||
|
}
|
||||||
|
|
||||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] }
|
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] }
|
||||||
}
|
}
|
||||||
@@ -170,8 +315,13 @@ module PEP249 {
|
|||||||
* recognize it as an alias for constructing a cursor and calling `executemany` on it.
|
* recognize it as an alias for constructing a cursor and calling `executemany` on it.
|
||||||
*/
|
*/
|
||||||
private class ExecutemanyCall extends SqlExecution::Range, DataFlow::CallCfgNode {
|
private class ExecutemanyCall extends SqlExecution::Range, DataFlow::CallCfgNode {
|
||||||
ExecutemanyCall() { this.getFunction() = executemany() }
|
ExecutemanyCall() {
|
||||||
|
this.getFunction() = executemany() and
|
||||||
|
not this instanceof ExecuteMethodCall
|
||||||
|
}
|
||||||
|
|
||||||
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] }
|
override DataFlow::Node getSql() {
|
||||||
|
result in [this.getArg(0), this.getArgByName(getSqlKwargName())]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,11 +163,9 @@ private module Peewee {
|
|||||||
* A call to the `connection` method on a `peewee.Database` instance.
|
* A call to the `connection` method on a `peewee.Database` instance.
|
||||||
* https://docs.peewee-orm.com/en/latest/peewee/api.html#Database.connection.
|
* https://docs.peewee-orm.com/en/latest/peewee/api.html#Database.connection.
|
||||||
*/
|
*/
|
||||||
class PeeweeDatabaseConnectionCall extends PEP249::Connection::InstanceSource,
|
class PeeweeDatabaseConnectionCall extends PEP249::DatabaseConnection {
|
||||||
DataFlow::CallCfgNode
|
|
||||||
{
|
|
||||||
PeeweeDatabaseConnectionCall() {
|
PeeweeDatabaseConnectionCall() {
|
||||||
this = Database::instance().getMember("connection").getACall()
|
this = Database::instance().getMember("connection").getReturn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,8 +173,8 @@ private module Peewee {
|
|||||||
* A call to the `cursor` method on a `peewee.Database` instance.
|
* A call to the `cursor` method on a `peewee.Database` instance.
|
||||||
* https://docs.peewee-orm.com/en/latest/peewee/api.html#Database.cursor.
|
* https://docs.peewee-orm.com/en/latest/peewee/api.html#Database.cursor.
|
||||||
*/
|
*/
|
||||||
class PeeweeDatabaseCursorCall extends PEP249::Cursor::InstanceSource, DataFlow::CallCfgNode {
|
class PeeweeDatabaseCursorCall extends PEP249::DatabaseCursor {
|
||||||
PeeweeDatabaseCursorCall() { this = Database::instance().getMember("cursor").getACall() }
|
PeeweeDatabaseCursorCall() { this = Database::instance().getMember("cursor").getReturn() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2435,9 +2435,14 @@ private module StdlibPrivate {
|
|||||||
* against a database.
|
* against a database.
|
||||||
*
|
*
|
||||||
* See https://devdocs.io/python~3.9/library/sqlite3
|
* See https://devdocs.io/python~3.9/library/sqlite3
|
||||||
|
* https://github.com/python/cpython/blob/3.11/Lib/sqlite3/dbapi2.py
|
||||||
*/
|
*/
|
||||||
class Sqlite3 extends PEP249::PEP249ModuleApiNode {
|
class Sqlite3 extends PEP249::PEP249ModuleApiNode {
|
||||||
Sqlite3() { this = API::moduleImport("sqlite3") }
|
Sqlite3() {
|
||||||
|
this = API::moduleImport("sqlite3")
|
||||||
|
or
|
||||||
|
this = API::moduleImport("sqlite3").getMember("dbapi2")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
import python
|
||||||
|
import experimental.meta.ConceptsTest
|
||||||
27
python/ql/test/library-tests/frameworks/aiosqlite/test.py
Normal file
27
python/ql/test/library-tests/frameworks/aiosqlite/test.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import aiosqlite
|
||||||
|
|
||||||
|
# see https://pypi.org/project/aiosqlite/
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
db = await aiosqlite.connect(...)
|
||||||
|
|
||||||
|
await db.execute("sql") # $ getSql="sql" constructedSql="sql"
|
||||||
|
await db.execute(sql="sql") # $ getSql="sql" constructedSql="sql"
|
||||||
|
|
||||||
|
cursor = await db.cursor()
|
||||||
|
cursor.execute("sql") # $ constructedSql="sql"
|
||||||
|
|
||||||
|
cursor = await db.execute("sql") # $ getSql="sql" constructedSql="sql"
|
||||||
|
cursor.execute("sql") # $ constructedSql="sql"
|
||||||
|
|
||||||
|
async with aiosqlite.connect(...) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
async with db.execute("sql") as cursor: # $ getSql="sql" constructedSql="sql"
|
||||||
|
async for row in cursor:
|
||||||
|
print(row['column'])
|
||||||
|
|
||||||
|
# nonstandard
|
||||||
|
await db.execute_insert("sql") # $ getSql="sql" constructedSql="sql"
|
||||||
|
await db.execute_fetchall("sql") # $ getSql="sql" constructedSql="sql"
|
||||||
|
await db.executescript("sql") # $ getSql="sql" constructedSql="sql"
|
||||||
|
await db.executescript(sql_script="sql") # $ getSql="sql" constructedSql="sql"
|
||||||
@@ -22,6 +22,9 @@ async def test_connection():
|
|||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
conn = await asyncpg.connection.connect()
|
||||||
|
conn.execute("sql") # $ mad-sink[sql-injection]="sql"
|
||||||
|
|
||||||
|
|
||||||
async def test_prepared_statement():
|
async def test_prepared_statement():
|
||||||
conn = await asyncpg.connect()
|
conn = await asyncpg.connect()
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
import python
|
||||||
|
import experimental.meta.ConceptsTest
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from cassandra.cluster import Cluster
|
||||||
|
|
||||||
|
cluster = Cluster(...)
|
||||||
|
session = cluster.connect()
|
||||||
|
|
||||||
|
session.execute("sql") # $ getSql="sql"
|
||||||
|
|
||||||
|
future = session.execute_async("sql") # $ constructedSql="sql"
|
||||||
|
future.result()
|
||||||
|
|
||||||
|
prepared = session.prepare("sql") # $ constructedSql="sql"
|
||||||
|
session.execute(prepared) # $ SPURIOUS: getSql=prepared
|
||||||
@@ -6,3 +6,10 @@ db.execute("some sql", (42,)) # $ getSql="some sql"
|
|||||||
|
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
cursor.execute("some sql", (42,)) # $ getSql="some sql"
|
cursor.execute("some sql", (42,)) # $ getSql="some sql"
|
||||||
|
cursor.executescript("sql") # $ getSql="sql"
|
||||||
|
cursor.executescript(sql_script="sql") # $ getSql="sql"
|
||||||
|
|
||||||
|
import sqlite3.dbapi2
|
||||||
|
conn = sqlite3.dbapi2.connect()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("some sql") # $ getSql="some sql"
|
||||||
|
|||||||
Reference in New Issue
Block a user