GitHub Advisoryadvisory · Go
GHSA-87m7-qffr-542v Klever-Go MultiDataInterceptor has remote OOM via crafted compressed P2P payload
HIGH
Official page ↗
- Aliases
- CVE-2026-44697
- 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:C/C:N/I:N/A:H
- Weaknesses
- CWE-409, CWE-770
- Reviewed
- 2026-05-13
Affected packages
• Go github.com/klever-io/klever-go: <= 1.7.16 (no fix yet)
Description
Summary
A remote, unauthenticated denial-of-service vulnerability in
`Batch.Decompress` (`data/batch/batch.go`) allows any peer that
participates in a topic served by `MultiDataInterceptor` to allocate
multi-gigabyte heaps on the receiving node from a sub-50 KiB gossip
payload. A single packet is sufficient to OOM-kill a validator with
conventional memory provisioning. Fleet-wide application affects chain
liveness.
The vulnerability was identified during an internal security review of
`core/process/interceptors/multiDataInterceptor.go` at commit
`405d01b0abbf0d3e73b4a990bd7394a01f200dc2`. It is distinct from, and
substantially more severe than, the throttler-slot-leak vulnerability
disclosed in `GHSA-74m6-4hjp-7226`. Both reports cover adjacent code in
the same call path; the patches must land together in one release
(rc2 superseding rc1).
Two additional, lower-severity hardening issues affecting the same code
path are documented in this report and remediated by the same patch.
They are not independently exploitable under the default deployed
anti-flood configuration and are not requested as separate CVEs.
Description
`MultiDataInterceptor.ProcessReceivedMessage`
(`core/process/interceptors/multiDataInterceptor.go:79`) handles every
gossip message received on the topics the interceptor is registered for.
At lines 95–102 it conditionally decompresses the payload via
`Batch.Decompress`:
```go
if b.IsCompressed {
err = b.Decompress(mdi.marshalizer)
if err != nil { ... return err }
}
```
`Batch.Decompress` (`data/batch/batch.go:109`) delegates the gzip step to
`decompressGzip` (`data/batch/batch.go:35-53`), which performs an
unbounded `io.ReadAll` on the gzip reader:
```go
func decompressGzip(data []byte) ([]byte, error) {
rdata := bytes.NewReader(data)
reader, err := gzip.NewReader(rdata)
if err != nil { return nil, err }
result, err := io.ReadAll(reader) // no LimitReader, no DataSize check
...
}
```
After the gzip step succeeds, `Decompress` re-`Unmarshal`s the inflated
bytes back into the `Batch` value, again with no size cap. The
attacker-set `ba.DataSize` field is never validated on decompression, so
the lie is free.
The order of operations in `ProcessReceivedMessage`:
```
preProcessMessage -> anti-flood by COMPRESSED size only
marshalizer.Unmarshal(&b, ..) -> outer Batch (small, cheap)
b.Decompress(...) -> UNBOUNDED here (bomb explodes)
... b.Data populated with N entries ...
antiflood.CanProcessMessagesOnTopic(..., uint32(len(b.Data)), ...)
```
The count-budget anti-flood check at line 111 runs *after* `Decompress`
completes, so no anti-flood configuration can prevent the explosion. The
only gate above `Decompress` is `preProcessMessage`'s byte budget, which
sees only the *compressed* payload size and is trivially satisfied by a
sub-MB bomb.
Proof of Concept
The PoC is a self-contained Go test that exercises the real
`data/batch.Batch.Decompress` function and the production
`factory.ProtoMarshalizer`. No mocks. Both the attacker-side construction
(marshal a `Batch` of millions of empty entries, gzip, wrap in an outer
compressed `Batch`) and the receiver-side path (`mrs.Unmarshal` →
`received.Decompress(mrs)`) are exactly what runs in production at the
reviewed commit.
The headline test (`TestC2_DecompressionBomb_ValidInner`) constructs a
~48 KiB outer wire payload that decompresses to 25 million `[]byte`
entries, and samples `runtime.HeapAlloc` every 5 ms during `Decompress`
to capture the peak (since the inflated buffer is freed once `Decompress`
returns).
Test source
Place the file under `playground/p2pflood/c2_decompression_bomb_test.go`
in a checkout of the reviewed commit, then run:
```
go test -v -count=1 -timeout=120s -run TestC2 ./playground/p2pflood/...
```
```go
package p2pflood_test
import (
"bytes"
"compress/gzip"
"runtime"
"sync/atomic"
"testing"
"time"
"github.com/klever-io/klever-go/data/batch"
"github.com/klever-io/klever-go/tools/marshal/factory"
)
const inflatedSize = 256 heap)", amp)
if allocated >100x", amp)
}
}
// TestC2_DecompressionBomb_ValidInner: realistic ceiling — gzip stream
// decompresses to a valid marshaled Batch with N=25M empty entries.
// Decompress's internal Unmarshal succeeds and additionally allocates
// the [][]byte slice. All before any count-based anti-flood runs.
func TestC2_DecompressionBomb_ValidInner(t *testing.T) {
mrs, err := factory.NewMarshalizer(factory.ProtoMarshalizer)
if err != nil {
t.Fatalf("ma…
Related
Alias · CVE (1)
- CVE-2026-44697 Klever-Go is the Go implementation of the Klever blockchain protocol. vulnerability