CVE-2026-46540
Description
Nimiq light clients prior to v1.4.0 could stall or verify blocks incorrectly after a rebranch due to improper state updates.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Nimiq light clients prior to v1.4.0 could stall or verify blocks incorrectly after a rebranch due to improper state updates.
Vulnerability
Nimiq, a Rust implementation of the Nimiq Proof-of-Stake protocol, is vulnerable in versions prior to 1.4.0. The LightBlockchain::rebranch() function fails to update critical state variables such as self.macro_head, self.election_head, and self.current_validators when adopting a fork chain whose tip is a macro block (checkpoint or election). This contrasts with the full Blockchain::rebranch() which correctly updates this state. This oversight occurs in the blockchain/src/blockchain/push.rs file.
Exploitation
An attacker could potentially trigger a rebranch operation in a light client. If the adopted fork's tip is a macro block, the client's state becomes inconsistent. Subsequent macro blocks pushed to the client will be verified against a stale macro_head, or if the rebranch target was an election block, subsequent blocks will fail verify_validators() due to stale current_validators, leading to a complete stall of the light client's chain progression.
Impact
If a rebranch occurs to a macro block, subsequent macro block verifications will use an incorrect predecessor, potentially leading to the acceptance of invalid blocks or chain progression failures. If the rebranch target was an election block, the light client will completely stall, failing to process any further blocks, effectively rendering it unusable and potentially leading to a denial of service for the light client.
Mitigation
This vulnerability has been patched in Nimiq version 1.4.0, released on June 10, 2026 [2]. There are no workarounds available. The vulnerability is detailed in [1] and [3].
AI Insight generated on Jun 10, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2(expand)+ 1 more
- (no CPE)
- (no CPE)range: <1.4.0
Patches
15e9db554f824Fixes rebranch logic in light blockchain
2 files changed · +101 −0
light-blockchain/src/push.rs+16 −0 modified@@ -326,6 +326,22 @@ impl LightBlockchain { this.head = new_head_info.head.clone(); + // If the new head is a macro block, update the macro-related state as well. + if let Block::Macro(ref macro_block) = new_head_info.head { + this.macro_head = macro_block.clone(); + + this.chain_store + .clear_old_blocks(new_head_info.head.block_number()); + + if macro_block.is_election() { + this.election_head = macro_block.clone(); + this.current_validators = macro_block.get_validators(); + + // Store the election block header. + this.chain_store.put_election(macro_block.header.clone()); + } + } + let mut reverted_blocks = Vec::with_capacity(revert_chain.len()); for (hash, chain_info) in revert_chain.into_iter().rev() { log::debug!(
light-blockchain/tests/push.rs+85 −0 modified@@ -673,6 +673,91 @@ fn it_can_rebranch_to_inferior_macro_block() { ); } +#[test] +fn rebranch_to_macro_updates_macro_head() { + let producer1 = TemporaryLightBlockProducer::new(); + let producer2 = TemporaryLightBlockProducer::new(); + + let inferior = producer1.next_block(vec![], false); + producer2.next_block(vec![], true); + assert_eq!(producer2.push(inferior), Ok(PushResult::Ignored)); + + for _ in 1..Policy::blocks_per_batch() - 1 { + let inferior = producer1.next_block(vec![], false); + producer2.next_block(vec![], false); + assert_eq!(producer2.push(inferior), Ok(PushResult::Ignored)); + } + + let macro_block = producer1.next_block(vec![], false); + assert!(macro_block.is_macro()); + + assert_eq!( + producer2.push(macro_block.clone()), + Ok(PushResult::Rebranched) + ); + + let light_blockchain = producer2.light_blockchain.read(); + assert_eq!(light_blockchain.head.hash(), macro_block.hash()); + assert_eq!(light_blockchain.macro_head.hash(), macro_block.hash()); +} + +#[test] +fn rebranch_to_election_macro_updates_light_state() { + let producer1 = TemporaryLightBlockProducer::new(); + let producer2 = TemporaryLightBlockProducer::new(); + + for _ in 0..Policy::blocks_per_epoch() - Policy::blocks_per_batch() { + let block = producer1.next_block(vec![], false); + assert_eq!(producer2.push(block), Ok(PushResult::Extended)); + } + + let inferior = producer1.next_block(vec![], false); + producer2.next_block(vec![], true); + assert_eq!(producer2.push(inferior), Ok(PushResult::Ignored)); + + for _ in 1..Policy::blocks_per_batch() - 1 { + let inferior = producer1.next_block(vec![], false); + producer2.next_block(vec![], false); + assert_eq!(producer2.push(inferior), Ok(PushResult::Ignored)); + } + + let election_block = producer1.next_block(vec![], false); + assert!(election_block.is_election()); + + assert_eq!( + producer2.push(election_block.clone()), + Ok(PushResult::Rebranched) + ); + + { + let light_blockchain = producer2.light_blockchain.read(); + let election_hash = election_block.hash(); + let election_epoch = Policy::epoch_at(election_block.block_number()); + + assert_eq!(light_blockchain.head.hash(), election_hash); + assert_eq!(light_blockchain.macro_head.hash(), election_hash); + assert_eq!(light_blockchain.election_head.hash(), election_hash); + assert_eq!( + light_blockchain.current_validators, + election_block.validators() + ); + assert_eq!( + light_blockchain.chain_store.get_election(election_epoch), + Some(&election_block.unwrap_macro_ref().header) + ); + } + + let next_block = producer1.next_block(vec![], false); + let full_result = producer2.temp_producer.push(next_block.clone()); + let light_result = LightBlockchain::push( + producer2.light_blockchain.upgradable_read(), + remove_micro_body(next_block), + ); + + assert_eq!(full_result, Ok(PushResult::Extended)); + assert_eq!(light_result, full_result); +} + #[test] fn can_push_zkps() { let temp_producer1 = TemporaryBlockProducer::new();
Vulnerability mechanics
Root cause
"LightBlockchain::rebranch() fails to update all necessary state variables when adopting a fork ending in a macro block."
Attack vector
An attacker can trigger this vulnerability by causing the light client to adopt a fork chain whose tip is a macro block (checkpoint or election) [ref_id=1]. This is possible because the `LightBlockchain::rebranch()` function does not correctly update all relevant state variables, unlike the full `Blockchain::rebranch()` function [ref_id=1]. The vulnerability is exploitable over the network without any user interface interaction or prior authentication.
Affected code
The vulnerability exists in the `LightBlockchain::rebranch()` function. This function fails to update `self.head`, `self.macro_head`, `self.election_head`, and `self.current_validators` when the adopted fork chain's tip is a macro block. This is contrasted with the full `Blockchain::rebranch()` at `blockchain/src/blockchain/push.rs:504-518` [ref_id=1].
What the fix does
The patch updates the `LightBlockchain::rebranch()` function to correctly update `self.macro_head`, `self.election_head`, `self.current_validators`, and store the election header in the `chain_store` when the adopted fork chain's tip is a macro block [ref_id=1]. This ensures that subsequent block verifications use the correct predecessor and validator set, preventing chain progression stalls or incorrect verifications [ref_id=1]. The vulnerability is fixed in version 1.4.0.
Preconditions
- networkThe vulnerability is network-exploitable.
- authNo authentication or privileges are required to exploit this vulnerability.
- inputThe attacker must provide a fork chain whose tip is a macro block.
Generated on Jun 10, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
1- Nimiq Core Rs Albatross: Seven Denial-of-Service Vulnerabilities DisclosedVypr Intelligence · Jun 10, 2026