Python: Rewrite ClickHouse SQL lib modeling

This did turn into a few changes, that maybe could have been split into
separate PRs 🤷

* Rename `ClickHouseDriver` => `ClickhouseDriver`, to better follow
  import name in `.qll` name
* Rewrote modeling to use API graphs
* Split modeling of `aioch` into separate `.qll` file, which does re-use
  the `getExecuteMethodName` predicate. I feel that sharing code between
  the modeling like this was the best approach, and stuck the
  `INTERNAL: Do not use.` labels on both modules.
* I also added handling of keyword arguments (see change in .py files)
This commit is contained in:
Rasmus Wriedt Larsen
2021-05-25 16:13:31 +02:00
parent c9a9535dbc
commit eb1da152a0
7 changed files with 128 additions and 96 deletions

View File

@@ -0,0 +1,52 @@
/**
* Provides classes modeling security-relevant aspects of the `aioch` PyPI package (an
* async-io version of the `clickhouse-driver` PyPI package).
*
* See https://pypi.org/project/aioch/
*/
private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.PEP249
private import experimental.semmle.python.frameworks.ClickhouseDriver
/**
* INTERNAL: Do not use.
*
* Provides models for `aioch` PyPI package (an async-io version of the
* `clickhouse-driver` PyPI package).
*
* See https://pypi.org/project/aioch/
*/
module Aioch {
/** Provides models for `aioch.Client` class and subclasses. */
module Client {
/** Gets a reference to the `aioch.Client` class or any subclass. */
API::Node subclassRef() {
result = API::moduleImport("aioch").getMember("Client").getASubclass*()
}
/** Gets a reference to an instance of `clickhouse_driver.Client` or any subclass. */
API::Node instance() { result = subclassRef().getReturn() }
}
/**
* A call to any of the the execute methods on a `aioch.Client`, which are just async
* versions of the methods in the `clickhouse-driver` PyPI package.
*
* See
* - https://clickhouse-driver.readthedocs.io/en/latest/api.html#clickhouse_driver.Client.execute
* - https://clickhouse-driver.readthedocs.io/en/latest/api.html#clickhouse_driver.Client.execute_iter
* - https://clickhouse-driver.readthedocs.io/en/latest/api.html#clickhouse_driver.Client.execute_with_progress
*/
class ClientExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {
ClientExecuteCall() {
exists(string methodName | methodName = ClickhouseDriver::getExecuteMethodName() |
this = Client::instance().getMember(methodName).getACall()
)
}
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
}
}

View File

@@ -1,85 +0,0 @@
/**
* Provides classes modeling security-relevant aspects of `clickhouse-driver` and `aioch` PyPI packages.
* See
* - https://pypi.org/project/clickhouse-driver/
* - https://pypi.org/project/aioch/
* - https://clickhouse-driver.readthedocs.io/en/latest/
*/
private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.PEP249
/**
* Provides models for `clickhouse-driver` and `aioch` PyPI packages.
* See
* - https://pypi.org/project/clickhouse-driver/
* - https://pypi.org/project/aioch/
* - https://clickhouse-driver.readthedocs.io/en/latest/
*/
module ClickHouseDriver {
/** Gets a reference to the `clickhouse_driver` module. */
API::Node clickhouse_driver() { result = API::moduleImport("clickhouse_driver") }
/** Gets a reference to the `aioch` module. This module allows to make async db queries. */
API::Node aioch() { result = API::moduleImport("aioch") }
/**
* `clickhouse_driver` implements PEP249,
* providing ways to execute SQL statements against a database.
*/
class ClickHouseDriverPEP249 extends PEP249ModuleApiNode {
ClickHouseDriverPEP249() { this = clickhouse_driver() }
}
module Client {
/** Gets a reference to a Client call. */
private DataFlow::Node client_ref() {
result = clickhouse_driver().getMember("Client").getASubclass*().getAUse()
or
result = aioch().getMember("Client").getASubclass*().getAUse()
}
/** A direct instantiation of `clickhouse_driver.Client`. */
private class ClientInstantiation extends DataFlow::CallCfgNode {
ClientInstantiation() { this.getFunction() = client_ref() }
}
/** Gets a reference to an instance of `clickhouse_driver.Client`. */
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof ClientInstantiation
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `clickhouse_driver.Client`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
}
/** clickhouse_driver.Client execute methods */
private string execute_function() {
result in ["execute_with_progress", "execute", "execute_iter"]
}
/** Gets a reference to the `clickhouse_driver.Client.execute` method */
private DataFlow::LocalSourceNode clickhouse_execute(DataFlow::TypeTracker t) {
t.startInAttr(execute_function()) and
result = Client::instance()
or
exists(DataFlow::TypeTracker t2 | result = clickhouse_execute(t2).track(t2, t))
}
/** Gets a reference to the `clickhouse_driver.Client.execute` method */
DataFlow::Node clickhouse_execute() {
clickhouse_execute(DataFlow::TypeTracker::end()).flowsTo(result)
}
/** A call to the `clickhouse_driver.Client.execute` method */
private class ExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {
ExecuteCall() { this.getFunction() = clickhouse_execute() }
override DataFlow::Node getSql() { result.asCfgNode() = node.getArg(0) }
}
}

View File

@@ -0,0 +1,65 @@
/**
* Provides classes modeling security-relevant aspects of the `clickhouse-driver` PyPI package.
* See
* - https://pypi.org/project/clickhouse-driver/
* - https://clickhouse-driver.readthedocs.io/en/latest/
*/
private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.PEP249
/**
* INTERNAL: Do not use.
*
* Provides models for `clickhouse-driver` PyPI package (imported as `clickhouse_driver`).
* See
* - https://pypi.org/project/clickhouse-driver/
* - https://clickhouse-driver.readthedocs.io/en/latest/
*/
module ClickhouseDriver {
/**
* `clickhouse_driver` implements PEP249,
* providing ways to execute SQL statements against a database.
*/
class ClickHouseDriverPEP249 extends PEP249ModuleApiNode {
ClickHouseDriverPEP249() { this = API::moduleImport("clickhouse_driver") }
}
/** Provides models for `clickhouse_driver.Client` class and subclasses. */
module Client {
/** Gets a reference to the `clickhouse_driver.Client` class or any subclass. */
API::Node subclassRef() {
exists(API::Node classRef |
// canonical definition
classRef = API::moduleImport("clickhouse_driver").getMember("client").getMember("Client")
or
// commonly used alias
classRef = API::moduleImport("clickhouse_driver").getMember("Client")
|
result = classRef.getASubclass*()
)
}
/** Gets a reference to an instance of `clickhouse_driver.Client` or any subclass. */
API::Node instance() { result = subclassRef().getReturn() }
}
/** `clickhouse_driver.Client` execute method names */
string getExecuteMethodName() { result in ["execute_with_progress", "execute", "execute_iter"] }
/**
* A call to any of the the execute methods on a `clickhouse_driver.Client` method
*
* See
* - https://clickhouse-driver.readthedocs.io/en/latest/api.html#clickhouse_driver.Client.execute
* - https://clickhouse-driver.readthedocs.io/en/latest/api.html#clickhouse_driver.Client.execute_iter
* - https://clickhouse-driver.readthedocs.io/en/latest/api.html#clickhouse_driver.Client.execute_with_progress
*/
class ClientExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {
ClientExecuteCall() { this = Client::instance().getMember(getExecuteMethodName()).getACall() }
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
}
}