fix(server): exclude IPv6 flow information from endpoint identity
Session identity is the address family, the IP address, the UDP source port and — for IPv6 — the scope identifier. `SocketAddrV6` equality additionally covers the flow label, so comparing endpoints with `==` admitted a field that identifies no endpoint: a client whose flow label changed mid-session could be refused its own close and strand the session, which nothing reclaims while expiry is unimplemented. It bought nothing in exchange, since the token, address, port and scope are what an off-path attacker would have to guess. Compare the specified components instead. Scope still counts, and the address is still stored verbatim, so a later reply goes back to exactly the endpoint the request came from. Not reachable through a default socket today: a receiver reports the flow label only when asked to, and there is no runtime slice yet. It is reachable through the public core, which takes caller-supplied addresses.
This commit is contained in:
2 files changed
+56
-1
No files matched your search
@@ -213,7 +213,7 @@ impl ServerCore {
|
||||
fn handle_close(&mut self, peer: SocketAddr, token: u64) {
|
||||
let owns_session = self
|
||||
.session(token)
|
||||
.is_some_and(|session| session.peer() == peer);
|
||||
.is_some_and(|session| same_endpoint(session.peer(), peer));
|
||||
if owns_session {
|
||||
self.sessions.remove(&token);
|
||||
}
|
||||
@@ -243,6 +243,29 @@ impl ServerCore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether two source endpoints identify the same session endpoint.
|
||||
///
|
||||
/// Identity is the address family, the IP address, the UDP source port and —
|
||||
/// for IPv6 — the scope identifier. That is the whole list, so this is not
|
||||
/// simply `==`: [`SocketAddrV6`] equality also covers the flow label, which
|
||||
/// identifies no endpoint. It is routing metadata that a sender or a forwarding
|
||||
/// path may legitimately vary within one flow, and a receiver reports it only
|
||||
/// when asked to, so admitting it into identity could strand a session that its
|
||||
/// own client can then never close. It grants nothing in exchange: the token,
|
||||
/// address, port and scope are what an off-path attacker would have to guess.
|
||||
///
|
||||
/// [`SocketAddrV6`]: std::net::SocketAddrV6
|
||||
fn same_endpoint(session: SocketAddr, peer: SocketAddr) -> bool {
|
||||
match (session, peer) {
|
||||
(SocketAddr::V6(session), SocketAddr::V6(peer)) => {
|
||||
session.ip() == peer.ip()
|
||||
&& session.port() == peer.port()
|
||||
&& session.scope_id() == peer.scope_id()
|
||||
}
|
||||
(session, peer) => session == peer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the open parameter rules the decoder cannot express.
|
||||
///
|
||||
/// Duration and Interval are accepted when absent, taking the wire default
|
||||
|
||||
Reference in new issue
Block a user