fix(interface): validate Borsh string lengths in unpack() to prevent SBF heap abort#87
Conversation
…SBF heap abort Forged u32 length prefixes in Initialize/UpdateField/RemoveKey payloads could trigger an oversized allocation in try_from_slice that aborts on the 32 KiB SBF heap. Validate each string length against the remaining buffer before deserializing, returning InvalidInstructionData for malformed payloads. Closes solana-program/token-2022#1152 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The doc comment on check_borsh_string uses both words; cargo spellcheck flagged them. They're a correct word and a known acronym (SBF sits alongside TLV, APY, DAO already in the dict). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@joncinque this is the |
|
You just |
|
ah i see, thankyou. makes sense, the re-run from my spellcheck push already pulled in the quinn-proto fix, so Audit's green now. my apologies for the extra issue. all checks pass on this one now |
joncinque
left a comment
There was a problem hiding this comment.
Thanks for your contribution!
It looks a bit hacky, but unless we switch to another serialization library like wincode, which allows you to specify max allocation sizes during deserialization, I don't think we have any better options.
Problem
TokenMetadataInstruction::unpack()callstry_from_slice(rest)?directly on attacker-controlled instruction data. A BorshStringis a little-endianu32length prefix followed by that many bytes, so a forged prefix likeu32::MAXmakes Borsh try to allocate ~1 MiB before the?error path runs and discovers the buffer is too short. On the 32 KiB SBF heap that allocation aborts the program instead of returning a clean error.Only the variants that carry Borsh strings are affected:
Initialize(name, symbol, uri),UpdateField(thevaluestring, plus the nested string inField::Key, tag 3), andRemoveKey(thekeystring after theidempotentbool).UpdateAuthorityandEmithave no strings and are left alone.Fix
A new
check_borsh_stringhelper reads each string's declared length and checks it against the remaining buffer beforetry_from_sliceruns, returningInvalidInstructionDatawhen it doesn't fit. The three affected match arms walk a cursor through their fields and validate each string first. The length arithmetic is checked so it can't overflow.Tests
Three tests feed a forged
u32::MAXlength prefix throughunpack()forInitialize,UpdateField, andRemoveKeyand assertInvalidInstructionData. They run natively, no SBF build needed. They also work as real regression guards: before the fix Borsh returns a different (IO) error, so the assertion would fail.Context
This puts the validation in
spl-token-metadata-interface, where the data is deserialized, rather than in the token-2022 caller. It follows the discussion on solana-program/token-2022#1194 (now closed), where @joncinque suggested the fix should live here. References solana-program/token-2022#1152. The token-2022 side is then just a dependency bump once this releases.🤖 Generated with Claude Code