From 864019d163ef44be65b5c6f8c21c7fb98bcfb220 Mon Sep 17 00:00:00 2001 From: F6BVP Date: Sat, 16 May 2026 12:09:15 +0200 Subject: [PATCH 1/6] rose: build with hamradio_compat.h on pre-7.0 kernels af_rose.c uses struct sockaddr_unsized in its .bind and .connect proto_ops callbacks. That type was introduced in Linux 7.0; on older kernels it does not exist. Add a compat shim to hamradio_compat.h that maps sockaddr_unsized to the traditional struct sockaddr when LINUX_VERSION_CODE < 7.0, and force-include the header for all rose sources via Kbuild so no per-file change is needed. --- include/hamradio_compat.h | 9 +++++++++ net/rose/Kbuild | 2 ++ 2 files changed, 11 insertions(+) diff --git a/include/hamradio_compat.h b/include/hamradio_compat.h index 111fabe..66c98ec 100644 --- a/include/hamradio_compat.h +++ b/include/hamradio_compat.h @@ -2,6 +2,7 @@ #ifndef _MOD_ORPHAN_COMPAT_H #define _MOD_ORPHAN_COMPAT_H +#include #include #include @@ -35,4 +36,12 @@ #endif /* __alloc_objs */ +/* + * struct sockaddr_unsized was introduced in Linux 7.0 to replace struct sockaddr + * in proto_ops .bind/.connect callbacks. Map it to struct sockaddr on older kernels. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(7, 0, 0) +#define sockaddr_unsized sockaddr +#endif + #endif /* _MOD_ORPHAN_COMPAT_H */ diff --git a/net/rose/Kbuild b/net/rose/Kbuild index 38c05de..8d17dcb 100644 --- a/net/rose/Kbuild +++ b/net/rose/Kbuild @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 +ccflags-y += -include $(src)/../../include/hamradio_compat.h + obj-m += rose.o rose-y := af_rose.o rose_dev.o rose_in.o rose_link.o rose_loopback.o \ From 5166d7456b9cb1783d159f270544646f9d36e352 Mon Sep 17 00:00:00 2001 From: F6BVP Date: Sat, 16 May 2026 12:09:33 +0200 Subject: [PATCH 2/6] rose: fix dev_put() leak in rose_loopback_timer() rose_rx_call_request() always consumes or returns the skb but never releases the device reference obtained from rose_dev_get(). When rose_rx_call_request() succeeds (returns non-zero) dev_put() was never called, leaking one reference per loopback CALL_REQUEST. Move dev_put() outside the conditional so it is called unconditionally after rose_rx_call_request() in all cases. Also remove the dead check (!rose_loopback_neigh->dev && !rose_loopback_neigh->loopback) that immediately precedes it: the loopback neighbour always has loopback=1 so this condition can never be true. Fixes: 0453c6824595 ("net/rose: fix unbound loop in rose_loopback_timer()") Signed-off-by: Bernard Pidoux --- net/rose/rose_loopback.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/net/rose/rose_loopback.c b/net/rose/rose_loopback.c index b538e39..914c8f4 100644 --- a/net/rose/rose_loopback.c +++ b/net/rose/rose_loopback.c @@ -96,22 +96,15 @@ static void rose_loopback_timer(struct timer_list *unused) } if (frametype == ROSE_CALL_REQUEST) { - if (!rose_loopback_neigh->dev && - !rose_loopback_neigh->loopback) { - kfree_skb(skb); - continue; - } - dev = rose_dev_get(dest); if (!dev) { kfree_skb(skb); continue; } - if (rose_rx_call_request(skb, dev, rose_loopback_neigh, lci_o) == 0) { - dev_put(dev); + if (rose_rx_call_request(skb, dev, rose_loopback_neigh, lci_o) == 0) kfree_skb(skb); - } + dev_put(dev); } else { kfree_skb(skb); } From 66e57f6c0ae28583f698c6c94e04e7ef7334d5be Mon Sep 17 00:00:00 2001 From: F6BVP Date: Sat, 16 May 2026 12:10:03 +0200 Subject: [PATCH 3/6] rose: hold loopback neighbour reference across timer callback rose_loopback_timer() dereferences rose_loopback_neigh throughout its body but holds no reference on it. A concurrent rose_loopback_clear() followed by rose_add_loopback_neigh() could free and reallocate the neighbour while the timer body is running, causing a use-after-free. Take a reference with rose_neigh_hold() at the start of the callback (bailing out if the pointer is already NULL) and release it with rose_neigh_put() at the single exit point. The neigh cannot be freed while the callback holds a reference. Fixes: d860d1faa6b2 ("net: rose: convert 'use' field to refcount_t") Signed-off-by: Bernard Pidoux --- net/rose/rose_loopback.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/net/rose/rose_loopback.c b/net/rose/rose_loopback.c index 914c8f4..d66913d 100644 --- a/net/rose/rose_loopback.c +++ b/net/rose/rose_loopback.c @@ -66,10 +66,15 @@ static void rose_loopback_timer(struct timer_list *unused) unsigned int lci_i, lci_o; int count; + if (rose_loopback_neigh) + rose_neigh_hold(rose_loopback_neigh); + else + return; + for (count = 0; count < ROSE_LOOPBACK_LIMIT; count++) { skb = skb_dequeue(&loopback_queue); if (!skb) - return; + goto out; if (skb->len < ROSE_MIN_LEN) { kfree_skb(skb); continue; @@ -109,6 +114,10 @@ static void rose_loopback_timer(struct timer_list *unused) kfree_skb(skb); } } + +out: + rose_neigh_put(rose_loopback_neigh); + if (!skb_queue_empty(&loopback_queue)) mod_timer(&loopback_timer, jiffies + 1); } From cefc89e7aee736a42663a8f7cf523f36a8eab7a1 Mon Sep 17 00:00:00 2001 From: F6BVP Date: Sat, 16 May 2026 12:10:20 +0200 Subject: [PATCH 4/6] rose: fix race between loopback timer and module removal rose_loopback_clear() called timer_delete() which returns immediately without waiting for any running callback to complete. If the timer fired concurrently with module removal, rose_loopback_timer() could re-arm the timer after timer_delete() returned and then access rose_loopback_neigh after it was freed. Two complementary changes close the race: 1. Add a loopback_stopping atomic flag. rose_loopback_timer() checks it at entry (before acquiring a reference) and again inside the loop; when set it drains the queue and exits without re-arming the timer. 2. Switch rose_loopback_clear() to timer_delete_sync() so it blocks until any in-flight callback has returned before freeing resources. The smp_mb() between setting the flag and calling timer_delete_sync() ensures the flag is visible to any callback that is about to run. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Bernard Pidoux --- net/rose/rose_loopback.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/net/rose/rose_loopback.c b/net/rose/rose_loopback.c index d66913d..80d7879 100644 --- a/net/rose/rose_loopback.c +++ b/net/rose/rose_loopback.c @@ -12,13 +12,15 @@ #include #include -static struct sk_buff_head loopback_queue; #define ROSE_LOOPBACK_LIMIT 1000 -static struct timer_list loopback_timer; +static struct timer_list loopback_timer; +static struct sk_buff_head loopback_queue; static void rose_set_loopback_timer(void); static void rose_loopback_timer(struct timer_list *unused); +static atomic_t loopback_stopping = ATOMIC_INIT(0); + void rose_loopback_init(void) { skb_queue_head_init(&loopback_queue); @@ -66,6 +68,9 @@ static void rose_loopback_timer(struct timer_list *unused) unsigned int lci_i, lci_o; int count; + if (atomic_read(&loopback_stopping)) + return; + if (rose_loopback_neigh) rose_neigh_hold(rose_loopback_neigh); else @@ -75,6 +80,13 @@ static void rose_loopback_timer(struct timer_list *unused) skb = skb_dequeue(&loopback_queue); if (!skb) goto out; + + if (atomic_read(&loopback_stopping)) { + kfree_skb(skb); + skb_queue_purge(&loopback_queue); + goto out; + } + if (skb->len < ROSE_MIN_LEN) { kfree_skb(skb); continue; @@ -118,7 +130,7 @@ static void rose_loopback_timer(struct timer_list *unused) out: rose_neigh_put(rose_loopback_neigh); - if (!skb_queue_empty(&loopback_queue)) + if (!atomic_read(&loopback_stopping) && !skb_queue_empty(&loopback_queue)) mod_timer(&loopback_timer, jiffies + 1); } @@ -126,10 +138,15 @@ void __exit rose_loopback_clear(void) { struct sk_buff *skb; - timer_delete(&loopback_timer); + atomic_set(&loopback_stopping, 1); + /* Pairs with atomic_read() in rose_loopback_timer(): ensure the + * stopping flag is visible before we cancel, so a concurrent + * callback aborts its loop early rather than re-arming the timer. + */ + smp_mb(); + + timer_delete_sync(&loopback_timer); - while ((skb = skb_dequeue(&loopback_queue)) != NULL) { - skb->sk = NULL; + while ((skb = skb_dequeue(&loopback_queue)) != NULL) kfree_skb(skb); - } } From 4a3e7f0de40633a6df134dac9847f520a13d6370 Mon Sep 17 00:00:00 2001 From: F6BVP Date: Sat, 16 May 2026 12:10:38 +0200 Subject: [PATCH 5/6] rose: clear neighbour pointer after rose_neigh_put() in state machines After calling rose_neigh_put() in rose_state1_machine() through rose_state5_machine(), rose->neighbour was left pointing at the potentially freed neighbour structure. A subsequent timer expiry or concurrent teardown path could dereference the stale pointer, causing a use-after-free. Set rose->neighbour to NULL immediately after each rose_neigh_put() call in the state machine functions. Fixes: d860d1faa6b2 ("net: rose: convert 'use' field to refcount_t") Signed-off-by: Bernard Pidoux --- net/rose/rose_in.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/rose/rose_in.c b/net/rose/rose_in.c index 57814fa..12b40c8 100644 --- a/net/rose/rose_in.c +++ b/net/rose/rose_in.c @@ -57,6 +57,7 @@ static int rose_state1_machine(struct sock *sk, struct sk_buff *skb, int framety rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION); rose_disconnect(sk, ECONNREFUSED, skb->data[3], skb->data[4]); rose_neigh_put(rose->neighbour); + rose->neighbour = NULL; break; default: @@ -80,11 +81,13 @@ static int rose_state2_machine(struct sock *sk, struct sk_buff *skb, int framety rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION); rose_disconnect(sk, 0, skb->data[3], skb->data[4]); rose_neigh_put(rose->neighbour); + rose->neighbour = NULL; break; case ROSE_CLEAR_CONFIRMATION: rose_disconnect(sk, 0, -1, -1); rose_neigh_put(rose->neighbour); + rose->neighbour = NULL; break; default: @@ -121,6 +124,7 @@ static int rose_state3_machine(struct sock *sk, struct sk_buff *skb, int framety rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION); rose_disconnect(sk, 0, skb->data[3], skb->data[4]); rose_neigh_put(rose->neighbour); + rose->neighbour = NULL; break; case ROSE_RR: @@ -234,6 +238,7 @@ static int rose_state4_machine(struct sock *sk, struct sk_buff *skb, int framety rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION); rose_disconnect(sk, 0, skb->data[3], skb->data[4]); rose_neigh_put(rose->neighbour); + rose->neighbour = NULL; break; default: @@ -254,6 +259,7 @@ static int rose_state5_machine(struct sock *sk, struct sk_buff *skb, int framety rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION); rose_disconnect(sk, 0, skb->data[3], skb->data[4]); rose_neigh_put(rose_sk(sk)->neighbour); + rose_sk(sk)->neighbour = NULL; } return 0; From a4cec23810864ac359f1a9451be890d0cd20137a Mon Sep 17 00:00:00 2001 From: F6BVP Date: Sat, 16 May 2026 12:10:55 +0200 Subject: [PATCH 6/6] rose: guard rose_neigh_put() against NULL in timer expiry In rose_timer_expiry(), the ROSE_STATE_2 branch calls rose_neigh_put(rose->neighbour) without first checking whether the pointer is NULL. After commit 5de7665e0a07 ("net: rose: fix timer races against user threads") the timer is re-armed when the socket is owned by a user thread; between the re-arm and the next firing, a device-down event or concurrent teardown via rose_kill_by_device() can set rose->neighbour to NULL, leading to a NULL-pointer dereference inside rose_neigh_put(). Add a NULL check before the put and clear the pointer afterwards. Fixes: 5de7665e0a07 ("net: rose: fix timer races against user threads") Signed-off-by: Bernard Pidoux --- net/rose/rose_timer.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/rose/rose_timer.c b/net/rose/rose_timer.c index bb60a16..d997d24 100644 --- a/net/rose/rose_timer.c +++ b/net/rose/rose_timer.c @@ -180,7 +180,10 @@ static void rose_timer_expiry(struct timer_list *t) break; case ROSE_STATE_2: /* T3 */ - rose_neigh_put(rose->neighbour); + if (rose->neighbour) { + rose_neigh_put(rose->neighbour); + rose->neighbour = NULL; + } rose_disconnect(sk, ETIMEDOUT, -1, -1); break;