// --- stubs --- typealias unichar = UInt16 struct Locale { } enum CInterop { typealias Char = CChar typealias PlatformChar = CInterop.Char } struct CharacterSet { static var whitespaces: CharacterSet { get { return CharacterSet() } } } class NSObject { } class NSString : NSObject { struct CompareOptions : OptionSet { init(rawValue: UInt) { self.rawValue = rawValue } var rawValue: UInt } } extension String : CVarArg { typealias CompareOptions = NSString.CompareOptions public var _cVarArgEncoding: [Int] { get { return [] } } static var availableStringEncodings: [String.Encoding] { get { [] } } static var defaultCStringEncoding: String.Encoding { get { String.Encoding.utf8 } } struct Encoding { static let utf8 = Encoding() } init?(data: Data, encoding: Encoding) { self.init() } init(format: String, _ arguments: CVarArg...) { self.init() } init(format: String, arguments: [CVarArg]) { self.init() } init(format: String, locale: Locale?, _ args: CVarArg...) { self.init() } init(format: String, locale: Locale?, arguments: [CVarArg]) { self.init() } static func localizedStringWithFormat(_ format: String, _ arguments: CVarArg...) -> String { return "" } init?(bytes: S, encoding: String.Encoding) where S : Sequence, S.Element == UInt8 { self.init() } init(cString nullTerminatedUTF8: UnsafePointer) { self.init() } init(cString nullTerminatedUTF8: UnsafePointer) { self.init() } init?(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int, encoding: String.Encoding, freeWhenDone flag: Bool) { self.init() } init?(utf8String bytes: UnsafePointer) { self.init() } init(utf16CodeUnits: UnsafePointer, count: Int) { self.init() } init(utf16CodeUnitsNoCopy: UnsafePointer, count: Int, freeWhenDone flag: Bool) { self.init() } init(platformString: UnsafePointer) { self.init() } func withPlatformString(_ body: (UnsafePointer) throws -> Result) rethrows -> Result { return 0 as! Result } mutating func withMutableCharacters(_ body: (inout String) -> R) -> R { return 0 as! R } mutating func replaceSubrange(_ subrange: Range, with newElements: C) where C : Collection, C.Element == Character {} } extension StringProtocol { var capitalized: String { get { "" } } var localizedCapitalized: String { get { "" } } var localizedLowercase: String { get { "" } } var localizedUppercase: String { get { "" } } var removingPercentEncoding: String? { get { "" } } var decomposedStringWithCanonicalMapping: String { get { "" } } var decomposedStringWithCompatibilityMapping: String { get { "" } } var precomposedStringWithCanonicalMapping: String { get { "" } } var precomposedStringWithCompatibilityMapping: String { get { "" } } func lowercased(with locale: Locale?) -> String { return "" } func uppercased(with locale: Locale?) -> String { return "" } func capitalized(with locale: Locale?) -> String { return "" } func substring(from index: Self.Index) -> String { return "" } func trimmingCharacters(in set: CharacterSet) -> String { return "" } func appending(_ aString: T) -> String where T : StringProtocol { return "" } func appendingFormat(_ format: T, _ arguments: CVarArg...) -> String where T : StringProtocol { return "" } func padding(toLength newLength: Int, withPad padString: T, startingAt padIndex: Int) -> String where T: StringProtocol { return "" } func components(separatedBy separator: CharacterSet) -> [String] { return [] } func folding(options: String.CompareOptions = [], locale: Locale?) -> String { return "" } func propertyListFromStringsFileFormat() -> [String : String] { return [:] } func cString(using encoding: String.Encoding) -> [CChar]? { return nil } func enumerateLines(invoking body: @escaping (String, inout Bool) -> Void) {} func replacingOccurrences(of target: Target, with replacement: Replacement, options: String.CompareOptions = [], range searchRange: Range? = nil) -> String where Target : StringProtocol, Replacement : StringProtocol { return "" } } class Data { init(_ elements: S) {} } extension Data : Collection { typealias Index = Int var startIndex: Data.Index { get { return 0 } } var endIndex: Data.Index { get { return 0 } } subscript(index: Data.Index) -> UInt8 { get { return 0 } } func index(after i: Data.Index) -> Data.Index { return 0 } } // --- tests --- func source() -> Int { return 0; } func sink(arg: Any) {} func taintThroughInterpolatedStrings() { var x = source() sink(arg: "\(x)") // $ tainted=137 sink(arg: "\(x) \(x)") // $ tainted=137 sink(arg: "\(x) \(0) \(x)") // $ tainted=137 let y = 42 sink(arg: "\(y)") // clean sink(arg: "\(x) hello \(y)") // $ tainted=137 sink(arg: "\(y) world \(x)") // $ tainted=137 x = 0 sink(arg: "\(x)") // clean } func source2() -> String { return ""; } func taintThroughStringConcatenation() { let clean = "abcdef" let tainted = source2() sink(arg: clean) sink(arg: tainted) // $ tainted=161 sink(arg: clean + clean) sink(arg: clean + tainted) // $ tainted=161 sink(arg: tainted + clean) // $ tainted=161 sink(arg: tainted + tainted) // $ tainted=161 sink(arg: ">" + clean + "<") sink(arg: ">" + tainted + "<") // $ tainted=161 sink(arg: clean.appending(clean)) sink(arg: clean.appending(tainted)) // $ tainted=161 sink(arg: tainted.appending(clean)) // $ tainted=161 sink(arg: tainted.appending(tainted)) // $ tainted=161 var str = "abc" sink(arg: str) str += "def" sink(arg: str) str += source2() sink(arg: str) // $ tainted=183 var str2 = "abc" sink(arg: str2) str2.append("def") sink(arg: str2) str2.append(source2()) sink(arg: str2) // $ tainted=190 var str3 = "abc" sink(arg: str3) str3.append(contentsOf: "def") sink(arg: str3) str3.append(contentsOf: source2()) sink(arg: str3) // $ tainted=197 var str4 = "abc" sink(arg: str4) str4.write("def") sink(arg: str4) str4.write(source2()) sink(arg: str4) // $ tainted=204 var str5 = "abc" sink(arg: str5) str5.insert(contentsOf: "abc", at: str5.startIndex) sink(arg: str5) str5.insert(contentsOf: source2(), at: str5.startIndex) sink(arg: str5) // $ tainted=211 } func taintThroughSimpleStringOperations() { let clean = "" let tainted = source2() let taintedInt = source() sink(arg: String(clean)) sink(arg: String(tainted)) // $ tainted=217 sink(arg: String(taintedInt)) // $ tainted=218 sink(arg: String(format: tainted, 1, 2, 3)) // $ tainted=217 sink(arg: String(format: tainted, arguments: [])) // $ tainted=217 sink(arg: String(format: tainted, locale: nil, 1, 2, 3)) // $ tainted=217 sink(arg: String(format: tainted, locale: nil, arguments: [])) // $ tainted=217 sink(arg: String.localizedStringWithFormat(tainted, 1, 2, 3)) // $ tainted=217 sink(arg: String.localizedStringWithFormat("%i %s %i", 1, tainted, 3)) // $ tainted=217 sink(arg: String(format: "%s", tainted)) // $ tainted=217 sink(arg: String(format: "%i %i %i", 1, 2, taintedInt)) // $ tainted=218 sink(arg: String(repeating: clean, count: 2)) sink(arg: String(repeating: tainted, count: 2)) // $ tainted=217 sink(arg: tainted.dropFirst(10)) // $ tainted=217 sink(arg: tainted.dropLast(10)) // $ tainted=217 sink(arg: tainted.substring(from: tainted.startIndex)) // $ tainted=217 sink(arg: tainted.lowercased()) // $ tainted=217 sink(arg: tainted.uppercased()) // $ tainted=217 sink(arg: tainted.lowercased(with: nil)) // $ tainted=217 sink(arg: tainted.uppercased(with: nil)) // $ tainted=217 sink(arg: tainted.capitalized(with: nil)) // $ tainted=217 sink(arg: tainted.reversed()) // $ tainted=217 sink(arg: tainted.split(separator: ",")) // $ tainted=217 sink(arg: tainted.split(whereSeparator: { c in return (c == ",") })) // $ tainted=217 sink(arg: tainted.trimmingCharacters(in: CharacterSet.whitespaces)) // $ tainted=217 sink(arg: tainted.padding(toLength: 20, withPad: " ", startingAt: 0)) // $ tainted=217 sink(arg: tainted.components(separatedBy: CharacterSet.whitespaces)) // $ tainted=217 sink(arg: tainted.components(separatedBy: CharacterSet.whitespaces)[0]) // $ tainted=217 sink(arg: tainted.folding(locale: nil)) // $ tainted=217 sink(arg: tainted.propertyListFromStringsFileFormat()) // $ tainted=217 sink(arg: tainted.propertyListFromStringsFileFormat()["key"]!) // $ tainted=217 sink(arg: clean.enumerateLines(invoking: { line, stop in sink(arg: line) sink(arg: stop) })) sink(arg: tainted.enumerateLines(invoking: { line, stop in sink(arg: line) // $ MISSING: tainted=217 sink(arg: stop) })) sink(arg: [clean, clean].joined()) sink(arg: [tainted, clean].joined()) // $ tainted=217 sink(arg: [clean, tainted].joined()) // $ tainted=217 sink(arg: [tainted, tainted].joined()) // $ tainted=217 sink(arg: clean.description) sink(arg: tainted.description) // $ tainted=217 sink(arg: clean.debugDescription) sink(arg: tainted.debugDescription) // $ tainted=217 sink(arg: clean.utf8) sink(arg: tainted.utf8) // $ tainted=217 sink(arg: clean.utf16) sink(arg: tainted.utf16) // $ tainted=217 sink(arg: clean.unicodeScalars) sink(arg: tainted.unicodeScalars) // $ tainted=217 sink(arg: clean.utf8CString) sink(arg: tainted.utf8CString) // $ tainted=217 sink(arg: clean.lazy) sink(arg: tainted.lazy) // $ tainted=217 sink(arg: clean.capitalized) sink(arg: tainted.capitalized) // $ tainted=217 sink(arg: clean.localizedCapitalized) sink(arg: tainted.localizedCapitalized) // $ tainted=217 sink(arg: clean.localizedLowercase) sink(arg: tainted.localizedLowercase) // $ tainted=217 sink(arg: clean.localizedUppercase) sink(arg: tainted.localizedUppercase) // $ tainted=217 sink(arg: clean.decomposedStringWithCanonicalMapping) sink(arg: tainted.decomposedStringWithCanonicalMapping) // $ tainted=217 sink(arg: clean.precomposedStringWithCompatibilityMapping) sink(arg: tainted.precomposedStringWithCompatibilityMapping) // $ tainted=217 sink(arg: clean.removingPercentEncoding!) sink(arg: tainted.removingPercentEncoding!) // $ tainted=217 sink(arg: clean.replacingOccurrences(of: "a", with: "b")) sink(arg: tainted.replacingOccurrences(of: "a", with: "b")) // $ tainted=217 sink(arg: clean.replacingOccurrences(of: "a", with: source2())) // $ tainted=305 } func taintThroughMutatingStringOperations() { var str1 = source2() sink(arg: str1) // $ tainted=309 sink(arg: str1.remove(at: str1.startIndex)) // $ tainted=309 sink(arg: str1) // $ tainted=309 var str2 = source2() sink(arg: str2) // $ tainted=314 str2.removeAll() sink(arg: str2) // $ SPURIOUS: tainted=314 var str3 = source2() sink(arg: str3) // $ tainted=319 str3.removeAll(where: { _ in true } ) sink(arg: str3) // $ SPURIOUS: tainted=319 var str4 = source2() sink(arg: str4) // $ tainted=324 sink(arg: str4.removeFirst()) // $ tainted=324 sink(arg: str4) // $ tainted=324 str4.removeFirst(5) sink(arg: str4) // $ tainted=324 sink(arg: str4.removeLast()) // $ tainted=324 sink(arg: str4) // $ tainted=324 str4.removeLast(5) sink(arg: str4) // $ tainted=324 var str5 = source2() sink(arg: str5) // $ tainted=335 str5.removeSubrange(str5.startIndex ... str5.index(str5.startIndex, offsetBy: 5)) sink(arg: str5) // $ tainted=335 var str6 = source2() sink(arg: str6) // $ tainted=340 str6.makeContiguousUTF8() sink(arg: str6) // $ tainted=340 var str7 = "" sink(arg: str7) str7.replaceSubrange((nil as Range?)!, with: source2()) sink(arg: str7) // $ tainted=347 } func source3() -> Data { return Data("") } func taintThroughData() { let stringClean = String(data: Data(""), encoding: String.Encoding.utf8) let stringTainted = String(data: source3(), encoding: String.Encoding.utf8) sink(arg: stringClean!) sink(arg: stringTainted!) // $ tainted=355 sink(arg: String(decoding: Data(""), as: UTF8.self)) sink(arg: String(decoding: source3(), as: UTF8.self)) // $ tainted=361 } func taintThroughEncodings() { var clean = "" var tainted = source2() clean.withUTF8({ buffer in sink(arg: buffer[0]) sink(arg: buffer.baseAddress!) }) tainted.withUTF8({ buffer in sink(arg: buffer[0]) // $ tainted=366 sink(arg: buffer.baseAddress!) // $ MISSING: tainted=366 }) clean.withCString({ ptr in sink(arg: ptr[0]) }) tainted.withCString({ ptr in sink(arg: ptr[0]) // $ tainted=366 }) clean.withCString(encodedAs: UTF8.self, { ptr in sink(arg: ptr[0]) }) tainted.withCString(encodedAs: UTF8.self, { ptr in sink(arg: ptr[0]) // $ tainted=366 }) let arrayString1 = clean.cString(using: String.Encoding.utf8)! sink(arg: arrayString1) arrayString1.withUnsafeBufferPointer({ buffer in sink(arg: buffer[0]) sink(arg: String(cString: buffer.baseAddress!)) }) let arrayString2 = tainted.cString(using: String.Encoding.utf8)! sink(arg: arrayString2) // $ tainted=366 arrayString2.withUnsafeBufferPointer({ buffer in sink(arg: buffer[0]) // $ tainted=366 sink(arg: String(cString: buffer.baseAddress!)) // $ MISSING: tainted=366 }) clean.withPlatformString({ ptr in sink(arg: ptr[0]) sink(arg: String(platformString: ptr)) let buffer = UnsafeBufferPointer(start: ptr, count: 10) let arrayString = Array(buffer) sink(arg: buffer[0]) sink(arg: arrayString[0]) sink(arg: String(platformString: arrayString)) }) tainted.withPlatformString({ ptr in sink(arg: ptr[0]) // $ tainted=366 sink(arg: String(platformString: ptr)) // $ tainted=366 let buffer = UnsafeBufferPointer(start: ptr, count: 10) let arrayString = Array(buffer) sink(arg: buffer[0]) // $ MISSING: tainted=366 sink(arg: arrayString[0]) // $ MISSING: tainted=366 sink(arg: String(platformString: arrayString)) // $ MISSING: tainted=366 }) clean.withContiguousStorageIfAvailable({ ptr in sink(arg: ptr[0]) sink(arg: ptr.baseAddress!) }) tainted.withContiguousStorageIfAvailable({ ptr in sink(arg: ptr[0]) // $ tainted=366 sink(arg: ptr.baseAddress!) // $ MISSING: tainted=366 }) } func source4() -> UInt8 { return 0 } func taintFromUInt8Array() { var cleanUInt8Values: [UInt8] = [0x41, 0x42, 0x43, 0] // "ABC" var taintedUInt8Values: [UInt8] = [source4()] sink(arg: taintedUInt8Values[0]) // $ tainted=450 let r1 = String(unsafeUninitializedCapacity: 256, initializingUTF8With: { (buffer: UnsafeMutableBufferPointer) -> Int in sink(arg: buffer[0]) let _ = buffer.initialize(from: cleanUInt8Values) sink(arg: buffer[0]) return 3 } ) sink(arg: r1) let r2 = String(unsafeUninitializedCapacity: 256, initializingUTF8With: { (buffer: UnsafeMutableBufferPointer) -> Int in sink(arg: buffer[0]) sink(arg: taintedUInt8Values[0]) // $ tainted=450 let _ = buffer.initialize(from: taintedUInt8Values) sink(arg: buffer[0]) // $ tainted=450 return 256 } ) sink(arg: r2) // $ tainted=450 let r3 = String(unsafeUninitializedCapacity: 256, initializingUTF8With: { (buffer: UnsafeMutableBufferPointer) -> Int in sink(arg: buffer[0]) buffer.update(repeating: source4()) sink(arg: buffer[0]) // $ tainted=475 return 256 } ) sink(arg: r3) // $ tainted=475 sink(arg: String(bytes: cleanUInt8Values, encoding: String.Encoding.utf8)!) sink(arg: String(bytes: taintedUInt8Values, encoding: String.Encoding.utf8)!) // $ tainted=450 sink(arg: String(cString: cleanUInt8Values)) sink(arg: String(cString: taintedUInt8Values)) // $ tainted=450 try! cleanUInt8Values.withUnsafeBufferPointer({ (buffer: UnsafeBufferPointer) throws in sink(arg: buffer[0]) sink(arg: buffer.baseAddress!) sink(arg: String(cString: buffer.baseAddress!)) }) try! taintedUInt8Values.withUnsafeBufferPointer({ (buffer: UnsafeBufferPointer) throws in sink(arg: buffer[0]) // $ tainted=450 sink(arg: buffer.baseAddress!) // $ MISSING: tainted=450 sink(arg: String(cString: buffer.baseAddress!)) // $ MISSING: tainted=450 }) try! cleanUInt8Values.withUnsafeMutableBytes({ (buffer: UnsafeMutableRawBufferPointer) throws in sink(arg: buffer[0]) sink(arg: buffer.baseAddress!) sink(arg: String(bytesNoCopy: buffer.baseAddress!, length: buffer.count, encoding: String.Encoding.utf8, freeWhenDone: false)!) }) try! taintedUInt8Values.withUnsafeMutableBytes({ (buffer: UnsafeMutableRawBufferPointer) throws in sink(arg: buffer[0]) // $ tainted=450 sink(arg: buffer.baseAddress!) // $ MISSING: tainted=450 sink(arg: String(bytesNoCopy: buffer.baseAddress!, length: buffer.count, encoding: String.Encoding.utf8, freeWhenDone: false)!) // $ MISSING: tainted=450 }) } func source5() -> CChar { return 0 } func taintThroughCCharArray() { let cleanCCharValues: [CChar] = [0x41, 0x42, 0x43, 0] let taintedCCharValues: [CChar] = [source5()] cleanCCharValues.withUnsafeBufferPointer({ ptr in sink(arg: ptr[0]) sink(arg: ptr.baseAddress!) sink(arg: String(utf8String: ptr.baseAddress!)!) sink(arg: String(validatingUTF8: ptr.baseAddress!)!) sink(arg: String(cString: ptr.baseAddress!)) }) taintedCCharValues.withUnsafeBufferPointer({ ptr in sink(arg: ptr[0]) // $ tainted=517 sink(arg: ptr.baseAddress!) // $ MISSING: tainted=517 sink(arg: String(utf8String: ptr.baseAddress!)!) // $ MISSING: tainted=517 sink(arg: String(validatingUTF8: ptr.baseAddress!)!) // $ MISSING: tainted=517 sink(arg: String(cString: ptr.baseAddress!)) // $ MISSING: tainted=517 }) sink(arg: String(cString: cleanCCharValues)) sink(arg: String(cString: taintedCCharValues)) // $ tainted=517 } func source6() -> unichar { return 0 } func taintThroughUnicharArray() { let cleanUnicharValues: [unichar] = [0x41, 0x42, 0x43, 0] let taintedUnicharValues: [unichar] = [source6()] cleanUnicharValues.withUnsafeBufferPointer({ ptr in sink(arg: ptr[0]) sink(arg: ptr.baseAddress!) sink(arg: String(utf16CodeUnits: ptr.baseAddress!, count: ptr.count)) sink(arg: String(utf16CodeUnitsNoCopy: ptr.baseAddress!, count: ptr.count, freeWhenDone: false)) }) taintedUnicharValues.withUnsafeBufferPointer({ ptr in sink(arg: ptr[0]) // $ tainted=544 sink(arg: ptr.baseAddress!) // $ MISSING: tainted=544 sink(arg: String(utf16CodeUnits: ptr.baseAddress!, count: ptr.count)) // $ MISSING: tainted=5344 sink(arg: String(utf16CodeUnitsNoCopy: ptr.baseAddress!, count: ptr.count, freeWhenDone: false)) // $ MISSING: tainted=544 }) } func source7() -> Substring { return Substring() } func taintThroughSubstring() { let tainted = source2() sink(arg: source7()) // $ tainted=567 let sub1 = tainted[tainted.startIndex ..< tainted.endIndex] sink(arg: sub1) // $ tainted=565 sink(arg: String(sub1)) // $ tainted=565 let sub2 = tainted.prefix(10) sink(arg: sub2) // $ tainted=565 sink(arg: String(sub2)) // $ tainted=565 let sub3 = tainted.prefix(through: tainted.endIndex) sink(arg: sub3) // $ tainted=565 sink(arg: String(sub3)) // $ tainted=565 let sub4 = tainted.prefix(upTo: tainted.endIndex) sink(arg: sub4) // $ tainted=565 sink(arg: String(sub4)) // $ tainted=565 let sub5 = tainted.suffix(10) sink(arg: sub5) // $ tainted=565 sink(arg: String(sub5)) // $ tainted=565 let sub6 = tainted.suffix(from: tainted.startIndex) sink(arg: sub6) // $ tainted=565 sink(arg: String(sub6)) // $ tainted=565 } func taintedThroughConversion() { sink(arg: String(0)) sink(arg: String(source())) // $ tainted=596 sink(arg: Int(0).description) sink(arg: source().description) // $ tainted=599 sink(arg: String(describing: 0)) sink(arg: String(describing: source())) // $ tainted=602 sink(arg: Int("123")!) sink(arg: Int(source2())!) // $ tainted=605 } func untaintedFields() { let tainted = source2() sink(arg: String.availableStringEncodings) sink(arg: String.defaultCStringEncoding) sink(arg: tainted.isContiguousUTF8) } func callbackWithCleanPointer(ptr: UnsafeBufferPointer) throws -> Int { sink(arg: ptr[0]) return 0 } func callbackWithTaintedPointer(ptr: UnsafeBufferPointer) throws -> Int { sink(arg: ptr[0]) // $ tainted=630 return source() } func furtherTaintThroughCallbacks() { let clean = "" let tainted = source2() // return values from the closure (1) let result1 = clean.withContiguousStorageIfAvailable({ ptr in return 0 }) sink(arg: result1!) let result2 = clean.withContiguousStorageIfAvailable({ ptr in return source() }) sink(arg: result2!) // $ tainted=640 // return values from the closure (2) if let result3 = clean.withContiguousStorageIfAvailable({ ptr in return 0 }) { sink(arg: result3) } if let result4 = clean.withContiguousStorageIfAvailable({ ptr in return source() }) { sink(arg: result4) // $ tainted=653 } // using a non-closure function let result5 = try? clean.withContiguousStorageIfAvailable(callbackWithCleanPointer) sink(arg: result5!) let result6 = try? tainted.withContiguousStorageIfAvailable(callbackWithTaintedPointer) sink(arg: result6!) // $ tainted=625 } func testAppendingFormat() { var s1 = source2() sink(arg: s1.appendingFormat("%s %i", "", 0)) // $ tainted=666 var s2 = "" sink(arg: s2.appendingFormat(source2(), "", 0)) // $ tainted=670 var s3 = "" sink(arg: s3.appendingFormat("%s %i", source2(), 0)) // $ tainted=673 var s4 = "" sink(arg: s4.appendingFormat("%s %i", "", source())) // $ tainted=676 } func sourceUInt8() -> UInt8 { return 0 } func testDecodeCString() { var input : [UInt8] = [1, 2, 3, sourceUInt8()] let (str1, repaired1) = String.decodeCString(input, as: UTF8.self)! sink(arg: str1) // $ tainted=682 sink(arg: repaired1) input.withUnsafeBufferPointer({ ptr in let (str2, repaired2) = String.decodeCString(ptr.baseAddress, as: UTF8.self)! sink(arg: str2) // $ MISSING: tainted=682 sink(arg: repaired2) }) let (str3, repaired3) = String.decodeCString(source2(), as: UTF8.self)! sink(arg: str3) // $ tainted=695 sink(arg: repaired3) let (str4, repaired4) = String.decodeCString(&input, as: UTF8.self)! sink(arg: str4) // $ tainted=682 sink(arg: repaired4) } func testSubstringMembers() { let clean = "" let tainted = source2() let sub1 = tainted[..