Skip to content

Commit 997fe01

Browse files
committed
[stdlib] Prevent type coercion from Bool to numerical types when decoding JSON and plist
JSONEncoder and PropertyListEncoder both use NSNumber to box Bool values. An encoded Bool can be coerced to any numerical type during decoding because (false as NSNumber).intValue == 0. As a remedy, all of the unbox(_:as:) methods of _JSONDecoder and _PlistDecoder for numerical types include a check that the value is not identical to either kCFBooleanTrue or kCFBooleanFalse, and throw a DecodingError._typeMismatch(at:expectation:) if this check fails.
1 parent 80c7d1e commit 997fe01

File tree

4 files changed

+98
-24
lines changed

4 files changed

+98
-24
lines changed

stdlib/public/SDK/Foundation/JSONEncoder.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,7 +1767,7 @@ extension _JSONDecoder {
17671767
fileprivate func unbox(_ value: Any, as type: Int.Type) throws -> Int? {
17681768
guard !(value is NSNull) else { return nil }
17691769

1770-
guard let number = value as? NSNumber else {
1770+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
17711771
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
17721772
}
17731773

@@ -1782,7 +1782,7 @@ extension _JSONDecoder {
17821782
fileprivate func unbox(_ value: Any, as type: Int8.Type) throws -> Int8? {
17831783
guard !(value is NSNull) else { return nil }
17841784

1785-
guard let number = value as? NSNumber else {
1785+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
17861786
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
17871787
}
17881788

@@ -1797,7 +1797,7 @@ extension _JSONDecoder {
17971797
fileprivate func unbox(_ value: Any, as type: Int16.Type) throws -> Int16? {
17981798
guard !(value is NSNull) else { return nil }
17991799

1800-
guard let number = value as? NSNumber else {
1800+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18011801
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18021802
}
18031803

@@ -1812,7 +1812,7 @@ extension _JSONDecoder {
18121812
fileprivate func unbox(_ value: Any, as type: Int32.Type) throws -> Int32? {
18131813
guard !(value is NSNull) else { return nil }
18141814

1815-
guard let number = value as? NSNumber else {
1815+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18161816
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18171817
}
18181818

@@ -1827,7 +1827,7 @@ extension _JSONDecoder {
18271827
fileprivate func unbox(_ value: Any, as type: Int64.Type) throws -> Int64? {
18281828
guard !(value is NSNull) else { return nil }
18291829

1830-
guard let number = value as? NSNumber else {
1830+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18311831
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18321832
}
18331833

@@ -1842,7 +1842,7 @@ extension _JSONDecoder {
18421842
fileprivate func unbox(_ value: Any, as type: UInt.Type) throws -> UInt? {
18431843
guard !(value is NSNull) else { return nil }
18441844

1845-
guard let number = value as? NSNumber else {
1845+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18461846
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18471847
}
18481848

@@ -1857,7 +1857,7 @@ extension _JSONDecoder {
18571857
fileprivate func unbox(_ value: Any, as type: UInt8.Type) throws -> UInt8? {
18581858
guard !(value is NSNull) else { return nil }
18591859

1860-
guard let number = value as? NSNumber else {
1860+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18611861
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18621862
}
18631863

@@ -1872,7 +1872,7 @@ extension _JSONDecoder {
18721872
fileprivate func unbox(_ value: Any, as type: UInt16.Type) throws -> UInt16? {
18731873
guard !(value is NSNull) else { return nil }
18741874

1875-
guard let number = value as? NSNumber else {
1875+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18761876
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18771877
}
18781878

@@ -1887,7 +1887,7 @@ extension _JSONDecoder {
18871887
fileprivate func unbox(_ value: Any, as type: UInt32.Type) throws -> UInt32? {
18881888
guard !(value is NSNull) else { return nil }
18891889

1890-
guard let number = value as? NSNumber else {
1890+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
18911891
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
18921892
}
18931893

@@ -1902,7 +1902,7 @@ extension _JSONDecoder {
19021902
fileprivate func unbox(_ value: Any, as type: UInt64.Type) throws -> UInt64? {
19031903
guard !(value is NSNull) else { return nil }
19041904

1905-
guard let number = value as? NSNumber else {
1905+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
19061906
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
19071907
}
19081908

@@ -1917,7 +1917,7 @@ extension _JSONDecoder {
19171917
fileprivate func unbox(_ value: Any, as type: Float.Type) throws -> Float? {
19181918
guard !(value is NSNull) else { return nil }
19191919

1920-
if let number = value as? NSNumber {
1920+
if let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse {
19211921
// We are willing to return a Float by losing precision:
19221922
// * If the original value was integral,
19231923
// * and the integral value was > Float.greatestFiniteMagnitude, we will fail
@@ -1963,7 +1963,7 @@ extension _JSONDecoder {
19631963
fileprivate func unbox(_ value: Any, as type: Double.Type) throws -> Double? {
19641964
guard !(value is NSNull) else { return nil }
19651965

1966-
if let number = value as? NSNumber {
1966+
if let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse {
19671967
// We are always willing to return the number as a Double:
19681968
// * If the original value was integral, it is guaranteed to fit in a Double; we are willing to lose precision past 2^53 if you encoded a UInt64 but requested a Double
19691969
// * If it was a Float or Double, you will get back the precise value

stdlib/public/SDK/Foundation/PlistEncoder.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,7 +1549,7 @@ extension _PlistDecoder {
15491549
fileprivate func unbox(_ value: Any, as type: Int.Type) throws -> Int? {
15501550
if let string = value as? String, string == _plistNull { return nil }
15511551

1552-
guard let number = value as? NSNumber else {
1552+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
15531553
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
15541554
}
15551555

@@ -1564,7 +1564,7 @@ extension _PlistDecoder {
15641564
fileprivate func unbox(_ value: Any, as type: Int8.Type) throws -> Int8? {
15651565
if let string = value as? String, string == _plistNull { return nil }
15661566

1567-
guard let number = value as? NSNumber else {
1567+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
15681568
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
15691569
}
15701570

@@ -1579,7 +1579,7 @@ extension _PlistDecoder {
15791579
fileprivate func unbox(_ value: Any, as type: Int16.Type) throws -> Int16? {
15801580
if let string = value as? String, string == _plistNull { return nil }
15811581

1582-
guard let number = value as? NSNumber else {
1582+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
15831583
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
15841584
}
15851585

@@ -1594,7 +1594,7 @@ extension _PlistDecoder {
15941594
fileprivate func unbox(_ value: Any, as type: Int32.Type) throws -> Int32? {
15951595
if let string = value as? String, string == _plistNull { return nil }
15961596

1597-
guard let number = value as? NSNumber else {
1597+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
15981598
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
15991599
}
16001600

@@ -1609,7 +1609,7 @@ extension _PlistDecoder {
16091609
fileprivate func unbox(_ value: Any, as type: Int64.Type) throws -> Int64? {
16101610
if let string = value as? String, string == _plistNull { return nil }
16111611

1612-
guard let number = value as? NSNumber else {
1612+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16131613
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16141614
}
16151615

@@ -1624,7 +1624,7 @@ extension _PlistDecoder {
16241624
fileprivate func unbox(_ value: Any, as type: UInt.Type) throws -> UInt? {
16251625
if let string = value as? String, string == _plistNull { return nil }
16261626

1627-
guard let number = value as? NSNumber else {
1627+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16281628
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16291629
}
16301630

@@ -1639,7 +1639,7 @@ extension _PlistDecoder {
16391639
fileprivate func unbox(_ value: Any, as type: UInt8.Type) throws -> UInt8? {
16401640
if let string = value as? String, string == _plistNull { return nil }
16411641

1642-
guard let number = value as? NSNumber else {
1642+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16431643
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16441644
}
16451645

@@ -1654,7 +1654,7 @@ extension _PlistDecoder {
16541654
fileprivate func unbox(_ value: Any, as type: UInt16.Type) throws -> UInt16? {
16551655
if let string = value as? String, string == _plistNull { return nil }
16561656

1657-
guard let number = value as? NSNumber else {
1657+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16581658
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16591659
}
16601660

@@ -1669,7 +1669,7 @@ extension _PlistDecoder {
16691669
fileprivate func unbox(_ value: Any, as type: UInt32.Type) throws -> UInt32? {
16701670
if let string = value as? String, string == _plistNull { return nil }
16711671

1672-
guard let number = value as? NSNumber else {
1672+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16731673
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16741674
}
16751675

@@ -1684,7 +1684,7 @@ extension _PlistDecoder {
16841684
fileprivate func unbox(_ value: Any, as type: UInt64.Type) throws -> UInt64? {
16851685
if let string = value as? String, string == _plistNull { return nil }
16861686

1687-
guard let number = value as? NSNumber else {
1687+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
16881688
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
16891689
}
16901690

@@ -1699,7 +1699,7 @@ extension _PlistDecoder {
16991699
fileprivate func unbox(_ value: Any, as type: Float.Type) throws -> Float? {
17001700
if let string = value as? String, string == _plistNull { return nil }
17011701

1702-
guard let number = value as? NSNumber else {
1702+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
17031703
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
17041704
}
17051705

@@ -1714,7 +1714,7 @@ extension _PlistDecoder {
17141714
fileprivate func unbox(_ value: Any, as type: Double.Type) throws -> Double? {
17151715
if let string = value as? String, string == _plistNull { return nil }
17161716

1717-
guard let number = value as? NSNumber else {
1717+
guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else {
17181718
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
17191719
}
17201720

test/stdlib/TestJSONEncoder.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,34 @@ class TestJSONEncoder : TestJSONEncoderSuper {
449449
// Optional URLs should encode the same way.
450450
_testRoundTrip(of: OptionalTopLevelWrapper(url), expectedJSON: expectedJSON)
451451
}
452+
453+
// MARK: - Type coercion
454+
func testTypeCoercion() {
455+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self)
456+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self)
457+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self)
458+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self)
459+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self)
460+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self)
461+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self)
462+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self)
463+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self)
464+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self)
465+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self)
466+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self)
467+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self)
468+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self)
469+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self)
470+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self)
471+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self)
472+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self)
473+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self)
474+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self)
475+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self)
476+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self)
477+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self)
478+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self)
479+
}
452480

453481
// MARK: - Helper Functions
454482
private var _jsonEmptyDictionary: Data {
@@ -498,6 +526,14 @@ class TestJSONEncoder : TestJSONEncoderSuper {
498526
expectUnreachable("Failed to decode \(T.self) from JSON: \(error)")
499527
}
500528
}
529+
530+
private func _testRoundTripTypeCoercionFailure<T,U>(of value: T, as type: U.Type) where T : Codable, U : Codable {
531+
do {
532+
let data = try JSONEncoder().encode(value)
533+
let _ = try JSONDecoder().decode(U.self, from: data)
534+
expectUnreachable("Coercion from \(T.self) to \(U.self) was expected to fail.")
535+
} catch {}
536+
}
501537
}
502538

503539
// MARK: - Helper Global Functions
@@ -1067,5 +1103,6 @@ JSONEncoderTests.test("testNestedContainerCodingPaths") { TestJSONEncoder().test
10671103
JSONEncoderTests.test("testSuperEncoderCodingPaths") { TestJSONEncoder().testSuperEncoderCodingPaths() }
10681104
JSONEncoderTests.test("testInterceptDecimal") { TestJSONEncoder().testInterceptDecimal() }
10691105
JSONEncoderTests.test("testInterceptURL") { TestJSONEncoder().testInterceptURL() }
1106+
JSONEncoderTests.test("testTypeCoercion") { TestJSONEncoder().testTypeCoercion() }
10701107
runAllTests()
10711108
#endif

test/stdlib/TestPlistEncoder.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,34 @@ class TestPropertyListEncoder : TestPropertyListEncoderSuper {
170170
_testRoundTrip(of: topLevel, in: .xml, expectedPlist: try! PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0))
171171
}
172172

173+
// MARK: - Type coercion
174+
func testTypeCoercion() {
175+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self)
176+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self)
177+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self)
178+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self)
179+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self)
180+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self)
181+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self)
182+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self)
183+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self)
184+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self)
185+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self)
186+
_testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self)
187+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self)
188+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self)
189+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self)
190+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self)
191+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self)
192+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self)
193+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self)
194+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self)
195+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self)
196+
_testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self)
197+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self)
198+
_testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self)
199+
}
200+
173201
// MARK: - Helper Functions
174202
private var _plistEmptyDictionaryBinary: Data {
175203
return Data(base64Encoded: "YnBsaXN0MDDQCAAAAAAAAAEBAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAJ")!
@@ -211,6 +239,14 @@ class TestPropertyListEncoder : TestPropertyListEncoderSuper {
211239
expectUnreachable("Failed to decode \(T.self) from plist: \(error)")
212240
}
213241
}
242+
243+
private func _testRoundTripTypeCoercionFailure<T,U>(of value: T, as type: U.Type) where T : Codable, U : Codable {
244+
do {
245+
let data = try PropertyListEncoder().encode(value)
246+
let _ = try PropertyListDecoder().decode(U.self, from: data)
247+
expectUnreachable("Coercion from \(T.self) to \(U.self) was expected to fail.")
248+
} catch {}
249+
}
214250
}
215251

216252
// MARK: - Helper Global Functions
@@ -694,5 +730,6 @@ PropertyListEncoderTests.test("testSuperEncoderCodingPaths") { TestPropertyListE
694730
PropertyListEncoderTests.test("testEncodingTopLevelData") { TestPropertyListEncoder().testEncodingTopLevelData() }
695731
PropertyListEncoderTests.test("testInterceptData") { TestPropertyListEncoder().testInterceptData() }
696732
PropertyListEncoderTests.test("testInterceptDate") { TestPropertyListEncoder().testInterceptDate() }
733+
PropertyListEncoderTests.test("testTypeCoercion") { TestPropertyListEncoder().testTypeCoercion() }
697734
runAllTests()
698735
#endif

0 commit comments

Comments
 (0)