Destroying your moneroj for fun and not for profit

TLDR: A lack of unlock time verification on the monero hardware wallets could have allowed a compromised host to permanently lock up a user’s XMR after a transaction. Both Trezor and Ledger patched the issue.

Every monero transaction has a field called unlock_time. It indicates how long the outputs have to mature after transaction creation before they can be spent again. The outputs of a transaction with an unlock_time of 0 can be spent immediately, or at least immediately after the normal 10 block maturity period. If the unlock_time is 100 blocks above the current block height, the recipient needs to wait for 100 blocks until he can finally spend the outputs again.

The maximum number of blocks that can be input is 500’000’000-1, which will probably be mined in about 1900 years. If the number surpasses this limit, the unlock_time gets interpreted as Unix time in seconds. Since this is a 64bit field, it allows locking up coins for over a hundred billion years.

Hardware wallets should protect users against theft, ransom, and loss of their coins by malware on their computers. For a monero wallet using either a Trezor Model T or Ledger Nano S/X, the same expectation needs to hold. Hardware wallets must check all information that is passed to them from the host to ensure that the user’s coins are and remain safe.

The Ledger and Trezor hardware wallets did not check the unlock_time field when signing a transaction. An adversary with malware on the user’s host machine could have permanently locked-up the user’s coins with a very high unlock_time. Since change amounts are not verified on the devices, the entire balance of the user could be locked-up, even if the user only made a small transaction.

Both Trezor and Ledger patched the issue. Trezor could simply patch their firmware since they already parsed the unlock_time field as part of their transaction logic. Both Trezor and Ledger display the raw number on their screen, so block height and time based unlock_times display in the same manner.

Some blocks

Ledger did not implement any logic for the unlock_time on their firmware, so they not only had to patch their firmware but also write a patch for the monero wallet.

Sadly, their initial patch introduced more problems. The parsed unlock time, which can be any 64-bit number, was cast to a signed 32-bit integer causing an integer overflow. Depending on the value, the device would either abort or worse, show the wrong unlock_time. After some trouble finding the actual culprit, this additional issue was patched as well.

Forever

Both Trezor and Ledger published security advisories on the issue, gave me attribution and paid out a generous bug bounty.

Written on May 29, 2020