fix(solana): disclose signed account identities on clear-sign screens - #591
fix(solana): disclose signed account identities on clear-sign screens#591BitHighlander wants to merge 3 commits into
Conversation
Verified-tier Solana clear-sign screens omitted signed account identities the parser already extracted, letting a compromised host substitute the funding/destination/mint/authority account while showing an identical device prompt. Also fixes two account-index bugs and a missing bounds check that let short account lists silently zero-fill identities (which base58-encodes to the real System Program address, so it did not look obviously fake on screen). lib/firmware/solana.c: - Stake/Vote Authorize read the current-authority signer from account index 1, which is the Clock sysvar in both canonical layouts, not the signer at index 2. Sibling instructions in the same file (Deactivate, Merge, Withdraw) already used the correct index. - Add num_acct_indices bounds checks to every verified-tier branch that was missing one (System nonce/assign/allocate, SPL Revoke/MintTo/Burn/ CloseAccount/Freeze/Thaw/SyncNative, all of Stake and Vote, ATA Create), falling through to SOL_INSTR_UNKNOWN + has_unknown=true (forces the whole tx opaque) on a short list, mirroring the one branch (TransferChecked) that already guarded this. lib/firmware/fsm_msg_solana.h: - Show the previously-hidden identity on every affected instruction screen: System transfer/nonce-ops/assign/allocate (funding/nonce/ affected account), SPL TransferChecked (source) and Revoke/Burn/ CloseAccount/Freeze/Thaw/SyncNative (affected account + mint where applicable), Stake Delegate/Withdraw/Authorize/Split/Deactivate/Merge (stake/vote accounts), Vote Authorize/Withdraw/UpdateValidator/ UpdateCommission (vote account), ATA Create (payer/address/owner/mint). CloseAccount is the sharpest case: the rent-refund destination was never shown, so a host could redirect it silently. - MintTo/Burn now scale by the signed decimals byte (pi->extra_u8) via the already-tested solana_formatTokenAmount(), instead of rendering the raw integer -- matching TransferChecked, which already did this. unittests/firmware/solana.cpp: 9 new regression tests -- both account-index fixes (signer is index 2, not the sysvar at index 1), the CloseAccount critical case (short-accounts opaque + canonical reveals both accounts), and representative short-accounts-is-opaque coverage across System/SPL/ Stake/ATA proving the new bounds checks reach even the deepest index (StakeDelegate, index 5). Verified locally: built the emulator Docker image (scripts/emulator/Dockerfile) and ran the full firmware-unit suite -- all 53 Solana tests (44 existing + 9 new) pass. clang-format and cppcheck both clean. 3 pre-existing failures elsewhere in the suite (Coins. TableSanity, Ethereum.TransformErc20*) are in files this change does not touch and are unrelated (tracked separately, e.g. #473). Fixes #558, #559, #560, #588.
Points at BitHighlander/python-keepkey#58 (merged 2bf87908), which updates the Stake instruction integration tests to canonical Solana account layouts matching the parser fixes in this PR.
…earsign-identities
|
Do not merge current head d7a9da0: issue #595 identifies an exact remaining ATA bypass. The parser accepts num_acct_indices >= 3 but mint is index 3, so a three-account ATA Create remains VERIFIED, zero-fills/omits the mint, and defeats the claimed disclosure. The added test uses only two accounts and misses this off-by-one. Require >=4, set has_mint=true, and test 3 => OPAQUE / 4 => all four identities preserved. PR #587 already contains that exact guard and regression. |
|
PR #591 still does not fully remediate this issue at head d7a9da0. Both MintToChecked and BurnChecked enter the verified branch at data_len >= 9, but their signed decimals byte is at offset 9 and therefore requires 10 bytes. A 9-byte truncated Checked instruction is still VERIFIED and displayed with fabricated decimals=0. The same >= checks also admit trailing instruction bytes, while the parser ignores them. The unchecked opcodes have no signed decimals at all, yet the shared display always calls solana_formatTokenAmount(..., extra_u8) with zero and labels the raw base-unit integer as tokens. Required:
PR #587 already implements and tests these exact boundaries. Do not close #560 from the current #591 head. |
|
Acknowledged — verified both findings directly against this branch's diff:
PR #587 is already 21 commits ahead of alpha / 0 behind (includes this fork's #589/#590/#593), and its Solana diff subsumes everything in this PR plus these two additional guards. Closing this PR without merging in favor of #587 rather than duplicating/reconciling two overlapping Solana diffs. Not closing #558/#559/#560/#588 from here — leaving that to #587's merge. |
Problem
A systematic parser-to-prompt comparison found that several verified-tier
Solana clear-sign screens omitted signed account identities the parser
already extracted, letting a compromised host substitute the account being
funded, closed, delegated, reassigned, or authorized while presenting an
identical "verified" device prompt. Two additional, independent defects
compound this:
signer was read from account index 1, which is the Clock sysvar in
both canonical layouts (
[0]=account, [1]=Clock sysvar, [2]=[SIGNER] authority), not the actual signer at index 2. Sibling instructions inthe same file (Deactivate, Merge, Withdraw) already used the correct
index.
SOL_TX_REVIEW_VERIFIEDwithout checking the instruction actuallylisted enough accounts. A short account list makes
copy_account()silently leave an identity zero-filled -- which base58-encodes to
11111111111111111111111111111111, the real Solana System Programaddress, so a short-listed, spoofed transaction did not look obviously
fake on screen.
Fixes #558, #559, #560, #588.
Fix
lib/firmware/solana.cnum_acct_indices >= Nbounds checks to every verified-tier branchthat was missing one (System nonce/assign/allocate, SPL Revoke/MintTo/
Burn/CloseAccount/Freeze/Thaw/SyncNative, all of Stake and Vote, ATA
Create). A short list now falls through to
SOL_INSTR_UNKNOWNwithhas_unknown = true, which forces the whole transaction opaque(AdvancedMode) instead of clear-signing a fabricated identity --
mirroring the one branch (TransferChecked) that already guarded this.
lib/firmware/fsm_msg_solana.hauthorize (nonce account), assign/allocate (affected account)
SyncNative (affected account + mint where applicable)
accounts)
account)
never shown, so a host could redirect it while displaying only "Close
token account?"
pi->extra_u8) viathe already-tested
solana_formatTokenAmount(), instead of a rawinteger -- matching TransferChecked, which already did this correctly.
unittests/firmware/solana.cpp: 9 new regression tests --the sysvar at index 1.
both
from/tocorrectly extracted.Stake/ATA, including StakeDelegate (index 5, the deepest of any
instruction here) to prove the bounds check isn't only reaching the
shallow indices.
Verification
scripts/emulator/Dockerfile) and ranthe full
firmware-unitsuite locally: all 53 Solana tests pass(44 pre-existing + 9 new).
clang-format --dry-run --Werrorclean on all three changed files.cppcheck(same flags as CI's static-analysis job) clean onsolana.c.(
Coins.TableSanity,Ethereum.TransformErc20RequiresCompleteCalldata ForClearSigning,Ethereum.TransformErc20RequiresBothTokensResolvable)are in files this change does not touch (confirmed via
git diff --name-only) and are unrelated -- pre-existing on alpha's tipindependent of this branch.
minimums before pushing; none needed fewer accounts than the new
bounds require, so no existing test needed adjustment.