From d017ed9f202e82118e11b6c738cbf9b3e9df1772 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Fri, 9 Oct 2026 23:12:09 -0700 Subject: [PATCH] fix(bsd): size In6Ifreq to the real struct in6_ifreq and read ifru_flags6 SIOCGIFAFLAG_IN6 encodes sizeof(struct in6_ifreq) = 0x120 bytes, but the local struct was 48 bytes, so the kernel's copyout wrote past it, and the flags were read from an offset the kernel never writes. Use the real union layout, with a compile-time size assert. Assisted-by: Claude:claude-sonnet-5-5 --- src/os/bsd/ipv6_addr_flags.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/os/bsd/ipv6_addr_flags.rs b/src/os/bsd/ipv6_addr_flags.rs index a2e6852..b9562b5 100644 --- a/src/os/bsd/ipv6_addr_flags.rs +++ b/src/os/bsd/ipv6_addr_flags.rs @@ -10,11 +10,24 @@ const IN6_IFF_DEPRECATED: u32 = 0x10; const IN6_IFF_TEMPORARY: u32 = 0x80; // `libc` does not expose `in6_ifreq` on FreeBSD/OpenBSD/NetBSD. +// +// Mirrors `struct in6_ifreq` from . `SIOCGIFAFLAG_IN6` encodes +// `sizeof(struct in6_ifreq)` (0x120 = 288 bytes on LP64), and the kernel copies that many +// bytes in and out, so the union must be padded to the size of its largest member +// (`icmp6_ifstat`, 34 x u64 = 272 bytes). The flags are returned in `ifru_flags6`, which +// overlaps `ifru_addr` at the start of the union. #[repr(C)] struct In6Ifreq { ifr_name: [u8; libc::IFNAMSIZ], - ifr_addr: libc::sockaddr_in6, - ifr_flags: libc::c_int, + ifr_ifru: In6IfreqUnion, +} + +#[repr(C)] +union In6IfreqUnion { + ifru_addr: libc::sockaddr_in6, + ifru_flags6: libc::c_int, + // 8-byte aligned, like the u64 counters in `in6_ifstat` / `icmp6_ifstat`. + _size: [u64; 34], } pub(crate) fn get_ipv6_addr_flags(ifname: &str, addr: &Ipv6Addr) -> Ipv6AddrFlags { @@ -25,6 +38,7 @@ pub(crate) fn get_ipv6_addr_flags(ifname: &str, addr: &Ipv6Addr) -> Ipv6AddrFlag } let mut req: In6Ifreq = std::mem::zeroed(); + const _: () = assert!(std::mem::size_of::() == 0x120); let name_bytes = ifname.as_bytes(); let copy_len = name_bytes.len().min(libc::IFNAMSIZ - 1); @@ -34,9 +48,9 @@ pub(crate) fn get_ipv6_addr_flags(ifname: &str, addr: &Ipv6Addr) -> Ipv6AddrFlag copy_len, ); - req.ifr_addr.sin6_family = libc::AF_INET6 as libc::sa_family_t; - req.ifr_addr.sin6_len = std::mem::size_of::() as u8; - req.ifr_addr.sin6_addr.s6_addr = addr.octets(); + req.ifr_ifru.ifru_addr.sin6_family = libc::AF_INET6 as libc::sa_family_t; + req.ifr_ifru.ifru_addr.sin6_len = std::mem::size_of::() as u8; + req.ifr_ifru.ifru_addr.sin6_addr.s6_addr = addr.octets(); let ret = libc::ioctl(fd, SIOCGIFAFLAG_IN6, &mut req); libc::close(fd); @@ -45,7 +59,7 @@ pub(crate) fn get_ipv6_addr_flags(ifname: &str, addr: &Ipv6Addr) -> Ipv6AddrFlag return Ipv6AddrFlags::default(); } - let raw = req.ifr_flags as u32; + let raw = req.ifr_ifru.ifru_flags6 as u32; Ipv6AddrFlags { deprecated: raw & IN6_IFF_DEPRECATED != 0,