fix(core): solana transfer showing LUT address
What changed, and why it matters
This update fixes a display bug in Trezor's Solana token transfer screens. When a transaction destination was stored in an off-device 'address lookup table' (ALT), the device used to show the lookup table's own address as the recipient, instead of admitting it could not resolve the real destination. The fix makes the device fall back to a more honest, reference-aware display so users are not misled into approving transfers to the wrong address. This is a user-interface/confusion issue rather than a remote code execution flaw.
Users should install the firmware version containing this fix and continue to verify all transaction details on-device. Developers should review whether other predefined transaction types (e.g., SOL transfers, staking) have similar ALT-reference display assumptions.
Security signals we found
UI spoofing/misattribution: wrong recipient address shown to user
Address Lookup Table (ALT) reference not resolvable on-device
Changelog explicitly labels the change as a security fix
Cherry-picked from another commit, indicating backport of a fix
No privilege escalation, memory corruption, or remote exploit vector present in diff
Evidence from the diff
In core/src/apps/solana/predefined_transaction.py, is_predefined_token_transfer() now returns False when any token-transfer destination is an address reference (is_address_reference). That prevents the predefined-token-transfer UI path from being selected, because that path would render the lookup-table account address as the recipient. Falling back to the generic reference-aware display avoids showing an incorrect recipient. The change is purely UI/display logic; no cryptographic checks or transaction parsing boundaries were altered. Tests were added for ALT-referenced destinations, including mixed direct/reference cases.
Changed components
Trezor Core firmwareapps/solana/predefined_transaction.pySolana token transfer confirmation UIInspect captured patch +58 / −12
diff --git a/core/.changelog.d/286.security b/core/.changelog.d/286.security
new file mode 100644
index 00000000..e16d0a7b
--- /dev/null
+++ b/core/.changelog.d/286.security
@@ -0,0 +1 @@
+Fixed Solana token transfer showing the lookup-table address as the recipient for ALT-referenced destinations.
diff --git a/core/src/apps/solana/predefined_transaction.py b/core/src/apps/solana/predefined_transaction.py
index 09527fd6..bf952aaa 100644
--- a/core/src/apps/solana/predefined_transaction.py
+++ b/core/src/apps/solana/predefined_transaction.py
@@ -105,6 +105,11 @@ def is_predefined_token_transfer(
owner = transfer_token_instructions[0].owner[0]
for transfer_token_instruction in transfer_token_instructions:
+ if is_address_reference(transfer_token_instruction.destination_account):
+ # ALT-referenced destination can't be resolved on-device, fall back
+ # to the generic reference-aware display instead of showing the
+ # lookup table address as the recipient.
+ return False
if (
transfer_token_instruction.program_id != token_program
or transfer_token_instruction.token_mint[0] != token_mint
diff --git a/core/tests/test_apps.solana.predefined_transaction.py b/core/tests/test_apps.solana.predefined_transaction.py
index d0ada56f..0ef97c09 100644
--- a/core/tests/test_apps.solana.predefined_transaction.py
+++ b/core/tests/test_apps.solana.predefined_transaction.py
@@ -7,6 +7,7 @@ if not utils.BITCOIN_ONLY:
from apps.solana.predefined_transaction import is_predefined_token_transfer
from apps.solana.transaction.instruction import Instruction
+ from apps.solana.types import AddressType
SYSTEM_PROGRAM_ID = "11111111111111111111111111111111"
STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111"
@@ -115,20 +116,30 @@ def create_mock_instruction(
return instruction
+def create_account(address, is_reference=False):
+ # direct address: (pubkey, type); ALT reference: (table_address, index, type)
+ if is_reference:
+ return (base58.decode(address), 0, AddressType.AddressRw)
+ return (base58.decode(address), AddressType.AddressRw)
+
+
def create_transfer_token_instruction(
program_id=TOKEN_PROGRAM_ID,
instruction_id=TOKEN_PROGRAM_ID_INS_TRANSFER_CHECKED,
token_mint="GHArwcWCuk9WkUG4XKUbt935rKfmBmywbEWyFxdH3mou",
destination_account="92YgwqTtTWB7qY92JT6mbL2WCmhAs7LPZL4jLcizNfwx",
owner="14CCvQzQzHCVgZM3j9soPnXuJXh1RmCfwLVUcdfbZVBS",
+ destination_is_reference=False,
):
return create_mock_instruction(
program_id,
instruction_id,
{
- "token_mint": (base58.decode(token_mint),),
- "destination_account": (base58.decode(destination_account),),
- "owner": (base58.decode(owner),),
+ "token_mint": create_account(token_mint),
+ "destination_account": create_account(
+ destination_account, destination_is_reference
+ ),
+ "owner": create_account(owner),
},
)
@@ -142,9 +153,9 @@ def create_create_token_account_instruction(
ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID_INS_CREATE,
{
- "token_mint": (base58.decode(token_mint),),
- "associated_token_account": (base58.decode(associated_token_account),),
- "spl_token": (base58.decode(spl_token),),
+ "token_mint": create_account(token_mint),
+ "associated_token_account": create_account(associated_token_account),
+ "spl_token": create_account(spl_token),
},
)
@@ -270,6 +281,15 @@ class TestSolanaPredefinedTransactions(unittest.TestCase):
create_transfer_token_instruction(),
create_create_token_account_instruction(),
],
+ # ALT-referenced destination, can't be resolved on-device
+ [
+ create_transfer_token_instruction(destination_is_reference=True),
+ ],
+ # one direct and one ALT-referenced destination
+ [
+ create_transfer_token_instruction(),
+ create_transfer_token_instruction(destination_is_reference=True),
+ ],
]
for instructions in valid_test_cases:
diff --git a/core/tests/test_apps.solana.predefined_transaction.py.mako b/core/tests/test_apps.solana.predefined_transaction.py.mako
index 16f1543c..f5420391 100644
--- a/core/tests/test_apps.solana.predefined_transaction.py.mako
+++ b/core/tests/test_apps.solana.predefined_transaction.py.mako
@@ -9,6 +9,7 @@ if not utils.BITCOIN_ONLY:
from apps.solana.predefined_transaction import is_predefined_token_transfer
from apps.solana.transaction.instruction import Instruction
+ from apps.solana.types import AddressType
% for program in programs["programs"]:
${getProgramId(program)} = "${program["id"]}"
@@ -44,20 +45,30 @@ def create_mock_instruction(
return instruction
+def create_account(address, is_reference=False):
+ # direct address: (pubkey, type); ALT reference: (table_address, index, type)
+ if is_reference:
+ return (base58.decode(address), 0, AddressType.AddressRw)
+ return (base58.decode(address), AddressType.AddressRw)
+
+
def create_transfer_token_instruction(
program_id=TOKEN_PROGRAM_ID,
instruction_id=TOKEN_PROGRAM_ID_INS_TRANSFER_CHECKED,
token_mint="GHArwcWCuk9WkUG4XKUbt935rKfmBmywbEWyFxdH3mou",
destination_account="92YgwqTtTWB7qY92JT6mbL2WCmhAs7LPZL4jLcizNfwx",
owner="14CCvQzQzHCVgZM3j9soPnXuJXh1RmCfwLVUcdfbZVBS",
+ destination_is_reference=False,
):
return create_mock_instruction(
program_id,
instruction_id,
{
- "token_mint": (base58.decode(token_mint),),
- "destination_account": (base58.decode(destination_account),),
- "owner": (base58.decode(owner),),
+ "token_mint": create_account(token_mint),
+ "destination_account": create_account(
+ destination_account, destination_is_reference
+ ),
+ "owner": create_account(owner),
},
)
@@ -71,9 +82,9 @@ def create_create_token_account_instruction(
ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID_INS_CREATE,
{
- "token_mint": (base58.decode(token_mint),),
- "associated_token_account": (base58.decode(associated_token_account),),
- "spl_token": (base58.decode(spl_token),),
+ "token_mint": create_account(token_mint),
+ "associated_token_account": create_account(associated_token_account),
+ "spl_token": create_account(spl_token),
},
)
@@ -199,6 +210,15 @@ class TestSolanaPredefinedTransactions(unittest.TestCase):
create_transfer_token_instruction(),
create_create_token_account_instruction(),
],
+ # ALT-referenced destination, can't be resolved on-device
+ [
+ create_transfer_token_instruction(destination_is_reference=True),
+ ],
+ # one direct and one ALT-referenced destination
+ [
+ create_transfer_token_instruction(),
+ create_transfer_token_instruction(destination_is_reference=True),
+ ],
]
for instructions in valid_test_cases:
Why this scored 63/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.