//! K425: bounded, no-offload virtio-net receive length policy. //! This only validates lengths; parsing an Ethernet header is K426. pub const VIRTIO_HEADER_BYTES: usize = 12; pub const MAX_ETHERNET_FRAME_BYTES: usize = 1514; pub const MAX_USED_BYTES: usize = VIRTIO_HEADER_BYTES + MAX_ETHERNET_FRAME_BYTES; pub const RX_DMA_BUFFER_BYTES: usize = 2048; pub const ETHERNET_HEADER_BYTES: usize = 14; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { ShortVirtioHeader, ShortEthernetHeader, UnsupportedEtherType, JumboFrame, DmaOverrun, InvalidCompletion, ShortResponse, UnexpectedLength, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum EtherType { Ipv4, Arp, } pub fn classify_ethertype(frame: &[u8]) -> Result { let header = copy_ethernet_header(frame)?; match u16::from_be_bytes([header[12], header[13]]) { 0x0800 => Ok(EtherType::Ipv4), 0x0806 => Ok(EtherType::Arp), _ => Err(Error::UnsupportedEtherType), } } pub fn copy_ethernet_header(frame: &[u8]) -> Result<[u8; ETHERNET_HEADER_BYTES], Error> { if frame.len() > MAX_ETHERNET_FRAME_BYTES { return Err(Error::JumboFrame); } let prefix = frame .get(..ETHERNET_HEADER_BYTES) .ok_or(Error::ShortEthernetHeader)?; let mut header = [0u8; ETHERNET_HEADER_BYTES]; header.copy_from_slice(prefix); Ok(header) } pub fn checked_frame_length(used_bytes: usize) -> Result { if used_bytes > RX_DMA_BUFFER_BYTES { return Err(Error::DmaOverrun); } let frame_bytes = used_bytes .checked_sub(VIRTIO_HEADER_BYTES) .ok_or(Error::ShortVirtioHeader)?; if frame_bytes > MAX_ETHERNET_FRAME_BYTES { return Err(Error::JumboFrame); } Ok(frame_bytes) } /// K428's one-descriptor, one-packet fixture has an exact expected frame. /// This is not a general multi-packet RX completion policy. pub fn validate_fixture_completion( used_index: u16, used_id: u32, used_len: u32, expected_frame_bytes: usize, ) -> Result<(), Error> { if used_index != 1 || used_id != 0 { return Err(Error::InvalidCompletion); } if !(ETHERNET_HEADER_BYTES..=MAX_ETHERNET_FRAME_BYTES).contains(&expected_frame_bytes) { return Err(Error::UnexpectedLength); } let actual = checked_frame_length(used_len as usize)?; if actual < expected_frame_bytes { return Err(Error::ShortResponse); } if actual > expected_frame_bytes { return Err(Error::UnexpectedLength); } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn k425_accepts_exact_no_offload_frame_limit() { assert_eq!(checked_frame_length(12), Ok(0)); assert_eq!(checked_frame_length(12 + 14), Ok(14)); assert_eq!(checked_frame_length(1526), Ok(1514)); assert!(RX_DMA_BUFFER_BYTES >= MAX_USED_BYTES); } #[test] fn k425_rejects_short_jumbo_and_dma_overrun_without_subtract_underflow() { for short in [0, 1, 11] { assert_eq!(checked_frame_length(short), Err(Error::ShortVirtioHeader)); } for jumbo in [1527, 1530, 2048] { assert_eq!(checked_frame_length(jumbo), Err(Error::JumboFrame)); } for overrun in [2049, usize::MAX] { assert_eq!(checked_frame_length(overrun), Err(Error::DmaOverrun)); } } #[test] fn k426_requires_fourteen_bytes_and_copies_exact_header() { let bytes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0x08, 0x00, 0xee]; assert_eq!( copy_ethernet_header(&bytes[..14]), Ok(bytes[..14].try_into().unwrap()) ); assert_eq!( copy_ethernet_header(&bytes), Ok(bytes[..14].try_into().unwrap()) ); } #[test] fn k426_short_or_jumbo_frame_is_rejected_before_header_copy() { let frame = [0u8; 1515]; for short in [0, 1, 13] { assert_eq!( copy_ethernet_header(&frame[..short]), Err(Error::ShortEthernetHeader) ); } assert_eq!(copy_ethernet_header(&frame[..1514]), Ok([0; 14])); assert_eq!(copy_ethernet_header(&frame), Err(Error::JumboFrame)); } #[test] fn k427_decodes_network_byte_order_for_ipv4_and_arp() { let mut frame = [0u8; 14]; frame[12] = 0x08; frame[13] = 0x00; assert_eq!(classify_ethertype(&frame), Ok(EtherType::Ipv4)); frame[13] = 0x06; assert_eq!(classify_ethertype(&frame), Ok(EtherType::Arp)); } #[test] fn k427_drops_unknown_vlan_ipv6_and_short_headers() { let mut frame = [0u8; 14]; for kind in [[0, 0], [0x86, 0xdd], [0x81, 0], [0, 8]] { frame[12..14].copy_from_slice(&kind); assert_eq!(classify_ethertype(&frame), Err(Error::UnsupportedEtherType)); } assert_eq!( classify_ethertype(&frame[..13]), Err(Error::ShortEthernetHeader) ); } #[test] fn k428_known_rx_completion_requires_exact_used_index_head_and_length() { assert_eq!(validate_fixture_completion(1, 0, 72, 60), Ok(())); assert_eq!( validate_fixture_completion(0, 0, 72, 60), Err(Error::InvalidCompletion) ); assert_eq!( validate_fixture_completion(1, 1, 72, 60), Err(Error::InvalidCompletion) ); assert_eq!( validate_fixture_completion(1, 0, 71, 60), Err(Error::ShortResponse) ); assert_eq!( validate_fixture_completion(1, 0, 73, 60), Err(Error::UnexpectedLength) ); } #[test] fn k428_fixture_rejects_invalid_expected_size_before_claiming_packet() { assert_eq!( validate_fixture_completion(1, 0, 72, 13), Err(Error::UnexpectedLength) ); assert_eq!( validate_fixture_completion(1, 0, 72, 1515), Err(Error::UnexpectedLength) ); assert_eq!( validate_fixture_completion(1, 0, 11, 60), Err(Error::ShortVirtioHeader) ); } }