mirror of
https://github.com/google/flatbuffers.git
synced 2026-11-07 20:59:10 +00:00
[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:
co-authored by
Darren Carreras
parent
81edeb17d9
commit
5761d6e67a
@@ -16,6 +16,30 @@
|
|||||||
|
|
||||||
import Foundation
|
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.
|
/// 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.
|
/// And would verify that the buffer passed is a valid `Flatbuffers` Object.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@@ -31,6 +55,10 @@ public func getPrefixedSizeCheckedRoot<T: FlatBufferTable & Verifiable>(
|
|||||||
fileId: String? = nil,
|
fileId: String? = nil,
|
||||||
options: VerifierOptions = .init()) throws -> T
|
options: VerifierOptions = .init()) throws -> T
|
||||||
{
|
{
|
||||||
|
try verifySizePrefix(
|
||||||
|
byteBuffer: &byteBuffer,
|
||||||
|
requireExactSize: false,
|
||||||
|
options: options)
|
||||||
byteBuffer.skipPrefix()
|
byteBuffer.skipPrefix()
|
||||||
return try getCheckedRoot(
|
return try getCheckedRoot(
|
||||||
byteBuffer: &byteBuffer,
|
byteBuffer: &byteBuffer,
|
||||||
@@ -53,10 +81,11 @@ public func getCheckedPrefixedSizeRoot<T: FlatBufferTable & Verifiable>(
|
|||||||
fileId: String? = nil,
|
fileId: String? = nil,
|
||||||
options: VerifierOptions = .init()) throws -> T
|
options: VerifierOptions = .init()) throws -> T
|
||||||
{
|
{
|
||||||
let prefix = byteBuffer.skipPrefix()
|
try verifySizePrefix(
|
||||||
if prefix != byteBuffer.size {
|
byteBuffer: &byteBuffer,
|
||||||
throw FlatbuffersErrors.prefixedSizeNotEqualToBufferSize
|
requireExactSize: true,
|
||||||
}
|
options: options)
|
||||||
|
byteBuffer.skipPrefix()
|
||||||
return try getCheckedRoot(
|
return try getCheckedRoot(
|
||||||
byteBuffer: &byteBuffer,
|
byteBuffer: &byteBuffer,
|
||||||
fileId: fileId,
|
fileId: fileId,
|
||||||
@@ -95,14 +124,15 @@ public func getCheckedRoot<T: FlatBufferTable & Verifiable>(
|
|||||||
options: VerifierOptions = .init()) throws -> T
|
options: VerifierOptions = .init()) throws -> T
|
||||||
{
|
{
|
||||||
var verifier = try Verifier(buffer: &byteBuffer, options: options)
|
var verifier = try Verifier(buffer: &byteBuffer, options: options)
|
||||||
|
let rootPosition = byteBuffer.reader
|
||||||
if let fileId = fileId {
|
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(
|
return T.init(
|
||||||
byteBuffer,
|
byteBuffer,
|
||||||
o: Int32(byteBuffer.read(def: UOffset.self, position: byteBuffer.reader))
|
o: Int32(byteBuffer.read(def: UOffset.self, position: rootPosition))
|
||||||
&+ Int32(byteBuffer.reader))
|
&+ Int32(rootPosition))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a `NON-Checked` flatbuffers object
|
/// Returns a `NON-Checked` flatbuffers object
|
||||||
|
|||||||
@@ -215,12 +215,16 @@ public struct Verifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
func verify(id: String) throws {
|
func verify(id: String, at position: Int) throws {
|
||||||
let size = MemoryLayout<Int32>.size
|
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
|
throw FlatbuffersErrors.bufferDoesntContainID
|
||||||
}
|
}
|
||||||
let str = _buffer.readString(at: size, count: size)
|
let str = _buffer.readString(at: position &+ size, count: size)
|
||||||
if id == str {
|
if id == str {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
@Test
|
||||||
func testFullVerifier() throws {
|
func testFullVerifier() throws {
|
||||||
_ =
|
_ =
|
||||||
|
|||||||
Reference in New Issue
Block a user