From d3fb6b707163105a8613fba365b06b2b84b95c39 Mon Sep 17 00:00:00 2001 From: Sudheer Obbu Date: Tue, 28 Apr 2026 20:01:44 -0400 Subject: [PATCH 1/2] fix(agent): prevent shift-left overflow in EpcNetIpKey::clone_by_masklen Fixes #8700 When a CIDR with prefix length 0 (e.g. ::/0) is registered and an IPv6 lookup is performed, clone_by_masklen(0, false) computes: u128::MAX << IPV6_BITS.saturating_sub(0) = u128::MAX << 128 Shifting a u128 by its own bit-width (128) is undefined behaviour and panics in Rust debug builds with: panicked at src/policy/labeler.rs:72:27: attempt to shift left with overflow The fix replaces the bare shift with checked_shl, which returns None when the shift amount equals or exceeds the bit-width, and falls back to 0. A mask of 0 is semantically correct for a /0 prefix (no network bits to preserve). Before: self.ip & (u128::MAX << max_prefix.saturating_sub(masklen)) After: self.ip & u128::MAX .checked_shl(max_prefix.saturating_sub(masklen) as u32) .unwrap_or(0) A regression test test_ipv6_zero_masklen_no_panic is added to cover the ::/0 case end-to-end through the labeler API. Signed-off-by: Sudheer Obbu --- agent/src/policy/labeler.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/agent/src/policy/labeler.rs b/agent/src/policy/labeler.rs index 62484e75bb5..c19435bf7eb 100644 --- a/agent/src/policy/labeler.rs +++ b/agent/src/policy/labeler.rs @@ -70,7 +70,7 @@ impl EpcNetIpKey { fn clone_by_masklen(&self, masklen: usize, is_ipv4: bool) -> Self { let max_prefix = if is_ipv4 { IPV4_BITS } else { IPV6_BITS }; Self { - ip: self.ip & (u128::MAX << max_prefix.saturating_sub(masklen)), + ip: self.ip & u128::MAX.checked_shl(max_prefix.saturating_sub(masklen) as u32).unwrap_or(0), epc_id: self.epc_id, masklen: masklen as u8, } @@ -1368,4 +1368,37 @@ mod tests { assert_eq!(endpoints.src_info.l2_epc_id, 10); assert_eq!(endpoints.src_info.l3_epc_id, 10); } + /// Regression test for https://github.com/deepflowio/deepflow/issues/8700 + /// + /// When a CIDR with masklen=0 (i.e. ::/0) is registered and an IPv6 lookup + /// is performed, `clone_by_masklen(0, false)` used to compute + /// `u128::MAX << 128` which panics in debug builds (and is UB in release). + /// The fix uses `checked_shl` so the shift is saturated to 0 instead. + #[test] + fn test_ipv6_zero_masklen_no_panic() { + use std::str::FromStr; + use ipnet::IpNet; + use crate::common::policy::{Cidr, CidrType}; + + let mut labeler: Labeler = Default::default(); + + // Register a catch-all IPv6 CIDR (::/0) — masklen 0 triggers the shift. + let cidr = Cidr { + ip: IpNet::from_str("::/0").unwrap(), + epc_id: 42, + cidr_type: CidrType::Lan, + ..Default::default() + }; + labeler.update_cidr_table(&vec![Arc::new(cidr)], false, &mut false); + + // A lookup for any IPv6 address must not panic. + let mut endpoint: EndpointInfo = Default::default(); + labeler.set_epc_by_cidr( + "2001:db8::1".parse().unwrap(), + 42, + &mut endpoint, + ); + assert_eq!(endpoint.l3_epc_id, 42); + } + } From 4e1088b6600b2de11f4c73c94cd4af80ebebc42b Mon Sep 17 00:00:00 2001 From: Sudheer Obbu Date: Tue, 12 May 2026 20:50:04 -0400 Subject: [PATCH 2/2] fix(agent): apply rustfmt formatting to labeler.rs - Break long checked_shl chain onto multiple lines per rustfmt style - Sort use imports alphabetically in test_ipv6_zero_masklen_no_panic - Collapse short set_epc_by_cidr call onto one line Signed-off-by: Sudheer Obbu --- agent/src/policy/labeler.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/agent/src/policy/labeler.rs b/agent/src/policy/labeler.rs index c19435bf7eb..35218d4faf5 100644 --- a/agent/src/policy/labeler.rs +++ b/agent/src/policy/labeler.rs @@ -70,7 +70,10 @@ impl EpcNetIpKey { fn clone_by_masklen(&self, masklen: usize, is_ipv4: bool) -> Self { let max_prefix = if is_ipv4 { IPV4_BITS } else { IPV6_BITS }; Self { - ip: self.ip & u128::MAX.checked_shl(max_prefix.saturating_sub(masklen) as u32).unwrap_or(0), + ip: self.ip + & u128::MAX + .checked_shl(max_prefix.saturating_sub(masklen) as u32) + .unwrap_or(0), epc_id: self.epc_id, masklen: masklen as u8, } @@ -1376,9 +1379,9 @@ mod tests { /// The fix uses `checked_shl` so the shift is saturated to 0 instead. #[test] fn test_ipv6_zero_masklen_no_panic() { - use std::str::FromStr; - use ipnet::IpNet; use crate::common::policy::{Cidr, CidrType}; + use ipnet::IpNet; + use std::str::FromStr; let mut labeler: Labeler = Default::default(); @@ -1393,11 +1396,7 @@ mod tests { // A lookup for any IPv6 address must not panic. let mut endpoint: EndpointInfo = Default::default(); - labeler.set_epc_by_cidr( - "2001:db8::1".parse().unwrap(), - 42, - &mut endpoint, - ); + labeler.set_epc_by_cidr("2001:db8::1".parse().unwrap(), 42, &mut endpoint); assert_eq!(endpoint.l3_epc_id, 42); }