Shutdown of less than 10% of network processing nodes without brute force actions, but does not shut down the network
Description
Brief/Intro
Nimbus-eth2 does not verify slot number of incoming attestations.
Because it uses checked integer calculations, malformed attestation may crash the node.
Vulnerability Details
Nim signed integer arithmetic is checked, so in case of overflow Defect will be thrown.
Defects inherit from System.Defect, they are not catchable and terminate the running process.
Let's see how incoming attestations are processed https://github.com/status-im/nimbus-eth2/blob/stable/beacon_chain/gossip_processing/eth2_processor.nim#L361
proc processAttestation*(
self: ref Eth2Processor, src: MsgSource,
attestation: phase0.Attestation | electra.Attestation, subnet_id: SubnetId,
checkSignature, checkValidator: bool
): Future[ValidationRes] {.async: (raises: [CancelledError]).} =
var wallTime = self.getCurrentBeaconTime()
let (afterGenesis, wallSlot) = wallTime.toSlot()
logScope:
attestation = shortLog(attestation)
subnet_id
wallSlot
if not afterGenesis:
notice "Attestation before genesis"
return errIgnore("Attestation before genesis")
let delay = wallTime - attestation.data.slot.attestation_deadline
debug "Attestation received", delay
# Now proceed to validation
let v =
await self.attestationPool.validateAttestation(
self.batchCrypto, attestation, wallTime, subnet_id, checkSignature)
return if v.isOk():
...
...
We are interested in attestation_deadline function https://github.com/status-im/nimbus-eth2/blob/stable/beacon_chain/spec/beacon_time.nim#L167
type
BeaconTime* = object
ns_since_genesis*: int64
TimeDiff* = object
nanoseconds*: int64
const
# Earlier spec versions had these at a different slot
GENESIS_SLOT* = Slot(0)
GENESIS_EPOCH* = Epoch(0) # compute_epoch_at_slot(GENESIS_SLOT)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.9/specs/phase0/fork-choice.md#constant
INTERVALS_PER_SLOT* = 3
FAR_FUTURE_BEACON_TIME* = BeaconTime(ns_since_genesis: int64.high())
NANOSECONDS_PER_SLOT* = SECONDS_PER_SLOT * 1_000_000_000'u64
const
attestationSlotOffset* = TimeDiff(nanoseconds:
NANOSECONDS_PER_SLOT.int64 div INTERVALS_PER_SLOT)
func start_beacon_time*(s: Slot): BeaconTime =
const maxSlot = Slot(
uint64(FAR_FUTURE_BEACON_TIME.ns_since_genesis) div NANOSECONDS_PER_SLOT)
1) if s > maxSlot: FAR_FUTURE_BEACON_TIME
else: BeaconTime(ns_since_genesis: int64(uint64(s) * NANOSECONDS_PER_SLOT))
func attestation_deadline*(s: Slot): BeaconTime =
2) s.start_beacon_time + attestationSlotOffset
Slot type is basically uint64, if it is very large, start_beacon_time() function will return FAR_FUTURE_BEACONTIME which is equal to int64.high
BeaconTime is int64, so if s.start_beacon_time is int64.high and attestationSlotOffset is not zero, this expression will overflow
As a result OverflowDefect will be thrown and beacon node will stop working
Impact Details
Attacker could crash nimbus-eth2 node with a single malformed attestation.