use proptest::prelude::*;
use super::{
attr::{AttrIter, NlAttr, get},
message::{MessageIter, NLMSG_HDRLEN, NlMsgHdr},
messages::{
AddressMessage, LinkMessage, NeighborMessage, RouteMessage, RuleMessage, TcMessage,
},
parse::FromNetlink,
};
fn loop_cap(len: usize) -> usize {
len + 2
}
proptest! {
#[test]
fn message_iter_terminates_without_panic(data in proptest::collection::vec(any::<u8>(), 0..1024)) {
let cap = loop_cap(data.len());
let count = MessageIter::new(&data).take(cap).count();
prop_assert!(count < cap, "MessageIter did not terminate within bound (possible infinite loop)");
}
#[test]
fn message_iter_exhausts_after_error(data in proptest::collection::vec(any::<u8>(), 0..1024)) {
let cap = loop_cap(data.len());
let mut it = MessageIter::new(&data);
let mut polled = 0;
let mut saw_err = false;
#[allow(clippy::while_let_on_iterator)]
while let Some(item) = it.next() {
polled += 1;
prop_assert!(polled <= cap, "iterator exceeded the termination bound");
if item.is_err() {
saw_err = true;
break;
}
}
if saw_err {
prop_assert!(
it.next().is_none(),
"MessageIter must be exhausted after yielding an error (Plan 193 rule 2)"
);
}
}
#[test]
fn attr_iter_terminates_without_panic(data in proptest::collection::vec(any::<u8>(), 0..1024)) {
let cap = loop_cap(data.len());
let count = AttrIter::new(&data).take(cap).count();
prop_assert!(count < cap, "AttrIter did not terminate within bound (possible infinite loop)");
}
#[test]
fn nested_message_attr_walk_never_panics(data in proptest::collection::vec(any::<u8>(), 0..2048)) {
let mut messages = 0;
for item in MessageIter::new(&data).take(loop_cap(data.len())) {
messages += 1;
prop_assert!(messages <= loop_cap(data.len()));
let Ok((_hdr, payload)) = item else { break };
let mut attrs = 0;
for (_kind, attr_payload) in AttrIter::new(payload).take(loop_cap(payload.len())) {
attrs += 1;
prop_assert!(attrs <= loop_cap(payload.len()));
let _ = get::u8(attr_payload);
let _ = get::u16_ne(attr_payload);
let _ = get::u32_ne(attr_payload);
let _ = get::u64_ne(attr_payload);
let _ = get::i32_ne(attr_payload);
let _ = get::string(attr_payload);
let _ = get::bytes(attr_payload);
}
}
}
#[test]
fn struct_from_bytes_never_panics(data in proptest::collection::vec(any::<u8>(), 0..128)) {
let _ = NlMsgHdr::from_bytes(&data);
let _ = NlAttr::from_bytes(&data);
}
#[test]
fn nlmsghdr_accepts_oversized_input(extra in proptest::collection::vec(any::<u8>(), 0..64)) {
let mut buf = vec![0u8; NLMSG_HDRLEN];
buf[0..4].copy_from_slice(&(NLMSG_HDRLEN as u32).to_ne_bytes());
buf.extend_from_slice(&extra);
prop_assert!(
NlMsgHdr::from_bytes(&buf).is_ok(),
"header-sized-or-larger input must parse (Plan 193 rule 1: accept trailing bytes)"
);
}
#[test]
fn typed_message_parsers_never_panic(data in proptest::collection::vec(any::<u8>(), 0..1024)) {
let _ = LinkMessage::from_bytes(&data);
let _ = RouteMessage::from_bytes(&data);
let _ = AddressMessage::from_bytes(&data);
let _ = NeighborMessage::from_bytes(&data);
let _ = RuleMessage::from_bytes(&data);
let _ = TcMessage::from_bytes(&data);
}
}
use super::{
EventSource,
netfilter::ConntrackEntry,
nftables::{RuleInfo, expr::parse_expressions},
protocol::{KobjectUevent, Netfilter, Route, Xfrm},
xfrm::{SecurityAssociation, SecurityPolicy},
};
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn non_rtnetlink_typed_parsers_never_panic(
data in proptest::collection::vec(any::<u8>(), 0..1024)
) {
let _ = ConntrackEntry::from_bytes(&data);
let _ = SecurityAssociation::from_bytes(&data);
let _ = SecurityPolicy::from_bytes(&data);
let _ = RuleInfo::from_bytes(&data);
}
#[test]
fn nftables_expression_decoding_never_panics(
data in proptest::collection::vec(any::<u8>(), 0..2048)
) {
let exprs = parse_expressions(&data);
prop_assert!(exprs.len() <= loop_cap(data.len()));
}
#[test]
fn event_parsers_never_panic(
data in proptest::collection::vec(any::<u8>(), 0..2048)
) {
let _ = <Route as EventSource>::parse_events(&data);
let _ = <Xfrm as EventSource>::parse_events(&data);
let _ = <Netfilter as EventSource>::parse_events(&data);
let _ = <KobjectUevent as EventSource>::parse_events(&data);
}
#[test]
fn uevent_parsing_never_panics(
data in proptest::collection::vec(any::<u8>(), 0..512)
) {
let _ = super::uevent::Uevent::parse(&data);
}
#[test]
fn uevent_parsing_never_panics_on_uevent_shaped_input(
action in "[a-z@=\u{0}]{0,16}",
devpath in "[a-z/@=\u{0}]{0,32}",
rest in proptest::collection::vec("[A-Z=\u{0}]{0,12}", 0..8),
) {
let mut buf = Vec::new();
buf.extend_from_slice(action.as_bytes());
buf.push(b'@');
buf.extend_from_slice(devpath.as_bytes());
buf.push(0);
for kv in &rest {
buf.extend_from_slice(kv.as_bytes());
buf.push(0);
}
let _ = super::uevent::Uevent::parse(&buf);
}
}
fn valid_attr_chain() -> impl Strategy<Value = Vec<u8>> {
proptest::collection::vec(
(any::<u16>(), proptest::collection::vec(any::<u8>(), 0..40)),
0..24,
)
.prop_map(|attrs| {
let mut buf = Vec::new();
for (ty, payload) in attrs {
let len = (4 + payload.len()) as u16;
buf.extend_from_slice(&len.to_ne_bytes());
buf.extend_from_slice(&ty.to_ne_bytes());
buf.extend_from_slice(&payload);
while buf.len() % 4 != 0 {
buf.push(0);
}
}
buf
})
}
fn header_plus_attrs(hdr_len: usize) -> impl Strategy<Value = Vec<u8>> {
(
proptest::collection::vec(any::<u8>(), hdr_len..=hdr_len),
valid_attr_chain(),
)
.prop_map(|(hdr, attrs)| {
let mut buf = hdr;
buf.extend_from_slice(&attrs);
buf
})
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(512))]
#[test]
fn typed_parsers_survive_well_formed_but_hostile_input(
body in header_plus_attrs(16),
) {
let _ = LinkMessage::from_bytes(&body);
let _ = RouteMessage::from_bytes(&body);
let _ = AddressMessage::from_bytes(&body);
let _ = NeighborMessage::from_bytes(&body);
let _ = RuleMessage::from_bytes(&body);
let _ = TcMessage::from_bytes(&body);
let _ = ConntrackEntry::from_bytes(&body);
let _ = SecurityAssociation::from_bytes(&body);
let _ = SecurityPolicy::from_bytes(&body);
let _ = RuleInfo::from_bytes(&body);
}
#[test]
fn expression_decoding_survives_nested_hostile_input(
outer in valid_attr_chain(),
) {
let mut nested = Vec::new();
let len = (4 + outer.len()) as u16;
nested.extend_from_slice(&len.to_ne_bytes());
nested.extend_from_slice(&(1u16 | 0x8000).to_ne_bytes()); nested.extend_from_slice(&outer);
while nested.len() % 4 != 0 {
nested.push(0);
}
let exprs = parse_expressions(&nested);
prop_assert!(exprs.len() <= loop_cap(nested.len()));
}
#[test]
fn event_parsers_survive_well_formed_messages(
msg_type in any::<u16>(),
flags in any::<u16>(),
body in header_plus_attrs(16),
) {
let total = NLMSG_HDRLEN + body.len();
let mut frame = Vec::with_capacity(total);
frame.extend_from_slice(&(total as u32).to_ne_bytes());
frame.extend_from_slice(&msg_type.to_ne_bytes());
frame.extend_from_slice(&flags.to_ne_bytes());
frame.extend_from_slice(&0u32.to_ne_bytes()); frame.extend_from_slice(&0u32.to_ne_bytes()); frame.extend_from_slice(&body);
let _ = <Route as EventSource>::parse_events(&frame);
let _ = <Xfrm as EventSource>::parse_events(&frame);
let _ = <Netfilter as EventSource>::parse_events(&frame);
let _ = <KobjectUevent as EventSource>::parse_events(&frame);
}
}