Description
This is most painful on Windows, where SOCKADDR_STORAGE is 132 bytes long.
Consider the following naive example code, where the SOCKADDR pointer here is a pointer to an ipv4/ipv6 address obtained via syscall:
unsafe fn sock_addr_to_ip(ptr: *const SOCKADDR, length: i32) -> Option<IpAddr> {
let sa = socket2::SockAddr::new(*(ptr as *const SOCKADDR_STORAGE), length);
sa.as_socket().map(|s| s.ip())
}
The issue is that SOCKADDR_STORAGE
is larger than SOCKADDR
, the pointer is only guaranteed to be valid through length
bytes, and so casting it to *const SOCKADDR_STORAGE
and dereferencing immediately introduces UB.
The correct code looks more like this:
unsafe fn sock_addr_to_ip(ptr: *const SOCKADDR, length: i32) -> Option<IpAddr> {
assert!(length as usize <= std::mem::size_of::<SOCKADDR_STORAGE>());
let mut storage = std::mem::MaybeUninit::<SOCKADDR_STORAGE>::uninit();
std::ptr::copy_nonoverlapping(ptr, &mut storage as *mut _ as *mut _, length as _);
let sa = socket2::SockAddr::new(storage.assume_init(), length);
sa.as_socket().map(|s| s.ip())
}
Since the backing storage for SockAddr is owning, we have to make a copy. It's incredibly awkward and a bit slower. And that's just so we can safely invoke the nice parsing logic in as_socket()
.
To fix this we'd probably have to add a SockAddrRef<'a> type that doesn't own its backing storage. If that's not immediately feasible, providing a constructor that performs this unsafe copy inside socket2 would also solve the immediate issue.