Skip to content

Commit

Permalink
add(fuzz): add fuzzing for parsing frames
Browse files Browse the repository at this point in the history
We can now fuzz frame parsing. This already caught multiple panics,
which are also fixed in this commit.

TODO: add fuzzing for emitting frames.
  • Loading branch information
thvdveld committed Mar 6, 2024
1 parent 9909f30 commit 8b19c22
Show file tree
Hide file tree
Showing 23 changed files with 532 additions and 164 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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

resolver = "2"

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

# 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);
}
});
}
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
37 changes: 35 additions & 2 deletions dot15d4/src/frame/ie/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub use payloads::*;
mod nested;
pub use nested::*;

use super::{Error, Result};

use heapless::Vec;

/// IEEE 802.15.4 Information Element reader.
Expand All @@ -17,10 +19,41 @@ pub struct InformationElements<T: AsRef<[u8]>> {
}

impl<T: AsRef<[u8]>> InformationElements<T> {
pub fn new(data: T) -> Self {
Self::new_unchecked(data)
/// Create a new [`InformationElements`] reader from a given buffer.
///
/// # Errors
///
/// Returns an error if the buffer is too short to contain the information elements.
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 information elements.
fn check_len(&self) -> bool {
let mut len = 0;

let mut iter = self.header_information_elements();
while iter.next().is_some() {}
len += iter.offset();

if len > self.data.as_ref().len() {
return false;
}

let mut iter = self.payload_information_elements();
while iter.next().is_some() {}
len += iter.offset();

self.data.as_ref().len() >= len
}

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

0 comments on commit 8b19c22

Please sign in to comment.