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

Reply via email to