Bug Description
Found that validators can submit unlimited exchange rates in a single oracle vote. The code only checks the string length (max 4096 chars) but doesn't limit how many denoms you can pack into that string.
Location: x/oracle/keeper/msg_server.go and x/oracle/types/msgs.go
The ValidateBasic function checks:
- String can't be longer than 4096 characters
- Must have at least one rate
But it doesn't check how many exchange rate tuples you actually submit. So you can pack hundreds or thousands of denoms into one vote as long as the string fits in 4096 chars.
How to reproduce
The validation code in x/oracle/types/msgs.go:
func (msg MsgAggregateExchangeRateVote) ValidateBasic() error {
// Only checks string length
if len(msg.ExchangeRates) > 4096 {
return errors.Wrap(sdkerrors.ErrInvalidRequest,
"exchange rates string can not exceed 4096 characters")
}
exchangeRates, err := ParseExchangeRateTuples(msg.ExchangeRates)
// ... validates each rate ...
// But NO CHECK on len(exchangeRates) here!
}
Then in msg_server.go it just loops through all of them:
for _, exchangeRate := range exchangeRates {
// processes every denom without limit
}
I tested submitting 1000 denoms - all accepted without error.
Impact
A validator could spam the network by submitting tons of exchange rates in each vote. This would:
- Slow down vote processing (loops through all denoms)
- Increase vote tally calculation time
- Potentially DoS the oracle module
Not critical but definitely a resource exhaustion vector.
Test Case
func TestUnboundedExchangeRateTuples(t *testing.T) {
// Try submitting 1000 denoms
rates := make([]string, 1000)
for i := 0; i < 1000; i++ {
rates[i] = fmt.Sprintf("1.5DENOM%d", i)
}
exchangeRateStr := strings.Join(rates, ",")
parsed, err := types.ParseExchangeRateTuples(exchangeRateStr)
require.NoError(t, err)
t.Logf("Accepted %d exchange rate tuples without bounds check", len(parsed))
}
$ cd /root/kiichain && /root/kiichain/go/bin/go test -v -tags=test ./x/oracle/keeper -run TestUnboundedExchangeRateTuples
=== RUN TestUnboundedExchangeRateTuples
test_unbounded_rates_real_test.go:24: Accepted 1000 exchange rate tuples without bounds check
--- PASS: TestUnboundedExchangeRateTuples (0.00s)
PASS
ok github.com/kiichain/kiichain/v7/x/oracle/keeper 0.162s
Environment
- Kiichain version: v7 (latest commit)
- Test environment: Ubuntu Linux
- Files affected:
x/oracle/types/msgs.go
x/oracle/keeper/msg_server.go
Suggested Fix
Add a maximum limit check in ValidateBasic():
const MaxExchangeRateTuples = 50 // reasonable limit based on typical whitelist size
func (msg MsgAggregateExchangeRateVote) ValidateBasic() error {
// ... existing string length check ...
exchangeRates, err := ParseExchangeRateTuples(msg.ExchangeRates)
if err != nil {
return err
}
// NEW: Check tuple count
if len(exchangeRates) > MaxExchangeRateTuples {
return fmt.Errorf("too many exchange rate tuples: %d (max %d)",
len(exchangeRates), MaxExchangeRateTuples)
}
// ... rest of validation ...
}
This prevents resource exhaustion while still allowing reasonable use cases.
Declaration
Bug Description
Found that validators can submit unlimited exchange rates in a single oracle vote. The code only checks the string length (max 4096 chars) but doesn't limit how many denoms you can pack into that string.
Location:
x/oracle/keeper/msg_server.goandx/oracle/types/msgs.goThe ValidateBasic function checks:
But it doesn't check how many exchange rate tuples you actually submit. So you can pack hundreds or thousands of denoms into one vote as long as the string fits in 4096 chars.
How to reproduce
The validation code in
x/oracle/types/msgs.go:Then in msg_server.go it just loops through all of them:
I tested submitting 1000 denoms - all accepted without error.
Impact
A validator could spam the network by submitting tons of exchange rates in each vote. This would:
Not critical but definitely a resource exhaustion vector.
Test Case
Environment
x/oracle/types/msgs.gox/oracle/keeper/msg_server.goSuggested Fix
Add a maximum limit check in
ValidateBasic():This prevents resource exhaustion while still allowing reasonable use cases.
Declaration