oss-sec mailing list archives
Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes
From: Solar Designer <solar () openwall com>
Date: Fri, 10 Apr 2026 04:58:03 +0200
On Wed, Apr 08, 2026 at 04:24:34PM -0700, Alan Coopersmith wrote:
https://groups.google.com/g/golang-announce/c/0uYbvbPZRWU announces:We have just released Go versions 1.26.2 and 1.25.9, minor point releases. These releases include 10 security fixes following the security policy:
This includes 2 issues in the compiler itself, which made some Go programs not memory safe:
* cmd/compile: no-op interface conversion bypasses overlap checking Previously, the compiler failed to unwrap pointers contained within a no-op interface conversion leading to an incorrect determination of a non-overlapping move. To prevent unsafe move operations, the compiler will now unwrap all such conversions before considering a move non-overlapping. Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue. This is CVE-2026-27144 and Go issue https://go.dev/issue/78371. * cmd/compile: possible memory corruption after bound check elimination Previously, slices and arrays accessed using induction variables were sometimes incorrectly proved in-bound. If the induction variable used for indexing were to overflow or underflow, it could allow access to memory beyond the scope of the original slice or array. To prevent this behavior, the compiler ensures that any mutated induction variable that overflows/underflows with respect to its loop condition is not used for bound check elimination. Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue. This is CVE-2026-27143 and Go issue https://go.dev/issue/78333.
Jakub Ciolek who found these 2 issues (and had inadvertently introduced "the underlying issue behind the first bug [...] more than three years earlier") posted an excellent write-up about them here: https://ciolek.dev/posts/when-the-compiler-lies Here's my quick attempt at a plain text conversion:
When the compiler lies: breaking memory safety in safe Go
0001 go / memory safety / compilers
Early in March, I reported two compiler bugs affecting Go releases up to
1.26.1 which broke the Go memory safety guarantees using only safe Go
code.
This means the proof-of-concepts did not import unsafe, did not use CGO
and did not rely on custom assembly nor data races. Using specially
constructed code, I was able to turn both bugs into control-flow hijack,
and with the loop bug I also got execution of injected instructions.
I’m not including the full end-to-end exploits, to allow the fixed
releases to become more widely available. I’ll briefly describe the
issues and show the problematic code patterns though. An interesting
learning from this, perhaps obvious to some people, is that memory
safety is a property of the whole toolchain, not only the language
itself.
I found the bugs after I decided to have a closer look at the compiler.
I had reported several denial-of-service issues in the standard library
before. I have spent a few years contributing smaller patches to the
compiler on-and-off so it was a codebase I understood reasonably well.
In late November I found two errors in the devel version of the prove
optimization pass (the step that infers limits and “proves”
facts about SSA values, among others to perform bounds check
elimination).
If you exclude the generated rewrite tables, prove is basically the
second largest backend SSA optimization pass, behind only the register
allocator itself. It also deals with arithmetic and signedness, two easy
ways to shoot yourself in the foot. It turned out, the hunch was correct
as there were more problems lurking in that area. This time in the
release versions of Go.
BUG 1: A LOOP THAT WRAPPED AND THE COMPILER SWORE IT DIDN’T
CVE-2026-27143
The first problem existed in the prove/loopbce reasoning about induction
variables and bound checks.
The triggering shape is surprisingly simple:
for i := int8(0); i <= int8(120); i += int8(10) {
arr[i] = value
}
Question to you: if i == 120 and you perform the iteration, then add 10
to it, what will you get? What’s the answer? 130? It turns out the
answer is -126. int8 has eight bits, it wraps.
That’s literally the whole bug. The compiler believed something
different though.
If you make prove emit debug data, these are the facts it inferred:
Induction variable: limits [0,120], increment 10
Proved IsInBounds
Once it takes those facts, it will confidently remove bound checking for
array access if the array size fits within the induction variable
limits. In this case, the index can become negative. With correct
arrangement of surrounding code, we can make the program jump to an
arbitrary address, change control flow or even inject instructions.
I must admit I could not believe this at first and reran the program
many times on different machines and different versions of the compiler.
Vulnerability hunting gives a large thrill when you finally confirm
something is a real security bug. When I found it, I was really
ecstatic, as bad as it sounds. That feeling would change soon, but more
about that later.
BUG 2: A NO-OP CONVERSION THAT CHANGED NOTHING AND BROKE EVERYTHING
CVE-2026-27144
The second problem lived in another place, slightly later, in the SSA
lowering phase. The compiler knows to be careful when copying from one
part of an array into an overlapping nearby part. This is important for
safety. It turns out, if you wrapped the source into a conversion that
didn’t really change anything, the compiler forgot to do that.
type T [N][]uint64
// buggy shape
*p = T(*q)
// control shape
*p = *q
Those two lines should mean the same thing here. The conversion does not
really matter, the overlap does. p and q can point at partially
overlapping windows of the same underlying array. Once that is true, the
compiler should use the careful copy path. If it does not do that, it
can overwrite data it has not read yet. If you do that over simple data,
you get a wrong result. If you do that over more complex composite
values it gets more interesting. If you do that to slice values, later
code still trusts the result. A harmless-looking assignment then turns
into memory corruption.
TWO DISTINCT PROBLEMS, BUT REALLY THE SAME CAUSE
Those two bugs resided in two different parts of the compiler, but the
root cause was the same. In the first case, the compiler erased the
possibility of signed wrap. In the second one, it excluded the
possibility of overlap.
Those were two different mechanisms of failure, but both caused by the
same problem: counterfeit certainty. I guess that’s how compilers
break, you can write some compiler code and it looks reasonable,
there’s math and tests and everything seems correct. Things get
reviewed by multiple people and merged, but it turns out you may
accidentally upgrade “probably safe” into “proved safe”
way too soon.
Then, once the compiler starts to generate and optimize code based on
that promise, it will miscompile. Sometimes, like in those cases, you
can make the code dance around it and break security boundaries.
THEN GIT BLAME GOT PERSONAL
As I was wrapping up the email to the Go security team, I sat down to
write the last part of the advisory. I was still ecstatic about the
find. The final part was to figure out when this was introduced. I did
the routine thing and ran git blame.
It came back with my own name. That was not ideal and I did not like it.
I got a sinking feeling in my stomach and went from feeling really smart
to feeling really dumb, real fast.
It turns out that the underlying issue behind the first bug came from a
CL I landed more than three years earlier.
I got over it by the next day and now see it as an amusing story, but it
was quite an experience.
DISCLOSURE
I reported both issues back in March.
I must commend the Go security team as they are always excellent. Neal
got back to me within 3 minutes of the first report and within 4 minutes
of the second report. Mind you, those were sent days apart. Typically,
the Go security advisories take up to a week to get a response. I guess
seeing “memory corruption” and “compiler bug” in the
same email thread speeds the process up.
I’ll publish the full minimized reproducers, deeper technical dive
and more once the fixed releases become more broadly available.
LEARNINGS
The main learning for myself is that a memory-safe language is only as
safe as the entire toolchain enforcing its invariants. Frontend,
optimizer, lowering, runtime and code generation. All of those parts sit
within the trust boundary. If parts of it start certifying wrong proofs
and generating code on top of that, the source code can stay safe while
the compiled program stops being memory-safe.
Every optimization is a security claim and most of the time those claims
are true. In this case, those two were not.
STATUS
Disclosure in progress
Full end-to-end exploits and minimized reproducers are intentionally
omitted until the fixed releases are more widely available.
AFFECTED
Two compiler bugs affecting release versions of Go up to 1.26.1.
Alexander
Current thread:
- Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Alan Coopersmith (Apr 08)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Solar Designer (Apr 09)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Matthias Ferdinand (Apr 17)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Eli Schwartz (Apr 17)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Sam James (Apr 17)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Matthias Ferdinand (Apr 19)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Dimitri Ledkov (Apr 20)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Morten Linderud (Apr 20)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Demi Marie Obenour (Apr 20)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Michael Orlitzky (Apr 21)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Demi Marie Obenour (Apr 21)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Matthias Ferdinand (Apr 17)
- Re: Go 1.26.2 and Go 1.25.9 are released with 10 security fixes Solar Designer (Apr 09)
