diff --git a/swift/Sources/FlatBuffers/ByteBuffer.swift b/swift/Sources/FlatBuffers/ByteBuffer.swift index f22ecff571..49fee66f99 100644 --- a/swift/Sources/FlatBuffers/ByteBuffer.swift +++ b/swift/Sources/FlatBuffers/ByteBuffer.swift @@ -370,7 +370,7 @@ public struct ByteBuffer: @unchecked Sendable { index: Int, count: Int) -> [T] { - assert( + precondition( index + count <= capacity, "Reading out of bounds is illegal") @@ -393,7 +393,7 @@ public struct ByteBuffer: @unchecked Sendable { count: Int, body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T { - assert( + precondition( index + count <= capacity, "Reading out of bounds is illegal") return try _storage.readWithUnsafeRawPointer(position: index) { @@ -413,7 +413,7 @@ public struct ByteBuffer: @unchecked Sendable { count: Int, type: String.Encoding = .utf8) -> String? { - assert( + precondition( index + count <= capacity, "Reading out of bounds is illegal") return _storage.readWithUnsafeRawPointer(position: index) { @@ -435,7 +435,7 @@ public struct ByteBuffer: @unchecked Sendable { at index: Int, count: Int) -> String? { - assert( + precondition( index + count <= capacity, "Reading out of bounds is illegal") return _storage.readWithUnsafeRawPointer(position: index) { @@ -465,6 +465,9 @@ public struct ByteBuffer: @unchecked Sendable { @usableFromInline @inline(__always) mutating func skipPrefix() -> Int32 { + precondition( + _readerIndex >= MemoryLayout.size && capacity >= MemoryLayout.size, + "Reading out of bounds is illegal") _readerIndex = _readerIndex &- MemoryLayout.size return read(def: Int32.self, position: 0) } diff --git a/swift/Sources/FlexBuffers/ByteBuffer.swift b/swift/Sources/FlexBuffers/ByteBuffer.swift index 6789618ced..ca8d123a54 100644 --- a/swift/Sources/FlexBuffers/ByteBuffer.swift +++ b/swift/Sources/FlexBuffers/ByteBuffer.swift @@ -411,7 +411,7 @@ public struct ByteBuffer { index: Int, count: Int) -> [T] { - assert( + precondition( index + count <= capacity, "Reading out of bounds is illegal") @@ -429,7 +429,7 @@ public struct ByteBuffer { count: Int, type: String.Encoding) -> String? { - assert( + precondition( index + count <= capacity, "Reading out of bounds is illegal") return _storage.readWithUnsafeRawPointer(position: index) { @@ -451,7 +451,7 @@ public struct ByteBuffer { at index: Int, count: Int) -> String? { - assert( + precondition( index + count <= capacity, "Reading out of bounds is illegal") return _storage.readWithUnsafeRawPointer(position: index) { @@ -470,7 +470,7 @@ public struct ByteBuffer { count: Int, body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T { - assert( + precondition( index + count <= capacity, "Reading out of bounds is illegal") return try _storage.readWithUnsafeRawPointer(position: index) { diff --git a/swift/Sources/FlexBuffers/Reader/Reference.swift b/swift/Sources/FlexBuffers/Reader/Reference.swift index e78efecce7..01bd75535f 100644 --- a/swift/Sources/FlexBuffers/Reader/Reference.swift +++ b/swift/Sources/FlexBuffers/Reader/Reference.swift @@ -47,6 +47,9 @@ public func getRoot(buffer: ByteBuffer) throws -> Reference? { @inline(__always) public func getRootChecked(buffer: ByteBuffer) throws -> Reference? { // TODO(mustiikhalil): implement verifier + // Until a full verifier exists, at minimum fail closed when the root + // header cannot possibly fit in the buffer. + precondition(buffer.capacity >= 3, "FlexBuffer too small to hold a root") return try getRoot(buffer: buffer) } diff --git a/swift/Sources/FlexBuffers/Reader/TypedVector.swift b/swift/Sources/FlexBuffers/Reader/TypedVector.swift index 45880a96c4..e838cecf59 100644 --- a/swift/Sources/FlexBuffers/Reader/TypedVector.swift +++ b/swift/Sources/FlexBuffers/Reader/TypedVector.swift @@ -41,6 +41,9 @@ public struct TypedVector: FlexBufferVector { @inline(__always) public subscript(index: Int) -> Reference? { + precondition( + index >= 0 && index < count, + "TypedVector index out of bounds") let elementOffset = offset &+ (numericCast(index) &* numericCast(byteWidth)) return Reference( byteBuffer: byteBuffer, diff --git a/tests/swift/Tests/Flatbuffers/ByteBufferBoundsRegressionTests.swift b/tests/swift/Tests/Flatbuffers/ByteBufferBoundsRegressionTests.swift new file mode 100644 index 0000000000..8536b1cff7 --- /dev/null +++ b/tests/swift/Tests/Flatbuffers/ByteBufferBoundsRegressionTests.swift @@ -0,0 +1,37 @@ +import XCTest + +@testable import FlatBuffers + +/// Regression coverage for the release-enforced read guards. +/// +/// `read`, `readSlice`, `readString`, `readDirectString`, and `skipPrefix` +/// guard against out-of-bounds reads with `precondition` (enforced in release +/// builds). These tests pin the in-bounds behavior so the guards cannot +/// accidentally reject well-formed access. +final class ByteBufferBoundsRegressionTests: XCTestCase { + + func testReadSliceInBounds() { + let buffer = ByteBuffer(bytes: [1, 2, 3, 4, 5, 6, 7, 8]) + let slice: [UInt8] = buffer.readSlice(index: 2, count: 4) + XCTAssertEqual(slice, [3, 4, 5, 6]) + } + + func testReadStringInBounds() { + let buffer = ByteBuffer(bytes: Array("hello flatbuffers".utf8)) + let str = buffer.readString(at: 0, count: 5) + XCTAssertEqual(str, "hello") + } + + func testReadStringInteriorInBounds() { + let buffer = ByteBuffer(bytes: Array("swift".utf8)) + let str = buffer.readString(at: 1, count: 4) + XCTAssertEqual(str, "wift") + } + + func testSkipPrefixOnPrefixedBuffer() { + var buffer = ByteBuffer(bytes: [0x03, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC]) + let prefix = buffer.skipPrefix() + XCTAssertEqual(prefix, 3) + XCTAssertEqual(buffer.read(def: UInt8.self, position: 4), 0xAA) // position is _readerIndex-relative + } +}