* [PATCH net v4 0/2] net/rds: scope-aware sendmsg connection cache
@ 2026-10-08 3:14 Allison Henderson
2026-10-08 3:14 ` [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one Allison Henderson
2026-10-08 3:14 ` [PATCH net v4 2/2] net/rds: include the scope id in the sendmsg connection cache check Allison Henderson
0 siblings, 2 replies; 8+ messages in thread
From: Allison Henderson @ 2026-10-08 3:14 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender
Hi all,
This is v4 of the sendmsg connection-cache scope fix (v1 at [1], v2 at
[2], v3 at [3]).
Patch 1 gives the connected peer's link-local scope its own field.
rds_connect() used to store it in rs_bound_scope_id, the scope of
the socket's own bound address, which a later bind() to a
non-link-local address resets to 0 and which a later connect()
pollutes for a globally bound socket. A send without a destination,
getpeername() and recvmsg() now take the connected peer's scope from
that field.
Patch 2 compares c_dev_if with the request's scope in the cache-hit
test, so the cache uses the same key as rds_conn_lookup().
Both were found by inspection while reworking this cache for the
connection-lifetime series on net-next; neither has been observed in
the field. The misdelivery in patch 2 needs a socket that sends to
the same link-local address through two interfaces, and the stall in
patch 1 a connect()/bind() order that loses the peer's scope.
Changes since v3 [3]:
- Patch 1: a destination-less send falls back to the bound scope for a
non-link-local peer, so send() and sendto() to the connected peer
compute the same scope (review of v3: a socket bound link-local and
connected to a global peer stalled otherwise); getpeername() and
recvmsg() report the connected peer's scope instead of the bound
scope (review of v3).
- Commit messages say how the issues were found.
Changes since v2 [2]:
- The v2 "scope 0 matches anything" wildcard is gone; the
destination-less send carries the connected peer's scope instead
(new patch 1), and the comparison is exact.
[1] https://lore.kernel.org/netdev/20260921215046.174745-1-achender@kernel.org/
[2] https://lore.kernel.org/netdev/20260927062312.169097-1-achender@kernel.org/
[3] https://lore.kernel.org/netdev/20261003163526.250805-1-achender@kernel.org/
Thank you,
Allison
Allison Henderson (2):
net/rds: keep the connected peer's scope id apart from the bound one
net/rds: include the scope id in the sendmsg connection cache check
net/rds/af_rds.c | 15 +++++++++------
net/rds/bind.c | 4 ++--
net/rds/rds.h | 2 ++
net/rds/recv.c | 3 ++-
net/rds/send.c | 9 +++++++--
5 files changed, 22 insertions(+), 11 deletions(-)
--
2.25.1
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one
2026-10-08 3:14 [PATCH net v4 0/2] net/rds: scope-aware sendmsg connection cache Allison Henderson
@ 2026-10-08 3:14 ` Allison Henderson
2026-10-08 3:23 ` sashiko-bot
` (2 more replies)
2026-10-08 3:14 ` [PATCH net v4 2/2] net/rds: include the scope id in the sendmsg connection cache check Allison Henderson
1 sibling, 3 replies; 8+ messages in thread
From: Allison Henderson @ 2026-10-08 3:14 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender
rds_connect() has nowhere to keep the scope of a link-local peer, so
it stores it in rs_bound_scope_id, the scope of the socket's own
bound address, for rds_bind() to check a later link-local bind
against. That field is then also what a send without a destination
uses as the request's scope, what getpeername() and recvmsg() report
as the peer's scope, and what a later bind() overwrites:
rds_add_bound() stores the bound address's scope unconditionally,
which for a non-link-local address is 0.
So after connect(fe80::x%ifA) followed by bind(global), a send() with
no destination asks for fe80::x with scope 0. rds_conn_lookup() keys
on the interface, so that finds or creates a connection with c_dev_if
0, which the TCP transport then tries to connect through
sin6_scope_id 0 and tcp_v6_connect() rejects for a link-local peer:
the connected socket's data is queued on a connection that can never
come up. In the other order, bind(global) then connect(fe80::x%ifB),
the connect leaves a global-bound socket with rs_bound_scope_id ifB,
so sends to other link-local peers are refused as off-link and sends
to global peers inherit a meaningless interface.
Give the connected peer its own rs_conn_scope_id. rds_connect()
records it there and leaves the bound scope alone, rds_bind()'s
connected-socket check compares against it, and the destination-less
send takes its scope from it - falling back to the bound scope for a
non-link-local peer, so that send() and sendto() to the connected
peer compute the same scope, and so the same connection.
getpeername() reports the connected peer with its own scope, and
recvmsg() reports a sender with the connected peer's scope when there
is one and the bound scope otherwise, as before.
Found by inspection while reworking the sendmsg connection cache; not
observed in the field.
Fixes: 1e2b44e78eea ("rds: Enable RDS IPv6 support")
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/af_rds.c | 15 +++++++++------
net/rds/bind.c | 4 ++--
net/rds/rds.h | 2 ++
net/rds/recv.c | 3 ++-
net/rds/send.c | 6 +++++-
5 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/net/rds/af_rds.c b/net/rds/af_rds.c
index d5defe9172e3..e84d00bbe9a1 100644
--- a/net/rds/af_rds.c
+++ b/net/rds/af_rds.c
@@ -136,8 +136,7 @@ static int rds_getname(struct socket *sock, struct sockaddr *uaddr,
sin6->sin6_port = rs->rs_conn_port;
sin6->sin6_addr = rs->rs_conn_addr;
sin6->sin6_flowinfo = 0;
- /* scope_id is the same as in the bound address. */
- sin6->sin6_scope_id = rs->rs_bound_scope_id;
+ sin6->sin6_scope_id = rs->rs_conn_scope_id;
uaddr_len = sizeof(*sin6);
}
} else {
@@ -572,6 +571,7 @@ static int rds_connect(struct socket *sock, struct sockaddr_unsized *uaddr,
}
ipv6_addr_set_v4mapped(sin->sin_addr.s_addr, &rs->rs_conn_addr);
rs->rs_conn_port = sin->sin_port;
+ rs->rs_conn_scope_id = 0;
break;
#if IS_ENABLED(CONFIG_IPV6)
@@ -616,11 +616,14 @@ static int rds_connect(struct socket *sock, struct sockaddr_unsized *uaddr,
ret = -EINVAL;
break;
}
- /* Remember the connected address scope ID. It will
- * be checked against the binding local address when
- * the socket is bound.
+ /* Remember the connected address scope ID. It is
+ * checked against the binding local address when
+ * the socket is bound, and gives a send without a
+ * destination its scope.
*/
- rs->rs_bound_scope_id = sin6->sin6_scope_id;
+ rs->rs_conn_scope_id = sin6->sin6_scope_id;
+ } else {
+ rs->rs_conn_scope_id = 0;
}
rs->rs_conn_addr = sin6->sin6_addr;
rs->rs_conn_port = sin6->sin6_port;
diff --git a/net/rds/bind.c b/net/rds/bind.c
index f800d920d969..3ac59cd512a2 100644
--- a/net/rds/bind.c
+++ b/net/rds/bind.c
@@ -233,8 +233,8 @@ int rds_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int addr_len)
* non-link local address (scope_id is 0).
*/
if (!ipv6_addr_any(&rs->rs_conn_addr) && scope_id &&
- rs->rs_bound_scope_id &&
- scope_id != rs->rs_bound_scope_id) {
+ rs->rs_conn_scope_id &&
+ scope_id != rs->rs_conn_scope_id) {
ret = -EINVAL;
goto out;
}
diff --git a/net/rds/rds.h b/net/rds/rds.h
index 2db49573dacd..9b1ffc49c0a6 100644
--- a/net/rds/rds.h
+++ b/net/rds/rds.h
@@ -646,6 +646,8 @@ struct rds_sock {
struct in6_addr rs_conn_addr;
#define rs_conn_addr_v4 rs_conn_addr.s6_addr32[3]
__be16 rs_conn_port;
+ /* scope of rs_conn_addr when it is link-local, 0 otherwise */
+ __u32 rs_conn_scope_id;
struct rds_transport *rs_transport;
/*
diff --git a/net/rds/recv.c b/net/rds/recv.c
index 6204e577a90a..9d81c76320ea 100644
--- a/net/rds/recv.c
+++ b/net/rds/recv.c
@@ -789,7 +789,8 @@ int rds_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
sin6->sin6_port = inc->i_hdr.h_sport;
sin6->sin6_addr = inc->i_saddr;
sin6->sin6_flowinfo = 0;
- sin6->sin6_scope_id = rs->rs_bound_scope_id;
+ sin6->sin6_scope_id = rs->rs_conn_scope_id ?:
+ rs->rs_bound_scope_id;
msg->msg_namelen = sizeof(*sin6);
}
}
diff --git a/net/rds/send.c b/net/rds/send.c
index 1afa981e5c06..7b525a6f7eac 100644
--- a/net/rds/send.c
+++ b/net/rds/send.c
@@ -1256,7 +1256,11 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
lock_sock(sk);
daddr = rs->rs_conn_addr;
dport = rs->rs_conn_port;
- scope_id = rs->rs_bound_scope_id;
+ /* The connected peer's scope for a link-local peer, else
+ * the bound scope, as an explicit send to that peer would
+ * compute below.
+ */
+ scope_id = rs->rs_conn_scope_id ?: rs->rs_bound_scope_id;
release_sock(sk);
}
--
2.25.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net v4 2/2] net/rds: include the scope id in the sendmsg connection cache check
2026-10-08 3:14 [PATCH net v4 0/2] net/rds: scope-aware sendmsg connection cache Allison Henderson
2026-10-08 3:14 ` [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one Allison Henderson
@ 2026-10-08 3:14 ` Allison Henderson
2026-10-08 3:23 ` sashiko-bot
2026-10-09 11:57 ` Simon Horman
1 sibling, 2 replies; 8+ messages in thread
From: Allison Henderson @ 2026-10-08 3:14 UTC (permalink / raw)
To: netdev, linux-rdma, pabeni, edumazet, kuba, horms; +Cc: achender
rds_sendmsg() reuses the connection cached in rs->rs_conn when its
peer address and ToS match the request. The interface index is part
of a connection's identity as well: rds_conn_create_outgoing() passes
the request's scope_id down as dev_if, and rds_conn_lookup() compares
c_dev_if, so sends to the same link-local address through two
interfaces are two different connections. The cache-hit test never
looked at it.
A socket bound to a non-link-local address has rs_bound_scope_id 0,
and the scope check at the top of rds_sendmsg() accepts any non-zero
destination scope for such a socket. So after a send to fe80::x%ifA,
a send to fe80::x%ifB hits the cached ifA connection and the datagram
leaves through ifA, to whichever peer answers to that address there.
Compare c_dev_if with the request's scope_id in the cache test, so
that such a send takes the lookup path and finds, or creates, the ifB
connection - the same key the lookup uses. With the previous patch a
send without a destination carries the connected peer's scope, or the
bound scope for a non-link-local peer, which is what an explicit send
to that peer computes, so the comparison is exact for it too.
Found by inspection while reworking this cache for the connection
lifetime series on net-next; not observed in the field.
Fixes: 1e2b44e78eea ("rds: Enable RDS IPv6 support")
Assisted-by: Claude-Code:claude-fable-5
Signed-off-by: Allison Henderson <achender@kernel.org>
---
net/rds/send.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/rds/send.c b/net/rds/send.c
index 7b525a6f7eac..f38967f65af1 100644
--- a/net/rds/send.c
+++ b/net/rds/send.c
@@ -1346,7 +1346,8 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
/* rds_conn_create has a spinlock that runs with IRQ off.
* Caching the conn in the socket helps a lot. */
if (rs->rs_conn && ipv6_addr_equal(&rs->rs_conn->c_faddr, &daddr) &&
- rs->rs_tos == rs->rs_conn->c_tos) {
+ rs->rs_tos == rs->rs_conn->c_tos &&
+ rs->rs_conn->c_dev_if == scope_id) {
conn = rs->rs_conn;
} else {
conn = rds_conn_create_outgoing(sock_net(sock->sk),
--
2.25.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH net v4 2/2] net/rds: include the scope id in the sendmsg connection cache check
2026-10-08 3:14 ` [PATCH net v4 2/2] net/rds: include the scope id in the sendmsg connection cache check Allison Henderson
@ 2026-10-08 3:23 ` sashiko-bot
2026-10-09 11:57 ` Simon Horman
1 sibling, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-10-08 3:23 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> rds_sendmsg() reuses the connection cached in rs->rs_conn when its
> peer address and ToS match the request. The interface index is part
> of a connection's identity as well: rds_conn_create_outgoing() passes
> the request's scope_id down as dev_if, and rds_conn_lookup() compares
> c_dev_if, so sends to the same link-local address through two
> interfaces are two different connections. The cache-hit test never
> looked at it.
>
> A socket bound to a non-link-local address has rs_bound_scope_id 0,
> and the scope check at the top of rds_sendmsg() accepts any non-zero
> destination scope for such a socket. So after a send to fe80::x%ifA,
> a send to fe80::x%ifB hits the cached ifA connection and the datagram
> leaves through ifA, to whichever peer answers to that address there.
>
> Compare c_dev_if with the request's scope_id in the cache test, so
> that such a send takes the lookup path and finds, or creates, the ifB
> connection - the same key the lookup uses. With the previous patch a
> send without a destination carries the connected peer's scope, or the
> bound scope for a non-link-local peer, which is what an explicit send
> to that peer computes, so the comparison is exact for it too.
>
> Found by inspection while reworking this cache for the connection
> lifetime series on net-next; not observed in the field.
>
> Fixes: 1e2b44e78eea ("rds: Enable RDS IPv6 support")
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008031426.1142344-1-achender@kernel.org?part=2
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one
2026-10-08 3:14 ` [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one Allison Henderson
@ 2026-10-08 3:23 ` sashiko-bot
2026-10-09 11:57 ` Simon Horman
2026-10-12 3:36 ` netdev-bot+sashiko
2 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-10-08 3:23 UTC (permalink / raw)
To: Allison Henderson; +Cc: linux-rdma
> rds_connect() has nowhere to keep the scope of a link-local peer, so
> it stores it in rs_bound_scope_id, the scope of the socket's own
> bound address, for rds_bind() to check a later link-local bind
> against. That field is then also what a send without a destination
> uses as the request's scope, what getpeername() and recvmsg() report
> as the peer's scope, and what a later bind() overwrites:
> rds_add_bound() stores the bound address's scope unconditionally,
> which for a non-link-local address is 0.
>
> So after connect(fe80::x%ifA) followed by bind(global), a send() with
> no destination asks for fe80::x with scope 0. rds_conn_lookup() keys
> on the interface, so that finds or creates a connection with c_dev_if
> 0, which the TCP transport then tries to connect through
> sin6_scope_id 0 and tcp_v6_connect() rejects for a link-local peer:
> the connected socket's data is queued on a connection that can never
> [ ... ]
> observed in the field.
>
> Fixes: 1e2b44e78eea ("rds: Enable RDS IPv6 support")
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008031426.1142344-1-achender@kernel.org?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one
2026-10-08 3:14 ` [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one Allison Henderson
2026-10-08 3:23 ` sashiko-bot
@ 2026-10-09 11:57 ` Simon Horman
2026-10-12 3:36 ` netdev-bot+sashiko
2 siblings, 0 replies; 8+ messages in thread
From: Simon Horman @ 2026-10-09 11:57 UTC (permalink / raw)
To: Allison Henderson; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba
On Wed, Oct 07, 2026 at 08:14:25PM -0700, Allison Henderson wrote:
> rds_connect() has nowhere to keep the scope of a link-local peer, so
> it stores it in rs_bound_scope_id, the scope of the socket's own
> bound address, for rds_bind() to check a later link-local bind
> against. That field is then also what a send without a destination
> uses as the request's scope, what getpeername() and recvmsg() report
> as the peer's scope, and what a later bind() overwrites:
> rds_add_bound() stores the bound address's scope unconditionally,
> which for a non-link-local address is 0.
>
> So after connect(fe80::x%ifA) followed by bind(global), a send() with
> no destination asks for fe80::x with scope 0. rds_conn_lookup() keys
> on the interface, so that finds or creates a connection with c_dev_if
> 0, which the TCP transport then tries to connect through
> sin6_scope_id 0 and tcp_v6_connect() rejects for a link-local peer:
> the connected socket's data is queued on a connection that can never
> come up. In the other order, bind(global) then connect(fe80::x%ifB),
> the connect leaves a global-bound socket with rs_bound_scope_id ifB,
> so sends to other link-local peers are refused as off-link and sends
> to global peers inherit a meaningless interface.
>
> Give the connected peer its own rs_conn_scope_id. rds_connect()
> records it there and leaves the bound scope alone, rds_bind()'s
> connected-socket check compares against it, and the destination-less
> send takes its scope from it - falling back to the bound scope for a
> non-link-local peer, so that send() and sendto() to the connected
> peer compute the same scope, and so the same connection.
> getpeername() reports the connected peer with its own scope, and
> recvmsg() reports a sender with the connected peer's scope when there
> is one and the bound scope otherwise, as before.
>
> Found by inspection while reworking the sendmsg connection cache; not
> observed in the field.
>
> Fixes: 1e2b44e78eea ("rds: Enable RDS IPv6 support")
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Reviewed-by: Simon Horman <horms@kernel.org>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v4 2/2] net/rds: include the scope id in the sendmsg connection cache check
2026-10-08 3:14 ` [PATCH net v4 2/2] net/rds: include the scope id in the sendmsg connection cache check Allison Henderson
2026-10-08 3:23 ` sashiko-bot
@ 2026-10-09 11:57 ` Simon Horman
1 sibling, 0 replies; 8+ messages in thread
From: Simon Horman @ 2026-10-09 11:57 UTC (permalink / raw)
To: Allison Henderson; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba
On Wed, Oct 07, 2026 at 08:14:26PM -0700, Allison Henderson wrote:
> rds_sendmsg() reuses the connection cached in rs->rs_conn when its
> peer address and ToS match the request. The interface index is part
> of a connection's identity as well: rds_conn_create_outgoing() passes
> the request's scope_id down as dev_if, and rds_conn_lookup() compares
> c_dev_if, so sends to the same link-local address through two
> interfaces are two different connections. The cache-hit test never
> looked at it.
>
> A socket bound to a non-link-local address has rs_bound_scope_id 0,
> and the scope check at the top of rds_sendmsg() accepts any non-zero
> destination scope for such a socket. So after a send to fe80::x%ifA,
> a send to fe80::x%ifB hits the cached ifA connection and the datagram
> leaves through ifA, to whichever peer answers to that address there.
>
> Compare c_dev_if with the request's scope_id in the cache test, so
> that such a send takes the lookup path and finds, or creates, the ifB
> connection - the same key the lookup uses. With the previous patch a
> send without a destination carries the connected peer's scope, or the
> bound scope for a non-link-local peer, which is what an explicit send
> to that peer computes, so the comparison is exact for it too.
>
> Found by inspection while reworking this cache for the connection
> lifetime series on net-next; not observed in the field.
>
> Fixes: 1e2b44e78eea ("rds: Enable RDS IPv6 support")
> Assisted-by: Claude-Code:claude-fable-5
> Signed-off-by: Allison Henderson <achender@kernel.org>
Reviewed-by: Simon Horman <horms@kernel.org>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one
2026-10-08 3:14 ` [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one Allison Henderson
2026-10-08 3:23 ` sashiko-bot
2026-10-09 11:57 ` Simon Horman
@ 2026-10-12 3:36 ` netdev-bot+sashiko
2 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 3:36 UTC (permalink / raw)
To: achender; +Cc: netdev, linux-rdma, pabeni, edumazet, kuba, horms
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1 (2)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] rds_recvmsg() (net/rds/recv.c:792-793) now sets the IPv6
source sockaddr's sin6_scope_id from socket state:…
- [Low] After this patch, getpeername() and recvmsg() can report
different sin6_scope_id values for the same connected peer.
Pre-existing issues:
- [Low] Baseline decision: discarded as a pre-existing Low issue.
- [Low] Baseline decision: discarded as a pre-existing, explicitly
accepted race.
--
net/rds: keep the connected peer's scope id apart from the bound one
This patch adds rs_conn_scope_id to struct rds_sock. rds_connect() now
stores the link-local peer's scope there instead of overloading
rs_bound_scope_id. The rds_bind() check, the destination-less send,
getpeername() and recvmsg() are updated to use the new field.
> diff --git a/net/rds/af_rds.c b/net/rds/af_rds.c
> index d5defe9172e36..e84d00bbe9a15 100644
> --- a/net/rds/af_rds.c
> +++ b/net/rds/af_rds.c
> @@ -136,8 +136,7 @@ static int rds_getname(struct socket *sock, struct sockaddr *uaddr,
> sin6->sin6_port = rs->rs_conn_port;
> sin6->sin6_addr = rs->rs_conn_addr;
> sin6->sin6_flowinfo = 0;
> - /* scope_id is the same as in the bound address. */
> - sin6->sin6_scope_id = rs->rs_bound_scope_id;
> + sin6->sin6_scope_id = rs->rs_conn_scope_id;
[Severity: Low]
This isn't a bug introduced by this patch, but rds_connect() writes
rs_conn_scope_id under lock_sock(). This line and rds_recvmsg() read it
without the lock. Could a concurrent connect() hand back an old
address with a new scope?
The same race was already there with rs_bound_scope_id, and
rds_getname() already starts with "racey, don't care". The field is an
aligned u32, so reads can't tear. Adding READ_ONCE()/WRITE_ONCE()
would only silence KCSAN, if that is wanted.
> uaddr_len = sizeof(*sin6);
> }
> } else {
[ ... ]
> diff --git a/net/rds/recv.c b/net/rds/recv.c
> index 6204e577a90ae..9d81c76320ea7 100644
> --- a/net/rds/recv.c
> +++ b/net/rds/recv.c
> @@ -789,7 +789,8 @@ int rds_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
> sin6->sin6_port = inc->i_hdr.h_sport;
> sin6->sin6_addr = inc->i_saddr;
> sin6->sin6_flowinfo = 0;
> - sin6->sin6_scope_id = rs->rs_bound_scope_id;
> + sin6->sin6_scope_id = rs->rs_conn_scope_id ?:
> + rs->rs_bound_scope_id;
[Severity: Medium]
Can this report the wrong scope for senders other than the connected
peer?
connect() doesn't filter incoming traffic. rds_recv_incoming() picks
the socket by local address, port and c_bound_if only:
net/rds/recv.c:rds_recv_incoming() {
...
rs = rds_find_bound(daddr, inc->i_hdr.h_dport, conn->c_bound_if);
...
}
__rds_conn_create() sets c_bound_if to 0 when the local address is not
link-local:
if (ipv6_addr_type(laddr) & IPV6_ADDR_LINKLOCAL)
conn->c_bound_if = dev_if;
else
conn->c_bound_if = 0;
So a connected socket bound to a global address receives from every
peer on every interface.
Take connect(fe80::x%ifA) followed by bind(2001:db8::a). Before this
patch, bind() overwrote rs_bound_scope_id with 0, so recvmsg reported
scope 0. Now rs_conn_scope_id stays at ifA, and every IPv6 sender is
reported with scope ifA.
That includes global senders. It also includes a link-local sender
fe80::z whose connection arrived on ifB. rds_tcp_accept_one() takes
c_dev_if from sk_bound_dev_if, which is the incoming interface for a
link-local peer.
Suppose the application replies with sendto(fe80::z%ifA).
rds_sendmsg() takes scope_id = ifA. This check passes because
rs_bound_scope_id is 0:
if (scope_id != rs->rs_bound_scope_id) {
if (!scope_id) {
scope_id = rs->rs_bound_scope_id;
} else if (rs->rs_bound_scope_id) {
...
ret = -EINVAL;
rds_conn_create_outgoing(..., ifA) then sends the reply out the wrong
link. It either sits on a connection that never comes up, or reaches
whatever host owns fe80::z on ifA. Before the patch, the equivalent
sendto(fe80::z%0) failed with -EINVAL.
The commit message says:
recvmsg() reports a sender with the connected peer's scope when there
is one and the bound scope otherwise, as before.
Does that hold for the connect-then-bind order?
Could the scope come from the connection the message arrived on
instead? That would be inc->i_conn->c_dev_if for a link-local sender
and 0 otherwise. inc->i_conn is already dereferenced a few lines
earlier for inc_copy_to_user().
[Severity: Low]
Can getpeername() and recvmsg() now report different scopes for the
same connected peer?
rds_getname() with peer set reports only rs_conn_scope_id. This line
falls back to rs_bound_scope_id. Take bind(fe80::l%ifC) followed by
connect(2001::p). rs_conn_scope_id is 0 and rs_bound_scope_id is ifC:
getpeername() -> 2001::p, scope 0
recvfrom() -> 2001::p, scope ifC
Before the patch both read rs_bound_scope_id and agreed (ifC).
Userspace that compares the full sockaddr_in6 from getpeername() and
recvfrom() to spot datagrams from its connected peer will now see a
mismatch.
The getpeername() value follows the inet6_getname() convention of 0
for a global address. The bound-scope fallback in recvmsg is the odd
one out. Taking the scope from the sender, as suggested above, would
fix this too.
> msg->msg_namelen = sizeof(*sin6);
> }
> }
> diff --git a/net/rds/send.c b/net/rds/send.c
> index 1afa981e5c06d..7b525a6f7eac2 100644
> --- a/net/rds/send.c
> +++ b/net/rds/send.c
> @@ -1256,7 +1256,11 @@ int rds_sendmsg(struct socket *sock, struct msghdr *msg, size_t payload_len)
> lock_sock(sk);
> daddr = rs->rs_conn_addr;
> dport = rs->rs_conn_port;
> - scope_id = rs->rs_bound_scope_id;
> + /* The connected peer's scope for a link-local peer, else
> + * the bound scope, as an explicit send to that peer would
> + * compute below.
> + */
> + scope_id = rs->rs_conn_scope_id ?: rs->rs_bound_scope_id;
> + release_sock(sk);
[Severity: Low]
This is a pre-existing issue, but does the new comment still hold if
bind() runs between the two lock_sock() sections?
scope_id is sampled here. The lock is then dropped and taken again:
lock_sock(sk);
if (ipv6_addr_any(&rs->rs_bound_addr) || ipv6_addr_any(&daddr)) {
...
} else if (namelen != 0) {
...
if (scope_id != rs->rs_bound_scope_id) {
The bound-scope fixup only runs when namelen != 0. Take a socket that
is connected to a global peer but not yet bound. It calls send() with
no destination while another thread runs bind(fe80::a%ifA) in that
window.
The send then sees a bound socket but keeps scope_id = 0. It calls
rds_conn_create_outgoing() with laddr fe80::a and dev_if 0, and
rds_tcp_conn_path_connect() can't bind that address with scope 0.
The code before this patch read rs_bound_scope_id in the same first
section, so the race isn't new. Merging the two critical sections
would close it.
> }
>
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008031426.1142344-1-achender%40kernel.org
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-12 3:36 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-08 3:14 [PATCH net v4 0/2] net/rds: scope-aware sendmsg connection cache Allison Henderson
2026-10-08 3:14 ` [PATCH net v4 1/2] net/rds: keep the connected peer's scope id apart from the bound one Allison Henderson
2026-10-08 3:23 ` sashiko-bot
2026-10-09 11:57 ` Simon Horman
2026-10-12 3:36 ` netdev-bot+sashiko
2026-10-08 3:14 ` [PATCH net v4 2/2] net/rds: include the scope id in the sendmsg connection cache check Allison Henderson
2026-10-08 3:23 ` sashiko-bot
2026-10-09 11:57 ` Simon Horman
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.