mirror of
https://github.com/luxfi/precompile.git
synced 2026-07-27 03:33:45 +00:00
pulsar: fix mlen uint64 overflow → chain-halt (CRITICAL launch blocker)
verify parsed mlen from attacker's low-8-bytes, then guarded with len(body) < mlen+sigSize — which OVERFLOWS uint64 when mlen is near MaxUint64, slipping past the guard so body[:mlen] panics. A panic in geth's precompile dispatch has no recover() → every validator halts on the tx. Fixed with a non-overflowing subtraction guard (mlen > len(body) || len(body)-mlen < sigSize). Regression test TestVerify_MlenOverflowRejected proves reject-not-panic. Red-team finding #5.
This commit is contained in:
+6
-1
@@ -193,7 +193,12 @@ func (p *pulsarVerifyPrecompile) Run(
|
||||
mlen = (mlen << 8) | uint64(body[i])
|
||||
}
|
||||
body = body[32:]
|
||||
if uint64(len(body)) < mlen+uint64(sigSize) {
|
||||
// Bounds check WITHOUT overflow: mlen is attacker-controlled (low 8 bytes
|
||||
// of a uint256), so mlen+sigSize can wrap uint64 and slip past a naive
|
||||
// guard, making body[:mlen] panic — and a panic in the geth precompile
|
||||
// dispatch (no recover) halts every validator on the tx. Compare with
|
||||
// subtraction on the trusted len(body) instead.
|
||||
if mlen > uint64(len(body)) || uint64(len(body))-mlen < uint64(sigSize) {
|
||||
return nil, suppliedGas, ErrInvalidInputLength
|
||||
}
|
||||
msg := body[:mlen]
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package pulsar
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
)
|
||||
|
||||
// TestVerify_MlenOverflowRejected proves the consensus-halt fix: an attacker
|
||||
// who sets the message-length field near MaxUint64 must get a clean
|
||||
// ErrInvalidInputLength, NOT a slice-out-of-range panic. A panic in geth's
|
||||
// precompile dispatch has no recover() — it would halt every validator on the
|
||||
// tx. Before the fix, mlen+sigSize wrapped uint64, the guard passed, and
|
||||
// body[:mlen] panicked.
|
||||
func TestVerify_MlenOverflowRejected(t *testing.T) {
|
||||
p := PulsarVerifyPrecompile
|
||||
|
||||
input := []byte{ModePulsar44}
|
||||
input = append(input, make([]byte, Pulsar44PublicKeySize)...)
|
||||
lenField := make([]byte, 32)
|
||||
binary.BigEndian.PutUint64(lenField[24:], ^uint64(0)-8)
|
||||
input = append(input, lenField...)
|
||||
input = append(input, make([]byte, 64)...)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("CONSENSUS-HALT: overflow mlen panicked instead of rejecting: %v", r)
|
||||
}
|
||||
}()
|
||||
_, _, err := p.Run(nil, common.Address{}, common.Address{}, input, 100_000_000, true)
|
||||
if err == nil {
|
||||
t.Fatal("expected ErrInvalidInputLength for overflow mlen, got nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user