Skip to content

[Swift] Read vtable field slots as unsigned VOffset to match the verifier - #9202

Open
bianyeyu wants to merge 1 commit into
google:masterfrom
bianyeyu:swift-read-vtable-slots-unsigned
Open

[Swift] Read vtable field slots as unsigned VOffset to match the verifier#9202
bianyeyu wants to merge 1 commit into
google:masterfrom
bianyeyu:swift-read-vtable-slots-unsigned

Conversation

@bianyeyu

Copy link
Copy Markdown

Problem

TableVerifier.dereference validates vtable field slots as VOffset (UInt16) and bounds-checks table position + slot. The accessors re-read the same slots as Int16. A buffer accepted by getCheckedRoot verification can therefore carry a slot such as 0x8000: the verifier treats it as +32768 (in bounds), while Table.offset interprets it as -32768, steering field accessors before the start of the verified buffer. Because assert() is compiled out in release builds and ByteBuffer reads/writes are unchecked, this is a genuine out-of-bounds access on a fully verified buffer - both reads through field accessors and writes through mutate.

The same divergence exists in the two static Table.offset(_:vOffset:fbb:) overloads used by generated lookupByKey / sortVectorOf* code; the ByteBuffer overload is reachable on externally supplied, verified buffers through public APIs such as scalarKeySortedTablesBy(key:).

Fix

Read the slot as VOffset in all three places so the accessors observe exactly the displacement the verifier validated. No per-access bounds checks are added (consistent with #6209); this is the accessor-side counterpart of the VOffset-typed verifier introduced in #8577.

Slots >= 0x8000 are format-legal (tables with more than 32KB of inline field data); they verify today but are misread by accessors, and this change makes them read correctly. Slots <= 0x7FFF are bit-identical between Int16 and VOffset, so existing buffers behave exactly as before.

Tests

Three crafted-buffer regression tests in FlatbuffersVerifierTests verify a buffer first, then assert the accessors reach the verified field positions:

  • read path: vector field whose vtable slot is 0x8000
  • write path: mutate on the same crafted slot
  • generated lookupByKey path: sorted-table vector on Monster

Without this change all three crash with out-of-bounds accesses; with it they pass, and the full Swift suite is green (97 tests).

Related: the analogous Kotlin sign-extension issue is tracked in #8990 (C-08). This PR covers the Swift runtime only; the verifier is unchanged.

@google-cla

google-cla Bot commented Aug 20, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@bianyeyu

Copy link
Copy Markdown
Author

@googlebot I signed it!

…fier

TableVerifier.dereference validates vtable field slots as VOffset
(UInt16), but the accessors re-read the same slots as Int16. A slot of
0x8000 is +32768 for the verifier (accepted when in bounds) but -32768
for the accessors, steering reads and writes before the start of a
verified buffer. Read the slot as VOffset in Table.offset and the two
static Table.offset(_:vOffset:fbb:) overloads used by generated
lookupByKey/sortVectorOf code, so accessors observe the same
displacement the verifier validated. Slots >= 0x8000 are format-legal
for tables with more than 32KB of inline field data and are handled
correctly after this change; slots <= 0x7FFF are bit-identical.

Adds crafted-buffer regression tests for the read, mutate, and
lookupByKey paths.
@bianyeyu
bianyeyu force-pushed the swift-read-vtable-slots-unsigned branch from 8e692a8 to 052de38 Compare August 20, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant