From: Greg Kroah-Hartman Date: Mon, 20 Apr 2026 15:11:40 +0000 (+0200) Subject: 7.0-stable patches X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;h=c3c3b10d7bc4ea06bc5bb1db821903f672622971;p=thirdparty%2Fkernel%2Fstable-queue.git 7.0-stable patches added patches: media-as102-fix-to-not-free-memory-after-the-device-is-registered-in-as102_usb_probe.patch media-hackrf-fix-to-not-free-memory-after-the-device-is-registered-in-hackrf_probe.patch media-vidtv-fix-pass-by-value-structs-causing-msan-warnings.patch mm-userfaultfd-fix-hugetlb-fault-mutex-hash-calculation.patch nilfs2-fix-null-i_assoc_inode-dereference-in-nilfs_mdt_save_to_shadow_map.patch wireguard-device-use-exit_rtnl-callback-instead-of-manual-rtnl_lock-in-pre_exit.patch --- diff --git a/queue-7.0/media-as102-fix-to-not-free-memory-after-the-device-is-registered-in-as102_usb_probe.patch b/queue-7.0/media-as102-fix-to-not-free-memory-after-the-device-is-registered-in-as102_usb_probe.patch new file mode 100644 index 0000000000..b389afd767 --- /dev/null +++ b/queue-7.0/media-as102-fix-to-not-free-memory-after-the-device-is-registered-in-as102_usb_probe.patch @@ -0,0 +1,70 @@ +From 8bd29dbe03fc5b0f039ab2395ff37b64236d2f0c Mon Sep 17 00:00:00 2001 +From: Jeongjun Park +Date: Sun, 11 Jan 2026 00:17:53 +0900 +Subject: media: as102: fix to not free memory after the device is registered in as102_usb_probe() + +From: Jeongjun Park + +commit 8bd29dbe03fc5b0f039ab2395ff37b64236d2f0c upstream. + +In as102_usb driver, the following race condition occurs: +``` + CPU0 CPU1 +as102_usb_probe() + kzalloc(); // alloc as102_dev_t + .... + usb_register_dev(); + fd = sys_open("/path/to/dev"); // open as102 fd + .... + usb_deregister_dev(); + .... + kfree(); // free as102_dev_t + .... + sys_close(fd); + as102_release() // UAF!! + as102_usb_release() + kfree(); // DFB!! +``` + +When a USB character device registered with usb_register_dev() is later +unregistered (via usb_deregister_dev() or disconnect), the device node is +removed so new open() calls fail. However, file descriptors that are +already open do not go away immediately: they remain valid until the last +reference is dropped and the driver's .release() is invoked. + +In as102, as102_usb_probe() calls usb_register_dev() and then, on an +error path, does usb_deregister_dev() and frees as102_dev_t right away. +If userspace raced a successful open() before the deregistration, that +open FD will later hit as102_release() --> as102_usb_release() and access +or free as102_dev_t again, occur a race to use-after-free and +double-free vuln. + +The fix is to never kfree(as102_dev_t) directly once usb_register_dev() +has succeeded. After deregistration, defer freeing memory to .release(). + +In other words, let release() perform the last kfree when the final open +FD is closed. + +Cc: +Reported-by: syzbot+47321e8fd5a4c84088db@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=47321e8fd5a4c84088db +Fixes: cd19f7d3e39b ("[media] as102: fix leaks at failure paths in as102_usb_probe()") +Signed-off-by: Jeongjun Park +Signed-off-by: Hans Verkuil +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/usb/as102/as102_usb_drv.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/drivers/media/usb/as102/as102_usb_drv.c ++++ b/drivers/media/usb/as102/as102_usb_drv.c +@@ -403,7 +403,9 @@ static int as102_usb_probe(struct usb_in + failed_dvb: + as102_free_usb_stream_buffer(as102_dev); + failed_stream: ++ usb_set_intfdata(intf, NULL); + usb_deregister_dev(intf, &as102_usb_class_driver); ++ return ret; + failed: + usb_put_dev(as102_dev->bus_adap.usb_dev); + usb_set_intfdata(intf, NULL); diff --git a/queue-7.0/media-hackrf-fix-to-not-free-memory-after-the-device-is-registered-in-hackrf_probe.patch b/queue-7.0/media-hackrf-fix-to-not-free-memory-after-the-device-is-registered-in-hackrf_probe.patch new file mode 100644 index 0000000000..07e50af291 --- /dev/null +++ b/queue-7.0/media-hackrf-fix-to-not-free-memory-after-the-device-is-registered-in-hackrf_probe.patch @@ -0,0 +1,87 @@ +From 3b7da2b4d0fe014eff181ed37e3bf832eb8ed258 Mon Sep 17 00:00:00 2001 +From: Jeongjun Park +Date: Sat, 10 Jan 2026 23:58:29 +0900 +Subject: media: hackrf: fix to not free memory after the device is registered in hackrf_probe() + +From: Jeongjun Park + +commit 3b7da2b4d0fe014eff181ed37e3bf832eb8ed258 upstream. + +In hackrf driver, the following race condition occurs: +``` + CPU0 CPU1 +hackrf_probe() + kzalloc(); // alloc hackrf_dev + .... + v4l2_device_register(); + .... + fd = sys_open("/path/to/dev"); // open hackrf fd + .... + v4l2_device_unregister(); + .... + kfree(); // free hackrf_dev + .... + sys_ioctl(fd, ...); + v4l2_ioctl(); + video_is_registered() // UAF!! + .... + sys_close(fd); + v4l2_release() // UAF!! + hackrf_video_release() + kfree(); // DFB!! +``` + +When a V4L2 or video device is unregistered, the device node is removed so +new open() calls are blocked. + +However, file descriptors that are already open-and any in-flight I/O-do +not terminate immediately; they remain valid until the last reference is +dropped and the driver's release() is invoked. + +Therefore, freeing device memory on the error path after hackrf_probe() +has registered dev it will lead to a race to use-after-free vuln, since +those already-open handles haven't been released yet. + +And since release() free memory too, race to use-after-free and +double-free vuln occur. + +To prevent this, if device is registered from probe(), it should be +modified to free memory only through release() rather than calling +kfree() directly. + +Cc: +Reported-by: syzbot+6ffd76b5405c006a46b7@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=6ffd76b5405c006a46b7 +Reported-by: syzbot+f1b20958f93d2d250727@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=f1b20958f93d2d250727 +Fixes: 8bc4a9ed8504 ("[media] hackrf: add support for transmitter") +Signed-off-by: Jeongjun Park +Signed-off-by: Hans Verkuil +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/usb/hackrf/hackrf.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +--- a/drivers/media/usb/hackrf/hackrf.c ++++ b/drivers/media/usb/hackrf/hackrf.c +@@ -1485,7 +1485,7 @@ static int hackrf_probe(struct usb_inter + if (ret) { + dev_err(dev->dev, + "Failed to register as video device (%d)\n", ret); +- goto err_v4l2_device_unregister; ++ goto err_v4l2_device_put; + } + dev_info(dev->dev, "Registered as %s\n", + video_device_node_name(&dev->rx_vdev)); +@@ -1513,8 +1513,9 @@ static int hackrf_probe(struct usb_inter + return 0; + err_video_unregister_device_rx: + video_unregister_device(&dev->rx_vdev); +-err_v4l2_device_unregister: +- v4l2_device_unregister(&dev->v4l2_dev); ++err_v4l2_device_put: ++ v4l2_device_put(&dev->v4l2_dev); ++ return ret; + err_v4l2_ctrl_handler_free_tx: + v4l2_ctrl_handler_free(&dev->tx_ctrl_handler); + err_v4l2_ctrl_handler_free_rx: diff --git a/queue-7.0/media-vidtv-fix-pass-by-value-structs-causing-msan-warnings.patch b/queue-7.0/media-vidtv-fix-pass-by-value-structs-causing-msan-warnings.patch new file mode 100644 index 0000000000..33abe98def --- /dev/null +++ b/queue-7.0/media-vidtv-fix-pass-by-value-structs-causing-msan-warnings.patch @@ -0,0 +1,176 @@ +From 5f8e73bde67e931468bc2a1860d78d72f0c6ba41 Mon Sep 17 00:00:00 2001 +From: Abd-Alrhman Masalkhi +Date: Sat, 21 Feb 2026 13:56:18 +0100 +Subject: media: vidtv: fix pass-by-value structs causing MSAN warnings + +From: Abd-Alrhman Masalkhi + +commit 5f8e73bde67e931468bc2a1860d78d72f0c6ba41 upstream. + +vidtv_ts_null_write_into() and vidtv_ts_pcr_write_into() take their +argument structs by value, causing MSAN to report uninit-value warnings. +While only vidtv_ts_null_write_into() has triggered a report so far, +both functions share the same issue. + +Fix by passing both structs by const pointer instead, avoiding the +stack copy of the struct along with its MSAN shadow and origin metadata. +The functions do not modify the structs, which is enforced by the const +qualifier. + +Fixes: f90cf6079bf67 ("media: vidtv: add a bridge driver") +Cc: stable@vger.kernel.org +Reported-by: syzbot+96f901260a0b2d29cd1a@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=96f901260a0b2d29cd1a +Tested-by: syzbot+96f901260a0b2d29cd1a@syzkaller.appspotmail.com +Suggested-by: Yihan Ding +Signed-off-by: Abd-Alrhman Masalkhi +Signed-off-by: Hans Verkuil +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/test-drivers/vidtv/vidtv_mux.c | 4 +- + drivers/media/test-drivers/vidtv/vidtv_ts.c | 50 +++++++++++++-------------- + drivers/media/test-drivers/vidtv/vidtv_ts.h | 4 +- + 3 files changed, 29 insertions(+), 29 deletions(-) + +--- a/drivers/media/test-drivers/vidtv/vidtv_mux.c ++++ b/drivers/media/test-drivers/vidtv/vidtv_mux.c +@@ -233,7 +233,7 @@ static u32 vidtv_mux_push_pcr(struct vid + /* the 27Mhz clock will feed both parts of the PCR bitfield */ + args.pcr = m->timing.clk; + +- nbytes += vidtv_ts_pcr_write_into(args); ++ nbytes += vidtv_ts_pcr_write_into(&args); + m->mux_buf_offset += nbytes; + + m->num_streamed_pcr++; +@@ -363,7 +363,7 @@ static u32 vidtv_mux_pad_with_nulls(stru + args.continuity_counter = &ctx->cc; + + for (i = 0; i < npkts; ++i) { +- m->mux_buf_offset += vidtv_ts_null_write_into(args); ++ m->mux_buf_offset += vidtv_ts_null_write_into(&args); + args.dest_offset = m->mux_buf_offset; + } + +--- a/drivers/media/test-drivers/vidtv/vidtv_ts.c ++++ b/drivers/media/test-drivers/vidtv/vidtv_ts.c +@@ -48,7 +48,7 @@ void vidtv_ts_inc_cc(u8 *continuity_coun + *continuity_counter = 0; + } + +-u32 vidtv_ts_null_write_into(struct null_packet_write_args args) ++u32 vidtv_ts_null_write_into(const struct null_packet_write_args *args) + { + u32 nbytes = 0; + struct vidtv_mpeg_ts ts_header = {}; +@@ -56,21 +56,21 @@ u32 vidtv_ts_null_write_into(struct null + ts_header.sync_byte = TS_SYNC_BYTE; + ts_header.bitfield = cpu_to_be16(TS_NULL_PACKET_PID); + ts_header.payload = 1; +- ts_header.continuity_counter = *args.continuity_counter; ++ ts_header.continuity_counter = *args->continuity_counter; + + /* copy TS header */ +- nbytes += vidtv_memcpy(args.dest_buf, +- args.dest_offset + nbytes, +- args.buf_sz, ++ nbytes += vidtv_memcpy(args->dest_buf, ++ args->dest_offset + nbytes, ++ args->buf_sz, + &ts_header, + sizeof(ts_header)); + +- vidtv_ts_inc_cc(args.continuity_counter); ++ vidtv_ts_inc_cc(args->continuity_counter); + + /* fill the rest with empty data */ +- nbytes += vidtv_memset(args.dest_buf, +- args.dest_offset + nbytes, +- args.buf_sz, ++ nbytes += vidtv_memset(args->dest_buf, ++ args->dest_offset + nbytes, ++ args->buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - nbytes); + +@@ -83,17 +83,17 @@ u32 vidtv_ts_null_write_into(struct null + return nbytes; + } + +-u32 vidtv_ts_pcr_write_into(struct pcr_write_args args) ++u32 vidtv_ts_pcr_write_into(const struct pcr_write_args *args) + { + u32 nbytes = 0; + struct vidtv_mpeg_ts ts_header = {}; + struct vidtv_mpeg_ts_adaption ts_adap = {}; + + ts_header.sync_byte = TS_SYNC_BYTE; +- ts_header.bitfield = cpu_to_be16(args.pid); ++ ts_header.bitfield = cpu_to_be16(args->pid); + ts_header.scrambling = 0; + /* cc is not incremented, but it is needed. see 13818-1 clause 2.4.3.3 */ +- ts_header.continuity_counter = *args.continuity_counter; ++ ts_header.continuity_counter = *args->continuity_counter; + ts_header.payload = 0; + ts_header.adaptation_field = 1; + +@@ -102,27 +102,27 @@ u32 vidtv_ts_pcr_write_into(struct pcr_w + ts_adap.PCR = 1; + + /* copy TS header */ +- nbytes += vidtv_memcpy(args.dest_buf, +- args.dest_offset + nbytes, +- args.buf_sz, ++ nbytes += vidtv_memcpy(args->dest_buf, ++ args->dest_offset + nbytes, ++ args->buf_sz, + &ts_header, + sizeof(ts_header)); + + /* write the adap after the TS header */ +- nbytes += vidtv_memcpy(args.dest_buf, +- args.dest_offset + nbytes, +- args.buf_sz, ++ nbytes += vidtv_memcpy(args->dest_buf, ++ args->dest_offset + nbytes, ++ args->buf_sz, + &ts_adap, + sizeof(ts_adap)); + + /* write the PCR optional */ +- nbytes += vidtv_ts_write_pcr_bits(args.dest_buf, +- args.dest_offset + nbytes, +- args.pcr); +- +- nbytes += vidtv_memset(args.dest_buf, +- args.dest_offset + nbytes, +- args.buf_sz, ++ nbytes += vidtv_ts_write_pcr_bits(args->dest_buf, ++ args->dest_offset + nbytes, ++ args->pcr); ++ ++ nbytes += vidtv_memset(args->dest_buf, ++ args->dest_offset + nbytes, ++ args->buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - nbytes); + +--- a/drivers/media/test-drivers/vidtv/vidtv_ts.h ++++ b/drivers/media/test-drivers/vidtv/vidtv_ts.h +@@ -90,7 +90,7 @@ void vidtv_ts_inc_cc(u8 *continuity_coun + * + * Return: The number of bytes written into the buffer. + */ +-u32 vidtv_ts_null_write_into(struct null_packet_write_args args); ++u32 vidtv_ts_null_write_into(const struct null_packet_write_args *args); + + /** + * vidtv_ts_pcr_write_into - Write a PCR packet into a buffer. +@@ -101,6 +101,6 @@ u32 vidtv_ts_null_write_into(struct null + * + * Return: The number of bytes written into the buffer. + */ +-u32 vidtv_ts_pcr_write_into(struct pcr_write_args args); ++u32 vidtv_ts_pcr_write_into(const struct pcr_write_args *args); + + #endif //VIDTV_TS_H diff --git a/queue-7.0/mm-userfaultfd-fix-hugetlb-fault-mutex-hash-calculation.patch b/queue-7.0/mm-userfaultfd-fix-hugetlb-fault-mutex-hash-calculation.patch new file mode 100644 index 0000000000..d34b512c06 --- /dev/null +++ b/queue-7.0/mm-userfaultfd-fix-hugetlb-fault-mutex-hash-calculation.patch @@ -0,0 +1,84 @@ +From 0217c7fb4de4a40cee667eb21901f3204effe5ac Mon Sep 17 00:00:00 2001 +From: Jianhui Zhou +Date: Tue, 10 Mar 2026 19:05:26 +0800 +Subject: mm/userfaultfd: fix hugetlb fault mutex hash calculation + +From: Jianhui Zhou + +commit 0217c7fb4de4a40cee667eb21901f3204effe5ac upstream. + +In mfill_atomic_hugetlb(), linear_page_index() is used to calculate the +page index for hugetlb_fault_mutex_hash(). However, linear_page_index() +returns the index in PAGE_SIZE units, while hugetlb_fault_mutex_hash() +expects the index in huge page units. This mismatch means that different +addresses within the same huge page can produce different hash values, +leading to the use of different mutexes for the same huge page. This can +cause races between faulting threads, which can corrupt the reservation +map and trigger the BUG_ON in resv_map_release(). + +Fix this by introducing hugetlb_linear_page_index(), which returns the +page index in huge page granularity, and using it in place of +linear_page_index(). + +Link: https://lkml.kernel.org/r/20260310110526.335749-1-jianhuizzzzz@gmail.com +Fixes: a08c7193e4f1 ("mm/filemap: remove hugetlb special casing in filemap.c") +Signed-off-by: Jianhui Zhou +Reported-by: syzbot+f525fd79634858f478e7@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=f525fd79634858f478e7 +Acked-by: SeongJae Park +Reviewed-by: David Hildenbrand (Arm) +Acked-by: Mike Rapoport (Microsoft) +Cc: Jane Chu +Cc: Andrea Arcangeli +Cc: Hugh Dickins +Cc: JonasZhou +Cc: Muchun Song +Cc: Oscar Salvador +Cc: Peter Xu +Cc: SeongJae Park +Cc: Sidhartha Kumar +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/hugetlb.h | 17 +++++++++++++++++ + mm/userfaultfd.c | 2 +- + 2 files changed, 18 insertions(+), 1 deletion(-) + +--- a/include/linux/hugetlb.h ++++ b/include/linux/hugetlb.h +@@ -796,6 +796,23 @@ static inline unsigned huge_page_shift(s + return h->order + PAGE_SHIFT; + } + ++/** ++ * hugetlb_linear_page_index() - linear_page_index() but in hugetlb ++ * page size granularity. ++ * @vma: the hugetlb VMA ++ * @address: the virtual address within the VMA ++ * ++ * Return: the page offset within the mapping in huge page units. ++ */ ++static inline pgoff_t hugetlb_linear_page_index(struct vm_area_struct *vma, ++ unsigned long address) ++{ ++ struct hstate *h = hstate_vma(vma); ++ ++ return ((address - vma->vm_start) >> huge_page_shift(h)) + ++ (vma->vm_pgoff >> huge_page_order(h)); ++} ++ + static inline bool order_is_gigantic(unsigned int order) + { + return order > MAX_PAGE_ORDER; +--- a/mm/userfaultfd.c ++++ b/mm/userfaultfd.c +@@ -573,7 +573,7 @@ retry: + * in the case of shared pmds. fault mutex prevents + * races with other faulting threads. + */ +- idx = linear_page_index(dst_vma, dst_addr); ++ idx = hugetlb_linear_page_index(dst_vma, dst_addr); + mapping = dst_vma->vm_file->f_mapping; + hash = hugetlb_fault_mutex_hash(mapping, idx); + mutex_lock(&hugetlb_fault_mutex_table[hash]); diff --git a/queue-7.0/nilfs2-fix-null-i_assoc_inode-dereference-in-nilfs_mdt_save_to_shadow_map.patch b/queue-7.0/nilfs2-fix-null-i_assoc_inode-dereference-in-nilfs_mdt_save_to_shadow_map.patch new file mode 100644 index 0000000000..8d3d4ebeaa --- /dev/null +++ b/queue-7.0/nilfs2-fix-null-i_assoc_inode-dereference-in-nilfs_mdt_save_to_shadow_map.patch @@ -0,0 +1,47 @@ +From 4a4e0328edd9e9755843787d28f16dd4165f8b48 Mon Sep 17 00:00:00 2001 +From: Deepanshu Kartikey +Date: Tue, 31 Mar 2026 09:47:21 +0900 +Subject: nilfs2: fix NULL i_assoc_inode dereference in nilfs_mdt_save_to_shadow_map + +From: Deepanshu Kartikey + +commit 4a4e0328edd9e9755843787d28f16dd4165f8b48 upstream. + +The DAT inode's btree node cache (i_assoc_inode) is initialized lazily +during btree operations. However, nilfs_mdt_save_to_shadow_map() +assumes i_assoc_inode is already initialized when copying dirty pages +to the shadow map during GC. + +If NILFS_IOCTL_CLEAN_SEGMENTS is called immediately after mount before +any btree operation has occurred on the DAT inode, i_assoc_inode is +NULL leading to a general protection fault. + +Fix this by calling nilfs_attach_btree_node_cache() on the DAT inode +in nilfs_dat_read() at mount time, ensuring i_assoc_inode is always +initialized before any GC operation can use it. + +Reported-by: syzbot+4b4093b1f24ad789bf37@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=4b4093b1f24ad789bf37 +Tested-by: syzbot+4b4093b1f24ad789bf37@syzkaller.appspotmail.com +Fixes: e897be17a441 ("nilfs2: fix lockdep warnings in page operations for btree nodes") +Signed-off-by: Deepanshu Kartikey +Signed-off-by: Ryusuke Konishi +Cc: stable@vger.kernel.org +Signed-off-by: Viacheslav Dubeyko +Signed-off-by: Greg Kroah-Hartman +--- + fs/nilfs2/dat.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/fs/nilfs2/dat.c ++++ b/fs/nilfs2/dat.c +@@ -524,6 +524,9 @@ int nilfs_dat_read(struct super_block *s + if (err) + goto failed; + ++ err = nilfs_attach_btree_node_cache(dat); ++ if (err) ++ goto failed; + err = nilfs_read_inode_common(dat, raw_inode); + if (err) + goto failed; diff --git a/queue-7.0/series b/queue-7.0/series index f816ecbb01..46d6384207 100644 --- a/queue-7.0/series +++ b/queue-7.0/series @@ -68,3 +68,9 @@ media-em28xx-fix-use-after-free-in-em28xx_v4l2_open.patch hwmon-powerz-fix-use-after-free-on-usb-disconnect.patch alsa-6fire-fix-use-after-free-on-disconnect.patch bcache-fix-cached_dev.sb_bio-use-after-free-and-crash.patch +wireguard-device-use-exit_rtnl-callback-instead-of-manual-rtnl_lock-in-pre_exit.patch +media-as102-fix-to-not-free-memory-after-the-device-is-registered-in-as102_usb_probe.patch +nilfs2-fix-null-i_assoc_inode-dereference-in-nilfs_mdt_save_to_shadow_map.patch +media-vidtv-fix-pass-by-value-structs-causing-msan-warnings.patch +media-hackrf-fix-to-not-free-memory-after-the-device-is-registered-in-hackrf_probe.patch +mm-userfaultfd-fix-hugetlb-fault-mutex-hash-calculation.patch diff --git a/queue-7.0/wireguard-device-use-exit_rtnl-callback-instead-of-manual-rtnl_lock-in-pre_exit.patch b/queue-7.0/wireguard-device-use-exit_rtnl-callback-instead-of-manual-rtnl_lock-in-pre_exit.patch new file mode 100644 index 0000000000..2acfaae705 --- /dev/null +++ b/queue-7.0/wireguard-device-use-exit_rtnl-callback-instead-of-manual-rtnl_lock-in-pre_exit.patch @@ -0,0 +1,72 @@ +From 60a25ef8dacb3566b1a8c4de00572a498e2a3bf9 Mon Sep 17 00:00:00 2001 +From: Shardul Bankar +Date: Tue, 14 Apr 2026 17:39:44 +0200 +Subject: wireguard: device: use exit_rtnl callback instead of manual rtnl_lock in pre_exit + +From: Shardul Bankar + +commit 60a25ef8dacb3566b1a8c4de00572a498e2a3bf9 upstream. + +wg_netns_pre_exit() manually acquires rtnl_lock() inside the +pernet .pre_exit callback. This causes a hung task when another +thread holds rtnl_mutex - the cleanup_net workqueue (or the +setup_net failure rollback path) blocks indefinitely in +wg_netns_pre_exit() waiting to acquire the lock. + +Convert to .exit_rtnl, introduced in commit 7a60d91c690b ("net: +Add ->exit_rtnl() hook to struct pernet_operations."), where the +framework already holds RTNL and batches all callbacks under a +single rtnl_lock()/rtnl_unlock() pair, eliminating the contention +window. + +The rcu_assign_pointer(wg->creating_net, NULL) is safe to move +from .pre_exit to .exit_rtnl (which runs after synchronize_rcu()) +because all RCU readers of creating_net either use maybe_get_net() +- which returns NULL for a dying namespace with zero refcount - or +access net->user_ns which remains valid throughout the entire +ops_undo_list sequence. + +Reported-by: syzbot+f2fbf7478a35a94c8b7c@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?id=cb64c22a492202ca929e18262fdb8cb89e635c70 +Signed-off-by: Shardul Bankar +[ Jason: added __net_exit and __read_mostly annotations that were missing. ] +Fixes: 900575aa33a3 ("wireguard: device: avoid circular netns references") +Cc: stable@vger.kernel.org +Signed-off-by: Jason A. Donenfeld +Link: https://patch.msgid.link/20260414153944.2742252-5-Jason@zx2c4.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Greg Kroah-Hartman +--- + drivers/net/wireguard/device.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +--- a/drivers/net/wireguard/device.c ++++ b/drivers/net/wireguard/device.c +@@ -411,12 +411,11 @@ static struct rtnl_link_ops link_ops __r + .newlink = wg_newlink, + }; + +-static void wg_netns_pre_exit(struct net *net) ++static void __net_exit wg_netns_exit_rtnl(struct net *net, struct list_head *dev_kill_list) + { + struct wg_device *wg; + struct wg_peer *peer; + +- rtnl_lock(); + list_for_each_entry(wg, &device_list, device_list) { + if (rcu_access_pointer(wg->creating_net) == net) { + pr_debug("%s: Creating namespace exiting\n", wg->dev->name); +@@ -429,11 +428,10 @@ static void wg_netns_pre_exit(struct net + mutex_unlock(&wg->device_update_lock); + } + } +- rtnl_unlock(); + } + +-static struct pernet_operations pernet_ops = { +- .pre_exit = wg_netns_pre_exit ++static struct pernet_operations pernet_ops __read_mostly = { ++ .exit_rtnl = wg_netns_exit_rtnl + }; + + int __init wg_device_init(void)