[Swift] Verify size-prefixed roots from reader position (#9196)
* [Swift] Verify size-prefixed roots from reader position
Validate the size prefix before advancing ByteBuffer.reader, then use the active reader position consistently for root, file identifier, and returned-object verification. Cover malformed decoy roots, oversized prefixes, short identifiers, and valid prefixed identifiers.
* Use wrapping arithmetic in Swift verifier
---------
Co-authored-by: Darren Carreras <283775510+carrerasdarren-cell@users.noreply.github.com>
diff --git a/swift/Sources/FlatBuffers/Root.swift b/swift/Sources/FlatBuffers/Root.swift
index e176414..807f7b4 100644
--- a/swift/Sources/FlatBuffers/Root.swift
+++ b/swift/Sources/FlatBuffers/Root.swift
@@ -16,6 +16,30 @@
import Foundation
+@inline(__always)
+private func verifySizePrefix(
+ byteBuffer: inout ByteBuffer,
+ requireExactSize: Bool,
+ options: VerifierOptions) throws
+{
+ let verifier = try Verifier(buffer: &byteBuffer, options: options)
+ let prefixPosition = byteBuffer.reader
+ let prefix: UOffset = try verifier.getValue(at: prefixPosition)
+ let availableSize = byteBuffer.size &- UOffset(MemoryLayout<UOffset>.size)
+
+ if requireExactSize {
+ guard prefix == availableSize else {
+ throw FlatbuffersErrors.prefixedSizeNotEqualToBufferSize
+ }
+ } else if prefix > availableSize {
+ throw FlatbuffersErrors.outOfBounds(
+ position: UInt(prefixPosition)
+ &+ UInt(MemoryLayout<UOffset>.size)
+ &+ UInt(prefix),
+ end: byteBuffer.capacity)
+ }
+}
+
/// Takes in a prefixed sized buffer, where the prefixed size would be skipped.
/// And would verify that the buffer passed is a valid `Flatbuffers` Object.
/// - Parameters:
@@ -31,6 +55,10 @@
fileId: String? = nil,
options: VerifierOptions = .init()) throws -> T
{
+ try verifySizePrefix(
+ byteBuffer: &byteBuffer,
+ requireExactSize: false,
+ options: options)
byteBuffer.skipPrefix()
return try getCheckedRoot(
byteBuffer: &byteBuffer,
@@ -53,10 +81,11 @@
fileId: String? = nil,
options: VerifierOptions = .init()) throws -> T
{
- let prefix = byteBuffer.skipPrefix()
- if prefix != byteBuffer.size {
- throw FlatbuffersErrors.prefixedSizeNotEqualToBufferSize
- }
+ try verifySizePrefix(
+ byteBuffer: &byteBuffer,
+ requireExactSize: true,
+ options: options)
+ byteBuffer.skipPrefix()
return try getCheckedRoot(
byteBuffer: &byteBuffer,
fileId: fileId,
@@ -95,14 +124,15 @@
options: VerifierOptions = .init()) throws -> T
{
var verifier = try Verifier(buffer: &byteBuffer, options: options)
+ let rootPosition = byteBuffer.reader
if let fileId = fileId {
- try verifier.verify(id: fileId)
+ try verifier.verify(id: fileId, at: rootPosition)
}
- try ForwardOffset<T>.verify(&verifier, at: 0, of: T.self)
+ try ForwardOffset<T>.verify(&verifier, at: rootPosition, of: T.self)
return T.init(
byteBuffer,
- o: Int32(byteBuffer.read(def: UOffset.self, position: byteBuffer.reader))
- &+ Int32(byteBuffer.reader))
+ o: Int32(byteBuffer.read(def: UOffset.self, position: rootPosition))
+ &+ Int32(rootPosition))
}
/// Returns a `NON-Checked` flatbuffers object
diff --git a/swift/Sources/FlatBuffers/Verifier.swift b/swift/Sources/FlatBuffers/Verifier.swift
index 525e149..5922ddb 100644
--- a/swift/Sources/FlatBuffers/Verifier.swift
+++ b/swift/Sources/FlatBuffers/Verifier.swift
@@ -215,12 +215,16 @@
}
@inline(__always)
- func verify(id: String) throws {
+ func verify(id: String, at position: Int) throws {
let size = MemoryLayout<Int32>.size
- guard storage.capacity >= (size &* 2) else {
+ guard
+ position >= 0,
+ position <= storage.capacity,
+ storage.capacity - position >= size &* 2
+ else {
throw FlatbuffersErrors.bufferDoesntContainID
}
- let str = _buffer.readString(at: size, count: size)
+ let str = _buffer.readString(at: position &+ size, count: size)
if id == str {
return
}
diff --git a/tests/swift/Tests/Flatbuffers/FlatbuffersVerifierTests.swift b/tests/swift/Tests/Flatbuffers/FlatbuffersVerifierTests.swift
index b4c94ca..0b5847b 100644
--- a/tests/swift/Tests/Flatbuffers/FlatbuffersVerifierTests.swift
+++ b/tests/swift/Tests/Flatbuffers/FlatbuffersVerifierTests.swift
@@ -374,6 +374,58 @@
}
@Test
+ func testSizePrefixedVerifierUsesPostPrefixRoot() throws {
+ // The size prefix points to a valid empty decoy table at byte 44. The real
+ // root at byte 4 points to a truncated UInt64 vector that must be rejected.
+ let bytes: [UInt8] = [
+ 44, 0, 0, 0,
+ 16, 0, 0, 0,
+ 6, 0, 8, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0,
+ 12, 0, 0, 0,
+ 8, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 65, 66,
+ 0, 0,
+ 4, 0, 4, 0,
+ 4, 0, 0, 0,
+ ]
+
+ var strictBuffer = ByteBuffer(bytes: bytes)
+ #expect(throws: FlatbuffersErrors.self) {
+ try getCheckedPrefixedSizeRoot(
+ byteBuffer: &strictBuffer) as Swift_Tests_Vectors
+ }
+
+ var prefixedBuffer = ByteBuffer(bytes: bytes)
+ #expect(throws: FlatbuffersErrors.self) {
+ try getPrefixedSizeCheckedRoot(
+ byteBuffer: &prefixedBuffer) as Swift_Tests_Vectors
+ }
+
+ var builder = FlatBufferBuilder()
+ let movie = Movie.createMovie(&builder)
+ Movie.finish(&builder, end: movie, prefix: true)
+ var validBuffer = builder.sizedBuffer
+ let _: Movie = try getCheckedPrefixedSizeRoot(
+ byteBuffer: &validBuffer,
+ fileId: Movie.id)
+
+ var shortBuffer = ByteBuffer(bytes: [0, 0, 0, 0])
+ #expect(throws: FlatbuffersErrors.bufferDoesntContainID) {
+ try getCheckedRoot(
+ byteBuffer: &shortBuffer,
+ fileId: Movie.id) as Movie
+ }
+
+ var oversizedPrefix = ByteBuffer(bytes: [5, 0, 0, 0, 0, 0, 0, 0])
+ #expect(throws: FlatbuffersErrors.outOfBounds(position: 9, end: 8)) {
+ try getPrefixedSizeCheckedRoot(
+ byteBuffer: &oversizedPrefix) as Movie
+ }
+ #expect(oversizedPrefix.reader == 0)
+ }
+
+ @Test
func testFullVerifier() throws {
_ =
try getCheckedRoot(