Merge pull request #15044 from RasmusWL/automated-subclass-models

Python: Automated subclass models
This commit is contained in:
Rasmus Wriedt Larsen
2024-01-05 10:43:48 +01:00
committed by GitHub
41 changed files with 176310 additions and 294 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Captured subclass relationships ahead-of-time for most popular PyPI packages so we are able to resolve subclass relationships even without having the packages installed. For example we have captured that `flask_restful.Resource` is a subclass of `flask.views.MethodView`, so our Flask modeling will still consider a function named `post` on a `class Foo(flask_restful.Resource):` as a HTTP request handler.

View File

@@ -177,7 +177,7 @@ private predicate legalDottedName(string name) {
}
bindingset[name]
private predicate legalShortName(string name) { name.regexpMatch("(\\p{L}|_)(\\p{L}|\\d|_)*") }
predicate legalShortName(string name) { name.regexpMatch("(\\p{L}|_)(\\p{L}|\\d|_)*") }
private string moduleNameFromBase(Container file) {
// We used to also require `isPotentialPackage(f)` to hold in this case,

View File

@@ -9,6 +9,7 @@ private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.ClickhouseDriver
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -24,6 +25,8 @@ module Aioch {
/** Gets a reference to the `aioch.Client` class or any subclass. */
API::Node subclassRef() {
result = API::moduleImport("aioch").getMember("Client").getASubclass*()
or
result = ModelOutput::getATypeNode("aioch.Client~Subclass").getASubclass*()
}
/** Gets a reference to an instance of `clickhouse_driver.Client` or any subclass. */

View File

@@ -14,6 +14,7 @@ private import semmle.python.frameworks.internal.SelfRefMixin
private import semmle.python.frameworks.Multidict
private import semmle.python.frameworks.Yarl
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -31,6 +32,8 @@ module AiohttpWebModel {
/** Gets a reference to the `aiohttp.web.View` class or any subclass. */
API::Node subclassRef() {
result = API::moduleImport("aiohttp").getMember("web").getMember("View").getASubclass*()
or
result = ModelOutput::getATypeNode("aiohttp.web.View~Subclass").getASubclass*()
}
}
@@ -706,10 +709,12 @@ module AiohttpWebModel {
}
/**
* INTERNAL: Do not use.
*
* Provides models for the web server part (`aiohttp.client`) of the `aiohttp` PyPI package.
* See https://docs.aiohttp.org/en/stable/client.html
*/
private module AiohttpClientModel {
module AiohttpClientModel {
/**
* Provides models for the `aiohttp.ClientSession` class
*
@@ -717,8 +722,10 @@ private module AiohttpClientModel {
*/
module ClientSession {
/** Gets a reference to the `aiohttp.ClientSession` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("aiohttp").getMember("ClientSession")
or
result = ModelOutput::getATypeNode("aiohttp.ClientSession~Subclass").getASubclass*()
}
/** Gets a reference to an instance of `aiohttp.ClientSession`. */

View File

@@ -9,6 +9,7 @@ private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.PEP249
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -37,6 +38,9 @@ module ClickhouseDriver {
or
// commonly used alias
classRef = API::moduleImport("clickhouse_driver").getMember("Client")
or
// Models-as-Data subclass
classRef = ModelOutput::getATypeNode("clickhouse_driver.client.Client~Subclass")
|
result = classRef.getASubclass*()
)

View File

@@ -16,6 +16,7 @@ private import semmle.python.frameworks.internal.PoorMansFunctionResolution
private import semmle.python.frameworks.internal.SelfRefMixin
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.security.dataflow.UrlRedirectCustomizations
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -85,6 +86,10 @@ module Django {
}
}
private class MaDSubclass extends ModeledSubclass {
MaDSubclass() { this = ModelOutput::getATypeNode("Django.Views.View~Subclass") }
}
/** Gets a reference to the `django.views.generic.View` class or any subclass. */
API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() }
}
@@ -185,6 +190,10 @@ module Django {
}
}
private class MaDSubclass extends ModeledSubclass {
MaDSubclass() { this = ModelOutput::getATypeNode("django.forms.BaseForm~Subclass") }
}
/** Gets a reference to the `django.forms.forms.BaseForm` class or any subclass. */
API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() }
}
@@ -290,6 +299,10 @@ module Django {
}
}
private class MaDSubclass extends ModeledSubclass {
MaDSubclass() { this = ModelOutput::getATypeNode("Django.Forms.Field~Subclass") }
}
/** Gets a reference to the `django.forms.fields.Field` class or any subclass. */
API::Node subclassRef() { result = any(ModeledSubclass subclass).getASubclass*() }
}
@@ -596,6 +609,8 @@ module PrivateDjango {
.getMember("models")
.getMember("PolymorphicModel")
.getASubclass*()
or
result = ModelOutput::getATypeNode("Django.db.models.Model~Subclass").getASubclass*()
}
/**
@@ -766,6 +781,9 @@ module PrivateDjango {
.getMember(className)
.getASubclass*()
)
or
result =
ModelOutput::getATypeNode("django.db.models.FileField~Subclass").getASubclass*()
}
}
@@ -823,6 +841,10 @@ module PrivateDjango {
or
// Commonly used alias
result = models().getMember("RawSQL")
or
result =
ModelOutput::getATypeNode("django.db.models.expressions.RawSQL~Subclass")
.getASubclass*()
}
/**
@@ -1157,6 +1179,9 @@ module PrivateDjango {
or
// handle django.http.HttpRequest alias
result = http().getMember("HttpRequest")
or
result =
ModelOutput::getATypeNode("django.http.request.HttpRequest~Subclass").getASubclass*()
}
/**
@@ -1322,7 +1347,13 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponse` class or any subclass. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*()
or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponse~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponse`, extend this class to model new instances.
@@ -1383,7 +1414,12 @@ module PrivateDjango {
}
/** Gets a reference to a subclass of the `django.http.response.HttpResponseRedirect` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponseRedirect~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponseRedirect`, extend this class to model new instances.
@@ -1446,7 +1482,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponsePermanentRedirect` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponsePermanentRedirect~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponsePermanentRedirect`, extend this class to model new instances.
@@ -1510,7 +1551,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponseNotModified` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponseNotModified~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponseNotModified`, extend this class to model new instances.
@@ -1562,7 +1608,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponseBadRequest` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponseBadRequest~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponseBadRequest`, extend this class to model new instances.
@@ -1616,7 +1667,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponseNotFound` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponseNotFound~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponseNotFound`, extend this class to model new instances.
@@ -1670,7 +1726,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponseForbidden` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponseForbidden~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponseForbidden`, extend this class to model new instances.
@@ -1724,7 +1785,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponseNotAllowed` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponseNotAllowed~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponseNotAllowed`, extend this class to model new instances.
@@ -1779,7 +1845,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponseGone` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponseGone~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponseGone`, extend this class to model new instances.
@@ -1833,7 +1904,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.HttpResponseServerError` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.HttpResponseServerError~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.HttpResponseServerError`, extend this class to model new instances.
@@ -1887,7 +1963,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.JsonResponse` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.JsonResponse~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.JsonResponse`, extend this class to model new instances.
@@ -1944,7 +2025,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.StreamingHttpResponse` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.StreamingHttpResponse~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.StreamingHttpResponse`, extend this class to model new instances.
@@ -1998,7 +2084,12 @@ module PrivateDjango {
}
/** Gets a reference to the `django.http.response.FileResponse` class. */
API::Node classRef() { result = baseClassRef().getASubclass*() }
API::Node classRef() {
result = baseClassRef().getASubclass*() or
result =
ModelOutput::getATypeNode("django.http.response.FileResponse~Subclass")
.getASubclass*()
}
/**
* A source of instances of `django.http.response.FileResponse`, extend this class to model new instances.

View File

@@ -12,6 +12,7 @@ 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.data.ModelsAsData
/**
* Provides classes modeling security-relevant aspects of the `fabric` PyPI package, for
@@ -65,12 +66,14 @@ private module FabricV1 {
}
/**
* INTERNAL: Do not use.
*
* Provides classes modeling security-relevant aspects of the `fabric` PyPI package, for
* version 2.x.
*
* See http://docs.fabfile.org/en/2.5/getting-st arted.html.
*/
private module FabricV2 {
module FabricV2 {
/** Gets a reference to the `fabric` module. */
API::Node fabric() { result = API::moduleImport("fabric") }
@@ -95,6 +98,9 @@ private module FabricV2 {
result = fabric().getMember("Connection")
or
result = connection().getMember("Connection")
or
result =
ModelOutput::getATypeNode("fabric.connection.Connection~Subclass").getASubclass*()
}
/**

View File

@@ -11,18 +11,23 @@ private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.Pydantic
private import semmle.python.frameworks.Starlette
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
*
* Provides models for the `fastapi` PyPI package.
* See https://fastapi.tiangolo.com/.
*/
private module FastApi {
module FastApi {
/**
* Provides models for FastAPI applications (an instance of `fastapi.FastAPI`).
*/
module App {
API::Node cls() { result = API::moduleImport("fastapi").getMember("FastAPI") }
/** Gets a reference to a FastAPI application (an instance of `fastapi.FastAPI`). */
API::Node instance() { result = API::moduleImport("fastapi").getMember("FastAPI").getReturn() }
API::Node instance() { result = cls().getReturn() }
}
/**
@@ -31,10 +36,14 @@ private module FastApi {
* See https://fastapi.tiangolo.com/tutorial/bigger-applications/.
*/
module ApiRouter {
/** Gets a reference to an instance of `fastapi.ApiRouter`. */
API::Node instance() {
result = API::moduleImport("fastapi").getMember("APIRouter").getASubclass*().getReturn()
API::Node cls() {
result = API::moduleImport("fastapi").getMember("APIRouter").getASubclass*()
or
result = ModelOutput::getATypeNode("fastapi.APIRouter~Subclass").getASubclass*()
}
/** Gets a reference to an instance of `fastapi.ApiRouter`. */
API::Node instance() { result = cls().getReturn() }
}
// ---------------------------------------------------------------------------

View File

@@ -14,6 +14,7 @@ private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.security.dataflow.PathInjectionCustomizations
private import semmle.python.dataflow.new.FlowSummary
private import semmle.python.frameworks.data.ModelsAsData
/**
* Provides models for the `flask` PyPI package.
@@ -39,6 +40,10 @@ module Flask {
"MethodView"
])
.getASubclass*()
or
result = ModelOutput::getATypeNode("flask.View~Subclass").getASubclass*()
or
result = ModelOutput::getATypeNode("flask.MethodView~Subclass").getASubclass*()
}
}
@@ -52,6 +57,8 @@ module Flask {
API::Node subclassRef() {
result =
API::moduleImport("flask").getMember("views").getMember("MethodView").getASubclass*()
or
result = ModelOutput::getATypeNode("flask.MethodView~Subclass").getASubclass*()
}
}
}
@@ -63,7 +70,10 @@ module Flask {
*/
module FlaskApp {
/** Gets a reference to the `flask.Flask` class. */
API::Node classRef() { result = API::moduleImport("flask").getMember("Flask") }
API::Node classRef() {
result = API::moduleImport("flask").getMember("Flask") or
result = ModelOutput::getATypeNode("flask.Flask~Subclass").getASubclass*()
}
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
API::Node instance() { result = classRef().getReturn() }
@@ -80,6 +90,8 @@ module Flask {
result = API::moduleImport("flask").getMember("Blueprint")
or
result = API::moduleImport("flask").getMember("blueprints").getMember("Blueprint")
or
result = ModelOutput::getATypeNode("flask.Blueprint~Subclass").getASubclass*()
}
/** Gets a reference to an instance of `flask.Blueprint`. */
@@ -87,7 +99,9 @@ module Flask {
}
/** Gets a reference to the `flask.request` object. */
API::Node request() { result = API::moduleImport("flask").getMember("request") }
API::Node request() {
result = API::moduleImport(["flask", "flask_restful"]).getMember("request")
}
/**
* Provides models for the `flask.Response` class
@@ -104,6 +118,8 @@ module Flask {
result = API::moduleImport("flask").getMember("Response")
or
result = [FlaskApp::classRef(), FlaskApp::instance()].getMember("response_class")
or
result = ModelOutput::getATypeNode("flask.Response~Subclass").getASubclass*()
}
/**

View File

@@ -9,15 +9,18 @@
private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
*
* Provides models for the `httpx` PyPI package.
*
* See
* - https://pypi.org/project/httpx/
* - https://www.python-httpx.org/
*/
private module HttpxModel {
module HttpxModel {
/**
* An outgoing HTTP request, from the `httpx` library.
*
@@ -59,8 +62,10 @@ private module HttpxModel {
*/
module Client {
/** Get a reference to the `httpx.Client` or `httpx.AsyncClient` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("httpx").getMember(["Client", "AsyncClient"])
or
result = ModelOutput::getATypeNode("httpx.Client~Subclass").getASubclass*()
}
/** A method call on a Client that sends off a request */

View File

@@ -7,12 +7,15 @@ 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.data.ModelsAsData
/**
* INTERNAL: Do not use.
*
* Provides models for the `invoke` PyPI package.
* See https://www.pyinvoke.org/.
*/
private module Invoke {
module Invoke {
// ---------------------------------------------------------------------------
// invoke
// ---------------------------------------------------------------------------
@@ -30,6 +33,8 @@ private module Invoke {
result = API::moduleImport("invoke").getMember("context").getMember("Context")
or
result = API::moduleImport("invoke").getMember("Context")
or
result = ModelOutput::getATypeNode("invoke.context.Context~Subclass").getASubclass*()
}
/** Gets a reference to an instance of `invoke.context.Context`. */

View File

@@ -10,15 +10,25 @@ 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.data.ModelsAsData
/**
* INTERNAL: Do not use.
*
* Provides classes modeling security-relevant aspects of the `lxml` PyPI package
*
* See
* - https://pypi.org/project/lxml/
* - https://lxml.de/tutorial.html
*/
private module Lxml {
module Lxml {
/** Gets a reference to the `lxml.etree` module */
API::Node etreeRef() {
result = API::moduleImport("lxml").getMember("etree")
or
result = ModelOutput::getATypeNode("lxml.etree~Alias")
}
// ---------------------------------------------------------------------------
// XPath
// ---------------------------------------------------------------------------
@@ -34,9 +44,7 @@ private module Lxml {
* - https://lxml.de/apidoc/lxml.etree.html#lxml.etree.ETXPath
*/
private class XPathClassCall extends XML::XPathConstruction::Range, DataFlow::CallCfgNode {
XPathClassCall() {
this = API::moduleImport("lxml").getMember("etree").getMember(["XPath", "ETXPath"]).getACall()
}
XPathClassCall() { this = etreeRef().getMember(["XPath", "ETXPath"]).getACall() }
override DataFlow::Node getXPath() { result in [this.getArg(0), this.getArgByName("path")] }
@@ -62,20 +70,11 @@ private module Lxml {
XPathCall() {
exists(API::Node parseResult |
parseResult =
API::moduleImport("lxml")
.getMember("etree")
.getMember(["parse", "fromstring", "fromstringlist", "HTML", "XML"])
.getReturn()
etreeRef().getMember(["parse", "fromstring", "fromstringlist", "HTML", "XML"]).getReturn()
or
// TODO: lxml.etree.parseid(<text>)[0] will contain the root element from parsing <text>
// but we don't really have a way to model that nicely.
parseResult =
API::moduleImport("lxml")
.getMember("etree")
.getMember("XMLParser")
.getReturn()
.getMember("close")
.getReturn()
parseResult = etreeRef().getMember("XMLParser").getReturn().getMember("close").getReturn()
|
this = parseResult.getMember("xpath").getACall()
)
@@ -87,14 +86,7 @@ private module Lxml {
}
class XPathEvaluatorCall extends XML::XPathExecution::Range, DataFlow::CallCfgNode {
XPathEvaluatorCall() {
this =
API::moduleImport("lxml")
.getMember("etree")
.getMember("XPathEvaluator")
.getReturn()
.getACall()
}
XPathEvaluatorCall() { this = etreeRef().getMember("XPathEvaluator").getReturn().getACall() }
override DataFlow::Node getXPath() { result = this.getArg(0) }
@@ -130,9 +122,7 @@ private module Lxml {
* See https://lxml.de/apidoc/lxml.etree.html?highlight=xmlparser#lxml.etree.XMLParser
*/
private class LxmlParser extends InstanceSource, API::CallNode {
LxmlParser() {
this = API::moduleImport("lxml").getMember("etree").getMember("XMLParser").getACall()
}
LxmlParser() { this = etreeRef().getMember("XMLParser").getACall() }
// NOTE: it's not possible to change settings of a parser after constructing it
override predicate vulnerableTo(XML::XmlParsingVulnerabilityKind kind) {
@@ -162,10 +152,7 @@ private module Lxml {
* See https://lxml.de/apidoc/lxml.etree.html?highlight=xmlparser#lxml.etree.get_default_parser
*/
private class LxmlDefaultParser extends InstanceSource, DataFlow::CallCfgNode {
LxmlDefaultParser() {
this =
API::moduleImport("lxml").getMember("etree").getMember("get_default_parser").getACall()
}
LxmlDefaultParser() { this = etreeRef().getMember("get_default_parser").getACall() }
override predicate vulnerableTo(XML::XmlParsingVulnerabilityKind kind) {
// as highlighted by
@@ -240,7 +227,7 @@ private module Lxml {
LxmlParsing() {
functionName in ["fromstring", "fromstringlist", "XML", "XMLID", "parse", "parseid"] and
this = API::moduleImport("lxml").getMember("etree").getMember(functionName).getACall()
this = etreeRef().getMember(functionName).getACall()
}
override DataFlow::Node getAnInput() {
@@ -309,9 +296,7 @@ private module Lxml {
private class LxmlIterparseCall extends API::CallNode, XML::XmlParsing::Range,
FileSystemAccess::Range
{
LxmlIterparseCall() {
this = API::moduleImport("lxml").getMember("etree").getMember("iterparse").getACall()
}
LxmlIterparseCall() { this = etreeRef().getMember("iterparse").getACall() }
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("source")] }

View File

@@ -9,12 +9,15 @@ private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
*
* Provides models for the `MarkupSafe` PyPI package.
* See https://markupsafe.palletsprojects.com/en/2.0.x/.
*/
private module MarkupSafeModel {
module MarkupSafeModel {
/**
* Provides models for the `markupsafe.Markup` class
*
@@ -26,6 +29,8 @@ private module MarkupSafeModel {
result = API::moduleImport("markupsafe").getMember("Markup")
or
result = API::moduleImport("flask").getMember("Markup")
or
result = ModelOutput::getATypeNode("markupsafe.Markup~Subclass").getASubclass*()
}
/**

View File

@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -28,6 +29,8 @@ module Multidict {
/** Gets a reference to a `MultiDictProxy` class. */
API::Node classRef() {
result = API::moduleImport("multidict").getMember(["MultiDictProxy", "CIMultiDictProxy"])
or
result = ModelOutput::getATypeNode("multidict.MultiDictProxy~Subclass").getASubclass*()
}
/**

View File

@@ -11,14 +11,17 @@ private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.PEP249
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
*
* Provides models for the `peewee` PyPI package.
* See
* - https://pypi.org/project/peewee/
* - https://docs.peewee-orm.com/en/latest/index.html
*/
private module Peewee {
module Peewee {
/** Provides models for the `peewee.Database` class and subclasses. */
module Database {
/** Gets a reference to the `peewee.Database` class or any subclass. */
@@ -31,7 +34,7 @@ private module Peewee {
.getMember(["SqliteDatabase", "MySQLDatabase", "PostgresqlDatabase"])
.getASubclass*()
or
// Ohter known subclasses, semi auto generated by using
// Other known subclasses, semi auto generated by using
// ```ql
// class DBClass extends Class, SelfRefMixin {
// DBClass() {
@@ -153,6 +156,8 @@ private module Peewee {
.getMember("sqliteq")
.getMember("SqliteQueueDatabase")
.getASubclass*()
or
result = ModelOutput::getATypeNode("peewee.Database~Subclass").getASubclass*()
}
/** Gets a reference to an instance of `peewee.Database` or any subclass. */

View File

@@ -9,15 +9,18 @@
private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
*
* Provides models for the `pycurl` PyPI package.
*
* See
* - https://pypi.org/project/pycurl/
* - https://pycurl.io/docs/latest/
*/
private module Pycurl {
module Pycurl {
/**
* Provides models for the `pycurl.Curl` class
*
@@ -25,7 +28,11 @@ private module Pycurl {
*/
module Curl {
/** Gets a reference to the `pycurl.Curl` class. */
private API::Node classRef() { result = API::moduleImport("pycurl").getMember("Curl") }
API::Node classRef() {
result = API::moduleImport("pycurl").getMember("Curl")
or
result = ModelOutput::getATypeNode("pycurl.Curl~Subclass").getASubclass*()
}
/** Gets a reference to an instance of `pycurl.Curl`. */
private API::Node instance() { result = classRef().getReturn() }

View File

@@ -11,6 +11,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -31,6 +32,8 @@ module Pydantic {
/** Gets a reference to a `pydantic.BaseModel` subclass (a pydantic model). */
API::Node subclassRef() {
result = API::moduleImport("pydantic").getMember("BaseModel").getASubclass+()
or
result = ModelOutput::getATypeNode("pydantic.BaseModel~Subclass").getASubclass*()
}
/**

View File

@@ -12,6 +12,7 @@ private import semmle.python.ApiGraphs
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.Stdlib
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -22,7 +23,7 @@ private import semmle.python.frameworks.Stdlib
* - https://pypi.org/project/requests/
* - https://requests.readthedocs.io/en/latest/
*/
private module Requests {
module Requests {
/**
* An outgoing HTTP request, from the `requests` library.
*
@@ -91,10 +92,12 @@ private module Requests {
*/
module Response {
/** Gets a reference to the `requests.models.Response` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("requests").getMember("models").getMember("Response")
or
result = API::moduleImport("requests").getMember("Response")
or
result = ModelOutput::getATypeNode("requests.models.Response~Subclass").getASubclass*()
}
/**

View File

@@ -16,6 +16,7 @@ private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.Django
private import semmle.python.frameworks.Stdlib
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -27,7 +28,7 @@ private import semmle.python.frameworks.Stdlib
* - https://www.django-rest-framework.org/
* - https://pypi.org/project/djangorestframework/
*/
private module RestFramework {
module RestFramework {
// ---------------------------------------------------------------------------
// rest_framework.views.APIView handling
// ---------------------------------------------------------------------------
@@ -215,8 +216,10 @@ private module RestFramework {
*/
module Request {
/** Gets a reference to the `rest_framework.request.Request` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("rest_framework").getMember("request").getMember("Request")
or
result = ModelOutput::getATypeNode("rest_framework.request.Request~Subclass").getASubclass*()
}
/**
@@ -299,8 +302,11 @@ private module RestFramework {
*/
module Response {
/** Gets a reference to the `rest_framework.response.Response` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("rest_framework").getMember("response").getMember("Response")
or
result =
ModelOutput::getATypeNode("rest_framework.response.Response~Subclass").getASubclass*()
}
/** A direct instantiation of `rest_framework.response.Response`. */
@@ -328,6 +334,23 @@ private module RestFramework {
* See https://www.django-rest-framework.org/api-guide/exceptions/#api-reference
*/
module ApiException {
API::Node classRef() {
exists(string className |
className in [
"APIException", "ValidationError", "ParseError", "AuthenticationFailed",
"NotAuthenticated", "PermissionDenied", "NotFound", "NotAcceptable"
] and
result =
API::moduleImport("rest_framework")
.getMember("exceptions")
.getMember(className)
.getASubclass*()
)
or
result =
ModelOutput::getATypeNode("rest_framework.exceptions.APIException~Subclass").getASubclass*()
}
/** A direct instantiation of `rest_framework.exceptions.ApiException` or subclass. */
private class ClassInstantiation extends Http::Server::HttpResponse::Range,
DataFlow::CallCfgNode
@@ -345,6 +368,8 @@ private module RestFramework {
.getMember("exceptions")
.getMember(className)
.getACall()
or
this = classRef().getACall() and className = "APIException"
}
override DataFlow::Node getBody() {

View File

@@ -13,6 +13,7 @@ private import semmle.python.Concepts
// This import is done like this to avoid importing the deprecated top-level things that
// would pollute the namespace
private import semmle.python.frameworks.PEP249::PEP249 as PEP249
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -34,10 +35,12 @@ module SqlAlchemy {
*/
module Engine {
/** Gets a reference to a SQLAlchemy Engine class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("sqlalchemy").getMember("engine").getMember("Engine")
or
result = API::moduleImport("sqlalchemy").getMember("future").getMember("Engine")
or
result = ModelOutput::getATypeNode("sqlalchemy.engine.Engine~Subclass").getASubclass*()
}
/**
@@ -87,7 +90,7 @@ module SqlAlchemy {
*/
module Connection {
/** Gets a reference to a SQLAlchemy Connection class. */
private API::Node classRef() {
API::Node classRef() {
result =
API::moduleImport("sqlalchemy")
.getMember("engine")
@@ -95,6 +98,8 @@ module SqlAlchemy {
.getMember("Connection")
or
result = API::moduleImport("sqlalchemy").getMember("future").getMember("Connection")
or
result = ModelOutput::getATypeNode("sqlalchemy.engine.Connection~Subclass").getASubclass*()
}
/**
@@ -178,8 +183,10 @@ module SqlAlchemy {
*/
module Session {
/** Gets a reference to the `sqlalchemy.orm.Session` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("sqlalchemy").getMember("orm").getMember("Session")
or
result = ModelOutput::getATypeNode("sqlalchemy.orm.Session~Subclass").getASubclass*()
}
/**

View File

@@ -13,6 +13,7 @@ private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.Stdlib
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -35,6 +36,8 @@ module Starlette {
result = API::moduleImport("starlette").getMember("websockets").getMember("WebSocket")
or
result = API::moduleImport("fastapi").getMember("WebSocket")
or
result = ModelOutput::getATypeNode("starlette.websockets.WebSocket~Subclass").getASubclass*()
}
/**
@@ -100,8 +103,10 @@ module Starlette {
*/
module Url {
/** Gets a reference to the `starlette.requests.URL` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("starlette").getMember("requests").getMember("URL")
or
result = ModelOutput::getATypeNode("starlette.requests.URL~Subclass").getASubclass*()
}
/**

View File

@@ -17,6 +17,7 @@ private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
// modeling split over multiple files to keep this file from becoming too big
private import semmle.python.frameworks.Stdlib.Urllib
private import semmle.python.frameworks.Stdlib.Urllib2
private import semmle.python.frameworks.data.ModelsAsData
/** Provides models for the Python standard library. */
module Stdlib {
@@ -181,8 +182,10 @@ module Stdlib {
*/
module SplitResult {
/** Gets a reference to the `urllib.parse.SplitResult` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("urllib").getMember("parse").getMember("SplitResult")
or
result = ModelOutput::getATypeNode("urllib.parse.SplitResult~Subclass").getASubclass*()
}
/**
@@ -252,8 +255,10 @@ module Stdlib {
*/
module Logger {
/** Gets a reference to the `logging.Logger` class or any subclass. */
private API::Node subclassRef() {
API::Node subclassRef() {
result = API::moduleImport("logging").getMember("Logger").getASubclass*()
or
result = ModelOutput::getATypeNode("logging.Logger~Subclass").getASubclass*()
}
/**
@@ -292,13 +297,15 @@ module Stdlib {
}
/**
* INTERNAL: Do not use.
*
* Provides models for the Python standard library.
*
* This module is marked private as exposing it means committing to 1-year deprecation
* policy, and the code is not in a polished enough state that we want to do so -- at
* least not without having convincing use-cases for it :)
*/
private module StdlibPrivate {
module StdlibPrivate {
// ---------------------------------------------------------------------------
// os
// ---------------------------------------------------------------------------
@@ -1293,14 +1300,36 @@ private module StdlibPrivate {
// pickle
// ---------------------------------------------------------------------------
/** Gets a reference to any of the `pickle` modules. */
API::Node pickle() { result = API::moduleImport(["pickle", "cPickle", "_pickle"]) }
API::Node pickle() {
result = API::moduleImport(["pickle", "cPickle", "_pickle"])
or
result = ModelOutput::getATypeNode("pickle~Alias")
}
/**
* Gets a reference to `pickle.load`
*/
API::Node pickle_load() {
result = pickle().getMember("load")
or
result = ModelOutput::getATypeNode("pickle.load~Alias")
}
/**
* Gets a reference to `pickle.loads`
*/
API::Node pickle_loads() {
result = pickle().getMember("loads")
or
result = ModelOutput::getATypeNode("pickle.loads~Alias")
}
/**
* A call to `pickle.load`
* See https://docs.python.org/3/library/pickle.html#pickle.load
*/
private class PickleLoadCall extends Decoding::Range, DataFlow::CallCfgNode {
PickleLoadCall() { this = pickle().getMember("load").getACall() }
private class PickleLoadCall extends Decoding::Range, API::CallNode {
PickleLoadCall() { this = pickle_load().getACall() }
override predicate mayExecuteInput() { any() }
@@ -1315,8 +1344,8 @@ private module StdlibPrivate {
* A call to `pickle.loads`
* See https://docs.python.org/3/library/pickle.html#pickle.loads
*/
private class PickleLoadsCall extends Decoding::Range, DataFlow::CallCfgNode {
PickleLoadsCall() { this = pickle().getMember("loads").getACall() }
private class PickleLoadsCall extends Decoding::Range, API::CallNode {
PickleLoadsCall() { this = pickle_loads().getACall() }
override predicate mayExecuteInput() { any() }
@@ -1729,8 +1758,21 @@ private module StdlibPrivate {
* See https://docs.python.org/3/library/cgi.html.
*/
module FieldStorage {
/** Gets a reference to the `cgi.FieldStorage` class. */
API::Node classRef() { result = cgi().getMember("FieldStorage") }
/**
* DEPRECATED: Use `subclassRef` predicate instead.
*
* Gets a reference to the `cgi.FieldStorage` class.
*/
deprecated API::Node classRef() {
result = API::moduleImport("cgi").getMember("FieldStorage")
}
/** Gets a reference to the `cgi.FieldStorage` class or any subclass. */
API::Node subclassRef() {
result = API::moduleImport("cgi").getMember("FieldStorage").getASubclass*()
or
result = ModelOutput::getATypeNode("cgi.FieldStorage~Subclass").getASubclass*()
}
/**
* A source of instances of `cgi.FieldStorage`, extend this class to model new instances.
@@ -1753,13 +1795,13 @@ private module StdlibPrivate {
private class ClassInstantiation extends InstanceSource, RemoteFlowSource::Range,
DataFlow::CallCfgNode
{
ClassInstantiation() { this = classRef().getACall() }
ClassInstantiation() { this = subclassRef().getACall() }
override string getSourceType() { result = "cgi.FieldStorage" }
}
/** Gets a reference to an instance of `cgi.FieldStorage`. */
API::Node instance() { result = classRef().getReturn() }
API::Node instance() { result = subclassRef().getReturn() }
/** Gets a reference to the `getvalue` method on a `cgi.FieldStorage` instance. */
API::Node getvalueRef() { result = instance().getMember("getvalue") }
@@ -2013,7 +2055,7 @@ private module StdlibPrivate {
* - https://docs.python.org/3.9/library/http.server.html#http.server.BaseHTTPRequestHandler
* - https://docs.python.org/2.7/library/basehttpserver.html#BaseHTTPServer.BaseHTTPRequestHandler
*/
private module HttpRequestHandler {
module BaseHttpRequestHandler {
/** Gets a reference to the `BaseHttpRequestHandler` class or any subclass. */
API::Node subclassRef() {
result =
@@ -2027,6 +2069,9 @@ private module StdlibPrivate {
API::moduleImport("http").getMember("server").getMember("SimpleHTTPRequestHandler"),
API::moduleImport("http").getMember("server").getMember("CGIHTTPRequestHandler"),
].getASubclass*()
or
result =
ModelOutput::getATypeNode("http.server.BaseHTTPRequestHandler~Subclass").getASubclass*()
}
/** A HttpRequestHandler class definition (most likely in project code). */
@@ -2121,17 +2166,20 @@ private module StdlibPrivate {
// wsgiref.simple_server
// ---------------------------------------------------------------------------
/** Provides models for the `wsgiref.simple_server` module. */
private module WsgirefSimpleServer {
module WsgirefSimpleServer {
API::Node subclassRef() {
result =
API::moduleImport("wsgiref")
.getMember("simple_server")
.getMember("WSGIServer")
.getASubclass*()
or
result =
ModelOutput::getATypeNode("wsgiref.simple_server.WSGIServer~Subclass").getASubclass*()
}
class WsgiServerSubclass extends Class, SelfRefMixin {
WsgiServerSubclass() {
this.getParent() =
API::moduleImport("wsgiref")
.getMember("simple_server")
.getMember("WSGIServer")
.getASubclass*()
.asSource()
.asExpr()
}
WsgiServerSubclass() { this.getParent() = subclassRef().asSource().asExpr() }
}
/**
@@ -2148,13 +2196,7 @@ private module StdlibPrivate {
exists(DataFlow::Node appArg, DataFlow::CallCfgNode setAppCall |
(
setAppCall =
API::moduleImport("wsgiref")
.getMember("simple_server")
.getMember("WSGIServer")
.getASubclass*()
.getReturn()
.getMember("set_app")
.getACall()
WsgirefSimpleServer::subclassRef().getReturn().getMember("set_app").getACall()
or
setAppCall
.(DataFlow::MethodCallNode)
@@ -2292,7 +2334,7 @@ private module StdlibPrivate {
*/
module HttpConnection {
/** Gets a reference to the `http.client.HttpConnection` class. */
private API::Node classRef() {
API::Node classRef() {
exists(string className | className in ["HTTPConnection", "HTTPSConnection"] |
// Python 3
result = API::moduleImport("http").getMember("client").getMember(className)
@@ -2303,6 +2345,8 @@ private module StdlibPrivate {
result =
API::moduleImport("six").getMember("moves").getMember("http_client").getMember(className)
)
or
result = ModelOutput::getATypeNode("http.client.HTTPConnection~Subclass").getASubclass*()
}
/**
@@ -2414,8 +2458,10 @@ private module StdlibPrivate {
*/
module HttpResponse {
/** Gets a reference to the `http.client.HttpResponse` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("http").getMember("client").getMember("HTTPResponse")
or
result = ModelOutput::getATypeNode("http.client.HTTPResponse~Subclass").getASubclass*()
}
/**
@@ -3534,8 +3580,10 @@ private module StdlibPrivate {
*/
module StringIO {
/** Gets a reference to the `io.StringIO` class. */
private API::Node classRef() {
API::Node classRef() {
result = API::moduleImport("io").getMember(["StringIO", "BytesIO"])
or
result = ModelOutput::getATypeNode("io.StringIO~Subclass").getASubclass*()
}
/**
@@ -3585,6 +3633,12 @@ private module StdlibPrivate {
// ---------------------------------------------------------------------------
// xml.etree.ElementTree
// ---------------------------------------------------------------------------
/** Gets a reference to the `xml.etree.ElementTree` class */
API::Node elementTreeClassRef() {
result = API::moduleImport("xml").getMember("etree").getMember("ElementTree").getASubclass*() or
result = ModelOutput::getATypeNode("xml.etree.ElementTree~Subclass").getASubclass*()
}
/**
* An instance of `xml.etree.ElementTree.ElementTree`.
*
@@ -3592,20 +3646,10 @@ private module StdlibPrivate {
*/
private API::Node elementTreeInstance() {
//parse to a tree
result =
API::moduleImport("xml")
.getMember("etree")
.getMember("ElementTree")
.getMember("parse")
.getReturn()
result = elementTreeClassRef().getMember("parse").getReturn()
or
// construct a tree without parsing
result =
API::moduleImport("xml")
.getMember("etree")
.getMember("ElementTree")
.getMember("ElementTree")
.getReturn()
result = elementTreeClassRef().getMember("ElementTree").getReturn()
}
/**
@@ -3618,21 +3662,9 @@ private module StdlibPrivate {
result = elementTreeInstance().getMember(["parse", "getroot"]).getReturn()
or
// parse directly to an element
result =
API::moduleImport("xml")
.getMember("etree")
.getMember("ElementTree")
.getMember(["fromstring", "fromstringlist", "XML"])
.getReturn()
result = elementTreeClassRef().getMember(["fromstring", "fromstringlist", "XML"]).getReturn()
or
result =
API::moduleImport("xml")
.getMember("etree")
.getMember("ElementTree")
.getMember("XMLParser")
.getReturn()
.getMember("close")
.getReturn()
result = elementTreeClassRef().getMember("XMLParser").getReturn().getMember("close").getReturn()
}
/**
@@ -3677,12 +3709,7 @@ private module StdlibPrivate {
/** A direct instantiation of `xml.etree` parsers. */
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
ClassInstantiation() {
this =
API::moduleImport("xml")
.getMember("etree")
.getMember("ElementTree")
.getMember(["XMLParser", "XMLPullParser"])
.getACall()
this = elementTreeClassRef().getMember(["XMLParser", "XMLPullParser"]).getACall()
}
}
@@ -3739,9 +3766,7 @@ private module StdlibPrivate {
private class XmlEtreeParsing extends DataFlow::CallCfgNode, XML::XmlParsing::Range {
XmlEtreeParsing() {
this =
API::moduleImport("xml")
.getMember("etree")
.getMember("ElementTree")
elementTreeClassRef()
.getMember(["fromstring", "fromstringlist", "XML", "XMLID", "parse", "iterparse"])
.getACall()
or
@@ -3789,12 +3814,7 @@ private module StdlibPrivate {
*/
private class FileAccessFromXmlEtreeParsing extends XmlEtreeParsing, FileSystemAccess::Range {
FileAccessFromXmlEtreeParsing() {
this =
API::moduleImport("xml")
.getMember("etree")
.getMember("ElementTree")
.getMember(["parse", "iterparse"])
.getACall()
this = elementTreeClassRef().getMember(["parse", "iterparse"]).getACall()
or
this = elementTreeInstance().getMember("parse").getACall()
// I considered whether we should try to reduce FPs from people passing file-like

View File

@@ -12,6 +12,7 @@ private import semmle.python.ApiGraphs
private import semmle.python.regex
private import semmle.python.frameworks.Stdlib
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
@@ -87,7 +88,11 @@ module Tornado {
*/
module RequestHandler {
/** Gets a reference to the `tornado.web.RequestHandler` class or any subclass. */
API::Node subclassRef() { result = web().getMember("RequestHandler").getASubclass*() }
API::Node subclassRef() {
result = web().getMember("RequestHandler").getASubclass*()
or
result = ModelOutput::getATypeNode("tornado.web.RequestHandler~Subclass").getASubclass*()
}
/** A RequestHandler class (most likely in project code). */
class RequestHandlerClass extends Class {
@@ -213,7 +218,11 @@ module Tornado {
*/
module Application {
/** Gets a reference to the `tornado.web.Application` class. */
API::Node classRef() { result = web().getMember("Application") }
API::Node classRef() {
result = web().getMember("Application")
or
result = ModelOutput::getATypeNode("tornado.web.Application~Subclass").getASubclass*()
}
/**
* A source of instances of `tornado.web.Application`, extend this class to model new instances.
@@ -270,7 +279,12 @@ module Tornado {
*/
module HttpServerRequest {
/** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */
API::Node classRef() { result = httputil().getMember("HttpServerRequest") }
API::Node classRef() {
result = httputil().getMember("HttpServerRequest")
or
result =
ModelOutput::getATypeNode("tornado.httputil.HttpServerRequest~Subclass").getASubclass*()
}
/**
* A source of instances of `tornado.httputil.HttpServerRequest`, extend this class to model new instances.

View File

@@ -9,15 +9,18 @@
private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
*
* Provides models for the `urllib3` PyPI package.
*
* See
* - https://pypi.org/project/urllib3/
* - https://urllib3.readthedocs.io/en/stable/reference/
*/
private module Urllib3 {
module Urllib3 {
/**
* Provides models for the `urllib3.request.RequestMethods` class and subclasses, such
* as the `urllib3.PoolManager` class
@@ -30,7 +33,7 @@ private module Urllib3 {
*/
module PoolManager {
/** Gets a reference to the `urllib3.PoolManager` class. */
private API::Node classRef() {
API::Node classRef() {
result =
API::moduleImport("urllib3")
.getMember(["PoolManager", "ProxyManager", "HTTPConnectionPool", "HTTPSConnectionPool"])
@@ -40,6 +43,8 @@ private module Urllib3 {
.getMember("request")
.getMember("RequestMethods")
.getASubclass+()
or
result = ModelOutput::getATypeNode("urllib3.PoolManager~Subclass").getASubclass*()
}
/**

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.internal.DataFlowDispatch
/**
* INTERNAL: Do not use.
@@ -16,13 +17,16 @@ abstract class SelfRefMixin extends Class {
/**
* Gets a reference to instances of this class, originating from a self parameter of
* a method defined on this class.
*
* Note: TODO: This doesn't take MRO into account
* Note: TODO: This doesn't take staticmethod/classmethod into account
*/
private DataFlow::TypeTrackingNode getASelfRef(DataFlow::TypeTracker t) {
t.start() and
result.(DataFlow::ParameterNode).getParameter() = this.getAMethod().getArg(0)
exists(Class cls, Function meth |
cls = getADirectSuperclass*(this) and
meth = cls.getAMethod() and
not isStaticmethod(meth) and
not isClassmethod(meth) and
result.(DataFlow::ParameterNode).getParameter() = meth.getArg(0)
)
or
exists(DataFlow::TypeTracker t2 | result = this.getASelfRef(t2).track(t2, t))
}
@@ -30,9 +34,6 @@ abstract class SelfRefMixin extends Class {
/**
* Gets a reference to instances of this class, originating from a self parameter of
* a method defined on this class.
*
* Note: TODO: This doesn't take MRO into account
* Note: TODO: This doesn't take staticmethod/classmethod into account
*/
DataFlow::Node getASelfRef() { this.getASelfRef(DataFlow::TypeTracker::end()).flowsTo(result) }
}

View File

@@ -7,32 +7,23 @@
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.internal.ImportResolution
private import semmle.python.ApiGraphs
private import semmle.python.filters.Tests
private import semmle.python.Module
// very much inspired by the draft at https://github.com/github/codeql/pull/5632
private module NotExposed {
module NotExposed {
// Instructions:
// This needs to be automated better, but for this prototype, here are some rough instructions:
// 0) get a database of the library you are about to model
// 1) fill out the `getAlreadyModeledClass` body below
// 2) quick-eval the `quickEvalMe` predicate below, and copy the output to your modeling predicate
class MySpec extends FindSubclassesSpec {
MySpec() { this = "MySpec" }
override API::Node getAlreadyModeledClass() {
// FILL ME OUT ! (but don't commit with any changes)
none()
// for example
// result = API::moduleImport("rest_framework").getMember("views").getMember("APIView")
}
}
predicate quickEvalMe(string newImport) {
newImport =
"// imports generated by python/frameworks/internal/SubclassFinder.qll\n" + "this = API::" +
concat(string newModelFullyQualified |
newModel(any(MySpec spec), newModelFullyQualified, _, _, _)
newModel(any(FindSubclassesSpec spec), newModelFullyQualified, _, _, _)
|
fullyQualifiedToApiGraphPath(newModelFullyQualified), " or this = API::"
)
@@ -75,14 +66,31 @@ private module NotExposed {
bindingset[this]
abstract class FindSubclassesSpec extends string {
/**
* Gets an API node for a class that has already been modeled. You can include
* `.getASubclass*()` without causing problems, but it is not needed.
*/
abstract API::Node getAlreadyModeledClass();
/**
* Gets the fully qualified name that this spec represents.
*
* This should be implemented by all classes for which `getSuperClass` is
* implemented, at least if they are defined in a different module than what they
* subclass.
*/
string getFullyQualifiedName() { none() }
FindSubclassesSpec getSuperClass() { none() }
final FindSubclassesSpec getSubClass() { result.getSuperClass() = this }
}
/**
* Holds if `newModelFullyQualified` describes either a new subclass, or a new alias, belonging to `spec` that we should include in our automated modeling.
* This new element is defined by `ast`, which is defined at `loc` in the module `mod`.
*/
query predicate newModel(
predicate newModel(
FindSubclassesSpec spec, string newModelFullyQualified, AstNode ast, Module mod, Location loc
) {
(
@@ -90,7 +98,8 @@ private module NotExposed {
or
newDirectAlias(spec, newModelFullyQualified, ast, mod, loc)
or
newImportStar(spec, newModelFullyQualified, ast, mod, _, _, loc)
newImportAlias(spec, newModelFullyQualified, mod, _, _, loc) and
ast = mod
)
}
@@ -103,15 +112,34 @@ private module NotExposed {
)
}
/**
* Holds if `fullyQualifiedName` is already explicitly modeled in the `spec`.
*
* For specs that do `.getASubclass*()`, items found by following a `.getASubclass`
* edge will not be considered explicitly modeled.
*/
bindingset[fullyQualifiedName]
predicate alreadyModeled(FindSubclassesSpec spec, string fullyQualifiedName) {
predicate alreadyExplicitlyModeled(FindSubclassesSpec spec, string fullyQualifiedName) {
fullyQualifiedToApiGraphPath(fullyQualifiedName) = spec.getAlreadyModeledClass().getPath()
}
predicate isNonTestProjectCode(AstNode ast) {
not ast.getScope*() instanceof TestScope and
not ast.getLocation().getFile().getRelativePath().matches("tests/%") and
exists(ast.getLocation().getFile().getRelativePath())
predicate isAllowedModule(Module mod) {
// for tests
mod.getName() = "find_subclass_test"
or
// don't include anything found in site-packages
exists(mod.getFile().getRelativePath()) and
not mod.getFile().getRelativePath().regexpMatch("(?i)((^|/)examples?|^docs)/.*") and
// to counter things like `my-example/app/foo.py` being allowed under `app.foo`
forall(string part | part = mod.getFile().getParent().getRelativePath().splitAt("/") |
legalShortName(part)
)
}
predicate isTestCode(AstNode ast) {
ast.getScope*() instanceof TestScope
or
ast.getLocation().getFile().getRelativePath().matches("tests/%")
}
predicate hasAllStatement(Module mod) {
@@ -144,40 +172,47 @@ private module NotExposed {
* ```
*/
predicate newDirectAlias(
FindSubclassesSpec spec, string newAliasFullyQualified, ImportMember importMember, Module mod,
Location loc
FindSubclassesSpec spec, string newAliasFullyQualified, Expr value, Module mod, Location loc
) {
importMember = newOrExistingModeling(spec).getAValueReachableFromSource().asExpr() and
importMember.getScope() = mod and
loc = importMember.getLocation() and
(
mod.isPackageInit() and
newAliasFullyQualified = mod.getPackageName() + "." + importMember.getName()
or
not mod.isPackageInit() and
newAliasFullyQualified = mod.getName() + "." + importMember.getName()
) and
(
not hasAllStatement(mod)
or
mod.declaredInAll(importMember.getName())
) and
not alreadyModeled(spec, newAliasFullyQualified) and
isNonTestProjectCode(importMember)
exists(Alias alias | value = alias.getValue() |
value = newOrExistingModeling(spec).getASubclass*().getAValueReachableFromSource().asExpr() and
value.getScope() = mod and
loc = value.getLocation() and
exists(string base |
mod.isPackageInit() and base = mod.getPackageName()
or
not mod.isPackageInit() and base = mod.getName()
|
newAliasFullyQualified = base + "." + alias.getAsname().(Name).getId()
) and
(
not hasAllStatement(mod)
or
mod.declaredInAll(alias.getAsname().(Name).getId())
) and
not alreadyExplicitlyModeled(spec, newAliasFullyQualified) and
not isTestCode(value) and
isAllowedModule(mod)
)
}
/** same as `newDirectAlias` predicate, but handling `from <module> import *`, considering all `<member>`, where `<module>.<member>` belongs to `spec`. */
predicate newImportStar(
FindSubclassesSpec spec, string newAliasFullyQualified, ImportStar importStar, Module mod,
API::Node relevantClass, string relevantName, Location loc
/**
* same as `newDirectAlias` predicate, but written in a generic way to handle any import (also import *).
*
* it might be safe to delete `newDirectAlias` with this in place, but have not done the testing yet.
*/
predicate newImportAlias(
FindSubclassesSpec spec, string newAliasFullyQualified, Module mod, DataFlow::Node def,
string relevantName, Location loc
) {
relevantClass = newOrExistingModeling(spec) and
loc = importStar.getLocation() and
importStar.getScope() = mod and
// WHAT A HACK :D :D
relevantClass.getPath() =
relevantClass.getAPredecessor().getPath() + ".getMember(\"" + relevantName + "\")" and
relevantClass.getAPredecessor().getAValueReachableFromSource().asExpr() = importStar.getModule() and
loc = mod.getLocation() and
exists(API::Node relevantClass, ControlFlowNode value |
relevantClass = newOrExistingModeling(spec).getASubclass*() and
ImportResolution::module_export(mod, relevantName, def) and
value = relevantClass.getAValueReachableFromSource().asCfgNode() and
value = def.asCfgNode()
// value could be a ClassExpr if a new class is defined, or a Name if defining an alias
) and
(
mod.isPackageInit() and
newAliasFullyQualified = mod.getPackageName() + "." + relevantName
@@ -190,8 +225,9 @@ private module NotExposed {
or
mod.declaredInAll(relevantName)
) and
not alreadyModeled(spec, newAliasFullyQualified) and
isNonTestProjectCode(importStar)
not alreadyExplicitlyModeled(spec, newAliasFullyQualified) and
not isTestCode(mod) and
isAllowedModule(mod)
}
/** Holds if `classExpr` defines a new subclass that belongs to `spec`, which has the fully qualified name `newSubclassQualified`. */
@@ -203,7 +239,8 @@ private module NotExposed {
classExpr.getScope() = mod and
newSubclassQualified = mod.getName() + "." + classExpr.getName() and
loc = classExpr.getLocation() and
not alreadyModeled(spec, newSubclassQualified) and
isNonTestProjectCode(classExpr)
not alreadyExplicitlyModeled(spec, newSubclassQualified) and
not isTestCode(classExpr) and
isAllowedModule(mod)
}
}