summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernard Pidoux <bernard.f6bvp@gmail.com>2026-05-31 15:41:45 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-06-27 11:07:40 +0100
commit69969796115a9d03941c05aa100fac899e28f13d (patch)
treeb4715bd2ea441952df1f0a38f6b0d454f3170952
parent4507ce5fec5edd8d2c2d8106cc8e6be0f167793e (diff)
downloadlinux-69969796115a9d03941c05aa100fac899e28f13d.tar.gz
linux-69969796115a9d03941c05aa100fac899e28f13d.zip
rose: don't free fd-owned sockets when reaping in the heartbeat
commit 56576518920edd7b6c3479477d8d490fe2ebdaaa upstream. The heartbeat reaps orphaned ROSE sockets after their bound device goes down. A socket still attached to a struct socket (sk->sk_socket != NULL -- e.g. an incoming connection an fpad client has accepted and kept open) is owned by that userspace fd: rose_release() frees it on close(). Freeing it from the heartbeat left the fd dangling, so the eventual close() touched freed memory -- slab-use-after-free in rose_release(). Reap only sockets with sk->sk_socket == NULL (unaccepted incoming connections and post-close orphans). For an fd-owned socket whose device went down, disconnect it and fall through to the switch so close() does the teardown. Also release the neighbour reference held by orphaned incoming sockets before tearing them down. Signed-off-by: Bernard Pidoux <bernard.f6bvp@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--net/rose/rose_timer.c57
1 files changed, 56 insertions, 1 deletions
diff --git a/net/rose/rose_timer.c b/net/rose/rose_timer.c
index 76204b8250d0..d967cbc1db3d 100644
--- a/net/rose/rose_timer.c
+++ b/net/rose/rose_timer.c
@@ -126,13 +126,68 @@ static void rose_heartbeat_expiry(struct timer_list *t)
sk_reset_timer(sk, &sk->sk_timer, jiffies + HZ/20);
goto out;
}
+
+ /* The bound device went down while we still hold a reference on it.
+ * This catches the narrow race where rose_loopback_timer() created a
+ * socket in the window after rose_kill_by_device()'s NETDEV_DOWN sweep
+ * but before rose_insert_socket() -- leaving a STATE_3 socket that no
+ * other branch reaps. A down device means the link is dead, so tear
+ * the socket down regardless of state. rose_destroy_socket() releases
+ * the held netdev reference (rose->device still set).
+ */
+ if (rose->device && !netif_running(rose->device)) {
+ if (rose->neighbour) {
+ rose_neigh_put(rose->neighbour);
+ rose->neighbour = NULL;
+ }
+ rose_disconnect(sk, ENETDOWN, -1, -1);
+
+ /* Only reap the socket if userspace no longer holds it. A socket
+ * still attached to a struct socket (sk->sk_socket != NULL -- e.g.
+ * a connection an fpad client has accepted and kept open) is owned
+ * by that fd: rose_release() will destroy it on close(). Dropping
+ * the last reference here leaves the open fd dangling, so the
+ * eventual close() touches freed memory -> slab-use-after-free in
+ * rose_release(). Unaccepted incoming sockets and post-close
+ * orphans have sk->sk_socket == NULL and stay safe to reap here.
+ */
+ if (!sk->sk_socket) {
+ sock_set_flag(sk, SOCK_DESTROY);
+ bh_unlock_sock(sk);
+ rose_destroy_socket(sk);
+ sock_put(sk);
+ return;
+ }
+
+ /* Owned by userspace: the link is down and the socket is now
+ * disconnected (rose_disconnect() moved it to STATE_0). Fall
+ * through to the switch, which re-arms the heartbeat; the close()
+ * will tear the socket down. */
+ }
+
switch (rose->state) {
case ROSE_STATE_0:
/* Destroy any orphaned STATE_0 socket: either explicitly
* flagged SOCK_DESTROY, or SOCK_DEAD (covers both unaccepted
* incoming connections and listening sockets whose link died).
*/
- if (sock_flag(sk, SOCK_DESTROY) || sock_flag(sk, SOCK_DEAD)) {
+ if ((sock_flag(sk, SOCK_DESTROY) || sock_flag(sk, SOCK_DEAD)) &&
+ !sk->sk_socket) {
+ /* Reap only orphaned sockets (sk->sk_socket == NULL). A
+ * socket still owned by a userspace fd reaches here via the
+ * STATE_2 device-gone branch, which sets SOCK_DESTROY without
+ * knowing about the fd; freeing it would race rose_release()
+ * at close() -> use-after-free. Leave it for close().
+ *
+ * Orphaned incoming sockets (rose_rx_call_request) hold a
+ * neighbour reference; release it before teardown, as the
+ * STATE_2 and device-down branches do. rose_destroy_socket()
+ * does not drop it.
+ */
+ if (rose->neighbour) {
+ rose_neigh_put(rose->neighbour);
+ rose->neighbour = NULL;
+ }
bh_unlock_sock(sk);
rose_destroy_socket(sk);
sock_put(sk);