Coldcard postmortem from MicroPython's perspective #19588
dpgeorge
announced in
Announcements and news
Replies: 1 comment
|
See related PR #19589 which aims to prevent incorrect use of RNG functions in the stm32 port. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Coldcard is a Bitcoin hardware wallet, and it's firmware is built using MicroPython. (Trezor is another hardware wallet also using MicroPython, but it's not relevant here and not discussed further.)
Recently a vulnerability in the Coldcard firmware was exploited to steal over $100M worth of Bitcoin. The vulnerability existed due to the accidental use of a pseudo random number generator (PRNG) instead of a true (hardware) random number generator.
Here's my analysis of how this vulnerability came to exist, from the perspective of the use of MicroPython in the Coldcard firmware.
Analysis
The stm32 port provides a
rng_get()function, which can either be hardware backed or a PRNG (which is seeded by the RTC and unique id). You select that based onMICROPY_HW_ENABLE_RNG(set to 1 to get HW RNG, set to 0 for PRNG).Coldcard firmware has a submodule called
libngu. This library externally references (and expects to link against)rng_get(). The relevant code is https://github.com/switck/libngu/blob/0371d6372eb7c1165f9c0410f6d6537e09882402/ngu/random.c :There is a macro check there which is trying to enforce the HW RNG, but it's only checking that
MICROPY_HW_ENABLE_RNGis defined, rather than checking it's enabled.The Coldcard firmware has a custom
rng.cimplementation in the board folder, which is included correctly in the firmware build. See https://github.com/Coldcard/firmware/blob/bcc2c382a324690a2fcf972c0bac3b79bf923f7b/stm32/COLDCARD_MK4/rng.cThat
rng.cfile implementsrng_get_or_fault()which uses the stm32 hardware RNG source (raises a Python exception if the hardware fails).The Coldcard MicroPython firmware builds and links fine with no duplicate symbols because they define
rng_get_or_fault()which does not clash with the existing stm32-providedrng_get().The Coldcard MicroPython firmware builds and links fine with no missing symbols because the
rng_get()thatlibnguexpects to call is provided by the MicroPython stm32 source code.Now,
libngudoesn't know aboutrng_get_or_fault()but it should be calling it instead of the stm32 code.At this point you can see the vulnerability:
libnguis using the PRNG implementation in stm32 becauseMICROPY_HW_ENABLE_RNGis set to 0. The PRNG algorithm can be inspected to create an exploit.Note this is all at the C level, there's no Python code involved here (not even Python-C bindings). It's a confluence of C-level (mis-)naming, build systems, submodules and configuration variables.
Fixes made post exploit
Post exploit, the fix to the Coldcard firmware was made: Coldcard/firmware@ca72463
That fix "deletes" the stm32's
rng.ccode and adds a wrapperuint32_t rng_get(void) { return rng_get_or_fault(); }function so thatlibngugets the hardware RNG (alternatively, could have changedlibngu'sCHIP_TRNG_32macro to callrng_get_or_fault()instead).There was also a fix to
libnguto fix the macro check discussed above. See switck/libngu@e9d5e80Lessons for MicroPython
MicroPython is used in many places, many which are open source and many which are kept closed. Due to the nature of embedded systems, those uses of MicroPython are sometimes in critical systems.
The engineers of the critical systems are responsible for ensuring their systems function correctly and are secure.
That said, it's also good practice for MicroPython to facilitate the construction of robust and secure systems, at least where practical and reasonable. When writing code related to random numbers, encryption, cryptography and other security-related components, it would be beneficial for MicroPython to think widely about how such code might be used, and structure the code to eliminate any obvious mistakes in its use.
All reactions