Skip to content

Commit 2162fc9

Browse files
authored
(140288075) Use-after-free in Swift implementation of NSData (#1108)
1 parent 90700c1 commit 2162fc9

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

Sources/FoundationEssentials/Data/Data.swift

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,41 @@ internal final class __DataStorage : @unchecked Sendable {
209209
return _bytes?.advanced(by: -_offset)
210210
}
211211

212+
@inlinable
213+
static var copyWillRetainMask: Int {
214+
#if _pointerBitWidth(_64)
215+
return Int(bitPattern: 0x8000000000000000)
216+
#elseif _pointerBitWidth(_32)
217+
return Int(bitPattern: 0x80000000)
218+
#endif
219+
}
220+
221+
@inlinable
222+
static var capacityMask: Int {
223+
#if _pointerBitWidth(_64)
224+
return Int(bitPattern: 0x7FFFFFFFFFFFFFFF)
225+
#elseif _pointerBitWidth(_32)
226+
return Int(bitPattern: 0x7FFFFFFF)
227+
#endif
228+
}
229+
212230
@inlinable // This is @inlinable as trivially computable.
213231
var capacity: Int {
214-
return _capacity
232+
return _capacity & __DataStorage.capacityMask
233+
}
234+
235+
@inlinable
236+
var _copyWillRetain: Bool {
237+
get {
238+
return _capacity & __DataStorage.copyWillRetainMask == 0
239+
}
240+
set {
241+
if !newValue {
242+
_capacity |= __DataStorage.copyWillRetainMask
243+
} else {
244+
_capacity &= __DataStorage.capacityMask
245+
}
246+
}
215247
}
216248

217249
@inlinable // This is @inlinable as trivially computable.
@@ -355,7 +387,7 @@ internal final class __DataStorage : @unchecked Sendable {
355387
func setLength(_ length: Int) {
356388
let origLength = _length
357389
let newLength = length
358-
if _capacity < newLength || _bytes == nil {
390+
if capacity < newLength || _bytes == nil {
359391
ensureUniqueBufferReference(growingTo: newLength, clear: true)
360392
} else if origLength < newLength && _needToZero {
361393
_ = memset(_bytes! + origLength, 0, newLength - origLength)
@@ -370,7 +402,7 @@ internal final class __DataStorage : @unchecked Sendable {
370402
precondition(length >= 0, "Length of appending bytes must not be negative")
371403
let origLength = _length
372404
let newLength = origLength + length
373-
if _capacity < newLength || _bytes == nil {
405+
if capacity < newLength || _bytes == nil {
374406
ensureUniqueBufferReference(growingTo: newLength, clear: false)
375407
}
376408
_length = newLength
@@ -437,7 +469,7 @@ internal final class __DataStorage : @unchecked Sendable {
437469
let range = range_.lowerBound - _offset ..< range_.upperBound - _offset
438470
if range.upperBound - range.lowerBound == 0 { return }
439471
if _length < range.upperBound {
440-
if _capacity <= range.upperBound {
472+
if capacity <= range.upperBound {
441473
ensureUniqueBufferReference(growingTo: range.upperBound, clear: false)
442474
}
443475
_length = range.upperBound
@@ -1962,7 +1994,17 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
19621994
deallocator._deallocator(bytes, count)
19631995
_representation = .empty
19641996
} else {
1965-
_representation = _Representation(__DataStorage(bytes: bytes, length: count, copy: false, deallocator: whichDeallocator, offset: 0), count: count)
1997+
let storage = __DataStorage(bytes: bytes, length: count, copy: false, deallocator: whichDeallocator, offset: 0)
1998+
switch deallocator {
1999+
// technically .custom can potential cause this too but there is a potential chance this is expected behavior
2000+
// commented out for now... revisit later
2001+
// case .custom: fallthrough
2002+
case .none:
2003+
storage._copyWillRetain = false
2004+
default:
2005+
break
2006+
}
2007+
_representation = _Representation(storage, count: count)
19662008
}
19672009
}
19682010

Tests/FoundationEssentialsTests/DataTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,3 +1887,29 @@ extension DataTests {
18871887
}
18881888
}
18891889
#endif // FOUNDATION_FRAMEWORK
1890+
1891+
#if FOUNDATION_FRAMEWORK // Bridging is not available in the FoundationPreview package
1892+
extension DataTests {
1893+
func test_noCustomDealloc_bridge() {
1894+
let bytes = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: MemoryLayout<AnyObject>.alignment)
1895+
1896+
let data: Data = Data(bytesNoCopy: bytes.baseAddress!, count: bytes.count, deallocator: .free)
1897+
let copy = data._bridgeToObjectiveC().copy() as! NSData
1898+
data.withUnsafeBytes { buffer in
1899+
XCTAssertEqual(buffer.baseAddress, copy.bytes)
1900+
}
1901+
}
1902+
1903+
func test_noCopy_uaf_bridge() {
1904+
// this can only really be tested (modulo ASAN) via comparison of the pointer address of the storage.
1905+
let bytes = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: MemoryLayout<AnyObject>.alignment)
1906+
1907+
let data: Data = Data(bytesNoCopy: bytes.baseAddress!, count: bytes.count, deallocator: .none)
1908+
let copy = data._bridgeToObjectiveC().copy() as! NSData
1909+
data.withUnsafeBytes { buffer in
1910+
XCTAssertNotEqual(buffer.baseAddress, copy.bytes)
1911+
}
1912+
bytes.deallocate()
1913+
}
1914+
}
1915+
#endif

0 commit comments

Comments
 (0)