From 382b18da9bb8869b813fefb549ab876f975f3ef7 Mon Sep 17 00:00:00 2001 From: Giuseppe Longo Date: Tue, 11 Aug 2026 09:47:58 +0200 Subject: [PATCH] dhcp: validate address time option lengths DHCP lease, renewal, and rebinding time options contain a four-byte value, but the parser previously ignored their declared length and always consumed four bytes. Reject these options unless their length is exactly four to prevent parser desynchronization and incorrect keyword matches. Ticket #8873 --- rust/src/dhcp/parser.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/rust/src/dhcp/parser.rs b/rust/src/dhcp/parser.rs index 0d0ee66375..ad42561625 100644 --- a/rust/src/dhcp/parser.rs +++ b/rust/src/dhcp/parser.rs @@ -81,6 +81,7 @@ pub struct DHCPOption { pub code: u8, pub data: Option>, pub option: DHCPOptionWrapper, + pub malformed: bool, } pub fn parse_header(i: &[u8]) -> IResult<&[u8], DHCPHeader> { @@ -135,13 +136,19 @@ pub fn parse_clientid_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { htype: 1, data: data.to_vec(), }), + malformed: false, }, )) } -pub fn parse_address_time_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { - let (i, code) = be_u8(i)?; - let (i, _len) = be_u8(i)?; +pub fn parse_address_time_option(i0: &[u8]) -> IResult<&[u8], DHCPOption> { + let (i, code) = be_u8(i0)?; + let (i, len) = be_u8(i)?; + if len != 4 { + let (i, mut opt) = parse_generic_option(i0)?; + opt.malformed = true; + return Ok((i, opt)); + } let (i, seconds) = be_u32(i)?; Ok(( i, @@ -149,6 +156,7 @@ pub fn parse_address_time_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { code, data: None, option: DHCPOptionWrapper::TimeValue(DHCPOptTimeValue { seconds }), + malformed: false, }, )) } @@ -165,6 +173,7 @@ pub fn parse_generic_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { option: DHCPOptionWrapper::Generic(DHCPOptGeneric { data: data.to_vec(), }), + malformed: false, }, )) } @@ -180,6 +189,7 @@ pub fn parse_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { code: opt, data: None, option: DHCPOptionWrapper::Pad, + malformed: false, }, )), DHCP_OPT_END => { @@ -193,6 +203,7 @@ pub fn parse_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { code, data: Some(data.to_vec()), option: DHCPOptionWrapper::End, + malformed: false, }, )) } @@ -217,11 +228,14 @@ fn find_overload_value(options: &[DHCPOption]) -> u8 { 0 } -fn parse_overloaded_field(field: &[u8], options: &mut Vec) { +fn parse_overloaded_field( + field: &[u8], options: &mut Vec, malformed_options: &mut bool, +) { let mut next = field; while !next.is_empty() { match parse_option(next) { Ok((rem, option)) => { + *malformed_options |= option.malformed; let done = option.code == DHCP_OPT_END; options.push(option); next = rem; @@ -239,11 +253,12 @@ pub fn parse_dhcp(input: &[u8]) -> IResult<&[u8], DHCPMessage> { Ok((rem, header)) => { let mut options = Vec::new(); let mut next = rem; - let malformed_options = false; + let mut malformed_options = false; let mut truncated_options = false; loop { match parse_option(next) { Ok((rem, option)) => { + malformed_options |= option.malformed; let done = option.code == DHCP_OPT_END; options.push(option); next = rem; @@ -259,10 +274,10 @@ pub fn parse_dhcp(input: &[u8]) -> IResult<&[u8], DHCPMessage> { } let overload = find_overload_value(&options); if overload & 0x01 != 0 { - parse_overloaded_field(&header.bootfilename, &mut options); + parse_overloaded_field(&header.bootfilename, &mut options, &mut malformed_options); } if overload & 0x02 != 0 { - parse_overloaded_field(&header.servername, &mut options); + parse_overloaded_field(&header.servername, &mut options, &mut malformed_options); } let message = DHCPMessage {