Add libxml2 sinks

This commit is contained in:
Tony Torralba
2022-11-08 15:23:43 +01:00
parent b4661f4a59
commit 16a76853f4
4 changed files with 195 additions and 0 deletions

View 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"] }
}

View File

@@ -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
)
}

View File

@@ -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" }

View File

@@ -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))
}