When the compiler lies: breaking memory safety in safe Go

April 8, 2026
Close-up of a rusty padlock on a decaying latch in San Juan, Puerto Rico.
Photo by Joaquin Lopez on Pexels

What happened

It has been reported that a researcher disclosed two serious Go compiler bugs affecting releases up to 1.26.1 that, alarmingly, can break Go’s memory-safety guarantees using only “safe” Go code. No unsafe imports, no CGO, no hand-written assembly and no data races — allegedly the proof‑of‑concepts relied purely on crafted Go patterns and compiler misreasoning. The author says they withheld full end‑to‑end exploits to let fixes propagate; prudent, but also a sobering reminder: memory safety is a property of the whole toolchain, not just the language syntax.

The bugs

The first bug (CVE-2026-27143) lives in the prove pass’s loop/bound-check elimination logic. The compiler inferred induction-variable limits for an int8 loop and confidently elided bounds checks — even though int8 wraps (120 + 10 → -126). Remove the checks, and under the right layout you can turn an index into a control‑flow primitive. The researcher writes that they converted the issue into control‑flow hijack and, in the loop case, even execution of injected instructions — allegedly. The thrill of finding it? High. The horror of what it enables? Higher.

The second issue (CVE-2026-27144) is more subtle and lives in SSA lowering. A no‑op conversion — something like assigning p = T(q) where T is effectively the same type — made the compiler skip the careful overlapping‑copy path. That conversion should be a non‑event. Instead, the optimizer treated it differently, allowing overwrites of unread data; sometimes you just get wrong results, sometimes things go off the rails. It’s the sort of thing that makes compiler engineers wince: small rewrite, big consequences.

Why it matters

So what now? Patch, audit, repeat. Compiler optimizations are where performance meets peril; prove and SSA lowering are complex, high‑impact passes and they can, as this case shows, shoot you in the foot if they infer the wrong facts. This episode slots neatly into the broader memory‑safety debate (hello, Rust), but its lesson is simple: choosing a “safe” language is only part of the story. The toolchain must earn that label every day. Users should upgrade when fixes are out, and maintainers should treat backend passes as first‑class security attack surface.

Sources: ciolek.dev, Lobsters