diff --git a/swift/Sources/FlatBuffers/Root.swift b/swift/Sources/FlatBuffers/Root.swift index e176414b09..807f7b4efa 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.size) + + if requireExactSize { + guard prefix == availableSize else { + throw FlatbuffersErrors.prefixedSizeNotEqualToBufferSize + } + } else if prefix > availableSize { + throw FlatbuffersErrors.outOfBounds( + position: UInt(prefixPosition) + &+ UInt(MemoryLayout.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 @@ public func getPrefixedSizeCheckedRoot( 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 @@ public func getCheckedPrefixedSizeRoot( 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 @@ public func getCheckedRoot( 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.verify(&verifier, at: 0, of: T.self) + try ForwardOffset.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 525e149429..396d4ea454 100644 --- a/swift/Sources/FlatBuffers/Verifier.swift +++ b/swift/Sources/FlatBuffers/Verifier.swift @@ -215,12 +215,16 @@ public struct Verifier { } @inline(__always) - func verify(id: String) throws { + func verify(id: String, at position: Int) throws { let size = MemoryLayout.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 b4c94ca25a..0b5847bcc5 100644 --- a/tests/swift/Tests/Flatbuffers/FlatbuffersVerifierTests.swift +++ b/tests/swift/Tests/Flatbuffers/FlatbuffersVerifierTests.swift @@ -373,6 +373,58 @@ final class FlatbuffersVerifierTests { } } + @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 { _ =