mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #9176 from geoffw0/xxe9
C++: Clean up the XXE query QL.
This commit is contained in:
78
cpp/ql/src/Security/CWE/CWE-611/Libxml2.qll
Normal file
78
cpp/ql/src/Security/CWE/CWE-611/Libxml2.qll
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Models the libxml2 XML library.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import XML
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
|
||||
/**
|
||||
* A call to a `libxml2` function that parses XML.
|
||||
*/
|
||||
class Libxml2ParseCall extends FunctionCall {
|
||||
int optionsArg;
|
||||
|
||||
Libxml2ParseCall() {
|
||||
exists(string fname | this.getTarget().getName() = fname |
|
||||
fname = "xmlCtxtUseOptions" and optionsArg = 1
|
||||
or
|
||||
fname = "xmlReadFile" and optionsArg = 2
|
||||
or
|
||||
fname = ["xmlCtxtReadFile", "xmlParseInNodeContext", "xmlReadDoc", "xmlReadFd"] and
|
||||
optionsArg = 3
|
||||
or
|
||||
fname = ["xmlCtxtReadDoc", "xmlCtxtReadFd", "xmlReadMemory"] and optionsArg = 4
|
||||
or
|
||||
fname = ["xmlCtxtReadMemory", "xmlReadIO"] and optionsArg = 5
|
||||
or
|
||||
fname = "xmlCtxtReadIO" and optionsArg = 6
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the argument that specifies `xmlParserOption`s.
|
||||
*/
|
||||
Expr getOptions() { result = this.getArgument(optionsArg) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An `xmlParserOption` for `libxml2` that is considered unsafe.
|
||||
*/
|
||||
class Libxml2BadOption extends EnumConstant {
|
||||
Libxml2BadOption() { this.getName() = ["XML_PARSE_NOENT", "XML_PARSE_DTDLOAD"] }
|
||||
}
|
||||
|
||||
/**
|
||||
* The libxml2 XML library.
|
||||
*/
|
||||
class LibXml2Library extends XmlLibrary {
|
||||
LibXml2Library() { this = "LibXml2Library" }
|
||||
|
||||
override predicate configurationSource(DataFlow::Node node, string flowstate) {
|
||||
// source is an `options` argument on a libxml2 parse call that specifies
|
||||
// at least one unsafe option.
|
||||
//
|
||||
// note: we don't need to track an XML object for libxml2, so we don't
|
||||
// really need data flow. Nevertheless we jam it into this configuration,
|
||||
// with matching sources and sinks. This allows results to be presented by
|
||||
// the same query, in a consistent way as other results with flow paths.
|
||||
exists(Libxml2ParseCall call, Expr options |
|
||||
options = call.getOptions() and
|
||||
node.asExpr() = options and
|
||||
flowstate = "libxml2" and
|
||||
exists(Libxml2BadOption opt |
|
||||
globalValueNumber(options).getAnExpr().getValue().toInt().bitAnd(opt.getValue().toInt()) !=
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate configurationSink(DataFlow::Node node, string flowstate) {
|
||||
// sink is the `options` argument on a `libxml2` parse call.
|
||||
exists(Libxml2ParseCall call, Expr options |
|
||||
options = call.getOptions() and
|
||||
node.asExpr() = options and
|
||||
flowstate = "libxml2"
|
||||
)
|
||||
}
|
||||
}
|
||||
55
cpp/ql/src/Security/CWE/CWE-611/XML.qll
Normal file
55
cpp/ql/src/Security/CWE/CWE-611/XML.qll
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Provides a abstract classes for modeling XML libraries. This design is
|
||||
* currently specialized for the purposes of the XXE query.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
import Xerces
|
||||
import Libxml2
|
||||
|
||||
/**
|
||||
* A flow state representing a possible configuration of an XML object.
|
||||
*/
|
||||
abstract class XxeFlowState extends DataFlow::FlowState {
|
||||
bindingset[this]
|
||||
XxeFlowState() { any() } // required characteristic predicate
|
||||
}
|
||||
|
||||
/**
|
||||
* An XML library or interface.
|
||||
*/
|
||||
abstract class XmlLibrary extends string {
|
||||
bindingset[this]
|
||||
XmlLibrary() { any() } // required characteristic predicate
|
||||
|
||||
/**
|
||||
* Holds if `node` is the source node for a potentially unsafe configuration
|
||||
* object for this XML library, along with `flowstate` representing its
|
||||
* initial state.
|
||||
*/
|
||||
abstract predicate configurationSource(DataFlow::Node node, string flowstate);
|
||||
|
||||
/**
|
||||
* Holds if `node` is the sink node where an unsafe configuration object is
|
||||
* used to interpret XML.
|
||||
*/
|
||||
abstract predicate configurationSink(DataFlow::Node node, string flowstate);
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Expr` that changes the configuration of an XML object, transforming the
|
||||
* `XxeFlowState` that flows through it.
|
||||
*/
|
||||
abstract class XxeFlowStateTransformer extends Expr {
|
||||
/**
|
||||
* Gets the flow state that `flowstate` is transformed into.
|
||||
*
|
||||
* Due to limitations of the implementation the transformation defined by this
|
||||
* predicate must be idempotent, that is, for any input `x` it must be that:
|
||||
* ```
|
||||
* transform(transform(x)) = transform(x)
|
||||
* ```
|
||||
*/
|
||||
abstract XxeFlowState transform(XxeFlowState flowstate);
|
||||
}
|
||||
@@ -13,344 +13,8 @@
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.ir.dataflow.DataFlow
|
||||
import XML
|
||||
import DataFlow::PathGraph
|
||||
import semmle.code.cpp.ir.IR
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
|
||||
/**
|
||||
* A flow state representing a possible configuration of an XML object.
|
||||
*/
|
||||
abstract class XXEFlowState extends DataFlow::FlowState {
|
||||
bindingset[this]
|
||||
XXEFlowState() { any() } // required characteristic predicate
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Expr` that changes the configuration of an XML object, transforming the
|
||||
* `XXEFlowState` that flows through it.
|
||||
*/
|
||||
abstract class XXEFlowStateTransformer extends Expr {
|
||||
/**
|
||||
* Gets the flow state that `flowstate` is transformed into.
|
||||
*
|
||||
* Due to limitations of the implementation the transformation defined by this
|
||||
* predicate must be idempotent, that is, for any input `x` it must be that:
|
||||
* ```
|
||||
* transform(transform(x)) = transform(x)
|
||||
* ```
|
||||
*/
|
||||
abstract XXEFlowState transform(XXEFlowState flowstate);
|
||||
}
|
||||
|
||||
/**
|
||||
* The `AbstractDOMParser` class.
|
||||
*/
|
||||
class AbstractDOMParserClass extends Class {
|
||||
AbstractDOMParserClass() { this.hasName("AbstractDOMParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `XercesDOMParser` class.
|
||||
*/
|
||||
class XercesDOMParserClass extends Class {
|
||||
XercesDOMParserClass() { this.hasName("XercesDOMParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DOMLSParser` class.
|
||||
*/
|
||||
class DomLSParserClass extends Class {
|
||||
DomLSParserClass() { this.hasName("DOMLSParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `SAXParser` class.
|
||||
*/
|
||||
class SaxParserClass extends Class {
|
||||
SaxParserClass() { this.hasName("SAXParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `SAX2XMLReader` class.
|
||||
*/
|
||||
class Sax2XmlReader extends Class {
|
||||
Sax2XmlReader() { this.hasName("SAX2XMLReader") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a valid flow state for `AbstractDOMParser` or `SAXParser` flow.
|
||||
*
|
||||
* These flow states take the form `Xerces-A-B`, where:
|
||||
* - A is 1 if `setDisableDefaultEntityResolution` is `true`, 0 otherwise.
|
||||
* - B is 1 if `setCreateEntityReferenceNodes` is `true`, 0 otherwise.
|
||||
*/
|
||||
predicate encodeXercesFlowState(
|
||||
string flowstate, int disabledDefaultEntityResolution, int createEntityReferenceNodes
|
||||
) {
|
||||
flowstate = "Xerces-0-0" and
|
||||
disabledDefaultEntityResolution = 0 and
|
||||
createEntityReferenceNodes = 0
|
||||
or
|
||||
flowstate = "Xerces-0-1" and
|
||||
disabledDefaultEntityResolution = 0 and
|
||||
createEntityReferenceNodes = 1
|
||||
or
|
||||
flowstate = "Xerces-1-0" and
|
||||
disabledDefaultEntityResolution = 1 and
|
||||
createEntityReferenceNodes = 0
|
||||
or
|
||||
flowstate = "Xerces-1-1" and
|
||||
disabledDefaultEntityResolution = 1 and
|
||||
createEntityReferenceNodes = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state representing the configuration of an `AbstractDOMParser` or
|
||||
* `SAXParser` object.
|
||||
*/
|
||||
class XercesFlowState extends XXEFlowState {
|
||||
XercesFlowState() { encodeXercesFlowState(this, _, _) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state transformer for a call to
|
||||
* `AbstractDOMParser.setDisableDefaultEntityResolution` or
|
||||
* `SAXParser.setDisableDefaultEntityResolution`. Transforms the flow
|
||||
* state through the qualifier according to the setting in the parameter.
|
||||
*/
|
||||
class DisableDefaultEntityResolutionTransformer extends XXEFlowStateTransformer {
|
||||
Expr newValue;
|
||||
|
||||
DisableDefaultEntityResolutionTransformer() {
|
||||
exists(Call call, Function f |
|
||||
call.getTarget() = f and
|
||||
(
|
||||
f.getDeclaringType() instanceof AbstractDOMParserClass or
|
||||
f.getDeclaringType() instanceof SaxParserClass
|
||||
) and
|
||||
f.hasName("setDisableDefaultEntityResolution") and
|
||||
this = call.getQualifier() and
|
||||
newValue = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
final override XXEFlowState transform(XXEFlowState flowstate) {
|
||||
exists(int createEntityReferenceNodes |
|
||||
encodeXercesFlowState(flowstate, _, createEntityReferenceNodes) and
|
||||
(
|
||||
globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // true
|
||||
encodeXercesFlowState(result, 1, createEntityReferenceNodes)
|
||||
or
|
||||
not globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // false or unknown
|
||||
encodeXercesFlowState(result, 0, createEntityReferenceNodes)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state transformer for a call to
|
||||
* `AbstractDOMParser.setCreateEntityReferenceNodes`. Transforms the flow
|
||||
* state through the qualifier according to the setting in the parameter.
|
||||
*/
|
||||
class CreateEntityReferenceNodesTransformer extends XXEFlowStateTransformer {
|
||||
Expr newValue;
|
||||
|
||||
CreateEntityReferenceNodesTransformer() {
|
||||
exists(Call call, Function f |
|
||||
call.getTarget() = f and
|
||||
f.getClassAndName("setCreateEntityReferenceNodes") instanceof AbstractDOMParserClass and
|
||||
this = call.getQualifier() and
|
||||
newValue = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
final override XXEFlowState transform(XXEFlowState flowstate) {
|
||||
exists(int disabledDefaultEntityResolution |
|
||||
encodeXercesFlowState(flowstate, disabledDefaultEntityResolution, _) and
|
||||
(
|
||||
globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // true
|
||||
encodeXercesFlowState(result, disabledDefaultEntityResolution, 1)
|
||||
or
|
||||
not globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // false or unknown
|
||||
encodeXercesFlowState(result, disabledDefaultEntityResolution, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `XMLUni.fgXercesDisableDefaultEntityResolution` constant.
|
||||
*/
|
||||
class FeatureDisableDefaultEntityResolution extends Variable {
|
||||
FeatureDisableDefaultEntityResolution() {
|
||||
this.getName() = "fgXercesDisableDefaultEntityResolution" and
|
||||
this.getDeclaringType().getName() = "XMLUni"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state transformer for a call to `SAX2XMLReader.setFeature`
|
||||
* specifying the feature `XMLUni::fgXercesDisableDefaultEntityResolution`.
|
||||
* Transforms the flow state through the qualifier according to this setting.
|
||||
*/
|
||||
class SetFeatureTransformer extends XXEFlowStateTransformer {
|
||||
Expr newValue;
|
||||
|
||||
SetFeatureTransformer() {
|
||||
exists(Call call, Function f |
|
||||
call.getTarget() = f and
|
||||
f.getClassAndName("setFeature") instanceof Sax2XmlReader and
|
||||
this = call.getQualifier() and
|
||||
globalValueNumber(call.getArgument(0)).getAnExpr().(VariableAccess).getTarget() instanceof
|
||||
FeatureDisableDefaultEntityResolution and
|
||||
newValue = call.getArgument(1)
|
||||
)
|
||||
}
|
||||
|
||||
final override XXEFlowState transform(XXEFlowState flowstate) {
|
||||
exists(int createEntityReferenceNodes |
|
||||
encodeXercesFlowState(flowstate, _, createEntityReferenceNodes) and
|
||||
(
|
||||
globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // true
|
||||
encodeXercesFlowState(result, 1, createEntityReferenceNodes)
|
||||
or
|
||||
not globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // false or unknown
|
||||
encodeXercesFlowState(result, 0, createEntityReferenceNodes)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DOMLSParser.getDomConfig` function.
|
||||
*/
|
||||
class GetDomConfig extends Function {
|
||||
GetDomConfig() { this.getClassAndName("getDomConfig") instanceof DomLSParserClass }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DOMConfiguration.setParameter` function.
|
||||
*/
|
||||
class DomConfigurationSetParameter extends Function {
|
||||
DomConfigurationSetParameter() {
|
||||
this.getClassAndName("setParameter").getName() = "DOMConfiguration"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state transformer for a call to `DOMConfiguration.setParameter`
|
||||
* specifying the feature `XMLUni::fgXercesDisableDefaultEntityResolution`.
|
||||
* This is a slightly more complex transformer because the qualifier is a
|
||||
* `DOMConfiguration` pointer returned by `DOMLSParser.getDomConfig` - and it
|
||||
* is *that* qualifier we want to transform the flow state of.
|
||||
*/
|
||||
class DomConfigurationSetParameterTransformer extends XXEFlowStateTransformer {
|
||||
Expr newValue;
|
||||
|
||||
DomConfigurationSetParameterTransformer() {
|
||||
exists(FunctionCall getDomConfigCall, FunctionCall setParameterCall |
|
||||
// this is the qualifier of a call to `DOMLSParser.getDomConfig`.
|
||||
getDomConfigCall.getTarget() instanceof GetDomConfig and
|
||||
this = getDomConfigCall.getQualifier() and
|
||||
// `setParameterCall` is a call to `setParameter` on the return value of
|
||||
// the same call to `DOMLSParser.getDomConfig`.
|
||||
setParameterCall.getTarget() instanceof DomConfigurationSetParameter and
|
||||
globalValueNumber(setParameterCall.getQualifier()).getAnExpr() = getDomConfigCall and
|
||||
// the parameter being set is
|
||||
// `XMLUni::fgXercesDisableDefaultEntityResolution`.
|
||||
globalValueNumber(setParameterCall.getArgument(0)).getAnExpr().(VariableAccess).getTarget()
|
||||
instanceof FeatureDisableDefaultEntityResolution and
|
||||
// the value being set is `newValue`.
|
||||
newValue = setParameterCall.getArgument(1)
|
||||
)
|
||||
}
|
||||
|
||||
final override XXEFlowState transform(XXEFlowState flowstate) {
|
||||
exists(int createEntityReferenceNodes |
|
||||
encodeXercesFlowState(flowstate, _, createEntityReferenceNodes) and
|
||||
(
|
||||
globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // true
|
||||
encodeXercesFlowState(result, 1, createEntityReferenceNodes)
|
||||
or
|
||||
not globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // false or unknown
|
||||
encodeXercesFlowState(result, 0, createEntityReferenceNodes)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `AbstractDOMParser.parse`, `DOMLSParserClass.parse`, `SAXParser.parse`
|
||||
* or `SAX2XMLReader.parse` method.
|
||||
*/
|
||||
class ParseFunction extends Function {
|
||||
ParseFunction() {
|
||||
this.getClassAndName("parse") instanceof AbstractDOMParserClass or
|
||||
this.getClassAndName("parse") instanceof DomLSParserClass or
|
||||
this.getClassAndName("parse") instanceof SaxParserClass or
|
||||
this.getClassAndName("parse") instanceof Sax2XmlReader
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `createLSParser` function that returns a newly created `DOMLSParser`
|
||||
* object.
|
||||
*/
|
||||
class CreateLSParser extends Function {
|
||||
CreateLSParser() {
|
||||
this.hasName("createLSParser") and
|
||||
this.getUnspecifiedType().(PointerType).getBaseType() instanceof DomLSParserClass // returns a `DOMLSParser *`.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `createXMLReader` function that returns a newly created `SAX2XMLReader`
|
||||
* object.
|
||||
*/
|
||||
class CreateXmlReader extends Function {
|
||||
CreateXmlReader() {
|
||||
this.hasName("createXMLReader") and
|
||||
this.getUnspecifiedType().(PointerType).getBaseType() instanceof Sax2XmlReader // returns a `SAX2XMLReader *`.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a `libxml2` function that parses XML.
|
||||
*/
|
||||
class Libxml2ParseCall extends FunctionCall {
|
||||
int optionsArg;
|
||||
|
||||
Libxml2ParseCall() {
|
||||
exists(string fname | this.getTarget().getName() = fname |
|
||||
fname = "xmlCtxtUseOptions" and optionsArg = 1
|
||||
or
|
||||
fname = "xmlReadFile" and optionsArg = 2
|
||||
or
|
||||
fname = ["xmlCtxtReadFile", "xmlParseInNodeContext", "xmlReadDoc", "xmlReadFd"] and
|
||||
optionsArg = 3
|
||||
or
|
||||
fname = ["xmlCtxtReadDoc", "xmlCtxtReadFd", "xmlReadMemory"] and optionsArg = 4
|
||||
or
|
||||
fname = ["xmlCtxtReadMemory", "xmlReadIO"] and optionsArg = 5
|
||||
or
|
||||
fname = "xmlCtxtReadIO" and optionsArg = 6
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the argument that specifies `xmlParserOption`s.
|
||||
*/
|
||||
Expr getOptions() { result = this.getArgument(optionsArg) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An `xmlParserOption` for `libxml2` that is considered unsafe.
|
||||
*/
|
||||
class Libxml2BadOption extends EnumConstant {
|
||||
Libxml2BadOption() { this.getName() = ["XML_PARSE_NOENT", "XML_PARSE_DTDLOAD"] }
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration for tracking XML objects and their states.
|
||||
@@ -359,85 +23,25 @@ class XXEConfiguration extends DataFlow::Configuration {
|
||||
XXEConfiguration() { this = "XXEConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node, string flowstate) {
|
||||
// source is the write on `this` of a call to the `XercesDOMParser`
|
||||
// constructor.
|
||||
exists(CallInstruction call |
|
||||
call.getStaticCallTarget() = any(XercesDOMParserClass c).getAConstructor() and
|
||||
node.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
|
||||
call.getThisArgument() and
|
||||
encodeXercesFlowState(flowstate, 0, 1) // default configuration
|
||||
)
|
||||
or
|
||||
// source is the result of a call to `createLSParser`.
|
||||
exists(Call call |
|
||||
call.getTarget() instanceof CreateLSParser and
|
||||
call = node.asExpr() and
|
||||
encodeXercesFlowState(flowstate, 0, 1) // default configuration
|
||||
)
|
||||
or
|
||||
// source is the write on `this` of a call to the `SAXParser`
|
||||
// constructor.
|
||||
exists(CallInstruction call |
|
||||
call.getStaticCallTarget() = any(SaxParserClass c).getAConstructor() and
|
||||
node.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
|
||||
call.getThisArgument() and
|
||||
encodeXercesFlowState(flowstate, 0, 1) // default configuration
|
||||
)
|
||||
or
|
||||
// source is the result of a call to `createXMLReader`.
|
||||
exists(Call call |
|
||||
call.getTarget() instanceof CreateXmlReader and
|
||||
call = node.asExpr() and
|
||||
encodeXercesFlowState(flowstate, 0, 1) // default configuration
|
||||
)
|
||||
or
|
||||
// source is an `options` argument on a `libxml2` parse call that specifies
|
||||
// at least one unsafe option.
|
||||
//
|
||||
// note: we don't need to track an XML object for `libxml2`, so we don't
|
||||
// really need data flow. Nevertheless we jam it into this configuration,
|
||||
// with matching sources and sinks. This allows results to be presented by
|
||||
// the same query, in a consistent way as other results with flow paths.
|
||||
exists(Libxml2ParseCall call, Expr options |
|
||||
options = call.getOptions() and
|
||||
node.asExpr() = options and
|
||||
flowstate = "libxml2" and
|
||||
exists(Libxml2BadOption opt |
|
||||
globalValueNumber(options).getAnExpr().getValue().toInt().bitAnd(opt.getValue().toInt()) !=
|
||||
0
|
||||
)
|
||||
)
|
||||
any(XmlLibrary l).configurationSource(node, flowstate)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node node, string flowstate) {
|
||||
// sink is the read of the qualifier of a call to `parse`.
|
||||
exists(Call call |
|
||||
call.getTarget() instanceof ParseFunction and
|
||||
call.getQualifier() = node.asConvertedExpr()
|
||||
) and
|
||||
flowstate instanceof XercesFlowState and
|
||||
not encodeXercesFlowState(flowstate, 1, 1) // safe configuration
|
||||
or
|
||||
// sink is the `options` argument on a `libxml2` parse call.
|
||||
exists(Libxml2ParseCall call, Expr options |
|
||||
options = call.getOptions() and
|
||||
node.asExpr() = options and
|
||||
flowstate = "libxml2"
|
||||
)
|
||||
any(XmlLibrary l).configurationSink(node, flowstate)
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node node1, string state1, DataFlow::Node node2, string state2
|
||||
) {
|
||||
// create additional flow steps for `XXEFlowStateTransformer`s
|
||||
state2 = node2.asConvertedExpr().(XXEFlowStateTransformer).transform(state1) and
|
||||
// create additional flow steps for `XxeFlowStateTransformer`s
|
||||
state2 = node2.asConvertedExpr().(XxeFlowStateTransformer).transform(state1) and
|
||||
DataFlow::simpleLocalFlowStep(node1, node2)
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node, string flowstate) {
|
||||
// when the flowstate is transformed at a call node, block the original
|
||||
// flowstate value.
|
||||
node.asConvertedExpr().(XXEFlowStateTransformer).transform(flowstate) != flowstate
|
||||
node.asConvertedExpr().(XxeFlowStateTransformer).transform(flowstate) != flowstate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
376
cpp/ql/src/Security/CWE/CWE-611/Xerces.qll
Normal file
376
cpp/ql/src/Security/CWE/CWE-611/Xerces.qll
Normal file
@@ -0,0 +1,376 @@
|
||||
/**
|
||||
* Models the Xerces XML library.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import XML
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
import semmle.code.cpp.ir.IR
|
||||
|
||||
/**
|
||||
* Gets a valid flow state for `AbstractDOMParser` or `SAXParser` flow.
|
||||
*
|
||||
* These flow states take the form `Xerces-A-B`, where:
|
||||
* - A is 1 if `setDisableDefaultEntityResolution` is `true`, 0 otherwise.
|
||||
* - B is 1 if `setCreateEntityReferenceNodes` is `true`, 0 otherwise.
|
||||
*/
|
||||
predicate encodeXercesFlowState(
|
||||
string flowstate, int disabledDefaultEntityResolution, int createEntityReferenceNodes
|
||||
) {
|
||||
flowstate = "Xerces-0-0" and
|
||||
disabledDefaultEntityResolution = 0 and
|
||||
createEntityReferenceNodes = 0
|
||||
or
|
||||
flowstate = "Xerces-0-1" and
|
||||
disabledDefaultEntityResolution = 0 and
|
||||
createEntityReferenceNodes = 1
|
||||
or
|
||||
flowstate = "Xerces-1-0" and
|
||||
disabledDefaultEntityResolution = 1 and
|
||||
createEntityReferenceNodes = 0
|
||||
or
|
||||
flowstate = "Xerces-1-1" and
|
||||
disabledDefaultEntityResolution = 1 and
|
||||
createEntityReferenceNodes = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state representing the configuration of an `AbstractDOMParser` or
|
||||
* `SAXParser` object.
|
||||
*/
|
||||
class XercesFlowState extends XxeFlowState {
|
||||
XercesFlowState() { encodeXercesFlowState(this, _, _) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `AbstractDOMParser` class.
|
||||
*/
|
||||
class AbstractDomParserClass extends Class {
|
||||
AbstractDomParserClass() { this.hasName("AbstractDOMParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `XercesDOMParser` class.
|
||||
*/
|
||||
class XercesDomParserClass extends Class {
|
||||
XercesDomParserClass() { this.hasName("XercesDOMParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `XercesDOMParser` interface for the Xerces XML library.
|
||||
*/
|
||||
class XercesDomParserLibrary extends XmlLibrary {
|
||||
XercesDomParserLibrary() { this = "XercesDomParserLibrary" }
|
||||
|
||||
override predicate configurationSource(DataFlow::Node node, string flowstate) {
|
||||
// source is the write on `this` of a call to the `XercesDOMParser`
|
||||
// constructor.
|
||||
exists(CallInstruction call |
|
||||
call.getStaticCallTarget() = any(XercesDomParserClass c).getAConstructor() and
|
||||
node.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
|
||||
call.getThisArgument() and
|
||||
encodeXercesFlowState(flowstate, 0, 1) // default configuration
|
||||
)
|
||||
}
|
||||
|
||||
override predicate configurationSink(DataFlow::Node node, string flowstate) {
|
||||
// sink is the read of the qualifier of a call to `AbstractDOMParser.parse`.
|
||||
exists(Call call |
|
||||
call.getTarget().getClassAndName("parse") instanceof AbstractDomParserClass and
|
||||
call.getQualifier() = node.asConvertedExpr()
|
||||
) and
|
||||
flowstate instanceof XercesFlowState and
|
||||
not encodeXercesFlowState(flowstate, 1, 1) // safe configuration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DOMLSParser` class.
|
||||
*/
|
||||
class DomLSParserClass extends Class {
|
||||
DomLSParserClass() { this.hasName("DOMLSParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `createLSParser` function that returns a newly created `DOMLSParser`
|
||||
* object.
|
||||
*/
|
||||
class CreateLSParser extends Function {
|
||||
CreateLSParser() {
|
||||
this.hasName("createLSParser") and
|
||||
this.getUnspecifiedType().(PointerType).getBaseType() instanceof DomLSParserClass // returns a `DOMLSParser *`.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The createLSParser interface for the Xerces XML library.
|
||||
*/
|
||||
class CreateLSParserLibrary extends XmlLibrary {
|
||||
CreateLSParserLibrary() { this = "CreateLSParserLibrary" }
|
||||
|
||||
override predicate configurationSource(DataFlow::Node node, string flowstate) {
|
||||
// source is the result of a call to `createLSParser`.
|
||||
exists(Call call |
|
||||
call.getTarget() instanceof CreateLSParser and
|
||||
call = node.asExpr() and
|
||||
encodeXercesFlowState(flowstate, 0, 1) // default configuration
|
||||
)
|
||||
}
|
||||
|
||||
override predicate configurationSink(DataFlow::Node node, string flowstate) {
|
||||
// sink is the read of the qualifier of a call to `DOMLSParserClass.parse`.
|
||||
exists(Call call |
|
||||
call.getTarget().getClassAndName("parse") instanceof DomLSParserClass and
|
||||
call.getQualifier() = node.asConvertedExpr()
|
||||
) and
|
||||
flowstate instanceof XercesFlowState and
|
||||
not encodeXercesFlowState(flowstate, 1, 1) // safe configuration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `SAXParser` class.
|
||||
*/
|
||||
class SaxParserClass extends Class {
|
||||
SaxParserClass() { this.hasName("SAXParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `SAX2XMLReader` class.
|
||||
*/
|
||||
class Sax2XmlReader extends Class {
|
||||
Sax2XmlReader() { this.hasName("SAX2XMLReader") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The SAXParser interface for the Xerces XML library.
|
||||
*/
|
||||
class SaxParserLibrary extends XmlLibrary {
|
||||
SaxParserLibrary() { this = "SaxParserLibrary" }
|
||||
|
||||
override predicate configurationSource(DataFlow::Node node, string flowstate) {
|
||||
// source is the write on `this` of a call to the `SAXParser`
|
||||
// constructor.
|
||||
exists(CallInstruction call |
|
||||
call.getStaticCallTarget() = any(SaxParserClass c).getAConstructor() and
|
||||
node.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() =
|
||||
call.getThisArgument() and
|
||||
encodeXercesFlowState(flowstate, 0, 1) // default configuration
|
||||
)
|
||||
}
|
||||
|
||||
override predicate configurationSink(DataFlow::Node node, string flowstate) {
|
||||
// sink is the read of the qualifier of a call to `SAXParser.parse`.
|
||||
exists(Call call |
|
||||
call.getTarget().getClassAndName("parse") instanceof SaxParserClass and
|
||||
call.getQualifier() = node.asConvertedExpr()
|
||||
) and
|
||||
flowstate instanceof XercesFlowState and
|
||||
not encodeXercesFlowState(flowstate, 1, 1) // safe configuration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `createXMLReader` function that returns a newly created `SAX2XMLReader`
|
||||
* object.
|
||||
*/
|
||||
class CreateXmlReader extends Function {
|
||||
CreateXmlReader() {
|
||||
this.hasName("createXMLReader") and
|
||||
this.getUnspecifiedType().(PointerType).getBaseType() instanceof Sax2XmlReader // returns a `SAX2XMLReader *`.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The SAX2XMLReader interface for the Xerces XML library.
|
||||
*/
|
||||
class Sax2XmlReaderLibrary extends XmlLibrary {
|
||||
Sax2XmlReaderLibrary() { this = "Sax2XmlReaderLibrary" }
|
||||
|
||||
override predicate configurationSource(DataFlow::Node node, string flowstate) {
|
||||
// source is the result of a call to `createXMLReader`.
|
||||
exists(Call call |
|
||||
call.getTarget() instanceof CreateXmlReader and
|
||||
call = node.asExpr() and
|
||||
encodeXercesFlowState(flowstate, 0, 1) // default configuration
|
||||
)
|
||||
}
|
||||
|
||||
override predicate configurationSink(DataFlow::Node node, string flowstate) {
|
||||
// sink is the read of the qualifier of a call to `SAX2XMLReader.parse`.
|
||||
exists(Call call |
|
||||
call.getTarget().getClassAndName("parse") instanceof Sax2XmlReader and
|
||||
call.getQualifier() = node.asConvertedExpr()
|
||||
) and
|
||||
flowstate instanceof XercesFlowState and
|
||||
not encodeXercesFlowState(flowstate, 1, 1) // safe configuration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state transformer for a call to
|
||||
* `AbstractDOMParser.setDisableDefaultEntityResolution` or
|
||||
* `SAXParser.setDisableDefaultEntityResolution`. Transforms the flow
|
||||
* state through the qualifier according to the setting in the parameter.
|
||||
*/
|
||||
class DisableDefaultEntityResolutionTransformer extends XxeFlowStateTransformer {
|
||||
Expr newValue;
|
||||
|
||||
DisableDefaultEntityResolutionTransformer() {
|
||||
exists(Call call, Function f |
|
||||
call.getTarget() = f and
|
||||
(
|
||||
f.getDeclaringType() instanceof AbstractDomParserClass or
|
||||
f.getDeclaringType() instanceof SaxParserClass
|
||||
) and
|
||||
f.hasName("setDisableDefaultEntityResolution") and
|
||||
this = call.getQualifier() and
|
||||
newValue = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
final override XxeFlowState transform(XxeFlowState flowstate) {
|
||||
exists(int createEntityReferenceNodes |
|
||||
encodeXercesFlowState(flowstate, _, createEntityReferenceNodes) and
|
||||
(
|
||||
globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // true
|
||||
encodeXercesFlowState(result, 1, createEntityReferenceNodes)
|
||||
or
|
||||
not globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // false or unknown
|
||||
encodeXercesFlowState(result, 0, createEntityReferenceNodes)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state transformer for a call to
|
||||
* `AbstractDOMParser.setCreateEntityReferenceNodes`. Transforms the flow
|
||||
* state through the qualifier according to the setting in the parameter.
|
||||
*/
|
||||
class CreateEntityReferenceNodesTransformer extends XxeFlowStateTransformer {
|
||||
Expr newValue;
|
||||
|
||||
CreateEntityReferenceNodesTransformer() {
|
||||
exists(Call call, Function f |
|
||||
call.getTarget() = f and
|
||||
f.getClassAndName("setCreateEntityReferenceNodes") instanceof AbstractDomParserClass and
|
||||
this = call.getQualifier() and
|
||||
newValue = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
final override XxeFlowState transform(XxeFlowState flowstate) {
|
||||
exists(int disabledDefaultEntityResolution |
|
||||
encodeXercesFlowState(flowstate, disabledDefaultEntityResolution, _) and
|
||||
(
|
||||
globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // true
|
||||
encodeXercesFlowState(result, disabledDefaultEntityResolution, 1)
|
||||
or
|
||||
not globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // false or unknown
|
||||
encodeXercesFlowState(result, disabledDefaultEntityResolution, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `XMLUni.fgXercesDisableDefaultEntityResolution` constant.
|
||||
*/
|
||||
class FeatureDisableDefaultEntityResolution extends Variable {
|
||||
FeatureDisableDefaultEntityResolution() {
|
||||
this.getName() = "fgXercesDisableDefaultEntityResolution" and
|
||||
this.getDeclaringType().getName() = "XMLUni"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state transformer for a call to `SAX2XMLReader.setFeature`
|
||||
* specifying the feature `XMLUni::fgXercesDisableDefaultEntityResolution`.
|
||||
* Transforms the flow state through the qualifier according to this setting.
|
||||
*/
|
||||
class SetFeatureTransformer extends XxeFlowStateTransformer {
|
||||
Expr newValue;
|
||||
|
||||
SetFeatureTransformer() {
|
||||
exists(Call call, Function f |
|
||||
call.getTarget() = f and
|
||||
f.getClassAndName("setFeature") instanceof Sax2XmlReader and
|
||||
this = call.getQualifier() and
|
||||
globalValueNumber(call.getArgument(0)).getAnExpr().(VariableAccess).getTarget() instanceof
|
||||
FeatureDisableDefaultEntityResolution and
|
||||
newValue = call.getArgument(1)
|
||||
)
|
||||
}
|
||||
|
||||
final override XxeFlowState transform(XxeFlowState flowstate) {
|
||||
exists(int createEntityReferenceNodes |
|
||||
encodeXercesFlowState(flowstate, _, createEntityReferenceNodes) and
|
||||
(
|
||||
globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // true
|
||||
encodeXercesFlowState(result, 1, createEntityReferenceNodes)
|
||||
or
|
||||
not globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // false or unknown
|
||||
encodeXercesFlowState(result, 0, createEntityReferenceNodes)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DOMLSParser.getDomConfig` function.
|
||||
*/
|
||||
class GetDomConfig extends Function {
|
||||
GetDomConfig() { this.getClassAndName("getDomConfig") instanceof DomLSParserClass }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DOMConfiguration.setParameter` function.
|
||||
*/
|
||||
class DomConfigurationSetParameter extends Function {
|
||||
DomConfigurationSetParameter() {
|
||||
this.getClassAndName("setParameter").getName() = "DOMConfiguration"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state transformer for a call to `DOMConfiguration.setParameter`
|
||||
* specifying the feature `XMLUni::fgXercesDisableDefaultEntityResolution`.
|
||||
* This is a slightly more complex transformer because the qualifier is a
|
||||
* `DOMConfiguration` pointer returned by `DOMLSParser.getDomConfig` - and it
|
||||
* is *that* qualifier we want to transform the flow state of.
|
||||
*/
|
||||
class DomConfigurationSetParameterTransformer extends XxeFlowStateTransformer {
|
||||
Expr newValue;
|
||||
|
||||
DomConfigurationSetParameterTransformer() {
|
||||
exists(FunctionCall getDomConfigCall, FunctionCall setParameterCall |
|
||||
// this is the qualifier of a call to `DOMLSParser.getDomConfig`.
|
||||
getDomConfigCall.getTarget() instanceof GetDomConfig and
|
||||
this = getDomConfigCall.getQualifier() and
|
||||
// `setParameterCall` is a call to `setParameter` on the return value of
|
||||
// the same call to `DOMLSParser.getDomConfig`.
|
||||
setParameterCall.getTarget() instanceof DomConfigurationSetParameter and
|
||||
globalValueNumber(setParameterCall.getQualifier()).getAnExpr() = getDomConfigCall and
|
||||
// the parameter being set is
|
||||
// `XMLUni::fgXercesDisableDefaultEntityResolution`.
|
||||
globalValueNumber(setParameterCall.getArgument(0)).getAnExpr().(VariableAccess).getTarget()
|
||||
instanceof FeatureDisableDefaultEntityResolution and
|
||||
// the value being set is `newValue`.
|
||||
newValue = setParameterCall.getArgument(1)
|
||||
)
|
||||
}
|
||||
|
||||
final override XxeFlowState transform(XxeFlowState flowstate) {
|
||||
exists(int createEntityReferenceNodes |
|
||||
encodeXercesFlowState(flowstate, _, createEntityReferenceNodes) and
|
||||
(
|
||||
globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // true
|
||||
encodeXercesFlowState(result, 1, createEntityReferenceNodes)
|
||||
or
|
||||
not globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // false or unknown
|
||||
encodeXercesFlowState(result, 0, createEntityReferenceNodes)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user