When random.bytes() runs but doesn't work
Posted by Funes- 1 day ago
Comments
Comment by londons_explore 1 day ago
They are the perfect bug for someone trying to "accidentally" make a secure system insecure.
I wonder if bribe money was involved or three letter agencies...
Comment by dist-epoch 1 day ago
And do you think the NSA desperately needed $90 mil (the amount stolen so far), so they created this elaborate backdoor? Or were they waiting until Satoshi or Binance decided to deposit a billion dollars into this wallet?
All the employees which touched the code will be on watch lists now anyway. And sudden new house or yacht will be immediately flagged.
Comment by londons_explore 1 day ago
The longer they wait, the more they get to steal after all.
Comment by josephg 1 day ago
If this was done as part of an operation, we would have no idea what it was actually used for. It’s very unlikely that the crypto wallet thefts we know about were the intended target.
Comment by anonymousiam 1 day ago
Comment by karamanolev 1 day ago
It has been established that "I didn't do it on purpose" is not enough in some cases. You have to _not do it_ _on purpose_. That "fake it till you make it", in this case, understanding what your code does, is perfectly fine in some contexts, not in others, like this one.
Comment by silvestrov 1 day ago
This is systematic for that user. Either the user needs to improve or let go.
The organization needs to learn that good commit messages are a requirement for anything to do with crypto and payment systems.
Comment by inigyou 1 day ago
Comment by anonymars 1 day ago
> NEW POLICY If I don't know you, I don't merge you! Thanks XZ!
Comment by Sharlin 1 day ago
Comment by eru 1 day ago
LLM agents are cheap and good enough that you can write in eg Lean or whatever. Or at least write it in Rust.
Comment by jgilias 1 day ago
Comment by thin_carapace 1 day ago
Comment by scott_w 1 day ago
1. A novice who doesn't know how to debug issues.
2. Totally incompetent and copying code from Stack Overflow.
I don't think it's specific to microcontrollers, either, the error from the C compiler is something that a competent programmer would be able to interpret, or at least see it as a signal to get someone more experienced in the domain to learn about.
Comment by eru 15 hours ago
Comment by nullc 22 hours ago
contrary to the post, this was almost certainly not an issue of compilers throwing errors.
Reduced familarity with C could have played a role, but given the number of C experts that looked at this knowing there was an error and still misidentified the cause I don't think we need to reach for that powerful an explanation.
Confusion of definedness vs value check would make for a fine underhanded C entry. The flaw was not particularly clear from the source... and most common QA procedures could not distinguish a PRNG from TRNG once the error happened.
Comment by scott_w 19 hours ago
The article makes the fair point of the size of the change and the complete lack of information in the commit message, which should have set off alarm bells…
Comment by jki275 1 day ago
Also running python on a microcontroller to do cryptography is fucking insane.
Comment by nullc 22 hours ago
I feel like I should confess to snubbing this (and some other) hardware wallet projects for the fact it used micropython. I think it's hard to draw the line between systems programmer snobbery and good advice... and I feel a little like a cop guilty of stop-and-frisk on the basis of skin color.
Because in general the ideas necessary to produce reliable software aren't well understood or agreed on there is a risk of letting style preferences which are only correlated with good engineering but aren't causative of good engineering get mistaken-- and this can cause errors in both directions, both mistaking stuff as good because it uses the "right" tools, or mistaking something as bad because it doesn't.
Comment by nullc 1 day ago
I'm commenting because I think it's important to understand the issue.
The article would have you think that the change in question was a tiny change to a flag to make it compile, but in reality the commit in question is a 1533 line addition of the entire RNG infrastructure.
The fundamental cause is a mixup between a value test and a definedness test.
Coldcard attempted to replace the micropython wrapper on the hardware TRNG, apparently in order to provide a more aggressive handling of fault/error conditions.
The micropython hwrng code is gated by an #if check, the replacement HWRNG code is gated by an ifndef. So "#define MICROPY_HW_ENABLE_RNG (0)" deactivated the micropython implementation but failed to activate the internal one (which was #ifndef MICROPY_HW_ENABLE_RNG ... which didn't fire because MICROPY_HW_ENABLE_RNG was _defined_).
This was easier to miss because the usages weren't only in different files-- they were in different repositories.
There is a more abstract point to make that in cryptographic software the absence of a secure randomness source (the STM32 TRNG) should never fall back to an insecure source (a trivial PRNG which might have only had on the order of 20-bits of uncertainty in its input). But the code that had the fallback was micropython which was not authored by the coldcard creators and is presumably not intended for cryptographic applications...
In later code (for MK4+ devices) the issue was further masked without being corrected by xor-ing in another insecure PRNG seeded by 32-bits from another TRNG. ... itself acting like an additional insecure fallback. (Why it first hashes 64-bits of TRNG output then throws away half the entropy is a mystery...)
RNG failures can be difficult to detect because the real randomness and a PRNG are indistinguishable by any simple tests of the output. I understand the coldcard developers ran extensive tests on the randomness generated by these devices-- they may well have been just testing the PRNG. It's something of a "color of your bits" issue ( https://ansuz.sooke.bc.ca/entry/23 ).
The same sort of issue happens at multiple levels of the stack, e.g. IIRC the STM32 TRNG itself does some kind of whitening that could have the same effect of concealing an RNG failure.
The existence of insecure fake randomness in the code at all was a red flag that had been noted previously -- though the really bad one was hidden away in the micropython code and not even obviously at play. I think this is a rare case of a bug that would be more easily found from binary analysis than review of the source code (e.g. no access to the STM32 TRNG at all).
Without these fallbacks the failure to use the TRNG would have been immediately detected by the developers (e.g. when every attempt generated the same seed), and a review could be validated by fault injection (NOP out the hwrng and verify that the test fails).
Comment by egwor 1 day ago
- not enough information provided in commit message. - this change ought to have been split into smaller chunks (e.g. introduce indirection whilst code continues to use hardware) - limited (automated) testing - potential confusion by the author over C concepts - additional complexity which could have been hidden behind a cleaner interface rather than using defines
I agree about your points about:
- defines over multiple repo's made this more complicated that was necessary. - fault injections would have been the appropriate way to test this (or maybe mocks) - testing of randomness needs careful design, e.g. RNG seeding can be a good way
Taking a step back, we as a community of excellence need to emphasise that this isn't a criticism about the person/author. There needs to be a clear analysis of what went wrong whilst being kind to the person/people involved.
Comment by inigyou 1 day ago
Comment by nullc 1 day ago
vs
https://github.com/Coldcard/micropython/blob/4107246f8a08080...
Comment by dale_glass 1 day ago
1. I believe it errors out if the HWRNG returns the same value twice. That's actually a thing that can legitimately happen. "0" is also a legitimate output.
2. "here" is a terrible name for a length
3. It does a memcpy of a minimum of 4 bytes to the destination, even if count is lower. It'll also overflow longer buffers with a length not divisible by 4.
Comment by nullc 23 hours ago
> It does a memcpy of a minimum of 4 bytes
This is a common misreading of MIN(). MIN(4,x) is a number that is a MAXIMUM of 4, not a minimum.
Count is the number of bytes remaining in the buffer. The input to the copy is a 4-byte word. min(4,count) will produce a number 0-4 which is always equal to or less than count. The copy will not overflow the buffer or overrun the input: If count is 3, for example, then here will be 3 and it will copy 3 bytes.
> believe it errors out if the HWRNG returns the same value twice. That's actually a thing that can legitimately happen. "0" is also a legitimate output.
It's been a while since I looked but I believe the STM32 manual advises you to throw away data when this happens, because the rng is updated async with the processor and reads that are too fast will produce 0s or duplicate values. Entropy loss from doing so is generally negligible. Were it me I'd read enough into a cryptographic hash to render the output cryptographically close to uniform and not have to worry about it further. (particularly since some uses of cryptographic numbers are extremely sensitive to even small biases)
I do wonder how fatal MP_EFAULT actually is...-- on a device like this being jumpy at failing the RNG is reasonable, but if it bricks the device (for example) that would be too much for a condition that (IIRC) the datasheet says can happen. If that error is worse than causing a reboot then it might be the case that their emergency fix deployment might have the effect of causing problems by deploying never-actually-tested code into the wild. When I initially looked at this code before finding the flaw I was somewhat surprised that this test didn't produce spurious failures.
[Maybe someone who isn't traveling and on vacation might want to trace out that error condition, -- and check my vague recollection of the STM32 datasheets, as I could be remembering some other part]
Comment by inigyou 23 hours ago
Comment by nullc 23 hours ago
Comment by killerstorm 21 hours ago
So I'd say the root cause that the developer didn't check what random.bytes() does under the hood or how libngu expects random to be generated.
Comment by smithcoin 1 day ago
Comment by nullc 22 hours ago
Comment by mlcrypto 1 day ago
Comment by nullc 1 day ago
Comment by RustyRussell 1 day ago
Comment by dist-epoch 1 day ago
Hard problem in general, but with LLMs surely this is possible now, either with them inspecting or by them providing some sort of "formal" proof - this function calls this function which reads this buffer which gets input from here...
Comment by koolba 1 day ago
No! Blanket statements like this is how you end up with 40 pages of slop AI comments in PRs that nobody reads.
Comments should be terse and meaningful. They should document surprising behavior or choices. The less comments you have, the more meaningful each one becomes because your time and eyeballs are limited as well.
Comment by coldbrewed 22 hours ago
I think we're all in agreement that a one word "runs" commit and a 6 paragraph slop commit both fail to communicate effectively.
Comment by nonfamous 1 day ago
>>> To every other developer: we believe this is a sober reality of the new AI paradigm. AI-assisted code review can now find latent bugs at a speed that is outpacing even the industry’s most seasoned experts. If your firmware is open-source or has ever been public, assume it's already being read by attackers and defenders alike.
Kinda turns the “many eyes” principle of OSS on its head, eh?
Comment by abecedarius 1 day ago
Comment by nullc 22 hours ago
One lesson out of this is that now that AI has made a certain grade of review cheap is that it would be useful to perform security review both against the source code and against the resulting binary.
Comment by dist-epoch 1 day ago
Comment by catlifeonmars 1 day ago
Let me amend your statement:
If the industry most seasoned experts are not engaging in regular security reviews of their firmware, they are not experts, but clowns.
LLMs are useful for this but not absolutely necessary to address the core issue.
Comment by dmitrygr 1 day ago
>Micropython creates the illusion embedded developers do not need to understand C, their CPU, or other advanced concepts to do embedded programming.
Amen.Comment by PunchyHamster 1 day ago
the commit message is not 235 character long, the commit message is this
> splice-script: Test for msat chan balances > splice-script: Round channel balances down
author mistakes commit message for PR which doesn't get persisted into code's history and don't even contain the ID of bug being fixed.
So yeah it is better but nothing to actually brag about
Comment by hypeatei 1 day ago
Or, you could realize that using a financial technology which relies on no software bugs ever is probably flawed at its core. "Not your keys, not your coins" was (is?) the zeitgeist but even people who did the "right thing" and used a cold wallet still got screwed. I don't know why anyone would keep their wealth in something that requires so much technical excellence.
Comment by DJBunnies 1 day ago
If one uses a good source of entropy (e.g. dice rolls) then one will not "get screwed."
> I don't know why anyone would keep their wealth in something that requires so much technical excellence.
It's not for everybody, for sure.
Comment by hypeatei 1 day ago
How many users were told to verify the source code to make sure it was using good entropy? How many would actually do that even if told? Obviously not even the most hardline bitcoiners were looking at it.
> dice rolls
Yes, the new cope I've seen is that you should've been using casino-grade dice[1] to make sure your entropy is good LOL. Mass adoption soon, I'm sure.
1: https://old.reddit.com/r/Bitcoin/comments/1vcr4r4/dice_rolls...
Comment by inigyou 1 day ago
Comment by anonymars 1 day ago
Comment by hypeatei 1 day ago
There is no blockchain, seed phrases, nor cold wallets required for this amazing feat of money transfer/remediation to take place.
Comment by inigyou 1 day ago
Comment by anonymars 1 day ago
Comment by hypeatei 1 day ago
Also infrequent purchases != storing wealth in Bitcoin.
Comment by systemsweird 1 day ago
Comment by jonathanlydall 1 day ago
I agree that a bad commit message combined with a big commit is a huge smell, but as it’s just metadata for the code and not the code itself, it is not in itself evidence in any form.
That being said, probably okay after explaining the technical code issues for the author add a couple of a sentences about how all this was was part of 1000s of a lines of code changed in commits with useless messages which demonstrates generally bad code hygiene.
Comment by smithcoin 1 day ago
Comment by fenestella 1 day ago
Comment by mangudai 1 day ago