The Radicle peer-to-peer
code-collaboration project has disclosed
two critical vulnerabilities in the network protocol used by Radicle
nodes. The first flaw is that the network protocol used by Radicle "does not
give the confidentiality it was expected to give", which allows anyone who
can observe the network between two nodes to read the data exchanged. The second
is that peer authentication is broken and allows impersonation, so an attacker
can spoof their Node ID and read private repositories they should not be able to
read.
In practice, the two flaws are most useful when they can be exploited
together: an attacker on the path sees the Node IDs at both ends of a
connection, and both are normally on the allow-list. That attacker can read
whatever is exchanged while they watch, and can then use a Node ID they saw to
fetch the whole repository on demand. The realistic threat is anyone on the path
between your node and node it syncs with, and no setting or allow-list protects
against them.
We are publishing this before the security update is available. You can act
on it today, and no fix we release later can undo an exposure that has already
happened.
See the post for workarounds that can be used today; a major update that will
be backward-incompatible is underway.
Yikes. 90 days since the first of the 2 bugs was disclosed, so I am assuming the security researcher would have disclosed if Radicle hadn't.
No fix but to wait for a major protocol upgrade without an ETA! (ummm and iroh looks like a paid service even though they pay some lip service to "open source" blah blah...?)
Hacking on Raspberry Pi board internals is one of my favourite topics. I know a bunch of obscure things about these cute little boards. Three years ago, I covered a Raspberry Pi 4 RAM upgrade story. Getting a BGA RAM chip and swapping it in seemed like a no-brainer to me – apart from all the numerous uncertain parts about it, you know. It was a joy to see hackers pull it off, and for it to function as well as it did!
Things changed. You can’t really get RAM chips anymore. You also can’t get RAM sticks. You can’t get even SSDs with RAM chips on them. Even getting Raspberry Pi boards can be hard unless you know where to look. This is where a recent three-minute video by [Jeff Geerling] finds us.
Turns out, Raspberry Pi Foundation pushed binary-blob bootloader changes that limit your ability to upgrade RAM. I’ve known about it since last year through the grapevine, and somehow, as I read about it, this didn’t bother me at all. Not enough to write a Hackaday article about it, even, much less talk about it more widely. Why didn’t it bother me? Today, I sat down and pondered this for a bit.
Here’s my conclusion: I don’t think it’s a big deal at all, even if it seems that many people would disagree. Come in, as you are, and I hope you find my thoughts on the situation entertaining.
How It’s Made
First, some ground facts. This change restricts upgrading the RAM chip on your Pi 4 and Pi 5, as well as Compute Modules. By the looks of it, it does not restrict replacing the RAM chip with a chip of a similar size, quote, “locking devices to their original RAM size”. As such, this does not prevent repair of your Raspberry Pi board, but does somewhat limit your repair part choice, at most.
This restriction is easily bypassable. The bootloader is stored in the SPI flash chip, which can be reflashed using the built-in mask ROM over USB and rpiboot, and you are not prevented from flashing older versions of the bootloader, so far. This means even if you manually swap the RAM chip, all you need to do is to also downgrade the bootloader to the last known good release — 2024-09-10 — and then your Pi board or Compute Module will function with upgraded RAM. If you have the skills to upgrade your RAM, you most certainly have the skills to downgrade the Raspberry Pi bootloader.
For most regular use, having a two-year old bootloader version won’t really matter. There have been about 15 releases since the 2024-09-10 one, so I went and read through the patchnotes. Checking quickly, the important features like NVMe boot have been available for a fair bit before this release, maybe you will miss out on a few quality of life fixes or more obscure hardware configuration problems, but that’s it. Hopefully I’m not missing something, please point it out if I am. You know what is an issue, by the way? The patch notes for the 2024-09-23 release don’t mention the RAM size check addition at all, maybe they should fix that.
What’s funny is, I am checking reports and it seems that the board will boot and function well with newer bootloaders (mostly 2025 versions), and in other cases (mostly but notalways2026 bootloader versions) it will outright refuse to boot with a 8 or 9 flash failure code. I haven’t compared details on what exactly might be causing this yet, but it might be that you don’t even need to downgrade bootloader all that badly – though I’d definitely start with the oldest known-good release.
How It Happened
This is the first reason I can’t bring myself to care. If you can upgrade your RAM, you can downgrade the bootloader. However, here’s the point of contention – did this change really need to happen?
For the reference, this bootloader change happened almost exactly two years ago, at some point between September 10 and September 23, 2024. This was exactly a year and a half after we covered the first Raspberry Pi RAM upgrade. What changed?
The Raspberry Pi Foundation (RPF) justifies this as follows: they saw third-party resellers sourcing low-RAM Compute Modules, upgrading them with RAM from unknown source and unknown stability. My observation is that they’d also be reselling the modules at a markup for purely commercial gain, while undercutting RPF who would otherwise direct that money into RnD, something I much enjoy to see them do. This creates perverse incentives and risk for people buying Raspberry Pi boards online, and RPF decided to limit this primarily for their users’ benefit, plus, if you ask me, some of theirs.
Is this justification true? I can’t know, but I went to check for signs of this happening, and the non-consensual RAM switcheroos seems to be happening all over the place! The related GitHub issues have a fair few pingbacks, and exploring them makes the problem look grim to me.
Off the cuff, I spent 15-20 minutes checking, and I can easily find fifteen different people on GitHub alone complaining about this failure. [(1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15)] As far as failure modes in popular hardware go, this is a surprisingly large number that suggests at least hundreds of hackers and hobbyists affected, if not more. None of them appear to have performed the upgrade themselves, hot air and flux way, the Hackaday way – all of them seemingly simply bought a “8 GB” version from either Aliexpress or Amazon. In at least one case, the seller immediately lied to the buyer about the issue and did not at all admit that an upgrade took place, even though it clearly did.
How It Makes Sense
This seems to map exactly to what RPF says happened. I can very much see why they would be worried. For instance – at the time they made this change, if my memory isn’t failing me, CM5 or CM4 2 GB boards went for $30-$40, 8 GB boards went for $100ish, and 4 GB boards were somewhere inbetween. As far as the intersection of hacker gadgets and industrial-lite hardware goes, that price is more than fair.
At this point, we’re no longer talking about people upgrading RAM on their boards for fun, and remember, you can still do all of that. Instead, that’s a fair bit of margin for someone to grab for 20 minutes of hot air work, someone who hasn’t done any of the RnD that RPF has put in. I personally much rather would see that margin go to RPF, and I’d like to have them deal with none of the headache, too.
Creating incentives for this kind of switcheroo business, unimpeded, seems deeply corrosive to me. For one, there’s zero repercussions for someone equipping those boards with substandard and harvested chips. RPF, as any experienced hardware company, would know about sourcing harvested or otherwise shady-origin RAM and other chips. A third party on Amazon/Aliexpress passing on those chips undisclosed to unqualified end users, now that goes far beyond the usual harvested chip horror stories. It turns into a game of “will/won’t the user notice the weirdness and ask the seller for a refund on time”, and at $100-$200, together with the ship-item-back requirement these prices bring, the answer will generally be “no”. Now, the buyer finds out about the tampering almost immediately, since Raspberry Pi boards auto-update the firmware.
If you don’t agree that this alone is corrosive – notice how this kind of “upgraded” board clearly requires sellers to misrepresent what they’re selling? You’re not going to find about this “upgrade” in the listing, there won’t be a “8GB reworked” label, clearly that would require lowering the price and/or cutting into the margins. Just like there’s no incentive for using a fresh RAM chip instead of a harvested one, there’s also zero incentive to be open about the mod. That’s no way to sell a pen, much less a full computer.
How RPF Does Things
Here’s the most confusing part to me. I’m seeing [Jeff] and others say that this is out of character for RPF to do. This does not seem true to me, at all? I don’t recall a time when Raspberry Pi has put effort into giving users freedom to hack on the board itself as soon as any sort of legal or competition issue arose.
Raspberry Pi puts cryptographic EEPROMs on Pi Camera boards, v2 and beyond, to thwart clone cameras that became so prolific and cheap with v1, I own at least five of those clones in different form-factors. I don’t like it, but at least the authenticity checks are disabled for Compute Module versions, as far as I remember. They still don’t sell the MLX7704 PMIC that immediately dies if you accidentally short-circuit GPIO header 5 V to 3.3 V, two male pins that are right on the side of the board and exceptionally easy to short-circuit from three sides simultaneously.
Raspberry Pi only ever publishes “reduced” schematics, ever since 2013, and they haven’t even released those for Pi 5. Neither have they published Pi 4 USB-C issue fix schematics despite having promised to do so. Half of the chips on a modern Raspberry Pi board are single-source and board-specific, the aforementioned PMIC being a fun example. Raspberry Pi boards ship closed-source software, from the bootloader to the GPU code whipping the CPU, to all the onboard wireless chips they have used so far, and this brings real issues you’ll stumble upon as you push the limits. The JTAG port has always been locked down, and even the bootloader is cryptographically signed – no patching out the RAM check code, sorry to spoil it.
I’m far from done listing ways in which Raspberry Pi prevents or works against low-level board tinkering, there’s at least five more things I could list from memory if I were bothered to check them thoroughly enough right now. Point is, if anyone came to Raspberry Pi for the low-level hackability of the hardware, there was never a shortage of reasons to look elsewhere. That was never the upside.
I don’t think low-level board hackery is what people genuinely expect from Raspberry Pi at all, even if they might say that they do – because it was never really there. You can’t genuinely expect something that was barely ever present. In contrast, there’s plenty of competition that publishes full board schematics, sometimes even gerbers, uses as much open code as possible, doesn’t sign bootloaders, and so on. Raspberry Pi is not open or hackable hardware by those metrics, and it never was. It’s not hackable hardware anywhere as much as it’s hardware made for hackers to use, and there’s clearly a big difference.
How Did We Get Here?
In practice, people clearly come to Raspberry Pi because of their community in the millions, meticulously explored and documented hardware, unmatched availability of boards and software alike, having solutions for nigh every little niggle and nitpick, RPF’s ability to experiment and release new cool hardware year after year, and even new standards they create along the way. Most of low-level problems get papered over through sheer scale – say, last year [Jonathan Clark] reverse-engineered the Raspberry Pi Zero 2 W, and then [TubeTime] reverse-engineered the Compute Module 5, how cool is that?
I could agree with [Jeff Geerling] that it could instead benefit from a “warranty bit” type of mechanism, but really, the bootloader downgrade isn’t that hard of a penalty, and “board autoupdates firmware, fails on next boot” is a much stronger indicator that you should go get a refund at haste. A refund is also way easier to get if you say “hey, the board stopped working immediately”, and who knows, maybe you’ll get a refund and get to keep the board so that you can downgrade its firmware and still use it! Win-win, seller loses out on selling you a shady board, and you get a shady board that might just work, for free. Chances are, we wouldn’t even be talking about the RAM swap restrictions if it weren’t for the RAM shortage, and that is not RPF’s fault at all whatsoever.
My advice: don’t lament Raspberry Pi RAM upgrades, especially given they’re only slightly harder to perform now. Very few hackers ever performed them, the main audience for them turned out to be dodgy hardware resellers online, and in most cases, repair doesn’t seem to be impeded at all, either.
Think of the users that will no longer be fooled by a shady seller on Amazon, especially now that the perverse incentives for board mods and reusing harvested RAM chips are at their highest. RAM shortages might make any RAM topic hurt deeper than usual, which to me is the most likely reason we’re discussing this in 2026 instead of 2025, but this change is far more likely to help a hacker in practice, than it is to hurt.
But Arya, the scammers can just load an old firmware, that's way easier than reworking with dubious RAM chips. So, how does it protect folks from being scammed?
how far the search engines have fallen, I can't find any hits for this code even though the mnemonic should find some very specific pages based on full text search. I'll be curious to see the answer. "clearly" it's an early 16 bit CPU, 15 16-bit registers plus one "zero" register.
Much of science is performed through inference, with the readings on instruments, a flash of light in heavy water, or the results of parsing through terabytes of sensor data after a particle accelerator collision either backing up a proposed scenario or weakening its foundations.
In the case of so-called dark matter, this is even more relevant, as we are talking about a proposed form of matter whose most pertinent feature is that it doesn’t interact with anything else except through gravity. This is where the wiggling of a levitating magnet may be the key to detecting it.
In this experimental setup by Rice University and Dutch researchers at the Leiden Institute, a tiny permanent magnet the size of a grain of sand is levitated above a superconductor, surrounded by highly sensitive detectors that should be able to spot even minuscule movements. So far, they have collected a month’s worth of data, with no conclusive results yet.
Even if they don’t detect any ‘knocks’ on this tiny levitating magnet, it will still help refine existing models of what dark matter’s properties might be. For the next phase of this research, they’ll add more of these sensors, which will also make it easier to distinguish background noise from any unusual readings.
One of those niche curiosities, I tried to run down an old joke that’s variously attributed to Bob Newhardt, Tom Lehrer, and many others.
I’ve seen a lot of variation in wording, but the core joke was always:
Well, aside from that, Mrs. Lincoln, how did you like the play?
The inestimable Barry Popik has the earliest citation to a famous midcentury political humor column:
22 May 1957, The Evening Star (Washington, DC), “Potomac Fever” by Fletcher Knebel, pg. A-23, col. 4:
What TV interviewing would have been at the time of the Civil War: “Well, aside from that, Mrs. Lincoln, what did you think of the play?”
By the time I found this I had gone through a fair amount of false leads and slop, so I wanted to confirm and provide a convenient copy for future citations.
The Library of Congress has helpfully digitized this page:
A function's return type is supposed to indicate the kind of data that it
produces.
Rust's "never" type, which is
denoted by an exclamation mark ("!"), is the type the language uses to mark
a function that never returns and other places where a value can never occur. For
a long time, the never type was used internally by the compiler, but was
considered an unstable feature. On
August 24, after more than two years of work,
Rust-compiler-contributor "waffle" finally managed to stabilize the type. It
took so long, in part, because it involved a small breaking
change to previous Rust editions, which the compiler maintainers needed to
ensure did not impact much real code.
(Note: Rust also uses exclamation marks to indicate calls to macros. The way the
syntax is constructed, a place where it is valid to use the never type is not a
valid place to put a macro invocation and vice versa.)
Why a never type?
There are two reasons that Rust has a never type, one practical and one
philosophical. The practical reason is that it allows for more efficient generic
code. For example, consider the
FromStr trait in the standard library, which is used for types that
can be instantiated from a string:
FromStr::from_str() either returns a converted result, or a custom
error type. For example, attempting to convert "foo" into an integer will return
a ParseIntError. But some types have an infallible conversion. For
example, it is always possible to convert a string into a
ByteString. That implementation of FromStr could set
Err to be the never type. Then the compiler would know that the
error branch of the returned Result is never present, and could
optimize out all of the code that touches it or checks for it.
impl FromStr for ByteString {
type Err = !;
fn from_str(s: &str) -> Result<Self, !> { ... }
// Keeps the same generic interface,
// but generates code equivalent to:
// fn from_str(s: &str) -> Self { ... }
}
The philosophical reason involves correct type inference. In Rust, constructs
such as if statements and while loops are expressions; their results can be
assigned to a variable. The compiler needs a type to infer for the result of an
infinite loop, if the programmer writes one. That shouldn't come up often in
real code, but it turns out to simplify type inference to be able to treat that
case uniformly, rather than adding special rules to handle it.
In particular, the never type has a useful property for simplifying code: it
automatically coerces to any other type. This sounds strange, but it is
safe, since the never type represents the "result" of a computation that will
never produce a value. So, anywhere that the code claims to have a value of the
never type, the compiler knows that it can't possibly reach
that code, and therefore it's safe to ignore it. This is a form of
type-system-driven dead-code elimination.
For both of these reasons, Rust programmers have wanted to be able to use the
never type in stable versions of the language. Making that happen
required resolving a particularly thorny corner case.
Never fallback
Because of the way that conversions from the never type to other types are
implemented, the compiler can sometimes end up in a situation where it cannot
naively infer the concrete type of an expression. Consider this example, which
defines an anonymous function (using ||, which is like
Python or LISP's lambda) that never returns, and then calls it in a way
that expects a concrete error type (using the
? operator):
let function_that_never_returns = || { loop {} };
function_that_never_returns()?;
That infinite loop is given a type of ! which is then implicitly
converted to whatever the function is supposed to return. But since the function
is defined locally and not given an explicit type, the compiler does not have
sufficient information to say what that type is.
The problem could be fixed by giving the function an explicit return type:
let function_that_never_returns = || -> Foo { loop {} };
Since such an annotation would only be required in cases where the function
cannot return anything, however, it would
be a bit pointless to require the programmer to assign a fictitious type to it.
So, the compiler includes a special rule: if, after all other type inference has
been done, there is still an ambiguous type that cannot be determined, just assume
that it should be the designated fallback type. Prior to the 2024
edition of Rust, that fallback type was () (the unit type, which has
exactly one possible value). In the 2024 edition, the fallback type was changed
to ! itself, essentially canceling out the implicit conversion. In the
compiler internals, the never type still gets converted to an unknown type and
then falls back, but from the programmer's perspective the behavior is identical
to having the never type only undergo implicit conversion when required for the
types to make sense.
That change of behavior was, technically, a breaking change. Type inference for
some code could change, which could in turn cause compilation errors. That is
the purpose of Rust's edition system: allow breaking changes in the front-end
design of the language without breaking older code or requiring the whole
ecosystem to update at once. In this case, however, there were reasons to want
the new behavior backported to old editions.
Never infallible
For many years, the standard library has had an
Infallible type to work
around the unstable nature of the never type. It served the same semantic
purpose as the never type, but did not have any special compiler support.
Therefore, code using it would be technically correct but suboptimal (such as
having an extra layer of tags in an enumeration or emitting dead code), because
the optimizer would not always be able to remove references to
Infallible. It was planned that, when the never type was
eventually stabilized, Infallible would become a type alias for
! and all that old code would silently become more efficient. However,
people pointed out a handful of ways that redefining
Infallible had accidentally been made into a breaking change. Since
! has implicit conversions, changing the definition of
Infallible could result in existing code needing additional type
specifiers in order to type check.
Luckily, changing the definition of Infallible and changing the default
fallback type, while both breaking changes, nearly cancel out. Any code that refers to the
standard library's Infallible type by name would continue to work; it
is only places where type inference is implicitly expected to produce
Infallible that pose a risk of breaking existing code. With Rust's lack
of implicit conversions in most cases, that will most often come up in places
where the never type used to be implicitly converted to Infallible. If
Infallible is made to be a type alias of the never type, then those
places may experience never-type fallback, which would, in turn, change the
inferred type and cause a compilation error if the never fallback type were not
updated at the same time.
With both changes occurring simultaneously, the Rust maintainers believed that
almost all existing Rust code would continue to compile — but "almost all" is
not a reassuring qualifier when dealing with backward-incompatible
changes. The Rust community does have a solution to this in the form of
crater, which can download and compile all publicly available Rust
libraries from
crates.io in search of code that is broken by a compiler change.
Never say never
Waffle ran crater in April and
found that, while there were 3,300 crates negatively impacted by the change,
only seven were fully broken, with the rest broken by depending on old
versions of libraries that had since been fixed. In the latter case, the problem
would theoretically be fixable by releasing backported fixes for a handful of
core libraries.
This is not an accident; Rust has been emitting a warning whenever
code triggers never-type fallback in a way that will break with the new change
since 2024, so most libraries had plenty of time to update of their own
initiative. The most common remaining error observed by crater is code
that calls a generic function without enough type information for the compiler
to pick a specific return type. Consider this function:
fn foo<T: Default>() -> Result<T, Error> { ... }
It returns either a value of some caller-chosen type T that must
implement the Default trait, or an error. If
it is called without specifying a value for T, however, then type
fallback can kick in:
// No type specified.
foo()?;
Previously, this would have made the compiler assume that T should be
(), which implements Default, and so the code compiles. After
this change (and on the 2024 edition), the compiler assumes that T
should be !, which doesn't implement Default, and therefore
causes a compilation error. The fix is to explicitly specify the type that
foo() should return, either in the call or by pattern-matching
assignment.
foo::<()>()?;
// or
() = foo()?;
Even though it's not a complicated change, the Rust maintainers were not willing
to break 3,300 crates. Waffle was
asked to work with the maintainers
of common libraries to backport simple changes like the above (making a new
patch version, which many Rust build environments will pick up automatically),
in order to
reduce the number of libraries depending on broken dependencies. Several library
authors were
willing to make the backports, but some refused on the grounds that those
old versions were past their end of life. Those maintainers pointed out that
users could stay on an older version of Rust or update to the maintained version
of the library. Even so, the successful backports
addressed 1,553 of the failing crates.
After fixing a
handful of related problems to reduce the number of broken crates even
further, the Rust maintainers eventually agreed that
even though there would still be some broken code it was worth making the change
to simplify the language. So, starting in Rust 1.99, the never type will be
stable and Infallible will be a type alias for the never type. Users
who find that this breaks their code have a few options:
Stay on Rust version 1.98.
Update their dependencies to supported versions that include a fix for the
problem.
Add a patch to explicitly specify the return types of affected function calls.
On the one hand, this is
a breaking change, and people may see code that had remained stable
and working suddenly fail to compile. That could be seen as a violation of
Rust's commitment
to backward compatibility. On the other hand, the problem is
relatively rare, there are multiple simple ways to fix it, it has been warned
about for years, and it has always been part of the plan for the language.
Additionally, the
Rust maintainers worked directly with the community to find and
address the breakage, even going so far as to help backport fixes to long-dead
versions of popular libraries. So, the whole process could also be seen as an
affirmation of Rust's commitment to backward compatibility.
In the
future, people learning the language will hopefully find the never type just a
little less special.
Either way, most users of Rust will probably not be affected at all, but never
say "never".