Skip to content

transformERC20 is exempt from the data_total binding and signs an undisclosed calldata tail #444

Description

@BitHighlander

What is wrong

#413 added a central binding so a contract decoder cannot claim a transaction whose calldata exceeds the initial chunk:

// lib/firmware/ethereum_contracts.c
67:  if (data_total != msg->data_initial_chunk.size) return false;

transformERC20 is hoisted above it and exempt:

60:  if (zx_isZxTransformERC20(msg)) return true;

and the decoder still discards the total:

// lib/firmware/ethereum_contracts/zxtransERC20.c:57-58
bool zx_confirmZxTransERC20(uint32_t data_total, const EthereumSignTx* msg) {
  (void)data_total;

When any decoder claims a transaction, lib/firmware/ethereum.c:720-728 clears both needs_confirm and data_needs_confirm, and ethereum.c:693 admits data_length up to 16,000,000 streamed into the hash. So the tail is signed and never shown.

Why the exemption does not hold

The in-code justification is that the outcome is "bounded by the input amount and minimum output amount shown on screen". That is an off-device invariant — it is the 0x ExchangeProxy's own min-output check, not anything this firmware verifies.

It also rests entirely on msg->to matching ZXSWAP_ADDRESS on an allowlisted chain (ethereum_contracts.c:38-45), and #441 records that allowlist as unverified against 0x's published deployments. So the firmware is trusting a contract's behaviour, at an address it has not confirmed is that contract, on chains it has not confirmed the contract exists on.

Reachable today

A host that sets to = 0xdef1c0ded9bec7f1a1670819833240f027b25eff, chain_id in {1, 56, 137, 8453, 42161, 43114}, selector 0x415565b0, and data_length well beyond data_initial_chunk.size gets #413's exact stated harm: a recognised, benign-looking call carrying arbitrary appended calldata that is hashed and signed after the user has approved.

Why this is filed separately

#413's commit (d674adf30) carries a Closes #413 trailer and states the residual in its own body. Without this issue, merging would auto-close #413 and this defect would leave the tracker entirely.

#400 does not cover it. That issue is about the generic layoutEthereumData path in ethereum.c — the screen a user reaches when no decoder claims the transaction. This is a decoder that does claim it and shows no raw data at all.

Options

  1. Remove the exemption. transformERC20 falls to the generic disclosure when it does not fit one chunk — consistent with every other decoder, at the cost of a raw-hex screen for large 0x swaps.
  2. Keep clear-signing but bind the tail: hash the streamed remainder and display a commitment the user can compare, or assert the decoded transformations[] covers exactly data_total bytes.
  3. Keep the exemption deliberately, but only once Verify the 0x Exchange Proxy chain allowlist against 0x's published deployment data #441 is closed — the argument depends on the address and chain being what the firmware assumes.

Option 1 is the smallest and fails closed. Option 3 is not viable on its own while #441 is open.

How to verify

git show release/7.14.2:lib/firmware/ethereum_contracts.c | sed -n '52,70p'
git show release/7.14.2:lib/firmware/ethereum_contracts/zxtransERC20.c | sed -n '55,60p'
git show release/7.14.2:lib/firmware/ethereum.c | sed -n '690,730p'

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    evmEthereum / EVM signingsecuritySecurity-relevant defect or hardening

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions