mirror of
https://github.com/github/codeql.git
synced 2026-04-29 18:55:14 +02:00
Add libxml2 sinks
This commit is contained in:
54
swift/ql/lib/codeql/swift/frameworks/Libxml2.qll
Normal file
54
swift/ql/lib/codeql/swift/frameworks/Libxml2.qll
Normal file
@@ -0,0 +1,54 @@
|
||||
import swift
|
||||
|
||||
/**
|
||||
* A call to a `libxml2` function that parses XML.
|
||||
*/
|
||||
class Libxml2ParseCall extends ApplyExpr {
|
||||
int xmlArg;
|
||||
int optionsArg;
|
||||
|
||||
Libxml2ParseCall() {
|
||||
exists(string fname | this.getStaticTarget().getName() = fname |
|
||||
fname = "xmlCtxtUseOptions(_:_:)" and xmlArg = 0 and optionsArg = 1
|
||||
or
|
||||
fname = "xmlReadFile(_:_:_:)" and xmlArg = 0 and optionsArg = 2
|
||||
or
|
||||
fname = ["xmlReadDoc(_:_:_:_:)", "xmlReadFd(_:_:_:_:)"] and
|
||||
xmlArg = 0 and
|
||||
optionsArg = 3
|
||||
or
|
||||
fname = ["xmlCtxtReadFile(_:_:_:_:)", "xmlParseInNodeContext(_:_:_:_:_:)"] and
|
||||
xmlArg = 1 and
|
||||
optionsArg = 3
|
||||
or
|
||||
fname = ["xmlCtxtReadDoc(_:_:_:_:_:)", "xmlCtxtReadFd(_:_:_:_:_:)"] and
|
||||
xmlArg = 1 and
|
||||
optionsArg = 4
|
||||
or
|
||||
fname = "xmlReadMemory(_:_:_:_:_:)" and xmlArg = 0 and optionsArg = 4
|
||||
or
|
||||
fname = "xmlCtxtReadMemory(_:_:_:_:_:_:)" and xmlArg = 1 and optionsArg = 5
|
||||
or
|
||||
fname = "xmlReadIO(_:_:_:_:_:_:)" and xmlArg = 0 and optionsArg = 5
|
||||
or
|
||||
fname = "xmlCtxtReadIO(_:_:_:_:_:_:_:)" and xmlArg = 1 and optionsArg = 6
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the argument that receives the XML raw data.
|
||||
*/
|
||||
Expr getXml() { result = this.getArgument(xmlArg).getExpr() }
|
||||
|
||||
/**
|
||||
* Gets the argument that specifies `xmlParserOption`s.
|
||||
*/
|
||||
Expr getOptions() { result = this.getArgument(optionsArg).getExpr() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An `xmlParserOption` for `libxml2` that is considered unsafe.
|
||||
*/
|
||||
class Libxml2BadOption extends ConcreteVarDecl {
|
||||
Libxml2BadOption() { this.getName() = ["XML_PARSE_NOENT", "XML_PARSE_DTDLOAD"] }
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import swift
|
||||
private import codeql.swift.dataflow.DataFlow
|
||||
private import codeql.swift.frameworks.AEXML
|
||||
private import codeql.swift.frameworks.Libxml2
|
||||
|
||||
/** A data flow sink for XML external entities (XXE) vulnerabilities. */
|
||||
abstract class XxeSink extends DataFlow::Node { }
|
||||
@@ -163,3 +164,40 @@ private class AexmlOptions extends Expr {
|
||||
this.getType() = any(LValueType t | t.getObjectType() instanceof AexmlOptionsType)
|
||||
}
|
||||
}
|
||||
|
||||
/** The XML argument of a `libxml2` parsing call vulnerable to XXE. */
|
||||
private class Libxml2XxeSink extends XxeSink {
|
||||
Libxml2XxeSink() {
|
||||
exists(Libxml2ParseCall c, Libxml2BadOption opt |
|
||||
this.asExpr() = c.getXml() and
|
||||
lib2xmlOptionLocalTaintStep*(DataFlow::exprNode(opt.getAnAccess()),
|
||||
DataFlow::exprNode(c.getOptions()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint can flow from `source` to `sink` in one local step,
|
||||
* including bitwise operations, accesses to `.rawValue`, and casts to `Int32`.
|
||||
*/
|
||||
private predicate lib2xmlOptionLocalTaintStep(DataFlow::Node source, DataFlow::Node sink) {
|
||||
DataFlow::localFlowStep(source, sink)
|
||||
or
|
||||
source.asExpr() = sink.asExpr().(BitwiseOperation).getAnOperand()
|
||||
or
|
||||
exists(MemberRefExpr rawValue | rawValue.getMember().(VarDecl).getName() = "rawValue" |
|
||||
source.asExpr() = rawValue.getBase() and sink.asExpr() = rawValue
|
||||
)
|
||||
or
|
||||
exists(ApplyExpr int32Init |
|
||||
int32Init
|
||||
.getStaticTarget()
|
||||
.(ConstructorDecl)
|
||||
.getEnclosingDecl()
|
||||
.(ExtensionDecl)
|
||||
.getExtendedTypeDecl()
|
||||
.getName() = "SignedInteger"
|
||||
|
|
||||
source.asExpr() = int32Init.getAnArgument().getExpr() and sink.asExpr() = int32Init
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import swift
|
||||
import codeql.swift.dataflow.FlowSources
|
||||
import codeql.swift.security.XXEQuery
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
class TestRemoteSource extends RemoteFlowSource {
|
||||
TestRemoteSource() { this.asExpr().(ApplyExpr).getStaticTarget().getName().matches("source%") }
|
||||
|
||||
override string getSourceType() { result = "Test source" }
|
||||
}
|
||||
|
||||
class XxeTest extends InlineExpectationsTest {
|
||||
XxeTest() { this = "XxeTest" }
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// --- stubs ---
|
||||
|
||||
class Data {
|
||||
init<S>(_ elements: S) {}
|
||||
func copyBytes(to: UnsafeMutablePointer<UInt8>, count: Int) {}
|
||||
}
|
||||
|
||||
struct URL {
|
||||
init?(string: String) {}
|
||||
}
|
||||
|
||||
extension String {
|
||||
init(contentsOf: URL) {
|
||||
let data = ""
|
||||
self.init(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct xmlParserOption : Hashable {
|
||||
let rawValue: UInt32 = 0
|
||||
}
|
||||
|
||||
var XML_PARSE_NOENT: xmlParserOption { get { return xmlParserOption() } }
|
||||
var XML_PARSE_DTDLOAD: xmlParserOption { get { return xmlParserOption() } }
|
||||
|
||||
typealias xmlChar = UInt8
|
||||
typealias xmlDocPtr = UnsafeMutablePointer<xmlDoc>
|
||||
typealias xmlNodePtr = UnsafeMutablePointer<xmlNode>
|
||||
typealias xmlParserCtxtPtr = UnsafeMutablePointer<xmlParserCtxt>
|
||||
struct xmlDoc {}
|
||||
struct xmlNode {}
|
||||
struct xmlParserCtxt {}
|
||||
struct xmlParserErrors {}
|
||||
struct xmlInputReadCallback {}
|
||||
struct xmlInputCloseCallback {}
|
||||
|
||||
func xmlCtxtUseOptions(_ ctxt: xmlParserCtxtPtr!, _ options: Int32) -> Int32 { return 0 }
|
||||
func xmlReadDoc(_ cur: UnsafePointer<xmlChar>!, _ URL: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
func xmlReadFile(_ URL: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
func xmlReadMemory(_ buffer: UnsafePointer<CChar>!, _ size: Int32, _ URL: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
func xmlReadFd(_ fd: Int32, _ URL: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
func xmlReadIO(_ ioread: xmlInputReadCallback!, _ ioclose: xmlInputCloseCallback!, _ ioctx: UnsafeMutableRawPointer!, _ URL: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
func xmlCtxtReadDoc(_ ctxt: xmlParserCtxtPtr!, _ cur: UnsafePointer<xmlChar>!, _ URL: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
func xmlCtxtReadFile(_ ctxt: xmlParserCtxtPtr!, _ filename: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
func xmlParseInNodeContext(_ node: xmlNodePtr!, _ data: UnsafePointer<CChar>!, _ datalen: Int32, _ options: Int32, _ lst: UnsafeMutablePointer<xmlNodePtr?>!) -> xmlParserErrors { return xmlParserErrors() }
|
||||
func xmlCtxtReadMemory(_ ctxt: xmlParserCtxtPtr!, _ buffer: UnsafePointer<CChar>!, _ size: Int32, _ URL: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
func xmlCtxtReadFd(_ ctxt: xmlParserCtxtPtr!, _ fd: Int32, _ URL: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
func xmlCtxtReadIO(_ ctxt: xmlParserCtxtPtr!, _ ioread: xmlInputReadCallback!, _ ioclose: xmlInputCloseCallback!, _ ioctx: UnsafeMutableRawPointer!, _ URL: UnsafePointer<CChar>!, _ encoding: UnsafePointer<CChar>!, _ options: Int32) -> xmlDocPtr! { return xmlDocPtr.allocate(capacity: 0) }
|
||||
|
||||
// --- tests ---
|
||||
|
||||
func sourcePtr() -> UnsafeMutablePointer<UInt8> { return UnsafeMutablePointer<UInt8>.allocate(capacity: 0) }
|
||||
func sourceCharPtr() -> UnsafeMutablePointer<CChar> { return UnsafeMutablePointer<CChar>.allocate(capacity: 0) }
|
||||
|
||||
func test() {
|
||||
let remotePtr = sourcePtr()
|
||||
let remoteCharPtr = sourceCharPtr()
|
||||
let _ = xmlReadFile(remoteCharPtr, nil, 0) // NO XXE: external entities not enabled
|
||||
let _ = xmlReadFile(remoteCharPtr, nil, Int32(XML_PARSE_NOENT.rawValue)) // $ hasXXE=57
|
||||
let _ = xmlReadFile(remoteCharPtr, nil, Int32(XML_PARSE_DTDLOAD.rawValue)) // $ hasXXE=57
|
||||
let _ = xmlReadDoc(remotePtr, nil, nil, 0) // NO XXE: external entities not enabled
|
||||
let _ = xmlReadDoc(remotePtr, nil, nil, Int32(XML_PARSE_NOENT.rawValue)) // $ hasXXE=56
|
||||
let _ = xmlReadDoc(remotePtr, nil, nil, Int32(XML_PARSE_DTDLOAD.rawValue)) // $ hasXXE=56
|
||||
let _ = xmlCtxtReadFile(nil, remoteCharPtr, nil, 0) // NO XXE: external entities not enabled
|
||||
let _ = xmlCtxtReadFile(nil, remoteCharPtr, nil, Int32(XML_PARSE_NOENT.rawValue)) // $ hasXXE=57
|
||||
let _ = xmlCtxtReadFile(nil, remoteCharPtr, nil, Int32(XML_PARSE_DTDLOAD.rawValue)) // $ hasXXE=57
|
||||
let _ = xmlParseInNodeContext(nil, remoteCharPtr, -1, 0, nil) // NO XXE: external entities not enabled
|
||||
let _ = xmlParseInNodeContext(nil, remoteCharPtr, -1, Int32(XML_PARSE_DTDLOAD.rawValue), nil) // $ hasXXE=57
|
||||
let _ = xmlParseInNodeContext(nil, remoteCharPtr, -1, Int32(XML_PARSE_NOENT.rawValue), nil) // $ hasXXE=57
|
||||
let _ = xmlCtxtReadDoc(nil, remotePtr, nil, nil, 0) // NO XXE: external entities not enabled
|
||||
let _ = xmlCtxtReadDoc(nil, remotePtr, nil, nil, Int32(XML_PARSE_NOENT.rawValue)) // $ hasXXE=56
|
||||
let _ = xmlCtxtReadDoc(nil, remotePtr, nil, nil, Int32(XML_PARSE_DTDLOAD.rawValue)) // $ hasXXE=56
|
||||
let _ = xmlReadMemory(remoteCharPtr, -1, nil, nil, 0) // NO XXE: external entities not enabled
|
||||
let _ = xmlReadMemory(remoteCharPtr, -1, nil, nil, Int32(XML_PARSE_NOENT.rawValue)) // $ hasXXE=57
|
||||
let _ = xmlReadMemory(remoteCharPtr, -1, nil, nil, Int32(XML_PARSE_DTDLOAD.rawValue)) // $ hasXXE=57
|
||||
let _ = xmlCtxtReadMemory(nil, remoteCharPtr, -1, nil, nil, 0) // NO XXE: external entities not enabled
|
||||
let _ = xmlCtxtReadMemory(nil, remoteCharPtr, -1, nil, nil, Int32(XML_PARSE_NOENT.rawValue)) // $ hasXXE=57
|
||||
let _ = xmlCtxtReadMemory(nil, remoteCharPtr, -1, nil, nil, Int32(XML_PARSE_DTDLOAD.rawValue)) // $ hasXXE=57
|
||||
// TODO: We would need to model taint around `xmlParserCtxtPtr`, file descriptors, and `xmlInputReadCallback`
|
||||
// to be able to alert on these methods. Not doing it for now because of the effort required vs the expected gain.
|
||||
let _ = xmlCtxtUseOptions(nil, 0)
|
||||
let _ = xmlCtxtUseOptions(nil, Int32(XML_PARSE_NOENT.rawValue))
|
||||
let _ = xmlCtxtUseOptions(nil, Int32(XML_PARSE_DTDLOAD.rawValue))
|
||||
let _ = xmlReadFd(0, nil, nil, 0)
|
||||
let _ = xmlReadFd(0, nil, nil, Int32(XML_PARSE_NOENT.rawValue))
|
||||
let _ = xmlReadFd(0, nil, nil, Int32(XML_PARSE_DTDLOAD.rawValue))
|
||||
let _ = xmlCtxtReadFd(nil, 0, nil, nil, 0)
|
||||
let _ = xmlCtxtReadFd(nil, 0, nil, nil, Int32(XML_PARSE_NOENT.rawValue))
|
||||
let _ = xmlCtxtReadFd(nil, 0, nil, nil, Int32(XML_PARSE_DTDLOAD.rawValue))
|
||||
let _ = xmlReadIO(nil, nil, nil, nil, nil, 0)
|
||||
let _ = xmlReadIO(nil, nil, nil, nil, nil, Int32(XML_PARSE_NOENT.rawValue))
|
||||
let _ = xmlReadIO(nil, nil, nil, nil, nil, Int32(XML_PARSE_DTDLOAD.rawValue))
|
||||
let _ = xmlCtxtReadIO(nil, nil, nil, nil, nil, nil, 0)
|
||||
let _ = xmlCtxtReadIO(nil, nil, nil, nil, nil, nil, Int32(XML_PARSE_NOENT.rawValue))
|
||||
let _ = xmlCtxtReadIO(nil, nil, nil, nil, nil, nil, Int32(XML_PARSE_DTDLOAD.rawValue))
|
||||
}
|
||||
Reference in New Issue
Block a user