Summary
On Cosmos CosmWasm (CW) indexer paths, event attributes are decoded with fallible hex::decode / base64 decode (returning Result), then passed to H256::from_slice without a length check.
H256::from_slice panics when the decoded byte length is not exactly 32. Callers use ? and expect a recoverable Err, not a task/process abort.
This is an availability / robustness issue (Cosmos CW indexer panic), not a claim of fund theft, ISM bypass, or message forgery.
Related patterns (different crates/paths):
|
|
| Type |
Availability / panicking parse |
| Severity |
Medium (DoS of Cosmos CW indexing under malformed event attributes) |
| Honest baseline |
Honest contracts typically emit 32-byte hex hashes; practical triggers need malformed/malicious event attributes or a bad data plane |
Environment
| Item |
Value |
| Repo |
hyperlane-xyz/hyperlane-monorepo |
| Commit checked |
12d763327282cfa856d7e31eb79f815f753c23c3 |
| Area |
rust/main/chains/hyperlane-cosmos (CW) |
Root cause pattern
// Pattern (approx.)
Some(H256::from_slice(hex::decode(value)?.as_slice()))
// or base64 decode then from_slice
| Step |
Behavior |
hex::decode("00") |
Ok(vec![0]) — success, length 1 |
H256::from_slice(&[0]) |
panic (len ≠ 32) |
| Expected |
wrong length → Err(...) via ? chain |
So: valid hex of the wrong length never becomes a ? error; it panics inside from_slice.
Call sites (approx. lines @ pin)
| File |
Lines (approx.) |
Notes |
rust/main/chains/hyperlane-cosmos/src/cw/merkle_tree_hook.rs |
~247 |
hex path |
| same file |
~251 |
base64 variant (same from_slice pattern) |
rust/main/chains/hyperlane-cosmos/src/cw/interchain_gas.rs |
~123 |
hex path |
| same file |
nearby |
base64 variant if present |
Byte source: chain/RPC event attribute strings (hash-like fields). Normal modules emit 32-byte encodings; malformed or adversarial attributes can be shorter/longer but still valid hex/base64.
Expected vs actual
|
Expected |
Actual |
| Non-32-byte payload after hex/base64 decode |
Err / skip event |
H256::from_slice panic |
| Same crate style elsewhere |
fallible parse + map_err |
CW paths use unchecked from_slice |
Proof of concept
Primitive behavior (same as used after decode):
H256::from_slice(len=0) → panic
H256::from_slice(len=1) → panic # e.g. hex::decode("00")
H256::from_slice(len=31) → panic
H256::from_slice(len=32) → ok
Minimal illustration:
use hyperlane_core::H256;
fn main() {
// Simulates: hex::decode("00")? → 1 byte → from_slice
let one = hex::decode("00").unwrap();
assert_eq!(one.len(), 1);
let _ = H256::from_slice(&one); // panics
}
// Pattern matching production style:
fn parse_attr(value: &str) -> Result<H256, Box<dyn std::error::Error>> {
Ok(H256::from_slice(hex::decode(value)?.as_slice())) // panics if len != 32
}
// parse_attr("00") → panic, not Err
// parse_attr("00".repeat(32)) with 32 hex pairs → ok if length matches
Impact
- Cosmos CW indexer code that assumes only
Result::Err on bad attributes may abort on panic.
- Affects availability of indexing under malformed event attributes (or a compromised/buggy event source).
- Does not claim unauthorized mint/transfer or ISM forgery.
- Honest mainnet modules that always emit canonical 32-byte hex make accidental triggers less likely; the API contract is still broken for defensive parsing.
Suggested fix
- After
hex::decode / base64 decode, check bytes.len() == 32 and return a typed error before from_slice.
- Prefer fallible constructors (
TryFrom, or a helper h256_from_slice(bytes) -> Result<H256, _>).
- Align with other Hyperlane paths that already use
map_err / length checks instead of panicking parses.
- Unit tests: short/long hex and base64 attributes must return
Err, must not panic.
Example sketch:
let raw = hex::decode(value)?;
if raw.len() != 32 {
return Err(/* InvalidLength / Decoding */);
}
let hash = H256::from_slice(&raw);
Out of scope
Related
Same root theme as other reports: fallible decode (Result) + panicking from_slice, used on network/event-derived data.
Thanks!
Summary
On Cosmos CosmWasm (CW) indexer paths, event attributes are decoded with fallible
hex::decode/ base64 decode (returningResult), then passed toH256::from_slicewithout a length check.H256::from_slicepanics when the decoded byte length is not exactly 32. Callers use?and expect a recoverableErr, not a task/process abort.This is an availability / robustness issue (Cosmos CW indexer panic), not a claim of fund theft, ISM bypass, or message forgery.
Related patterns (different crates/paths):
HyperlaneMessage::fromon short EVM/etc. event bytesdecode_h256/from_sliceon RPC/account data (same primitive: uncheckedfrom_sliceafter fallible decode)Environment
hyperlane-xyz/hyperlane-monorepo12d763327282cfa856d7e31eb79f815f753c23c3rust/main/chains/hyperlane-cosmos(CW)Root cause pattern
hex::decode("00")Ok(vec![0])— success, length 1H256::from_slice(&[0])Err(...)via?chainSo: valid hex of the wrong length never becomes a
?error; it panics insidefrom_slice.Call sites (approx. lines @ pin)
rust/main/chains/hyperlane-cosmos/src/cw/merkle_tree_hook.rsfrom_slicepattern)rust/main/chains/hyperlane-cosmos/src/cw/interchain_gas.rsByte source: chain/RPC event attribute strings (hash-like fields). Normal modules emit 32-byte encodings; malformed or adversarial attributes can be shorter/longer but still valid hex/base64.
Expected vs actual
Err/ skip eventH256::from_slicepanicfrom_sliceProof of concept
Primitive behavior (same as used after decode):
Minimal illustration:
Impact
Result::Erron bad attributes may abort on panic.Suggested fix
hex::decode/ base64 decode, checkbytes.len() == 32and return a typed error beforefrom_slice.TryFrom, or a helperh256_from_slice(bytes) -> Result<H256, _>).map_err/ length checks instead of panicking parses.Err, must not panic.Example sketch:
Out of scope
HyperlaneMessage::fromon EVM/Tron/Fuel/Cosmos native event message bytesdecode_h256/delivered_message_account(separate issue if filed; samefrom_slicefamily)Related
Same root theme as other reports: fallible decode (
Result) + panickingfrom_slice, used on network/event-derived data.Thanks!