use std::panic::catch_unwind;
use proptest::{
collection::vec,
prelude::{BoxedStrategy, ProptestConfig, Strategy, any, prop},
prop_assert, prop_oneof, proptest,
};
use sciparse::{
core::{
encode::{EncodeError, WireEncode},
view::View,
},
packet::{
model::{ScionRawPacket, ScionScmpPacket, ScionUdpPacket},
view::ScionRawPacketView,
},
};
#[test]
fn encoding_invalid_packets_must_not_panic() {
proptest!(
ProptestConfig::with_cases(5_000),
|(invalid_opts: packet_manipulation::InvalidPacketOptions)| {
encoding_invalid_packets_impl(invalid_opts)?;
}
);
fn encoding_invalid_packets_impl(
invalid_opts: packet_manipulation::InvalidPacketOptions,
) -> Result<(), proptest::prelude::TestCaseError> {
let unwind = catch_unwind(|| {
let packet = invalid_opts.into_packet();
let required_size = packet.required_size();
let mut buf = vec![0u8; required_size];
match packet.try_encode(&mut buf) {
Ok(_) => {
let (view, _rest) = ScionRawPacketView::try_from_mut_slice(&mut buf)
.expect("Anything that encodes must be decodable");
sciparse::util::fuzz::view_function_checks::packet::exec_every_view_function(
view,
);
}
Err(EncodeError::InvalidStructure(_)) => {
return Ok(());
}
Err(e) => {
prop_assert!(
false,
"Unexpected error during invalid packet encoding {:?}",
e
);
}
}
Ok(())
});
match unwind {
Ok(res) => res,
Err(panic) => {
println!("{:?}", panic.downcast_ref::<&str>());
prop_assert!(false, "Panic during invalid packet encoding");
Ok(())
}
}
}
}
mod packet_manipulation {
use proptest::prelude::Arbitrary;
use sciparse::{
address::host_addr::WireHostAddr,
dataplane_path::{
model::DpPath,
standard::{
layout::StdPathMetaLayout,
model::HopField,
types::{HopFieldFlags, HopFieldMac},
},
types::PathType,
},
packet::classify::ClassifiedPacket,
};
use tinyvec::ArrayVec;
use super::*;
#[derive(Debug, Clone)]
pub struct InvalidPacketOptions {
base: ClassifiedPacket,
modifications: Vec<PacketModification>,
}
#[derive(Debug, Clone)]
pub enum PacketModification {
EmptySegment(u8),
InvalidCurrentHop(u8),
InvalidCurrentInfo(u8),
TooManyHopFields(u8, u8),
InvalidUnknownPathBytes(Vec<u8>),
InvalidSrcAddrBytes(ArrayVec<[u8; 16]>),
InvalidDstAddrBytes(ArrayVec<[u8; 16]>),
WrongNextHeader(u8),
}
impl InvalidPacketOptions {
pub fn into_packet(self) -> ClassifiedPacket {
let InvalidPacketOptions {
base,
modifications,
} = self;
let (mut header, payload) = match base {
ClassifiedPacket::Udp(pkt) => {
(
pkt.header,
PayloadState::Typed(ClassifiedPayload::Udp(pkt.payload)),
)
}
ClassifiedPacket::Scmp(pkt) => {
(
pkt.header,
PayloadState::Typed(ClassifiedPayload::Scmp(pkt.payload)),
)
}
ClassifiedPacket::Other(pkt) => (pkt.header, PayloadState::Raw(pkt.payload)),
};
for modification in modifications {
match modification {
PacketModification::EmptySegment(seg_idx) => {
if let DpPath::Standard(ref mut std_path) = header.path {
if std_path.segments.is_empty() {
continue;
}
let target =
(seg_idx as usize).min(std_path.segments.len().saturating_sub(1));
if let Some(segment) = std_path.segments.get_mut(target) {
segment.hop_fields.clear();
}
}
}
PacketModification::InvalidCurrentHop(exceed_by) => {
if let DpPath::Standard(ref mut std_path) = header.path {
let count = std_path.hop_field_count();
let max_valid = if count == 0 { 0 } else { count - 1 };
let invalid = max_valid + exceed_by as usize;
let clamped = invalid.min(StdPathMetaLayout::MAX_SEGMENT_HOPS);
std_path.current_hop_field = clamped as u8;
}
}
PacketModification::InvalidCurrentInfo(exceed_by) => {
if let DpPath::Standard(ref mut std_path) = header.path {
let count = std_path.info_field_count();
let max_valid = if count == 0 { 0 } else { count - 1 };
let invalid = max_valid + exceed_by as usize;
let clamped =
invalid.min(StdPathMetaLayout::CURR_INFO_FIELD_RNG.max_uint());
std_path.current_info_field = clamped as u8;
}
}
PacketModification::TooManyHopFields(seg_idx, extra) => {
if let DpPath::Standard(ref mut std_path) = header.path {
let seg_len = std_path.segments.len();
if seg_len == 0 {
continue;
}
let seg_idx = (seg_idx as usize).min(seg_len - 1);
let segment = &mut std_path.segments[seg_idx];
let target_hops = 63usize.saturating_add(extra as usize);
if segment.hop_fields.len() < target_hops {
segment.hop_fields.resize(
target_hops,
HopField {
flags: HopFieldFlags::empty(),
expiration_units: 0,
cons_ingress: 0,
cons_egress: 0,
mac: HopFieldMac::from([0; 6]),
},
);
}
}
}
PacketModification::InvalidUnknownPathBytes(raw_bytes) => {
header.path = DpPath::Unsupported {
path_type: PathType::Other(6),
data: raw_bytes,
};
}
PacketModification::InvalidSrcAddrBytes(raw_bytes) => {
header.address.src_host_addr = WireHostAddr::Unknown {
id: 7,
bytes: raw_bytes,
};
}
PacketModification::InvalidDstAddrBytes(raw_bytes) => {
header.address.dst_host_addr = WireHostAddr::Unknown {
id: 7,
bytes: raw_bytes,
};
}
PacketModification::WrongNextHeader(nh) => {
header.common.next_header = nh.into();
}
}
}
match payload {
PayloadState::Typed(ClassifiedPayload::Udp(udp)) => {
ClassifiedPacket::Udp(ScionUdpPacket {
header,
payload: udp,
})
}
PayloadState::Typed(ClassifiedPayload::Scmp(scmp)) => {
ClassifiedPacket::Scmp(ScionScmpPacket {
header,
payload: scmp,
})
}
PayloadState::Raw(raw) => {
ClassifiedPacket::Other(ScionRawPacket {
header,
payload: raw,
})
}
}
}
}
#[derive(Debug, Clone)]
enum PayloadState {
Typed(ClassifiedPayload),
Raw(Vec<u8>),
}
#[derive(Debug, Clone)]
enum ClassifiedPayload {
Udp(sciparse::payload::udp::model::UdpDatagram),
Scmp(sciparse::payload::scmp::model::ScmpMessage),
}
impl Arbitrary for InvalidPacketOptions {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
any::<ClassifiedPacket>()
.prop_flat_map(|base| {
let has_scion_path = match &base {
ClassifiedPacket::Udp(p) => {
matches!(p.header.path, DpPath::Standard(_))
}
ClassifiedPacket::Scmp(p) => {
matches!(p.header.path, DpPath::Standard(_))
}
ClassifiedPacket::Other(p) => {
matches!(p.header.path, DpPath::Standard(_))
}
};
prop::collection::vec(packet_modification_strategy(has_scion_path), 1..=5)
.prop_map(move |modifications| {
InvalidPacketOptions {
base: base.clone(),
modifications,
}
})
})
.boxed()
}
}
fn packet_modification_strategy(has_scion_path: bool) -> BoxedStrategy<PacketModification> {
if has_scion_path {
prop_oneof![
(0u8..=2).prop_map(PacketModification::EmptySegment),
(1u8..=255).prop_map(PacketModification::InvalidCurrentHop),
(1u8..=4).prop_map(PacketModification::InvalidCurrentInfo),
(0u8..=2, 1u8..=255)
.prop_map(|(seg, extra)| PacketModification::TooManyHopFields(seg, extra)),
vec(prop::num::u8::ANY, 0..=15).prop_map(|mut bytes| {
if bytes.len().is_multiple_of(4) {
bytes.pop();
}
PacketModification::InvalidSrcAddrBytes(ArrayVec::from_iter(bytes))
}),
vec(prop::num::u8::ANY, 0..=15).prop_map(|mut bytes| {
if bytes.len().is_multiple_of(4) {
bytes.pop();
}
PacketModification::InvalidDstAddrBytes(ArrayVec::from_iter(bytes))
}),
any::<u8>().prop_map(PacketModification::WrongNextHeader),
]
.boxed()
} else {
prop_oneof![
vec(prop::num::u8::ANY, 1..=100).prop_map(|mut bytes| {
if bytes.len().is_multiple_of(4) {
bytes.pop();
}
PacketModification::InvalidUnknownPathBytes(bytes)
}),
vec(prop::num::u8::ANY, 0..=15).prop_map(|mut bytes| {
if bytes.len().is_multiple_of(4) {
bytes.pop();
}
PacketModification::InvalidSrcAddrBytes(ArrayVec::from_iter(bytes))
}),
vec(prop::num::u8::ANY, 0..=15).prop_map(|mut bytes| {
if bytes.len().is_multiple_of(4) {
bytes.pop();
}
PacketModification::InvalidDstAddrBytes(ArrayVec::from_iter(bytes))
}),
any::<u8>().prop_map(PacketModification::WrongNextHeader),
]
.boxed()
}
}
}