Expr Recursion DoS: CVE-2025-68156 Patch and MaxDepth Guard

  • Thread Author
Expr’s evaluator can be crashed by ordinary builtin calls: a newly assigned CVE shows several widely used functions in the Expr Go package performed unbounded recursion over user-supplied data and could exhaust the Go runtime stack, allowing attackers to cause a process-level denial of service unless the library is upgraded or guarded.

Cartoon penguin trapped in a spiraling code stack, under MaxDepth Guard with patch notes.Background / Overview​

Expr is an expression language and runtime for Go that lets applications evaluate small expressions against maps, structs, and other Go values at runtime. It’s commonly embedded in configuration engines, rule evaluators, and systems that accept dynamic expressions from users or operators. Several of Expr’s builtin helpers — notably flatten, min, max, mean, and median — traversed input values recursively without enforcing any defensive limit on recursion depth. Under crafted input (very deep nesting or cyclic references), those traversals can recurse until the Go runtime stack overflows and the process panics and exits, producing a denial-of-service (DoS) condition for the host application. This vulnerability was tracked as CVE-2025-68156 and rated high severity (CVSS v3.1 = 7.5) by the project and public trackers; it was patched in Expr v1.17.7 with a change that introduces a recursion depth guard and exposes a configurable MaxDepth to accommodate legitimate deep structures.

Why this matters: availability, supply-chain, and real-world impact​

Applications embed expression engines to gain flexibility — but that flexibility becomes a live attack surface whenever untrusted or partially trusted input can shape the evaluation environment. The practical risk here is availability, not data leakage or privilege escalation: a remote attacker who can get an application to evaluate expressions against attacker-controlled or attacker-constructed data structures can repeatedly trigger stack exhaustion and crash processes. That is especially consequential for:
  • Network-facing services that evaluate user-provided expressions or rules (webhooks, policy engines, automation endpoints).
  • Multi-tenant platforms and server-side rule engines where one tenant’s expression or environment can affect shared workers.
  • Long-running agents, daemons, or sidecars embedded in infrastructure stacks; repeated crashes create instability and increase operational load.
The underlying class of bug — unbounded recursion in otherwise innocuous builtin routines — is low-complexity to exploit inside the right threat model. Organizations should treat such defects as urgent for availability-critical services.

Technical anatomy: what goes wrong (detailed)​

Builtin functions and recursive traversal​

The affected functions (for example, flatten, min, max, mean, median) are implemented to operate generically across arbitrarily nested containers. To compute an aggregation or flatten nested lists, the implementation walks nested arrays, maps, and structs recursively to reach primitive elements. If the traversal logic has no recursion-depth cap and also does not detect cycles, it is vulnerable to:
  • Deep nesting: A chain of container values nested N levels deep will cause N recursive calls.
  • Cyclic data: A container that contains a reference to itself (directly or via another container) will cause infinite recursion.
When the traversal depth is sufficiently large (or cyclic), the call stack grows until the Go runtime raises a panic (stack overflow), which typically terminates the goroutine and — unless recover is used at a safe boundary — crashes the process. The Expr project observed this and implemented a recursion guard to prevent a panic and instead return an error.

The fix​

The upstream patch introduces:
  • A maximum recursion depth enforced in the vulnerable builtin traversals.
  • A configurable parameter (exposed as builtin.MaxDepth) so applications that legitimately operate on very deep structures can increase the cap in a controlled manner.
  • Defensive tests that assert guarded behavior and avoid regressions.
The result: when the traversal depth exceeds the configured maximum, evaluation aborts gracefully with a descriptive error rather than triggering an uncontrolled panic and process exit.

Exploitation model and realistic attack scenarios​

Exploitability is contextual. Key factors that determine whether an attacker can weaponize this bug:
  • Can the attacker supply or influence the evaluation environment (the map/struct/array values) that Expr will examine? If yes, the attack surface exists.
  • Does the application evaluate expressions that call one of the affected builtin functions against that environment? If yes, a simple expression can exercise the vulnerable path.
  • Does the application run expression evaluation inside the main process without isolation or panic recovery? If yes, a single induced panic will likely terminate the host process.
Common real-world scenarios where this becomes actionable:
  • A policy engine that evaluates user-supplied rules over user-supplied JSON/object data.
  • A templating or configuration tool that accepts arbitrary data and uses Expr to compute derived values.
  • A webhook or API that runs user-specified expressions to filter or transform inbound payloads.
In environments where untrusted input cannot reach Expr (strict input validation, only operator-defined expressions, or isolated runtimes), the practical risk is lowered. Where untrusted inputs are accepted, attackers can craft cyclic references or deep nesting (deep JSON arrays or maps) to trigger the DoS reliably.

Affected versions and authoritative confirmation​

  • Affected: Expr versions earlier than v1.17.7.
  • Patched: v1.17.7 implements the recursion-limit guard and exposes builtin.MaxDepth for controlled configuration.
These facts are documented in the project’s GitHub Security Advisory and the pull request that implements the change, and have been mirrored in public vulnerability databases and trackers. If your dependency graph includes github.com/expr-lang/expr at versions older than 1.17.7, you should assume the library is vulnerable until upgraded.

Detection and inventory: how to find Expr in your codebase and artifacts​

Successful remediation requires knowing where Expr is used directly or transitively. Recommended inventory steps:
  • Source-level scan (for Go modules):
  • In module roots: run
  • go list -m all | grep github.com/expr-lang/expr
  • Or inspect go.mod / go.sum entries for github.com/expr-lang/expr.
  • Repository and CI artifact scan:
  • Search repository trees, lockfiles, and vendor directories for “expr” imports or module paths.
  • For monorepos and multi-language projects, search for the module path string across the repo: grep -R "github.com/expr-lang/expr" .
  • Binary and container image scan:
  • Use SBOM/SCA tooling or image scanners (Syft, Trivy, Snyk) to detect packaged Go modules in built images or serverless bundles.
  • For statically linked Go binaries (the common default), check build pipelines and vendor packaging lists to find which toolchain version and dependency set were used.
  • Runtime discovery:
  • Add runtime instrumentation to log the Expr package version when it is initialized (if possible), or add a lightweight wrapper in your own code to log builtin.MaxDepth / module.Version on startup.
These are practical, repeatable steps to locate both direct and transitive uses. Because Go binaries are frequently statically linked, source-only upgrades are not sufficient — you must rebuild and redeploy artifacts with the fixed module.

Short-term mitigations (if you can’t upgrade immediately)​

While upgrading to v1.17.7 is the correct fix, there are interim measures that reduce exploitability:
  • Sanitize and validate data before exposing it to Expr:
  • Reject extremely deep structures by bounding nesting depth during ingestion.
  • Strip or disallow cyclic references where possible.
  • Isolate expression evaluation:
  • Run expression evaluation in a dedicated, restartable subprocess or container; when the worker crashes, the host can restart it without affecting the main service.
  • Enforce resource limits (process CPU/memory, container cgroup limits, RLIMIT_STACK where feasible).
  • Use panic recovery boundaries:
  • Wrap Expr evaluations in Go code that uses defer + recover to catch panics and convert them into controlled errors; caution: recover must be used carefully to ensure program invariants are preserved and not to mask other serious bugs.
  • Rate-limit untrusted requests:
  • Impose rate limits and quotas on expression evaluation endpoints to limit the impact of repeated crash attempts.
These mitigations reduce immediate risk but do not substitute for upgrading to the patched build which prevents the panic at the root.

Remediation checklist — prioritized​

  • Upgrade dependencies and rebuild artifacts:
  • Update go.mod to reference github.com/expr-lang/expr v1.17.7 (or newer).
  • Rebuild all Go binaries and container images that include Expr.
  • Redeploy rebuilt artifacts through normal CI/CD pipelines; do not rely only on runtime replacement of modules without rebuilding.
  • Verify the new version is present in deployed images via image scans or runtime logging.
  • Configure the runtime:
  • If your application legitimately needs deep traversal, set builtin.MaxDepth to a carefully chosen, well-documented value that balances correctness and safety. Otherwise use the conservative default.
  • Harden endpoints that accept untrusted expressions/environments:
  • Enforce input validation, quotas, and request authentication where appropriate.
  • Isolate evaluation to worker processes to reduce blast radius.
  • Add tests and monitoring:
  • Add unit tests that emulate deep and cyclic inputs to ensure evaluation returns sanitized errors rather than panics.
  • Monitor crash counts, OOM/killed-by-signal events, and process restart rates after patching and during rollout.
  • Communicate with vendors:
  • If you consume third-party products that embed Expr (for instance, open-source projects or vendor appliances), ask vendors whether they have issued patched builds or when they plan to update their release artifacts.

Detection in the wild and telemetry — what we know​

Public vulnerability trackers (NVD, GitHub advisory, and multiple mirrors) list CVE-2025-68156, note the affected range (< 1.17.7), and reflect a CVSS v3.1 vector of AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (high availability impact but no confidentiality or integrity loss). The upstream fix and PR are public, and the advisory credits the reporter, along with a clear remediation path. There were no widely publicized exploit chains or mass-exploitation telemetry at the time of disclosure; however, algorithmic and recursion vulnerabilities are trivial to weaponize in the appropriate trust model, so defenders must not equate “no known in-the-wild exploit” with “no risk.”

Broader context: this is not unique — algorithmic complexity and recursion bugs recur​

Unbounded recursion and algorithmic complexity vulnerabilities recur across languages and libraries: similar stack-exhaustion or resource-exhaustion issues have been reported in ASN.1 parsers, XML processors, and JSON/XML tree builders. These problems are classic security pitfalls: they’re easy to introduce during library design and costly in production because they often reside in code paths that developers assume are benign (helpers, error formatting, aggregation helpers). One concrete analogy: a parsing library that implemented recursive DER parsing without a depth cap resulted in stack exhaustion and required adding a configurable max-depth guard — the pattern and fix mirrors Expr’s corrective approach.

Risks, trade-offs, and critical analysis​

Strengths of the upstream response​

  • The Expr maintainers released a targeted patch that is small and low-risk: guarding recursion depth is a surgical change that avoids redesigning the library.
  • The patch makes the limit configurable (builtin.MaxDepth), which balances safety with legitimate advanced use-cases.
  • The project published a security advisory and an explicit PR, which gives operators clear upgrade instructions and a reproducible fix path.

Residual and operational risks​

  • Static linking in Go means upgrades require rebuilding and redeploying binaries and container images; organizations that delay rebuilds remain exposed even after downstream libraries publish patches.
  • Some vendors or third-party products may bundle Expr (or embed compiled Go binaries) and may be slow to release patched versions, increasing the remediation tail.
  • Relying on panic recovery as a mitigation is brittle: recovered panics can leave programs in inconsistent states unless carefully designed and tested.
  • The presence of a configurable MaxDepth is useful, but operational teams must determine safe values for their workloads and be disciplined about documenting and testing exceptions.

Prioritization guidance​

  • Treat this as high-priority for any public-facing service that evaluates expressions or ingests user-supplied data structures into an expression evaluation context.
  • For internal-only services with strict input validation and no user-exposed expression interfaces, treat the upgrade as medium priority but schedule the rebuild within standard maintenance windows.

Practical tips for Windows administrators and developers​

  • Inventory Windows builds and images: many Go-built tools and agents run on Windows (agents, automation tools, developer utilities). Check build logs and CI pipelines to determine whether a given Windows artifact was built with a module set that included Expr < 1.17.7.
  • Rebuild Windows executables: if you package Go binaries for Windows (for example, .exe from static builds), rebuilding the binary with the patched module is mandatory.
  • Use image scanning for Windows containers: if Windows containers are used, run image scans to detect vulnerable module versions.
  • Add an emergency restart policy for isolated evaluator workers: set automatic restart/back-off to avoid cascading failures if an unpatched evaluator is abused.
  • Educate dev teams: ensure developers know the danger of evaluating expressions against untrusted payloads and teach them to prefer validated, whitelisted expression features or sandboxed execution.

Conclusion​

CVE-2025-68156 is a classic and practical example of how useful helper functions can become an availability hazard when they traverse untrusted data without defensive limits. The Expr maintainers addressed the problem with a straightforward and correct fix — adding a recursion-depth guard and making it configurable — and operators can eliminate the vulnerability by upgrading to v1.17.7 and rebuilding artifacts. In the meantime, organizations should inventory usage, apply isolation and input validation, and treat expression-evaluation endpoints as security-sensitive surfaces. Algorithmic complexity and recursion vulnerabilities will continue to appear; this case reinforces the need for bounding work, adding tests for pathological inputs, and making evaluation safety explicit in both library design and operational deployment.
Source: MSRC Security Update Guide - Microsoft Security Response Center
 

Back
Top