Skip to content

[Security] Staking precompile uses Address::ZERO as SYSTEM_ADDRESS #96

@qj0r9j0vc2

Description

@qj0r9j0vc2

Problem

The staking precompile defines SYSTEM_ADDRESS = Address::ZERO, which creates a security risk because the zero address is commonly used as a default/null value elsewhere in Ethereum.

Location

crates/execution/src/precompiles/staking.rs:22-24

Code Analysis

/// System address allowed to call slash function.
///
/// In production, this should be the consensus layer's system account.
pub const SYSTEM_ADDRESS: Address = Address::ZERO;

This is used in the slash() function:

fn slash(&self, data: &[u8], gas_limit: u64, caller: Address) -> PrecompileResult {
    // ...
    
    // Only callable by system
    if caller != SYSTEM_ADDRESS {
        return Err(PrecompileError::Fatal(
            "Unauthorized: system-only function".to_string(),
        ));
    }
    
    // ... performs slash
}

Security Concern

Address::ZERO (0x0000...0000) is problematic because:

  1. Common default value: Many Ethereum libraries and contracts use Address::ZERO as a sentinel/null value
  2. Accidental matches: A bug elsewhere in the code that leaves an address uninitialized could accidentally match the system address
  3. Transfer destination: The zero address is often used as a burn address
  4. CREATE operation: Contract creation transactions have to: Address::ZERO, though this doesn't affect precompile calls directly

Production Risk

The comment itself acknowledges this is temporary:

/// In production, this should be the consensus layer's system account.

If deployed as-is, anyone who can make a call with caller=Address::ZERO could slash validators.

Suggested Fix

Use a dedicated system address derived deterministically:

/// System address for privileged operations.
/// Derived as keccak256("CipherBFT.SystemAddress")[12..32]
pub const SYSTEM_ADDRESS: Address = address!("1111111111111111111111111111111111111111");
// Or use a canonical Ethereum system address pattern
// pub const SYSTEM_ADDRESS: Address = address!("fffffffffffffffffffffffffffffffffffffffe");

Alternative: Make it configurable at genesis:

pub struct StakingPrecompile {
    state: Arc<RwLock<StakingState>>,
    system_address: Address,  // Configurable
}

impl StakingPrecompile {
    pub fn new(system_address: Address) -> Self {
        Self {
            state: Arc::new(RwLock::new(StakingState::default())),
            system_address,
        }
    }
}

Severity

High - Security vulnerability that could allow unauthorized slashing if any code path creates calls with uninitialized/zero addresses.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions