Untrusted input is used as trusted consensus state
Description
The function verify_header
is called during the QueryMsg::VerifyClientMessage
, which is used to verify the client message if the client message is of the type Header
. The verified header is then used to update the consensus and the client state. The header
is provided via the user and has to be verified to be correct before using the header to update the states.
The function verify_header
creates a TrustedConsensusState
struct via the consensus_state
and the header.trusted_sync_committee.sync_committee
where the consensus_state
is the state stored corresponding to the latest slot and the header.trusted_sync_committee.sync_committee
is what the user provided, as shown below:
pub fn verify_header<V: BlsVerify>(
consensus_state: &ConsensusState,
client_state: &ClientState,
current_timestamp: u64,
header: &Header,
bls_verifier: V,
) -> Result<(), EthereumIBCError> {
let trusted_consensus_state = TrustedConsensusState {
state: consensus_state.clone(),
sync_committee: header.trusted_sync_committee.sync_committee.clone(),
};
Here, the sync_committee
is not validated against the aggregated key stored in the consensus state and hence the trusted sync committee remains untrusted.
There is a similar issue in update_consensus_state
where the trusted_slot
comes from the header, which is not verified against the stored consensus state.
Impact
As the sync committee is used to validate the attested_header
, an incorrect header could be provided, which could be used to prove an incorrect finalized_root
and state root. This could be used to update the light client with incorrect states.
Recommendations
We recommend verifying the sync_committee
provided by the header against the current_sync_committee
or next_sync_committee
stored in the consensus state of the storage.
Remediation
This issue has been acknowledged by Interchain Labs, and a fix was implemented in commit 1f242e04↗.