Skip to content

Commit 5761d6e

Browse files
[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>
1 parent 81edeb1 commit 5761d6e

3 files changed

Lines changed: 97 additions & 11 deletions

File tree

swift/Sources/FlatBuffers/Root.swift

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,30 @@
1616

1717
import Foundation
1818

19+
@inline(__always)
20+
private func verifySizePrefix(
21+
byteBuffer: inout ByteBuffer,
22+
requireExactSize: Bool,
23+
options: VerifierOptions) throws
24+
{
25+
let verifier = try Verifier(buffer: &byteBuffer, options: options)
26+
let prefixPosition = byteBuffer.reader
27+
let prefix: UOffset = try verifier.getValue(at: prefixPosition)
28+
let availableSize = byteBuffer.size &- UOffset(MemoryLayout<UOffset>.size)
29+
30+
if requireExactSize {
31+
guard prefix == availableSize else {
32+
throw FlatbuffersErrors.prefixedSizeNotEqualToBufferSize
33+
}
34+
} else if prefix > availableSize {
35+
throw FlatbuffersErrors.outOfBounds(
36+
position: UInt(prefixPosition)
37+
&+ UInt(MemoryLayout<UOffset>.size)
38+
&+ UInt(prefix),
39+
end: byteBuffer.capacity)
40+
}
41+
}
42+
1943
/// Takes in a prefixed sized buffer, where the prefixed size would be skipped.
2044
/// And would verify that the buffer passed is a valid `Flatbuffers` Object.
2145
/// - Parameters:
@@ -31,6 +55,10 @@ public func getPrefixedSizeCheckedRoot<T: FlatBufferTable & Verifiable>(
3155
fileId: String? = nil,
3256
options: VerifierOptions = .init()) throws -> T
3357
{
58+
try verifySizePrefix(
59+
byteBuffer: &byteBuffer,
60+
requireExactSize: false,
61+
options: options)
3462
byteBuffer.skipPrefix()
3563
return try getCheckedRoot(
3664
byteBuffer: &byteBuffer,
@@ -53,10 +81,11 @@ public func getCheckedPrefixedSizeRoot<T: FlatBufferTable & Verifiable>(
5381
fileId: String? = nil,
5482
options: VerifierOptions = .init()) throws -> T
5583
{
56-
let prefix = byteBuffer.skipPrefix()
57-
if prefix != byteBuffer.size {
58-
throw FlatbuffersErrors.prefixedSizeNotEqualToBufferSize
59-
}
84+
try verifySizePrefix(
85+
byteBuffer: &byteBuffer,
86+
requireExactSize: true,
87+
options: options)
88+
byteBuffer.skipPrefix()
6089
return try getCheckedRoot(
6190
byteBuffer: &byteBuffer,
6291
fileId: fileId,
@@ -95,14 +124,15 @@ public func getCheckedRoot<T: FlatBufferTable & Verifiable>(
95124
options: VerifierOptions = .init()) throws -> T
96125
{
97126
var verifier = try Verifier(buffer: &byteBuffer, options: options)
127+
let rootPosition = byteBuffer.reader
98128
if let fileId = fileId {
99-
try verifier.verify(id: fileId)
129+
try verifier.verify(id: fileId, at: rootPosition)
100130
}
101-
try ForwardOffset<T>.verify(&verifier, at: 0, of: T.self)
131+
try ForwardOffset<T>.verify(&verifier, at: rootPosition, of: T.self)
102132
return T.init(
103133
byteBuffer,
104-
o: Int32(byteBuffer.read(def: UOffset.self, position: byteBuffer.reader))
105-
&+ Int32(byteBuffer.reader))
134+
o: Int32(byteBuffer.read(def: UOffset.self, position: rootPosition))
135+
&+ Int32(rootPosition))
106136
}
107137

108138
/// Returns a `NON-Checked` flatbuffers object

swift/Sources/FlatBuffers/Verifier.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,16 @@ public struct Verifier {
215215
}
216216

217217
@inline(__always)
218-
func verify(id: String) throws {
218+
func verify(id: String, at position: Int) throws {
219219
let size = MemoryLayout<Int32>.size
220-
guard storage.capacity >= (size &* 2) else {
220+
guard
221+
position >= 0,
222+
position <= storage.capacity,
223+
storage.capacity - position >= size &* 2
224+
else {
221225
throw FlatbuffersErrors.bufferDoesntContainID
222226
}
223-
let str = _buffer.readString(at: size, count: size)
227+
let str = _buffer.readString(at: position &+ size, count: size)
224228
if id == str {
225229
return
226230
}

tests/swift/Tests/Flatbuffers/FlatbuffersVerifierTests.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,58 @@ final class FlatbuffersVerifierTests {
373373
}
374374
}
375375

376+
@Test
377+
func testSizePrefixedVerifierUsesPostPrefixRoot() throws {
378+
// The size prefix points to a valid empty decoy table at byte 44. The real
379+
// root at byte 4 points to a truncated UInt64 vector that must be rejected.
380+
let bytes: [UInt8] = [
381+
44, 0, 0, 0,
382+
16, 0, 0, 0,
383+
6, 0, 8, 0, 4, 0,
384+
0, 0, 0, 0, 0, 0,
385+
12, 0, 0, 0,
386+
8, 0, 0, 0, 0, 0, 0, 0,
387+
2, 0, 0, 0, 65, 66,
388+
0, 0,
389+
4, 0, 4, 0,
390+
4, 0, 0, 0,
391+
]
392+
393+
var strictBuffer = ByteBuffer(bytes: bytes)
394+
#expect(throws: FlatbuffersErrors.self) {
395+
try getCheckedPrefixedSizeRoot(
396+
byteBuffer: &strictBuffer) as Swift_Tests_Vectors
397+
}
398+
399+
var prefixedBuffer = ByteBuffer(bytes: bytes)
400+
#expect(throws: FlatbuffersErrors.self) {
401+
try getPrefixedSizeCheckedRoot(
402+
byteBuffer: &prefixedBuffer) as Swift_Tests_Vectors
403+
}
404+
405+
var builder = FlatBufferBuilder()
406+
let movie = Movie.createMovie(&builder)
407+
Movie.finish(&builder, end: movie, prefix: true)
408+
var validBuffer = builder.sizedBuffer
409+
let _: Movie = try getCheckedPrefixedSizeRoot(
410+
byteBuffer: &validBuffer,
411+
fileId: Movie.id)
412+
413+
var shortBuffer = ByteBuffer(bytes: [0, 0, 0, 0])
414+
#expect(throws: FlatbuffersErrors.bufferDoesntContainID) {
415+
try getCheckedRoot(
416+
byteBuffer: &shortBuffer,
417+
fileId: Movie.id) as Movie
418+
}
419+
420+
var oversizedPrefix = ByteBuffer(bytes: [5, 0, 0, 0, 0, 0, 0, 0])
421+
#expect(throws: FlatbuffersErrors.outOfBounds(position: 9, end: 8)) {
422+
try getPrefixedSizeCheckedRoot(
423+
byteBuffer: &oversizedPrefix) as Movie
424+
}
425+
#expect(oversizedPrefix.reader == 0)
426+
}
427+
376428
@Test
377429
func testFullVerifier() throws {
378430
_ =

0 commit comments

Comments
 (0)