Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing validation checks for all IBC message types #650

Merged
merged 26 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e1bee48
Add missing ClientType validation
Farhad-Shabani Apr 18, 2023
eef2633
Move ClientType verification under validate.rs to cover all needed ch…
Farhad-Shabani Apr 19, 2023
3244dbc
Move ClientType tests into the validate.rs
Farhad-Shabani Apr 19, 2023
f2e599f
Some adjustments
Farhad-Shabani Apr 19, 2023
24877e4
Fix tests
Farhad-Shabani Apr 19, 2023
e5f6710
Mend ClientId for upgrade-client tests
Farhad-Shabani Apr 19, 2023
3010b6d
Rename to default_validate_identifier
Farhad-Shabani Apr 19, 2023
262170e
Refactor for more generic functions
Farhad-Shabani Apr 20, 2023
4d437cb
Merge branch 'main' into farhad/client-type-validation
Farhad-Shabani Apr 20, 2023
c6ebd3e
Revise changelog
Farhad-Shabani Apr 20, 2023
72f635e
Add missing counterparty client_id check
Farhad-Shabani Apr 20, 2023
f04699e
Remove duplicate checks and unnecessary methods
Farhad-Shabani Apr 20, 2023
5f02452
Merge branch 'main' into farhad/client-type-validation
Farhad-Shabani Apr 24, 2023
5080510
Add missing changelog
Farhad-Shabani Apr 24, 2023
5517583
Add missing message validation checks
Farhad-Shabani Apr 25, 2023
4af0a70
Adjusted counterpary init Conn/Chan id checks
Farhad-Shabani Apr 26, 2023
0ce56e6
Merge branch 'main' into farhad/missing-validation-checks
Farhad-Shabani May 1, 2023
513c041
Merge branch 'main' into farhad/missing-validation-checks
Farhad-Shabani May 2, 2023
655815d
Fix some nits
Farhad-Shabani May 2, 2023
c3b1d8b
Merge branch 'main' into farhad/missing-validation-checks
Farhad-Shabani May 3, 2023
5609fd1
name changes
plafer May 5, 2023
1134f01
fmt
plafer May 5, 2023
7bec947
Fix channel state check for acknowledgement
Farhad-Shabani May 5, 2023
1f01e76
Remove comment of connection_hops_length check
Farhad-Shabani May 5, 2023
7f65497
Remove previous_channel_id field from MsgChannelOpenTry
Farhad-Shabani May 5, 2023
cb71f2c
Expose verify_connection_hops_length for chan_open_init/try
Farhad-Shabani May 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Add missing validation checks for all the IBC message types
([#233](https://github.com/cosmos/ibc-rs/issues/233))
31 changes: 9 additions & 22 deletions crates/ibc/src/applications/transfer/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,13 @@ use crate::prelude::*;
pub enum TokenTransferError {
/// context error: `{0}`
ContextError(ContextError),
/// invalid identifier: `{0}`
InvalidIdentifier(ValidationError),
/// destination channel not found in the counterparty of port_id `{port_id}` and channel_id `{channel_id}`
DestinationChannelNotFound {
port_id: PortId,
channel_id: ChannelId,
},
/// invalid port identifier `{context}`, validation error: `{validation_error}`
InvalidPortId {
context: String,
validation_error: ValidationError,
},
/// invalid channel identifier `{context}`, validation error: `{validation_error}`
InvalidChannelId {
context: String,
validation_error: ValidationError,
},
/// invalid packet timeout height value `{context}`
InvalidPacketTimeoutHeight { context: String },
/// invalid packet timeout timestamp value `{timestamp}`
InvalidPacketTimeoutTimestamp { timestamp: u64 },
/// base denomination is empty
EmptyBaseDenom,
/// invalid prot id n trace at position: `{pos}`, validation error: `{validation_error}`
Expand Down Expand Up @@ -88,14 +76,7 @@ impl std::error::Error for TokenTransferError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self {
Self::ContextError(e) => Some(e),
Self::InvalidPortId {
validation_error: e,
..
} => Some(e),
Self::InvalidChannelId {
validation_error: e,
..
} => Some(e),
Self::InvalidIdentifier(e) => Some(e),
Self::InvalidTracePortId {
validation_error: e,
..
Expand Down Expand Up @@ -123,3 +104,9 @@ impl From<ContextError> for TokenTransferError {
Self::ContextError(err)
}
}

impl From<ValidationError> for TokenTransferError {
fn from(err: ValidationError) -> TokenTransferError {
Self::InvalidIdentifier(err)
}
}
40 changes: 17 additions & 23 deletions crates/ibc/src/applications/transfer/msgs/transfer.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
//! This is the definition of a transfer messages that an application submits to a chain.

use crate::applications::transfer::packet::PacketData;
use crate::prelude::*;

use ibc_proto::google::protobuf::Any;
use ibc_proto::ibc::applications::transfer::v1::MsgTransfer as RawMsgTransfer;
use ibc_proto::protobuf::Protobuf;

use crate::applications::transfer::error::TokenTransferError;
use crate::applications::transfer::packet::PacketData;
use crate::core::ics04_channel::error::PacketError;
use crate::core::ics04_channel::timeout::TimeoutHeight;
use crate::core::ics24_host::identifier::{ChannelId, PortId};
use crate::core::ContextError;
use crate::timestamp::Timestamp;
use crate::tx_msg::Msg;

Expand Down Expand Up @@ -51,30 +53,22 @@ impl TryFrom<RawMsgTransfer> for MsgTransfer {

fn try_from(raw_msg: RawMsgTransfer) -> Result<Self, Self::Error> {
let timeout_timestamp_on_b = Timestamp::from_nanoseconds(raw_msg.timeout_timestamp)
.map_err(|_| TokenTransferError::InvalidPacketTimeoutTimestamp {
timestamp: raw_msg.timeout_timestamp,
})?;

let timeout_height_on_b: TimeoutHeight =
raw_msg.timeout_height.try_into().map_err(|e| {
TokenTransferError::InvalidPacketTimeoutHeight {
context: format!("invalid timeout height {e}"),
}
})?;
.map_err(PacketError::InvalidPacketTimestamp)
.map_err(ContextError::from)?;

let timeout_height_on_b: TimeoutHeight = raw_msg
.timeout_height
.try_into()
.map_err(ContextError::from)?;

// Packet timeout height and packet timeout timestamp cannot both be unset.
if !timeout_height_on_b.is_set() && !timeout_timestamp_on_b.is_set() {
return Err(PacketError::MissingTimeout).map_err(ContextError::from)?;
}

Ok(MsgTransfer {
port_id_on_a: raw_msg.source_port.parse().map_err(|e| {
TokenTransferError::InvalidPortId {
context: raw_msg.source_port.clone(),
validation_error: e,
}
})?,
chan_id_on_a: raw_msg.source_channel.parse().map_err(|e| {
TokenTransferError::InvalidChannelId {
context: raw_msg.source_channel.clone(),
validation_error: e,
}
})?,
port_id_on_a: raw_msg.source_port.parse()?,
chan_id_on_a: raw_msg.source_channel.parse()?,
packet_data: PacketData {
token: raw_msg
.token
Expand Down
20 changes: 6 additions & 14 deletions crates/ibc/src/applications/transfer/relay/send_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ where
ctx_a.can_send_coins()?;

let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &msg.chan_id_on_a);
let chan_end_on_a = ctx_a
.channel_end(&chan_end_path_on_a)
.map_err(TokenTransferError::ContextError)?;
let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?;

let port_id_on_b = chan_end_on_a.counterparty().port_id().clone();
let chan_id_on_b = chan_end_on_a
Expand All @@ -44,9 +42,7 @@ where
.clone();

let seq_send_path_on_a = SeqSendPath::new(&msg.port_id_on_a, &msg.chan_id_on_a);
let sequence = ctx_a
.get_next_sequence_send(&seq_send_path_on_a)
.map_err(TokenTransferError::ContextError)?;
let sequence = ctx_a.get_next_sequence_send(&seq_send_path_on_a)?;

let token = &msg.packet_data.token;

Expand Down Expand Up @@ -84,7 +80,7 @@ where
}
};

send_packet_validate(ctx_a, &packet).map_err(TokenTransferError::ContextError)?;
send_packet_validate(ctx_a, &packet)?;

Ok(())
}
Expand All @@ -97,9 +93,7 @@ where
Ctx: TokenTransferExecutionContext,
{
let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &msg.chan_id_on_a);
let chan_end_on_a = ctx_a
.channel_end(&chan_end_path_on_a)
.map_err(TokenTransferError::ContextError)?;
let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?;

let port_on_b = chan_end_on_a.counterparty().port_id().clone();
let chan_on_b = chan_end_on_a
Expand All @@ -113,9 +107,7 @@ where

// get the next sequence
let seq_send_path_on_a = SeqSendPath::new(&msg.port_id_on_a, &msg.chan_id_on_a);
let sequence = ctx_a
.get_next_sequence_send(&seq_send_path_on_a)
.map_err(TokenTransferError::ContextError)?;
let sequence = ctx_a.get_next_sequence_send(&seq_send_path_on_a)?;

let token = &msg.packet_data.token;

Expand Down Expand Up @@ -155,7 +147,7 @@ where
}
};

send_packet_execute(ctx_a, packet).map_err(TokenTransferError::ContextError)?;
send_packet_execute(ctx_a, packet)?;

{
ctx_a.log_message(format!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ impl ClientState {
client_id: &ClientId,
misbehaviour: TmMisbehaviour,
) -> Result<(), ClientError> {
misbehaviour.validate_basic()?;
plafer marked this conversation as resolved.
Show resolved Hide resolved

let header_1 = misbehaviour.header1();
let trusted_consensus_state_1 = {
let consensus_state_path =
Expand All @@ -41,39 +43,11 @@ impl ClientState {
downcast_tm_consensus_state(consensus_state.as_ref())
}?;

self.check_misbehaviour_headers_consistency(header_1, header_2)?;

let current_timestamp = ctx.host_timestamp()?;
self.verify_misbehaviour_header(header_1, &trusted_consensus_state_1, current_timestamp)?;
self.verify_misbehaviour_header(header_2, &trusted_consensus_state_2, current_timestamp)
}

pub fn check_misbehaviour_headers_consistency(
&self,
header_1: &TmHeader,
header_2: &TmHeader,
) -> Result<(), ClientError> {
if header_1.signed_header.header.chain_id != header_2.signed_header.header.chain_id {
return Err(Error::InvalidRawMisbehaviour {
reason: "headers must have identical chain_ids".to_owned(),
}
.into());
}

if header_1.height() < header_2.height() {
return Err(Error::InvalidRawMisbehaviour {
reason: format!(
"headers1 height is less than header2 height ({} < {})",
header_1.height(),
header_2.height()
),
}
.into());
}

Ok(())
}

pub fn verify_misbehaviour_header(
&self,
header: &TmHeader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,12 @@ impl ClientState {
client_id: &ClientId,
header: TmHeader,
) -> Result<(), ClientError> {
// Checks that the header fields are valid.
header.validate_basic()?;
plafer marked this conversation as resolved.
Show resolved Hide resolved

// The tendermint-light-client crate though works on heights that are assumed
// to have the same revision number. We ensure this here.
if header.height().revision_number() != self.chain_id().version() {
return Err(ClientError::ClientSpecific {
description: Error::MismatchedRevisions {
current_revision: self.chain_id().version(),
update_revision: header.height().revision_number(),
}
.to_string(),
});
}

// We also need to ensure that the trusted height (representing the
// height of the header already on chain for which this client update is
// based on) must be smaller than height of the new header that we're
// installing.
if header.height() <= header.trusted_height {
return Err(ClientError::HeaderVerificationFailure {
reason: format!(
"header height <= header trusted height ({} <= {})",
header.height(),
header.trusted_height
),
});
}
header.verify_chain_id_version_matches_height(&self.chain_id())?;

// Delegate to tendermint-light-client, which contains the required checks
// of the new header against the trusted consensus state.
Expand Down
30 changes: 19 additions & 11 deletions crates/ibc/src/clients/ics07_tendermint/consensus_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ impl TryFrom<RawConsensusState> for ConsensusState {
type Error = Error;

fn try_from(raw: RawConsensusState) -> Result<Self, Self::Error> {
let proto_root = raw
.root
.ok_or(Error::InvalidRawClientState {
reason: "missing commitment root".into(),
})?
.hash;
if proto_root.is_empty() {
return Err(Error::InvalidRawClientState {
reason: "empty commitment root".into(),
});
};

let ibc_proto::google::protobuf::Timestamp { seconds, nanos } =
raw.timestamp.ok_or(Error::InvalidRawClientState {
reason: "missing timestamp".into(),
Expand All @@ -62,19 +74,15 @@ impl TryFrom<RawConsensusState> for ConsensusState {
reason: format!("invalid timestamp: {e}"),
})?;

let next_validators_hash = Hash::from_bytes(Algorithm::Sha256, &raw.next_validators_hash)
.map_err(|e| Error::InvalidRawClientState {
reason: e.to_string(),
})?;

Ok(Self {
root: raw
.root
.ok_or(Error::InvalidRawClientState {
reason: "missing commitment root".into(),
})?
.hash
.into(),
root: proto_root.into(),
timestamp,
next_validators_hash: Hash::from_bytes(Algorithm::Sha256, &raw.next_validators_hash)
.map_err(|e| Error::InvalidRawClientState {
reason: e.to_string(),
})?,
next_validators_hash,
})
}
}
Expand Down
25 changes: 10 additions & 15 deletions crates/ibc/src/clients/ics07_tendermint/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use tendermint::account::Id;
use tendermint::{Error as TendermintError, Hash};
use tendermint_light_client_verifier::errors::VerificationErrorDetail as LightClientErrorDetail;
use tendermint_light_client_verifier::operations::VotingPowerTally;
use tendermint_light_client_verifier::types::Validator;
use tendermint_light_client_verifier::Verdict;

#[derive(Debug, Display)]
Expand Down Expand Up @@ -69,11 +68,13 @@ pub enum Error {
InvalidHeaderHeight { height: u64 },
/// Disallowed to create a new client with a frozen height
FrozenHeightNotAllowed,
/// the header's current/trusted revision number (`{current_revision}`) and the update's revision number (`{update_revision}`) should be the same
MismatchedRevisions {
current_revision: u64,
update_revision: u64,
/// the header's trusted revision number (`{trusted_revision}`) and the update's revision number (`{header_revision}`) should be the same
MismatchHeightRevisions {
trusted_revision: u64,
header_revision: u64,
},
/// the given chain-id (`{given}`) does not match the chain-id of the client (`{expected}`)
MismatchHeaderChainId { given: String, expected: String },
/// not enough trust because insufficient validators overlap: `{reason}`
NotEnoughTrustedValsSigned { reason: VotingPowerTally },
/// verification failed: `{detail}`
Expand All @@ -82,11 +83,10 @@ pub enum Error {
ProcessedTimeNotFound { client_id: ClientId, height: Height },
/// Processed height for the client `{client_id}` at height `{height}` not found
ProcessedHeightNotFound { client_id: ClientId, height: Height },
/// trusted validators `{trusted_validator_set:?}`, does not hash to latest trusted validators. Expected: `{next_validators_hash}`, got: `{trusted_val_hash}`
MisbehaviourTrustedValidatorHashMismatch {
trusted_validator_set: Vec<Validator>,
next_validators_hash: Hash,
trusted_val_hash: Hash,
/// The given hash of the validators does not matches the given hash in the signed header. Expected: `{signed_header_validators_hash}`, got: `{validators_hash}`
MismatchValidatorsHashes {
validators_hash: Hash,
signed_header_validators_hash: Hash,
},
/// current timestamp minus the latest consensus state timestamp is greater than or equal to the trusting period (`{duration_since_consensus_state:?}` >= `{trusting_period:?}`)
ConsensusStateTimestampGteTrustingPeriod {
Expand All @@ -97,11 +97,6 @@ pub enum Error {
MisbehaviourHeadersBlockHashesEqual,
/// headers are not at same height and are monotonically increasing
MisbehaviourHeadersNotAtSameHeight,
/// header chain-id `{header_chain_id}` does not match the light client's chain-id `{chain_id}`)
MisbehaviourHeadersChainIdMismatch {
header_chain_id: String,
chain_id: String,
},
/// invalid raw client id: `{client_id}`
InvalidRawClientId { client_id: String },
}
Expand Down
Loading