From 4e7a5bbd74ce661de933f3ed17b17f250ceda23e Mon Sep 17 00:00:00 2001 From: Marco Cadetg Date: Fri, 1 May 2026 18:31:35 +0200 Subject: [PATCH] fix(quic): bounds-check token_len in Initial packet parser A crafted Initial packet with an oversized variable-length token length pushed the parse offset past the end of the packet, panicking on the next slice. Use checked arithmetic and verify offset is in range before slicing. --- src/network/dpi/quic.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/network/dpi/quic.rs b/src/network/dpi/quic.rs index 6897983..cfdd858 100644 --- a/src/network/dpi/quic.rs +++ b/src/network/dpi/quic.rs @@ -674,7 +674,16 @@ fn try_decrypt_initial_with_secret(packet: &[u8], secret: &[u8], version: u32) - // Parse token length (for Initial packets) let (token_len, bytes_read) = parse_variable_length_int(&packet[offset..])?; - offset += bytes_read + token_len as usize; + // QUIC variable-length ints go up to 2^62 — guard against overflow on + // 32-bit targets and against crafted token lengths that exceed the packet. + let token_len_usize = usize::try_from(token_len).ok()?; + offset = offset + .checked_add(bytes_read)? + .checked_add(token_len_usize)?; + if offset > packet.len() { + debug!("QUIC: token_len pushed offset past end of packet"); + return None; + } // Parse packet length let (packet_payload_length, bytes_read) = parse_variable_length_int(&packet[offset..])?; @@ -2459,6 +2468,26 @@ mod tests { assert_eq!(result, Some("example.co[PARTIAL]".to_string())); } + #[test] + fn test_initial_packet_oversized_token_len_does_not_panic() { + // Crafted Initial packet whose declared token length pushes the parse + // offset past the end of the packet. Pre-fix this panicked when + // slicing &packet[offset..] in try_decrypt_initial_with_secret. + let mut packet = Vec::new(); + packet.push(0xC0); // long header, type=Initial + packet.extend_from_slice(&1u32.to_be_bytes()); // version 1 + packet.push(0); // DCID len = 0 + packet.push(0); // SCID len = 0 + // Token length: 2-byte QUIC varint encoding 1000 (top 2 bits = 01). + let token_varint: u16 = 1000 | 0x4000; + packet.extend_from_slice(&token_varint.to_be_bytes()); + // Intentionally no token bytes follow — declared length far exceeds packet. + + let secret = [0u8; 32]; + let result = try_decrypt_initial_with_secret(&packet, &secret, 1); + assert!(result.is_none()); + } + #[test] fn test_parse_alpn_extension() { // Build ALPN extension data (without the extension type/length header)