Skip to content

Commit 24c5806

Browse files
committed
add(fuzz): add fuzzing for parsing frames
We can now fuzz frame parsing. This already caught multiple panics, which are also fixed in this commit. TODO: add fuzzing for emitting frames.
1 parent 9909f30 commit 24c5806

23 files changed

+541
-164
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/target
22
/Cargo.lock
3+
dot15d4-fuzz/out

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
resolver = "2"
44

5-
members = ["dot15d4", "dot15d4-macros"]
5+
members = ["dot15d4", "dot15d4-fuzz", "dot15d4-macros"]

dot15d4-fuzz/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "dot15d4-fuzz"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
afl = "0.15.3"
10+
dot15d4 = { path = "../dot15d4" }

dot15d4-fuzz/in/ack

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
022e37cdab0200020002000200020fe18f

dot15d4-fuzz/in/data1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
41c846efbeffff06000000000000007a3b3a1a9b0124181ef0070008f00000fe800000000000000200000000000001040e00080c0a000001000000001e003c

dot15d4-fuzz/in/data2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
41d801cdabffffffd9b514004b12002b

dot15d4-fuzz/in/enhanced-beacon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
40ebcdabffff0100010001000100003f1188061a0e0000000000011c0001c800011b00

dot15d4-fuzz/src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use afl::*;
2+
use dot15d4::frame::{Frame, FrameRepr};
3+
4+
fn main() {
5+
fuzz!(|data: &[u8]| {
6+
if let Ok(frame) = Frame::new(data) {
7+
let _ = FrameRepr::parse(&frame);
8+
}
9+
});
10+
}

dot15d4/src/frame/addressing.rs

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use super::FrameControl;
44
use super::FrameControlRepr;
55
use super::FrameVersion;
6+
use super::{Error, Result};
67

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

137138
impl<T: AsRef<[u8]>> AddressingFields<T> {
138-
pub fn new(buffer: T) -> Self {
139-
Self::new_unchecked(buffer)
139+
/// Create a new [`AddressingFields`] reader/writer from a given buffer.
140+
///
141+
/// # Errors
142+
///
143+
/// This function will check the length of the buffer to ensure it is large enough to contain
144+
/// the addressing fields. If the buffer is too small, an error will be returned.
145+
pub fn new(buffer: T, fc: &FrameControl<T>) -> Result<Self> {
146+
let af = Self::new_unchecked(buffer);
147+
148+
if !af.check_len(fc) {
149+
return Err(Error);
150+
}
151+
152+
Ok(af)
140153
}
141154

155+
/// Check if the buffer is large enough to contain the addressing fields.
156+
fn check_len(&self, fc: &FrameControl<T>) -> bool {
157+
let Some((dst_pan_id_present, dst_addr_mode, src_pan_id_present, src_addr_mode)) = self
158+
.address_present_flags(
159+
fc.frame_version(),
160+
fc.dst_addressing_mode(),
161+
fc.src_addressing_mode(),
162+
fc.pan_id_compression(),
163+
)
164+
else {
165+
return false;
166+
};
167+
168+
let expected_len = (if dst_pan_id_present { 2 } else { 0 })
169+
+ dst_addr_mode.size()
170+
+ (if src_pan_id_present { 2 } else { 0 })
171+
+ src_addr_mode.size();
172+
173+
self.buffer.as_ref().len() >= expected_len
174+
}
175+
176+
/// Create a new [`AddressingFields`] reader/writer from a given buffer without checking the
177+
/// length.
142178
pub fn new_unchecked(buffer: T) -> Self {
143179
Self { buffer }
144180
}
@@ -166,14 +202,13 @@ impl<T: AsRef<[u8]>> AddressingFields<T> {
166202

167203
fn address_present_flags(
168204
&self,
169-
fc: &FrameControl<T>,
205+
frame_version: FrameVersion,
206+
dst_addr_mode: AddressingMode,
207+
src_addr_mode: AddressingMode,
208+
pan_id_compression: bool,
170209
) -> Option<(bool, AddressingMode, bool, AddressingMode)> {
171-
let dst_addr_mode = fc.dst_addressing_mode();
172-
let src_addr_mode = fc.src_addressing_mode();
173-
let pan_id_compression = fc.pan_id_compression();
174-
175210
use AddressingMode::*;
176-
match fc.frame_version() {
211+
match frame_version {
177212
FrameVersion::Ieee802154_2003 | FrameVersion::Ieee802154_2006 => {
178213
match (dst_addr_mode, src_addr_mode) {
179214
(Absent, src) => Some((false, Absent, true, src)),
@@ -209,7 +244,12 @@ impl<T: AsRef<[u8]>> AddressingFields<T> {
209244

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

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

236276
/// Return the IEEE 802.15.4 source [`Address`] if not absent.
237277
pub fn src_address(&self, fc: &FrameControl<T>) -> Option<Address> {
238-
if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.address_present_flags(fc) {
278+
if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.address_present_flags(
279+
fc.frame_version(),
280+
fc.dst_addressing_mode(),
281+
fc.src_addressing_mode(),
282+
fc.pan_id_compression(),
283+
) {
239284
let mut offset = if dst_pan_id { 2 } else { 0 };
240285
offset += dst_addr.size();
241286
offset += if src_pan_id { 2 } else { 0 };
@@ -263,7 +308,12 @@ impl<T: AsRef<[u8]>> AddressingFields<T> {
263308

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

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

dot15d4/src/frame/frame_control.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! IEEE 802.15.4 Frame Control field readers and writers.
22
33
use super::AddressingMode;
4+
use super::{Error, Result};
45

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

5859
impl<T: AsRef<[u8]>> FrameControl<T> {
59-
pub fn new(buffer: T) -> Self {
60+
/// Create a new [`FrameControl`] reader/writer from a given buffer.
61+
///
62+
/// # Errors
63+
///
64+
/// Returns an error if the buffer is too short.
65+
pub fn new(buffer: T) -> Result<Self> {
66+
let fc = Self::new_unchecked(buffer);
67+
68+
if !fc.check_len() {
69+
return Err(Error);
70+
}
71+
72+
Ok(fc)
73+
}
74+
75+
/// Returns `false` if the buffer is too short to contain the Frame Control field.
76+
fn check_len(&self) -> bool {
77+
self.buffer.as_ref().len() >= 2
78+
}
79+
80+
/// Create a new [`FrameControl`] reader/writer from a given buffer without length checking.
81+
pub fn new_unchecked(buffer: T) -> Self {
6082
Self { buffer }
6183
}
6284

dot15d4/src/frame/ie/headers.rs

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! IEEE 802.15.4 Header Information Element reader and writers.
22
3+
use crate::frame::{Error, Result};
34
use crate::time::Duration;
45
use dot15d4_macros::frame;
56

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

1213
impl<T: AsRef<[u8]>> HeaderInformationElement<T> {
13-
pub fn new(data: T) -> Self {
14-
Self::new_unchecked(data)
14+
/// Create a new [`HeaderInformationElement`] reader/writer from a given buffer.
15+
///
16+
/// # Errors
17+
///
18+
/// Returns an error if the length field is less than 2.
19+
pub fn new(data: T) -> Result<Self> {
20+
let ie = Self::new_unchecked(data);
21+
22+
if !ie.check_len() {
23+
return Err(Error);
24+
}
25+
26+
Ok(ie)
1527
}
1628

29+
/// Returns `false` if the buffer is too short to contain the Header Information Element.
30+
fn check_len(&self) -> bool {
31+
self.data.as_ref().len() >= 2
32+
}
33+
34+
/// Create a new [`HeaderInformationElement`] reader/writer from a given buffer without length checking.
1735
pub fn new_unchecked(data: T) -> Self {
1836
Self { data }
1937
}
@@ -92,7 +110,10 @@ impl<T: AsRef<[u8]>> core::fmt::Display for HeaderInformationElement<T> {
92110
)
93111
}
94112
HeaderElementId::TimeCorrection => {
95-
write!(f, "{} {}", id, TimeCorrection::new(self.content()))
113+
let Ok(tc) = TimeCorrection::new(self.content()) else {
114+
return write!(f, "{:?}({:0x?})", id, self.content());
115+
};
116+
write!(f, "{} {}", id, tc)
96117
}
97118
id => write!(f, "{:?}({:0x?})", id, self.content()),
98119
}
@@ -180,9 +201,7 @@ impl<'f> Iterator for HeaderInformationElementsIterator<'f> {
180201
if self.terminated {
181202
None
182203
} else {
183-
let ie = HeaderInformationElement {
184-
data: &self.data[self.offset..],
185-
};
204+
let ie = HeaderInformationElement::new(&self.data[self.offset..]).ok()?;
186205

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

294313
impl<T: AsRef<[u8]>> TimeCorrection<T> {
295-
pub fn new(buffer: T) -> Self {
296-
Self { buffer }
314+
/// Create a new [`TimeCorrection`] reader/writer from a given buffer.
315+
///
316+
/// # Errors
317+
///
318+
/// Returns an error if the buffer is too short.
319+
pub fn new(buffer: T) -> Result<Self> {
320+
let ie = Self::new_unchecked(buffer);
321+
322+
if !ie.check_len() {
323+
return Err(Error);
324+
}
325+
326+
Ok(ie)
327+
}
328+
329+
/// Returns `false` if the buffer is too short to contain the Time Correction field.
330+
fn check_len(&self) -> bool {
331+
self.buffer.as_ref().len() >= 2
297332
}
298333

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

dot15d4/src/frame/ie/mod.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub use payloads::*;
99
mod nested;
1010
pub use nested::*;
1111

12+
use super::{Error, Result};
13+
1214
use heapless::Vec;
1315

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

1921
impl<T: AsRef<[u8]>> InformationElements<T> {
20-
pub fn new(data: T) -> Self {
21-
Self::new_unchecked(data)
22+
/// Create a new [`InformationElements`] reader from a given buffer.
23+
///
24+
/// # Errors
25+
///
26+
/// Returns an error if the buffer is too short to contain the information elements.
27+
pub fn new(data: T) -> Result<Self> {
28+
let ie = Self::new_unchecked(data);
29+
30+
if !ie.check_len() {
31+
return Err(Error);
32+
}
33+
34+
Ok(ie)
35+
}
36+
37+
/// Returns `false` if the buffer is too short to contain the information elements.
38+
fn check_len(&self) -> bool {
39+
let mut len = 0;
40+
41+
let mut iter = self.header_information_elements();
42+
while iter.next().is_some() {}
43+
len += iter.offset();
44+
45+
if len > self.data.as_ref().len() {
46+
return false;
47+
}
48+
49+
let mut iter = self.payload_information_elements();
50+
while iter.next().is_some() {}
51+
len += iter.offset();
52+
53+
self.data.as_ref().len() >= len
2254
}
2355

56+
/// Create a new [`InformationElements`] reader from a given buffer without length checking.
2457
pub fn new_unchecked(data: T) -> Self {
2558
Self { data }
2659
}

0 commit comments

Comments
 (0)