Summary
On February 17th 2026, at around 16:00 UTC, the Polygon team rolled out Bor release v2.5.9.
This version addresses several security and liveness issues in the networking stack, header validation and Heimdall state sync integration. Some of these issues could be triggered by unauthenticated peers and, in the worst case, lead to node crashes, consensus failures or incorrect behaviour in contracts that rely on block numbers.
The main issues are
-
A devp2p handshake bug inherited from upstream
go-ethereumthat allowed a remote attacker to crash CGO built Bor nodes with a malformed public key in the initial RLPx handshake -
A header sync bug inherited from upstream
go-ethereumwhere a malformed parent header could trigger a panic duringEIP-1559base fee checks when verifying a child header -
A Heimdall-v2 state sync client inconsistency where the
gRPCclient fetched fewer events than the HTTP and application clients, leading to differentStateSyncTxhashes and block rejection between nodes -
A block number continuity bug where consensus and import checks used a truncated 64 bit view of the height while the EVM exposed the full big integer to contracts, allowing authorised producers to create abnormal block numbers and bypass block number based application logic
Vulnerabilities 1 and 2 originated in the upstream go ethereum codebase and were inherited through Bor’s fork. Bor v2.5.9 adopts the corresponding upstream fixes and applies additional checks where necessary.
Root Cause
Issue 1: devp2p handshake panic on malformed public keys inherited from go ethereum
Bor’s p2p stack uses an encrypted devp2p handshake based on elliptic curve public keys. The inbound side of the handshake accepted remote public keys that were mathematically invalid in the underlying curve field, but still passed higher level checks.
In CGO builds that use the native secp256k1 implementation, a later step in the connection setup tried to re encode and compress this untrusted public key using a library routine that performs strict field validation. When the invalid coordinates were detected at this point, the library reported a fatal error via a panic.
The goroutine that performs the inbound handshake was not wrapped in any recovery logic at this layer, so the panic would terminate the entire Bor process.
Because the attacker controlled the initial handshake message and did not need any prior peer relationship or valid private key, any party able to open a TCP connection to the devp2p port of a CGO built Bor node could trigger this behaviour.
This issue matches a class of bugs that were identified and fixed in upstream go-ethereum. Bor v2.5.9 brings the relevant code paths in line with the upstream behaviour, together with additional checks.
Issue 2: header sync panic on malformed parent base fee inherited from go ethereum
After the London upgrade, Bor verifies EIP-1559 base fee behaviour when importing headers. Header fields are RLP encoded and some fields, including the base fee, are optional at the encoding level.
In previous versions a malformed parent header could be constructed such that
-
The parent omits the base fee field and decodes with a missing base fee
-
The parent is treated as a future block and rejected early with an error that does not perform deeper validation
-
The same parent header is still passed as the basis for verifying the next header in the batch
When the child header is verified under the London rules, the EIP-1559 checks compute an expected base fee from the parent and use the parent base fee field without first ensuring that it is present. For a parent header with a missing base fee this results in an unhandled error and panic during header verification.
Because this takes place in the header verification pipeline and is not recovered, a remote peer able to send crafted header sequences could crash a node during header sync.
This issue was also originally found and fixed in upstream go-ethereum. Bor v2.5.9 incorporates the corresponding fix and adds stricter validation around header fields before they are used in cascading checks.
Issue 3: Heimdall v2 state sync pagination gap and StateSyncTx hash differences
Bor integrates with Heimdall to process state sync events. Heimdall exposes the same data via multiple client interfaces and enforces a fixed per request limit on the number of events returned in a single call.
The HTTP and application level clients used by Bor were implemented with an explicit pagination loop. They repeatedly fetched pages of events until fewer than the maximum number of events were returned and then used the full set to compute the StateSyncTx payload and hash.
The gRPC client path, introduced as part of the Heimdall integration, only issued a single request with a fixed limit and returned the events from that request without looping. When more than the per request limit of events were present for a given time window, nodes using the HTTP or application client saw a different set of events compared to nodes using the gRPC client.
After the Madhugiri upgrade the StateSyncTx hash became part of block validation. In situations where more than one page of state sync events existed for a given window, producers and validators using different client modes could compute different StateSyncTx hashes and reject each other’s blocks, leading to transient consensus failures and, in practice, the need to deploy temporary overrides to restore progress.
Issue 4: truncated block number continuity checks and large block numbers
Bor stores block numbers as arbitrary precision integers, but several consensus and chain import checks used a 64 bit truncated view of the height to enforce continuity between parent and child.
Specifically the checks responsible for
-
Verifying that a header’s number is one greater than its parent
-
Ensuring that batches of headers or blocks are contiguous before handing them to the consensus engine
relied on converting the big integer block number to a 64 bit unsigned integer and comparing those truncated values. At the same time, the EVM execution context exposed the full big integer block number to contracts.
An authorised block producer could exploit this mismatch by constructing a block where the true block number is significantly larger than the parent plus one, but the low 64 bits still satisfy the continuity checks. Such a block would pass import and become canonical from the perspective of nodes that accepted it, while contracts would observe a much larger block.number value.
This breaks the core invariant that canonical block numbers increase by exactly one per block and can allow the block producer to bypass application logic that relies on block.number based thresholds, such as timelocks, vesting schedules, or governance delays.
Impact
Taken together, these issues had the following impact profile on Bor prior to v2.5.9.
-
Node availability and liveness
a) The devp2p handshake bug allowed unauthenticated remote peers to crash CGO built Bor nodes by sending malformed handshake messages.
b) The header sync base fee bug allowed remote peers to crash nodes during header import by sending specific sequences of malformed headers.
-
Consensus consistency and state sync
The Heimdall state sync pagination gap meant that nodes using different Heimdall client modes could disagree on the set of state sync events to include in a given StateSyncTx. After StateSyncTx hashes became part of block validation, this disagreement could manifest as block rejection between honest nodes and require operator intervention.
-
Application level correctness
The block number continuity issue enabled an authorised block producer to create blocks with abnormally large block numbers that still passed consensus continuity checks. Contracts using block.number for timelocks, vesting, governance timing or similar gating conditions could be tricked into executing earlier than intended, and corrupting assumptions made by off chain systems that rely on monotonic, unit step block heights.
Resolution and Recovery
Release v2.5.9 addresses these issues with a combination of upstream alignment, stricter validation and improved client behaviour. The rollout was created and published on February 17th 2026 at around 16:00 UTC.
The main fixes are
-
Safe handling of malformed public keys in the devp2p handshake
-
Robust header validation around base fee and parent usage
-
Consistent Heimdall state sync pagination and StateSyncTx hashing
-
Correct block number continuity checks on full big integers.