diff --git a/net/src/headers/mod.rs b/net/src/headers/mod.rs index 67e439abff..d81a84b950 100644 --- a/net/src/headers/mod.rs +++ b/net/src/headers/mod.rs @@ -890,6 +890,20 @@ impl Headers { } } + pub(crate) fn transport_payload_len(&self) -> Option { + let ip_payload_len = match self.net.as_ref()? { + Net::Ipv4(ip) => usize::from(ip.0.payload_len().ok()?), + Net::Ipv6(ip) => usize::from(ip.0.payload_length), + }; + let after_net = self + .net_ext + .iter() + .map(|ext| usize::from(ext.size().get())) + .sum::() + + usize::from(self.transport.as_ref()?.size().get()); + ip_payload_len.checked_sub(after_net) + } + /// update the checksums of the headers pub(crate) fn update_checksums(&mut self, payload: impl AsRef<[u8]>) { let is_vxlan = self.try_vxlan().is_some(); diff --git a/net/src/packet/mod.rs b/net/src/packet/mod.rs index 3cdee54bf3..003206faa4 100644 --- a/net/src/packet/mod.rs +++ b/net/src/packet/mod.rs @@ -327,7 +327,12 @@ impl Packet { /// Update the network and transport checksums based on the current headers. pub fn update_checksums(&mut self) -> &mut Self { - self.headers.update_checksums(&self.payload); + let payload = self.payload.as_ref(); + let payload = match self.headers.transport_payload_len() { + Some(len) if len <= payload.len() => &payload[..len], + _ => payload, + }; + self.headers.update_checksums(payload); self.meta_mut().set_checksum_refresh(false); self } @@ -888,3 +893,281 @@ mod qos_roundtrip_tests { } } } + +#[cfg(test)] +mod padding_tests { + use crate::buffer::TestBuffer; + use crate::checksum::Checksum; + use crate::eth::ethtype::EthType; + use crate::headers::{Headers, Net, TryHeaders, TryIcmp4, TryIp, TryTcp, TryUdp}; + use crate::packet::Packet; + use crate::packet::test_utils::make_default_for_eth; + use crate::tcp::TcpChecksumPayload; + use crate::udp::UdpChecksumPayload; + use crate::udp::UdpEncap; + use crate::vxlan::{Vni, Vxlan, VxlanEncap}; + use arrayvec::ArrayVec; + + const MIN_ETHERNET_FRAME: usize = 60; + + fn frame(protocol: u8, l4: &[u8]) -> Vec { + let mut frame = Vec::new(); + frame.extend_from_slice(&[0x02, 0, 0, 0, 0, 1]); // destination mac + frame.extend_from_slice(&[0x02, 0, 0, 0, 0, 2]); // source mac + frame.extend_from_slice(&[0x08, 0x00]); // ipv4 + frame.extend_from_slice(&[0x45, 0x00]); + #[allow(clippy::cast_possible_truncation)] // test input is small + frame.extend_from_slice(&((20 + l4.len()) as u16).to_be_bytes()); + frame.extend_from_slice(&[0x00, 0x01, 0x00, 0x00, 0x40, protocol, 0x00, 0x00]); + frame.extend_from_slice(&[192, 168, 0, 1]); // source ip + frame.extend_from_slice(&[192, 168, 0, 2]); // destination ip + frame.extend_from_slice(l4); + frame + } + + fn tcp_ack() -> Vec { + let mut tcp = Vec::new(); + tcp.extend_from_slice(&1000_u16.to_be_bytes()); // source port + tcp.extend_from_slice(&2000_u16.to_be_bytes()); // destination port + tcp.extend_from_slice(&[0, 0, 0, 1]); // sequence number + tcp.extend_from_slice(&[0, 0, 0, 2]); // acknowledgement number + tcp.extend_from_slice(&[0x50, 0x10]); // data offset 5, ACK + tcp.extend_from_slice(&1024_u16.to_be_bytes()); // window + tcp.extend_from_slice(&[0, 0]); // checksum + tcp.extend_from_slice(&[0, 0]); // urgent pointer + tcp + } + + fn udp(payload: &[u8]) -> Vec { + let mut udp = Vec::new(); + udp.extend_from_slice(&1000_u16.to_be_bytes()); // source port + udp.extend_from_slice(&2000_u16.to_be_bytes()); // destination port + #[allow(clippy::cast_possible_truncation)] // test input is small + udp.extend_from_slice(&((8 + payload.len()) as u16).to_be_bytes()); + udp.extend_from_slice(&[0, 0]); // checksum + udp.extend_from_slice(payload); + udp + } + + fn icmp4_echo_request() -> Vec { + vec![8, 0, 0, 0, 0x00, 0x2a, 0x00, 0x01] + } + + fn parse(frame: &[u8]) -> Packet { + Packet::new(TestBuffer::from_raw_data(frame)).expect("frame does not parse") + } + + fn pad(mut frame: Vec, filler: u8) -> Vec { + assert!(frame.len() < MIN_ETHERNET_FRAME, "frame needs no padding"); + frame.resize(MIN_ETHERNET_FRAME, filler); + frame + } + + #[test] + fn tcp_checksum_excludes_zeroed_ethernet_padding() { + let mut packet = parse(&pad(frame(6, &tcp_ack()), 0)); + packet.update_checksums(); + let net = packet.headers().try_ip().expect("no ip header").clone(); + packet + .headers() + .try_tcp() + .expect("no tcp header") + .validate_checksum(&TcpChecksumPayload::new(&net, &[])) + .expect("padding leaked into the tcp checksum"); + } + + #[test] + fn udp_checksum_excludes_non_zero_ethernet_padding() { + let mut packet = parse(&pad(frame(17, &udp(&[])), 0xab)); + packet.update_checksums(); + let net = packet.headers().try_ip().expect("no ip header").clone(); + packet + .headers() + .try_udp() + .expect("no udp header") + .validate_checksum(&UdpChecksumPayload::new(&net, &[])) + .expect("padding leaked into the udp checksum"); + } + + #[test] + fn icmp4_checksum_excludes_non_zero_ethernet_padding() { + let mut packet = parse(&pad(frame(1, &icmp4_echo_request()), 0xab)); + packet.update_checksums(); + packet + .headers() + .try_icmp4() + .expect("no icmp header") + .validate_checksum(&[]) + .expect("padding leaked into the icmp checksum"); + } + + #[test] + fn checksum_still_covers_a_real_payload() { + let payload: Vec = (0..32_u8).collect(); + let mut packet = parse(&frame(17, &udp(&payload))); + assert_eq!(packet.payload().as_ref(), payload.as_slice()); + packet.update_checksums(); + let net = packet.headers().try_ip().expect("no ip header").clone(); + packet + .headers() + .try_udp() + .expect("no udp header") + .validate_checksum(&UdpChecksumPayload::new(&net, &payload)) + .expect("payload dropped out of the udp checksum"); + } + + #[test] + fn truncated_payload_does_not_panic() { + let payload: Vec = (0..32_u8).collect(); + let mut frame = frame(17, &udp(&payload)); + frame.truncate(frame.len() - 8); + let mut packet = parse(&frame); + packet.update_checksums(); + } + + fn tcp(payload: &[u8]) -> Vec { + let mut tcp = tcp_ack(); + tcp.extend_from_slice(payload); + tcp + } + + fn vxlan_wrap(inner: &[u8]) -> Vec { + let mut packet = + Packet::new(TestBuffer::from_raw_data(inner)).expect("inner frame does not parse"); + packet + .vxlan_encap(&vxlan_encap_params()) + .expect("vxlan encap failed"); + packet + .serialize() + .expect("vxlan frame does not serialize") + .as_ref() + .to_vec() + } + + fn vxlan_encap_params() -> VxlanEncap { + let mut ip = crate::ipv4::Ipv4::default(); + ip.set_source( + crate::ipv4::addr::UnicastIpv4Addr::new("10.0.0.1".parse().unwrap()).unwrap(), + ); + ip.set_destination("10.0.0.2".parse().unwrap()); + ip.set_ttl(64); + ip.set_next_header(crate::ip::NextHeader::UDP); + + let headers = Headers { + eth: Some(make_default_for_eth(EthType::IPV4)), + vlan: ArrayVec::default(), + net: Some(Net::Ipv4(ip)), + net_ext: ArrayVec::default(), + transport: None, + udp_encap: Some(UdpEncap::Vxlan(Vxlan::new(Vni::new_checked(42).unwrap()))), + embedded_ip: None, + }; + VxlanEncap::new(headers).unwrap_or_else(|e| unreachable!("{e:?}")) + } + + #[test] + fn vxlan_carries_padding_that_the_inner_checksum_must_ignore() { + let inner = pad(frame(6, &tcp_ack()), 0xab); + assert_eq!( + inner.len(), + MIN_ETHERNET_FRAME, + "the inner frame should have been padded" + ); + + let mut packet = parse(&vxlan_wrap(&inner)); + packet + .vxlan_decap() + .expect("not a vxlan packet") + .expect("inner frame does not parse"); + packet.update_checksums(); + + let net = packet + .headers() + .try_ip() + .expect("no inner ip header") + .clone(); + packet + .headers() + .try_tcp() + .expect("no inner tcp header") + .validate_checksum(&TcpChecksumPayload::new(&net, &[])) + .expect("inner ethernet padding leaked into the tcp checksum"); + } + + #[test] + fn trailing_octets_never_enter_a_tcp_checksum() { + bolero::check!() + .with_type::<(Vec, Vec)>() + .for_each(|(payload, trailer)| { + let payload = &payload[..payload.len().min(256)]; + let trailer = &trailer[..trailer.len().min(64)]; + let mut bytes = frame(6, &tcp(payload)); + bytes.extend_from_slice(trailer); + let Ok(mut packet) = Packet::new(TestBuffer::from_raw_data(&bytes)) else { + return; + }; + packet.update_checksums(); + let net = packet.headers().try_ip().expect("no ip header").clone(); + packet + .headers() + .try_tcp() + .expect("no tcp header") + .validate_checksum(&TcpChecksumPayload::new(&net, payload)) + .expect("trailing octets leaked into the tcp checksum"); + }); + } + + #[test] + fn trailing_octets_never_enter_a_udp_checksum() { + bolero::check!() + .with_type::<(Vec, Vec)>() + .for_each(|(payload, trailer)| { + let payload = &payload[..payload.len().min(256)]; + let trailer = &trailer[..trailer.len().min(64)]; + let mut bytes = frame(17, &udp(payload)); + bytes.extend_from_slice(trailer); + let Ok(mut packet) = Packet::new(TestBuffer::from_raw_data(&bytes)) else { + return; + }; + packet.update_checksums(); + let net = packet.headers().try_ip().expect("no ip header").clone(); + packet + .headers() + .try_udp() + .expect("no udp header") + .validate_checksum(&UdpChecksumPayload::new(&net, payload)) + .expect("trailing octets leaked into the udp checksum"); + }); + } + + #[test] + fn trailing_octets_never_enter_a_vxlan_inner_tcp_checksum() { + bolero::check!() + .with_type::<(Vec, Vec)>() + .for_each(|(payload, trailer)| { + let payload = &payload[..payload.len().min(128)]; + let trailer = &trailer[..trailer.len().min(64)]; + let mut inner = frame(6, &tcp(payload)); + inner.extend_from_slice(trailer); + let Ok(mut packet) = Packet::new(TestBuffer::from_raw_data(&vxlan_wrap(&inner))) + else { + return; + }; + let Some(Ok(_)) = packet.vxlan_decap() else { + return; + }; + packet.update_checksums(); + let net = packet + .headers() + .try_ip() + .expect("no inner ip header") + .clone(); + packet + .headers() + .try_tcp() + .expect("no inner tcp header") + .validate_checksum(&TcpChecksumPayload::new(&net, payload)) + .expect("inner trailing octets leaked into the tcp checksum"); + }); + } +}