fix(ecies): bound initial header body size (#12172)

This commit is contained in:
Dan Cline
2024-10-30 07:29:48 -04:00
committed by GitHub
parent 367b4ed18a
commit 6e794ee673
3 changed files with 42 additions and 5 deletions

View File

@ -650,7 +650,8 @@ impl ECIES {
out.extend_from_slice(tag.as_slice());
}
/// Extracts the header from slice and returns the body size.
/// Reads the `RLPx` header from the slice, setting up the MAC and AES, returning the body
/// size contained in the header.
pub fn read_header(&mut self, data: &mut [u8]) -> Result<usize, ECIESError> {
// If the data is not large enough to fit the header and mac bytes, return an error
//

View File

@ -1,12 +1,15 @@
//! This contains the main codec for `RLPx` ECIES messages
use crate::{algorithm::ECIES, ECIESError, EgressECIESValue, IngressECIESValue};
use crate::{algorithm::ECIES, ECIESError, ECIESErrorImpl, EgressECIESValue, IngressECIESValue};
use alloy_primitives::{bytes::BytesMut, B512 as PeerId};
use secp256k1::SecretKey;
use std::{fmt::Debug, io};
use tokio_util::codec::{Decoder, Encoder};
use tracing::{instrument, trace};
/// The max size that the initial handshake packet can be. Currently 2KiB.
const MAX_INITIAL_HANDSHAKE_SIZE: usize = 2048;
/// Tokio codec for ECIES
#[derive(Debug)]
pub struct ECIESCodec {
@ -26,6 +29,11 @@ pub enum ECIESState {
/// message containing the nonce and other metadata.
Ack,
/// This is the same as the [`ECIESState::Header`] stage, but occurs only after the first
/// [`ECIESState::Ack`] message. This is so that the initial handshake message can be properly
/// validated.
InitialHeader,
/// The third stage of the ECIES handshake, where header is parsed, message integrity checks
/// performed, and message is decrypted.
Header,
@ -70,7 +78,7 @@ impl Decoder for ECIESCodec {
self.ecies.read_auth(&mut buf.split_to(total_size))?;
self.state = ECIESState::Header;
self.state = ECIESState::InitialHeader;
return Ok(Some(IngressECIESValue::AuthReceive(self.ecies.remote_id())))
}
ECIESState::Ack => {
@ -89,9 +97,29 @@ impl Decoder for ECIESCodec {
self.ecies.read_ack(&mut buf.split_to(total_size))?;
self.state = ECIESState::Header;
self.state = ECIESState::InitialHeader;
return Ok(Some(IngressECIESValue::Ack))
}
ECIESState::InitialHeader => {
if buf.len() < ECIES::header_len() {
trace!("current len {}, need {}", buf.len(), ECIES::header_len());
return Ok(None)
}
let body_size =
self.ecies.read_header(&mut buf.split_to(ECIES::header_len()))?;
if body_size > MAX_INITIAL_HANDSHAKE_SIZE {
trace!(?body_size, max=?MAX_INITIAL_HANDSHAKE_SIZE, "Header exceeds max initial handshake size");
return Err(ECIESErrorImpl::InitialHeaderBodyTooLarge {
body_size,
max_body_size: MAX_INITIAL_HANDSHAKE_SIZE,
}
.into())
}
self.state = ECIESState::Body;
}
ECIESState::Header => {
if buf.len() < ECIES::header_len() {
trace!("current len {}, need {}", buf.len(), ECIES::header_len());
@ -131,7 +159,7 @@ impl Encoder<EgressECIESValue> for ECIESCodec {
Ok(())
}
EgressECIESValue::Ack => {
self.state = ECIESState::Header;
self.state = ECIESState::InitialHeader;
self.ecies.write_ack(buf);
Ok(())
}

View File

@ -62,6 +62,14 @@ pub enum ECIESErrorImpl {
/// The encrypted data is not large enough for all fields
#[error("encrypted data is not large enough for all fields")]
EncryptedDataTooSmall,
/// The initial header body is too large.
#[error("initial header body is {body_size} but the max is {max_body_size}")]
InitialHeaderBodyTooLarge {
/// The body size from the header
body_size: usize,
/// The max body size
max_body_size: usize,
},
/// Error when trying to split an array beyond its length
#[error("requested {idx} but array len is {len}")]
OutOfBounds {