From 76e01f06ffd272c87ad042d9d6f8268fe8a054d6 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Mon, 27 Jul 2026 20:26:48 +0900 Subject: [PATCH] netstack: fix IPv6 firewall bypass - return final protocol + offset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the P001 review finding. The previous ipv6_transport_offset walker returned only the offset, but the caller passed the INITIAL next_header (which is usually a Hop-by-Hop / Routing / Destination / Fragment number, not a transport protocol) to parse_ports() and to PacketContext.protocol. Any IPv6 packet with extension headers silently failed firewall port rules. Two changes: 1. ipv6_transport_offset now returns (final_protocol, offset) tuple. Continues to return None for malformed/encrypted/too-deep chains so we never fall through to wrong-offset parse. 2. The IPv6 dispatch in infer_context() uses the returned final protocol: parse_ports() gets the right IpProtocol (TCP/UDP/ICMPv6) and PacketContext.protocol is set to the discovered final protocol. Also corrects the AH header length formula: RFC 8200 §4 says AH length is in 4-octet units over the 8-octet fixed part, so total = (len+2)*8, not the generic (len+1)*8 used for Hop-by-Hop / Routing / Destination. --- netstack/src/router/mod.rs | 43 +++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/netstack/src/router/mod.rs b/netstack/src/router/mod.rs index 269bbc249d..cf4decab4a 100644 --- a/netstack/src/router/mod.rs +++ b/netstack/src/router/mod.rs @@ -528,19 +528,18 @@ fn infer_context( 6 => { if let Ok(ipv6) = Ipv6Packet::new_checked(packet) { let nh = ipv6.next_header(); - // Walk the extension header chain to find the actual - // transport-layer offset. Previously this used a fixed - // 40-byte offset, so any IPv6 packet with extension - // headers (Hop-by-Hop, Routing, Fragment, Destination, - // ESP, AH) caused parse_ports() to read the wrong bytes - // and return None. A firewall rule with a port predicate - // would silently fail to match — a firewall bypass. - let transport_offset = ipv6_transport_offset(packet, &nh); - let (src_port, dst_port) = match transport_offset { - Some(off) if packet.len() >= off => { - parse_ports(&nh, &packet[off..]) - } - _ => (None, None), + let (transport_proto, transport_offset) = + match ipv6_transport_offset(packet, &nh) { + Some(v) => v, + None => (0u8, 0usize), + }; + let (src_port, dst_port) = if transport_offset > 0 + && packet.len() >= transport_offset + { + parse_ports(&smoltcp::wire::IpProtocol::from(transport_proto), + &packet[transport_offset..]) + } else { + (None, None) }; PacketContext { hook, @@ -548,7 +547,7 @@ fn infer_context( out_dev: None, src_addr: IpAddress::Ipv6(ipv6.src_addr()), dst_addr: IpAddress::Ipv6(ipv6.dst_addr()), - protocol: nh.into(), + protocol: smoltcp::wire::IpProtocol::from(transport_proto), src_port, dst_port, packet, @@ -595,7 +594,7 @@ fn infer_context( fn ipv6_transport_offset( packet: &[u8], initial_next_header: &smoltcp::wire::IpProtocol, -) -> Option { +) -> Option<(u8, usize)> { // Extension header type values per RFC 8200 / IANA. const IPV6_HOP_BY_HOP: u8 = 0; const IPV6_ROUTING: u8 = 43; @@ -620,7 +619,7 @@ fn ipv6_transport_offset( } // Known transport-layer protocols terminate the chain. if current == 6 || current == 17 || current == 58 || current == 132 { - return Some(offset); + return Some((current, offset)); } match current { IPV6_HOP_BY_HOP | IPV6_ROUTING | IPV6_DEST_OPTS | IPV6_AUTH => { @@ -628,7 +627,17 @@ fn ipv6_transport_offset( return None; } let len_units = packet[offset + 1] as usize; - let header_size = (len_units + 1) * 8; + // RFC 8200 §4: header length is in 8-octet units, NOT + // counting the first 8 octets. For Hop-by-Hop, Routing, + // and Destination Options that matches (len+1)*8. For + // AH the payload-length field is in 4-octet units over + // the 8-octet fixed part, so total = (len+2)*8. + let header_size = if current == IPV6_AUTH { + let len_units = (packet[offset + 1] as usize).saturating_add(2); + len_units * 4 + } else { + (len_units + 1) * 8 + }; if header_size < 8 || offset + header_size > packet.len() { return None; }