Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

RDS tcp loopback connection can hang

When TCP is used as transport and a program on the
system connects to RDS port 16385, connection is
accepted but denied per the rules of RDS. However,
RDS connections object is left in the list. Next
loopback connection will select that connection
object as it is at the head of list. The connection
attempt will hang as the connection object is set
to connect over TCP which is not allowed

The issue can be reproduced easily, use rds-ping
to ping a local IP address. After that use any
program like ncat to connect to the same IP
address and port 16385. This will hang so ctrl-c out.
Now try rds-ping, it will hang.

To fix the issue this patch adds checks to disallow
the connection object creation and destroys the
connection object.

Signed-off-by: Rao Shoaib <rao.shoaib@oracle.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Rao Shoaib and committed by
David S. Miller
aced3ce5 29bf1993

+27 -9
+17 -6
net/rds/connection.c
··· 240 240 if (loop_trans) { 241 241 rds_trans_put(loop_trans); 242 242 conn->c_loopback = 1; 243 - if (is_outgoing && trans->t_prefer_loopback) { 244 - /* "outgoing" connection - and the transport 245 - * says it wants the connection handled by the 246 - * loopback transport. This is what TCP does. 247 - */ 248 - trans = &rds_loop_transport; 243 + if (trans->t_prefer_loopback) { 244 + if (likely(is_outgoing)) { 245 + /* "outgoing" connection to local address. 246 + * Protocol says it wants the connection 247 + * handled by the loopback transport. 248 + * This is what TCP does. 249 + */ 250 + trans = &rds_loop_transport; 251 + } else { 252 + /* No transport currently in use 253 + * should end up here, but if it 254 + * does, reset/destroy the connection. 255 + */ 256 + kmem_cache_free(rds_conn_slab, conn); 257 + conn = ERR_PTR(-EOPNOTSUPP); 258 + goto out; 259 + } 249 260 } 250 261 } 251 262
+2 -2
net/rds/tcp.c
··· 313 313 } 314 314 #endif 315 315 316 - static int rds_tcp_laddr_check(struct net *net, const struct in6_addr *addr, 317 - __u32 scope_id) 316 + int rds_tcp_laddr_check(struct net *net, const struct in6_addr *addr, 317 + __u32 scope_id) 318 318 { 319 319 struct net_device *dev = NULL; 320 320 #if IS_ENABLED(CONFIG_IPV6)
+2 -1
net/rds/tcp.h
··· 59 59 u64 rds_tcp_map_seq(struct rds_tcp_connection *tc, u32 seq); 60 60 extern struct rds_transport rds_tcp_transport; 61 61 void rds_tcp_accept_work(struct sock *sk); 62 - 62 + int rds_tcp_laddr_check(struct net *net, const struct in6_addr *addr, 63 + __u32 scope_id); 63 64 /* tcp_connect.c */ 64 65 int rds_tcp_conn_path_connect(struct rds_conn_path *cp); 65 66 void rds_tcp_conn_path_shutdown(struct rds_conn_path *conn);
+6
net/rds/tcp_listen.c
··· 167 167 } 168 168 #endif 169 169 170 + if (!rds_tcp_laddr_check(sock_net(sock->sk), peer_addr, dev_if)) { 171 + /* local address connection is only allowed via loopback */ 172 + ret = -EOPNOTSUPP; 173 + goto out; 174 + } 175 + 170 176 conn = rds_conn_create(sock_net(sock->sk), 171 177 my_addr, peer_addr, 172 178 &rds_tcp_transport, 0, GFP_KERNEL, dev_if);