Hash Collision
Detects hash collision vulnerabilities in cryptographic operations including weak algorithms, truncated hashes, and merkle proof misuse.
Hash Collision
Overview
The hash collision detector identifies dangerous patterns in cryptographic hash usage within Solana programs. It detects weak hash algorithms, truncated hash outputs that reduce collision resistance, and unknown syscalls that may be custom hash implementations. For remediation steps, see the Hash Collision Remediation.
Why This Is an Issue
Cryptographic hashes provide collision resistance — it should be computationally infeasible to find two inputs that produce the same output. When programs truncate hash outputs (e.g., taking only the first 4 bytes of a 32-byte SHA-256 hash), the output space shrinks dramatically. A 32-bit truncated hash has only 2^32 possible values, making birthday attacks trivial with roughly 2^16 attempts.
Hash collisions can enable:
- Signature forgery: Two messages with the same truncated hash produce the same signature.
- Merkle proof manipulation: Crafted inputs that collide at truncated nodes bypass merkle verification.
- Replay attacks: Different transactions that hash to the same truncated identifier are treated as identical.
- Access control bypass: Crafted inputs that collide with authorized hash values grant unauthorized access.
Programs that hash small constant data (under 8 bytes) are also flagged because the input space may be small enough for rainbow table attacks.
How to Resolve
// Before: Vulnerable -- hash truncated to 4 bytes
let full_hash = solana_program::hash::hash(data);
let short_id = u32::from_le_bytes(full_hash.to_bytes()[..4].try_into().unwrap());
// Collision attack: find two data values with same first 4 bytes
// After: Fixed -- use full hash output
let full_hash = solana_program::hash::hash(data);
// Use all 32 bytes for comparisons and storage
if full_hash != expected_hash {
return Err(ProgramError::InvalidArgument);
}
Examples
Vulnerable Code
pub fn verify_merkle_proof(
proof: &[u8],
leaf: &[u8],
root: &[u8; 4], // Truncated root!
) -> ProgramResult {
let computed = solana_program::hash::hash(leaf);
let truncated = &computed.to_bytes()[..4];
if truncated != root {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
Fixed Code
pub fn verify_merkle_proof(
proof: &[u8],
leaf: &[u8],
root: &[u8; 32], // Full 256-bit root
) -> ProgramResult {
let computed = solana_program::hash::hash(leaf);
if computed.to_bytes() != *root {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
Example JSON Finding
{
"detector": "hash-collision",
"severity": "critical",
"confidence": 0.78,
"title": "Hash Truncation Enables Collision Attacks",
"description": "Hash output is being truncated using bitwise AND with a small mask, dramatically reducing the output space.",
"cwe_ids": [328]
}
Detection Methodology
- Syscall classification: Identifies cryptographic syscalls (sol_sha256, sol_keccak256, sol_blake3) and checks for misuse patterns. Non-crypto syscalls (logging, memory, CPI) are ignored.
- Truncation detection: Scans for bitwise AND with small masks or right-shift operations applied to hash outputs, indicating truncation below safe thresholds.
- Small input detection: Flags hash operations on data smaller than 8 bytes, where the input space may be vulnerable to preimage enumeration.
- Unknown syscall flagging: Reports unrecognized syscalls (excluding internal BPF function calls in the 0xFFFF0000+ range) that may be weak custom hash implementations.
Limitations
False positives: Non-security-critical hash truncation (e.g., display-only short identifiers) will be flagged. Unknown syscalls that are legitimate newer Solana runtime additions may trigger medium-severity findings. False negatives: Hash truncation performed via array slicing rather than bitwise operations may not be detected. Custom hash implementations called via CPI are not analyzed.
Related Detectors
- Ed25519 Signature Malleability — detects cryptographic signature weaknesses
- Weak Randomness — detects insecure entropy sources