diff options
| author | Bernard Pidoux <bernard.f6bvp@gmail.com> | 2026-05-31 15:41:45 +0200 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2026-06-27 11:07:40 +0100 |
| commit | 69969796115a9d03941c05aa100fac899e28f13d (patch) | |
| tree | b4715bd2ea441952df1f0a38f6b0d454f3170952 | |
| parent | 4507ce5fec5edd8d2c2d8106cc8e6be0f167793e (diff) | |
| download | linux-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.c | 57 |
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); |
