Skip to content

Commit beffbc6

Browse files
authored
[Foundation] Discontiguous data slices should not heap corrupt (#13252)
* [Foundation] Byte access and methods that funnel to byte access for slices of discontiguous data (ala backed by dispatch_data_t) should void heap corruption and walking off the ends of buffers * add missing parens on test_byte_access_of_discontiguousData * Use the proper byte count in testing
1 parent 5341155 commit beffbc6

File tree

2 files changed

+51
-23
lines changed

2 files changed

+51
-23
lines changed

stdlib/public/SDK/Foundation/Data.swift

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -139,23 +139,26 @@ public final class _DataStorage {
139139
} else {
140140
var buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: range.count, alignment: MemoryLayout<UInt>.alignment)
141141
defer { buffer.deallocate() }
142+
142143
let sliceRange = NSRange(location: range.lowerBound - _offset, length: range.count)
143144
var enumerated = 0
144145
d.enumerateBytes { (ptr, byteRange, stop) in
145-
if NSIntersectionRange(sliceRange, byteRange).length > 0 {
146-
let lower = Swift.max(byteRange.location, sliceRange.location)
147-
let upper = Swift.min(byteRange.location + byteRange.length, sliceRange.location + sliceRange.length)
148-
let offset = lower - byteRange.location
149-
let effectiveRange = NSRange(location: lower, length: upper - lower)
150-
if effectiveRange == sliceRange {
151-
memcpy(buffer.baseAddress!, ptr, effectiveRange.length)
146+
if byteRange.upperBound - _offset < range.lowerBound {
147+
// before the range that we are looking for...
148+
} else if byteRange.lowerBound - _offset > range.upperBound {
149+
stop.pointee = true // we are past the range in question so we need to stop
150+
} else {
151+
// the byteRange somehow intersects the range in question that we are looking for...
152+
let lower = Swift.max(byteRange.lowerBound - _offset, range.lowerBound)
153+
let upper = Swift.min(byteRange.upperBound - _offset, range.upperBound)
154+
155+
let len = upper - lower
156+
memcpy(buffer.baseAddress!.advanced(by: enumerated), ptr.advanced(by: lower - (byteRange.lowerBound - _offset)), len)
157+
enumerated += len
158+
159+
if upper == range.upperBound {
152160
stop.pointee = true
153-
} else {
154-
memcpy(buffer.baseAddress!.advanced(by: enumerated), ptr, effectiveRange.length)
155161
}
156-
enumerated += byteRange.length
157-
} else if sliceRange.location + sliceRange.length < byteRange.location {
158-
stop.pointee = true
159162
}
160163
}
161164
return try apply(UnsafeRawBufferPointer(buffer))
@@ -170,22 +173,26 @@ public final class _DataStorage {
170173
} else {
171174
var buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: range.count, alignment: MemoryLayout<UInt>.alignment)
172175
defer { buffer.deallocate() }
176+
173177
let sliceRange = NSRange(location: range.lowerBound - _offset, length: range.count)
174178
var enumerated = 0
175179
d.enumerateBytes { (ptr, byteRange, stop) in
176-
if NSIntersectionRange(sliceRange, byteRange).length > 0 {
177-
let lower = Swift.max(byteRange.location, sliceRange.location)
178-
let upper = Swift.min(byteRange.location + byteRange.length, sliceRange.location + sliceRange.length)
179-
let effectiveRange = NSRange(location: lower, length: upper - lower)
180-
if effectiveRange == sliceRange {
181-
memcpy(buffer.baseAddress!, ptr, effectiveRange.length)
180+
if byteRange.upperBound - _offset < range.lowerBound {
181+
// before the range that we are looking for...
182+
} else if byteRange.lowerBound - _offset > range.upperBound {
183+
stop.pointee = true // we are past the range in question so we need to stop
184+
} else {
185+
// the byteRange somehow intersects the range in question that we are looking for...
186+
let lower = Swift.max(byteRange.lowerBound - _offset, range.lowerBound)
187+
let upper = Swift.min(byteRange.upperBound - _offset, range.upperBound)
188+
189+
let len = upper - lower
190+
memcpy(buffer.baseAddress!.advanced(by: enumerated), ptr.advanced(by: lower - (byteRange.lowerBound - _offset)), len)
191+
enumerated += len
192+
193+
if upper == range.upperBound {
182194
stop.pointee = true
183-
} else {
184-
memcpy(buffer.baseAddress!.advanced(by: enumerated), ptr, effectiveRange.length)
185195
}
186-
enumerated += byteRange.length
187-
} else if sliceRange.location + sliceRange.length < byteRange.location {
188-
stop.pointee = true
189196
}
190197
}
191198
return try apply(UnsafeRawBufferPointer(buffer))

test/stdlib/TestData.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
23
// Licensed under Apache License v2.0 with Runtime Library Exception
34
//
@@ -3721,6 +3722,25 @@ class TestData : TestDataSuper {
37213722
}
37223723
expectEqual(data[data.startIndex.advanced(by: 1)], 0xFF)
37233724
}
3725+
3726+
func test_byte_access_of_discontiguousData() {
3727+
var d = DispatchData.empty
3728+
let bytes: [UInt8] = [0, 1, 2, 3, 4, 5]
3729+
for _ in 0..<3 {
3730+
bytes.withUnsafeBufferPointer {
3731+
d.append($0)
3732+
}
3733+
}
3734+
let ref = d as AnyObject
3735+
let data = ref as! Data
3736+
3737+
let cnt = data.count - 4
3738+
let t = data.dropFirst(4).withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
3739+
return Data(bytes: bytes, count: cnt)
3740+
}
3741+
3742+
expectEqual(Data(bytes: [4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]), t)
3743+
}
37243744
}
37253745

37263746
#if !FOUNDATION_XCTEST
@@ -4037,6 +4057,7 @@ DataTests.test("test_validateMutation_slice_immutableBacking_withUnsafeMutableBy
40374057
DataTests.test("test_validateMutation_slice_mutableBacking_withUnsafeMutableBytes_lengthLessThanLowerBound") { TestData().test_validateMutation_slice_mutableBacking_withUnsafeMutableBytes_lengthLessThanLowerBound() }
40384058
DataTests.test("test_validateMutation_slice_customBacking_withUnsafeMutableBytes_lengthLessThanLowerBound") { TestData().test_validateMutation_slice_customBacking_withUnsafeMutableBytes_lengthLessThanLowerBound() }
40394059
DataTests.test("test_validateMutation_slice_customMutableBacking_withUnsafeMutableBytes_lengthLessThanLowerBound") { TestData().test_validateMutation_slice_customMutableBacking_withUnsafeMutableBytes_lengthLessThanLowerBound() }
4060+
DataTests.test("test_byte_access_of_discontiguousData") { TestData().test_byte_access_of_discontiguousData() }
40404061

40414062
// XCTest does not have a crash detection, whereas lit does
40424063
DataTests.test("bounding failure subdata") {

0 commit comments

Comments
 (0)