Skip to content

fix(oracle): add denom length validation and query result limits#287

Merged
Thaleszh merged 6 commits into
KiiChain:mainfrom
giwaov:fix/security-vulnerabilities-audit
Mar 26, 2026
Merged

fix(oracle): add denom length validation and query result limits#287
Thaleszh merged 6 commits into
KiiChain:mainfrom
giwaov:fix/security-vulnerabilities-audit

Conversation

@giwaov

@giwaov giwaov commented Mar 17, 2026

Copy link
Copy Markdown

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

Severity Issue Impact
HIGH Missing denom validation Memory exhaustion via oversized inputs
HIGH Unbounded query iteration Memory exhaustion, validator DoS

Breaking Changes

None. All changes are backward compatible and only affect input validation and resource limits.

@giwaov giwaov requested a review from jhelison as a code owner March 17, 2026 09:43
Copilot AI review requested due to automatic review settings March 17, 2026 09:43
@coderabbitai

coderabbitai Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Introduces denom-length validation and capped query results in the oracle module. Adds MaxDenomLength = 128 (precompiles and keeper) and rejects denom inputs with len(denom) > MaxDenomLength in ParseGetExchangeRateArgs and QueryServer.ExchangeRate. Adds MaxQueryResults = 1000 and MaxSnapshotResults = 500; list-style queries (ExchangeRates, Actives, VoteTargets) now stop iteration once MaxQueryResults is reached, and PriceSnapshotHistory stops once MaxSnapshotResults is reached. VoteTargets now iterates via keeper walk and accumulates results.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive and relates to the changeset, covering denom validation and unbounded query iteration fixes with clear vulnerability explanations and impact assessment.
Title check ✅ Passed The title accurately and concisely summarizes the main changes: denom length validation and query result limits for the oracle module.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) and RequiredGas(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 truncated flag) 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.

Comment thread x/oracle/keeper/query_server.go
Comment thread precompiles/oracle/types.go
Comment thread ante/gov_vote_ante.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Add iteration limit to VoteTargets query to match other list queries.

The VoteTargets method lacks bounds like those applied to ExchangeRates and Actives queries in the same file. Both of these use MaxQueryResults (1000) to prevent memory exhaustion during unbounded iteration. Apply the same pattern to VoteTargets:

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), but MaxDenomLength is 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

📥 Commits

Reviewing files that changed from the base of the PR and between b103c1c and 9bd170d.

📒 Files selected for processing (4)
  • ante/gov_vote_ante.go
  • precompiles/oracle/oracle.go
  • precompiles/oracle/types.go
  • x/oracle/keeper/query_server.go

@Thaleszh

Copy link
Copy Markdown
Contributor

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).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9bd170d and 00ba472.

📒 Files selected for processing (2)
  • precompiles/oracle/types.go
  • x/oracle/keeper/query_server.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • x/oracle/keeper/query_server.go

Comment thread precompiles/oracle/types.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.
@giwaov

giwaov commented Mar 19, 2026

Copy link
Copy Markdown
Author

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:

  • Denom length validation in the precompile and query server
  • Unbounded query limits for ExchangeRates, Actives, VoteTargets, and PriceSnapshotHistory

Also addressed the CodeRabbit review feedback:

  • Lowered MaxDenomLength from 256 to 128 to match the SDK constraint
  • Added the same iteration limit to VoteTargets (was missing)
  • Centralized the denom length check in QueryServer.ExchangeRate so it applies to all callers, not just the precompile

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

codecov Bot commented Mar 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.30435% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
x/oracle/keeper/query_server.go 90.47% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@Thaleszh

Copy link
Copy Markdown
Contributor

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

@giwaov giwaov changed the title fix: Security vulnerabilities in ante handlers, precompiles, and oracle queries fix(oracle): add denom length validation and query result limits Mar 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0b69210 and e43741e.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • precompiles/oracle/types_test.go
  • x/oracle/keeper/query_server_test.go
✅ Files skipped from review due to trivial changes (2)
  • CHANGELOG.md
  • precompiles/oracle/types_test.go

Comment thread x/oracle/keeper/query_server_test.go
@giwaov

giwaov commented Mar 25, 2026

Copy link
Copy Markdown
Author

Hey @Thaleszh, thanks for the review! All three items have been addressed:

  • PR title updated to reflect the current scope
  • Changelog added under ## Unreleased with both fixes listed
  • Regression tests added for denom-length validation in both the precompile (TestParseGetExchangeRateArgsDenomLength) and the query server (TestQueryExchangeRateDenomLength), covering boundary cases (at max, over max, empty, extremely long)

Let me know if anything else needs adjusting!

@giwaov

giwaov commented Mar 25, 2026

Copy link
Copy Markdown
Author

Hey @Thaleszh, thanks for the review! All three items have been addressed:

  • PR title updated to reflect the current scope
  • Changelog added under Unreleased with both fixes listed
  • Regression tests added for denom-length validation in both the precompile and the query server, covering boundary cases (at max, over max, empty, extremely long)

Let me know if anything else needs adjusting. thank you.

@Thaleszh Thaleszh merged commit 6e1c3db into KiiChain:main Mar 26, 2026
10 checks passed
@giwaov

giwaov commented Mar 26, 2026

Copy link
Copy Markdown
Author

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.

@giwaov

giwaov commented Mar 26, 2026

Copy link
Copy Markdown
Author

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.

@Thaleszh

Copy link
Copy Markdown
Contributor

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.

@giwaov

giwaov commented Mar 30, 2026

Copy link
Copy Markdown
Author

Hi, I'm Sae Gowon on Discord confirming this is my GitHub account. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants