Skip to content

MsgUpdateOperationalChainParams missing range validation can panic zetaclient outbound scheduler #4602

Description

@kingpinXD

Summary

MsgUpdateOperationalChainParams.ValidateBasic accepts values that ChainParams.Validate() would reject. The largest gap is OutboundScheduleInterval: ValidateBasic only rejects negatives, but the canonical validator rejects 0 and any value above 100. The keeper applies the update through SetChainParamsList without running ChainParams.Validate() against the resulting list, so an operational-policy holder can write an OutboundScheduleInterval = 0 to state. Every chain scheduler in zetaclient then reads that value and performs nonce % scheduleInterval, which crashes the goroutine.

Root cause

  • x/observer/types/message_update_operational_chain_params.go:58-81ValidateBasic only checks Creator, ChainId < 0, OutboundScheduleInterval < 0, OutboundScheduleLookahead < 0, and ConfirmationParams.Validate(). The four ticker fields and the two int64 schedule fields are not range-checked against ChainParams.Validate().
  • x/observer/keeper/msg_server_update_operational_chain_params.go:14-52 — handler assigns fields directly onto the matched ChainParams entry and calls SetChainParamsList without running cp.Validate().
  • x/observer/types/chain_params.go:74-164 — canonical ranges that the operational path bypasses: GasPriceTicker (0,300], InboundTicker (0,300], OutboundTicker (0,300], WatchUtxoTicker <= 300, OutboundScheduleInterval [1,100], OutboundScheduleLookahead [1,500].

Scheduler consumption sites that panic on interval = 0

All read the value via ChainParams().OutboundScheduleInterval once per tick and feed it straight into a modulo without a zero-guard:

  • zetaclient/chains/bitcoin/bitcoin.go:138,186
  • zetaclient/chains/base/signer_batch_sign.go:58-83 (EVM scheduleKeysign at evm.go:220-221)
  • zetaclient/chains/sui/sui.go:136,176
  • zetaclient/chains/ton/ton.go:187
  • zetaclient/chains/solana/solana.go:196

A panic in t.exec(ctx) unwinds out of blockTicker.Start (pkg/scheduler/tickers.go:81-113), back into bg.Work (pkg/bg/bg.go:35-65), which recovers and logs but does not restart. The ticker goroutine ends. Outbound CCTX processing for the affected chain stops until each observer-signer node is manually restarted with corrected params. Related but distinct from #3437 (closed) which asked for a panic-tolerance test on the scheduler — the validation gap itself was not closed.

Reachability

MsgUpdateOperationalChainParams is in OperationPolicyMessages (x/authority/types/authorization_list.go:24), gated to the groupOperational policy role. Admin-error class, not permissionless. There is no on-chain path for an untrusted account to send this message.

Fix

  1. Tighten ValidateBasic to mirror ChainParams.Validate() ranges:

    • GasPriceTicker, InboundTicker, OutboundTicker: require > 0 && <= 300.
    • WatchUtxoTicker: require <= 300.
    • OutboundScheduleInterval: require > 0 && <= 100.
    • OutboundScheduleLookahead: require > 0 && <= 500.
  2. In the keeper handler, after applying field assignments and before SetChainParamsList, run chainParamsList.ChainParams[i].Validate() and return on failure (belt-and-braces against future field additions).

  3. Fix the test fixture at x/observer/keeper/msg_server_update_operational_chain_params_test.go:134-150 to use in-range values — current fixture asserts require.NoError(t, err) on values that ChainParams.Validate() would reject.

Optional defense-in-depth: zero-guards at the scheduler consumption sites (~10 LOC across five files) so zetaclient is resilient to any future config path that produces a zero interval.

Severity

Low. Admin-gated (operational policy is a trusted governance role), but recovery from a bad update requires a coordinated restart of every observer-signer for the affected chain. Worth fixing as validation hardening.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions