Bech32 reference C code written by Bitcoin developer Pieter Wuille for the BIP 173 standard contained an unsigned integer overflow that leads to a buffer overflow for certain malformed Bech32 addresses. While this code is not used in the Bitcoin core implementation, it is included in over a dozen cryptocurrency projects, notably in multiple hardware wallets and a Lightning node implementation.
I found this issue during security research on the Trezor One in September 2018.
The code in question is in the C reference implementation of the Bech32 decoder, the other seven language variants are not affected.
The problematic lines are:
This code section should return early on invalid inputs, but does not function correctly.
If the address input string does not contain the separator character
1 then *
data_len is equal to
hrp_len would be assigned the value
-1, which would satisfy
hrp_len < 1. However,
input_len are declared as unsigned integers via
hrp_len is set to a large positive number instead (=
MAX_UINT). In C, the overflow or underflow of unsigned integers is well defined, so compilers would see no problem with this step.
The integer underflow behavior causes the described if-condition to miss the check and the address parsing is allowed to continue.
In the case of malicious inputs, the following loop then runs over the larger-than-expected
hrp_len and copies more input bytes to the
hrp buffer than it should:
In practice, the bech32_decode function returns once the
NUL terminator of the input string is found (
ch == 0 goes into line 4 of the code snippet). Problematic inputs are between 85 and 90 characters in length (longer inputs are rejected at an earlier stage) which will perform out of bounds writes behind the array since the calling function
segwit_addr_decode() only allocates 84 bytes for the
Example input address to trigger this behavior:
(85 bytes, no separator, valid charset)
Attack scenario and security implications
Bech32 addresses are - by design - meant to be unsanitized, printable input from external sources. Consequentially, arbitrary address strings provided by an attacker can reach affected implementations in a number of ways, either through automated protocols or potentially through manual copy operations by the end user.
Once the issue is triggered, up to six bytes of out of bounds writes directly behind the
hrp buffer on the stack are be performed. Attackers are limited to writing the following characters into memory:
These limitations are due to the input checks shown in the previous section.
We were able to build a POC for webbrower-based remote attack on the Trezor One, which showed that this vulnerability could be of value to an attacker.
In theory, this flaw could potentially be used to change the program flow through manipulation of addresses or content on the stack. To my knowledge, the main impact for most or all projects with hardened compiler settings was limited to a denial of service, as described in the next section. The inherent range and size limitations would likely make remote code execution on non-hardened projects very difficult, but it is hard to rule this out completely.
For projects compiled with stack protection flags such as
-fstack-protector-all, the linear out of bounds write behind the intended buffer is reliably detected and leads to a deliberate program stop. As a result, the exploitation is limited to a denial of service attack on implementations with this countermeasure.
Potential issues for the Lightning network
During the impact assessment for c-lightning, Rusty Russell brought up the fact that the BOLT #11 standard mandates the use of Bech32 addresses for certain parts of the Lightning P2P protocol. Due to the importance of stable operations for Lightning network nodes, a potential remote denial of service vulnerability via the Lightning network itself could obviously be serious and have even monetary implications for some users (closed channels, ..). For this reason, we spent extra effort in making sure that this was fixed as soon as possible via an early mitigation patch (as noted below) and included another Lightning node project in the disclosure.
Lessons learned - development perspective
- Reference implementations can have flaws in them
- Fuzz foreign C libraries, particularly if they handle external input
- Address Sanitizer + libFuzzer found the out of bounds write
- The non-standard
-fsanitize=unsigned-integer-overflowsetting is needed for the Undefined Behavior Sanitizer to catch the original unsigned integer underflow
-fstack-protector-allwherever you can
Due to the potential high-profile nature of the vulnerability in the context of actively used cryptocurrency systems, I quickly reached out to upstream author Pieter Wuille and began searching for potentially affected open source projects. Relevant projects were contacted with mandatory PGP encryption to keep the issue confidential.
Due to the number of affected projects, this turned out to be a time-consuming and complex task.
Fortunately, the available fix was a one-liner with no known negative side effects, so at communicating the patch was straightforward.
After receiving the impact assessment from the major affected projects as well as positive feedback from Pieter Wuille, I decided to set the end of the information embargo to late October, with less than 45 days total between discovery and disclosure.
Exceptions to the coordinated disclosure date
Due to the above mentioned concerns for P2P network-based attacks on c-lightning nodes, I agreed to a preliminary patch by Rusty Russell that did not fix the integer underflow, but corrected the target buffer sizes and therefore mitigated the practical impact.
Early Trezor One firmware binary
SatoshiLabs had to ship a new firmware shortly before the ZCash hardfork on 29.10.2018 (-> ~one day before the regular disclosure end date) and asked to include the Bech32 fix in the firmware binary to ensure a quick and wide rollout to their users, which I agreed to. Public patches and disclosure followed on the regular embargo end date.
Ledger disclosure and related cash_decode vulnerability in trezor-crypto
During the search for potentially affected projects, I noted that ledger-app-particl and ledger-app-btc both contained the problematic Bech32 code, but did not use it and were therefore not practically affected. I decided to disclose the issue to Ledger anyway, in part because there was a chance that they used the affected Bech32 code in other places, e.g. their non-public code base, which would be allowed by the relevant MIT license.
In Ledger’s reply, they informed me that they had already found the Bech32 vulnerability independently a few weeks ago, but had not disclosed it yet to other projects.
On the same day, they disclosed to SatoshiLabs that the
cash_decode() function used in the Trezor One and KeepKey via the trezor-crypto library was similarly affected. This step was helpful and allowed the second issue to be fixed in time alongside the Bech32 patches. I thank them for their quick reaction in this matter.
|bech32 reference implementation||Github||git|
|SatoshiLabs Trezor One||Github||1.7.1||Trezor article, issue 9 + 10||practical impact only on 1.6.2 and 1.6.3|
|ElementsProject c-lightning||Github||0.6.2-rc1, 0.6.3||mailing list|
|Riddle & Code ECLet_STM32||Github||git|
|Ledger ledger-app-btc||Github||no||unused code, no practical impact|
|Ledger ledger-app-particl||Github||no||unused code, no practical impact|
|nym-zone bech32 utility||Github||no||no reply|
|CWV crypto lib||Github||no||no reply|
|ACINQ Eclair||Github||-||written in Scala, not affected|
|2018-09-26||The bech32_decode issue and resulting buffer overflow are discovered|
|2018-09-26||Disclosure to SatoshiLabs, initial fix is suggested|
|2018-09-27||Crash is reproduced on Trezor One hardware|
|2018-09-28||Internal proof of concept for a remote attack on the Trezor One|
|2018-10-04||First attempt to inform Pieter Wuille|
|2018-10-06||Second attempt to inform Pieter Wuille|
|2018-10-11||First round of disclosure to affected vendors including ShapeShift, ElementsCore and libbtc|
|2018-10-11||Initial contact with Pieter Wuille|
|2018-10-13||libwally-core is informed|
|2018-10-14||Pieter Wuille confirms the bug|
|2018-10-16||ACINQ is informed|
|2018-10-17||Komodo is informed|
|2018-10-18||Riddle & Code and Nayutaco are informed|
|2018-10-23||Proposed public disclosure release date: 2018-10-30|
|2018-10-24||Ledger is informed. The
|2018-10-26||Square is informed|
|2018-10-30 12:00 UTC+1||Coordinated public disclosure|
SatoshiLabs and ShapeShift provided a bug bounty.
I would like to thank Dr. Jochen Hoenicke⁽¹⁾ for his help during multiple steps of the disclosure process. Please credit him as co-author in public references of this issue.