diff --git a/swift/ql/lib/change-notes/2023-10-13-rawrepresentable.md b/swift/ql/lib/change-notes/2023-10-13-rawrepresentable.md new file mode 100644 index 00000000000..114afd58ab8 --- /dev/null +++ b/swift/ql/lib/change-notes/2023-10-13-rawrepresentable.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- + +* Added taint flow models for `RawRepresentable`. diff --git a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/RawRepresentable.qll b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/RawRepresentable.qll new file mode 100644 index 00000000000..8d56ffb4dfd --- /dev/null +++ b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/RawRepresentable.qll @@ -0,0 +1,28 @@ +/** + * Provides models the `RawRepresentable` Swift class. + */ + +import swift +private import codeql.swift.dataflow.DataFlow +private import codeql.swift.dataflow.ExternalFlow +private import codeql.swift.dataflow.FlowSteps + +/** + * A model for `RawRepresentable` class members that permit taint flow. + */ +private class RawRepresentableSummaries extends SummaryModelCsv { + override predicate row(string row) { + row = ";RawRepresentable;true;init(rawValue:);;;Argument[0];ReturnValue;taint" + } +} + +/** + * A content implying that, if a `RawRepresentable` is tainted, then the + * `rawValue` field is tainted as well. This model has been extended to assume + * that any object's `rawValue` field also inherits taint. + */ +private class RawRepresentableFieldsInheritTaint extends TaintInheritingContent, + DataFlow::Content::FieldContent +{ + RawRepresentableFieldsInheritTaint() { this.getField().getName() = "rawValue" } +} diff --git a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/StandardLibrary.qll b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/StandardLibrary.qll index 65c1679eebb..7fe479162b5 100644 --- a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/StandardLibrary.qll +++ b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/StandardLibrary.qll @@ -17,6 +17,7 @@ private import NsObject private import NsString private import NsUrl private import Numeric +private import RawRepresentable private import PointerTypes private import Sequence private import Set diff --git a/swift/ql/lib/codeql/swift/security/XXEExtensions.qll b/swift/ql/lib/codeql/swift/security/XXEExtensions.qll index a1f7780a3b1..b9c4dea1a87 100644 --- a/swift/ql/lib/codeql/swift/security/XXEExtensions.qll +++ b/swift/ql/lib/codeql/swift/security/XXEExtensions.qll @@ -172,31 +172,12 @@ private class Libxml2XxeSink extends XxeSink { Libxml2XxeSink() { exists(Libxml2ParseCall c, Libxml2BadOption opt | this.asExpr() = c.getXml() and - lib2xmlOptionLocalTaintStep*(DataFlow::exprNode(opt.getAnAccess()), + TaintTracking::localTaintStep*(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) { - TaintTracking::localTaintStep(source, sink) - or - exists(MemberRefExpr rawValue | rawValue.getMember().(VarDecl).getName() = "rawValue" | - source.asExpr() = rawValue.getBase() and sink.asExpr() = rawValue - ) - or - exists(ApplyExpr int32Init | - int32Init.getStaticTarget().(Initializer).getEnclosingDecl().asNominalTypeDecl().getName() = - "SignedInteger" - | - source.asExpr() = int32Init.getAnArgument().getExpr() and sink.asExpr() = int32Init - ) -} - /** * A sink defined in a CSV model. */ diff --git a/swift/ql/test/library-tests/dataflow/taint/libraries/optionset.swift b/swift/ql/test/library-tests/dataflow/taint/libraries/optionset.swift new file mode 100644 index 00000000000..e4f704d8ef5 --- /dev/null +++ b/swift/ql/test/library-tests/dataflow/taint/libraries/optionset.swift @@ -0,0 +1,66 @@ + +// --- stubs --- + +// --- tests --- + +func sourceInt() -> Int { return 0 } +func sourceUInt() -> UInt { return 0 } +func sink(arg: Any) {} + +// --- + +enum MyRawRepresentable : RawRepresentable { + case valueOne + case valueTwo + + init?(rawValue: Int) { + switch rawValue { + case 1: self = .valueOne + case 2: self = .valueTwo + default: return nil + } + } + + var rawValue: Int { + switch self { + case .valueOne: return 1 + case .valueTwo: return 2 + } + } +} + +func testRawRepresentable() { + let rr1 = MyRawRepresentable.valueOne + let rr2 = MyRawRepresentable(rawValue: 1)! + let rr3 = MyRawRepresentable(rawValue: sourceInt())! + + sink(arg: rr1) + sink(arg: rr2) + sink(arg: rr3) // $ tainted=35 + + sink(arg: rr1.rawValue) + sink(arg: rr2.rawValue) + sink(arg: rr3.rawValue) // $ tainted=35 +} + +// --- + +struct MyOptionSet : OptionSet { + let rawValue: UInt + + static let red = MyOptionSet(rawValue: 1 << 0) + static let green = MyOptionSet(rawValue: 1 << 1) + static let blue = MyOptionSet(rawValue: 1 << 2) +} + +func testOptionSet() { + sink(arg: MyOptionSet.red) + sink(arg: MyOptionSet([.red, .green])) + sink(arg: MyOptionSet(rawValue: 0)) + sink(arg: MyOptionSet(rawValue: sourceUInt())) // $ tainted=60 + + sink(arg: MyOptionSet.red.rawValue) + sink(arg: MyOptionSet([.red, .green]).rawValue) + sink(arg: MyOptionSet(rawValue: 0).rawValue) + sink(arg: MyOptionSet(rawValue: sourceUInt()).rawValue) // $ tainted=65 +} diff --git a/swift/ql/test/query-tests/Security/CWE-611/XXETest.expected b/swift/ql/test/query-tests/Security/CWE-611/XXETest.expected index 48de9172b36..8ec8033d086 100644 --- a/swift/ql/test/query-tests/Security/CWE-611/XXETest.expected +++ b/swift/ql/test/query-tests/Security/CWE-611/XXETest.expected @@ -1,2 +1,2 @@ -failures testFailures +failures