[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>
This commit is contained in:
carrerasdarren-cell
2026-08-11 21:49:44 +02:00
committed by GitHub
co-authored by Darren Carreras
parent 81edeb17d9
commit 5761d6e67a
3 changed files with 97 additions and 11 deletions
+38 -8
View File
@@ -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 @@ public func getPrefixedSizeCheckedRoot<T: FlatBufferTable & Verifiable>(
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<T: FlatBufferTable & Verifiable>(
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<T: FlatBufferTable & Verifiable>(
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
+7 -3
View File
@@ -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<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
}