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:
- a descriptor accepted by the device and containing an
ARRAYdata path; - a matching attacker-controlled ABI array and a descriptor-selected range that requires more than 255 parser passes; and
- security-relevant actions in the omitted entries, with an encoded payload that fits the device’s parser limits.
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:
- do not rely on affected clear-signing flows as the sole approval control for high-value transactions;
- independently decode and simulate transactions;
- enforce policy checks on destination contracts, selectors, values, token movements, and expected state changes; and
- verify the Ethereum app version, not only the device firmware version.
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:
- 256 preceding
USDC.approve(attacker, MAX_UINT256)calls; and - one final
USDC.transfer(vitalik, 10 USDC)call.
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:
- An affected Apex P
1.21.3build in Speculos, using the original parser and unchanged 16 KiB allocator, displayed only the final transfer and returned an EIP-712SafeTxsignature over the entire batch. - Safe’s own
getTransactionHashreturned the exact signed hash0x06c362e254679dfa33598e00480d768d12597dd031c3dd80cae6f517131c206f, and the signature recovered to the recorded Ledger test signer. - An unrelated relayer submitted those same fields and that signature to a real Safe 1.4.1 proxy on a pinned Anvil mainnet fork.
- Safe accepted the signature and
execTransactionsucceeded. The receipt contained 256 undisplayed USDCApprovalevents, the displayed 10 USDCTransfer, and Safe’sExecutionSuccess. The nonce advanced from0to1, and the attacker’s allowance becameuint256.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
- Historical matrix: All 62 applicable target/version configurations rebuilt from public app and device-package tags displayed only the final transfer and signed all 257 calls. These are controlled Speculos builds, not byte-for-byte copies of historical Manager packages.
- Physical devices: Nano S Plus and Flex running Ethereum app
1.21.3, with production-signed descriptors, confirmed the 257-entry display/signature mismatch. These tests corroborate the parser behavior but are not the distributed attack PoC. - Aave production context: The affected Apex P
1.21.3app under Speculos production-PKI validation displayedSupply 10 USDCand omitted a precedingwithdraw(USDC, MAX_UINT256, attacker). The returned signature was verified over the complete 257-entry direct Aave transaction. No Aave transaction was broadcast or executed. - Morpho production context: An independent run with the same affected app
displayed
Morpho Flash Loan 10 USDCand omitted a precedingmorphoWithdraw(..., MAX_UINT256, attacker). The returned signature was verified over the complete 257-entry direct Morpho transaction. No Morpho transaction was broadcast or executed. - Safe authorization: An Apex P
1.21.3Speculos build with the unchanged parser and 16 KiB allocator signed EIP-712SafeTxdata covering all 257 calls after displaying only the final transfer. The build used test-only metadata and profiling flags. - Safe execution: A Safe
1.4.1proxy on a pinned Anvil mainnet fork accepted the recorded signature.execTransactionemitted 256 approvals, one transfer, andExecutionSuccess. This was a threshold-one laboratory test, not a reproduced multi-owner ceremony; no public-network transaction or real funds were involved. - Fixed-version regression: The exact
1.22.3tagged parser rejected the same flow with status0x6a80, before misleading review or signing.
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.
- 2026-03-28 (Email): Bitfinding submits the encrypted report and PoC.
- 2026-03-31 (Email): Ledger acknowledges receipt.
- 2026-04-23 (Email): Ledger validates exploitability, classifies the report High, and begins remediation coordination with Bitfinding.
- 2026-05-04 (Email): The parties coordinate an early-September public disclosure window.
- 2026-05-05 (GitHub):
PR #1017merges the correction into publicdevelop. - 2026-08-13 (GitHub): Public source tag
1.22.2is finalized without the correction. - 2026-08-23 (Email): Bitfinding warns that the correction is absent
from
1.22.2, asks Ledger to identify the first installable fixed release, and reiterates the coordinated publication window. - 2026-08-25 (GitHub / production catalog): Ledger publishes Ethereum app
1.22.3with the backported correction for Nano S Plus, Nano X, Flex, Stax, and Apex P. Bitfinding verifies the public source tag, catalog availability, and fixed-version regression result. - 2026-08-27 (Ledger): Ledger publishes LSB-024, publicly credits Bitfinding, notes that Florian Pradines independently reported a variant, and documents the vulnerability, affected releases, impact, remediation, and coordinated-disclosure timeline.
References
- Ledger Security Bulletin 024: Clear-signing bypass via array-count truncation
- Root-cause fix commit
2c3d93cc: Fix array iteration truncation - Related GTP hardening commit
085880b0: Add overflow check inpath_ref - Fixed source tag
1.22.3 - Ledger production Manager catalog
- Ledger Devices Bug Bounty scope
- Ledger signer threat model
- Ledger Secure Display and Inputs
- Ledger User Consent
- Ledger: The Importance of the Trusted Display and Secure Execution
- ERC-7730
- Sygnia: Investigation of the 2025 Bybit compromise
- Ledger: Learning From The Bybit/Safe Attack
- Safe smart account technical overview