Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions store/gaskv/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,28 @@ package gaskv

import (
"io"
"math"

"cosmossdk.io/store/types"
)

var _ types.KVStore = &Store{}

// multiplyGasSafely performs multiplication of gas cost per byte with byte length
// and returns the result or panics with ErrorGasOverflow if overflow occurs.
func multiplyGasSafely(costPerByte types.Gas, byteLength int) types.Gas {
if costPerByte == 0 || byteLength == 0 {
return 0
}

// Check for overflow: if costPerByte > math.MaxUint64 / byteLength, then overflow
if costPerByte > math.MaxUint64/uint64(byteLength) {
panic(types.ErrorGasOverflow{Descriptor: "gas calculation overflow"})
}

return costPerByte * types.Gas(byteLength)
}

// Store applies gas tracking to an underlying KVStore. It implements the
// KVStore interface.
type Store struct {
Expand Down Expand Up @@ -37,9 +53,9 @@ func (gs *Store) Get(key []byte) (value []byte) {
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, types.GasReadCostFlatDesc)
value = gs.parent.Get(key)

// TODO overflow-safe math?
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasReadPerByteDesc)
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasReadPerByteDesc)
// Use overflow-safe math for gas calculation
gs.gasMeter.ConsumeGas(multiplyGasSafely(gs.gasConfig.ReadCostPerByte, len(key)), types.GasReadPerByteDesc)
gs.gasMeter.ConsumeGas(multiplyGasSafely(gs.gasConfig.ReadCostPerByte, len(value)), types.GasReadPerByteDesc)

return value
}
Expand All @@ -49,9 +65,9 @@ func (gs *Store) Set(key, value []byte) {
types.AssertValidKey(key)
types.AssertValidValue(value)
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, types.GasWriteCostFlatDesc)
// TODO overflow-safe math?
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(key)), types.GasWritePerByteDesc)
gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(value)), types.GasWritePerByteDesc)
// Use overflow-safe math for gas calculation
gs.gasMeter.ConsumeGas(multiplyGasSafely(gs.gasConfig.WriteCostPerByte, len(key)), types.GasWritePerByteDesc)
gs.gasMeter.ConsumeGas(multiplyGasSafely(gs.gasConfig.WriteCostPerByte, len(value)), types.GasWritePerByteDesc)
gs.parent.Set(key, value)
}

Expand Down Expand Up @@ -170,8 +186,9 @@ func (gi *gasIterator) consumeSeekGas() {
key := gi.Key()
value := gi.Value()

gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasValuePerByteDesc)
gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasValuePerByteDesc)
// Use overflow-safe math for gas calculation
gi.gasMeter.ConsumeGas(multiplyGasSafely(gi.gasConfig.ReadCostPerByte, len(key)), types.GasValuePerByteDesc)
gi.gasMeter.ConsumeGas(multiplyGasSafely(gi.gasConfig.ReadCostPerByte, len(value)), types.GasValuePerByteDesc)
}
gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, types.GasIterNextCostFlatDesc)
}
76 changes: 76 additions & 0 deletions store/gaskv/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gaskv_test

import (
"fmt"
"math"
"testing"

dbm "github.com/cosmos/cosmos-db"
Expand Down Expand Up @@ -118,3 +119,78 @@ func TestGasKVStoreOutOfGasIterator(t *testing.T) {
iterator.Next()
require.Panics(t, func() { iterator.Value() }, "Expected out-of-gas")
}

func TestGasKVStoreOverflowSafeMath(t *testing.T) {
t.Parallel()

// Test with very large values that would cause overflow
mem := dbadapter.Store{DB: dbm.NewMemDB()}
meter := types.NewInfiniteGasMeter() // Use infinite meter to avoid out-of-gas

// Create a gas config with high per-byte costs to trigger overflow
// Use a value that when multiplied by MaxKeyLength would cause overflow
highCostPerByte := types.Gas(math.MaxUint64/uint64(types.MaxKeyLength) + 1)
config := types.GasConfig{
HasCost: 1000,
DeleteCost: 1000,
ReadCostFlat: 1000,
ReadCostPerByte: highCostPerByte,
WriteCostFlat: 2000,
WriteCostPerByte: highCostPerByte,
IterNextCostFlat: 30,
}

st := gaskv.NewStore(mem, meter, config)

// Test Get with large key that would cause overflow
largeKey := make([]byte, types.MaxKeyLength) // Max key length * high cost > MaxUint64
require.Panics(t, func() { st.Get(largeKey) }, "Expected gas overflow for large key in Get")

// Test Set with large value that would cause overflow
smallKey := []byte("key")
largeValue := make([]byte, types.MaxKeyLength) // Max key length * high cost > MaxUint64
require.Panics(t, func() { st.Set(smallKey, largeValue) }, "Expected gas overflow for large value in Set")

// Test that normal operations still work with lower cost
normalConfig := types.GasConfig{
HasCost: 1000,
DeleteCost: 1000,
ReadCostFlat: 1000,
ReadCostPerByte: 1, // Low cost per byte
WriteCostFlat: 2000,
WriteCostPerByte: 1, // Low cost per byte
IterNextCostFlat: 30,
}
normalSt := gaskv.NewStore(mem, meter, normalConfig)
normalKey := []byte("normal")
normalValue := []byte("value")
require.NotPanics(t, func() { normalSt.Set(normalKey, normalValue) }, "Normal operations should not panic")
require.NotPanics(t, func() { normalSt.Get(normalKey) }, "Normal operations should not panic")
}

func TestGasKVStoreOverflowSafeMathWithZeroCost(t *testing.T) {
t.Parallel()

// Test with zero cost per byte (should not cause overflow)
mem := dbadapter.Store{DB: dbm.NewMemDB()}
meter := types.NewInfiniteGasMeter()

config := types.GasConfig{
HasCost: 1000,
DeleteCost: 1000,
ReadCostFlat: 1000,
ReadCostPerByte: 0, // Zero cost per byte
WriteCostFlat: 2000,
WriteCostPerByte: 0, // Zero cost per byte
IterNextCostFlat: 30,
}

st := gaskv.NewStore(mem, meter, config)

// Test with reasonably large data within limits - should not panic with zero cost
largeKey := make([]byte, 1000) // Within MaxKeyLength limit
largeValue := make([]byte, 1000) // Within MaxValueLength limit

require.NotPanics(t, func() { st.Set(largeKey, largeValue) }, "Zero cost should not cause overflow")
require.NotPanics(t, func() { st.Get(largeKey) }, "Zero cost should not cause overflow")
}