fix(oracle): add denom length validation and query result limits#287
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughIntroduces denom-length validation and capped query results in the oracle module. Adds Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR addresses several audit-found DoS/spam vectors across the governance ante decorator, the oracle EVM precompile, and oracle gRPC query endpoints by replacing panics with errors and adding resource/gas bounds.
Changes:
- Replace a
panic()in the governance vote ante decorator with safe error propagation during delegation iteration. - Enforce minimum gas on malformed/unknown oracle precompile invocations and add denom length validation for precompile args.
- Add hard caps to oracle query endpoints to prevent unbounded KV iteration and large in-memory responses.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
x/oracle/keeper/query_server.go |
Adds max-result caps to list-style oracle queries to prevent unbounded iteration/memory growth. |
precompiles/oracle/types.go |
Adds a maximum denom length check for oracle precompile argument parsing. |
precompiles/oracle/oracle.go |
Charges a minimum gas amount for malformed inputs / invalid method IDs to prevent zero-cost spam. |
ante/gov_vote_ante.go |
Replaces a panic on invalid validator address with error handling to avoid validator crashes. |
Comments suppressed due to low confidence (3)
x/oracle/keeper/query_server.go:139
- Same as ExchangeRates/Actives: PriceSnapshotHistory now enforces a hard limit but the response provides no indication that results may be truncated. If there are more than MaxSnapshotResults snapshots, callers will receive an incomplete history with no way to page or detect truncation. Consider returning an explicit error on truncation (e.g., after collecting MaxSnapshotResults+1) or adding pagination support.
// MaxSnapshotResults is the maximum number of snapshots returned
const MaxSnapshotResults = 500
// PriceSnapshotHistory queries all snapshots
func (qs QueryServer) PriceSnapshotHistory(ctx context.Context, req *types.QueryPriceSnapshotHistoryRequest) (*types.QueryPriceSnapshotHistoryResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
// Get the snapshots available on the KVStore
priceSnapshots := []types.PriceSnapshot{}
count := 0
err := qs.Keeper.PriceSnapshot.Walk(sdkCtx, nil, func(_ int64, snapshot types.PriceSnapshot) (bool, error) {
priceSnapshots = append(priceSnapshots, snapshot)
count++
// Limit results to prevent memory exhaustion
return count >= MaxSnapshotResults, nil
})
if err != nil {
return nil, err
}
return &types.QueryPriceSnapshotHistoryResponse{PriceSnapshot: priceSnapshots}, nil
precompiles/oracle/oracle.go:91
- Test coverage: RequiredGas now charges MinimumPrecompileGas for malformed inputs and invalid method IDs, which is an important anti-spam guarantee. Please add unit tests that assert
RequiredGas(<4 bytes)andRequiredGas(4 bytes of unknown method id)both return MinimumPrecompileGas (and that valid calls still return the expected non-zero gas).
// MinimumPrecompileGas is the minimum gas charged for any precompile invocation
// This prevents zero-cost spam attacks with malformed inputs
const MinimumPrecompileGas uint64 = 3000
// RequiredGas returns the required gas for the precompile
func (p Precompile) RequiredGas(input []byte) uint64 {
// Charge minimum gas for malformed inputs to prevent spam
if len(input) < 4 {
return MinimumPrecompileGas
}
// Get the method ID from the first 4 bytes
methodID := input[:4]
// Get the method from the ABI
method, err := p.MethodById(methodID)
if err != nil {
// Charge minimum gas for invalid method IDs
return MinimumPrecompileGas
}
// Get the gas required for the method
return p.Precompile.RequiredGas(input, p.IsTransaction(method))
}
x/oracle/keeper/query_server.go:87
- The new MaxQueryResults limit stops iteration and returns a partial list without signaling truncation to the caller. That can silently break clients that rely on complete results (including the oracle precompile) and makes debugging hard. Consider either (a) implementing pagination, or (b) detecting truncation by collecting up to MaxQueryResults+1 and returning a gRPC ResourceExhausted/FailedPrecondition error (or adding an explicit
truncatedflag) when more results exist.
// MaxQueryResults is the maximum number of results returned by list queries
// This prevents memory exhaustion from unbounded iteration
const MaxQueryResults = 1000
// ExchangeRates returns all exchange rates
func (qs QueryServer) ExchangeRates(ctx context.Context, req *types.QueryExchangeRatesRequest) (*types.QueryExchangeRatesResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
exchangeRates := []types.DenomOracleExchangeRate{}
count := 0
err := qs.Keeper.ExchangeRate.Walk(sdkCtx, nil, func(denom string, exchangeRate types.OracleExchangeRate) (bool, error) {
exchangeRates = append(exchangeRates, types.DenomOracleExchangeRate{Denom: denom, OracleExchangeRate: &exchangeRate})
count++
// Limit results to prevent memory exhaustion
return count >= MaxQueryResults, nil
})
if err != nil {
return nil, err
}
return &types.QueryExchangeRatesResponse{DenomOracleExchangeRate: exchangeRates}, nil
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
x/oracle/keeper/query_server.go (1)
110-117:⚠️ Potential issue | 🟠 MajorAdd iteration limit to
VoteTargetsquery to match other list queries.The
VoteTargetsmethod lacks bounds like those applied toExchangeRatesandActivesqueries in the same file. Both of these useMaxQueryResults(1000) to prevent memory exhaustion during unbounded iteration. Apply the same pattern toVoteTargets:func (qs QueryServer) VoteTargets(ctx context.Context, req *types.QueryVoteTargetsRequest) (*types.QueryVoteTargetsResponse, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) voteTargets := []string{} count := 0 err := qs.Keeper.VoteTarget.Walk(sdkCtx, nil, func(denom string, denomInfo types.Denom) (bool, error) { voteTargets = append(voteTargets, denom) count++ // Limit results to prevent memory exhaustion return count >= MaxQueryResults, nil }) if err != nil { return nil, err } return &types.QueryVoteTargetsResponse{VoteTargets: voteTargets}, err }This ensures consistent protection across all list-based queries.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@x/oracle/keeper/query_server.go` around lines 110 - 117, The VoteTargets query currently returns all targets via qs.Keeper.GetVoteTargets and lacks an iteration bound; replace the unbounded fetch with a bounded walk over the store (similar to ExchangeRates/Actives) by iterating via qs.Keeper.VoteTarget.Walk (or the Keeper iterator for vote targets), appending each denom to a local []string, incrementing a counter and returning true when count >= MaxQueryResults to stop iteration, check and return any Walk error, and finally return &types.QueryVoteTargetsResponse{VoteTargets: voteTargets}, nil (or the actual error) so the result is limited to MaxQueryResults and matches the other list queries' pattern.
🧹 Nitpick comments (1)
precompiles/oracle/types.go (1)
12-14: Consider aligning MaxDenomLength with SDK's maximum denom length.The SDK's maximum denom length is 128 bytes (as noted in
x/tokenfactory/types/denoms.go), butMaxDenomLengthis set to 256. While this still prevents DoS via extremely long strings, consider lowering it to 128 to match the SDK constraint—any denom longer than 128 bytes will be invalid anyway.💡 Suggested adjustment
// MaxDenomLength is the maximum allowed length for denom strings // This prevents DoS attacks via extremely long strings -const MaxDenomLength = 256 +const MaxDenomLength = 128 // Matches SDK's maximum denom length🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@precompiles/oracle/types.go` around lines 12 - 14, The constant MaxDenomLength is set to 256 but should match the SDK's maximum denom length of 128; change the value of MaxDenomLength in precompiles/oracle/types.go from 256 to 128 so validation aligns with x/tokenfactory's constraint and avoids allowing denom lengths that the SDK would reject (update the const MaxDenomLength declaration accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@x/oracle/keeper/query_server.go`:
- Around line 110-117: The VoteTargets query currently returns all targets via
qs.Keeper.GetVoteTargets and lacks an iteration bound; replace the unbounded
fetch with a bounded walk over the store (similar to ExchangeRates/Actives) by
iterating via qs.Keeper.VoteTarget.Walk (or the Keeper iterator for vote
targets), appending each denom to a local []string, incrementing a counter and
returning true when count >= MaxQueryResults to stop iteration, check and return
any Walk error, and finally return &types.QueryVoteTargetsResponse{VoteTargets:
voteTargets}, nil (or the actual error) so the result is limited to
MaxQueryResults and matches the other list queries' pattern.
---
Nitpick comments:
In `@precompiles/oracle/types.go`:
- Around line 12-14: The constant MaxDenomLength is set to 256 but should match
the SDK's maximum denom length of 128; change the value of MaxDenomLength in
precompiles/oracle/types.go from 256 to 128 so validation aligns with
x/tokenfactory's constraint and avoids allowing denom lengths that the SDK would
reject (update the const MaxDenomLength declaration accordingly).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1b3fb82d-1862-4601-9e15-70ceda7b85ca
📒 Files selected for processing (4)
ante/gov_vote_ante.goprecompiles/oracle/oracle.goprecompiles/oracle/types.gox/oracle/keeper/query_server.go
|
Hello @giwaov, we reviewed the issues you opened and some of them are not real, but some are! Can you take a look again on the issues that remained open? Also, feel free to discuss any of the issues we closed. After that, you will be needing to refactor both this PR and your other one, since they include changes we don't agree with (solving non-issues). |
9bd170d to
00ba472
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@precompiles/oracle/types.go`:
- Around line 29-32: The denom length check currently only exists in the
precompile parser and can be bypassed; add the same validation to the shared
query boundary by enforcing MaxDenomLength inside QueryServer.ExchangeRate:
validate that denom is non-empty and len(denom) <= MaxDenomLength (use the
existing MaxDenomLength constant) and return an error when exceeded so all
callers (not just precompile paths) receive consistent protection; keep the same
error message format used elsewhere for consistency.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9d3db021-74e7-4c3a-b208-a7775f18aefd
📒 Files selected for processing (2)
precompiles/oracle/types.gox/oracle/keeper/query_server.go
🚧 Files skipped from review as they are similar to previous changes (1)
- x/oracle/keeper/query_server.go
Add MaxDenomLength check to QueryServer.ExchangeRate to ensure denom length validation applies to all callers (gRPC, CLI, etc.), not just the EVM precompile path.
|
Hey @Thaleszh, this PR has been refactored removed the ante handler panic fix and the zero-gas precompile change (same as PR #288). This PR now only contains:
Also addressed the CodeRabbit review feedback:
Let me know if there is anthing else that needs adjusting. thank you! |
- Lower MaxDenomLength from 256 to 128 to match SDK's maximum denom length constraint (x/tokenfactory/types/denoms.go) - Add MaxQueryResults iteration limit to VoteTargets query, consistent with ExchangeRates and Actives queries
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Hello there, the changes are looking good. Can you just update the PR name and the changelog with the changes? If you could add a regression test to check denom-length, it would also make the PR more complete |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@x/oracle/keeper/query_server_test.go`:
- Around line 341-354: The "valid - denom at max length" test case is marked
expectError:false but no exchange rate is seeded for strings.Repeat("a",
MaxDenomLength), so the loop's assertion (which only checks results for
utils.AtomDenom) never verifies it; update the test setup to seed an exchange
rate for the max-length denom (the same denom string used in that test case) so
querier.ExchangeRate(ctx, &types.QueryExchangeRateRequest{Denom: tc.denom})
returns a non-error and a non-nil response, and/or extend the per-case
assertions in the loop (around the call to querier.ExchangeRate) to explicitly
assert success for tc.denom == strings.Repeat("a", utils.MaxDenomLength) instead
of relying only on utils.AtomDenom.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 76645f19-6945-483d-9e4b-6e2463573d0f
📒 Files selected for processing (3)
CHANGELOG.mdprecompiles/oracle/types_test.gox/oracle/keeper/query_server_test.go
✅ Files skipped from review due to trivial changes (2)
- CHANGELOG.md
- precompiles/oracle/types_test.go
|
Hey @Thaleszh, thanks for the review! All three items have been addressed:
Let me know if anything else needs adjusting! |
|
Hey @Thaleszh, thanks for the review! All three items have been addressed:
Let me know if anything else needs adjusting. thank you. |
|
Hey @Thaleszh, glad both PRs got merged! Thanks for the guidance throughout the review process. Quick question I reached out to the moderators on Discord about whether these security contributions are eligible for Oro rewards, but the response I got wasn't very clear. Since you reviewed the work directly, would you be able to point me to the right process or the right person to talk to about this? Appreciate any help. |
|
Hey @Thaleszh, glad both PRs got merged! Thanks for the guidance throughout the review process. Quick question - I reached out to the moderators on Discord about whether these security contributions are eligible for Oro rewards, but the response I got wasn't very clear. Since you reviewed the work directly, would you be able to point me to the right process or the right person to talk to about this? Appreciate any help. |
|
Hello @giwaov, every closed PR with a meaningful change is rewarded, even typos, so yours definitely are! You can open a ticket on discord, mention your PRs and Issues and ping me, we will talk internally and then give you a code for ORO. |
|
Hi, I'm Sae Gowon on Discord confirming this is my GitHub account. Thanks! |
Summary
This PR addresses security vulnerabilities discovered during a security audit of the KiiChain oracle module.
Vulnerabilities Fixed
1. HIGH: Missing Denom String Length Validation
Files: \precompiles/oracle/types.go, \x/oracle/keeper/query_server.go\
No maximum length validation for denom strings in oracle queries. Extremely long strings could cause memory exhaustion during KVStore operations.
Fix: Added \MaxDenomLength\ constant (128 bytes, matching the SDK constraint) with validation in both the precompile parser and the gRPC query handler, ensuring all callers are protected.
2. HIGH: Unbounded Memory Allocation in Oracle Queries (DoS)
File: \x/oracle/keeper/query_server.go\
Multiple query functions (\ExchangeRates, \Actives, \VoteTargets, \PriceSnapshotHistory) iterated over all stored data without any limits, allowing attackers to exhaust validator memory.
Fix: Added \MaxQueryResults\ (1000) and \MaxSnapshotResults\ (500) constants to limit iteration and prevent memory exhaustion across all list-style queries.
Impact Assessment
Breaking Changes
None. All changes are backward compatible and only affect input validation and resource limits.