GitHub Advisoryadvisory · Go
GHSA-rm5c-5x2p-48wr Klever-Go KVM: Unauthenticated remote node crash (nil-pointer DoS) in klever-go P2P transaction interceptor (txVersionChecker nil RawData) - potential chain halt
HIGH
Official page ↗
- Aliases
- CVE-2026-52878
- Ecosystem
- Go
- Packages
- github.com/klever-io/klever-go
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
- Weaknesses
- CWE-476
- Reviewed
- 2026-06-05
Affected packages
• Go github.com/klever-io/klever-go: >= 1.7.14, < 1.7.18 → fixed in 1.7.18
Description
Summary
Every transaction gossiped on the klever-go P2P network is decoded and validated
synchronously inside the libp2p pubsub topic-validator callback. The validator
`txVersionChecker.CheckTxVersion` dereferences `tx.RawData.Version` with no nil
check. A protobuf `Transaction` whose embedded `RawData` sub-message is omitted
decodes to `RawData == nil`, so validating it triggers a nil-pointer panic.
The libp2p pubsub callback, the underlying go-libp2p-pubsub validation worker, and
klever's own `network/p2p` layer install no `recover()`, so the panic propagates and
crashes the entire node process. The attacker payload is a 3-byte protobuf message;
no validator key, stake, funds, or on-chain account is required. Aimed at enough of
the BLS validator set, repeated delivery halts block production (chain halt).
Affected component
• Root cause: `core/versioning/txVersionChecker.go:22`
• Reached via: `core/process/transaction/interceptedTransaction.go:203` (integrity) and `:154` (CheckValidity)
• Production tx-topic path: `core/process/interceptors/multiDataInterceptor.go:171` and `:223`
• Unprotected caller: `network/p2p/libp2p/netMessenger.go` `pubsubCallback` (no recover)
• Topic wiring: `core/process/factory/interceptorscontainer/baseInterceptorsContainerFactory.go` (`createOneTxInterceptor`)
Details
Synchronous validation path, no recovery at any frame:
```
libp2p pubsubCallback network/p2p/libp2p/netMessenger.go (no recover)
-> MultiDataInterceptor.ProcessReceivedMessage core/process/interceptors/multiDataInterceptor.go:171
-> interceptedData(...) core/process/interceptors/multiDataInterceptor.go:223
-> InterceptedTransaction.CheckValidity core/process/transaction/interceptedTransaction.go:154
-> integrity() core/process/transaction/interceptedTransaction.go:203
-> txVersionChecker.CheckTxVersion(tx) core/versioning/txVersionChecker.go:22 panic
return process.ErrInvalidTransactionVersion
}
return nil
}
```
`integrity()` calls `CheckTxVersion` as its very first statement, before any
`RawData` nil-check, and `CheckValidity()` runs before the whitelist / originator-
election gate in the interceptor, so node-role and whitelist restrictions do not
protect this path.
Preconditions
• Attacker runs an ordinary libp2p peer reachable to the target via normal peering /
kad-dht discovery on the `transactions` gossip topic.
• Production runs with `withMessageSigning = true`, which only requires the gossip
message to be signed by the attacker's OWN libp2p peer key (a self-generated
identity; NOT a validator key, NOT funded, NOT authorized).
• No special config or feature flag; the tx interceptor is built unconditionally and
subscribes to `transactions` on every node.
Impact
• Deterministic, immediate crash of any targeted node (validator, sentry, or
observer) from a single ~3-byte message.
• Gossipsub validates before relaying, so the victim does not forward the crashing
message; the attacker delivers it directly to each target (one tiny message/node).
• With auto-restart (systemd), re-sending sustains the outage.
• Directed at > 1/3 of the BLS validator set, this prevents consensus and halts the chain.
• NOTE: the HTTP `POST /transaction/send` path is NOT crash-exploitable - the REST
server uses `gin.Default()` (Recovery middleware) and returns HTTP 500. The
exploitable vector is the P2P interceptor.
Exploit cost / attack complexity
• Cost: negligible (one self-signed libp2p peer; 3-byte payload; no gas/capital).
• Complexity: LOW. Unauthenticated, remote, deterministic.
PoC-Source
Scenario
• Build the malicious transaction as it appears on the wire: a protobuf `Transaction`
with `RawData` omitted (plus a throwaway `Signature` so the batch entry looks like a
real tx). With the production proto marshalizer this encodes to 3 bytes
(`12 01 78`) and round-trips back to `RawData == nil`.
• Feed it through the REAL production interceptors. The `transactions` gossip topic is
served by a `MultiDataInterceptor` (`baseInterceptorsContainerFactory.go`,
`createOneTxInterceptor`); the test wraps the tx in a `Batch` exactly like a bulk-tx
gossip message and calls `ProcessReceivedMessage`, which is precisely what the
panic-free libp2p `pubsubCallback` invokes in production. A second test drives the
generic `SingleDataInterceptor` to show the bug is in the shared validation chain.
• The data factory is a faithful copy of the production `interceptedTxDataFactory.Create`:
it builds a genuine `*InterceptedTransaction`. No validation behavior is stubbed;
only leaf crypto/marshal helpers use the repo's own in-tree mocks. The panic occurs
on the first line of `integrity()`, upstream of any mock.
How to run
1. `git clone https://github.com/klever-io/klever-go && cd klever-go`
(Go toolchain matching go.mod `go 1.25.7`; verified locally on go1.26.3.)
2. Save the source below as `core/process/interceptors/poc_nil_rawdata_dos_test.go`.
3. Run either (separately - the first panic aborts the test binary):
• Production tx-topic path: `go test ./core/process/interceptors/ -run TestPoC_NilRawData_MultiDataInterceptor -v`
• Generic path: `go test ./core/process/interceptors/ -run TestPoC_NilRawData_SingleDataInterceptor -v`
• Dependencies: none beyond the repo's own go.mod (uses in-repo mocks only).
Full PoC source (`poc_nil_rawdata_dos_test.go`):
```go
// Target component: klever-go P2P transaction interceptor (network availability)
// core/process/transaction/interceptedTransaction.go
// core/versioning/txVersionChecker.go:22
// Vulnerability type: Unauthenticated remote Denial-of-Service (nil-pointer panic / chain-wide node crash)
// CWE-476 (NULL Pointer Dereference) reached from untrusted P2P input.
//
// Summary:
// Every gossiped transaction is decoded and validated synchronously inside the
// libp2p pubsub topic-validator callback
// (network/p2p/libp2p/netMessenger.go -> pubsubCallback). That callback has NO
// recover(). The validation chain is:
//
// (Multi|Single)DataInterceptor.ProcessReceivedMessage
// -> InterceptedTransaction.CheckValidity
// -> integrity()
// -> txVersionChecker.CheckTxVersion(tx) // tx.RawData.Version core/process/interceptors/poc_nil_rawdata_dos_test.go
// 3) go test ./core/process/interceptors/ -run TestPoC_NilRawData -v
//
// Expected output:
// The test process aborts with:
// panic: runtime error: invalid memory address or nil pointer dereference
// ... core/versioning.(*txVersionChecker).CheckTxVersion ... txVersionChecker.go:22
// ... InterceptedTransaction.integrity ... -> CheckValidity
// ... (Multi|Single)DataInterceptor.ProcessReceivedMessage
// i.e. the crash originates from the interceptor's synchronous message-handling frame,
// exactly where the panic-free libp2p pubsub callback would call it in production.
//
// Dependencies: none beyond the repo's own go.mod (uses in-repo mocks only).
package interceptors_test
import (
"testing"
"github.com/klever-io/klever-go/common/mock"
"github.com/klev…
Related
Alias · CVE (1)
- CVE-2026-52878 Klever-Go is the Go implementation of the Klever blockchain protocol. vulnerability
Weakness · CWE (1)
- CWE-476 NULL Pointer Dereference weakness