Skip to content

Commit

Permalink
Merge pull request #5 from thvdveld/fuzzing
Browse files Browse the repository at this point in the history
Add fuzzing for parsing frames
  • Loading branch information
thvdveld authored Mar 6, 2024
2 parents 9909f30 + fd37f06 commit 0067a11
Show file tree
Hide file tree
Showing 24 changed files with 576 additions and 178 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
/Cargo.lock
dot15d4-fuzz/out
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@

resolver = "2"

members = ["dot15d4", "dot15d4-macros"]
members = [
"dot15d4",
"dot15d4-macros",
]
11 changes: 11 additions & 0 deletions dot15d4-fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "dot15d4-fuzz"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
afl = "0.15.3"
dot15d4 = { path = "../dot15d4" }
1 change: 1 addition & 0 deletions dot15d4-fuzz/in/ack
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
022e37cdab0200020002000200020fe18f
1 change: 1 addition & 0 deletions dot15d4-fuzz/in/data1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
41c846efbeffff06000000000000007a3b3a1a9b0124181ef0070008f00000fe800000000000000200000000000001040e00080c0a000001000000001e003c
1 change: 1 addition & 0 deletions dot15d4-fuzz/in/data2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
41d801cdabffffffd9b514004b12002b
1 change: 1 addition & 0 deletions dot15d4-fuzz/in/enhanced-beacon
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
40ebcdabffff0100010001000100003f1188061a0e0000000000011c0001c800011b00
10 changes: 10 additions & 0 deletions dot15d4-fuzz/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use afl::*;
use dot15d4::frame::{Frame, FrameRepr};

fn main() {
fuzz!(|data: &[u8]| {
if let Ok(frame) = Frame::new(data) {
let _ = FrameRepr::parse(&frame);
}
});
}
45 changes: 31 additions & 14 deletions dot15d4/src/bin/dot15d4-cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ fn main() {
);
}

if let Some(_) = frame.auxiliary_security_header() {
if frame.auxiliary_security_header().is_some() {
println!("{}", "Auxiliary Security Header".underline().bold());
println!(" unimplementec");
}
Expand All @@ -139,7 +139,11 @@ fn main() {

match id {
HeaderElementId::TimeCorrection => {
println!(" {}", TimeCorrection::new(header.content()));
if let Ok(tc) = TimeCorrection::new(header.content()) {
println!(" {}", tc);
} else {
println!(" invalid");
}
}
_ => println!(" unimplemented"),
}
Expand All @@ -155,7 +159,8 @@ fn main() {
for payload in payloads {
match payload.group_id() {
PayloadGroupId::Mlme => {
println!(" {}", "Mlme");
println!(" MLME");

for nested in payload.nested_information_elements() {
println!(
" {}",
Expand All @@ -167,25 +172,37 @@ fn main() {

match nested.sub_id() {
NestedSubId::Short(NestedSubIdShort::TschSynchronization) => {
println!(
" {}",
TschSynchronization::new(nested.content())
);
if let Ok(sync) = TschSynchronization::new(nested.content())
{
println!(" {sync}");
} else {
println!(" invalid");
}
}
NestedSubId::Short(NestedSubIdShort::TschTimeslot) => {
println!(" {}", TschTimeslot::new(nested.content()));
if let Ok(timeslot) = TschTimeslot::new(nested.content()) {
println!(" {timeslot}");
} else {
println!(" invalid");
}
}
NestedSubId::Short(NestedSubIdShort::TschSlotframeAndLink) => {
println!(
" {}",
if let Ok(slotframe_and_link) =
TschSlotframeAndLink::new(nested.content())
);
{
println!(" {slotframe_and_link}");
} else {
println!(" invalid");
}
}
NestedSubId::Long(NestedSubIdLong::ChannelHopping) => {
println!(
" {}",
if let Ok(channel_hopping) =
ChannelHopping::new(nested.content())
);
{
println!(" {channel_hopping}");
} else {
println!(" invalid");
}
}
_ => println!(" unimplemented"),
}
Expand Down
79 changes: 67 additions & 12 deletions dot15d4/src/frame/addressing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use super::FrameControl;
use super::FrameControlRepr;
use super::FrameVersion;
use super::{Error, Result};

/// An IEEE 802.15.4 address.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
Expand Down Expand Up @@ -135,10 +136,45 @@ pub struct AddressingFields<T: AsRef<[u8]>> {
}

impl<T: AsRef<[u8]>> AddressingFields<T> {
pub fn new(buffer: T) -> Self {
Self::new_unchecked(buffer)
/// Create a new [`AddressingFields`] reader/writer from a given buffer.
///
/// # Errors
///
/// This function will check the length of the buffer to ensure it is large enough to contain
/// the addressing fields. If the buffer is too small, an error will be returned.
pub fn new(buffer: T, fc: &FrameControl<T>) -> Result<Self> {
let af = Self::new_unchecked(buffer);

if !af.check_len(fc) {
return Err(Error);
}

Ok(af)
}

/// Check if the buffer is large enough to contain the addressing fields.
fn check_len(&self, fc: &FrameControl<T>) -> bool {
let Some((dst_pan_id_present, dst_addr_mode, src_pan_id_present, src_addr_mode)) = self
.address_present_flags(
fc.frame_version(),
fc.dst_addressing_mode(),
fc.src_addressing_mode(),
fc.pan_id_compression(),
)
else {
return false;
};

let expected_len = (if dst_pan_id_present { 2 } else { 0 })
+ dst_addr_mode.size()
+ (if src_pan_id_present { 2 } else { 0 })
+ src_addr_mode.size();

self.buffer.as_ref().len() >= expected_len
}

/// Create a new [`AddressingFields`] reader/writer from a given buffer without checking the
/// length.
pub fn new_unchecked(buffer: T) -> Self {
Self { buffer }
}
Expand Down Expand Up @@ -166,14 +202,13 @@ impl<T: AsRef<[u8]>> AddressingFields<T> {

fn address_present_flags(
&self,
fc: &FrameControl<T>,
frame_version: FrameVersion,
dst_addr_mode: AddressingMode,
src_addr_mode: AddressingMode,
pan_id_compression: bool,
) -> Option<(bool, AddressingMode, bool, AddressingMode)> {
let dst_addr_mode = fc.dst_addressing_mode();
let src_addr_mode = fc.src_addressing_mode();
let pan_id_compression = fc.pan_id_compression();

use AddressingMode::*;
match fc.frame_version() {
match frame_version {
FrameVersion::Ieee802154_2003 | FrameVersion::Ieee802154_2006 => {
match (dst_addr_mode, src_addr_mode) {
(Absent, src) => Some((false, Absent, true, src)),
Expand Down Expand Up @@ -209,7 +244,12 @@ impl<T: AsRef<[u8]>> AddressingFields<T> {

/// Return the IEEE 802.15.4 destination [`Address`] if not absent.
pub fn dst_address(&self, fc: &FrameControl<T>) -> Option<Address> {
if let Some((dst_pan_id, dst_addr, _, _)) = self.address_present_flags(fc) {
if let Some((dst_pan_id, dst_addr, _, _)) = self.address_present_flags(
fc.frame_version(),
fc.dst_addressing_mode(),
fc.src_addressing_mode(),
fc.pan_id_compression(),
) {
let offset = if dst_pan_id { 2 } else { 0 };

match dst_addr {
Expand All @@ -235,7 +275,12 @@ impl<T: AsRef<[u8]>> AddressingFields<T> {

/// Return the IEEE 802.15.4 source [`Address`] if not absent.
pub fn src_address(&self, fc: &FrameControl<T>) -> Option<Address> {
if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.address_present_flags(fc) {
if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.address_present_flags(
fc.frame_version(),
fc.dst_addressing_mode(),
fc.src_addressing_mode(),
fc.pan_id_compression(),
) {
let mut offset = if dst_pan_id { 2 } else { 0 };
offset += dst_addr.size();
offset += if src_pan_id { 2 } else { 0 };
Expand Down Expand Up @@ -263,7 +308,12 @@ impl<T: AsRef<[u8]>> AddressingFields<T> {

/// Return the IEEE 802.15.4 destination PAN ID if not elided.
pub fn dst_pan_id(&self, fc: &FrameControl<T>) -> Option<u16> {
if let Some((true, _, _, _)) = self.address_present_flags(fc) {
if let Some((true, _, _, _)) = self.address_present_flags(
fc.frame_version(),
fc.dst_addressing_mode(),
fc.src_addressing_mode(),
fc.pan_id_compression(),
) {
let b = &self.buffer.as_ref()[..2];
Some(u16::from_le_bytes([b[0], b[1]]))
} else {
Expand All @@ -273,7 +323,12 @@ impl<T: AsRef<[u8]>> AddressingFields<T> {

/// Return the IEEE 802.15.4 source PAN ID if not elided.
pub fn src_pan_id(&self, fc: &FrameControl<T>) -> Option<u16> {
if let Some((dst_pan_id, dst_addr, true, _)) = self.address_present_flags(fc) {
if let Some((dst_pan_id, dst_addr, true, _)) = self.address_present_flags(
fc.frame_version(),
fc.dst_addressing_mode(),
fc.src_addressing_mode(),
fc.pan_id_compression(),
) {
let mut offset = if dst_pan_id { 2 } else { 0 };
offset += dst_addr.size();

Expand Down
24 changes: 23 additions & 1 deletion dot15d4/src/frame/frame_control.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! IEEE 802.15.4 Frame Control field readers and writers.

use super::AddressingMode;
use super::{Error, Result};

/// IEEE 802.15.4 frame type.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
Expand Down Expand Up @@ -56,7 +57,28 @@ pub struct FrameControl<T: AsRef<[u8]>> {
}

impl<T: AsRef<[u8]>> FrameControl<T> {
pub fn new(buffer: T) -> Self {
/// Create a new [`FrameControl`] reader/writer from a given buffer.
///
/// # Errors
///
/// Returns an error if the buffer is too short.
pub fn new(buffer: T) -> Result<Self> {
let fc = Self::new_unchecked(buffer);

if !fc.check_len() {
return Err(Error);
}

Ok(fc)
}

/// Returns `false` if the buffer is too short to contain the Frame Control field.
fn check_len(&self) -> bool {
self.buffer.as_ref().len() >= 2
}

/// Create a new [`FrameControl`] reader/writer from a given buffer without length checking.
pub fn new_unchecked(buffer: T) -> Self {
Self { buffer }
}

Expand Down
52 changes: 44 additions & 8 deletions dot15d4/src/frame/ie/headers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! IEEE 802.15.4 Header Information Element reader and writers.

use crate::frame::{Error, Result};
use crate::time::Duration;
use dot15d4_macros::frame;

Expand All @@ -10,10 +11,27 @@ pub struct HeaderInformationElement<T: AsRef<[u8]>> {
}

impl<T: AsRef<[u8]>> HeaderInformationElement<T> {
pub fn new(data: T) -> Self {
Self::new_unchecked(data)
/// Create a new [`HeaderInformationElement`] reader/writer from a given buffer.
///
/// # Errors
///
/// Returns an error if the length field is less than 2.
pub fn new(data: T) -> Result<Self> {
let ie = Self::new_unchecked(data);

if !ie.check_len() {
return Err(Error);
}

Ok(ie)
}

/// Returns `false` if the buffer is too short to contain the Header Information Element.
fn check_len(&self) -> bool {
self.data.as_ref().len() >= 2
}

/// Create a new [`HeaderInformationElement`] reader/writer from a given buffer without length checking.
pub fn new_unchecked(data: T) -> Self {
Self { data }
}
Expand Down Expand Up @@ -92,7 +110,10 @@ impl<T: AsRef<[u8]>> core::fmt::Display for HeaderInformationElement<T> {
)
}
HeaderElementId::TimeCorrection => {
write!(f, "{} {}", id, TimeCorrection::new(self.content()))
let Ok(tc) = TimeCorrection::new(self.content()) else {
return write!(f, "{:?}({:0x?})", id, self.content());
};
write!(f, "{} {}", id, tc)
}
id => write!(f, "{:?}({:0x?})", id, self.content()),
}
Expand Down Expand Up @@ -180,9 +201,7 @@ impl<'f> Iterator for HeaderInformationElementsIterator<'f> {
if self.terminated {
None
} else {
let ie = HeaderInformationElement {
data: &self.data[self.offset..],
};
let ie = HeaderInformationElement::new(&self.data[self.offset..]).ok()?;

self.terminated = matches!(
ie.element_id(),
Expand Down Expand Up @@ -292,10 +311,27 @@ pub struct TimeCorrection<T: AsRef<[u8]>> {
}

impl<T: AsRef<[u8]>> TimeCorrection<T> {
pub fn new(buffer: T) -> Self {
Self { buffer }
/// Create a new [`TimeCorrection`] reader/writer from a given buffer.
///
/// # Errors
///
/// Returns an error if the buffer is too short.
pub fn new(buffer: T) -> Result<Self> {
let ie = Self::new_unchecked(buffer);

if !ie.check_len() {
return Err(Error);
}

Ok(ie)
}

/// Returns `false` if the buffer is too short to contain the Time Correction field.
fn check_len(&self) -> bool {
self.buffer.as_ref().len() >= 2
}

/// Create a new [`TimeCorrection`] reader/writer from a given buffer without length checking.
pub fn new_unchecked(buffer: T) -> Self {
Self { buffer }
}
Expand Down
Loading

0 comments on commit 0067a11

Please sign in to comment.