CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-3WHF-VGF2-9W6G

GHSA-3WHF-VGF2-9W6G: Denial of Service via Unbounded Recursion and State Panic in zaino-state

Amit Schendel
Amit Schendel
Senior Security Researcher

Jul 31, 2026·6 min read·4 visits

Executive Summary (TL;DR)

zaino-state is vulnerable to unconstrained recursive block reorganization and an unhandled panic during state cache pruning, causing denial of service.

The zaino-state crate contains two critical flaws in its block reorganization and state synchronization logic. An unbounded recursive async function handling block reorganization fails to validate cyclic relationships, enabling network peers to cause infinite loops that exhaust CPU and memory resources. Furthermore, a logical pruning error during non-finalized block cache trimming can purge all cached blocks, triggering an immediate panic and crash of the daemon.

Vulnerability Overview

The zaino-state crate is a core state-tracking component in the Zaino indexer, which indexes and monitors state transitions for the Zcash blockchain. The indexer relies on maintaining a non-finalized state representing potential chain reorganizations (reorgs) prior to transaction finality.

To manage state updates, the component processes incoming blocks and traces changes backward along parent hash pointers to align with the active main chain. This model introduces an attack surface where processed chain metadata influences recursive state traversal. Specifically, malicious or incorrectly formatted inputs can exploit logic paths within the state tracking mechanism.

This advisory tracks two operational vulnerabilities: an unconstrained recursive async loop in handle_reorg and an unhandled panic state within the snapshot cache trimming process. Both vulnerabilities allow an unauthenticated actor to disrupt the availability of the indexing node, leading to resource exhaustion or immediate process crashes.

Root Cause Analysis

The primary vulnerability arises from the implementation of NonFinalizedState::handle_reorg. When the indexer detects a block reorganization, it must verify parent relationships across blocks that are not yet finalized. It achieves this by recursively traversing backward along parent hashes using the prev_hash_bytes_serialized_order method to find a common ancestor.

Because Rust async functions cannot have an unconstrained size at compile time, recursive async functions require heap allocation via pin boxing (Box::pin). However, the implementation of handle_reorg lacked cycle detection and an explicit recursion depth limit. If a node processes a block that references itself as its parent, or a group of blocks that form a cyclic reference graph, the loop executes indefinitely.

The second vulnerability resides in NonfinalizedBlockCacheSnapshot::remove_finalized_blocks. This function trims finalized blocks from the active cache using a simple retain logic (block.height() >= finalized_height). Under extreme network forks or rapid finality shifting, if the finalized_height exceeds the height of all blocks in the cache, the pruning logic retains zero blocks. The downstream code then executes .expect("empty snapshot impossible") on an empty map, triggering an immediate process panic.

Code Analysis

The original vulnerable implementation of the recursive async function structure lacks bounds checks or loop state. The recursive call pattern within handle_reorg was defined as follows:

// Line 459 and 483 in non_finalised_state.rs (Original)
Some(prev_block) => {
    if !working_snapshot
        .heights_to_hashes
        .values()
        .any(|hash| hash == prev_block.hash())
    {
        // Unbounded recursive call allocating a new heap future on every iteration
        Box::pin(self.handle_reorg(working_snapshot, &prev_block)).await?
    } else {
        prev_block
    }
}

The corresponding patch introduces a recursion_count: u8 parameter initialized to 0 at the entry point of the reorganization handler. The function is modified to validate the parameter on each recursive step:

// Patched version showing recursive depth validation
async fn handle_reorg(
    &self,
    working_snapshot: &mut NonfinalizedBlockCacheSnapshot,
    block: &impl Block,
    recursion_count: u8, // Introduced recursion counter
) -> Result<IndexedBlock, SyncError> {
    // Limit recursion to 110 steps to account for complete reorg margins
    if recursion_count > 110 {
        return Err(SyncError::ReorgFailure(
            "reorg handling recursed beyond reason".to_string(),
        ));
    }
    // ...
    // Recursive calls increment the counter
    Box::pin(self.handle_reorg(working_snapshot, &prev_block, recursion_count + 1)).await?
}

For the snapshot pruning panic, the previous logic unconditionally pruned blocks based on height without ensuring the structural integrity of the cache. The patched logic obtains the highest block hash first, ensuring that at least one block is preserved:

// Patched implementation ensuring the top block is retained
fn remove_finalized_blocks(&mut self, finalized_height: Height) {
    let top_block_hash = match self
        .heights_to_hashes
        .iter()
        .max_by_key(|(height, _hash)| *height)
    {
        Some((_height, hash)) => *hash,
        None => return,
    };
    
    // Retain blocks that meet the height requirement OR match the top block hash
    self.blocks.retain(|_hash, block| {
        block.height() >= finalized_height || block.hash() == &top_block_hash
    });
}

Exploitation Mechanics

To exploit the unbounded recursion vulnerability, an attacker must have the capability to feed custom block headers into the indexing pipeline. This occurs if the indexing daemon connects to a malicious validator node, or if the network sync interface accepts block data from untrusted peers.

An attacker constructs a block header where the parent block hash field matches the block's own cryptographic hash:

$$\text{header.previous_block_hash} = \text{block.hash()}$$

When this crafted header is processed, the daemon attempts to trace the history. It extracts the parent hash, retrieves the same block, and recursively calls handle_reorg. Because the recursion relies on Box::pin, every single call allocates memory on the heap while keeping the CPU thread fully saturated.

Alternatively, an attacker can create a multi-block loop (such as A to B to A) to achieve the same infinite recursive state. The thread will spin continuously at 100% CPU usage, rapidly leaking heap space until the platform kernel terminates the process due to out-of-memory constraints.

Impact Assessment

The impact of these two vulnerabilities is a complete Denial of Service (DoS) of the zaino indexer node. Because block synchronization runs within critical application execution contexts, blocking the state reorganization thread stalls all downstream operations.

While the CVSS v4 score is calculated as 5.1 (Medium), the real-world operational impact on indexer infrastructures is significant. A node impacted by the infinite recursion bug remains unresponsive and consumes system-wide computational resources, potentially affecting adjacent applications co-located on the same server instance.

The snapshot pruning panic is deterministic and results in a clean termination of the binary via Rust's panic infrastructure. While a panic does not lead to a resource leak, it terminates the service immediately. If automatic process recovery is not configured, the service remains offline indefinitely.

Remediation & Mitigation Guidance

The primary remediation strategy is upgrading the zaino-state dependency to version 0.4.1 or higher. This version contains the mitigations merged in Pull Request #1172, enforcing a maximum recursion depth of 110 frames and ensuring snapshot stability during pruning.

While the recursion limit successfully prevents the infinite loop, it is a defensive bound rather than a formal cycle detection mechanism. The code continues to process up to 110 recursive heap allocations per malicious block. For comprehensive resilience, operators should restrict validator connections to trusted network peers.

For systems where immediate code updates are not feasible, process supervision is highly recommended. Utilizing monitoring tools like systemd with automatic restart properties ensures that nodes crashing due to the snapshot panic can recover automatically. To handle CPU spikes from recursive loops, resource limits (such as systemd CPUQuota or container-based limits) should be applied to prevent the indexer from degrading overall host performance.

Official Patches

ZingoLabsFix PR for reorganization limits and snapshot panics

Fix Analysis (3)

Technical Appendix

CVSS Score
5.1/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N

Affected Systems

zaino-state indexer nodes

Affected Versions Detail

Product
Affected Versions
Fixed Version
zaino-state
zingolabs
>= 0.0.0, < 0.4.10.4.1
AttributeDetail
CWE IDCWE-770
Attack VectorNetwork
CVSS v4 Score5.1
ImpactDenial of Service (CPU & Memory Exhaustion, Daemon Crash)
Exploit StatusPoC Mechanics Documented
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1499.004Endpoint Denial of Service: Application Exhaustion
Impact
CWE-770
Allocation of Resources Without Limits or Throttling

The software allocates search or recursion resources without restricting the quantity, allowing consumption of CPU and memory leading to a denial of service.

Known Exploits & Detection

GitHub AdvisoryAdvisory documenting the cyclic block construction vector and memory consumption mechanisms.

Vulnerability Timeline

Original fix for the reorg recursion depth limit committed
2026-06-05
Finalized block trim panic fix committed
2026-06-09
Vulnerability publicly disclosed and published in GHSA database
2026-07-31

References & Sources

  • [1]GitHub Advisory Page for GHSA-3WHF-VGF2-9W6G
  • [2]Advisory Source
  • [3]Fix Pull Request #1172

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•27 minutes ago•CVE-2026-53551
6.9

CVE-2026-53551: Improper Input Validation in free5GC Authentication Server Function (AUSF)

Improper input validation of the supiOrSuci field in free5GC Authentication Server Function (AUSF) allows unauthenticated remote attackers to trigger an unhandled parsing exception, resulting in a Denial of Service (DoS) and internal stack trace exposure.

Alon Barad
Alon Barad
0 views•5 min read
•about 2 hours ago•CVE-2026-53504
7.5

CVE-2026-53504: Regular Expression Denial of Service (ReDoS) in Thumbor Convolution Filter

A critical Regular Expression Denial of Service (ReDoS) vulnerability exists in Thumbor prior to version 7.8.0. The vulnerability resides within the dynamic filter-parsing engine, specifically inside the 'convolution' filter parameter processing logic. Due to overlapping and nested quantifiers in the regular expression used to parse matrix values, a remote, unauthenticated attacker can supply a specially crafted, malformed filter payload inside a request URL. This causes Python's standard NFA-based regular expression engine to undergo exponential backtracking, exhausting CPU resources and leading to a complete Denial of Service.

Amit Schendel
Amit Schendel
5 views•6 min read
•about 3 hours ago•CVE-2026-54737
7.3

CVE-2026-54737: Prototype Pollution in @phun-ky/defaults-deep

CVE-2026-54737 is a high-severity Prototype Pollution vulnerability in the @phun-ky/defaults-deep npm library prior to version 2.0.5. Due to unsafe recursive object merging, unauthenticated attackers can supply structured payloads that modify the properties of Object.prototype, compromising the runtime process state.

Alon Barad
Alon Barad
6 views•5 min read
•about 4 hours ago•CVE-2026-54729
8.7

CVE-2026-54729: SSRF Protection Bypass in dssrf-js via NXDOMAIN Resolution Discrepancy

CVE-2026-54729 is a critical Server-Side Request Forgery (SSRF) bypass vulnerability in the dssrf-js Node.js library prior to version 1.0.5. The flaw occurs because the library's DNS validation mechanism incorrectly treats domains like 'localhost' as safe when the configured upstream DNS resolver returns NXDOMAIN. Since the system's HTTP client later falls back to OS-level resolution (resolving 'localhost' to '127.0.0.1'), attackers can bypass validation and access internal loopback addresses.

Amit Schendel
Amit Schendel
5 views•6 min read
•about 5 hours ago•GHSA-XRMJ-5G4G-8987
4.2

GHSA-xrmj-5g4g-8987: Workflow Template Injection in @dynatrace-oss/dynatrace-mcp-server

A template injection vulnerability in @dynatrace-oss/dynatrace-mcp-server allows untrusted input to be interpolated directly into Dynatrace Workflows using Jinja2 syntax, leading to persistent data exposure and exfiltration.

Amit Schendel
Amit Schendel
7 views•5 min read
•about 11 hours ago•CVE-2026-67437
7.5

CVE-2026-67437: Unauthenticated Denial of Service via OAuth2 State Memory Exhaustion in OliveTin

An uncontrolled resource consumption vulnerability (CWE-400) in OliveTin allows unauthenticated remote attackers to exhaust server memory and trigger a denial of service (DoS). By repeatedly initiating the OAuth2 login flow without completing it, attackers can force the server to allocate state variables in an unbounded in-memory map. This heap-based resource exhaustion eventually causes the host operating system to terminate the OliveTin process via the Out-Of-Memory (OOM) killer.

Amit Schendel
Amit Schendel
7 views•8 min read