Skip to content

Commit 0b9d16a

Browse files
authored
Merge pull request github#12636 from RasmusWL/sql-modeling
Python: Some more SQL modeling
2 parents d3c3f2d + 77f1539 commit 0b9d16a

File tree

20 files changed

+361
-89
lines changed

20 files changed

+361
-89
lines changed

docs/codeql/reusables/supported-frameworks.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ and the CodeQL library pack ``codeql/python-all`` (`changelog <https://github.co
223223
aioch, Database
224224
aiomysql, Database
225225
aiopg, Database
226+
aiosqlite, Database
226227
asyncpg, Database
228+
cassandra-driver, Database
227229
clickhouse-driver, Database
228230
cx_Oracle, Database
229231
mysql-connector-python, Database
@@ -233,9 +235,9 @@ and the CodeQL library pack ``codeql/python-all`` (`changelog <https://github.co
233235
oracledb, Database
234236
phoenixdb, Database
235237
psycopg2, Database
236-
pyodbc, Database
237238
pymssql, Database
238239
PyMySQL, Database
240+
pyodbc, Database
239241
sqlite3, Database
240242
Flask-SQLAlchemy, Database ORM
241243
peewee, Database ORM
@@ -276,4 +278,3 @@ and the CodeQL library pack ``codeql/ruby-all`` (`changelog <https://github.com/
276278
Ruby on Rails, Web framework
277279
rubyzip, Compression library
278280
typhoeus, HTTP client
279-
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* 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()`.

python/ql/lib/semmle/python/Frameworks.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
*/
44

55
// If you add modeling of a new framework/library, remember to add it to the docs in
6-
// `docs/codeql/support/reusables/frameworks.rst`
6+
// `docs/codeql/reusables/supported-frameworks.rst`
77
private import semmle.python.frameworks.Aioch
88
private import semmle.python.frameworks.Aiohttp
99
private import semmle.python.frameworks.Aiomysql
10+
private import semmle.python.frameworks.Aiosqlite
1011
private import semmle.python.frameworks.Aiopg
1112
private import semmle.python.frameworks.Asyncpg
13+
private import semmle.python.frameworks.CassandraDriver
1214
private import semmle.python.frameworks.ClickhouseDriver
1315
private import semmle.python.frameworks.Cryptodome
1416
private import semmle.python.frameworks.Cryptography

python/ql/lib/semmle/python/frameworks/Aiomysql.qll

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ private import python
99
private import semmle.python.dataflow.new.DataFlow
1010
private import semmle.python.Concepts
1111
private import semmle.python.ApiGraphs
12+
private import semmle.python.frameworks.PEP249
1213

1314
/** Provides models for the `aiomysql` PyPI package. */
1415
private module Aiomysql {
15-
private import semmle.python.internal.Awaited
16-
1716
/**
1817
* Gets a `ConnectionPool` that is created when the result of `aiomysql.create_pool()` is awaited.
1918
* See https://aiomysql.readthedocs.io/en/stable/pool.html
@@ -23,49 +22,29 @@ private module Aiomysql {
2322
}
2423

2524
/**
26-
* Gets a `Connection` that is created when
25+
* A Connection that is created when
2726
* - the result of `aiomysql.connect()` is awaited.
2827
* - the result of calling `acquire` on a `ConnectionPool` is awaited.
29-
* See https://aiomysql.readthedocs.io/en/stable/connection.html#connection
28+
* See
29+
* - https://aiomysql.readthedocs.io/en/stable/connection.html#connection
30+
* - https://aiomysql.readthedocs.io/en/stable/pool.html#Pool.acquire
3031
*/
31-
API::Node connection() {
32-
result = API::moduleImport("aiomysql").getMember("connect").getReturn().getAwaited()
33-
or
34-
result = connectionPool().getMember("acquire").getReturn().getAwaited()
32+
class AiomysqlConnection extends PEP249::AsyncDatabaseConnection {
33+
AiomysqlConnection() {
34+
this = API::moduleImport("aiomysql").getMember("connect").getReturn().getAwaited()
35+
or
36+
this = connectionPool().getMember("acquire").getReturn().getAwaited()
37+
}
3538
}
3639

3740
/**
38-
* Gets a `Cursor` that is created when
41+
* An additional cursor, that is created when
3942
* - the result of calling `cursor` on a `ConnectionPool` is awaited.
40-
* - the result of calling `cursor` on a `Connection` is awaited.
41-
* See https://aiomysql.readthedocs.io/en/stable/cursors.html
43+
* See
44+
* - https://aiomysql.readthedocs.io/en/stable/pool.html##Pool.cursor
4245
*/
43-
API::Node cursor() {
44-
result = connectionPool().getMember("cursor").getReturn().getAwaited()
45-
or
46-
result = connection().getMember("cursor").getReturn().getAwaited()
47-
}
48-
49-
/**
50-
* A query. Calling `execute` on a `Cursor` constructs a query.
51-
* See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
52-
*/
53-
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
54-
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
55-
56-
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").asSink() }
57-
}
58-
59-
/**
60-
* An awaited query. Awaiting the result of calling `execute` executes the query.
61-
* See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
62-
*/
63-
class AwaitedCursorExecuteCall extends SqlExecution::Range {
64-
CursorExecuteCall executeCall;
65-
66-
AwaitedCursorExecuteCall() { this = executeCall.getReturn().getAwaited().asSource() }
67-
68-
override DataFlow::Node getSql() { result = executeCall.getSql() }
46+
class AiomysqlCursor extends PEP249::AsyncDatabaseCursor {
47+
AiomysqlCursor() { this = connectionPool().getMember("cursor").getReturn().getAwaited() }
6948
}
7049

7150
/**

python/ql/lib/semmle/python/frameworks/Aiopg.qll

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ private import python
99
private import semmle.python.dataflow.new.DataFlow
1010
private import semmle.python.Concepts
1111
private import semmle.python.ApiGraphs
12+
private import semmle.python.frameworks.PEP249
1213

1314
/** Provides models for the `aiopg` PyPI package. */
1415
private module Aiopg {
15-
private import semmle.python.internal.Awaited
16-
1716
/**
1817
* Gets a `ConnectionPool` that is created when the result of `aiopg.create_pool()` is awaited.
1918
* See https://aiopg.readthedocs.io/en/stable/core.html#pool
@@ -23,49 +22,29 @@ private module Aiopg {
2322
}
2423

2524
/**
26-
* Gets a `Connection` that is created when
25+
* A Connection that is created when
2726
* - the result of `aiopg.connect()` is awaited.
2827
* - the result of calling `acquire` on a `ConnectionPool` is awaited.
29-
* See https://aiopg.readthedocs.io/en/stable/core.html#connection
28+
* See
29+
* - https://aiopg.readthedocs.io/en/stable/core.html#connection
30+
* - https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Pool.acquire
3031
*/
31-
API::Node connection() {
32-
result = API::moduleImport("aiopg").getMember("connect").getReturn().getAwaited()
33-
or
34-
result = connectionPool().getMember("acquire").getReturn().getAwaited()
32+
class AiopgConnection extends PEP249::AsyncDatabaseConnection {
33+
AiopgConnection() {
34+
this = API::moduleImport("aiopg").getMember("connect").getReturn().getAwaited()
35+
or
36+
this = connectionPool().getMember("acquire").getReturn().getAwaited()
37+
}
3538
}
3639

3740
/**
38-
* Gets a `Cursor` that is created when
41+
* An additional cursor, that is created when
3942
* - the result of calling `cursor` on a `ConnectionPool` is awaited.
40-
* - the result of calling `cursor` on a `Connection` is awaited.
41-
* See https://aiopg.readthedocs.io/en/stable/core.html#cursor
42-
*/
43-
API::Node cursor() {
44-
result = connectionPool().getMember("cursor").getReturn().getAwaited()
45-
or
46-
result = connection().getMember("cursor").getReturn().getAwaited()
47-
}
48-
49-
/**
50-
* A query. Calling `execute` on a `Cursor` constructs a query.
51-
* See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
52-
*/
53-
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
54-
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
55-
56-
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").asSink() }
57-
}
58-
59-
/**
60-
* An awaited query. Awaiting the result of calling `execute` executes the query.
61-
* See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
43+
* See
44+
* - https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Pool.cursor
6245
*/
63-
class AwaitedCursorExecuteCall extends SqlExecution::Range {
64-
CursorExecuteCall execute;
65-
66-
AwaitedCursorExecuteCall() { this = execute.getReturn().getAwaited().asSource() }
67-
68-
override DataFlow::Node getSql() { result = execute.getSql() }
46+
class AiopgCursor extends PEP249::AsyncDatabaseCursor {
47+
AiopgCursor() { this = connectionPool().getMember("cursor").getReturn().getAwaited() }
6948
}
7049

7150
/**
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `aiosqlite` PyPI package.
3+
* See
4+
* - https://pypi.org/project/aiosqlite/
5+
*/
6+
7+
private import python
8+
private import semmle.python.dataflow.new.DataFlow
9+
private import semmle.python.Concepts
10+
private import semmle.python.ApiGraphs
11+
private import semmle.python.frameworks.PEP249
12+
13+
/** Provides models for the `aiosqlite` PyPI package. */
14+
private module Aiosqlite {
15+
/**
16+
* A model of `aiosqlite` as a module that implements PEP 249 using asyncio, providing
17+
* ways to execute SQL statements against a database.
18+
*/
19+
class AiosqlitePEP249 extends PEP249::AsyncPEP249ModuleApiNode {
20+
AiosqlitePEP249() { this = API::moduleImport("aiosqlite") }
21+
}
22+
23+
/**
24+
* An additional cursor, that is return from the coroutine Connection.execute,
25+
* see https://aiosqlite.omnilib.dev/en/latest/api.html#aiosqlite.Connection.execute
26+
*/
27+
class AiosqliteCursor extends PEP249::AsyncDatabaseCursor {
28+
AiosqliteCursor() {
29+
this =
30+
API::moduleImport("aiosqlite")
31+
.getMember("connect")
32+
.getReturn()
33+
.getAwaited()
34+
.getMember("execute")
35+
.getReturn()
36+
.getAwaited()
37+
}
38+
}
39+
}

python/ql/lib/semmle/python/frameworks/Asyncpg.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ private module Asyncpg {
2222
// * - the result of `asyncpg.connect()` is awaited.
2323
// * - the result of calling `acquire` on a `ConnectionPool` is awaited.
2424
"asyncpg.Connection;asyncpg;Member[connect].ReturnValue.Awaited",
25+
"asyncpg.Connection;asyncpg;Member[connection].Member[connect].ReturnValue.Awaited",
2526
"asyncpg.Connection;asyncpg.ConnectionPool;Member[acquire].ReturnValue.Awaited",
2627
// Creating an internal `~Connection` type that contains both `Connection` and `ConnectionPool`.
2728
"asyncpg.~Connection;asyncpg.Connection;", //
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `cassandra-driver` PyPI package.
3+
* See https://pypi.org/project/cassandra-driver/
4+
*/
5+
6+
private import python
7+
private import semmle.python.dataflow.new.DataFlow
8+
private import semmle.python.dataflow.new.RemoteFlowSources
9+
private import semmle.python.Concepts
10+
private import semmle.python.ApiGraphs
11+
private import semmle.python.frameworks.PEP249
12+
13+
/**
14+
* Provides models for the `cassandra-driver` PyPI package.
15+
* See https://pypi.org/project/cassandra-driver/
16+
*/
17+
private module CassandraDriver {
18+
/**
19+
* A cassandra cluster session.
20+
*
21+
* see
22+
* - https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Cluster.connect
23+
* - https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Session
24+
*/
25+
API::Node session() {
26+
result =
27+
API::moduleImport("cassandra")
28+
.getMember("cluster")
29+
.getMember("Cluster")
30+
.getReturn()
31+
.getMember("connect")
32+
.getReturn()
33+
}
34+
35+
/**
36+
* see https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Session.execute
37+
*/
38+
class CassandraSessionExecuteCall extends SqlExecution::Range, API::CallNode {
39+
CassandraSessionExecuteCall() { this = session().getMember("execute").getACall() }
40+
41+
override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() }
42+
}
43+
44+
/**
45+
* see https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Session.execute_async
46+
*/
47+
class CassandraSessionExecuteAsyncCall extends SqlConstruction::Range, API::CallNode {
48+
CassandraSessionExecuteAsyncCall() { this = session().getMember("execute_async").getACall() }
49+
50+
override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() }
51+
}
52+
53+
/**
54+
* see https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cluster/#cassandra.cluster.Session.prepare
55+
*/
56+
class CassandraSessionPrepareCall extends SqlConstruction::Range, API::CallNode {
57+
CassandraSessionPrepareCall() { this = session().getMember("prepare").getACall() }
58+
59+
override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() }
60+
}
61+
}

python/ql/lib/semmle/python/frameworks/Django.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,8 @@ module PrivateDjango {
561561
API::Node connection() { result = db().getMember("connection") }
562562

563563
/** A `django.db.connection` is a PEP249 compliant DB connection. */
564-
class DjangoDbConnection extends PEP249::Connection::InstanceSource {
565-
DjangoDbConnection() { this = connection().asSource() }
564+
class DjangoDbConnection extends PEP249::DatabaseConnection {
565+
DjangoDbConnection() { this = connection() }
566566
}
567567

568568
// -------------------------------------------------------------------------

0 commit comments

Comments
 (0)