Ledger Ethereum Clear-Signing Bypass via Array-Count Truncation

Security advisory LEDGER_CLEAR_SIGNING_BYPASS, published by Bitfinding.

A compromised transaction frontend could make an affected Ledger display a benign action while signing a transaction that also authorized hidden token approvals. Bitfinding reproduced the display/signature mismatch on physical devices and demonstrated the resulting Safe authorization on a controlled mainnet fork. The issue is fixed in Ledger Ethereum app 1.22.3.

Advisory information

Field Value
Advisory ID LEDGER_CLEAR_SIGNING_BYPASS
Publication stage Advisory
Status Fixed in Ledger Ethereum app 1.22.3
Severity High (validated by Ledger)
Component Ledger Ethereum app Generic Transaction Parser (GTP)
Affected releases 1.19.0 through 1.22.2; Apex P packages begin at 1.19.3
Weakness CWE-197: Numeric Truncation Error
Security property Trusted-display integrity / What You See Is What You Sign
Researcher Felipe A. Manzano, Bitfinding
Last updated 2026-08-28
Disclosure Coordinated with Ledger
Vendor advisory Ledger Security Bulletin 024 (LSB-024)
Canonical publication BitFinding/publications/LEDGER_CLEAR_SIGNING_BYPASS.md

Executive summary

Ledger’s Ethereum Generic Transaction Parser stored a 16-bit array pass count derived from attacker-controlled calldata in an 8-bit countdown. When a descriptor requested traversal of all 257 elements, the value truncated to 1. The device could display only a benign suffix of an operation list while its signature authorized the complete list.

In Bitfinding’s controlled proof of concept, the Ledger screen presented a Clear Signed Send 10 USDC action. The signed payload also contained 256 preceding USDC.approve(attacker, MAX_UINT256) calls that were not shown on the trusted display.

Production exploitation requires a device-accepted Clear Signing descriptor for the target transaction shape. In Ledger’s default flow, its production metadata catalog supplies these signed descriptors. During Bitfinding’s research, the catalog contained array-backed CALLDATA descriptors for Aave, Morpho, and Safe BatchExecutor.

One concrete threat is a compromised Safe transaction frontend such as Ledger Enterprise Multisig. Clear Signing is intended to protect owners even when the browser is compromised. Malicious frontend code could send the same attacker-crafted EIP-712 SafeTx to each signer while every affected device displays only its benign final action. Once enough owners sign, any relayer can submit the complete malicious authorization.

Bitfinding confirmed the parser mismatch on physical Nano S Plus and Flex devices and separately produced a blockchain-usable Safe owner signature with an affected Speculos build. Safe executed that exact signed payload on a disposable mainnet fork. Bitfinding did not compromise a production frontend or reproduce a live multi-owner signing ceremony.

Separate production-context Apex P Speculos runs reproduced the same display/signature failure in direct Aave and Morpho transactions. The Aave device review showed Supply 10 USDC while its signature also covered an omitted maximum withdrawal to the attacker. The Morpho review showed Morpho Flash Loan 10 USDC while its signature also covered an omitted maximum withdrawal to the attacker. These two runs stopped at the display and signature layer; no Aave or Morpho transaction was broadcast or executed.

The demonstrated flow affects Ethereum app versions 1.19.0 through 1.22.2. Regression testing confirmed that 1.22.3 rejects it before review or signing.

Impact and threat model

This advisory centers on the compromised-frontend failure mode exposed by the 2025 Bybit incident. Ledger itself described independent hardware display and Clear Signing as defenses when a multisig web interface is compromised. Here, the attacker is code running inside a malicious or compromised Safe transaction UX, not someone who must approach each signer directly. During an ordinary signing ceremony, that code controls the authorization and Clear Signing material sent to each Ledger, and it collects the returned signatures.

In Ledger’s default integration, a host-side Context Module resolves contract-specific metadata that defines the intent, fields, labels, paths, and formatting shown on the device. The Ledger Enterprise Multisig flow observed by Bitfinding retrieved descriptors, certificates, and token metadata from Ledger API services; the relevant artifacts carried Ledger production signatures. This authorization prevents arbitrary unsigned metadata from being accepted, but it also places Ledger’s metadata service and signing authority inside the trusted display path. The device verifies who authorized the rendering instructions and then executes them with its parser; that verification does not independently establish that the resulting display completely describes the signed payload.

The Ledger screen is intended to be the independent verification boundary when the host cannot be trusted. This vulnerability breaks that boundary: the host can place security-relevant calls in omitted array entries, while each affected device reviews only the attacker-chosen final entry. Every signature remains valid for the complete payload.

The bug does not bypass the target’s existing authorization requirements. An attacker must still satisfy the applicable signature thresholds, account permissions, nonces, and independent policy controls. Instead, the vulnerability attacks informed consent: several apparently independent device confirmations can repeat the same incomplete rendering while authorizing the complete payload. The receiving account or protocol can verify the authorization, but cannot determine whether the hardware accurately described it.

The underlying flaw is generic: it affects GTP iteration whenever a descriptor causes the parser to traverse more than 255 array elements from attacker-controlled calldata. Safe’s BatchExecutor is the concrete, executable validation vehicle documented here. Separate Aave multicall and Morpho multicall runs validate the display/signature failure with production-signed metadata. Morpho reallocate reaches the same vulnerable array-backed parsing path but was not part of those retained protocol-specific runs. This is not a demonstrated memory-corruption issue.

Exploitability requirements

Production exploitation through Ledger’s default Clear Signing path requires:

In the production flows observed by Bitfinding, Ledger services supplied the descriptors and the relevant artifacts carried Ledger production signatures. The attacker does not need to forge this metadata. A legitimate descriptor defines how the device traverses and renders the transaction; attacker-controlled calldata supplies the array size and contents that make the selected traversal exceed 255 passes. For the demonstrated Safe payload, an array-backed CALLDATA field allowed each logical entry to contain a nested contract call, while repeated ABI offsets kept the 257-entry encoding within the device’s limits.

Bitfinding’s production metadata snapshot contained the required array-backed CALLDATA shape for these transaction families:

Catalog entry Selector Array field
Safe BatchExecutor batchExecute 0x1a833ee3 Transaction (Call[])
Aave multicall 0xac9650d8 Call (bytes[])
Morpho multicall 0x374f435d Action (Call[])
Morpho reallocate 0x803a7fba Action (Call[])

Safe BatchExecutor is the executable validation vehicle documented below. Aave and Morpho are separately validated through the display and signature layer only.

Affected releases

The vulnerable code is present in public source tags through 1.22.2. The exact nested-calldata proof of concept applies from 1.19.0, when the required PARAM_CALLDATA capability entered the release line.

Target Affected Ethereum app versions Fixed Ethereum app versions
Nano S Plus 1.19.0 through 1.22.2 >= 1.22.3
Nano X 1.19.0 through 1.22.2 >= 1.22.3
Flex 1.19.0 through 1.22.2 >= 1.22.3
Stax 1.19.0 through 1.22.2 >= 1.22.3
Apex P 1.19.3 through 1.22.2 >= 1.22.3
Nano S Not applicable Not applicable

Bitfinding rebuilt and tested all 62 applicable target/version configurations from the public app and device-package tags. Every configuration displayed only the final transfer while returning a valid signature over all 257 calls, including the 256 hidden approvals.

The emulator matrix validates tagged source and SDK behavior, not byte-for-byte equivalence with historical Ledger Manager packages. Test-only metadata flags did not change array parsing or transaction signing. Separate Nano S Plus and Flex hardware tests used production-signed descriptors.

Resolution and mitigation

Ledger released Ethereum app 1.22.3 on 2026-08-25 for Nano S Plus, Nano X, Flex, Stax, and Apex P. The public source tag points to d6d699bc and contains backport 451eea22, which rejects array pass counts greater than UINT8_MAX. Ledger’s production application catalog lists 1.22.3 for all five targets. Bitfinding independently regression-tested the tagged release and confirmed that it stops the vulnerable flow.

The original correction was merged into public develop through PR #1017 on 2026-05-05. Public tags 1.22.1 and 1.22.2 do not contain it and must not be treated as fixed releases.

Users should install Ethereum app 1.22.3 or later and verify the app version on the Ledger signer. Until an affected app can be updated:

Technical details and proof of concept

Root cause: numeric truncation

The vulnerable state stores an array countdown in eight bits:

typedef struct {
    uint8_t depth;
    uint8_t passes_remaining[MAX_ARRAYS];
    uint8_t index;
} s_arrays_info;

path_array() computes the pass count in a uint16_t and assigns it to that eight-bit field. The upstream 2c3d93cc correction shows the exact assignment and the added bounds check:

diff --git a/src/features/generic_tx_parser/gtp_data_path.c b/src/features/generic_tx_parser/gtp_data_path.c
index cd859424..1f6bb921 100644
--- a/src/features/generic_tx_parser/gtp_data_path.c
+++ b/src/features/generic_tx_parser/gtp_data_path.c
@@ -261,6 +261,9 @@ static bool path_array(const s_array_args *array,
     *offset += 1;
     if (arrays_info->index == arrays_info->depth) {
         // new depth
+        if (passes > UINT8_MAX) {
+            return false;
+        }
         arrays_info->passes_remaining[arrays_info->index] = passes;
         arrays_info->depth += 1;
     }

For a full traversal of a 257-element array:

passes = 257
passes_remaining = (uint8_t)257 = 1
idx = 0 + (257 - 1) = 256

The parser therefore processes one element, starting at the final element. The signature operation still covers the complete serialized transaction. The correction rejects pass counts that cannot fit in the countdown before the narrowing assignment occurs.

The truncation and resulting start index can be reproduced independently of a Ledger device with this complete C program:

#include <assert.h>
#include <stdint.h>

int main(void) {
    uint16_t passes = 257;
    uint8_t passes_remaining = passes;
    uint16_t first_index = passes - passes_remaining;

    assert(passes_remaining == 1);
    assert(first_index == 256);
    return 0;
}

Compile and run it with:

cc -std=c11 -Wall -Wextra -Werror truncation.c -o truncation
./truncation

Exploit construction

The demonstrated vehicle was Safe’s BatchExecutor batchExecute(Call[]) descriptor. The crafted batch contained:

A conventional encoding stores a separate dynamic tuple body for every call and produces 65,860 bytes. That does not fit the tested device flow. This calls for some offset thinking: dynamic array entries contain relative offsets to tuple bodies, and those offsets do not need to be unique. The PoC points the first 256 logical entries to one shared malicious tuple and the final entry to a separate benign tuple:

257-entry offset table

  calls[0]   --\
  calls[1]   ----+--> 0x2020: approve(attacker, MAX_UINT256)
      ...    ----+             one tuple body, referenced 256 times
  calls[255] --/

  calls[256] ------> 0x2100: transfer(vitalik, 10 USDC)
                               separate tuple, referenced once

  naive:   [257 offsets] [257 tuple bodies] = 65,860 bytes
  aliased: [257 offsets] [  2 tuple bodies] =  8,740 bytes

BatchExecutor still iterates 257 logical calls: entries 0 through 255 decode to the same malicious approval tuple, while entry 256 decodes to the benign transfer. Storing only two tuple bodies brings the calldata below the unchanged 16 KiB parser allocation. This offset-aliasing step is what makes the demonstrated payload practical on the tested signer.

The affected parser reviewed only array element 256. The device displayed the final 10 USDC transfer and returned a signature over the complete payload, including all 256 preceding approvals.

Compromised Safe frontend and executable authorization

The executable proof models the compromised-frontend pattern described in the threat model above and in public reporting on the 2025 Bybit incident. Malicious JavaScript inside an authorized Safe transaction UX chooses what is sent to each Ledger and collects the returned signatures.

For the demonstrated payload, the threat sequence is:

compromised Safe UX / injected JavaScript
        |
        | same malicious SafeTx hash
        +----> owner 1 Ledger: displays only "Send 10 USDC" --> signature 1
        +----> owner 2 Ledger: displays only "Send 10 USDC" --> signature 2
        +----> owner M Ledger: displays only "Send 10 USDC" --> signature M
                                                                    |
                                            threshold signatures ---+
                                                                    v
                                                  Safe.execTransaction
                                                                    |
                                  visible transfer + hidden approvals

This diagram describes the operational threat, not an end-to-end multi-owner test performed by Bitfinding. The research directly validates the single-owner authorization and execution boundary described below. A real M-of-N attack would require the compromised frontend to repeat the signing request until it obtained M owner signatures and to pass any independent Safe guards or policy controls.

This is the same deception pattern as the Bybit incident, not a claim that this vulnerability was used against Bybit. The historical attack used a different payload and implementation-takeover mechanism. Here, the validated payload creates an unlimited token allowance while preserving the expected transfer.

Separate supporting tests on physical Nano S Plus and Flex devices, using production-signed descriptors, confirmed the underlying 257-entry display/signature mismatch. Those tests validate the parser behavior and are not distributed as the attack PoC. The executable proof follows Safe’s actual authorization path: the compromised UX asks the owner to sign this EIP-712 SafeTx:

SafeTx field Signed value
to BatchExecutor
operation DELEGATECALL (1)
data The same aliased 257-entry batch

Under Safe’s documented transaction model, the owner signs the Safe authorization and an executor later submits the signed fields to Safe.execTransaction. The owner does not sign the executor’s outer Ethereum transaction.

Bitfinding validated that complete authorization and execution chain:

  1. An affected Apex P 1.21.3 build in Speculos, using the original parser and unchanged 16 KiB allocator, displayed only the final transfer and returned an EIP-712 SafeTx signature over the entire batch.
  2. Safe’s own getTransactionHash returned the exact signed hash 0x06c362e254679dfa33598e00480d768d12597dd031c3dd80cae6f517131c206f, and the signature recovered to the recorded Ledger test signer.
  3. An unrelated relayer submitted those same fields and that signature to a real Safe 1.4.1 proxy on a pinned Anvil mainnet fork.
  4. Safe accepted the signature and execTransaction succeeded. The receipt contained 256 undisplayed USDC Approval events, the displayed 10 USDC Transfer, and Safe’s ExecutionSuccess. The nonce advanced from 0 to 1, and the attacker’s allowance became uint256.max.

For the execution test, the disposable fork was configured with the recovered Ledger test address as its effective owner and threshold 1. That isolates the question the proof needed to answer: whether the misleading Ledger signature is a valid, executable Safe authorization. It does not represent a reproduced multi-owner signing ceremony.

This proves that the affected Ledger parsing path can produce a valid Safe owner authorization for the malicious batch and that Safe can execute that exact authorization in the required DELEGATECALL context.

The full-flow emulator build used test-only descriptor-signature, challenge, and memory-profiling flags while leaving the parser and allocator unchanged. This result does not claim a physical-device SafeTx replay with production-signed EIP-712 filtering context. No transaction was broadcast to a public network and no real funds moved.

Validation summary

Proof-of-concept availability

Bitfinding retains private PoCs for Safe, Aave, and Morpho transaction flows. These artifacts are not included in this advisory and no release schedule is announced.

Disclosure timeline

Dates are shown in YYYY-MM-DD format. The timeline records the material disclosure, remediation, and release milestones.

References

  1. Ledger Security Bulletin 024: Clear-signing bypass via array-count truncation
  2. Root-cause fix commit 2c3d93cc: Fix array iteration truncation
  3. Related GTP hardening commit 085880b0: Add overflow check in path_ref
  4. Fixed source tag 1.22.3
  5. Ledger production Manager catalog
  6. Ledger Devices Bug Bounty scope
  7. Ledger signer threat model
  8. Ledger Secure Display and Inputs
  9. Ledger User Consent
  10. Ledger: The Importance of the Trusted Display and Secure Execution
  11. ERC-7730
  12. Sygnia: Investigation of the 2025 Bybit compromise
  13. Ledger: Learning From The Bybit/Safe Attack
  14. Safe smart account technical overview