Skip to content

GHSA-gf9r-m956-97qx CVE-2026-52735

zebrad has consensus divergence via P2SH sigop undercount in pure-Rust disabled-opcode parser

Critical crates.io
Severity
0.0 / 10
Critical · CVSS 4.0
Exploit chance (EPSS)
0.51%
Chance of exploitation in the next 30 days. Higher than 43.26% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-684 Incorrect Provision of Specified Functionality
Fix
Available
2 packages have a fixed version

Description

Am I affected

You are affected if:

  1. You run any version of zebrad up to and including v4.4.1.
  2. Your node validates blocks on mainnet, testnet, or any network where both Zebra and zcashd nodes participate.

All default configurations are affected. No feature flags, non-default settings, or special build options are required.

Summary

Zebra's P2SH sigop counter uses a pure-Rust code path that short-circuits on disabled opcodes (such as OP_CODESEPARATOR), returning a partial count of zero for any sigops following the disabled opcode. The reference implementation (zcashd) correctly counts through disabled opcodes in its static sigop analysis. This produces a consensus divergence: Zebra accepts blocks that zcashd rejects when the block-wide MAX_BLOCK_SIGOPS = 20,000 threshold is crossed on one side but not the other.

An attacker can exploit this without mining capability. Broadcasting transactions that spend P2SH outputs with malicious redeem scripts is sufficient; any Zebra miner who includes those transactions in a block triggers a chain split between Zebra and zcashd validators.

Details

The P2SH sigop counter at zebra-script/src/lib.rs:399 calls script::Code(redeemed_bytes).sig_op_count(true), which is a pure-Rust path through zcash_script-0.4.4. The legacy (non-P2SH) sigop counter at lib.rs:282-289 correctly uses the C++ FFI via interpreter.legacy_sigop_count_script(). Only the P2SH path bypasses the FFI.

The Rust parser in zcash_script-0.4.4/src/opcode/mod.rs:1247-1260 treats 16 disabled opcodes (0x7e through 0xab, including OP_CAT, OP_SUBSTR, OP_AND, OP_OR, OP_XOR, OP_2MUL, OP_2DIV, OP_MUL, OP_DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT, and OP_CODESEPARATOR) as Err(Error::Disabled(...)). The sig_op_count function at iter.rs:104-115 uses try_fold, which terminates on the first Err and returns the partial sum accumulated so far.

zcashd's GetOp2 (script.h:514-562) returns true for all non-push opcodes including the disabled range. Its GetSigOpCount(true) (script.cpp:152-174) continues counting through disabled opcodes. zcashd rejects disabled opcodes at execution time in the interpreter, not during static sigop analysis.

A redeem script of [0xab, OP_CHECKMULTISIG x 50] produces: Zebra = 0 sigops, zcashd = 1,000 sigops. Across 21 inputs in a block, Zebra computes 0 while zcashd computes 21,000, crossing the MAX_BLOCK_SIGOPS = 20,000 threshold on one side only.

Patches

Patched in Zebra 4.4.2. The fix routes the P2SH sigop counter through the same C++ FFI already used by the legacy sigop counter.

Workarounds

There is no configuration-level workaround. All Zebra nodes validating blocks on a network shared with zcashd are affected. Upgrade as soon as the patched version is available.

Impact

A chain split between Zebra and zcashd validators. The attacker broadcasts spending transactions referencing P2SH outputs whose redeem scripts contain a disabled opcode followed by OP_CHECKSIG or OP_CHECKMULTISIG opcodes. When a Zebra miner (estimated ~30% of current network hashrate) includes these transactions in a block, Zebra validators accept the block while zcashd validators reject it with bad-blk-sigops. The two halves of the network diverge and every subsequent block extending the Zebra-side tip inherits the divergence.

The attacker does not need mining capability, RPC access, or any special privileges. The cost is the transaction fees for the funding and spending transactions.

Credit

Reported by @samsulselfut via a private GitHub Security Advisory submission.

Affected versions

crates.io

PackageVulnerableFixed in
zebra-script
< 7.0.0
7.0.0
zebrad
< 4.5.0
4.5.0

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

zebra-script crates.io → 7.0.0

cargo
cargo update -p zebra-script --precise 7.0.0

zebrad crates.io → 4.5.0

cargo
cargo update -p zebrad --precise 4.5.0

Severity breakdown

CVSS 4.0 0.0

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

Attack vector
Network
Attack complexity
Low
Attack requirements
None
Privileges required
None
User interaction
None
Confidentiality (vulnerable system)
None
Integrity (vulnerable system)
High
Availability (vulnerable system)
None
Confidentiality (subsequent systems)
None
Integrity (subsequent systems)
High
Availability (subsequent systems)
High

Weakness

CWE-684: Incorrect Provision of Specified Functionality

The code does not function according to its published specifications, potentially leading to incorrect usage.

How to prevent it
  • Implementation: Ensure that your code strictly conforms to specifications.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-ww9q-8r59-xv46
Zebra: Missing copy constraint in halo2_gadgets variable-base scalar multiplication allows under-constrained base, breaking Orchard Action circuit soundness
Critical Also affects zebrad
GHSA-63wg-wjjj-7cp8
Zebra Address Book Aborted by IPv4-Mapped Mempool Misbehavior Update
High Also affects zebrad
GHSA-65jj-fmw8-468q
zebrad has unbounded memory leak in mempool download pipeline via timeout path cancel_handles retention
Medium Also affects zebrad
GHSA-2gf8-q9rr-jq3h
zebrad has persistent on-disk corruption of Sapling/Orchard subtree roots after chain fork via pop_tip
Medium Also affects zebrad
GHSA-hhm7-qrv5-h4r6
Zebra: Repeated Non-Finalized Shielded Transaction Aborts Zebra Before Duplicate-Nullifier Rejection
Medium Also affects zebrad
GHSA-w834-cf6p-9m9w
Zebra: Finalized address balance credit-first overflow on consensus-valid blocks
Medium Also affects zebrad
GHSA-gvjc-3w7c-92jx
Zebra has sync restart poisoning from single unauthenticated peer via above-lookahead block
Medium Also affects zebrad
GHSA-4m69-67m6-prqp
Zebra has block suppression via NU5 same-header body poisoning of sent-hash cache
High Also affects zebrad
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor