fix(solana): check owner in predefined flow
What changed, and why it matters
This update tightens a Solana staking flow in Trezor firmware so that the device now verifies the staking account is owned by the legitimate Stake program before showing the user a friendly confirmation screen. Previously, a crafted transaction with a different owner could slip through the predefined staking prompt, though the Solana network itself would likely reject it. The change also fixes a missing display option in the claim flow.
No immediate user action required; ensure firmware is updated to a release containing this commit. Developers should review whether other predefined flows have similar owner/program checks.
Security signals we found
Missing input validation in a predefined transaction confirmation path
UI prompt could be shown for a transaction with an unexpected program owner
Defense-in-depth check added for Solana staking account owner
Secondary fix: missing argument passed to recipient confirmation UI
Evidence from the diff
The patch adds an owner check in try_confirm_staking_transaction() in core/src/apps/solana/predefined_transaction.py: it now returns False (falling back to generic transaction confirmation) if the create instruction’s owner is not the Stake program ID. Test fixtures were updated to use the correct Stake program owner and regenerated expected signatures. A secondary fix passes chunkify to confirm_claim_recipient().
Changed components
core/src/apps/solana/predefined_transaction.pycommon/tests/fixtures/solana/sign_tx.staking_transactions.jsonInspect captured patch +11 / −7
diff --git a/common/tests/fixtures/solana/sign_tx.staking_transactions.json b/common/tests/fixtures/solana/sign_tx.staking_transactions.json
index 892756c4..ea715c4e 100644
--- a/common/tests/fixtures/solana/sign_tx.staking_transactions.json
+++ b/common/tests/fixtures/solana/sign_tx.staking_transactions.json
@@ -52,7 +52,7 @@
},
"lamports": 20000000,
"space": 0,
- "owner": "11111111111111111111111111111111"
+ "owner": "Stake11111111111111111111111111111111111111"
}
},
{
@@ -89,7 +89,7 @@
}
},
"result": {
- "expected_signature": "d38bf15aefa994ec712ae801ce35c6ebd54ef1df46a52e8577376baefefb4e842261e7d4c39f465a38caf9520cbbbd59a94313ccbe4c55ca16721f2f0674af0d"
+ "expected_signature": "a04d014846ded6ae99271f082492550dcb65bd8876634fe6713c49ce19b1f09e32e0866a3efd1decfc0e6b0935a7c95476b2b646e47f529e3e7a76909d17bb09"
}
},
{
@@ -140,7 +140,7 @@
},
"lamports": 20000000,
"space": 0,
- "owner": "11111111111111111111111111111111"
+ "owner": "Stake11111111111111111111111111111111111111"
}
},
{
@@ -177,7 +177,7 @@
}
},
"result": {
- "expected_signature": "479530b87f2bf7b56c8894a1a842531837635b5aa2f9f17b875ef768b23190e97e0079c43b3981f06d20718f0dcd0f1b218557b16a525021b066f42f8d98510c"
+ "expected_signature": "31beb28077266f9c58aef5d081c20740629b5b3f92100cef807b50d80e2983275a0f50c691fcb9a7a7015ff95497376e10da1577e2acfb6458e9a4bd260d2c0b"
}
},
{
@@ -228,7 +228,7 @@
},
"lamports": 20000000,
"space": 0,
- "owner": "11111111111111111111111111111111"
+ "owner": "Stake11111111111111111111111111111111111111"
}
},
{
@@ -265,7 +265,7 @@
}
},
"result": {
- "expected_signature": "e4bbe5bec49dbc9d3cdeaa00612facb67e7b1d1b2446600d6c0cddc61a89da4343d62be788ea72734b4a6818471eaf1a37fbbad34b33e7b6cdfe8c705c8c8701"
+ "expected_signature": "4e40dec9765719cd36ba8e882e6a41399f424e48e3a4697f36ef4aec94bad16c7ab961ac75f7ee6de3b7d76f30aa0e694a8a94878226431a1ffc79f1c204e701"
}
},
{
diff --git a/core/src/apps/solana/predefined_transaction.py b/core/src/apps/solana/predefined_transaction.py
index a5c210ab..09527fd6 100644
--- a/core/src/apps/solana/predefined_transaction.py
+++ b/core/src/apps/solana/predefined_transaction.py
@@ -5,6 +5,7 @@ from trezor.wire import ProcessError
from .transaction import Transaction
from .transaction.instructions import (
+ _STAKE_PROGRAM_ID,
_SYSTEM_PROGRAM_ID,
AssociatedTokenAccountProgramCreateInstruction,
Instruction,
@@ -298,6 +299,9 @@ async def try_confirm_staking_transaction(
from .layout import confirm_stake_transaction, confirm_stake_withdrawer
create, init, delegate = instructions
+ if base58.encode(create.owner) != _STAKE_PROGRAM_ID:
+ return False
+
if signer_public_key != create.funding_account[0]:
return False
if signer_public_key != create.base:
@@ -360,7 +364,7 @@ async def try_confirm_staking_transaction(
total_amount += withdraw.lamports
if recipient != signer_public_key:
- await confirm_claim_recipient(recipient)
+ await confirm_claim_recipient(recipient, chunkify)
await confirm_claim_transaction(
fee=fee,
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.