VYPR
Medium severity6.5NVD Advisory· Published Jun 10, 2026

CVE-2026-46540

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

Patches

1
5e9db554f824

Fixes rebranch logic in light blockchain

https://github.com/nimiq/core-rs-albatrossviquezclaudioApr 7, 2026via nvd-ref
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

3

News mentions

1