URL has been copied successfully!
Detect Go’s silent arithmetic bugs with go-panikint
URL has been copied successfully!

Collecting Cyber-News from over 60 sources

Detect Go’s silent arithmetic bugs with go-panikint

Go’s arithmetic operations on standard integer types are silent by default, meaning overflows “wrap around” without panicking. This behavior has hidden an entire class of security vulnerabilities from fuzzing campaigns. Today we’re changing that by releasing go-panikint, a modified Go compiler that turns silent integer overflows into explicit panics. We used it to find a live integer overflow in the Cosmos SDK’s RPC pagination logic, showing how this approach eliminates a major blind spot for anyone fuzzing Go projects. (The issue in the Cosmos SDK has not been fixed, but a pull request has been created to mitigate it.)

The sound of silence

In Rust, debug builds are designed to panic on integer overflow, a feature that is highly valuable for fuzzing. Go, however, takes a different approach. In Go, arithmetic overflows on standard integer types are silent by default. The operations simply “wrap around,” which can be a risky behavior and a potential source of serious vulnerabilities. This is not an oversight but a deliberate, long-debated design choice in the Go community. While Go’s memory safety prevents entire classes of vulnerabilities, its integers are not safe from overflow. Unchecked arithmetic operations can lead to logic bugs that bypass critical security checks. Of course, static analysis tools can identify potential integer overflows. The problem is that they often produce a high number of false positives. It’s difficult to know if a flagged line of code is truly reachable by an attacker or if the overflow is actually harmless due to mitigating checks in the surrounding code. Fuzzing, on the other hand, provides a definitive answer: if you can trigger it with a fuzzer, the bug is real and reachable. However, the problem remained that Go’s default behavior wouldn’t cause a crash, letting these bugs go undetected.

How go-panikint works

To solve this, we forked the Go compiler and modified its backend. The core of go-panikint’s functionality is injected during the compiler’s conversion of code into Static Single Assignment (SSA) form, a lower-level intermediate representation (IR). At this stage, for every mathematical operation, our compiler inserts additional checks. If one of these checks fails at runtime, it triggers a panic with a detailed error message. These runtime checks are compiled directly into the final binary. In addition to arithmetic overflows, go-panikint can also detect integer truncation issues, where converting a value to a smaller integer type causes data loss. Here’s an example:

var x uint16 = 256 result := uint8(x) 
Figure 1: Conversion leading to data loss due to unsafe casting

While this feature is functional, we found that it generated false positives during our fuzzing campaigns. For this reason, we will not investigate further and will focus on arithmetic issues. Let’s analyze the checks for a program that adds up two numbers. If we compile this program and then decompile it, we can clearly see how these checks are inserted. Here, the if condition is used to detect signed integer overflow: Case 1: Both operands are negative. The result should also be negative. If instead the result (sVar23) becomes larger (less negative or even positive), this indicates signed overflow. Case 2: Both operands are non-negative. The result should be greater than or equal to each operand. If instead the result becomes smaller than one operand, this indicates signed overflow. Case 3: Only one operand is negative. In this case, signed overflow cannot occur.

if (*x_00 == '+') {  val = (uint32)*(undefined8 *)(puVar9 + 0x60);  sVar23 = val + sVar21;  puVar17 = puVar9 + 8;  if (((sdword)val < 0 && sVar21 < 0) && (sdword)val < sVar23 

Loading

Share via Email
Share on Facebook
Tweet on X (Twitter)
Share on Whatsapp
Share on LinkedIn
Share on Xing
Copy link