netstack: fix IPv6 firewall bypass - return final protocol + offset
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.
This commit is contained in:
+26
-17
@@ -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<usize> {
|
||||
) -> 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user