Skip to content

Commit c0a2c25

Browse files
committed
Python: Restructure modeling of xml.etree parsers
1 parent a033b71 commit c0a2c25

File tree

2 files changed

+59
-41
lines changed
  • python/ql
    • src/experimental/semmle/python/frameworks
    • test/experimental/library-tests/frameworks/XML

2 files changed

+59
-41
lines changed

python/ql/src/experimental/semmle/python/frameworks/Xml.qll

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,65 @@ private import semmle.python.ApiGraphs
1010

1111
private module XmlEtree {
1212
/**
13-
* A call to `xml.etree.ElementTree.XMLParser`.
13+
* Provides models for `xml.etree` parsers
14+
*
15+
* See
16+
* - https://docs.python.org/3.10/library/xml.etree.elementtree.html#xml.etree.ElementTree.XMLParser
17+
* - https://docs.python.org/3.10/library/xml.etree.elementtree.html#xml.etree.ElementTree.XMLPullParser
1418
*/
15-
private class XMLEtreeParser extends DataFlow::CallCfgNode, XML::XMLParser::Range {
16-
XMLEtreeParser() {
17-
this =
18-
API::moduleImport("xml")
19-
.getMember("etree")
20-
.getMember("ElementTree")
21-
.getMember("XMLParser")
22-
.getACall()
19+
module XMLParser {
20+
/**
21+
* A source of instances of `xml.etree` parsers, extend this class to model new instances.
22+
*
23+
* This can include instantiations of the class, return values from function
24+
* calls, or a special parameter that will be set when functions are called by an external
25+
* library.
26+
*
27+
* Use the predicate `XMLParser::instance()` to get references to instances of `xml.etree` parsers.
28+
*/
29+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
30+
31+
/** A direct instantiation of `xml.etree` parsers. */
32+
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
33+
ClassInstantiation() {
34+
this =
35+
API::moduleImport("xml")
36+
.getMember("etree")
37+
.getMember("ElementTree")
38+
.getMember("XMLParser")
39+
.getACall()
40+
or
41+
this =
42+
API::moduleImport("xml")
43+
.getMember("etree")
44+
.getMember("ElementTree")
45+
.getMember("XMLPullParser")
46+
.getACall()
47+
}
2348
}
2449

25-
override DataFlow::Node getAnInput() { none() }
50+
/** Gets a reference to an `xml.etree` parser instance. */
51+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
52+
t.start() and
53+
result instanceof InstanceSource
54+
or
55+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
56+
}
2657

27-
override predicate vulnerable(XML::XMLVulnerabilityKind kind) {
28-
kind.isBillionLaughs() or kind.isQuadraticBlowup()
58+
/** Gets a reference to an `xml.etree` parser instance. */
59+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
60+
61+
/**
62+
* A call to the `feed` method of an `xml.etree` parser.
63+
*/
64+
private class XMLEtreeParserFeedCall extends DataFlow::MethodCallNode, XML::XMLParsing::Range {
65+
XMLEtreeParserFeedCall() { this.calls(instance(), "feed") }
66+
67+
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
68+
69+
override predicate vulnerable(XML::XMLVulnerabilityKind kind) {
70+
kind.isBillionLaughs() or kind.isQuadraticBlowup()
71+
}
2972
}
3073
}
3174

@@ -61,33 +104,8 @@ private module XmlEtree {
61104
}
62105

63106
override predicate vulnerable(XML::XMLVulnerabilityKind kind) {
64-
not exists(this.getArgByName("parser")) and
65-
(kind.isBillionLaughs() or kind.isQuadraticBlowup())
66-
or
67-
exists(XML::XMLParser xmlParser |
68-
xmlParser = this.getArgByName("parser").getALocalSource() and xmlParser.vulnerable(kind)
69-
)
70-
}
71-
}
72-
73-
/**
74-
* A call to the `feed` method of an `xml.etree` parser.
75-
*/
76-
private class XMLEtreeParserFeedCall extends DataFlow::CallCfgNode, XML::XMLParsing::Range {
77-
XMLEtreeParserFeedCall() {
78-
this =
79-
API::moduleImport("xml")
80-
.getMember("etree")
81-
.getMember("ElementTree")
82-
.getMember("XMLParser")
83-
.getReturn()
84-
.getMember("feed")
85-
.getACall()
86-
}
87-
88-
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
89-
90-
override predicate vulnerable(XML::XMLVulnerabilityKind kind) {
107+
// note: it does not matter what `xml.etree` parser you are using, you cannot
108+
// change the security features anyway :|
91109
kind.isBillionLaughs() or kind.isQuadraticBlowup()
92110
}
93111
}

python/ql/test/experimental/library-tests/frameworks/XML/xml_etree.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535

3636
# manual use of feed method on XMLPullParser
3737
parser = xml.etree.ElementTree.XMLPullParser()
38-
parser.feed(x) # $ MISSING: input=x vuln='Billion Laughs' vuln='Quadratic Blowup'
39-
parser.feed(data=x) # $ MISSING: input=x vuln='Billion Laughs' vuln='Quadratic Blowup'
38+
parser.feed(x) # $ input=x vuln='Billion Laughs' vuln='Quadratic Blowup'
39+
parser.feed(data=x) # $ input=x vuln='Billion Laughs' vuln='Quadratic Blowup'
4040
parser.close()
4141

4242
# note: it's technically possible to use the thing wrapper func `fromstring` with an

0 commit comments

Comments
 (0)