From b6099150949f822999b5e989db1ad66e7850d925 Mon Sep 17 00:00:00 2001 From: Gabriel Krisman Bertazi Date: Wed, 17 Jun 2026 15:27:22 -0400 Subject: [PATCH 01/56] io_uring/net: Avoid msghdr on op_connect/op_bind async data [ Upstream commit 3979840cd858f30f43ea9f4e7f7f1f56de82d698 ] This fixes a memory leak due to the lack of the cleanup hook for the iovec. The stable backport differs from upstream by dropping the io_connect_bpf_populate hunk, which didn't exist at the time and by fixing the merge conflict due to the introduction of io_bind_file_create. Both IORING_OP_CONNECT and IORING_OP_BIND reuse the msghdr object just to store the sockaddr. Beyond allocating a much larger object than needed, msghdr can also wrap an iovec, which will be recycled unnecessarily. This uses the sockaddr directly. Cc: stable@vger.kernel.org Signed-off-by: Gabriel Krisman Bertazi Link: https://patch.msgid.link/20260602215327.1885109-2-krisman@suse.de Signed-off-by: Jens Axboe Signed-off-by: Gabriel Krisman Bertazi Signed-off-by: Sasha Levin --- io_uring/net.c | 36 ++++++++++++++++++------------------ io_uring/opdef.c | 4 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/io_uring/net.c b/io_uring/net.c index a46c7e8170402..3ab2bfca1bd5d 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -1771,7 +1771,7 @@ int io_socket(struct io_kiocb *req, unsigned int issue_flags) int io_connect_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) { struct io_connect *conn = io_kiocb_to_cmd(req, struct io_connect); - struct io_async_msghdr *io; + struct sockaddr_storage *addr; if (sqe->len || sqe->buf_index || sqe->rw_flags || sqe->splice_fd_in) return -EINVAL; @@ -1780,17 +1780,17 @@ int io_connect_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) conn->addr_len = READ_ONCE(sqe->addr2); conn->in_progress = conn->seen_econnaborted = false; - io = io_msg_alloc_async(req); - if (unlikely(!io)) + addr = io_uring_alloc_async_data(NULL, req); + if (unlikely(!addr)) return -ENOMEM; - return move_addr_to_kernel(conn->addr, conn->addr_len, &io->addr); + return move_addr_to_kernel(conn->addr, conn->addr_len, addr); } int io_connect(struct io_kiocb *req, unsigned int issue_flags) { struct io_connect *connect = io_kiocb_to_cmd(req, struct io_connect); - struct io_async_msghdr *io = req->async_data; + struct sockaddr_storage *addr = req->async_data; unsigned file_flags; int ret; bool force_nonblock = issue_flags & IO_URING_F_NONBLOCK; @@ -1804,8 +1804,7 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags) file_flags = force_nonblock ? O_NONBLOCK : 0; - ret = __sys_connect_file(req->file, &io->addr, connect->addr_len, - file_flags); + ret = __sys_connect_file(req->file, addr, connect->addr_len, file_flags); if ((ret == -EAGAIN || ret == -EINPROGRESS || ret == -ECONNABORTED) && force_nonblock) { if (ret == -EINPROGRESS) { @@ -1834,7 +1833,6 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags) out: if (ret < 0) req_set_fail(req); - io_req_msg_cleanup(req, issue_flags); io_req_set_res(req, ret, 0); return IOU_COMPLETE; } @@ -1844,15 +1842,15 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags) * which in turn end up in mnt_want_write() which will grab the fs * percpu start write sem. This can trigger a lockdep warning. */ -static int io_bind_file_create(const struct io_async_msghdr *io, int addr_len) +static int io_bind_file_create(const struct sockaddr_storage *addr, int addr_len) { const struct sockaddr_un *sun; - if (io->addr.ss_family != AF_UNIX) + if (addr->ss_family != AF_UNIX) return 0; if (addr_len <= offsetof(struct sockaddr_un, sun_path)) return 0; - sun = (const struct sockaddr_un *) &io->addr; + sun = (const struct sockaddr_un *) addr; return sun->sun_path[0] != '\0'; } @@ -1860,7 +1858,7 @@ int io_bind_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) { struct io_bind *bind = io_kiocb_to_cmd(req, struct io_bind); struct sockaddr __user *uaddr; - struct io_async_msghdr *io; + struct sockaddr_storage *addr; int ret; if (sqe->len || sqe->buf_index || sqe->rw_flags || sqe->splice_fd_in) @@ -1869,21 +1867,23 @@ int io_bind_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) uaddr = u64_to_user_ptr(READ_ONCE(sqe->addr)); bind->addr_len = READ_ONCE(sqe->addr2); - io = io_msg_alloc_async(req); - if (unlikely(!io)) + addr = io_uring_alloc_async_data(NULL, req); + if (unlikely(!addr)) return -ENOMEM; - ret = move_addr_to_kernel(uaddr, bind->addr_len, &io->addr); + + ret = move_addr_to_kernel(uaddr, bind->addr_len, addr); if (unlikely(ret)) return ret; - if (io_bind_file_create(io, bind->addr_len)) + if (io_bind_file_create(addr, bind->addr_len)) req->flags |= REQ_F_FORCE_ASYNC; return 0; } + int io_bind(struct io_kiocb *req, unsigned int issue_flags) { struct io_bind *bind = io_kiocb_to_cmd(req, struct io_bind); - struct io_async_msghdr *io = req->async_data; + struct sockaddr_storage *addr = req->async_data; struct socket *sock; int ret; @@ -1891,7 +1891,7 @@ int io_bind(struct io_kiocb *req, unsigned int issue_flags) if (unlikely(!sock)) return -ENOTSOCK; - ret = __sys_bind_socket(sock, &io->addr, bind->addr_len); + ret = __sys_bind_socket(sock, addr, bind->addr_len); if (ret < 0) req_set_fail(req); io_req_set_res(req, ret, 0); diff --git a/io_uring/opdef.c b/io_uring/opdef.c index 932319633eac2..a57c820567f77 100644 --- a/io_uring/opdef.c +++ b/io_uring/opdef.c @@ -207,7 +207,7 @@ const struct io_issue_def io_issue_defs[] = { .unbound_nonreg_file = 1, .pollout = 1, #if defined(CONFIG_NET) - .async_size = sizeof(struct io_async_msghdr), + .async_size = sizeof(struct sockaddr_storage), .prep = io_connect_prep, .issue = io_connect, #else @@ -504,7 +504,7 @@ const struct io_issue_def io_issue_defs[] = { .needs_file = 1, .prep = io_bind_prep, .issue = io_bind, - .async_size = sizeof(struct io_async_msghdr), + .async_size = sizeof(struct sockaddr_storage), #else .prep = io_eopnotsupp_prep, #endif From 29706ac73f93b72f84deea32dd48aa88f5615538 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Fri, 30 Jan 2026 20:04:57 +0000 Subject: [PATCH 02/56] net: stmmac: fix stm32 (and potentially others) resume regression [ Upstream commit dbbec8c5a79f4c7aa8d07da8c0b5a34d76c50699 ] Marek reported that suspending stm32 causes the following errors when the interface is administratively down: $ echo devices > /sys/power/pm_test $ echo mem > /sys/power/state ... ck_ker_eth2stp already disabled ... ck_ker_eth2stp already unprepared ... On suspend, stm32 starts the eth2stp clock in its suspend method, and stops it in the resume method. This is because the blamed commit omits the call to the platform glue ->suspend() method, but does make the call to the platform glue ->resume() method. This problem affects all other converted drivers as well - e.g. looking at the PCIe drivers, pci_save_state() will not be called, but pci_restore_state() will be. Similar issues affect all other drivers. Fix this by always calling the ->suspend() method, even when the network interface is down. This fixes all the conversions to the platform glue ->suspend() and ->resume() methods. Link: https://lore.kernel.org/r/20260114081809.12758-1-marex@nabladev.com Fixes: 07bbbfe7addf ("net: stmmac: add suspend()/resume() platform ops") Reported-by: Marek Vasut Tested-by: Marek Vasut Signed-off-by: Russell King (Oracle) Link: https://patch.msgid.link/E1vlujh-00000007Hkw-2p6r@rmk-PC.armlinux.org.uk Signed-off-by: Jakub Kicinski Signed-off-by: Sasha Levin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 41b270a486308..1ceedd74e4290 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7760,7 +7760,7 @@ int stmmac_suspend(struct device *dev) u32 chan; if (!ndev || !netif_running(ndev)) - return 0; + goto suspend_bsp; mutex_lock(&priv->lock); @@ -7803,6 +7803,7 @@ int stmmac_suspend(struct device *dev) if (stmmac_fpe_supported(priv)) ethtool_mmsv_stop(&priv->fpe_cfg.mmsv); +suspend_bsp: if (priv->plat->suspend) return priv->plat->suspend(dev, priv->plat->bsp_priv); From af2892249d982a1c036ca456cc135374e68b6677 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Mon, 18 May 2026 22:28:06 -0700 Subject: [PATCH 03/56] fuse: re-lock request before replacing page cache folio commit a078484921052d0badd827fcc2770b5cfc1d4120 upstream. fuse_try_move_folio() unlocks the request on entry but does not re-lock it on the success path. This means fuse_chan_abort() can end the request and free the fuse_io_args (eg fuse_readpages_end()) while the subsequent copy chain logic after fuse_try_move_folio() accesses the fuse_io_args, leading to use-after-free issues. Fix this by calling lock_request() before replace_page_cache_folio(). This ensures the request is locked on the success path which will prevent the fuse_io_args from being freed while the later copying logic runs, and also ensures that the ap->folios[i]->mapping is never null since ap->folios[i] will always point to the newfolio after replace_page_cache_folio(). Fixes: ce534fb05292 ("fuse: allow splice to move pages") Cc: stable@vger.kernel.org Reported-by: Lei Lu Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi Signed-off-by: Greg Kroah-Hartman --- fs/fuse/dev.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 8bd6cf17355f6..527f60f7ad7be 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1037,6 +1037,10 @@ static int fuse_try_move_folio(struct fuse_copy_state *cs, struct folio **foliop if (WARN_ON(folio_test_mlocked(oldfolio))) goto out_fallback_unlock; + err = lock_request(cs->req); + if (err) + goto out_fallback_unlock; + replace_page_cache_folio(oldfolio, newfolio); folio_get(newfolio); @@ -1050,20 +1054,7 @@ static int fuse_try_move_folio(struct fuse_copy_state *cs, struct folio **foliop */ pipe_buf_release(cs->pipe, buf); - err = 0; - spin_lock(&cs->req->waitq.lock); - if (test_bit(FR_ABORTED, &cs->req->flags)) - err = -ENOENT; - else - *foliop = newfolio; - spin_unlock(&cs->req->waitq.lock); - - if (err) { - folio_unlock(newfolio); - folio_put(newfolio); - goto out_put_old; - } - + *foliop = newfolio; folio_unlock(oldfolio); /* Drop ref for ap->pages[] array */ folio_put(oldfolio); From 95f9eb19d5e65f7c32276f2bd77265c3f60727a6 Mon Sep 17 00:00:00 2001 From: Yang Erkun Date: Wed, 13 May 2026 10:42:52 +0800 Subject: [PATCH 04/56] Revert "NFSD: Defer sub-object cleanup in export put callbacks" commit 516403d4d85607fdef3ca41d4a56b54e5566fa9a upstream. This reverts commit 48db892356d6cb80f6942885545de4a6dd8d2a29. Commit 48db892356d6 ("NFSD: Defer sub-object cleanup in export put callbacks") moved path_put() and auth_domain_put() out of svc_export_put() and expkey_put() and behind queue_rcu_work() to close a claimed use-after-free in e_show() and c_show() against ex_path and ex_client->name. Discussion in [1] shows neither the diagnosis nor the remedy survives review. The downstream teardown of both sub-objects is already RCU-deferred. auth_domain_put() reaches svcauth_unix_domain_release(), which frees the unix_domain and its ->name through call_rcu(). path_put() reaches dentry_free(), which frees the dentry through call_rcu(), and prepend_path() is already structured to tolerate concurrent dentry teardown. A reader in cache_seq_start_rcu() therefore observes both sub-objects through the next grace period regardless of whether svc_export_put() runs synchronously, so the synchronous form was never unsafe. The crash signature in the report cited by commit 48db892356d6 ("NFSD: Defer sub-object cleanup in export put callbacks") has a different root cause: a /proc/net/rpc cache file held open across network-namespace exit lets cache_destroy_net() free cd->hash_table while a reader is still walking it. The correct fix pins cd->net for the open fd's lifetime and does not require any deferral inside svc_export_put(). Meanwhile, deferring path_put() out of svc_export_put() reintroduces the regression that commit 69d803c40ede ("nfsd: Revert "nfsd: release svc_expkey/svc_export with rcu_work"") repaired: after "exportfs -r" drops the last cache reference, the mount reference held through ex_path lingers in the workqueue, so a subsequent umount fails with EBUSY. Restore the synchronous path_put() and auth_domain_put() in svc_export_put() and expkey_put() and the call_rcu()/kfree_rcu() free of the containing structures. The unrelated fix for ex_uuid/ex_stats from commit 2530766492ec ("nfsd: fix UAF when access ex_uuid or ex_stats") is preserved. Link: https://lore.kernel.org/all/10019b42-4589-4f9f-8d5b-d8197db1ce3c@huawei.com/ [1] Fixes: 48db892356d6 ("NFSD: Defer sub-object cleanup in export put callbacks") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Tested-by: Alexandr Alexandrov Signed-off-by: Yang Erkun Signed-off-by: Chuck Lever Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/export.c | 63 +++++++----------------------------------------- fs/nfsd/export.h | 7 ++---- fs/nfsd/nfsctl.c | 8 +----- 3 files changed, 12 insertions(+), 66 deletions(-) diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index 18933ca407bef..2a1499f2ad196 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -36,30 +36,19 @@ * second map contains a reference to the entry in the first map. */ -static struct workqueue_struct *nfsd_export_wq; - #define EXPKEY_HASHBITS 8 #define EXPKEY_HASHMAX (1 << EXPKEY_HASHBITS) #define EXPKEY_HASHMASK (EXPKEY_HASHMAX -1) -static void expkey_release(struct work_struct *work) +static void expkey_put(struct kref *ref) { - struct svc_expkey *key = container_of(to_rcu_work(work), - struct svc_expkey, ek_rwork); + struct svc_expkey *key = container_of(ref, struct svc_expkey, h.ref); if (test_bit(CACHE_VALID, &key->h.flags) && !test_bit(CACHE_NEGATIVE, &key->h.flags)) path_put(&key->ek_path); auth_domain_put(key->ek_client); - kfree(key); -} - -static void expkey_put(struct kref *ref) -{ - struct svc_expkey *key = container_of(ref, struct svc_expkey, h.ref); - - INIT_RCU_WORK(&key->ek_rwork, expkey_release); - queue_rcu_work(nfsd_export_wq, &key->ek_rwork); + kfree_rcu(key, ek_rcu); } static int expkey_upcall(struct cache_detail *cd, struct cache_head *h) @@ -364,13 +353,11 @@ static void export_stats_destroy(struct export_stats *stats) EXP_STATS_COUNTERS_NUM); } -static void svc_export_release(struct work_struct *work) +static void svc_export_release(struct rcu_head *rcu_head) { - struct svc_export *exp = container_of(to_rcu_work(work), - struct svc_export, ex_rwork); + struct svc_export *exp = container_of(rcu_head, struct svc_export, + ex_rcu); - path_put(&exp->ex_path); - auth_domain_put(exp->ex_client); nfsd4_fslocs_free(&exp->ex_fslocs); export_stats_destroy(exp->ex_stats); kfree(exp->ex_stats); @@ -382,8 +369,9 @@ static void svc_export_put(struct kref *ref) { struct svc_export *exp = container_of(ref, struct svc_export, h.ref); - INIT_RCU_WORK(&exp->ex_rwork, svc_export_release); - queue_rcu_work(nfsd_export_wq, &exp->ex_rwork); + path_put(&exp->ex_path); + auth_domain_put(exp->ex_client); + call_rcu(&exp->ex_rcu, svc_export_release); } static int svc_export_upcall(struct cache_detail *cd, struct cache_head *h) @@ -1490,36 +1478,6 @@ const struct seq_operations nfs_exports_op = { .show = e_show, }; -/** - * nfsd_export_wq_init - allocate the export release workqueue - * - * Called once at module load. The workqueue runs deferred svc_export and - * svc_expkey release work scheduled by queue_rcu_work() in the cache put - * callbacks. - * - * Return values: - * %0: workqueue allocated - * %-ENOMEM: allocation failed - */ -int nfsd_export_wq_init(void) -{ - nfsd_export_wq = alloc_workqueue("nfsd_export", WQ_UNBOUND, 0); - if (!nfsd_export_wq) - return -ENOMEM; - return 0; -} - -/** - * nfsd_export_wq_shutdown - drain and free the export release workqueue - * - * Called once at module unload. Per-namespace teardown in - * nfsd_export_shutdown() has already drained all deferred work. - */ -void nfsd_export_wq_shutdown(void) -{ - destroy_workqueue(nfsd_export_wq); -} - /* * Initialize the exports module. */ @@ -1581,9 +1539,6 @@ nfsd_export_shutdown(struct net *net) cache_unregister_net(nn->svc_expkey_cache, net); cache_unregister_net(nn->svc_export_cache, net); - /* Drain deferred export and expkey release work. */ - rcu_barrier(); - flush_workqueue(nfsd_export_wq); cache_destroy_net(nn->svc_expkey_cache, net); cache_destroy_net(nn->svc_export_cache, net); svcauth_unix_purge(net); diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h index b053993745749..d2b09cd761453 100644 --- a/fs/nfsd/export.h +++ b/fs/nfsd/export.h @@ -7,7 +7,6 @@ #include #include -#include #include #include @@ -76,7 +75,7 @@ struct svc_export { u32 ex_layout_types; struct nfsd4_deviceid_map *ex_devid_map; struct cache_detail *cd; - struct rcu_work ex_rwork; + struct rcu_head ex_rcu; unsigned long ex_xprtsec_modes; struct export_stats *ex_stats; }; @@ -93,7 +92,7 @@ struct svc_expkey { u32 ek_fsid[6]; struct path ek_path; - struct rcu_work ek_rwork; + struct rcu_head ek_rcu; }; #define EX_ISSYNC(exp) (!((exp)->ex_flags & NFSEXP_ASYNC)) @@ -111,8 +110,6 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp, /* * Function declarations */ -int nfsd_export_wq_init(void); -void nfsd_export_wq_shutdown(void); int nfsd_export_init(struct net *); void nfsd_export_shutdown(struct net *); void nfsd_export_flush(struct net *); diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 4ed19d81577b6..1da5c40d6722a 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2262,12 +2262,9 @@ static int __init init_nfsd(void) if (retval) goto out_free_pnfs; nfsd_lockd_init(); /* lockd->nfsd callbacks */ - retval = nfsd_export_wq_init(); - if (retval) - goto out_free_lockd; retval = register_pernet_subsys(&nfsd_net_ops); if (retval < 0) - goto out_free_export_wq; + goto out_free_lockd; retval = register_cld_notifier(); if (retval) goto out_free_subsys; @@ -2296,8 +2293,6 @@ static int __init init_nfsd(void) unregister_cld_notifier(); out_free_subsys: unregister_pernet_subsys(&nfsd_net_ops); -out_free_export_wq: - nfsd_export_wq_shutdown(); out_free_lockd: nfsd_lockd_shutdown(); nfsd_drc_slab_free(); @@ -2318,7 +2313,6 @@ static void __exit exit_nfsd(void) nfsd4_destroy_laundry_wq(); unregister_cld_notifier(); unregister_pernet_subsys(&nfsd_net_ops); - nfsd_export_wq_shutdown(); nfsd_drc_slab_free(); nfsd_lockd_shutdown(); nfsd4_free_slabs(); From a460935022f512e167b4c5d4c12d85f89ba6aabd Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Mon, 22 Jun 2026 11:54:38 +0200 Subject: [PATCH 05/56] debugobjects: Allow to refill the pool before SYSTEM_SCHEDULING commit 06e0ae988f6e3499785c407429953ade19c1096b upstream. The pool of free objects is refilled on several occasions such as object initialisation. On PREEMPT_RT refilling is limited to preemptible sections due to sleeping locks used by the memory allocator. The system boots with disabled interrupts so the pool can not be refilled. If too many objects are initialized and the pool gets empty then debugobjects disables itself. Refiling can also happen early in the boot with disabled interrupts as long as the scheduler is not operational. If the scheduler can not preempt a task then a sleeping lock can not be contended. Allow to additionally refill the pool if the scheduler is not operational. Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20251127153652.291697-2-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- lib/debugobjects.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/debugobjects.c b/lib/debugobjects.c index e4b7f77ece3b4..9d59b797d1b50 100644 --- a/lib/debugobjects.c +++ b/lib/debugobjects.c @@ -731,7 +731,7 @@ static void debug_objects_fill_pool(void) * raw_spinlock_t are basically the same type and this lock-type * inversion works just fine. */ - if (!IS_ENABLED(CONFIG_PREEMPT_RT) || preemptible()) { + if (!IS_ENABLED(CONFIG_PREEMPT_RT) || preemptible() || system_state < SYSTEM_SCHEDULING) { /* * Annotate away the spinlock_t inside raw_spinlock_t warning * by temporarily raising the wait-type to WAIT_SLEEP, matching From 9cd2087cd7026995c8a8e2c768198ed1de6eb011 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Mon, 22 Jun 2026 11:54:42 +0200 Subject: [PATCH 06/56] debugobjects: Use LD_WAIT_CONFIG instead of LD_WAIT_SLEEP commit 37de2dbc318ee10577c1c2704de5a803e75e55a2 upstream. fill_pool_map is used to suppress nesting violations caused by acquiring a spinlock_t (from within the memory allocator) while holding a raw_spinlock_t. The used annotation is wrong. LD_WAIT_SLEEP is for always sleeping lock types such as mutex_t. LD_WAIT_CONFIG is for lock type which are sleeping while spinning on PREEMPT_RT such as spinlock_t. Use LD_WAIT_CONFIG as override. Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20251127153652.291697-3-bigeasy@linutronix.de Signed-off-by: Sasha Levin --- lib/debugobjects.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/debugobjects.c b/lib/debugobjects.c index 9d59b797d1b50..4343dc5e5c99d 100644 --- a/lib/debugobjects.c +++ b/lib/debugobjects.c @@ -734,10 +734,10 @@ static void debug_objects_fill_pool(void) if (!IS_ENABLED(CONFIG_PREEMPT_RT) || preemptible() || system_state < SYSTEM_SCHEDULING) { /* * Annotate away the spinlock_t inside raw_spinlock_t warning - * by temporarily raising the wait-type to WAIT_SLEEP, matching + * by temporarily raising the wait-type to LD_WAIT_CONFIG, matching * the preemptible() condition above. */ - static DEFINE_WAIT_OVERRIDE_MAP(fill_pool_map, LD_WAIT_SLEEP); + static DEFINE_WAIT_OVERRIDE_MAP(fill_pool_map, LD_WAIT_CONFIG); lock_map_acquire_try(&fill_pool_map); fill_pool(); lock_map_release(&fill_pool_map); From 3a408cae608d9c075dd3a9e5cfc03b3cb0726863 Mon Sep 17 00:00:00 2001 From: Helen Koike Date: Mon, 22 Jun 2026 11:54:46 +0200 Subject: [PATCH 07/56] debugobjects: Do not fill_pool() if pi_blocked_on commit 5f41161059fd0f1bbf18c90f3180e38cc45a14eb upstream. On RT enabled kernels, fill_pool() ends up calling rtlock_lock(), which asserts if current::pi_blocked_on is set, because a task can obviously only block on one lock as otherwise the priority inheritenace chain gets corrupted. Prevent this by expanding the conditional to take current::pi_blocked_on into account. Fixes: 4bedcc28469a ("debugobjects: Make them PREEMPT_RT aware") Reported-by: syzbot+b8ca586b9fc235f0c0df@syzkaller.appspotmail.com Signed-off-by: Helen Koike Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260511215359.3351259-1-koike@igalia.com Closes: https://syzkaller.appspot.com/bug?extid=b8ca586b9fc235f0c0df Signed-off-by: Sasha Levin --- lib/debugobjects.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/debugobjects.c b/lib/debugobjects.c index 4343dc5e5c99d..cbd025dae5ce9 100644 --- a/lib/debugobjects.c +++ b/lib/debugobjects.c @@ -711,6 +711,15 @@ static struct debug_obj *lookup_object_or_alloc(void *addr, struct debug_bucket return NULL; } +static inline bool debug_objects_is_pi_blocked_on(void) +{ +#ifdef CONFIG_RT_MUTEXES + return current->pi_blocked_on != NULL; +#else + return false; +#endif +} + static void debug_objects_fill_pool(void) { if (!static_branch_likely(&obj_cache_enabled)) @@ -727,11 +736,12 @@ static void debug_objects_fill_pool(void) /* * On RT enabled kernels the pool refill must happen in preemptible - * context -- for !RT kernels we rely on the fact that spinlock_t and - * raw_spinlock_t are basically the same type and this lock-type - * inversion works just fine. + * context and not enqueued on an rt_mutex -- for !RT kernels we rely + * on the fact that spinlock_t and raw_spinlock_t are basically the + * same type and this lock-type inversion works just fine. */ - if (!IS_ENABLED(CONFIG_PREEMPT_RT) || preemptible() || system_state < SYSTEM_SCHEDULING) { + if (!IS_ENABLED(CONFIG_PREEMPT_RT) || system_state < SYSTEM_SCHEDULING || + (preemptible() && !debug_objects_is_pi_blocked_on())) { /* * Annotate away the spinlock_t inside raw_spinlock_t warning * by temporarily raising the wait-type to LD_WAIT_CONFIG, matching From 44b8b03a9fb5c575548fc72c674653d6baba142a Mon Sep 17 00:00:00 2001 From: Waiman Long Date: Mon, 22 Jun 2026 11:54:51 +0200 Subject: [PATCH 08/56] debugobjects: Dont call fill_pool() in early boot hardirq context commit 0d046ae106255cba5eb83b23f78ee93f3620247d upstream. When booting a debug PREEMPT_RT kernel on an ARM64 system, a "inconsistent {HARDIRQ-ON-W} -> {IN-HARDIRQ-W} usage" lockdep warning message was reported to the console. During early boot, interrupts are enabled before the scheduler is enabled. In this window (before SYSTEM_SCHEDULING is set) interrupts can fire and in the hard interrupt context handler attempt to fill the pool This can lead to a deadlock when the interrupt occurred when the interrupt hits a region which holds a lock that is required to be taken in the allocation path. Add a new can_fill_pool() helper and reorder the exception rule and forbid this scenario by excluding allocations from hard interrupt context. Fixes: 06e0ae988f6e ("debugobjects: Allow to refill the pool before SYSTEM_SCHEDULING") Suggested-by: Sebastian Andrzej Siewior Suggested-by: Thomas Gleixner Signed-off-by: Waiman Long Signed-off-by: Thomas Gleixner Reviewed-by: Sebastian Andrzej Siewior Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260605173038.495075-1-longman@redhat.com Signed-off-by: Sasha Levin --- lib/debugobjects.c | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/debugobjects.c b/lib/debugobjects.c index cbd025dae5ce9..17f116247bb42 100644 --- a/lib/debugobjects.c +++ b/lib/debugobjects.c @@ -720,6 +720,41 @@ static inline bool debug_objects_is_pi_blocked_on(void) #endif } +static inline bool can_fill_pool(void) +{ + /* + * On !RT enabled kernels there are no restrictions and spinlock_t and + * raw_spinlock_t are the same types. + */ + if (!IS_ENABLED(CONFIG_PREEMPT_RT)) + return true; + + /* + * On RT enabled kernels, the task must not be blocked on a lock as + * that could corrupt the PI state when blocking on a lock in the + * allocation path. + */ + if (debug_objects_is_pi_blocked_on()) + return false; + + /* + * On RT enabled kernels the pool refill should happen in preemptible + * context. + */ + if (preemptible()) + return true; + + /* + * Though during system boot before scheduling is set up, preemption is + * disabled and the pool can get exhausted. Before scheduling is active + * a task cannot be blocked on a sleeping lock, but it might hold a lock + * and if interrupted then hard interrupt context might run into a lock + * inversion. So exclude hard interrupt context from allocations before + * scheduling is active. + */ + return system_state < SYSTEM_SCHEDULING && !in_hardirq(); +} + static void debug_objects_fill_pool(void) { if (!static_branch_likely(&obj_cache_enabled)) @@ -734,18 +769,11 @@ static void debug_objects_fill_pool(void) if (likely(!pool_should_refill(&pool_global))) return; - /* - * On RT enabled kernels the pool refill must happen in preemptible - * context and not enqueued on an rt_mutex -- for !RT kernels we rely - * on the fact that spinlock_t and raw_spinlock_t are basically the - * same type and this lock-type inversion works just fine. - */ - if (!IS_ENABLED(CONFIG_PREEMPT_RT) || system_state < SYSTEM_SCHEDULING || - (preemptible() && !debug_objects_is_pi_blocked_on())) { + if (can_fill_pool()) { /* * Annotate away the spinlock_t inside raw_spinlock_t warning * by temporarily raising the wait-type to LD_WAIT_CONFIG, matching - * the preemptible() condition above. + * the preemptible() condition in can_fill_pool(). */ static DEFINE_WAIT_OVERRIDE_MAP(fill_pool_map, LD_WAIT_CONFIG); lock_map_acquire_try(&fill_pool_map); From e2b143df29003d2704b51f62e9297006953dbacb Mon Sep 17 00:00:00 2001 From: Lord Ulf Henrik Holmberg Date: Sat, 9 May 2026 10:40:11 +0200 Subject: [PATCH 09/56] RDMA/bnxt_re: zero shared page before exposing to userspace commit f6b079629becfa977f9c51fe53ad2e6dcc55ef44 upstream. bnxt_re_alloc_ucontext() allocates uctx->shpg via __get_free_page(GFP_KERNEL). The buddy allocator does not zero pages without __GFP_ZERO, so the page contains stale kernel data from whatever object most recently freed it. The page is then mapped into userspace via vm_insert_page() under BNXT_RE_MMAP_SH_PAGE in bnxt_re_mmap(). The driver only ever writes 4 bytes (a u32 AVID) at offset BNXT_RE_AVID_OFFT (0x10) inside bnxt_re_create_ah(); the remaining 4092 bytes of the page are exposed to userspace unsanitised, leaking kernel memory contents. Any user with access to /dev/infiniband/uverbsX on a host with a bnxt_re device (typically rdma group membership) can read this data via a single mmap() at pgoff 0 after IB_USER_VERBS_CMD_GET_CONTEXT. Other shared pages in the same file already use get_zeroed_page() correctly: drivers/infiniband/hw/bnxt_re/ib_verbs.c srq->uctx_srq_page = (void *)get_zeroed_page(GFP_KERNEL); cq->uctx_cq_page = (void *)get_zeroed_page(GFP_KERNEL); uctx->shpg is the only outlier. Bring it in line with the existing convention by switching to get_zeroed_page(). Fixes: 1ac5a4047975 ("RDMA/bnxt_re: Add bnxt_re RoCE driver") Signed-off-by: Lord Ulf Henrik Holmberg Link: https://patch.msgid.link/20260509084011.11971-1-pomzm67@gmail.com Signed-off-by: Leon Romanovsky Signed-off-by: Greg Kroah-Hartman --- drivers/infiniband/hw/bnxt_re/ib_verbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/bnxt_re/ib_verbs.c b/drivers/infiniband/hw/bnxt_re/ib_verbs.c index ff91511bd3389..c1645de495a72 100644 --- a/drivers/infiniband/hw/bnxt_re/ib_verbs.c +++ b/drivers/infiniband/hw/bnxt_re/ib_verbs.c @@ -4350,7 +4350,7 @@ int bnxt_re_alloc_ucontext(struct ib_ucontext *ctx, struct ib_udata *udata) uctx->rdev = rdev; - uctx->shpg = (void *)__get_free_page(GFP_KERNEL); + uctx->shpg = (void *)get_zeroed_page(GFP_KERNEL); if (!uctx->shpg) { rc = -ENOMEM; goto fail; From 5f4d2bd028ebb6e4c09a9d64842546022321d4a7 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 15 Apr 2026 01:23:39 +0800 Subject: [PATCH 10/56] i2c: stub: Reject I2C block transfers with invalid length commit 6036b5067a8199ba7a2dc7b377d4b9dd276d5f9e upstream. The I2C_SMBUS_I2C_BLOCK_DATA case in stub_xfer() uses data->block[0] as the transfer length. The existing check only clamps it to avoid overrunning the chip->words[256] register array, but does not validate it against I2C_SMBUS_BLOCK_MAX (32), which is the limit of the union i2c_smbus_data.block buffer (34 bytes total). The driver is a development/test tool (CONFIG_I2C_STUB=m, not built by default) that must be loaded with a chip_addr= parameter. A local user with access to /dev/i2c-* can issue an I2C_SMBUS ioctl with I2C_SMBUS_I2C_BLOCK_DATA and data->block[0] > 32, causing stub_xfer() to read or write past the end of the union i2c_smbus_data.block buffer: BUG: KASAN: stack-out-of-bounds in stub_xfer (drivers/i2c/i2c-stub.c:223) Read of size 1 at addr ffff88800abcfd92 by task exploit/81 Call Trace: stub_xfer (drivers/i2c/i2c-stub.c:223) __i2c_smbus_xfer (drivers/i2c/i2c-core-smbus.c:593) i2c_smbus_xfer (drivers/i2c/i2c-core-smbus.c:536) i2cdev_ioctl_smbus (drivers/i2c/i2c-dev.c:391) i2cdev_ioctl (drivers/i2c/i2c-dev.c:478) __x64_sys_ioctl (fs/ioctl.c:583) do_syscall_64 (arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130) The bug exists because i2c-stub implements .smbus_xfer directly, bypassing the I2C_SMBUS_BLOCK_MAX validation in i2c_smbus_xfer_emulated(). The I2C_SMBUS_BLOCK_DATA case in the same function correctly validates against I2C_SMBUS_BLOCK_MAX, but the I2C_SMBUS_I2C_BLOCK_DATA case does not. Fix by rejecting transfers with data->block[0] == 0 or data->block[0] > I2C_SMBUS_BLOCK_MAX with -EINVAL, consistent with both the I2C_SMBUS_BLOCK_DATA case in the same function and the I2C_SMBUS_I2C_BLOCK_DATA validation in i2c_smbus_xfer_emulated(). Fixes: 4710317891e4 ("i2c-stub: Implement I2C block support") Reported-by: Xiang Mei Signed-off-by: Weiming Shi Reviewed-by: Jean Delvare Signed-off-by: Wolfram Sang Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/i2c-stub.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/i2c/i2c-stub.c b/drivers/i2c/i2c-stub.c index 09e7b7bf4c5f7..a6e2ce4360ca5 100644 --- a/drivers/i2c/i2c-stub.c +++ b/drivers/i2c/i2c-stub.c @@ -214,6 +214,11 @@ static s32 stub_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, * We ignore banks here, because banked chips don't use I2C * block transfers */ + if (data->block[0] == 0 || + data->block[0] > I2C_SMBUS_BLOCK_MAX) { + ret = -EINVAL; + break; + } if (data->block[0] > 256 - command) /* Avoid overrun */ data->block[0] = 256 - command; len = data->block[0]; From 8b17adf6d4fb6bf61fa4c3f58366a7c082799a71 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Thu, 14 May 2026 05:25:12 -0700 Subject: [PATCH 11/56] net: qualcomm: rmnet: fix endpoint use-after-free in rmnet_dellink() commit d00c953a8f69921f484b629801766da68f27f658 upstream. rmnet_dellink() removes the endpoint from the hash table with hlist_del_init_rcu() and then immediately frees it with kfree(). However, RCU readers on the receive path (rmnet_rx_handler -> __rmnet_map_ingress_handler) may still hold a reference to the endpoint and dereference ep->egress_dev after the memory has been freed. The endpoint is a kmalloc-32 object, and the stale read at offset 8 corresponds to the egress_dev pointer. BUG: unable to handle page fault for address: ffffffffde942eef Oops: 0002 [#1] SMP NOPTI CPU: 1 UID: 0 PID: 137 Comm: poc_write Not tainted 7.0.0+ #4 PREEMPTLAZY RIP: 0010:rmnet_vnd_rx_fixup (rmnet_vnd.c:27) Call Trace: __rmnet_map_ingress_handler (rmnet_handlers.c:48 rmnet_handlers.c:101) rmnet_rx_handler (rmnet_handlers.c:129 rmnet_handlers.c:235) __netif_receive_skb_core.constprop.0 (net/core/dev.c:6096) __netif_receive_skb_one_core (net/core/dev.c:6208) netif_receive_skb (net/core/dev.c:6467) tun_get_user (drivers/net/tun.c:1955) tun_chr_write_iter (drivers/net/tun.c:2003) vfs_write (fs/read_write.c:688) ksys_write (fs/read_write.c:740) Add an rcu_head field to struct rmnet_endpoint and replace kfree() with kfree_rcu() so the endpoint memory remains valid through the RCU grace period. Also remove the rmnet_vnd_dellink() call and inline only the nr_rmnet_devs decrement, since rmnet_vnd_dellink() would set ep->egress_dev to NULL during the grace period, creating a data race with lockless readers. Fixes: ceed73a2cf4a ("drivers: net: ethernet: qualcomm: rmnet: Initial implementation") Reported-by: Xiang Mei Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260514122511.3083479-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c | 8 ++++---- drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c index ba8763cac9d9e..3a132633c066e 100644 --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c @@ -213,8 +213,8 @@ static void rmnet_dellink(struct net_device *dev, struct list_head *head) ep = rmnet_get_endpoint(real_port, mux_id); if (ep) { hlist_del_init_rcu(&ep->hlnode); - rmnet_vnd_dellink(mux_id, real_port, ep); - kfree(ep); + real_port->nr_rmnet_devs--; + kfree_rcu(ep, rcu); } netdev_upper_dev_unlink(real_dev, dev); @@ -238,9 +238,9 @@ static void rmnet_force_unassociate_device(struct net_device *real_dev) hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) { unregister_netdevice_queue(ep->egress_dev, &list); netdev_upper_dev_unlink(real_dev, ep->egress_dev); - rmnet_vnd_dellink(ep->mux_id, port, ep); hlist_del_init_rcu(&ep->hlnode); - kfree(ep); + port->nr_rmnet_devs--; + kfree_rcu(ep, rcu); } rmnet_unregister_real_device(real_dev); unregister_netdevice_many(&list); diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h index ed112d51ac5a7..f50fae1c6bdd5 100644 --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h @@ -18,6 +18,7 @@ struct rmnet_endpoint { u8 mux_id; struct net_device *egress_dev; struct hlist_node hlnode; + struct rcu_head rcu; }; struct rmnet_egress_agg_params { From 53483a9f4ee9eeb18aa866ec16cce79e136987e1 Mon Sep 17 00:00:00 2001 From: Mingyu Wang <25181214217@stu.xidian.edu.cn> Date: Mon, 4 May 2026 15:48:23 +0800 Subject: [PATCH 12/56] agp/amd64: Fix broken error propagation in agp_amd64_probe() commit b08472db93b1ccff84a7adec5779d47f0e9d3a30 upstream. A NULL pointer dereference was observed in the AMD64 AGP driver when running in a virtualized environment (e.g. qemu/kvm) without a physical AMD northbridge. The crash occurs in amd64_fetch_size() when attempting to dereference the pointer returned by node_to_amd_nb(0). The root cause of this crash is broken error propagation in agp_amd64_probe(): When no AMD northbridges are found, cache_nbs() correctly returns -ENODEV. However, the probe function erroneously checks the return value against exactly -1, rather than < 0. As a result, the hardware absence error is masked, allowing the driver to improperly proceed with initialization. It eventually calls agp_add_bridge(), which invokes amd64_fetch_size(). Since the hardware does not exist, node_to_amd_nb(0) returns NULL, leading to a General Protection Fault (GPF) when accessing its ->misc member. Fix the issue by correcting the error check in agp_amd64_probe() to abort properly when cache_nbs() returns any negative error code. This prevents the driver from erroneously proceeding without hardware, thereby avoiding the subsequent NULL pointer dereference at its source. Fixes: a32073bffc65 ("[PATCH] x86_64: Clean and enhance up K8 northbridge access code") Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn> Signed-off-by: Lukas Wunner Reviewed-by: Lukas Wunner Cc: stable@vger.kernel.org # v2.6.18+ Link: https://patch.msgid.link/20260504074823.99377-1-w15303746062@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/char/agp/amd64-agp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/agp/amd64-agp.c b/drivers/char/agp/amd64-agp.c index 2505df1f4e691..6741270e0a982 100644 --- a/drivers/char/agp/amd64-agp.c +++ b/drivers/char/agp/amd64-agp.c @@ -546,7 +546,7 @@ static int agp_amd64_probe(struct pci_dev *pdev, /* Fill in the mode register */ pci_read_config_dword(pdev, bridge->capndx+PCI_AGP_STATUS, &bridge->mode); - if (cache_nbs(pdev, cap_ptr) == -1) { + if (cache_nbs(pdev, cap_ptr) < 0) { agp_put_bridge(bridge); return -ENODEV; } From 19b3691ec94023aa9953376c48ac84f9aca2c7c1 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Wed, 24 Jun 2026 14:38:16 +0800 Subject: [PATCH 13/56] ACPI: scan: Use async schedule function in acpi_scan_clear_dep_fn() [ Upstream commit 7cf28b3797a81b616bb7eb3e90cf131afc452919 ] The device object rescan in acpi_scan_clear_dep_fn() is scheduled on a system workqueue which is not guaranteed to be finished before entering userspace. This may cause some key devices to be missing when userspace init task tries to find them. Two issues observed on RISCV platforms: - Kernel panic due to userspace init cannot have an opened console. The console device scanning is queued by acpi_scan_clear_dep_queue() and not finished by the time userspace init process running, thus by the time userspace init runs, no console is present. - Entering rescue shell due to the lack of root devices (PCIe nvme in our case). Same reason as above, the PCIe host bridge scanning is queued on a system workqueue and finished after init process runs. The reason is because both devices (console, PCIe host bridge) depend on riscv-aplic irqchip to serve their interrupts (console's wired interrupt and PCI's INTx interrupts). In order to keep the dependency, these devices are scanned and created after initializing riscv-aplic. The riscv-aplic is initialized in device_initcall() and a device scan work is queued via acpi_scan_clear_dep_queue(), which is close to the time userspace init process is run. Since system_dfl_wq is used in acpi_scan_clear_dep_queue() with no synchronization, the issues will happen if userspace init runs before these devices are ready. The solution is to wait for the queued work to complete before entering userspace init. One possible way would be to use a dedicated workqueue instead of system_dfl_wq, and explicitly flush it somewhere in the initcall stage before entering userspace. Another way is to use async_schedule_dev_nocall() for scanning these devices. It's designed for asynchronous initialization and will work in the same way as before because it's using a dedicated unbound workqueue as well, but the kernel init code calls async_synchronize_full() right before entering userspace init which will wait for the work to complete. Compared to a dedicated workqueue, the second approach is simpler because the async schedule framework takes care of all of the details. The ACPI code only needs to focus on its job. A dedicated workqueue for this could also be redundant because some platforms don't need acpi_scan_clear_dep_queue() for their device scanning. Signed-off-by: Yicong Yang [ rjw: Subject adjustment, changelog edits ] Link: https://patch.msgid.link/20260128132848.93638-1-yang.yicong@picoheart.com Signed-off-by: Rafael J. Wysocki [ Vivian: Adjust system_dfl_wq -> system_unbound_wq in removed lines ] Signed-off-by: Vivian Wang Signed-off-by: Sasha Levin --- drivers/acpi/scan.c | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 50f60da6cf27f..16704c2a730c0 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -5,6 +5,7 @@ #define pr_fmt(fmt) "ACPI: " fmt +#include #include #include #include @@ -2360,46 +2361,34 @@ static int acpi_dev_get_next_consumer_dev_cb(struct acpi_dep_data *dep, void *da return 0; } -struct acpi_scan_clear_dep_work { - struct work_struct work; - struct acpi_device *adev; -}; - -static void acpi_scan_clear_dep_fn(struct work_struct *work) +static void acpi_scan_clear_dep_fn(void *dev, async_cookie_t cookie) { - struct acpi_scan_clear_dep_work *cdw; - - cdw = container_of(work, struct acpi_scan_clear_dep_work, work); + struct acpi_device *adev = to_acpi_device(dev); acpi_scan_lock_acquire(); - acpi_bus_attach(cdw->adev, (void *)true); + acpi_bus_attach(adev, (void *)true); acpi_scan_lock_release(); - acpi_dev_put(cdw->adev); - kfree(cdw); + acpi_dev_put(adev); } static bool acpi_scan_clear_dep_queue(struct acpi_device *adev) { - struct acpi_scan_clear_dep_work *cdw; - if (adev->dep_unmet) return false; - cdw = kmalloc(sizeof(*cdw), GFP_KERNEL); - if (!cdw) - return false; - - cdw->adev = adev; - INIT_WORK(&cdw->work, acpi_scan_clear_dep_fn); /* - * Since the work function may block on the lock until the entire - * initial enumeration of devices is complete, put it into the unbound - * workqueue. + * Async schedule the deferred acpi_scan_clear_dep_fn() since: + * - acpi_bus_attach() needs to hold acpi_scan_lock which cannot + * be acquired under acpi_dep_list_lock (held here) + * - the deferred work at boot stage is ensured to be finished + * before userspace init task by the async_synchronize_full() + * barrier + * + * Use _nocall variant since it'll return on failure instead of + * run the function synchronously. */ - queue_work(system_unbound_wq, &cdw->work); - - return true; + return async_schedule_dev_nocall(acpi_scan_clear_dep_fn, &adev->dev); } static void acpi_scan_delete_dep_data(struct acpi_dep_data *dep) From 7dac298524b415dc26e07f603caf67ac4eaec1fb Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Sat, 16 May 2026 12:09:33 +0200 Subject: [PATCH 14/56] rose: fix dev_put() leak in rose_loopback_timer() commit ff91adc54db2b62c7cdf063ff761eceb5adf2215 upstream. 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 Signed-off-by: Greg Kroah-Hartman --- 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 b538e39b3df54..914c8f453a1d7 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 fe8cbcc3e79d458d7867009247645a50ea988191 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Sat, 16 May 2026 12:10:03 +0200 Subject: [PATCH 15/56] rose: hold loopback neighbour reference across timer callback commit d270a7a5793af84555c40dd1eb80f1d497fdf53c upstream. 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 Signed-off-by: Greg Kroah-Hartman --- 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 914c8f453a1d7..d66913df360df 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 940f39e153323589b3b55026f7b2dc221b6c44d8 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Sat, 16 May 2026 12:10:20 +0200 Subject: [PATCH 16/56] rose: fix race between loopback timer and module removal commit 47dd6ec1a77d77895afb00aa2e68373a48289108 upstream. 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 Signed-off-by: Greg Kroah-Hartman --- 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 d66913df360df..80d7879ef36a2 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 270ef709257eb65c415973b7d7f40daca363ec13 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Sat, 16 May 2026 12:10:38 +0200 Subject: [PATCH 17/56] rose: clear neighbour pointer after rose_neigh_put() in state machines commit e8eb0c6faa8849ba7769516c1a8c84d9f612acf6 upstream. 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 Signed-off-by: Greg Kroah-Hartman --- 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 0276b393f0e53..622527f1354f8 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: @@ -122,6 +125,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: @@ -235,6 +239,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: @@ -255,6 +260,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 1d94857c11d607ff68496866c5c90604f1aa1d8a Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Sat, 16 May 2026 12:10:55 +0200 Subject: [PATCH 18/56] rose: guard rose_neigh_put() against NULL in timer expiry commit 2b67342c6ff899a0b83359517146a5b7b243af97 upstream. 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 Signed-off-by: Greg Kroah-Hartman --- 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 bb60a1654d612..d997d24ab0818 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; From 19139026dc1c0a958798fe7b1c26f4cd479b706e Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Tue, 26 May 2026 15:57:04 +0200 Subject: [PATCH 19/56] rose: fix netdev double-hold in rose_rx_call_request() commit c675277c3ba0d2310e0825577d58308c39931e14 upstream. rose_rx_call_request() used netdev_tracker_alloc() after assigning make_rose->device, intending to take ownership of the reference passed by the caller. But every caller -- rose_route_frame() and rose_loopback_timer() -- already calls dev_put() for its own hold after the function returns, so the socket ended up with a tracker entry pointing at a reference that had already been released. The result was spurious refcount_t warnings ("saturated", "decrement hit 0") on every incoming CALL_REQUEST, leading to refcount corruption and eventual silent freeze. Replace netdev_tracker_alloc() with netdev_hold() so that rose_rx_call_request() acquires its own independent reference. Each caller retains its own hold from rose_dev_get() and releases it via dev_put() as before; socket cleanup releases the socket's separate hold via netdev_put(). Signed-off-by: Bernard Pidoux Signed-off-by: Greg Kroah-Hartman --- net/rose/af_rose.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/rose/af_rose.c b/net/rose/af_rose.c index 53c9bc71f813d..8160b800ea3b7 100644 --- a/net/rose/af_rose.c +++ b/net/rose/af_rose.c @@ -1078,9 +1078,11 @@ int rose_rx_call_request(struct sk_buff *skb, struct net_device *dev, struct ros make_rose->source_digis[n] = facilities.source_digis[n]; make_rose->neighbour = neigh; make_rose->device = dev; - /* Caller got a reference for us. */ - netdev_tracker_alloc(make_rose->device, &make_rose->dev_tracker, - GFP_ATOMIC); + /* Take an independent reference for this socket; callers keep their + * own reference (from rose_dev_get / dev_hold) and will release it + * themselves via dev_put(). + */ + netdev_hold(make_rose->device, &make_rose->dev_tracker, GFP_ATOMIC); make_rose->facilities = facilities; rose_neigh_hold(make_rose->neighbour); From c98cc00c2d3b17d849e0ba806e5740c7e0b6b138 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Tue, 26 May 2026 15:57:47 +0200 Subject: [PATCH 20/56] rose: fix notifier unregistered too early in rose_exit() commit f71a8a1edc14dba746edde38adddd654ba202b4d upstream. rose_exit() called unregister_netdevice_notifier() before the loop that calls unregister_netdev() on each ROSE virtual device. As a result, the NETDEV_DOWN event fired by unregister_netdev() was never delivered to rose_device_event(), so rose_kill_by_device() never ran. Every socket whose rose->device pointed at a ROSE device therefore kept its netdev_tracker entry live until free_netdev() destroyed the ref_tracker_dir, at which point the kernel reported all of them as leaked references (165 entries in a typical FPAC setup). Worse, those sockets retained stale device pointers and live timers that could fire into freed module text after module unload, causing a silent system freeze with no kernel panic logged. Fix by moving unregister_netdevice_notifier() to after the device- unregistration loop. unregister_netdev() then delivers NETDEV_DOWN while the notifier is still registered, rose_kill_by_device() runs for each device, releases all netdev references held by open sockets, and calls rose_disconnect() which stops the per-socket timers. Signed-off-by: Bernard Pidoux Signed-off-by: Greg Kroah-Hartman --- net/rose/af_rose.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/net/rose/af_rose.c b/net/rose/af_rose.c index 8160b800ea3b7..38e46757c1798 100644 --- a/net/rose/af_rose.c +++ b/net/rose/af_rose.c @@ -1669,19 +1669,28 @@ static void __exit rose_exit(void) #ifdef CONFIG_SYSCTL rose_unregister_sysctl(); #endif - unregister_netdevice_notifier(&rose_dev_notifier); - sock_unregister(PF_ROSE); for (i = 0; i < rose_ndevs; i++) { struct net_device *dev = dev_rose[i]; if (dev) { + /* unregister_netdev() fires NETDEV_DOWN, which -- while the + * notifier is still registered below -- invokes + * rose_kill_by_device(dev). That releases every socket's + * netdev reference and disconnects all active circuits. + * Unregistering the notifier before this loop was the + * original bug: NETDEV_DOWN was never delivered, leaving + * 165 netdev_tracker entries leaked and stale timers live. + */ unregister_netdev(dev); free_netdev(dev); } } + /* Now safe to remove the notifier -- all ROSE devices are gone. */ + unregister_netdevice_notifier(&rose_dev_notifier); + kfree(dev_rose); proto_unregister(&rose_proto); } From ab849a6972c99e8a83ca92fbe463d782118e39f8 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Wed, 27 May 2026 14:11:21 +0200 Subject: [PATCH 21/56] rose: set SOCK_DESTROY in rose_kill_by_device() for prompt cleanup commit 741a4863ad570889c75f7a8e404567d8f3e46335 upstream. When rose_kill_by_device() is called (via NETDEV_DOWN on module exit or interface removal), it calls rose_disconnect() which transitions sockets to ROSE_STATE_0 and sets SOCK_DEAD. However, rose_heartbeat_expiry() only calls rose_destroy_socket() at ROSE_STATE_0 if SOCK_DESTROY is set -- the SOCK_DEAD path is reserved for TCP_LISTEN sockets. Without SOCK_DESTROY, orphaned sockets in ROSE_STATE_2 (clearing) loop indefinitely in the heartbeat without ever being freed, keeping the module use-count elevated and blocking modprobe -r rose until the T1 timer (up to 200 s) expires. Set SOCK_DESTROY immediately after rose_disconnect() so the heartbeat destroys the socket at its next tick (within 5 s), allowing clean module unload. Signed-off-by: Bernard Pidoux Signed-off-by: Greg Kroah-Hartman --- net/rose/af_rose.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/rose/af_rose.c b/net/rose/af_rose.c index 38e46757c1798..e2defdc69ea8b 100644 --- a/net/rose/af_rose.c +++ b/net/rose/af_rose.c @@ -211,6 +211,11 @@ static void rose_kill_by_device(struct net_device *dev) spin_lock_bh(&rose_list_lock); if (rose->device == dev) { rose_disconnect(sk, ENETUNREACH, ROSE_OUT_OF_ORDER, 0); + /* Mark for destruction so rose_heartbeat_expiry() + * cleans up the socket at its next tick rather than + * looping forever in ROSE_STATE_0 with no owner. + */ + sock_set_flag(sk, SOCK_DESTROY); if (rose->neighbour) rose_neigh_put(rose->neighbour); netdev_put(rose->device, &rose->dev_tracker); From ce27bcdd857a98c9069703a209cbe7fc4c2587ba Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Thu, 28 May 2026 17:38:18 +0200 Subject: [PATCH 22/56] rose: disconnect orphaned STATE_2 sockets when device is gone commit d4f4cf9f09a3f5fafa8f09110a7c1b5d10f2f261 upstream. When ax25stop brings down ROSE interfaces, sockets in ROSE_STATE_2 (awaiting CLEAR CONFIRM) whose device pointer is already NULL are not reached by rose_kill_by_device() and wait for T3 (up to 180s) before self-cleaning via rose_timer_expiry(). This keeps the rose module usecount at 1, blocking rmmod for the full T3 duration. In rose_heartbeat_expiry(), detect ROSE_STATE_2 sockets with no device, cancel T3, release the neighbour reference, and call rose_disconnect() + sock_set_flag(SOCK_DESTROY). The next heartbeat tick (<=5s) then destroys the socket via the existing ROSE_STATE_0/SOCK_DESTROY path, allowing clean module unload within 10s instead of up to 180s. Signed-off-by: Bernard Pidoux Signed-off-by: Greg Kroah-Hartman --- net/rose/rose_timer.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/net/rose/rose_timer.c b/net/rose/rose_timer.c index d997d24ab0818..aa3dd4ab4a49a 100644 --- a/net/rose/rose_timer.c +++ b/net/rose/rose_timer.c @@ -139,6 +139,20 @@ static void rose_heartbeat_expiry(struct timer_list *t) } break; + case ROSE_STATE_2: + /* Device gone before CLEAR CONFIRM arrived: stop waiting for T3 + * and disconnect now instead of blocking rmmod for up to 180s. */ + if (!rose->device) { + rose_stop_timer(sk); + if (rose->neighbour) { + rose_neigh_put(rose->neighbour); + rose->neighbour = NULL; + } + rose_disconnect(sk, ENETDOWN, -1, -1); + sock_set_flag(sk, SOCK_DESTROY); + } + break; + case ROSE_STATE_3: /* * Check for the state of the receive buffer. From c794d35f73a7bc3d77224ccc6395c26f49f51752 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Thu, 28 May 2026 19:11:55 +0200 Subject: [PATCH 23/56] rose: fix netdev double-hold in rose_make_new() commit b9fb21ceb4f0d043767a1eba60786ec84809033b upstream. rose_make_new() copies orose->device from the listener socket and calls netdev_hold(), storing the tracker in rose->dev_tracker. The only caller, rose_rx_call_request(), then overwrites both make_rose->device and make_rose->dev_tracker with a fresh netdev_hold() for the actual incoming-call device. This orphans the tracker allocated by rose_make_new(): it remains in the device's refcount_tracker list but no pointer exists to free it via netdev_put(). The result is one spurious outstanding reference per accepted CALL_REQUEST, visible at rmmod time as: ref_tracker: netdev@X has 2/2 users at rose_rx_call_request+0xba3/0x1d50 [rose] rose_loopback_timer+0x3eb/0x670 [rose] The second entry is the orphaned tracker from rose_make_new(); the first is the correctly-managed socket reference from rose_rx_call_request(). Fix: initialise rose->device to NULL in rose_make_new() and let rose_rx_call_request() -- the sole caller -- assign the correct device and take the sole netdev_hold() as it already does. Signed-off-by: Bernard Pidoux Signed-off-by: Greg Kroah-Hartman --- net/rose/af_rose.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/rose/af_rose.c b/net/rose/af_rose.c index e2defdc69ea8b..15ccbc558f972 100644 --- a/net/rose/af_rose.c +++ b/net/rose/af_rose.c @@ -631,9 +631,7 @@ static struct sock *rose_make_new(struct sock *osk) rose->hb = orose->hb; rose->idle = orose->idle; rose->defer = orose->defer; - rose->device = orose->device; - if (rose->device) - netdev_hold(rose->device, &rose->dev_tracker, GFP_ATOMIC); + rose->device = NULL; /* rose_rx_call_request() sets this */ rose->qbitincl = orose->qbitincl; return sk; From 74cbe94c913a4a3e069c0a82d8185586f0c37976 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Thu, 28 May 2026 19:38:31 +0200 Subject: [PATCH 24/56] rose: release netdev ref and destroy orphaned incoming sockets commit df12be096302d2c947388acc25764456c7f18cc1 upstream. Two related cleanup gaps left the module unremovable after a loopback session: 1. rose_destroy_socket() did not release the device reference. When an unaccepted incoming socket (created by rose_rx_call_request()) is destroyed via rose_heartbeat_expiry(), it is removed from rose_list before rose_kill_by_device() can find it, so the netdev_hold() taken in rose_rx_call_request() was never matched by netdev_put(). Add the release at the top of rose_destroy_socket() guarded by a NULL check so that rose_release() and rose_kill_by_device(), which already call netdev_put() and set device = NULL, are not affected. 2. rose_heartbeat_expiry() STATE_0 cleanup required TCP_LISTEN in addition to SOCK_DEAD. Unaccepted incoming sockets are TCP_ESTABLISHED, so the condition was never true and those sockets lingered forever, holding the module use count above zero and blocking rmmod. Drop the TCP_LISTEN restriction: any STATE_0 + SOCK_DEAD socket is orphaned and should be destroyed. Together with the earlier rose_make_new() double-hold fix these three patches allow clean rmmod after loopback sessions. Signed-off-by: Bernard Pidoux Signed-off-by: Greg Kroah-Hartman --- net/rose/af_rose.c | 9 +++++++++ net/rose/rose_timer.c | 9 +++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/net/rose/af_rose.c b/net/rose/af_rose.c index 15ccbc558f972..8424e8e4014bf 100644 --- a/net/rose/af_rose.c +++ b/net/rose/af_rose.c @@ -363,6 +363,7 @@ static void rose_destroy_timer(struct timer_list *t) */ void rose_destroy_socket(struct sock *sk) { + struct rose_sock *rose = rose_sk(sk); struct sk_buff *skb; rose_remove_socket(sk); @@ -370,6 +371,14 @@ void rose_destroy_socket(struct sock *sk) rose_stop_idletimer(sk); rose_stop_timer(sk); + /* Drop any device reference not already released by rose_kill_by_device() + * or rose_release() -- e.g. incoming sockets that were never accepted. + */ + if (rose->device) { + netdev_put(rose->device, &rose->dev_tracker); + rose->device = NULL; + } + rose_clear_queues(sk); /* Flush the queues */ while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) { diff --git a/net/rose/rose_timer.c b/net/rose/rose_timer.c index aa3dd4ab4a49a..76204b8250d04 100644 --- a/net/rose/rose_timer.c +++ b/net/rose/rose_timer.c @@ -128,10 +128,11 @@ static void rose_heartbeat_expiry(struct timer_list *t) } switch (rose->state) { case ROSE_STATE_0: - /* Magic here: If we listen() and a new link dies before it - is accepted() it isn't 'dead' so doesn't get removed. */ - if (sock_flag(sk, SOCK_DESTROY) || - (sk->sk_state == TCP_LISTEN && sock_flag(sk, SOCK_DEAD))) { + /* 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)) { bh_unlock_sock(sk); rose_destroy_socket(sk); sock_put(sk); From c31a0fa15a4b4c07130ca5bd1ac222b2541ab011 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Thu, 28 May 2026 20:20:55 +0200 Subject: [PATCH 25/56] rose: drop CALL_REQUEST in loopback timer when device is not running commit cf5567a2652e44866eae8987dff4c1ea507680df upstream. When ax25stop brings down rose0 while the loopback timer has pending CALL_REQUEST frames, rose_loopback_timer() calls rose_dev_get() and finds the device still registered (unregister_netdevice waits for refs to drop), then calls rose_rx_call_request() which takes a netdev_hold() for the new socket. But NETDEV_DOWN fires only once: rose_kill_by_device() already ran before this timer tick, so the new socket is never cleaned up. The stuck reference prevents unregister_netdevice from completing, and the orphan socket's timers eventually fire on freed memory (KASAN slab-use-after-free in __run_timers). The kernel clears IFF_UP via dev_close() before sending NETDEV_DOWN, so checking netif_running() after rose_dev_get() is sufficient: if the device is no longer running, the CALL_REQUEST is silently dropped and no socket is created. This closes the race without touching the module-exit path (which already stops the timer via loopback_stopping). Tested: unregister_netdevice completes immediately after ax25stop with active loopback connections; no ref_tracker warnings, no KASAN. Signed-off-by: Bernard Pidoux Signed-off-by: Greg Kroah-Hartman --- net/rose/rose_loopback.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/net/rose/rose_loopback.c b/net/rose/rose_loopback.c index 80d7879ef36a2..fd4e4795776a3 100644 --- a/net/rose/rose_loopback.c +++ b/net/rose/rose_loopback.c @@ -118,6 +118,16 @@ static void rose_loopback_timer(struct timer_list *unused) kfree_skb(skb); continue; } + /* rose_kill_by_device() runs on NETDEV_DOWN (IFF_UP cleared) + * before the device is unregistered. If we create a new + * socket here after that cleanup, the ref never gets released + * because NETDEV_DOWN fires only once. Drop the call instead. + */ + if (!netif_running(dev)) { + dev_put(dev); + kfree_skb(skb); + continue; + } if (rose_rx_call_request(skb, dev, rose_loopback_neigh, lci_o) == 0) kfree_skb(skb); From 9e8fc2195f8b5a79af184a4dc3d22c84395a8ce9 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Sun, 31 May 2026 15:41:45 +0200 Subject: [PATCH 26/56] rose: cancel neighbour timers in rose_neigh_put() before freeing commit 9b222cb1d23ff210975e9df5ebab7b011acb6fad upstream. rose_neigh_put() kfree()s the neighbour but never cancels its ftimer and t0timer. Until now every caller that dropped the final reference first called rose_remove_neigh(), which deletes those timers. The socket heartbeat reaping path drops the last reference directly, so a neighbour could be freed with t0timer still armed -- it re-arms itself in rose_t0timer_expiry() -- leading to a use-after-free write in enqueue_timer(). Cancel both timers with timer_delete_sync() (the synchronous variant, to wait out a concurrently running, self-rearming handler) in the refcount-zero branch of rose_neigh_put(). Signed-off-by: Bernard Pidoux Signed-off-by: Greg Kroah-Hartman --- include/net/rose.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/net/rose.h b/include/net/rose.h index 2b5491bbf39ab..95d1f9c582dcc 100644 --- a/include/net/rose.h +++ b/include/net/rose.h @@ -160,6 +160,18 @@ static inline void rose_neigh_hold(struct rose_neigh *rose_neigh) static inline void rose_neigh_put(struct rose_neigh *rose_neigh) { if (refcount_dec_and_test(&rose_neigh->use)) { + /* We are dropping the last reference, so we are about to free the + * neighbour. Its timers may still be armed -- t0timer in particular + * re-arms itself in rose_t0timer_expiry(). rose_remove_neigh() + * cancels them before its own put, but callers that drop the final + * reference without first calling rose_remove_neigh() (the socket + * heartbeat reaping path) would otherwise kfree() a neighbour with a + * live timer -> use-after-free. timer_delete_sync() (not the async + * variant) is required: it waits out a concurrently running handler + * and loops until the self-rearming timer stays stopped. + */ + timer_delete_sync(&rose_neigh->ftimer); + timer_delete_sync(&rose_neigh->t0timer); if (rose_neigh->ax25) ax25_cb_put(rose_neigh->ax25); kfree(rose_neigh->digipeat); From 395b6573b389f44473b68285a36020a37a4025c2 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Sun, 31 May 2026 15:41:45 +0200 Subject: [PATCH 27/56] rose: clear neighbour pointer in rose_kill_by_device() commit 606e42d195b467480d4d405f8814c48d1651a76a upstream. rose_kill_by_device() drops the neighbour reference but leaves rose->neighbour pointing at it, unlike every other rose_neigh_put() site (see "rose: clear neighbour pointer after rose_neigh_put() in state machines"). The heartbeat STATE_0 reaping path then puts the same neighbour a second time, causing a rose_neigh refcount underflow and a use-after-free. Set rose->neighbour = NULL after the put, restoring the invariant. Signed-off-by: Bernard Pidoux Signed-off-by: Greg Kroah-Hartman --- net/rose/af_rose.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/rose/af_rose.c b/net/rose/af_rose.c index 8424e8e4014bf..38bfe60665c0f 100644 --- a/net/rose/af_rose.c +++ b/net/rose/af_rose.c @@ -216,8 +216,16 @@ static void rose_kill_by_device(struct net_device *dev) * looping forever in ROSE_STATE_0 with no owner. */ sock_set_flag(sk, SOCK_DESTROY); - if (rose->neighbour) + if (rose->neighbour) { rose_neigh_put(rose->neighbour); + /* Clear the pointer after dropping the reference, as + * every other rose_neigh_put() site does. Otherwise + * rose_heartbeat_expiry() (STATE_0 reaping) sees a stale + * rose->neighbour and puts it a second time -> rose_neigh + * refcount underflow / use-after-free. + */ + rose->neighbour = NULL; + } netdev_put(rose->device, &rose->dev_tracker); rose->device = NULL; } From 9477cbc5107a8dc31d54f199338449358621d569 Mon Sep 17 00:00:00 2001 From: Bernard Pidoux Date: Sun, 31 May 2026 15:41:45 +0200 Subject: [PATCH 28/56] 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 Signed-off-by: Greg Kroah-Hartman --- net/rose/rose_timer.c | 57 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/net/rose/rose_timer.c b/net/rose/rose_timer.c index 76204b8250d04..d967cbc1db3db 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); From bde74af8d4466213007bdd42cc85fa72c861dea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Fri, 9 Jan 2026 08:38:38 +0000 Subject: [PATCH 29/56] regulator: core: fix locking in regulator_resolve_supply() error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 497330b203d2c59c5ff3fa4c34d14494d7203bc3 upstream. If late enabling of a supply regulator fails in regulator_resolve_supply(), the code currently triggers a lockdep warning: WARNING: drivers/regulator/core.c:2649 at _regulator_put+0x80/0xa0, CPU#6: kworker/u32:4/596 ... Call trace: _regulator_put+0x80/0xa0 (P) regulator_resolve_supply+0x7cc/0xbe0 regulator_register_resolve_supply+0x28/0xb8 as the regulator_list_mutex must be held when calling _regulator_put(). To solve this, simply switch to using regulator_put(). While at it, we should also make sure that no concurrent access happens to our rdev while we clear out the supply pointer. Add appropriate locking to ensure that. While the code in question will be removed altogether in a follow-up commit, I believe it is still beneficial to have this corrected before removal for future reference. Fixes: 36a1f1b6ddc6 ("regulator: core: Fix memory leak in regulator_resolve_supply()") Fixes: 8e5356a73604 ("regulator: core: Clear the supply pointer if enabling fails") Signed-off-by: André Draszik Link: https://patch.msgid.link/20260109-regulators-defer-v2-2-1a25dc968e60@linaro.org Signed-off-by: Mark Brown Signed-off-by: Nazar Kalashnikov Signed-off-by: Greg Kroah-Hartman --- drivers/regulator/core.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 765bd1b5deb3a..761a3f9058593 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -2159,8 +2159,16 @@ static int regulator_resolve_supply(struct regulator_dev *rdev) if (rdev->use_count) { ret = regulator_enable(rdev->supply); if (ret < 0) { - _regulator_put(rdev->supply); + struct regulator *supply; + + regulator_lock_two(rdev, rdev->supply->rdev, &ww_ctx); + + supply = rdev->supply; rdev->supply = NULL; + + regulator_unlock_two(rdev, supply->rdev, &ww_ctx); + + regulator_put(supply); goto out; } } From 072bbd2846d1b7f1c9c95d10ae142b9b786a60e9 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 16 Jun 2026 12:55:57 -0400 Subject: [PATCH 30/56] hv: utils: handle and propagate errors in kvp_register [ Upstream commit 3fcf923302a8f5c0dc3af3d2ca2657cb5fae4297 ] Make kvp_register() return an error code instead of silently ignoring failures, and propagate the error from kvp_handle_handshake() instead of returning success. This propagates both kzalloc_obj() and hvutil_transport_send() failures to kvp_handle_handshake() and thus to kvp_on_msg(). Fixes: 245ba56a52a3 ("Staging: hv: Implement key/value pair (KVP)") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Reviewed-by: Long Li Signed-off-by: Wei Liu Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_kvp.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c index 62795f6cbb001..8b409a3d41f04 100644 --- a/drivers/hv/hv_kvp.c +++ b/drivers/hv/hv_kvp.c @@ -93,7 +93,7 @@ static void kvp_send_key(struct work_struct *dummy); static void kvp_respond_to_host(struct hv_kvp_msg *msg, int error); static void kvp_timeout_func(struct work_struct *dummy); static void kvp_host_handshake_func(struct work_struct *dummy); -static void kvp_register(int); +static int kvp_register(int); static DECLARE_DELAYED_WORK(kvp_timeout_work, kvp_timeout_func); static DECLARE_DELAYED_WORK(kvp_host_handshake_work, kvp_host_handshake_func); @@ -127,24 +127,26 @@ static void kvp_register_done(void) hv_poll_channel(kvp_transaction.recv_channel, kvp_poll_wrapper); } -static void +static int kvp_register(int reg_value) { struct hv_kvp_msg *kvp_msg; char *version; + int ret; kvp_msg = kzalloc(sizeof(*kvp_msg), GFP_KERNEL); + if (!kvp_msg) + return -ENOMEM; - if (kvp_msg) { - version = kvp_msg->body.kvp_register.version; - kvp_msg->kvp_hdr.operation = reg_value; - strcpy(version, HV_DRV_VERSION); + version = kvp_msg->body.kvp_register.version; + kvp_msg->kvp_hdr.operation = reg_value; + strcpy(version, HV_DRV_VERSION); - hvutil_transport_send(hvt, kvp_msg, sizeof(*kvp_msg), - kvp_register_done); - kfree(kvp_msg); - } + ret = hvutil_transport_send(hvt, kvp_msg, sizeof(*kvp_msg), + kvp_register_done); + kfree(kvp_msg); + return ret; } static void kvp_timeout_func(struct work_struct *dummy) @@ -186,9 +188,8 @@ static int kvp_handle_handshake(struct hv_kvp_msg *msg) */ pr_debug("KVP: userspace daemon ver. %d connected\n", msg->kvp_hdr.operation); - kvp_register(dm_reg_value); - return 0; + return kvp_register(dm_reg_value); } From 02ac3ba41628ab0c454b7d05b4bc20c011b32783 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Tue, 16 Jun 2026 12:56:09 -0400 Subject: [PATCH 31/56] Drivers: hv: vmbus: Improve the logic of reserving fb_mmio on Gen2 VMs [ Upstream commit 016a25e4b0df4d77e7c258edee4aaf982e4ee809 ] If vmbus_reserve_fb() in the kdump/kexec kernel fails to properly reserve the framebuffer MMIO range (which is below 4GB) due to a Gen2 VM's screen.lfb_base being zero [1], there is an MMIO conflict between the drivers hyperv-drm and pci-hyperv: when the driver pci-hyperv's hv_allocate_config_window() calls vmbus_allocate_mmio() to get an MMIO range, typically it gets a 32-bit MMIO range that overlaps with the framebuffer MMIO range, and later hv_pci_enter_d0() fails with an error message "PCI Pass-through VSP failed D0 Entry with status" since the host thinks that PCI devices must not use MMIO space that the host has assigned to the framebuffer. This is especially an issue if pci-hyperv is built-in and hyperv-drm is built as a module. Consequently, the kdump/kexec kernel fails to detect PCI devices via pci-hyperv, and may fail to mount the root file system, which may reside in a NVMe disk. The issue described here has existed for SR-IOV VF NICs since day one of the pci-hyperv driver, and has been worked around on x64 when possible. With the recent introduction of ARM64 VMs that boot from NVMe, there is no workaround, so we need a formal fix. On Gen2 VMs, if the screen.lfb_base is 0 in the kdump/kexec kernel [1], fall back to the low MMIO base, which should be equal to the framebuffer MMIO base [2] (the statement is true according to my testing on x64 Windows Server 2016, and on x64 and ARM64 Windows Server 2025 and on Azure. I checked with the Hyper-V team and they said the statement should continue to be true for Gen2 VMs). In the first kernel, screen.lfb_base is not 0; if the user specifies a very high resolution, it's not enough to only reserve 8MB: let's always reserve half of the space below 4GB, but cap the reservation to 128MB, which is the required framebuffer size of the highest resolution 7680*4320 supported by Hyper-V. While at it, fix the comparison "end > VTPM_BASE_ADDRESS" by changing the > to >=. Here the 'end' is an inclusive end (typically, it's 0xFFFF_FFFF for the low MMIO range). Note: vmbus_reserve_fb() now also reserves an MMIO range at the beginning of the low MMIO range on CVMs, which have no framebuffers (the 'screen.lfb_base' in vmbus_reserve_fb() is 0 for CVMs), just in case the host might treat the beginning of the low MMIO range specially [3]. BTW, the OpenHCL kernel is not affected by the change, because that kernel boots with DeviceTree rather than ACPI (so vmbus_reserve_fb() won't run there), and there is no framebuffer device for that kernel. Note: normally Gen1 VMs don't have the MMIO conflict issue because the framebuffer MMIO range (which is hardcoded to base=4GB-128MB and size=64MB for Gen1 VMs by the host) is always reported via the legacy PCI graphics device's BAR, so the kdump/kexec kernel can reserve the 64MB MMIO range; however, if the VM is configured to use a very high resolution and the required framebuffer size exceeds 64MB (AFAIK, in practice, this isn't a typical configuration by users), the hyperv-drm driver may need to allocate an MMIO range above 4GB and change the framebuffer MMIO location to the allocated MMIO range -- in this case, there can still be issues [4] which can't be easily fixed: any possible affected Gen1 users would have to use a resolution whose framebuffer size is <= 64MB, or switch to Gen2 VMs. [1] https://lore.kernel.org/all/SA1PR21MB692176C1BC53BFC9EAE5CF8EBF51A@SA1PR21MB6921.namprd21.prod.outlook.com/ [2] https://lore.kernel.org/all/SA1PR21MB69218F955B62DFF62E3E88D2BF222@SA1PR21MB6921.namprd21.prod.outlook.com/ [3] https://lore.kernel.org/all/SN6PR02MB415726B17D5A6027CD1717E8D4342@SN6PR02MB4157.namprd02.prod.outlook.com/ [4] https://lore.kernel.org/all/SA1PR21MB69213486F821CA5A2C793C81BF342@SA1PR21MB6921.namprd21.prod.outlook.com/ Fixes: 4daace0d8ce8 ("PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs") CC: stable@vger.kernel.org Reviewed-by: Michael Kelley Tested-by: Krister Johansen Tested-by: Matthew Ruffell Signed-off-by: Dexuan Cui Signed-off-by: Wei Liu Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/hv/vmbus_drv.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 8a090e2a28f92..137cf82e2292e 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -2291,8 +2291,8 @@ static acpi_status vmbus_walk_resources(struct acpi_resource *res, void *ctx) return AE_NO_MEMORY; /* If this range overlaps the virtual TPM, truncate it. */ - if (end > VTPM_BASE_ADDRESS && start < VTPM_BASE_ADDRESS) - end = VTPM_BASE_ADDRESS; + if (end >= VTPM_BASE_ADDRESS && start < VTPM_BASE_ADDRESS) + end = VTPM_BASE_ADDRESS - 1; new_res->name = "hyperv mmio"; new_res->flags = IORESOURCE_MEM; @@ -2359,6 +2359,7 @@ static void vmbus_mmio_remove(void) static void __maybe_unused vmbus_reserve_fb(void) { resource_size_t start = 0, size; + resource_size_t low_mmio_base; struct pci_dev *pdev; if (efi_enabled(EFI_BOOT)) { @@ -2366,6 +2367,24 @@ static void __maybe_unused vmbus_reserve_fb(void) if (IS_ENABLED(CONFIG_SYSFB)) { start = screen_info.lfb_base; size = max_t(__u32, screen_info.lfb_size, 0x800000); + + low_mmio_base = hyperv_mmio->start; + if (!low_mmio_base || upper_32_bits(low_mmio_base) || + (start && start < low_mmio_base)) { + pr_warn("Unexpected low mmio base %pa\n", &low_mmio_base); + } else { + /* + * If the kdump/kexec or CVM kernel's lfb_base + * is 0, fall back to the low mmio base. + */ + if (!start) + start = low_mmio_base; + /* + * Reserve half of the space below 4GB for high + * resolutions, but cap the reservation to 128MB. + */ + size = min((SZ_4G - start) / 2, SZ_128M); + } } } else { /* Gen1 VM: get FB base from PCI */ @@ -2386,8 +2405,10 @@ static void __maybe_unused vmbus_reserve_fb(void) pci_dev_put(pdev); } - if (!start) + if (!start) { + pr_warn("Unexpected framebuffer mmio base of zero\n"); return; + } /* * Make a claim for the frame buffer in the resource tree under the @@ -2397,6 +2418,8 @@ static void __maybe_unused vmbus_reserve_fb(void) */ for (; !fb_mmio && (size >= 0x100000); size >>= 1) fb_mmio = __request_region(hyperv_mmio, start, size, fb_mmio_name, 0); + + pr_info("hv_mmio=%pR,%pR fb=%pR\n", hyperv_mmio, hyperv_mmio->sibling, fb_mmio); } /** From eee6be6ab63755f7847ce22288f7d722fda419d1 Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Tue, 16 Jun 2026 21:47:19 -0400 Subject: [PATCH 32/56] firmware: samsung: acpm: Fix cross-thread RX length corruption [ Upstream commit f133bd4b5daf71bccdde0ad1a4f47fac76a6bfb1 ] Sashiko identified a cross-thread RX length corruption bug when reviewing the thermal addition to ACPM [1]. When multiple threads concurrently send IPC requests, the ACPM polling mechanism can encounter responses belonging to other threads. To drain the queue, the driver saves these concurrent responses into an internal cache (`rx_data->cmd`) to be retrieved later by the owning thread. Previously, the driver incorrectly used `xfer->rxcnt` (the expected receive length of the *current* polling thread) when copying data for *other* threads into this cache. If the threads expected responses of different lengths, this resulted in buffer underflows (leading to reads of uninitialized memory) or potential buffer overflows. Fix this by replacing the boolean `response` flag in `struct acpm_rx_data` with `rxcnt`, caching the exact expected receive length for each specific transaction during transfer preparation. Use this cached length when saving concurrent responses. Consequently, ensure that `xfer->rxcnt` is explicitly zeroed in driver helpers (e.g., `acpm_dvfs_set_xfer`) for fire-and-forget messages to prevent uninitialized stack garbage from being interpreted as a massive expected receive length. Cc: stable@vger.kernel.org Fixes: a88927b534ba ("firmware: add Exynos ACPM protocol driver") Closes: https://sashiko.dev/#/patchset/20260420-acpm-tmu-v3-0-3dc8e93f0b26%40linaro.org [1] Reported-by: Titouan Ameline de Cadeville Closes: https://lore.kernel.org/r/20260426210255.73674-1-titouan.ameline@gmail.com/ Signed-off-by: Tudor Ambarus Link: https://patch.msgid.link/20260505-acpm-fixes-sashiko-reports-v5-1-43b5ee7f1674@linaro.org Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/samsung/exynos-acpm.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index d3bd4bafe2811..b19f9a91a5ec7 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -103,12 +103,12 @@ struct acpm_queue { * * @cmd: pointer to where the data shall be saved. * @n_cmd: number of 32-bit commands. - * @response: true if the client expects the RX data. + * @rxcnt: expected length of the response in 32-bit words. */ struct acpm_rx_data { u32 *cmd; size_t n_cmd; - bool response; + size_t rxcnt; }; #define ACPM_SEQNUM_MAX 64 @@ -196,7 +196,7 @@ static void acpm_get_saved_rx(struct acpm_chan *achan, const struct acpm_rx_data *rx_data = &achan->rx_data[tx_seqnum - 1]; u32 rx_seqnum; - if (!rx_data->response) + if (!rx_data->rxcnt) return; rx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, rx_data->cmd[0]); @@ -253,7 +253,7 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) seqnum = rx_seqnum - 1; rx_data = &achan->rx_data[seqnum]; - if (rx_data->response) { + if (rx_data->rxcnt) { if (rx_seqnum == tx_seqnum) { __ioread32_copy(xfer->rxd, addr, xfer->rxlen / 4); @@ -267,7 +267,7 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) * after the response is copied to the request. */ __ioread32_copy(rx_data->cmd, addr, - xfer->rxlen / 4); + rx_data->rxcnt); } } else { clear_bit(seqnum, achan->bitmap_seqnum); @@ -379,8 +379,8 @@ static void acpm_prepare_xfer(struct acpm_chan *achan, /* Clear data for upcoming responses */ rx_data = &achan->rx_data[achan->seqnum - 1]; memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd); - if (xfer->rxd) - rx_data->response = true; + /* zero means no response expected */ + rx_data->rxcnt = xfer->rxlen / 4; /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */ set_bit(achan->seqnum - 1, achan->bitmap_seqnum); From 0de7db2eb27e82b983157016fa604b1ba664ae5f Mon Sep 17 00:00:00 2001 From: Xin Long Date: Thu, 25 Jun 2026 10:43:46 +0300 Subject: [PATCH 33/56] sctp: disable BH before calling udp_tunnel_xmit_skb() commit 2cd7e6971fc2787408ceef17906ea152791448cf upstream. udp_tunnel_xmit_skb() / udp_tunnel6_xmit_skb() are expected to run with BH disabled. After commit 6f1a9140ecda ("add xmit recursion limit to tunnel xmit functions"), on the path: udp(6)_tunnel_xmit_skb() -> ip(6)tunnel_xmit() dev_xmit_recursion_inc()/dec() must stay balanced on the same CPU. Without local_bh_disable(), the context may move between CPUs, which can break the inc/dec pairing. This may lead to incorrect recursion level detection and cause packets to be dropped in ip(6)_tunnel_xmit() or __dev_queue_xmit(). Fix it by disabling BH around both IPv4 and IPv6 SCTP UDP xmit paths. In my testing, after enabling the SCTP over UDP: # ip net exec ha sysctl -w net.sctp.udp_port=9899 # ip net exec ha sysctl -w net.sctp.encap_port=9899 # ip net exec hb sysctl -w net.sctp.udp_port=9899 # ip net exec hb sysctl -w net.sctp.encap_port=9899 # ip net exec ha iperf3 -s - without this patch: # ip net exec hb iperf3 -c 192.168.0.1 --sctp [ 5] 0.00-10.00 sec 37.2 MBytes 31.2 Mbits/sec sender [ 5] 0.00-10.00 sec 37.1 MBytes 31.1 Mbits/sec receiver - with this patch: # ip net exec hb iperf3 -c 192.168.0.1 --sctp [ 5] 0.00-10.00 sec 3.14 GBytes 2.69 Gbits/sec sender [ 5] 0.00-10.00 sec 3.14 GBytes 2.69 Gbits/sec receiver Fixes: 6f1a9140ecda ("net: add xmit recursion limit to tunnel xmit functions") Fixes: 046c052b475e ("sctp: enable udp tunneling socks") Signed-off-by: Xin Long Acked-by: Marcelo Ricardo Leitner Link: https://patch.msgid.link/c874a8548221dcd56ff03c65ba75a74e6cf99119.1776017727.git.lucien.xin@gmail.com Signed-off-by: Jakub Kicinski Signed-off-by: Alexander Martyniuk Signed-off-by: Greg Kroah-Hartman --- net/sctp/ipv6.c | 2 ++ net/sctp/protocol.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c index d725b21587588..7434309785cc5 100644 --- a/net/sctp/ipv6.c +++ b/net/sctp/ipv6.c @@ -261,9 +261,11 @@ static int sctp_v6_xmit(struct sk_buff *skb, struct sctp_transport *t) skb_set_inner_ipproto(skb, IPPROTO_SCTP); label = ip6_make_flowlabel(sock_net(sk), skb, fl6->flowlabel, true, fl6); + local_bh_disable(); udp_tunnel6_xmit_skb(dst, sk, skb, NULL, &fl6->saddr, &fl6->daddr, tclass, ip6_dst_hoplimit(dst), label, sctp_sk(sk)->udp_port, t->encap_port, false, 0); + local_bh_enable(); return 0; } diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c index 9dbc24af749b5..6ce58fc95ef51 100644 --- a/net/sctp/protocol.c +++ b/net/sctp/protocol.c @@ -1102,10 +1102,12 @@ static inline int sctp_v4_xmit(struct sk_buff *skb, struct sctp_transport *t) skb_reset_inner_mac_header(skb); skb_reset_inner_transport_header(skb); skb_set_inner_ipproto(skb, IPPROTO_SCTP); + local_bh_disable(); udp_tunnel_xmit_skb(dst_rtable(dst), sk, skb, fl4->saddr, fl4->daddr, dscp, ip4_dst_hoplimit(dst), df, sctp_sk(sk)->udp_port, t->encap_port, false, false, 0); + local_bh_enable(); return 0; } From efce8a486bffcbad9897b059f05a01b8f25197ea Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Fri, 15 May 2026 15:42:11 +0300 Subject: [PATCH 34/56] mm: introduce VM_MAYBE_GUARD and make visible in /proc/$pid/smaps commit 5dba5cc2e0ffa76f2f6c8922a04469dc9602c396 upstream. Patch series "introduce VM_MAYBE_GUARD and make it sticky", v4. Currently, guard regions are not visible to users except through /proc/$pid/pagemap, with no explicit visibility at the VMA level. This makes the feature less useful, as it isn't entirely apparent which VMAs may have these entries present, especially when performing actions which walk through memory regions such as those performed by CRIU. This series addresses this issue by introducing the VM_MAYBE_GUARD flag which fulfils this role, updating the smaps logic to display an entry for these. The semantics of this flag are that a guard region MAY be present if set (we cannot be sure, as we can't efficiently track whether an MADV_GUARD_REMOVE finally removes all the guard regions in a VMA) - but if not set the VMA definitely does NOT have any guard regions present. It's problematic to establish this flag without further action, because that means that VMAs with guard regions in them become non-mergeable with adjacent VMAs for no especially good reason. To work around this, this series also introduces the concept of 'sticky' VMA flags - that is flags which: a. if set in one VMA and not in another still permit those VMAs to be merged (if otherwise compatible). b. When they are merged, the resultant VMA must have the flag set. The VMA logic is updated to propagate these flags correctly. Additionally, VM_MAYBE_GUARD being an explicit VMA flag allows us to solve an issue with file-backed guard regions - previously these established an anon_vma object for file-backed mappings solely to have vma_needs_copy() correctly propagate guard region mappings to child processes. We introduce a new flag alias VM_COPY_ON_FORK (which currently only specifies VM_MAYBE_GUARD) and update vma_needs_copy() to check explicitly for this flag and to copy page tables if it is present, which resolves this issue. Additionally, we add the ability for allow-listed VMA flags to be atomically writable with only mmap/VMA read locks held. The only flag we allow so far is VM_MAYBE_GUARD, which we carefully ensure does not cause any races by being allowed to do so. This allows us to maintain guard region installation as a read-locked operation and not endure the overhead of obtaining a write lock here. Finally we introduce extensive VMA userland tests to assert that the sticky VMA logic behaves correctly as well as guard region self tests to assert that smaps visibility is correctly implemented. This patch (of 9): Currently, if a user needs to determine if guard regions are present in a range, they have to scan all VMAs (or have knowledge of which ones might have guard regions). Since commit 8e2f2aeb8b48 ("fs/proc/task_mmu: add guard region bit to pagemap") and the related commit a516403787e0 ("fs/proc: extend the PAGEMAP_SCAN ioctl to report guard regions"), users can use either /proc/$pid/pagemap or the PAGEMAP_SCAN functionality to perform this operation at a virtual address level. This is not ideal, and it gives no visibility at a /proc/$pid/smaps level that guard regions exist in ranges. This patch remedies the situation by establishing a new VMA flag, VM_MAYBE_GUARD, to indicate that a VMA may contain guard regions (it is uncertain because we cannot reasonably determine whether a MADV_GUARD_REMOVE call has removed all of the guard regions in a VMA, and additionally VMAs may change across merge/split). We utilise 0x800 for this flag which makes it available to 32-bit architectures also, a flag that was previously used by VM_DENYWRITE, which was removed in commit 8d0920bde5eb ("mm: remove VM_DENYWRITE") and hasn't bee reused yet. We also update the smaps logic and documentation to identify these VMAs. Another major use of this functionality is that we can use it to identify that we ought to copy page tables on fork. We do not actually implement usage of this flag in mm/madvise.c yet as we need to allow some VMA flags to be applied atomically under mmap/VMA read lock in order to avoid the need to acquire a write lock for this purpose. Link: https://lkml.kernel.org/r/cover.1763460113.git.ljs@kernel.org Link: https://lkml.kernel.org/r/cf8ef821eba29b6c5b5e138fffe95d6dcabdedb9.1763460113.git.ljs@kernel.org Signed-off-by: Lorenzo Stoakes Reviewed-by: Pedro Falcato Reviewed-by: Vlastimil Babka Acked-by: David Hildenbrand (Red Hat) Reviewed-by: Lance Yang Cc: Andrei Vagin Cc: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Jann Horn Cc: Jonathan Corbet Cc: Liam Howlett Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Michal Hocko Cc: Mike Rapoport Cc: Nico Pache Cc: Ryan Roberts Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton Signed-off-by: Ahmed Elaidy Signed-off-by: Greg Kroah-Hartman --- Documentation/filesystems/proc.rst | 5 +++-- fs/proc/task_mmu.c | 1 + include/linux/mm.h | 3 +++ include/trace/events/mmflags.h | 1 + mm/memory.c | 4 ++++ tools/testing/vma/vma_internal.h | 1 + 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst index 0b86a8022fa1e..8256e857e2d74 100644 --- a/Documentation/filesystems/proc.rst +++ b/Documentation/filesystems/proc.rst @@ -553,7 +553,7 @@ otherwise. kernel flags associated with the particular virtual memory area in two letter encoded manner. The codes are the following: - == ======================================= + == ============================================================= rd readable wr writeable ex executable @@ -591,7 +591,8 @@ encoded manner. The codes are the following: sl sealed lf lock on fault pages dp always lazily freeable mapping - == ======================================= + gu maybe contains guard regions (if not set, definitely doesn't) + == ============================================================= Note that there is no guarantee that every flag and associated mnemonic will be present in all further kernel releases. Things get changed, the flags may diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index b490245ff9be7..4c5adfd4fc1f1 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -1159,6 +1159,7 @@ static void show_smap_vma_flags(struct seq_file *m, struct vm_area_struct *vma) [ilog2(VM_MAYSHARE)] = "ms", [ilog2(VM_GROWSDOWN)] = "gd", [ilog2(VM_PFNMAP)] = "pf", + [ilog2(VM_MAYBE_GUARD)] = "gu", [ilog2(VM_LOCKED)] = "lo", [ilog2(VM_IO)] = "io", [ilog2(VM_SEQ_READ)] = "sr", diff --git a/include/linux/mm.h b/include/linux/mm.h index d905cf528b23c..43c0ddcbef154 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -269,6 +269,8 @@ extern struct rw_semaphore nommu_region_sem; extern unsigned int kobjsize(const void *objp); #endif +#define VM_MAYBE_GUARD_BIT 11 + /* * vm_flags in vm_area_struct, see mm_types.h. * When changing, update also include/trace/events/mmflags.h @@ -294,6 +296,7 @@ extern unsigned int kobjsize(const void *objp); #define VM_UFFD_MISSING 0 #endif /* CONFIG_MMU */ #define VM_PFNMAP 0x00000400 /* Page-ranges managed without "struct page", just pure PFN */ +#define VM_MAYBE_GUARD BIT(VM_MAYBE_GUARD_BIT) /* The VMA maybe contains guard regions. */ #define VM_UFFD_WP 0x00001000 /* wrprotect pages tracking */ #define VM_LOCKED 0x00002000 diff --git a/include/trace/events/mmflags.h b/include/trace/events/mmflags.h index aa441f593e9a6..a6e5a44c9b429 100644 --- a/include/trace/events/mmflags.h +++ b/include/trace/events/mmflags.h @@ -213,6 +213,7 @@ IF_HAVE_PG_ARCH_3(arch_3) {VM_UFFD_MISSING, "uffd_missing" }, \ IF_HAVE_UFFD_MINOR(VM_UFFD_MINOR, "uffd_minor" ) \ {VM_PFNMAP, "pfnmap" }, \ + {VM_MAYBE_GUARD, "maybe_guard" }, \ {VM_UFFD_WP, "uffd_wp" }, \ {VM_LOCKED, "locked" }, \ {VM_IO, "io" }, \ diff --git a/mm/memory.c b/mm/memory.c index 42b733b785236..2e3ae47cccb76 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1494,6 +1494,10 @@ vma_needs_copy(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma) if (src_vma->anon_vma) return true; + /* Guard regions have modified page tables that require copying. */ + if (src_vma->vm_flags & VM_MAYBE_GUARD) + return true; + /* * Don't copy ptes where a page fault will fill them correctly. Fork * becomes much lighter when there are big shared or private readonly diff --git a/tools/testing/vma/vma_internal.h b/tools/testing/vma/vma_internal.h index 9f724954a0f6b..5104b02bb5b77 100644 --- a/tools/testing/vma/vma_internal.h +++ b/tools/testing/vma/vma_internal.h @@ -56,6 +56,7 @@ extern unsigned long dac_mmap_min_addr; #define VM_MAYEXEC 0x00000040 #define VM_GROWSDOWN 0x00000100 #define VM_PFNMAP 0x00000400 +#define VM_MAYBE_GUARD 0x00000800 #define VM_LOCKED 0x00002000 #define VM_IO 0x00004000 #define VM_SEQ_READ 0x00008000 /* App will access data sequentially */ From bdeadba743375699b580dff218611d50dab07ba8 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Fri, 15 May 2026 15:42:12 +0300 Subject: [PATCH 35/56] mm: add atomic VMA flags and set VM_MAYBE_GUARD as such commit 568822502383acd57d7cc1c72ee43932c45a9524 upstream. This patch adds the ability to atomically set VMA flags with only the mmap read/VMA read lock held. As this could be hugely problematic for VMA flags in general given that all other accesses are non-atomic and serialised by the mmap/VMA locks, we implement this with a strict allow-list - that is, only designated flags are allowed to do this. We make VM_MAYBE_GUARD one of these flags. Link: https://lkml.kernel.org/r/97e57abed09f2663077ed7a36fb8206e243171a9.1763460113.git.ljs@kernel.org Signed-off-by: Lorenzo Stoakes Reviewed-by: Pedro Falcato Reviewed-by: Vlastimil Babka Acked-by: David Hildenbrand (Red Hat) Reviewed-by: Lance Yang Cc: Andrei Vagin Cc: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Jann Horn Cc: Jonathan Corbet Cc: Liam Howlett Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Michal Hocko Cc: Mike Rapoport Cc: Nico Pache Cc: Ryan Roberts Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton Signed-off-by: Ahmed Elaidy Signed-off-by: Greg Kroah-Hartman --- include/linux/mm.h | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/include/linux/mm.h b/include/linux/mm.h index 43c0ddcbef154..bd1c987e80fea 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -501,6 +501,9 @@ extern unsigned int kobjsize(const void *objp); /* This mask represents all the VMA flag bits used by mlock */ #define VM_LOCKED_MASK (VM_LOCKED | VM_LOCKONFAULT) +/* These flags can be updated atomically via VMA/mmap read lock. */ +#define VM_ATOMIC_SET_ALLOWED VM_MAYBE_GUARD + /* Arch-specific flags to clear when updating VM flags on protection change */ #ifndef VM_ARCH_CLEAR # define VM_ARCH_CLEAR VM_NONE @@ -843,6 +846,47 @@ static inline void vm_flags_mod(struct vm_area_struct *vma, __vm_flags_mod(vma, set, clear); } +static inline bool __vma_flag_atomic_valid(struct vm_area_struct *vma, + int bit) +{ + const vm_flags_t mask = BIT(bit); + + /* Only specific flags are permitted */ + if (WARN_ON_ONCE(!(mask & VM_ATOMIC_SET_ALLOWED))) + return false; + + return true; +} + +/* + * Set VMA flag atomically. Requires only VMA/mmap read lock. Only specific + * valid flags are allowed to do this. + */ +static inline void vma_flag_set_atomic(struct vm_area_struct *vma, int bit) +{ + /* mmap read lock/VMA read lock must be held. */ + if (!rwsem_is_locked(&vma->vm_mm->mmap_lock)) + vma_assert_locked(vma); + + if (__vma_flag_atomic_valid(vma, bit)) + set_bit(bit, &ACCESS_PRIVATE(vma, __vm_flags)); +} + +/* + * Test for VMA flag atomically. Requires no locks. Only specific valid flags + * are allowed to do this. + * + * This is necessarily racey, so callers must ensure that serialisation is + * achieved through some other means, or that races are permissible. + */ +static inline bool vma_flag_test_atomic(struct vm_area_struct *vma, int bit) +{ + if (__vma_flag_atomic_valid(vma, bit)) + return test_bit(bit, &vma->vm_flags); + + return false; +} + static inline void vma_set_anonymous(struct vm_area_struct *vma) { vma->vm_ops = NULL; From a093c80a1f139425c7cf186b91e5ed5c7b27d98e Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Fri, 15 May 2026 15:42:13 +0300 Subject: [PATCH 36/56] mm: update vma_modify_flags() to handle residual flags, document commit 9119d6c2095bb20292cb9812dd70d37f17e3bd37 upstream. The vma_modify_*() family of functions each either perform splits, a merge or no changes at all in preparation for the requested modification to occur. When doing so for a VMA flags change, we currently don't account for any flags which may remain (for instance, VM_SOFTDIRTY) despite the requested change in the case that a merge succeeded. This is made more important by subsequent patches which will introduce the concept of sticky VMA flags which rely on this behaviour. This patch fixes this by passing the VMA flags parameter as a pointer and updating it accordingly on merge and updating callers to accommodate for this. Additionally, while we are here, we add kdocs for each of the vma_modify_*() functions, as the fact that the requested modification is not performed is confusing so it is useful to make this abundantly clear. We also update the VMA userland tests to account for this change. Link: https://lkml.kernel.org/r/23b5b549b0eaefb2922625626e58c2a352f3e93c.1763460113.git.ljs@kernel.org Signed-off-by: Lorenzo Stoakes Reviewed-by: Pedro Falcato Reviewed-by: Vlastimil Babka Cc: Andrei Vagin Cc: Baolin Wang Cc: Barry Song Cc: David Hildenbrand (Red Hat) Cc: Dev Jain Cc: Jann Horn Cc: Jonathan Corbet Cc: Lance Yang Cc: Liam Howlett Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Michal Hocko Cc: Mike Rapoport Cc: Nico Pache Cc: Ryan Roberts Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton Signed-off-by: Ahmed Elaidy Signed-off-by: Greg Kroah-Hartman --- mm/madvise.c | 2 +- mm/mlock.c | 2 +- mm/mprotect.c | 2 +- mm/mseal.c | 7 +- mm/vma.c | 56 ++++++++-------- mm/vma.h | 140 +++++++++++++++++++++++++++++----------- tools/testing/vma/vma.c | 3 +- 7 files changed, 143 insertions(+), 69 deletions(-) diff --git a/mm/madvise.c b/mm/madvise.c index fb1c86e630b66..0b3280752bfb7 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -167,7 +167,7 @@ static int madvise_update_vma(vm_flags_t new_flags, range->start, range->end, anon_name); else vma = vma_modify_flags(&vmi, madv_behavior->prev, vma, - range->start, range->end, new_flags); + range->start, range->end, &new_flags); if (IS_ERR(vma)) return PTR_ERR(vma); diff --git a/mm/mlock.c b/mm/mlock.c index f59c6d8d376ff..73551c71cebfc 100644 --- a/mm/mlock.c +++ b/mm/mlock.c @@ -480,7 +480,7 @@ static int mlock_fixup(struct vma_iterator *vmi, struct vm_area_struct *vma, */ goto out; - vma = vma_modify_flags(vmi, *prev, vma, start, end, newflags); + vma = vma_modify_flags(vmi, *prev, vma, start, end, &newflags); if (IS_ERR(vma)) { ret = PTR_ERR(vma); goto out; diff --git a/mm/mprotect.c b/mm/mprotect.c index 988c366137d50..fa818cd582018 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c @@ -813,7 +813,7 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb, newflags &= ~VM_ACCOUNT; } - vma = vma_modify_flags(vmi, *pprev, vma, start, end, newflags); + vma = vma_modify_flags(vmi, *pprev, vma, start, end, &newflags); if (IS_ERR(vma)) { error = PTR_ERR(vma); goto fail; diff --git a/mm/mseal.c b/mm/mseal.c index c561f0ea93e84..3d2f06046e90b 100644 --- a/mm/mseal.c +++ b/mm/mseal.c @@ -69,9 +69,10 @@ static int mseal_apply(struct mm_struct *mm, const unsigned long curr_end = MIN(vma->vm_end, end); if (!(vma->vm_flags & VM_SEALED)) { - vma = vma_modify_flags(&vmi, prev, vma, - curr_start, curr_end, - vma->vm_flags | VM_SEALED); + vm_flags_t vm_flags = vma->vm_flags | VM_SEALED; + + vma = vma_modify_flags(&vmi, prev, vma, curr_start, + curr_end, &vm_flags); if (IS_ERR(vma)) return PTR_ERR(vma); vm_flags_set(vma, VM_SEALED); diff --git a/mm/vma.c b/mm/vma.c index eeb6a187c3d8b..06b539371f1dc 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -1676,25 +1676,35 @@ static struct vm_area_struct *vma_modify(struct vma_merge_struct *vmg) return vma; } -struct vm_area_struct *vma_modify_flags( - struct vma_iterator *vmi, struct vm_area_struct *prev, - struct vm_area_struct *vma, unsigned long start, unsigned long end, - vm_flags_t vm_flags) +struct vm_area_struct *vma_modify_flags(struct vma_iterator *vmi, + struct vm_area_struct *prev, struct vm_area_struct *vma, + unsigned long start, unsigned long end, + vm_flags_t *vm_flags_ptr) { VMG_VMA_STATE(vmg, vmi, prev, vma, start, end); + const vm_flags_t vm_flags = *vm_flags_ptr; + struct vm_area_struct *ret; vmg.vm_flags = vm_flags; - return vma_modify(&vmg); + ret = vma_modify(&vmg); + if (IS_ERR(ret)) + return ret; + + /* + * For a merge to succeed, the flags must match those requested. For + * flags which do not obey typical merge rules (i.e. do not need to + * match), we must let the caller know about them. + */ + if (vmg.state == VMA_MERGE_SUCCESS) + *vm_flags_ptr = ret->vm_flags; + return ret; } -struct vm_area_struct -*vma_modify_name(struct vma_iterator *vmi, - struct vm_area_struct *prev, - struct vm_area_struct *vma, - unsigned long start, - unsigned long end, - struct anon_vma_name *new_name) +struct vm_area_struct *vma_modify_name(struct vma_iterator *vmi, + struct vm_area_struct *prev, struct vm_area_struct *vma, + unsigned long start, unsigned long end, + struct anon_vma_name *new_name) { VMG_VMA_STATE(vmg, vmi, prev, vma, start, end); @@ -1703,12 +1713,10 @@ struct vm_area_struct return vma_modify(&vmg); } -struct vm_area_struct -*vma_modify_policy(struct vma_iterator *vmi, - struct vm_area_struct *prev, - struct vm_area_struct *vma, - unsigned long start, unsigned long end, - struct mempolicy *new_pol) +struct vm_area_struct *vma_modify_policy(struct vma_iterator *vmi, + struct vm_area_struct *prev, struct vm_area_struct *vma, + unsigned long start, unsigned long end, + struct mempolicy *new_pol) { VMG_VMA_STATE(vmg, vmi, prev, vma, start, end); @@ -1717,14 +1725,10 @@ struct vm_area_struct return vma_modify(&vmg); } -struct vm_area_struct -*vma_modify_flags_uffd(struct vma_iterator *vmi, - struct vm_area_struct *prev, - struct vm_area_struct *vma, - unsigned long start, unsigned long end, - vm_flags_t vm_flags, - struct vm_userfaultfd_ctx new_ctx, - bool give_up_on_oom) +struct vm_area_struct *vma_modify_flags_uffd(struct vma_iterator *vmi, + struct vm_area_struct *prev, struct vm_area_struct *vma, + unsigned long start, unsigned long end, vm_flags_t vm_flags, + struct vm_userfaultfd_ctx new_ctx, bool give_up_on_oom) { VMG_VMA_STATE(vmg, vmi, prev, vma, start, end); diff --git a/mm/vma.h b/mm/vma.h index d73e1b324bfd1..1f2d11bb08b43 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -266,47 +266,115 @@ void remove_vma(struct vm_area_struct *vma); void unmap_region(struct ma_state *mas, struct vm_area_struct *vma, struct vm_area_struct *prev, struct vm_area_struct *next); -/* We are about to modify the VMA's flags. */ -__must_check struct vm_area_struct -*vma_modify_flags(struct vma_iterator *vmi, +/** + * vma_modify_flags() - Peform any necessary split/merge in preparation for + * setting VMA flags to *@vm_flags in the range @start to @end contained within + * @vma. + * @vmi: Valid VMA iterator positioned at @vma. + * @prev: The VMA immediately prior to @vma or NULL if @vma is the first. + * @vma: The VMA containing the range @start to @end to be updated. + * @start: The start of the range to update. May be offset within @vma. + * @end: The exclusive end of the range to update, may be offset within @vma. + * @vm_flags_ptr: A pointer to the VMA flags that the @start to @end range is + * about to be set to. On merge, this will be updated to include any additional + * flags which remain in place. + * + * IMPORTANT: The actual modification being requested here is NOT applied, + * rather the VMA is perhaps split, perhaps merged to accommodate the change, + * and the caller is expected to perform the actual modification. + * + * In order to account for VMA flags which may persist (e.g. soft-dirty), the + * @vm_flags_ptr parameter points to the requested flags which are then updated + * so the caller, should they overwrite any existing flags, correctly retains + * these. + * + * Returns: A VMA which contains the range @start to @end ready to have its + * flags altered to *@vm_flags. + */ +__must_check struct vm_area_struct *vma_modify_flags(struct vma_iterator *vmi, + struct vm_area_struct *prev, struct vm_area_struct *vma, + unsigned long start, unsigned long end, + vm_flags_t *vm_flags_ptr); + +/** + * vma_modify_name() - Peform any necessary split/merge in preparation for + * setting anonymous VMA name to @new_name in the range @start to @end contained + * within @vma. + * @vmi: Valid VMA iterator positioned at @vma. + * @prev: The VMA immediately prior to @vma or NULL if @vma is the first. + * @vma: The VMA containing the range @start to @end to be updated. + * @start: The start of the range to update. May be offset within @vma. + * @end: The exclusive end of the range to update, may be offset within @vma. + * @new_name: The anonymous VMA name that the @start to @end range is about to + * be set to. + * + * IMPORTANT: The actual modification being requested here is NOT applied, + * rather the VMA is perhaps split, perhaps merged to accommodate the change, + * and the caller is expected to perform the actual modification. + * + * Returns: A VMA which contains the range @start to @end ready to have its + * anonymous VMA name changed to @new_name. + */ +__must_check struct vm_area_struct *vma_modify_name(struct vma_iterator *vmi, struct vm_area_struct *prev, struct vm_area_struct *vma, unsigned long start, unsigned long end, - vm_flags_t vm_flags); - -/* We are about to modify the VMA's anon_name. */ -__must_check struct vm_area_struct -*vma_modify_name(struct vma_iterator *vmi, - struct vm_area_struct *prev, - struct vm_area_struct *vma, - unsigned long start, - unsigned long end, - struct anon_vma_name *new_name); - -/* We are about to modify the VMA's memory policy. */ -__must_check struct vm_area_struct -*vma_modify_policy(struct vma_iterator *vmi, - struct vm_area_struct *prev, - struct vm_area_struct *vma, + struct anon_vma_name *new_name); + +/** + * vma_modify_policy() - Peform any necessary split/merge in preparation for + * setting NUMA policy to @new_pol in the range @start to @end contained + * within @vma. + * @vmi: Valid VMA iterator positioned at @vma. + * @prev: The VMA immediately prior to @vma or NULL if @vma is the first. + * @vma: The VMA containing the range @start to @end to be updated. + * @start: The start of the range to update. May be offset within @vma. + * @end: The exclusive end of the range to update, may be offset within @vma. + * @new_pol: The NUMA policy that the @start to @end range is about to be set + * to. + * + * IMPORTANT: The actual modification being requested here is NOT applied, + * rather the VMA is perhaps split, perhaps merged to accommodate the change, + * and the caller is expected to perform the actual modification. + * + * Returns: A VMA which contains the range @start to @end ready to have its + * NUMA policy changed to @new_pol. + */ +__must_check struct vm_area_struct *vma_modify_policy(struct vma_iterator *vmi, + struct vm_area_struct *prev, struct vm_area_struct *vma, unsigned long start, unsigned long end, struct mempolicy *new_pol); -/* We are about to modify the VMA's flags and/or uffd context. */ -__must_check struct vm_area_struct -*vma_modify_flags_uffd(struct vma_iterator *vmi, - struct vm_area_struct *prev, - struct vm_area_struct *vma, - unsigned long start, unsigned long end, - vm_flags_t vm_flags, - struct vm_userfaultfd_ctx new_ctx, - bool give_up_on_oom); - -__must_check struct vm_area_struct -*vma_merge_new_range(struct vma_merge_struct *vmg); - -__must_check struct vm_area_struct -*vma_merge_extend(struct vma_iterator *vmi, - struct vm_area_struct *vma, - unsigned long delta); +/** + * vma_modify_flags_uffd() - Peform any necessary split/merge in preparation for + * setting VMA flags to @vm_flags and UFFD context to @new_ctx in the range + * @start to @end contained within @vma. + * @vmi: Valid VMA iterator positioned at @vma. + * @prev: The VMA immediately prior to @vma or NULL if @vma is the first. + * @vma: The VMA containing the range @start to @end to be updated. + * @start: The start of the range to update. May be offset within @vma. + * @end: The exclusive end of the range to update, may be offset within @vma. + * @vm_flags: The VMA flags that the @start to @end range is about to be set to. + * @new_ctx: The userfaultfd context that the @start to @end range is about to + * be set to. + * @give_up_on_oom: If an out of memory condition occurs on merge, simply give + * up on it and treat the merge as best-effort. + * + * IMPORTANT: The actual modification being requested here is NOT applied, + * rather the VMA is perhaps split, perhaps merged to accommodate the change, + * and the caller is expected to perform the actual modification. + * + * Returns: A VMA which contains the range @start to @end ready to have its VMA + * flags changed to @vm_flags and its userfaultfd context changed to @new_ctx. + */ +__must_check struct vm_area_struct *vma_modify_flags_uffd(struct vma_iterator *vmi, + struct vm_area_struct *prev, struct vm_area_struct *vma, + unsigned long start, unsigned long end, vm_flags_t vm_flags, + struct vm_userfaultfd_ctx new_ctx, bool give_up_on_oom); + +__must_check struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg); + +__must_check struct vm_area_struct *vma_merge_extend(struct vma_iterator *vmi, + struct vm_area_struct *vma, unsigned long delta); void unlink_file_vma_batch_init(struct unlink_vma_file_batch *vb); diff --git a/tools/testing/vma/vma.c b/tools/testing/vma/vma.c index 656e1c75b711e..fd37ce3b26280 100644 --- a/tools/testing/vma/vma.c +++ b/tools/testing/vma/vma.c @@ -339,6 +339,7 @@ static bool test_simple_modify(void) struct mm_struct mm = {}; struct vm_area_struct *init_vma = alloc_vma(&mm, 0, 0x3000, 0, vm_flags); VMA_ITERATOR(vmi, &mm, 0x1000); + vm_flags_t flags = VM_READ | VM_MAYREAD; ASSERT_FALSE(attach_vma(&mm, init_vma)); @@ -347,7 +348,7 @@ static bool test_simple_modify(void) * performs the merge/split only. */ vma = vma_modify_flags(&vmi, init_vma, init_vma, - 0x1000, 0x2000, VM_READ | VM_MAYREAD); + 0x1000, 0x2000, &flags); ASSERT_NE(vma, NULL); /* We modify the provided VMA, and on split allocate new VMAs. */ ASSERT_EQ(vma, init_vma); From 05cdec24a858967c7dc768f6b66a71f61f5ed1ee Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Fri, 15 May 2026 15:42:14 +0300 Subject: [PATCH 37/56] mm: implement sticky VMA flags commit 64212ba02e66e705cabce188453ba4e61e9d7325 upstream. It is useful to be able to designate that certain flags are 'sticky', that is, if two VMAs are merged one with a flag of this nature and one without, the merged VMA sets this flag. As a result we ignore these flags for the purposes of determining VMA flag differences between VMAs being considered for merge. This patch therefore updates the VMA merge logic to perform this action, with flags possessing this property being described in the VM_STICKY bitmap. Those flags which ought to be ignored for the purposes of VMA merge are described in the VM_IGNORE_MERGE bitmap, which the VMA merge logic is also updated to use. As part of this change we place VM_SOFTDIRTY in VM_IGNORE_MERGE as it already had this behaviour, alongside VM_STICKY as sticky flags by implication must not disallow merge. Ultimately it seems that we should make VM_SOFTDIRTY a sticky flag in its own right, but this change is out of scope for this series. The only sticky flag designated as such is VM_MAYBE_GUARD, so as a result of this change, once the VMA flag is set upon guard region installation, VMAs with guard ranges will now not have their merge behaviour impacted as a result and can be freely merged with other VMAs without VM_MAYBE_GUARD set. Also update the comments for vma_modify_flags() to directly reference sticky flags now we have established the concept. We also update the VMA userland tests to account for the changes. Link: https://lkml.kernel.org/r/22ad5269f7669d62afb42ce0c79bad70b994c58d.1763460113.git.ljs@kernel.org Signed-off-by: Lorenzo Stoakes Reviewed-by: Pedro Falcato Reviewed-by: Vlastimil Babka Cc: Andrei Vagin Cc: Baolin Wang Cc: Barry Song Cc: David Hildenbrand (Red Hat) Cc: Dev Jain Cc: Jann Horn Cc: Jonathan Corbet Cc: Lance Yang Cc: Liam Howlett Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Michal Hocko Cc: Mike Rapoport Cc: Nico Pache Cc: Ryan Roberts Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton Signed-off-by: Ahmed Elaidy Signed-off-by: Greg Kroah-Hartman --- include/linux/mm.h | 28 ++++++++++++++++++++++++++++ mm/vma.c | 31 +++++++++++++++++-------------- mm/vma.h | 10 ++++------ tools/testing/vma/vma_internal.h | 28 ++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index bd1c987e80fea..acf04923b48f5 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -510,6 +510,34 @@ extern unsigned int kobjsize(const void *objp); #endif #define VM_FLAGS_CLEAR (ARCH_VM_PKEY_FLAGS | VM_ARCH_CLEAR) +/* + * Flags which should be 'sticky' on merge - that is, flags which, when one VMA + * possesses it but the other does not, the merged VMA should nonetheless have + * applied to it: + * + * VM_MAYBE_GUARD - If a VMA may have guard regions in place it implies that + * mapped page tables may contain metadata not described by the + * VMA and thus any merged VMA may also contain this metadata, + * and thus we must make this flag sticky. + */ +#define VM_STICKY VM_MAYBE_GUARD + +/* + * VMA flags we ignore for the purposes of merge, i.e. one VMA possessing one + * of these flags and the other not does not preclude a merge. + * + * VM_SOFTDIRTY - Should not prevent from VMA merging, if we match the flags but + * dirty bit -- the caller should mark merged VMA as dirty. If + * dirty bit won't be excluded from comparison, we increase + * pressure on the memory system forcing the kernel to generate + * new VMAs when old one could be extended instead. + * + * VM_STICKY - When merging VMAs, VMA flags must match, unless they are + * 'sticky'. If any sticky flags exist in either VMA, we simply + * set all of them on the merged VMA. + */ +#define VM_IGNORE_MERGE (VM_SOFTDIRTY | VM_STICKY) + /* * mapping from the currently active vm_flags protection bits (the * low four bits) to a page protection mask.. diff --git a/mm/vma.c b/mm/vma.c index 06b539371f1dc..30148e6af7751 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -82,15 +82,7 @@ static inline bool is_mergeable_vma(struct vma_merge_struct *vmg, bool merge_nex if (!mpol_equal(vmg->policy, vma_policy(vma))) return false; - /* - * VM_SOFTDIRTY should not prevent from VMA merging, if we - * match the flags but dirty bit -- the caller should mark - * merged VMA as dirty. If dirty bit won't be excluded from - * comparison, we increase pressure on the memory system forcing - * the kernel to generate new VMAs when old one could be - * extended instead. - */ - if ((vma->vm_flags ^ vmg->vm_flags) & ~VM_SOFTDIRTY) + if ((vma->vm_flags ^ vmg->vm_flags) & ~VM_IGNORE_MERGE) return false; if (vma->vm_file != vmg->file) return false; @@ -810,6 +802,7 @@ static bool can_merge_remove_vma(struct vm_area_struct *vma) static __must_check struct vm_area_struct *vma_merge_existing_range( struct vma_merge_struct *vmg) { + vm_flags_t sticky_flags = vmg->vm_flags & VM_STICKY; struct vm_area_struct *middle = vmg->middle; struct vm_area_struct *prev = vmg->prev; struct vm_area_struct *next; @@ -904,11 +897,13 @@ static __must_check struct vm_area_struct *vma_merge_existing_range( if (merge_right) { vma_start_write(next); vmg->target = next; + sticky_flags |= (next->vm_flags & VM_STICKY); } if (merge_left) { vma_start_write(prev); vmg->target = prev; + sticky_flags |= (prev->vm_flags & VM_STICKY); } if (merge_both) { @@ -978,6 +973,7 @@ static __must_check struct vm_area_struct *vma_merge_existing_range( if (err || commit_merge(vmg)) goto abort; + vm_flags_set(vmg->target, sticky_flags); khugepaged_enter_vma(vmg->target, vmg->vm_flags); vmg->state = VMA_MERGE_SUCCESS; return vmg->target; @@ -1156,14 +1152,20 @@ int vma_expand(struct vma_merge_struct *vmg) struct vm_area_struct *target = vmg->target; struct vm_area_struct *next = vmg->next; int ret = 0; + vm_flags_t sticky_flags; + + sticky_flags = vmg->vm_flags & VM_STICKY; + sticky_flags |= target->vm_flags & VM_STICKY; VM_WARN_ON_VMG(!target, vmg); mmap_assert_write_locked(vmg->mm); vma_start_write(target); - if (next && target != next && vmg->end == next->vm_end) + if (next && target != next && vmg->end == next->vm_end) { + sticky_flags |= next->vm_flags & VM_STICKY; remove_next = true; + } /* We must have a target. */ VM_WARN_ON_VMG(!target, vmg); @@ -1197,6 +1199,7 @@ int vma_expand(struct vma_merge_struct *vmg) if (commit_merge(vmg)) goto nomem; + vm_flags_set(target, sticky_flags); return 0; nomem: @@ -1692,9 +1695,9 @@ struct vm_area_struct *vma_modify_flags(struct vma_iterator *vmi, return ret; /* - * For a merge to succeed, the flags must match those requested. For - * flags which do not obey typical merge rules (i.e. do not need to - * match), we must let the caller know about them. + * For a merge to succeed, the flags must match those + * requested. However, sticky flags may have been retained, so propagate + * them to the caller. */ if (vmg.state == VMA_MERGE_SUCCESS) *vm_flags_ptr = ret->vm_flags; @@ -1959,7 +1962,7 @@ static int anon_vma_compatible(struct vm_area_struct *a, struct vm_area_struct * return a->vm_end == b->vm_start && mpol_equal(vma_policy(a), vma_policy(b)) && a->vm_file == b->vm_file && - !((a->vm_flags ^ b->vm_flags) & ~(VM_ACCESS_FLAGS | VM_SOFTDIRTY)) && + !((a->vm_flags ^ b->vm_flags) & ~(VM_ACCESS_FLAGS | VM_IGNORE_MERGE)) && b->vm_pgoff == a->vm_pgoff + ((b->vm_start - a->vm_start) >> PAGE_SHIFT); } diff --git a/mm/vma.h b/mm/vma.h index 1f2d11bb08b43..4c7ea8b893ab7 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -276,17 +276,15 @@ void unmap_region(struct ma_state *mas, struct vm_area_struct *vma, * @start: The start of the range to update. May be offset within @vma. * @end: The exclusive end of the range to update, may be offset within @vma. * @vm_flags_ptr: A pointer to the VMA flags that the @start to @end range is - * about to be set to. On merge, this will be updated to include any additional - * flags which remain in place. + * about to be set to. On merge, this will be updated to include sticky flags. * * IMPORTANT: The actual modification being requested here is NOT applied, * rather the VMA is perhaps split, perhaps merged to accommodate the change, * and the caller is expected to perform the actual modification. * - * In order to account for VMA flags which may persist (e.g. soft-dirty), the - * @vm_flags_ptr parameter points to the requested flags which are then updated - * so the caller, should they overwrite any existing flags, correctly retains - * these. + * In order to account for sticky VMA flags, the @vm_flags_ptr parameter points + * to the requested flags which are then updated so the caller, should they + * overwrite any existing flags, correctly retains these. * * Returns: A VMA which contains the range @start to @end ready to have its * flags altered to *@vm_flags. diff --git a/tools/testing/vma/vma_internal.h b/tools/testing/vma/vma_internal.h index 5104b02bb5b77..4b9c405796f6d 100644 --- a/tools/testing/vma/vma_internal.h +++ b/tools/testing/vma/vma_internal.h @@ -117,6 +117,34 @@ extern unsigned long dac_mmap_min_addr; #define VM_SEALED VM_NONE #endif +/* + * Flags which should be 'sticky' on merge - that is, flags which, when one VMA + * possesses it but the other does not, the merged VMA should nonetheless have + * applied to it: + * + * VM_MAYBE_GUARD - If a VMA may have guard regions in place it implies that + * mapped page tables may contain metadata not described by the + * VMA and thus any merged VMA may also contain this metadata, + * and thus we must make this flag sticky. + */ +#define VM_STICKY VM_MAYBE_GUARD + +/* + * VMA flags we ignore for the purposes of merge, i.e. one VMA possessing one + * of these flags and the other not does not preclude a merge. + * + * VM_SOFTDIRTY - Should not prevent from VMA merging, if we match the flags but + * dirty bit -- the caller should mark merged VMA as dirty. If + * dirty bit won't be excluded from comparison, we increase + * pressure on the memory system forcing the kernel to generate + * new VMAs when old one could be extended instead. + * + * VM_STICKY - When merging VMAs, VMA flags must match, unless they are + * 'sticky'. If any sticky flags exist in either VMA, we simply + * set all of them on the merged VMA. + */ +#define VM_IGNORE_MERGE (VM_SOFTDIRTY | VM_STICKY) + #define FIRST_USER_ADDRESS 0UL #define USER_PGTABLES_CEILING 0UL From 3d6cb2ed06f7f3c7b3d05a7d77e4b2c4a3dd930b Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Fri, 15 May 2026 15:42:15 +0300 Subject: [PATCH 38/56] mm: introduce copy-on-fork VMAs and make VM_MAYBE_GUARD one commit ab04b530e7e8bd5cf9fb0c1ad20e0deee8f569ec upstream. Gather all the VMA flags whose presence implies that page tables must be copied on fork into a single bitmap - VM_COPY_ON_FORK - and use this rather than specifying individual flags in vma_needs_copy(). We also add VM_MAYBE_GUARD to this list, as it being set on a VMA implies that there may be metadata contained in the page tables (that is - guard markers) which would will not and cannot be propagated upon fork. This was already being done manually previously in vma_needs_copy(), but this makes it very explicit, alongside VM_PFNMAP, VM_MIXEDMAP and VM_UFFD_WP all of which imply the same. Note that VM_STICKY flags ought generally to be marked VM_COPY_ON_FORK too - because equally a flag being VM_STICKY indicates that the VMA contains metadat that is not propagated by being faulted in - i.e. that the VMA metadata does not fully describe the VMA alone, and thus we must propagate whatever metadata there is on a fork. However, for maximum flexibility, we do not make this necessarily the case here. Link: https://lkml.kernel.org/r/5d41b24e7bc622cda0af92b6d558d7f4c0d1bc8c.1763460113.git.ljs@kernel.org Signed-off-by: Lorenzo Stoakes Reviewed-by: Pedro Falcato Reviewed-by: Vlastimil Babka Acked-by: David Hildenbrand (Red Hat) Cc: Andrei Vagin Cc: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Jann Horn Cc: Jonathan Corbet Cc: Lance Yang Cc: Liam Howlett Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Michal Hocko Cc: Mike Rapoport Cc: Nico Pache Cc: Ryan Roberts Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton Signed-off-by: Ahmed Elaidy Signed-off-by: Greg Kroah-Hartman --- include/linux/mm.h | 26 ++++++++++++++++++++++++++ mm/memory.c | 18 ++++-------------- tools/testing/vma/vma_internal.h | 26 ++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index acf04923b48f5..937e0c9fb1548 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -538,6 +538,32 @@ extern unsigned int kobjsize(const void *objp); */ #define VM_IGNORE_MERGE (VM_SOFTDIRTY | VM_STICKY) +/* + * Flags which should result in page tables being copied on fork. These are + * flags which indicate that the VMA maps page tables which cannot be + * reconsistuted upon page fault, so necessitate page table copying upon + * + * VM_PFNMAP / VM_MIXEDMAP - These contain kernel-mapped data which cannot be + * reasonably reconstructed on page fault. + * + * VM_UFFD_WP - Encodes metadata about an installed uffd + * write protect handler, which cannot be + * reconstructed on page fault. + * + * We always copy pgtables when dst_vma has uffd-wp + * enabled even if it's file-backed + * (e.g. shmem). Because when uffd-wp is enabled, + * pgtable contains uffd-wp protection information, + * that's something we can't retrieve from page cache, + * and skip copying will lose those info. + * + * VM_MAYBE_GUARD - Could contain page guard region markers which + * by design are a property of the page tables + * only and thus cannot be reconstructed on page + * fault. + */ +#define VM_COPY_ON_FORK (VM_PFNMAP | VM_MIXEDMAP | VM_UFFD_WP | VM_MAYBE_GUARD) + /* * mapping from the currently active vm_flags protection bits (the * low four bits) to a page protection mask.. diff --git a/mm/memory.c b/mm/memory.c index 2e3ae47cccb76..ea54cf86124bd 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1479,25 +1479,15 @@ copy_p4d_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, static bool vma_needs_copy(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma) { + if (src_vma->vm_flags & VM_COPY_ON_FORK) + return true; /* - * Always copy pgtables when dst_vma has uffd-wp enabled even if it's - * file-backed (e.g. shmem). Because when uffd-wp is enabled, pgtable - * contains uffd-wp protection information, that's something we can't - * retrieve from page cache, and skip copying will lose those info. + * The presence of an anon_vma indicates an anonymous VMA has page + * tables which naturally cannot be reconstituted on page fault. */ - if (userfaultfd_wp(dst_vma)) - return true; - - if (src_vma->vm_flags & (VM_PFNMAP | VM_MIXEDMAP)) - return true; - if (src_vma->anon_vma) return true; - /* Guard regions have modified page tables that require copying. */ - if (src_vma->vm_flags & VM_MAYBE_GUARD) - return true; - /* * Don't copy ptes where a page fault will fill them correctly. Fork * becomes much lighter when there are big shared or private readonly diff --git a/tools/testing/vma/vma_internal.h b/tools/testing/vma/vma_internal.h index 4b9c405796f6d..fd0f3de57c579 100644 --- a/tools/testing/vma/vma_internal.h +++ b/tools/testing/vma/vma_internal.h @@ -145,6 +145,32 @@ extern unsigned long dac_mmap_min_addr; */ #define VM_IGNORE_MERGE (VM_SOFTDIRTY | VM_STICKY) +/* + * Flags which should result in page tables being copied on fork. These are + * flags which indicate that the VMA maps page tables which cannot be + * reconsistuted upon page fault, so necessitate page table copying upon + * + * VM_PFNMAP / VM_MIXEDMAP - These contain kernel-mapped data which cannot be + * reasonably reconstructed on page fault. + * + * VM_UFFD_WP - Encodes metadata about an installed uffd + * write protect handler, which cannot be + * reconstructed on page fault. + * + * We always copy pgtables when dst_vma has uffd-wp + * enabled even if it's file-backed + * (e.g. shmem). Because when uffd-wp is enabled, + * pgtable contains uffd-wp protection information, + * that's something we can't retrieve from page cache, + * and skip copying will lose those info. + * + * VM_MAYBE_GUARD - Could contain page guard region markers which + * by design are a property of the page tables + * only and thus cannot be reconstructed on page + * fault. + */ +#define VM_COPY_ON_FORK (VM_PFNMAP | VM_MIXEDMAP | VM_UFFD_WP | VM_MAYBE_GUARD) + #define FIRST_USER_ADDRESS 0UL #define USER_PGTABLES_CEILING 0UL From b836839c1fd94f9fc7ccdce153844fa26116ccc0 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Fri, 15 May 2026 15:42:16 +0300 Subject: [PATCH 39/56] mm: set the VM_MAYBE_GUARD flag on guard region install commit 49e14dabed7a294427588d4b315f57fbfcab9990 upstream. Now we have established the VM_MAYBE_GUARD flag and added the capacity to set it atomically, do so upon MADV_GUARD_INSTALL. The places where this flag is used currently and matter are: * VMA merge - performed under mmap/VMA write lock, therefore excluding racing writes. * /proc/$pid/smaps - can race the write, however this isn't meaningful as the flag write is performed at the point of the guard region being established, and thus an smaps reader can't reasonably expect to avoid races. Due to atomicity, a reader will observe either the flag being set or not. Therefore consistency will be maintained. In all other cases the flag being set is irrelevant and atomicity guarantees other flags will be read correctly. Note that non-atomic updates of unrelated flags do not cause an issue with this flag being set atomically, as writes of other flags are performed under mmap/VMA write lock, and these atomic writes are performed under mmap/VMA read lock, which excludes the write, avoiding RMW races. Note that we do not encounter issues with KCSAN by adjusting this flag atomically, as we are only updating a single bit in the flag bitmap and therefore we do not need to annotate these changes. We intentionally set this flag in advance of actually updating the page tables, to ensure that any racing atomic read of this flag will only return false prior to page tables being updated, to allow for serialisation via page table locks. Note that we set vma->anon_vma for anonymous mappings. This is because the expectation for anonymous mappings is that an anon_vma is established should they possess any page table mappings. This is also consistent with what we were doing prior to this patch (unconditionally setting anon_vma on guard region installation). We also need to update retract_page_tables() to ensure that madvise(..., MADV_COLLAPSE) doesn't incorrectly collapse file-backed ranges contain guard regions. This was previously guarded by anon_vma being set to catch MAP_PRIVATE cases, but the introduction of VM_MAYBE_GUARD necessitates that we check this flag instead. We utilise vma_flag_test_atomic() to do so - we first perform an optimistic check, then after the PTE page table lock is held, we can check again safely, as upon guard marker install the flag is set atomically prior to the page table lock being taken to actually apply it. So if the initial check fails either: * Page table retraction acquires page table lock prior to VM_MAYBE_GUARD being set - guard marker installation will be blocked until page table retraction is complete. OR: * Guard marker installation acquires page table lock after setting VM_MAYBE_GUARD, which raced and didn't pick this up in the initial optimistic check, blocking page table retraction until the guard regions are installed - the second VM_MAYBE_GUARD check will prevent page table retraction. Either way we're safe. We refactor the retraction checks into a single file_backed_vma_is_retractable(), there doesn't seem to be any reason that the checks were separated as before. Note that VM_MAYBE_GUARD being set atomically remains correct as vma_needs_copy() is invoked with the mmap and VMA write locks held, excluding any race with madvise_guard_install(). Link: https://lkml.kernel.org/r/e9e9ce95b6ac17497de7f60fc110c7dd9e489e8d.1763460113.git.ljs@kernel.org Signed-off-by: Lorenzo Stoakes Reviewed-by: Vlastimil Babka Cc: Andrei Vagin Cc: Baolin Wang Cc: Barry Song Cc: David Hildenbrand (Red Hat) Cc: Dev Jain Cc: Jann Horn Cc: Jonathan Corbet Cc: Lance Yang Cc: Liam Howlett Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Michal Hocko Cc: Mike Rapoport Cc: Nico Pache Cc: Pedro Falcato Cc: Ryan Roberts Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Zi Yan Signed-off-by: Andrew Morton Signed-off-by: Ahmed Elaidy Signed-off-by: Greg Kroah-Hartman --- mm/khugepaged.c | 71 ++++++++++++++++++++++++++++++++----------------- mm/madvise.c | 22 +++++++++------ 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index abe54f0043c73..3dcd884c844e8 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -1715,6 +1715,43 @@ int collapse_pte_mapped_thp(struct mm_struct *mm, unsigned long addr, return result; } +/* Can we retract page tables for this file-backed VMA? */ +static bool file_backed_vma_is_retractable(struct vm_area_struct *vma) +{ + /* + * Check vma->anon_vma to exclude MAP_PRIVATE mappings that + * got written to. These VMAs are likely not worth removing + * page tables from, as PMD-mapping is likely to be split later. + */ + if (READ_ONCE(vma->anon_vma)) + return false; + + /* + * When a vma is registered with uffd-wp, we cannot recycle + * the page table because there may be pte markers installed. + * Other vmas can still have the same file mapped hugely, but + * skip this one: it will always be mapped in small page size + * for uffd-wp registered ranges. + */ + if (userfaultfd_wp(vma)) + return false; + + /* + * If the VMA contains guard regions then we can't collapse it. + * + * This is set atomically on guard marker installation under mmap/VMA + * read lock, and here we may not hold any VMA or mmap lock at all. + * + * This is therefore serialised on the PTE page table lock, which is + * obtained on guard region installation after the flag is set, so this + * check being performed under this lock excludes races. + */ + if (vma_flag_test_atomic(vma, VM_MAYBE_GUARD_BIT)) + return false; + + return true; +} + static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff) { struct vm_area_struct *vma; @@ -1729,14 +1766,6 @@ static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff) spinlock_t *ptl; bool success = false; - /* - * Check vma->anon_vma to exclude MAP_PRIVATE mappings that - * got written to. These VMAs are likely not worth removing - * page tables from, as PMD-mapping is likely to be split later. - */ - if (READ_ONCE(vma->anon_vma)) - continue; - addr = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT); if (addr & ~HPAGE_PMD_MASK || vma->vm_end < addr + HPAGE_PMD_SIZE) @@ -1748,14 +1777,8 @@ static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff) if (hpage_collapse_test_exit(mm)) continue; - /* - * When a vma is registered with uffd-wp, we cannot recycle - * the page table because there may be pte markers installed. - * Other vmas can still have the same file mapped hugely, but - * skip this one: it will always be mapped in small page size - * for uffd-wp registered ranges. - */ - if (userfaultfd_wp(vma)) + + if (!file_backed_vma_is_retractable(vma)) continue; /* PTEs were notified when unmapped; but now for the PMD? */ @@ -1782,15 +1805,15 @@ static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff) spin_lock_nested(ptl, SINGLE_DEPTH_NESTING); /* - * Huge page lock is still held, so normally the page table - * must remain empty; and we have already skipped anon_vma - * and userfaultfd_wp() vmas. But since the mmap_lock is not - * held, it is still possible for a racing userfaultfd_ioctl() - * to have inserted ptes or markers. Now that we hold ptlock, - * repeating the anon_vma check protects from one category, - * and repeating the userfaultfd_wp() check from another. + * Huge page lock is still held, so normally the page table must + * remain empty; and we have already skipped anon_vma and + * userfaultfd_wp() vmas. But since the mmap_lock is not held, + * it is still possible for a racing userfaultfd_ioctl() or + * madvise() to have inserted ptes or markers. Now that we hold + * ptlock, repeating the retractable checks protects us from + * races against the prior checks. */ - if (likely(!vma->anon_vma && !userfaultfd_wp(vma))) { + if (likely(file_backed_vma_is_retractable(vma))) { pgt_pmd = pmdp_collapse_flush(vma, addr, pmd); pmdp_get_lockless_sync(); success = true; diff --git a/mm/madvise.c b/mm/madvise.c index 0b3280752bfb7..5dbe40be7c650 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -1141,15 +1141,21 @@ static long madvise_guard_install(struct madvise_behavior *madv_behavior) return -EINVAL; /* - * If we install guard markers, then the range is no longer - * empty from a page table perspective and therefore it's - * appropriate to have an anon_vma. - * - * This ensures that on fork, we copy page tables correctly. + * Set atomically under read lock. All pertinent readers will need to + * acquire an mmap/VMA write lock to read it. All remaining readers may + * or may not see the flag set, but we don't care. + */ + vma_flag_set_atomic(vma, VM_MAYBE_GUARD_BIT); + + /* + * If anonymous and we are establishing page tables the VMA ought to + * have an anon_vma associated with it. */ - err = anon_vma_prepare(vma); - if (err) - return err; + if (vma_is_anonymous(vma)) { + err = anon_vma_prepare(vma); + if (err) + return err; + } /* * Optimistically try to install the guard marker pages first. If any From f563ce913a8315bd722c1540a8743a0d12d95d4d Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Fri, 15 May 2026 15:42:18 +0300 Subject: [PATCH 40/56] mm: propagate VM_SOFTDIRTY on merge commit 6707915e030a3258868355f989b80140c1a45bbe upstream. Patch series "make VM_SOFTDIRTY a sticky VMA flag", v2. Currently we set VM_SOFTDIRTY when a new mapping is set up (whether by establishing a new VMA, or via merge) as implemented in __mmap_complete() and do_brk_flags(). However, when performing a merge of existing mappings such as when performing mprotect(), we may lose the VM_SOFTDIRTY flag. Now we have the concept of making VMA flags 'sticky', that is that they both don't prevent merge and, importantly, are propagated to merged VMAs, this seems a sensible alternative to the existing special-casing of VM_SOFTDIRTY. We additionally add a self-test that demonstrates that this logic behaves as expected. This patch (of 2): Currently we set VM_SOFTDIRTY when a new mapping is set up (whether by establishing a new VMA, or via merge) as implemented in __mmap_complete() and do_brk_flags(). However, when performing a merge of existing mappings such as when performing mprotect(), we may lose the VM_SOFTDIRTY flag. This is because currently we simply ignore VM_SOFTDIRTY for the purposes of merge, so one VMA may possess the flag and another not, and whichever happens to be the target VMA will be the one upon which the merge is performed which may or may not have VM_SOFTDIRTY set. Now we have the concept of 'sticky' VMA flags, let's make VM_SOFTDIRTY one which solves this issue. Additionally update VMA userland tests to propagate changes. [akpm@linux-foundation.org: update comments, per Lorenzo] Link: https://lkml.kernel.org/r/0019e0b8-ee1e-4359-b5ee-94225cbe5588@lucifer.local Link: https://lkml.kernel.org/r/cover.1763399675.git.ljs@kernel.org Link: https://lkml.kernel.org/r/955478b5170715c895d1ef3b7f68e0cd77f76868.1763399675.git.ljs@kernel.org Signed-off-by: Lorenzo Stoakes Suggested-by: Vlastimil Babka Acked-by: David Hildenbrand (Red Hat) Reviewed-by: Pedro Falcato Acked-by: Andrey Vagin Reviewed-by: Vlastimil Babka Acked-by: Cyrill Gorcunov Cc: Jann Horn Cc: Liam Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Signed-off-by: Andrew Morton Signed-off-by: Ahmed Elaidy Fixes: 34228d473efe ("mm: ignore VM_SOFTDIRTY on VMA merging") Signed-off-by: Greg Kroah-Hartman --- include/linux/mm.h | 15 +++++++-------- tools/testing/vma/vma_internal.h | 18 ++++++------------ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index 937e0c9fb1548..cc9a2072857f8 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -515,28 +515,27 @@ extern unsigned int kobjsize(const void *objp); * possesses it but the other does not, the merged VMA should nonetheless have * applied to it: * + * VM_SOFTDIRTY - if a VMA is marked soft-dirty, that is has not had its + * references cleared via /proc/$pid/clear_refs, any merged VMA + * should be considered soft-dirty also as it operates at a VMA + * granularity. + * * VM_MAYBE_GUARD - If a VMA may have guard regions in place it implies that * mapped page tables may contain metadata not described by the * VMA and thus any merged VMA may also contain this metadata, * and thus we must make this flag sticky. */ -#define VM_STICKY VM_MAYBE_GUARD +#define VM_STICKY (VM_SOFTDIRTY | VM_MAYBE_GUARD) /* * VMA flags we ignore for the purposes of merge, i.e. one VMA possessing one * of these flags and the other not does not preclude a merge. * - * VM_SOFTDIRTY - Should not prevent from VMA merging, if we match the flags but - * dirty bit -- the caller should mark merged VMA as dirty. If - * dirty bit won't be excluded from comparison, we increase - * pressure on the memory system forcing the kernel to generate - * new VMAs when old one could be extended instead. - * * VM_STICKY - When merging VMAs, VMA flags must match, unless they are * 'sticky'. If any sticky flags exist in either VMA, we simply * set all of them on the merged VMA. */ -#define VM_IGNORE_MERGE (VM_SOFTDIRTY | VM_STICKY) +#define VM_IGNORE_MERGE VM_STICKY /* * Flags which should result in page tables being copied on fork. These are diff --git a/tools/testing/vma/vma_internal.h b/tools/testing/vma/vma_internal.h index fd0f3de57c579..f64064ee48587 100644 --- a/tools/testing/vma/vma_internal.h +++ b/tools/testing/vma/vma_internal.h @@ -122,28 +122,22 @@ extern unsigned long dac_mmap_min_addr; * possesses it but the other does not, the merged VMA should nonetheless have * applied to it: * - * VM_MAYBE_GUARD - If a VMA may have guard regions in place it implies that - * mapped page tables may contain metadata not described by the - * VMA and thus any merged VMA may also contain this metadata, - * and thus we must make this flag sticky. + * VM_SOFTDIRTY - if a VMA is marked soft-dirty, that is has not had its + * references cleared via /proc/$pid/clear_refs, any merged VMA + * should be considered soft-dirty also as it operates at a VMA + * granularity. */ -#define VM_STICKY VM_MAYBE_GUARD +#define VM_STICKY (VM_SOFTDIRTY | VM_MAYBE_GUARD) /* * VMA flags we ignore for the purposes of merge, i.e. one VMA possessing one * of these flags and the other not does not preclude a merge. * - * VM_SOFTDIRTY - Should not prevent from VMA merging, if we match the flags but - * dirty bit -- the caller should mark merged VMA as dirty. If - * dirty bit won't be excluded from comparison, we increase - * pressure on the memory system forcing the kernel to generate - * new VMAs when old one could be extended instead. - * * VM_STICKY - When merging VMAs, VMA flags must match, unless they are * 'sticky'. If any sticky flags exist in either VMA, we simply * set all of them on the merged VMA. */ -#define VM_IGNORE_MERGE (VM_SOFTDIRTY | VM_STICKY) +#define VM_IGNORE_MERGE VM_STICKY /* * Flags which should result in page tables being copied on fork. These are From cc1494fd6c65de588d6478811ffc05445ee81ee8 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Fri, 15 May 2026 15:42:19 +0300 Subject: [PATCH 41/56] testing/selftests/mm: add soft-dirty merge self-test commit c7ba92bcfea34f6b4afc744c3b65c8f7420fefe0 upstream. Assert that we correctly merge VMAs containing VM_SOFTDIRTY flags now that we correctly handle these as sticky. In order to do so, we have to account for the fact the pagemap interface checks soft dirty PTEs and additionally that newly merged VMAs are marked VM_SOFTDIRTY. We do this by using use unfaulted anon VMAs, establishing one and clearing references on that one, before establishing another and merging the two before checking that soft-dirty is propagated as expected. We check that this functions correctly with mremap() and mprotect() as sample cases, because VMA merge of adjacent newly mapped VMAs will automatically be made soft-dirty due to existing logic which does so. We are therefore exercising other means of merging VMAs. Link: https://lkml.kernel.org/r/d5a0f735783fb4f30a604f570ede02ccc5e29be9.1763399675.git.ljs@kernel.org Signed-off-by: Lorenzo Stoakes Cc: Andrey Vagin Cc: David Hildenbrand (Red Hat) Cc: Jann Horn Cc: Liam Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Pedro Falcato Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Cyrill Gorcunov Signed-off-by: Andrew Morton Signed-off-by: Ahmed Elaidy Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/mm/soft-dirty.c | 127 +++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/mm/soft-dirty.c b/tools/testing/selftests/mm/soft-dirty.c index 4ee4db3750c16..c3a9585de98cd 100644 --- a/tools/testing/selftests/mm/soft-dirty.c +++ b/tools/testing/selftests/mm/soft-dirty.c @@ -184,6 +184,130 @@ static void test_mprotect(int pagemap_fd, int pagesize, bool anon) close(test_fd); } +static void test_merge(int pagemap_fd, int pagesize) +{ + char *reserved, *map, *map2; + + /* + * Reserve space for tests: + * + * ---padding to --- + * | avoid adj. | + * v merge v + * |---|---|---|---|---| + * | | 1 | 2 | 3 | | + * |---|---|---|---|---| + */ + reserved = mmap(NULL, 5 * pagesize, PROT_NONE, + MAP_ANON | MAP_PRIVATE, -1, 0); + if (reserved == MAP_FAILED) + ksft_exit_fail_msg("mmap failed\n"); + munmap(reserved, 4 * pagesize); + + /* + * Establish initial VMA: + * + * S/D + * |---|---|---|---|---| + * | | 1 | | | | + * |---|---|---|---|---| + */ + map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); + if (map == MAP_FAILED) + ksft_exit_fail_msg("mmap failed\n"); + + /* This will clear VM_SOFTDIRTY too. */ + clear_softdirty(); + + /* + * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from + * map: + * + * - S/D + * |---|---|---|---|---| + * | | 1 | | 2 | | + * |---|---|---|---|---| + */ + map2 = mmap(&reserved[3 * pagesize], pagesize, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); + if (map2 == MAP_FAILED) + ksft_exit_fail_msg("mmap failed\n"); + + /* + * Now remap it immediately adjacent to map, if the merge correctly + * propagates VM_SOFTDIRTY, we should then observe the VMA as a whole + * being marked soft-dirty: + * + * merge + * S/D + * |---|-------|---|---| + * | | 1 | | | + * |---|-------|---|---| + */ + map2 = mremap(map2, pagesize, pagesize, MREMAP_FIXED | MREMAP_MAYMOVE, + &reserved[2 * pagesize]); + if (map2 == MAP_FAILED) + ksft_exit_fail_msg("mremap failed\n"); + ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, + "Test %s-anon soft-dirty after remap merge 1st pg\n", + __func__); + ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, + "Test %s-anon soft-dirty after remap merge 2nd pg\n", + __func__); + + munmap(map, 2 * pagesize); + + /* + * Now establish another VMA: + * + * S/D + * |---|---|---|---|---| + * | | 1 | | | | + * |---|---|---|---|---| + */ + map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); + if (map == MAP_FAILED) + ksft_exit_fail_msg("mmap failed\n"); + + /* Clear VM_SOFTDIRTY... */ + clear_softdirty(); + /* ...and establish incompatible adjacent VMA: + * + * - S/D + * |---|---|---|---|---| + * | | 1 | 2 | | | + * |---|---|---|---|---| + */ + map2 = mmap(&reserved[2 * pagesize], pagesize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); + if (map2 == MAP_FAILED) + ksft_exit_fail_msg("mmap failed\n"); + + /* + * Now mprotect() VMA 1 so it's compatible with 2 and therefore merges: + * + * merge + * S/D + * |---|-------|---|---| + * | | 1 | | | + * |---|-------|---|---| + */ + if (mprotect(map, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC)) + ksft_exit_fail_msg("mprotect failed\n"); + + ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1, + "Test %s-anon soft-dirty after mprotect merge 1st pg\n", + __func__); + ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1, + "Test %s-anon soft-dirty after mprotect merge 2nd pg\n", + __func__); + + munmap(map, 2 * pagesize); +} + static void test_mprotect_anon(int pagemap_fd, int pagesize) { test_mprotect(pagemap_fd, pagesize, true); @@ -204,7 +328,7 @@ int main(int argc, char **argv) if (!softdirty_supported()) ksft_exit_skip("soft-dirty is not support\n"); - ksft_set_plan(15); + ksft_set_plan(19); pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY); if (pagemap_fd < 0) ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH); @@ -216,6 +340,7 @@ int main(int argc, char **argv) test_hugepage(pagemap_fd, pagesize); test_mprotect_anon(pagemap_fd, pagesize); test_mprotect_file(pagemap_fd, pagesize); + test_merge(pagemap_fd, pagesize); close(pagemap_fd); From c5b3871b567c24642c3a0408794c29783e986207 Mon Sep 17 00:00:00 2001 From: "Mike Marciniszyn (Meta)" Date: Sat, 7 Mar 2026 05:58:43 -0500 Subject: [PATCH 42/56] net: export netif_open for self_test usage commit 3fdd33697c2be9184668c89ba4f24a5ecbc8ec51 upstream. dev_open() already is exported, but drivers which use the netdev instance lock need to use netif_open() instead. netif_close() is also already exported [1] so this completes the pairing. This export is required for the following fbnic self tests to avoid calling ndo_stop() and ndo_open() in favor of the more appropriate netif_open() and netif_close() that notifies any listeners that the interface went down to test and is now coming back up. Link: https://patch.msgid.link/20250309215851.2003708-1-sdf@fomichev.me [1] Signed-off-by: Mike Marciniszyn (Meta) Link: https://patch.msgid.link/20260307105847.1438-2-mike.marciniszyn@gmail.com Signed-off-by: Paolo Abeni Signed-off-by: Greg Kroah-Hartman --- net/core/dev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/dev.c b/net/core/dev.c index 681d7de89c505..5853aada258ca 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1724,6 +1724,7 @@ int netif_open(struct net_device *dev, struct netlink_ext_ack *extack) return ret; } +EXPORT_SYMBOL(netif_open); static void __dev_close_many(struct list_head *head) { From 76db05493184641855d3236e694fb5e455d18d0f Mon Sep 17 00:00:00 2001 From: Faicker Mo Date: Mon, 11 May 2026 22:05:51 +0800 Subject: [PATCH 43/56] net: net_failover: Fix the deadlock in slave register commit b84c5632c7b31f8910167075a8128cfb9e50fcfe upstream. There is netdev_lock_ops() before the NETDEV_REGISTER notifier in register_netdevice(), so use the non-locking functions in net_failover_slave_register(). failover_slave_register() in failover_existing_slave_register() adds lock and unlock ops too. Call Trace: __schedule+0x30d/0x7a0 schedule+0x27/0x90 schedule_preempt_disabled+0x15/0x30 __mutex_lock.constprop.0+0x538/0x9e0 __mutex_lock_slowpath+0x13/0x20 mutex_lock+0x3b/0x50 dev_set_mtu+0x40/0xe0 net_failover_slave_register+0x24/0x280 failover_slave_register+0x103/0x1b0 failover_event+0x15e/0x210 ? dropmon_net_event+0xac/0xe0 notifier_call_chain+0x5e/0xe0 raw_notifier_call_chain+0x16/0x30 call_netdevice_notifiers_info+0x52/0xa0 register_netdevice+0x5f4/0x7c0 register_netdev+0x1e/0x40 _mlx5e_probe+0xe2/0x370 [mlx5_core] mlx5e_probe+0x59/0x70 [mlx5_core] ? __pfx_mlx5e_probe+0x10/0x10 [mlx5_core] Fixes: 4c975fd70002 ("net: hold instance lock during NETDEV_REGISTER/UP") Signed-off-by: Faicker Mo Signed-off-by: Jakub Kicinski Signed-off-by: Greg Kroah-Hartman --- drivers/net/net_failover.c | 12 ++++++------ net/core/failover.c | 6 +++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/net/net_failover.c b/drivers/net/net_failover.c index 5b50d9186f121..cd16b3dd7720a 100644 --- a/drivers/net/net_failover.c +++ b/drivers/net/net_failover.c @@ -502,7 +502,7 @@ static int net_failover_slave_register(struct net_device *slave_dev, /* Align MTU of slave with failover dev */ orig_mtu = slave_dev->mtu; - err = dev_set_mtu(slave_dev, failover_dev->mtu); + err = netif_set_mtu(slave_dev, failover_dev->mtu); if (err) { netdev_err(failover_dev, "unable to change mtu of %s to %u register failed\n", slave_dev->name, failover_dev->mtu); @@ -512,11 +512,11 @@ static int net_failover_slave_register(struct net_device *slave_dev, dev_hold(slave_dev); if (netif_running(failover_dev)) { - err = dev_open(slave_dev, NULL); + err = netif_open(slave_dev, NULL); if (err && (err != -EBUSY)) { netdev_err(failover_dev, "Opening slave %s failed err:%d\n", slave_dev->name, err); - goto err_dev_open; + goto err_netif_open; } } @@ -562,10 +562,10 @@ static int net_failover_slave_register(struct net_device *slave_dev, err_vlan_add: dev_uc_unsync(slave_dev, failover_dev); dev_mc_unsync(slave_dev, failover_dev); - dev_close(slave_dev); -err_dev_open: + netif_close(slave_dev); +err_netif_open: dev_put(slave_dev); - dev_set_mtu(slave_dev, orig_mtu); + netif_set_mtu(slave_dev, orig_mtu); done: return err; } diff --git a/net/core/failover.c b/net/core/failover.c index 2a140b3ea6694..7fc3b9d8ab2a6 100644 --- a/net/core/failover.c +++ b/net/core/failover.c @@ -12,6 +12,7 @@ #include #include #include +#include #include static LIST_HEAD(failover_list); @@ -221,8 +222,11 @@ failover_existing_slave_register(struct net_device *failover_dev) for_each_netdev(net, dev) { if (netif_is_failover(dev)) continue; - if (ether_addr_equal(failover_dev->perm_addr, dev->perm_addr)) + if (ether_addr_equal(failover_dev->perm_addr, dev->perm_addr)) { + netdev_lock_ops(dev); failover_slave_register(dev); + netdev_unlock_ops(dev); + } } rtnl_unlock(); } From 0a89002737ee34decc20fa232204dbe5fe83e0de Mon Sep 17 00:00:00 2001 From: Sam Daly Date: Thu, 14 May 2026 18:23:21 +0200 Subject: [PATCH 44/56] iio: light: veml6075: add bounds check to veml6075_it_ms index commit 307dc4240bd41852d9e0912921e298160db1c109 upstream. veml6075_it_ms has 5 elements but VEML6075_CONF_IT can yield values 0-7. If it returns a value >= 5, this causes an out-of-bounds array access. Add a bounds check and return -EINVAL if the index is out of range. The problem values are reserved so should never be read from the register. Hence this is hardening against fault device, missprogramming or bus corruption. Assisted-by: gkh_clanker_2000 Cc: stable Signed-off-by: Sam Daly Signed-off-by: Greg Kroah-Hartman Reviewed-by: Javier Carrasco Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/light/veml6075.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/iio/light/veml6075.c b/drivers/iio/light/veml6075.c index edbb434070543..f7eb159e5cb40 100644 --- a/drivers/iio/light/veml6075.c +++ b/drivers/iio/light/veml6075.c @@ -100,7 +100,7 @@ static const struct iio_chan_spec veml6075_channels[] = { static int veml6075_request_measurement(struct veml6075_data *data) { - int ret, conf, int_time; + int ret, conf, int_time, int_index; ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf); if (ret < 0) @@ -117,7 +117,11 @@ static int veml6075_request_measurement(struct veml6075_data *data) * time for all possible configurations. Using a 1.50 factor simplifies * operations and ensures reliability under all circumstances. */ - int_time = veml6075_it_ms[FIELD_GET(VEML6075_CONF_IT, conf)]; + int_index = FIELD_GET(VEML6075_CONF_IT, conf); + if (int_index >= ARRAY_SIZE(veml6075_it_ms)) + return -EINVAL; + + int_time = veml6075_it_ms[int_index]; msleep(int_time + (int_time / 2)); /* shutdown again, data registers are still accessible */ From d08d82d83ed45fd8c001a9df66ad7ebb86c9d6c6 Mon Sep 17 00:00:00 2001 From: Sam Daly Date: Thu, 14 May 2026 18:23:20 +0200 Subject: [PATCH 45/56] iio: adc: ti-ads1298: add bounds check to pga_settings index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 95e8a48d7a85d4226934020e57815a3316d3a14b upstream. ads1298_pga_settings has 7 elements but ADS1298_MASK_CH_PGA can yield values 0-7. If it yields a value >= 7, this causes an out-of-bounds array access. Add a bounds check and return -EINVAL if the index is out of range. Note that the remaining value b111 is reserved so should not be seen in a correctly functioning system. Assisted-by: gkh_clanker_2000 Cc: stable Cc: Jonathan Cameron Cc: David Lechner Cc: "Nuno Sá" Cc: Andy Shevchenko Signed-off-by: Sam Daly Signed-off-by: Greg Kroah-Hartman Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/iio/adc/ti-ads1298.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ti-ads1298.c b/drivers/iio/adc/ti-ads1298.c index ae30b47e45142..731792f06993a 100644 --- a/drivers/iio/adc/ti-ads1298.c +++ b/drivers/iio/adc/ti-ads1298.c @@ -279,6 +279,7 @@ static const u8 ads1298_pga_settings[] = { 6, 1, 2, 3, 4, 8, 12 }; static int ads1298_get_scale(struct ads1298_private *priv, int channel, int *val, int *val2) { + unsigned int pga_idx; int ret; unsigned int regval; u8 gain; @@ -302,7 +303,11 @@ static int ads1298_get_scale(struct ads1298_private *priv, if (ret) return ret; - gain = ads1298_pga_settings[FIELD_GET(ADS1298_MASK_CH_PGA, regval)]; + pga_idx = FIELD_GET(ADS1298_MASK_CH_PGA, regval); + if (pga_idx >= ARRAY_SIZE(ads1298_pga_settings)) + return -EINVAL; + + gain = ads1298_pga_settings[pga_idx]; *val /= gain; /* Full scale is VREF / gain */ *val2 = ADS1298_BITS_PER_SAMPLE - 1; /* Signed, hence the -1 */ From b8ebf008696de1ec08c90d51f94d7e40bd448be1 Mon Sep 17 00:00:00 2001 From: Giovanni Cabiddu Date: Mon, 11 May 2026 11:04:08 +0100 Subject: [PATCH 46/56] crypto: qat - remove unused character device and IOCTLs commit d237230728c567297f2f98b425d63156ab2ed17f upstream. The QAT driver exposes a character device (qat_adf_ctl) with IOCTLs for device configuration, start, stop, status query and enumeration. These IOCTLs are not part of any public uAPI header and have no known in-tree or out-of-tree users. Device lifecycle is already managed via sysfs. The ioctl interface also increases the attack surface and is the subject of a number of bug reports. Remove the character device, the IOCTL definitions, and the related data structures (adf_dev_status_info, adf_user_cfg_key_val, adf_user_cfg_section, adf_user_cfg_ctl_data). Drop the now-unused adf_cfg_user.h header and strip adf_ctl_drv.c down to the minimal module_init/module_exit hooks for workqueue, AER, and crypto/compression algorithm registration. Clean up leftover dead code that was only reachable from the removed IOCTL paths: adf_cfg_del_all(), adf_devmgr_verify_id(), adf_devmgr_get_num_dev(), adf_devmgr_get_dev_by_id(), adf_get_vf_real_id() and the unused ADF_CFG macros. Additionally, drop the entry associated to QAT IOCTLs in ioctl-number.rst. Cc: stable@vger.kernel.org Fixes: d8cba25d2c68 ("crypto: qat - Intel(R) QAT driver framework") Reported-by: Zhi Wang Reported-by: Bin Yu Reported-by: MingYu Wang Closes: https://lore.kernel.org/all/61d6d499.ab89.19b9b7f3186.Coremail.wangzhi_xd@stu.xidian.edu.cn/ Link: https://lore.kernel.org/all/20260508034841.256794-1-w15303746062@163.com/ Link: https://lore.kernel.org/all/20260508023542.256299-1-w15303746062@163.com/ Link: https://lore.kernel.org/all/20260504025120.98242-1-w15303746062@163.com/ Signed-off-by: Giovanni Cabiddu Reviewed-by: Ahsan Atta Signed-off-by: Herbert Xu Signed-off-by: Greg Kroah-Hartman --- .../userspace-api/ioctl/ioctl-number.rst | 1 - drivers/crypto/intel/qat/qat_common/adf_cfg.c | 10 - drivers/crypto/intel/qat/qat_common/adf_cfg.h | 1 - .../intel/qat/qat_common/adf_cfg_common.h | 32 -- .../intel/qat/qat_common/adf_cfg_user.h | 38 -- .../intel/qat/qat_common/adf_common_drv.h | 3 - .../crypto/intel/qat/qat_common/adf_ctl_drv.c | 404 +----------------- .../crypto/intel/qat/qat_common/adf_dev_mgr.c | 70 --- 8 files changed, 1 insertion(+), 558 deletions(-) delete mode 100644 drivers/crypto/intel/qat/qat_common/adf_cfg_user.h diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 7c527a01d1cf5..d896b9991a4f7 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -229,7 +229,6 @@ Code Seq# Include File Comments 'a' all linux/atm*.h, linux/sonet.h ATM on linux -'a' 00-0F drivers/crypto/qat/qat_common/adf_cfg_common.h conflict! qat driver 'b' 00-FF conflict! bit3 vme host bridge 'b' 00-0F linux/dma-buf.h conflict! diff --git a/drivers/crypto/intel/qat/qat_common/adf_cfg.c b/drivers/crypto/intel/qat/qat_common/adf_cfg.c index b0fc453fa3fb5..97273da40b438 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_cfg.c +++ b/drivers/crypto/intel/qat/qat_common/adf_cfg.c @@ -103,16 +103,6 @@ static void adf_cfg_section_del_all(struct list_head *head); static void adf_cfg_section_del_all_except(struct list_head *head, const char *section_name); -void adf_cfg_del_all(struct adf_accel_dev *accel_dev) -{ - struct adf_cfg_device_data *dev_cfg_data = accel_dev->cfg; - - down_write(&dev_cfg_data->lock); - adf_cfg_section_del_all(&dev_cfg_data->sec_list); - up_write(&dev_cfg_data->lock); - clear_bit(ADF_STATUS_CONFIGURED, &accel_dev->status); -} - void adf_cfg_del_all_except(struct adf_accel_dev *accel_dev, const char *section_name) { diff --git a/drivers/crypto/intel/qat/qat_common/adf_cfg.h b/drivers/crypto/intel/qat/qat_common/adf_cfg.h index 2afa6f0d15c51..108032b392425 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_cfg.h +++ b/drivers/crypto/intel/qat/qat_common/adf_cfg.h @@ -34,7 +34,6 @@ void adf_cfg_dev_remove(struct adf_accel_dev *accel_dev); void adf_cfg_dev_dbgfs_add(struct adf_accel_dev *accel_dev); void adf_cfg_dev_dbgfs_rm(struct adf_accel_dev *accel_dev); int adf_cfg_section_add(struct adf_accel_dev *accel_dev, const char *name); -void adf_cfg_del_all(struct adf_accel_dev *accel_dev); void adf_cfg_del_all_except(struct adf_accel_dev *accel_dev, const char *section_name); int adf_cfg_add_key_value_param(struct adf_accel_dev *accel_dev, diff --git a/drivers/crypto/intel/qat/qat_common/adf_cfg_common.h b/drivers/crypto/intel/qat/qat_common/adf_cfg_common.h index 81e9e9d7eccd2..d63f4dcccbb5d 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_cfg_common.h +++ b/drivers/crypto/intel/qat/qat_common/adf_cfg_common.h @@ -4,18 +4,11 @@ #define ADF_CFG_COMMON_H_ #include -#include #define ADF_CFG_MAX_STR_LEN 64 #define ADF_CFG_MAX_KEY_LEN_IN_BYTES ADF_CFG_MAX_STR_LEN #define ADF_CFG_MAX_VAL_LEN_IN_BYTES ADF_CFG_MAX_STR_LEN #define ADF_CFG_MAX_SECTION_LEN_IN_BYTES ADF_CFG_MAX_STR_LEN -#define ADF_CFG_BASE_DEC 10 -#define ADF_CFG_BASE_HEX 16 -#define ADF_CFG_ALL_DEVICES 0xFE -#define ADF_CFG_NO_DEVICE 0xFF -#define ADF_CFG_AFFINITY_WHATEVER 0xFF -#define MAX_DEVICE_NAME_SIZE 32 #define ADF_MAX_DEVICES (32 * 32) #define ADF_DEVS_ARRAY_SIZE BITS_TO_LONGS(ADF_MAX_DEVICES) @@ -51,29 +44,4 @@ enum adf_device_type { DEV_420XX, DEV_6XXX, }; - -struct adf_dev_status_info { - enum adf_device_type type; - __u32 accel_id; - __u32 instance_id; - __u8 num_ae; - __u8 num_accel; - __u8 num_logical_accel; - __u8 banks_per_accel; - __u8 state; - __u8 bus; - __u8 dev; - __u8 fun; - char name[MAX_DEVICE_NAME_SIZE]; -}; - -#define ADF_CTL_IOC_MAGIC 'a' -#define IOCTL_CONFIG_SYS_RESOURCE_PARAMETERS _IOW(ADF_CTL_IOC_MAGIC, 0, \ - struct adf_user_cfg_ctl_data) -#define IOCTL_STOP_ACCEL_DEV _IOW(ADF_CTL_IOC_MAGIC, 1, \ - struct adf_user_cfg_ctl_data) -#define IOCTL_START_ACCEL_DEV _IOW(ADF_CTL_IOC_MAGIC, 2, \ - struct adf_user_cfg_ctl_data) -#define IOCTL_STATUS_ACCEL_DEV _IOW(ADF_CTL_IOC_MAGIC, 3, __u32) -#define IOCTL_GET_NUM_DEVICES _IOW(ADF_CTL_IOC_MAGIC, 4, __s32) #endif diff --git a/drivers/crypto/intel/qat/qat_common/adf_cfg_user.h b/drivers/crypto/intel/qat/qat_common/adf_cfg_user.h deleted file mode 100644 index 421f4fb8b4dd2..0000000000000 --- a/drivers/crypto/intel/qat/qat_common/adf_cfg_user.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) */ -/* Copyright(c) 2014 - 2020 Intel Corporation */ -#ifndef ADF_CFG_USER_H_ -#define ADF_CFG_USER_H_ - -#include "adf_cfg_common.h" -#include "adf_cfg_strings.h" - -struct adf_user_cfg_key_val { - char key[ADF_CFG_MAX_KEY_LEN_IN_BYTES]; - char val[ADF_CFG_MAX_VAL_LEN_IN_BYTES]; - union { - struct adf_user_cfg_key_val *next; - __u64 padding3; - }; - enum adf_cfg_val_type type; -} __packed; - -struct adf_user_cfg_section { - char name[ADF_CFG_MAX_SECTION_LEN_IN_BYTES]; - union { - struct adf_user_cfg_key_val *params; - __u64 padding1; - }; - union { - struct adf_user_cfg_section *next; - __u64 padding3; - }; -} __packed; - -struct adf_user_cfg_ctl_data { - union { - struct adf_user_cfg_section *config_section; - __u64 padding; - }; - __u8 device_id; -} __packed; -#endif diff --git a/drivers/crypto/intel/qat/qat_common/adf_common_drv.h b/drivers/crypto/intel/qat/qat_common/adf_common_drv.h index 6cf3a95489e8a..c625324bd47f9 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_common_drv.h +++ b/drivers/crypto/intel/qat/qat_common/adf_common_drv.h @@ -68,10 +68,7 @@ int adf_devmgr_add_dev(struct adf_accel_dev *accel_dev, void adf_devmgr_rm_dev(struct adf_accel_dev *accel_dev, struct adf_accel_dev *pf); struct list_head *adf_devmgr_get_head(void); -struct adf_accel_dev *adf_devmgr_get_dev_by_id(u32 id); struct adf_accel_dev *adf_devmgr_pci_to_accel_dev(struct pci_dev *pci_dev); -int adf_devmgr_verify_id(u32 id); -void adf_devmgr_get_num_dev(u32 *num); int adf_devmgr_in_reset(struct adf_accel_dev *accel_dev); int adf_dev_started(struct adf_accel_dev *accel_dev); int adf_dev_restarting_notify(struct adf_accel_dev *accel_dev); diff --git a/drivers/crypto/intel/qat/qat_common/adf_ctl_drv.c b/drivers/crypto/intel/qat/qat_common/adf_ctl_drv.c index c2e6f0cb7480b..f01f2946de6e8 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_ctl_drv.c +++ b/drivers/crypto/intel/qat/qat_common/adf_ctl_drv.c @@ -2,410 +2,13 @@ /* Copyright(c) 2014 - 2020 Intel Corporation */ #include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include "adf_accel_devices.h" #include "adf_common_drv.h" -#include "adf_cfg.h" -#include "adf_cfg_common.h" -#include "adf_cfg_user.h" - -#define ADF_CFG_MAX_SECTION 512 -#define ADF_CFG_MAX_KEY_VAL 256 - -#define DEVICE_NAME "qat_adf_ctl" - -static DEFINE_MUTEX(adf_ctl_lock); -static long adf_ctl_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); - -static const struct file_operations adf_ctl_ops = { - .owner = THIS_MODULE, - .unlocked_ioctl = adf_ctl_ioctl, - .compat_ioctl = compat_ptr_ioctl, -}; - -static const struct class adf_ctl_class = { - .name = DEVICE_NAME, -}; - -struct adf_ctl_drv_info { - unsigned int major; - struct cdev drv_cdev; -}; - -static struct adf_ctl_drv_info adf_ctl_drv; - -static void adf_chr_drv_destroy(void) -{ - device_destroy(&adf_ctl_class, MKDEV(adf_ctl_drv.major, 0)); - cdev_del(&adf_ctl_drv.drv_cdev); - class_unregister(&adf_ctl_class); - unregister_chrdev_region(MKDEV(adf_ctl_drv.major, 0), 1); -} - -static int adf_chr_drv_create(void) -{ - dev_t dev_id; - struct device *drv_device; - int ret; - - if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME)) { - pr_err("QAT: unable to allocate chrdev region\n"); - return -EFAULT; - } - - ret = class_register(&adf_ctl_class); - if (ret) - goto err_chrdev_unreg; - - adf_ctl_drv.major = MAJOR(dev_id); - cdev_init(&adf_ctl_drv.drv_cdev, &adf_ctl_ops); - if (cdev_add(&adf_ctl_drv.drv_cdev, dev_id, 1)) { - pr_err("QAT: cdev add failed\n"); - goto err_class_destr; - } - - drv_device = device_create(&adf_ctl_class, NULL, - MKDEV(adf_ctl_drv.major, 0), - NULL, DEVICE_NAME); - if (IS_ERR(drv_device)) { - pr_err("QAT: failed to create device\n"); - goto err_cdev_del; - } - return 0; -err_cdev_del: - cdev_del(&adf_ctl_drv.drv_cdev); -err_class_destr: - class_unregister(&adf_ctl_class); -err_chrdev_unreg: - unregister_chrdev_region(dev_id, 1); - return -EFAULT; -} - -static struct adf_user_cfg_ctl_data *adf_ctl_alloc_resources(unsigned long arg) -{ - struct adf_user_cfg_ctl_data *cfg_data; - - cfg_data = memdup_user((void __user *)arg, sizeof(*cfg_data)); - if (IS_ERR(cfg_data)) - pr_err("QAT: failed to copy from user cfg_data.\n"); - return cfg_data; -} - -static int adf_add_key_value_data(struct adf_accel_dev *accel_dev, - const char *section, - const struct adf_user_cfg_key_val *key_val) -{ - if (key_val->type == ADF_HEX) { - long *ptr = (long *)key_val->val; - long val = *ptr; - - if (adf_cfg_add_key_value_param(accel_dev, section, - key_val->key, (void *)val, - key_val->type)) { - dev_err(&GET_DEV(accel_dev), - "failed to add hex keyvalue.\n"); - return -EFAULT; - } - } else { - if (adf_cfg_add_key_value_param(accel_dev, section, - key_val->key, key_val->val, - key_val->type)) { - dev_err(&GET_DEV(accel_dev), - "failed to add keyvalue.\n"); - return -EFAULT; - } - } - return 0; -} - -static int adf_copy_key_value_data(struct adf_accel_dev *accel_dev, - struct adf_user_cfg_ctl_data *ctl_data) -{ - struct adf_user_cfg_key_val key_val; - struct adf_user_cfg_key_val *params_head; - struct adf_user_cfg_section section, *section_head; - int i, j; - - section_head = ctl_data->config_section; - - for (i = 0; section_head && i < ADF_CFG_MAX_SECTION; i++) { - if (copy_from_user(§ion, (void __user *)section_head, - sizeof(*section_head))) { - dev_err(&GET_DEV(accel_dev), - "failed to copy section info\n"); - goto out_err; - } - - if (adf_cfg_section_add(accel_dev, section.name)) { - dev_err(&GET_DEV(accel_dev), - "failed to add section.\n"); - goto out_err; - } - - params_head = section.params; - - for (j = 0; params_head && j < ADF_CFG_MAX_KEY_VAL; j++) { - if (copy_from_user(&key_val, (void __user *)params_head, - sizeof(key_val))) { - dev_err(&GET_DEV(accel_dev), - "Failed to copy keyvalue.\n"); - goto out_err; - } - if (adf_add_key_value_data(accel_dev, section.name, - &key_val)) { - goto out_err; - } - params_head = key_val.next; - } - section_head = section.next; - } - return 0; -out_err: - adf_cfg_del_all(accel_dev); - return -EFAULT; -} - -static int adf_ctl_ioctl_dev_config(struct file *fp, unsigned int cmd, - unsigned long arg) -{ - struct adf_user_cfg_ctl_data *ctl_data; - struct adf_accel_dev *accel_dev; - int ret = 0; - - ctl_data = adf_ctl_alloc_resources(arg); - if (IS_ERR(ctl_data)) - return PTR_ERR(ctl_data); - - accel_dev = adf_devmgr_get_dev_by_id(ctl_data->device_id); - if (!accel_dev) { - ret = -EFAULT; - goto out; - } - - if (adf_dev_started(accel_dev)) { - ret = -EFAULT; - goto out; - } - - if (adf_copy_key_value_data(accel_dev, ctl_data)) { - ret = -EFAULT; - goto out; - } - set_bit(ADF_STATUS_CONFIGURED, &accel_dev->status); -out: - kfree(ctl_data); - return ret; -} - -static int adf_ctl_is_device_in_use(int id) -{ - struct adf_accel_dev *dev; - - list_for_each_entry(dev, adf_devmgr_get_head(), list) { - if (id == dev->accel_id || id == ADF_CFG_ALL_DEVICES) { - if (adf_devmgr_in_reset(dev) || adf_dev_in_use(dev)) { - dev_info(&GET_DEV(dev), - "device qat_dev%d is busy\n", - dev->accel_id); - return -EBUSY; - } - } - } - return 0; -} - -static void adf_ctl_stop_devices(u32 id) -{ - struct adf_accel_dev *accel_dev; - - list_for_each_entry(accel_dev, adf_devmgr_get_head(), list) { - if (id == accel_dev->accel_id || id == ADF_CFG_ALL_DEVICES) { - if (!adf_dev_started(accel_dev)) - continue; - - /* First stop all VFs */ - if (!accel_dev->is_vf) - continue; - - adf_dev_down(accel_dev); - } - } - - list_for_each_entry(accel_dev, adf_devmgr_get_head(), list) { - if (id == accel_dev->accel_id || id == ADF_CFG_ALL_DEVICES) { - if (!adf_dev_started(accel_dev)) - continue; - - adf_dev_down(accel_dev); - } - } -} - -static int adf_ctl_ioctl_dev_stop(struct file *fp, unsigned int cmd, - unsigned long arg) -{ - int ret; - struct adf_user_cfg_ctl_data *ctl_data; - - ctl_data = adf_ctl_alloc_resources(arg); - if (IS_ERR(ctl_data)) - return PTR_ERR(ctl_data); - - if (adf_devmgr_verify_id(ctl_data->device_id)) { - pr_err("QAT: Device %d not found\n", ctl_data->device_id); - ret = -ENODEV; - goto out; - } - - ret = adf_ctl_is_device_in_use(ctl_data->device_id); - if (ret) - goto out; - - if (ctl_data->device_id == ADF_CFG_ALL_DEVICES) - pr_info("QAT: Stopping all acceleration devices.\n"); - else - pr_info("QAT: Stopping acceleration device qat_dev%d.\n", - ctl_data->device_id); - - adf_ctl_stop_devices(ctl_data->device_id); - -out: - kfree(ctl_data); - return ret; -} - -static int adf_ctl_ioctl_dev_start(struct file *fp, unsigned int cmd, - unsigned long arg) -{ - int ret; - struct adf_user_cfg_ctl_data *ctl_data; - struct adf_accel_dev *accel_dev; - - ctl_data = adf_ctl_alloc_resources(arg); - if (IS_ERR(ctl_data)) - return PTR_ERR(ctl_data); - - ret = -ENODEV; - accel_dev = adf_devmgr_get_dev_by_id(ctl_data->device_id); - if (!accel_dev) - goto out; - - dev_info(&GET_DEV(accel_dev), - "Starting acceleration device qat_dev%d.\n", - ctl_data->device_id); - - ret = adf_dev_up(accel_dev, false); - - if (ret) { - dev_err(&GET_DEV(accel_dev), "Failed to start qat_dev%d\n", - ctl_data->device_id); - adf_dev_down(accel_dev); - } -out: - kfree(ctl_data); - return ret; -} - -static int adf_ctl_ioctl_get_num_devices(struct file *fp, unsigned int cmd, - unsigned long arg) -{ - u32 num_devices = 0; - - adf_devmgr_get_num_dev(&num_devices); - if (copy_to_user((void __user *)arg, &num_devices, sizeof(num_devices))) - return -EFAULT; - - return 0; -} - -static int adf_ctl_ioctl_get_status(struct file *fp, unsigned int cmd, - unsigned long arg) -{ - struct adf_hw_device_data *hw_data; - struct adf_dev_status_info dev_info; - struct adf_accel_dev *accel_dev; - - if (copy_from_user(&dev_info, (void __user *)arg, - sizeof(struct adf_dev_status_info))) { - pr_err("QAT: failed to copy from user.\n"); - return -EFAULT; - } - - accel_dev = adf_devmgr_get_dev_by_id(dev_info.accel_id); - if (!accel_dev) - return -ENODEV; - - hw_data = accel_dev->hw_device; - dev_info.state = adf_dev_started(accel_dev) ? DEV_UP : DEV_DOWN; - dev_info.num_ae = hw_data->get_num_aes(hw_data); - dev_info.num_accel = hw_data->get_num_accels(hw_data); - dev_info.num_logical_accel = hw_data->num_logical_accel; - dev_info.banks_per_accel = hw_data->num_banks - / hw_data->num_logical_accel; - strscpy(dev_info.name, hw_data->dev_class->name, sizeof(dev_info.name)); - dev_info.instance_id = hw_data->instance_id; - dev_info.type = hw_data->dev_class->type; - dev_info.bus = accel_to_pci_dev(accel_dev)->bus->number; - dev_info.dev = PCI_SLOT(accel_to_pci_dev(accel_dev)->devfn); - dev_info.fun = PCI_FUNC(accel_to_pci_dev(accel_dev)->devfn); - - if (copy_to_user((void __user *)arg, &dev_info, - sizeof(struct adf_dev_status_info))) { - dev_err(&GET_DEV(accel_dev), "failed to copy status.\n"); - return -EFAULT; - } - return 0; -} - -static long adf_ctl_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) -{ - int ret; - - if (mutex_lock_interruptible(&adf_ctl_lock)) - return -EFAULT; - - switch (cmd) { - case IOCTL_CONFIG_SYS_RESOURCE_PARAMETERS: - ret = adf_ctl_ioctl_dev_config(fp, cmd, arg); - break; - - case IOCTL_STOP_ACCEL_DEV: - ret = adf_ctl_ioctl_dev_stop(fp, cmd, arg); - break; - - case IOCTL_START_ACCEL_DEV: - ret = adf_ctl_ioctl_dev_start(fp, cmd, arg); - break; - - case IOCTL_GET_NUM_DEVICES: - ret = adf_ctl_ioctl_get_num_devices(fp, cmd, arg); - break; - - case IOCTL_STATUS_ACCEL_DEV: - ret = adf_ctl_ioctl_get_status(fp, cmd, arg); - break; - default: - pr_err_ratelimited("QAT: Invalid ioctl %d\n", cmd); - ret = -EFAULT; - break; - } - mutex_unlock(&adf_ctl_lock); - return ret; -} static int __init adf_register_ctl_device_driver(void) { - if (adf_chr_drv_create()) - goto err_chr_dev; - if (adf_init_misc_wq()) goto err_misc_wq; @@ -437,15 +40,11 @@ static int __init adf_register_ctl_device_driver(void) err_aer: adf_exit_misc_wq(); err_misc_wq: - adf_chr_drv_destroy(); -err_chr_dev: - mutex_destroy(&adf_ctl_lock); return -EFAULT; } static void __exit adf_unregister_ctl_device_driver(void) { - adf_chr_drv_destroy(); adf_exit_misc_wq(); adf_exit_aer(); adf_exit_vf_wq(); @@ -453,7 +52,6 @@ static void __exit adf_unregister_ctl_device_driver(void) qat_crypto_unregister(); qat_compression_unregister(); adf_clean_vf_map(false); - mutex_destroy(&adf_ctl_lock); } module_init(adf_register_ctl_device_driver); diff --git a/drivers/crypto/intel/qat/qat_common/adf_dev_mgr.c b/drivers/crypto/intel/qat/qat_common/adf_dev_mgr.c index 34b9f7731c782..81dd22d2083ff 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_dev_mgr.c +++ b/drivers/crypto/intel/qat/qat_common/adf_dev_mgr.c @@ -45,19 +45,6 @@ static struct vf_id_map *adf_find_vf(u32 bdf) return NULL; } -static int adf_get_vf_real_id(u32 fake) -{ - struct list_head *itr; - - list_for_each(itr, &vfs_table) { - struct vf_id_map *ptr = - list_entry(itr, struct vf_id_map, list); - if (ptr->fake_id == fake) - return ptr->id; - } - return -1; -} - /** * adf_clean_vf_map() - Cleans VF id mappings * @vf: flag indicating whether mappings is cleaned @@ -304,63 +291,6 @@ struct adf_accel_dev *adf_devmgr_pci_to_accel_dev(struct pci_dev *pci_dev) } EXPORT_SYMBOL_GPL(adf_devmgr_pci_to_accel_dev); -struct adf_accel_dev *adf_devmgr_get_dev_by_id(u32 id) -{ - struct list_head *itr; - int real_id; - - mutex_lock(&table_lock); - real_id = adf_get_vf_real_id(id); - if (real_id < 0) - goto unlock; - - id = real_id; - - list_for_each(itr, &accel_table) { - struct adf_accel_dev *ptr = - list_entry(itr, struct adf_accel_dev, list); - if (ptr->accel_id == id) { - mutex_unlock(&table_lock); - return ptr; - } - } -unlock: - mutex_unlock(&table_lock); - return NULL; -} - -int adf_devmgr_verify_id(u32 id) -{ - if (id == ADF_CFG_ALL_DEVICES) - return 0; - - if (adf_devmgr_get_dev_by_id(id)) - return 0; - - return -ENODEV; -} - -static int adf_get_num_dettached_vfs(void) -{ - struct list_head *itr; - int vfs = 0; - - mutex_lock(&table_lock); - list_for_each(itr, &vfs_table) { - struct vf_id_map *ptr = - list_entry(itr, struct vf_id_map, list); - if (ptr->bdf != ~0 && !ptr->attached) - vfs++; - } - mutex_unlock(&table_lock); - return vfs; -} - -void adf_devmgr_get_num_dev(u32 *num) -{ - *num = num_devices - adf_get_num_dettached_vfs(); -} - /** * adf_dev_in_use() - Check whether accel_dev is currently in use * @accel_dev: Pointer to acceleration device. From 7cc3dd79777f6ae4625ec37e84dd18a26dc88bde Mon Sep 17 00:00:00 2001 From: Yi Yang Date: Thu, 4 Jun 2026 06:07:34 +0000 Subject: [PATCH 47/56] vc_screen: fix null-ptr-deref in vcs_notifier() during concurrent vcs_write commit a287620312dc6dcb9a093417a0e589bf30fcf38a upstream. A KASAN null-ptr-deref was observed in vcs_notifier(): BUG: KASAN: null-ptr-deref in vcs_notifier+0x98/0x130 Read of size 2 at addr qmp_cmd_name: qmp_capabilities, arguments: {} The issue is a race condition in vcs_write(). When the console_lock is temporarily dropped (to copy data from userspace), the vc_data pointer obtained from vcs_vc() may become stale. After re-acquiring the lock, vcs_vc() is called again to re-validate the pointer. If the vc has been deallocated in the meantime, vcs_vc() returns NULL, and the while loop breaks (with written > 0). However, after the loop, vcs_scr_updated(vc) is still called with the now-NULL vc pointer, leading to a null pointer dereference in the notifier chain (vcs_notifier dereferences param->vc). Fix this by adding a NULL check for vc before calling vcs_scr_updated(). Fixes: 8fb9ea65c9d1 ("vc_screen: reload load of struct vc_data pointer in vcs_write() to avoid UAF") Cc: stable@vger.kernel.org Signed-off-by: Yi Yang Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20260604060734.2914976-1-yiyang13@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vc_screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c index c814644ef4ee6..60b12d40724c8 100644 --- a/drivers/tty/vt/vc_screen.c +++ b/drivers/tty/vt/vc_screen.c @@ -686,7 +686,7 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) } *ppos += written; ret = written; - if (written) + if (written && vc) vcs_scr_updated(vc); return ret; From 50b72074c5e8d6fe7d8c39c71277dba6d3ac225c Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Thu, 28 May 2026 22:48:07 +0530 Subject: [PATCH 48/56] serial: qcom_geni: Fix RX DMA stall when SE_DMA_RX_LEN_IN is zero commit b93062b6d8a1b2d9bad235cac25558a909819026 upstream. In qcom_geni_serial_handle_rx_dma(), geni_se_rx_dma_unprep() clears port->rx_dma_addr before SE_DMA_RX_LEN_IN is read. If the register is zero, for example when the RX stale counter fires on an idle line, the handler returns without calling geni_se_rx_dma_prep(). The next RX DMA interrupt then hits the !port->rx_dma_addr guard and returns immediately, so the RX DMA buffer is never rearmed and later input is lost. Keep the handler on the rearm path when rx_in is zero. Warn about the unexpected zero-length DMA completion, skip received-data handling, and always call geni_se_rx_dma_prep(). Fixes: 2aaa43c70778 ("tty: serial: qcom-geni-serial: add support for serial engine DMA") Cc: stable@vger.kernel.org Reviewed-by: Bartosz Golaszewski Signed-off-by: Viken Dadhaniya Link: https://patch.msgid.link/20260528-serial-rx-0-byte-fix-v2-1-b4195cfe342f@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 337fa3261ac7c..a34817a4428c9 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -896,12 +896,9 @@ static void qcom_geni_serial_handle_rx_dma(struct uart_port *uport, bool drop) port->rx_dma_addr = 0; rx_in = readl(uport->membase + SE_DMA_RX_LEN_IN); - if (!rx_in) { - dev_warn(uport->dev, "serial engine reports 0 RX bytes in!\n"); - return; - } - - if (!drop) + if (!rx_in) + dev_warn_ratelimited(uport->dev, "serial engine reports 0 RX bytes in!\n"); + else if (!drop) handle_rx_uart(uport, rx_in); ret = geni_se_rx_dma_prep(&port->se, port->rx_buf, From 6c25bf4e44a2b6a14332f952bba0974521f5b72d Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Thu, 14 May 2026 02:26:57 -0700 Subject: [PATCH 49/56] drivers/base/memory: set mem->altmap after successful device registration commit a2b8d7827f48ee54a686cb80e4a1d0ff954ec42a upstream. If __add_memory_block() fails at xa_store() (under memory pressure for example), device_unregister() is called, which eventually triggers memory_block_release() with mem->altmap still set, causing a WARN_ON(mem->altmap). This was triggered by modifying virtio-mem driver. Fix this by delaying the assignment of mem->altmap until after __add_memory_block() has succeeded. Link: https://lore.kernel.org/20260514092657.3057141-1-georgi.djakov@oss.qualcomm.com Fixes: 1a8c64e11043 ("mm/memory_hotplug: embed vmem_altmap details in memory block") Signed-off-by: Georgi Djakov Acked-by: Oscar Salvador (SUSE) Cc: Vishal Verma Cc: Mike Rapoport Cc: Richard Cheng Cc: David Hildenbrand Cc: Georgi Djakov Cc: Greg Kroah-Hartman Cc: "Rafael J. Wysocki" Cc: Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/base/memory.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/base/memory.c b/drivers/base/memory.c index 0ecb43556736b..fdbec49f5f5b2 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -828,7 +828,6 @@ static int add_memory_block(unsigned long block_id, int nid, unsigned long state mem->start_section_nr = block_id * sections_per_block; mem->state = state; mem->nid = nid; - mem->altmap = altmap; INIT_LIST_HEAD(&mem->group_next); #ifndef CONFIG_NUMA @@ -846,6 +845,8 @@ static int add_memory_block(unsigned long block_id, int nid, unsigned long state if (ret) return ret; + mem->altmap = altmap; + if (group) { mem->group = group; list_add(&mem->group_next, &group->memory_blocks); From 7cad3ceaf679c55bc9946685dacafce78ce6b51a Mon Sep 17 00:00:00 2001 From: Gil Portnoy Date: Thu, 11 Jun 2026 22:59:19 +0900 Subject: [PATCH 50/56] ksmbd: reject non-VALID session in compound request branch commit 609ca17d869d04ba249e32cdcbf13c0b1c66f43c upstream. smb2_check_user_session() takes a shortcut for any operation that is not the first in a COMPOUND request: it reuses work->sess (the session bound by the first operation) and validates only the SessionId, then returns "valid". It never re-checks work->sess->state == SMB2_SESSION_VALID, and a SessionId of 0xFFFFFFFFFFFFFFFF (ULLONG_MAX, the MS-SMB2 related-operation value) skips even the id comparison. The standalone path (ksmbd_session_lookup_all() plus the SESSION_SETUP state machine) does enforce the VALID state; the compound branch bypasses all of it. A SESSION_SETUP carrying only an NTLM Type-1 (NtLmNegotiate) blob publishes a fresh SMB2_SESSION_IN_PROGRESS session whose sess->user is still NULL (->user is assigned later, by ntlm_authenticate()). Used as operation 1 of a COMPOUND with operation 2 = TREE_CONNECT (related, SessionId=ULLONG_MAX, \\host\IPC$), the tree-connect then runs on that IN_PROGRESS session and reaches ksmbd_ipc_tree_connect_request(), which dereferences user_name(sess->user) with sess->user == NULL (transport_ipc.c:687/701/704) -> remote NULL-pointer dereference and a kernel Oops that wedges the ksmbd worker for all clients. Reject any non-first compound operation that lands on a session which is not SMB2_SESSION_VALID, mirroring the validity the standalone lookup path enforces. SESSION_SETUP itself legitimately runs on an IN_PROGRESS session, but it is never carried as a non-first compound operation, so multi-leg authentication is unaffected by this check. Fixes: 5005bcb42191 ("ksmbd: validate session id and tree id in the compound request") Cc: stable@vger.kernel.org Signed-off-by: Gil Portnoy Acked-by: Namjae Jeon Signed-off-by: Steve French Signed-off-by: Greg Kroah-Hartman --- fs/smb/server/smb2pdu.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index c21bca5a34c4e..c012c5a93cc61 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -613,6 +613,11 @@ int smb2_check_user_session(struct ksmbd_work *work) sess_id, work->sess->id); return -EINVAL; } + if (work->sess->state != SMB2_SESSION_VALID) { + pr_err("compound request on a non-valid session (state %d)\n", + work->sess->state); + return -EINVAL; + } return 1; } From f965cf22dda7f512f4922415894c3e528269a4ae Mon Sep 17 00:00:00 2001 From: Ruslan Valiyev Date: Tue, 17 Mar 2026 17:05:44 +0000 Subject: [PATCH 51/56] media: vidtv: fix NULL pointer dereference in vidtv_mux_push_si commit 7d8bf3d8f91073f4db347ed3aa6302b56107499c upstream. syzbot reported a general protection fault in vidtv_psi_ts_psi_write_into [1]. vidtv_mux_get_pid_ctx() can return NULL, but vidtv_mux_push_si() does not check for this before dereferencing the returned pointer to access the continuity counter. This leads to a general protection fault when accessing a near-NULL address. The root cause is that vidtv_mux_pid_ctx_init() does not check the return value of vidtv_mux_create_pid_ctx_once() for PMT section PIDs. If the allocation fails, the PID context is never created, but init returns success. The subsequent vidtv_mux_push_si() call then gets NULL from vidtv_mux_get_pid_ctx() and crashes. Fix both the root cause (add error check in vidtv_mux_pid_ctx_init for PMT PIDs) and add defensive NULL checks in vidtv_mux_push_si for all vidtv_mux_get_pid_ctx() calls. [1] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN PTI KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] Workqueue: events vidtv_mux_tick RIP: 0010:vidtv_psi_ts_psi_write_into+0x54a/0xbc0 drivers/media/test-drivers/vidtv/vidtv_psi.c:197 Call Trace: vidtv_psi_table_header_write_into drivers/media/test-drivers/vidtv/vidtv_psi.c:799 [inline] vidtv_psi_pmt_write_into+0x3b2/0xa70 drivers/media/test-drivers/vidtv/vidtv_psi.c:1231 vidtv_mux_push_si+0x932/0xe80 drivers/media/test-drivers/vidtv/vidtv_mux.c:196 vidtv_mux_tick+0xe9b/0x1480 drivers/media/test-drivers/vidtv/vidtv_mux.c:408 Fixes: f90cf6079bf67 ("media: vidtv: add a bridge driver") Cc: stable@vger.kernel.org Reported-by: syzbot+814c351d094f4f1a1b86@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=814c351d094f4f1a1b86 Signed-off-by: Ruslan Valiyev Signed-off-by: Hans Verkuil Signed-off-by: Greg Kroah-Hartman --- drivers/media/test-drivers/vidtv/vidtv_mux.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/media/test-drivers/vidtv/vidtv_mux.c b/drivers/media/test-drivers/vidtv/vidtv_mux.c index 7dad97881fdb0..00ae52dbc96e2 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_mux.c +++ b/drivers/media/test-drivers/vidtv/vidtv_mux.c @@ -101,7 +101,8 @@ static int vidtv_mux_pid_ctx_init(struct vidtv_mux *m) /* add a ctx for all PMT sections */ while (p) { pid = vidtv_psi_get_pat_program_pid(p); - vidtv_mux_create_pid_ctx_once(m, pid); + if (!vidtv_mux_create_pid_ctx_once(m, pid)) + goto free; p = p->next; } @@ -170,6 +171,9 @@ static u32 vidtv_mux_push_si(struct vidtv_mux *m) nit_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_NIT_PID); eit_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_EIT_PID); + if (!pat_ctx || !sdt_ctx || !nit_ctx || !eit_ctx) + return 0; + pat_args.offset = m->mux_buf_offset; pat_args.continuity_counter = &pat_ctx->cc; @@ -186,6 +190,8 @@ static u32 vidtv_mux_push_si(struct vidtv_mux *m) } pmt_ctx = vidtv_mux_get_pid_ctx(m, pmt_pid); + if (!pmt_ctx) + continue; pmt_args.offset = m->mux_buf_offset; pmt_args.pmt = m->si.pmt_secs[i]; From 2abfd3ffbd9452f72535d96ff3982b3ab1f8f2f9 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 28 May 2026 10:58:24 +0200 Subject: [PATCH 52/56] virtiofs: fix UAF on submount umount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 06b41351779e9289e8785694ade9042ae85e41ea upstream. iput() called from fuse_release_end() can Oops if the super block has already been destroyed. Normally this is prevented by waiting for num_waiting to go down to zero before commencing with super block shutdown. This only works, however, for the last submount instance, as the wait counter is per connection, not per superblock. Revert to using synchronous release requests for the auto_submounts case, which is virtiofs only at this time. Reported-by: Aurélien Bombo Reported-by: Zhihao Cheng Cc: Greg Kurz Closes: https://github.com/kata-containers/kata-containers/issues/12589 Fixes: 26e5c67deb2e ("fuse: fix livelock in synchronous file put from fuseblk workers") Cc: stable@vger.kernel.org Reviewed-by: Greg Kurz Signed-off-by: Miklos Szeredi Signed-off-by: Greg Kroah-Hartman --- fs/fuse/file.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 7821781245121..79d1b502e7319 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -374,8 +374,14 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff, * aio and closes the fd before the aio completes. Since aio takes its * own ref to the file, the IO completion has to drop the ref, which is * how the fuse server can end up closing its clients' files. + * + * Exception is virtio-fs, which is not affected by the above (server is + * on host, cannot close open files in guest). Virtio-fs needs sync + * release, because the num_waiting mechanism to wait for all requests + * before commencing with fs shutdown doesn't work if submounts are + * used. */ - fuse_file_put(ff, false); + fuse_file_put(ff, ff->fm->fc->auto_submounts); } void fuse_release_common(struct file *file, bool isdir) From 71003a32bef546b6e31aaa40db621846dbc98582 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Wed, 14 Jan 2026 11:00:06 +0000 Subject: [PATCH 53/56] mm: do not copy page tables unnecessarily for VM_UFFD_WP commit 35e247032606f06c2f19d90a6562bc315206b7a7 upstream. Commit ab04b530e7e8 ("mm: introduce copy-on-fork VMAs and make VM_MAYBE_GUARD one") aggregates flags checks in vma_needs_copy(), including VM_UFFD_WP. However in doing so, it incorrectly performed this check against src_vma. This check was done on the assumption that all relevant flags are copied upon fork. However the userfaultfd logic is very innovative in that it implements custom logic on fork in dup_userfaultfd(), including a rather well hidden case where lacking UFFD_FEATURE_EVENT_FORK causes VM_UFFD_WP to not be propagated to the destination VMA. And indeed, vma_needs_copy(), prior to this patch, did check this property on dst_vma, not src_vma. Since all the other relevant flags are copied on fork, we can simply fix this by checking against dst_vma. While we're here, we fix a comment against VM_COPY_ON_FORK (noting that it did indeed already reference dst_vma) to make it abundantly clear that we must check against the destination VMA. Link: https://lkml.kernel.org/r/20260114110006.1047071-1-lorenzo.stoakes@oracle.com Fixes: ab04b530e7e8 ("mm: introduce copy-on-fork VMAs and make VM_MAYBE_GUARD one") Signed-off-by: Lorenzo Stoakes Reported-by: Chris Mason Closes: https://lore.kernel.org/all/20260113231257.3002271-1-clm@meta.com/ Acked-by: David Hildenbrand (Red Hat) Acked-by: Pedro Falcato Cc: Liam Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- include/linux/mm.h | 6 +++++- mm/memory.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/linux/mm.h b/include/linux/mm.h index cc9a2072857f8..92ba2489f2374 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -540,7 +540,11 @@ extern unsigned int kobjsize(const void *objp); /* * Flags which should result in page tables being copied on fork. These are * flags which indicate that the VMA maps page tables which cannot be - * reconsistuted upon page fault, so necessitate page table copying upon + * reconsistuted upon page fault, so necessitate page table copying upon fork. + * + * Note that these flags should be compared with the DESTINATION VMA not the + * source, as VM_UFFD_WP may not be propagated to destination, while all other + * flags will be. * * VM_PFNMAP / VM_MIXEDMAP - These contain kernel-mapped data which cannot be * reasonably reconstructed on page fault. diff --git a/mm/memory.c b/mm/memory.c index ea54cf86124bd..9b071e269d1ec 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1479,7 +1479,11 @@ copy_p4d_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, static bool vma_needs_copy(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma) { - if (src_vma->vm_flags & VM_COPY_ON_FORK) + /* + * We check against dst_vma as while sane VMA flags will have been + * copied, VM_UFFD_WP may be set only on dst_vma. + */ + if (dst_vma->vm_flags & VM_COPY_ON_FORK) return true; /* * The presence of an anon_vma indicates an anonymous VMA has page From 0c503cf3dde2e53614f05261ece12f9d3d4c3c20 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Sat, 27 Jun 2026 11:06:50 +0100 Subject: [PATCH 54/56] Linux 6.18.37 Link: https://lore.kernel.org/r/20260625125645.554579168@linuxfoundation.org Tested-by: Florian Fainelli Tested-by: Brett A C Sheffield Tested-by: Peter Schneider Tested-by: Shuah Khan Tested-by: Ron Economos Tested-by: Miguel Ojeda Tested-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7f5abef39fd38..9c16001ccdcd1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 VERSION = 6 PATCHLEVEL = 18 -SUBLEVEL = 36 +SUBLEVEL = 37 EXTRAVERSION = NAME = Baby Opossum Posse From 868deefc37dfbdfe93d760155e0c84f5f3363cc2 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Mon, 29 Jun 2026 16:47:21 +0200 Subject: [PATCH 55/56] ARM: Fixup the stable merge. This a merge fixup, properly replaces ARM: mm: fault: Move harden_branch_predictor() before interrupts are enabled ARM: mm: fault: Enable interrupts before invoking __do_user_fault() with ARM: ensure interrupts are enabled in __do_user_fault() It is needed because "other" patches were applied as part of the stable series. This contains the patches that were applied upstream. Signed-off-by: Sebastian Andrzej Siewior --- arch/arm/mm/fault.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index 6369dc2ce9ac6..6c27ebd490938 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -190,7 +190,8 @@ __do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr, /* * Something tried to access memory that isn't in our memory map.. - * User mode accesses just cause a SIGSEGV + * User mode accesses just cause a SIGSEGV. Ensure interrupts are enabled + * for preempt RT. */ static void __do_user_fault(unsigned long addr, unsigned int fsr, unsigned int sig, @@ -198,6 +199,8 @@ __do_user_fault(unsigned long addr, unsigned int fsr, unsigned int sig, { struct task_struct *tsk = current; + local_irq_enable(); + #ifdef CONFIG_DEBUG_USER if (((user_debug & UDBG_SEGV) && (sig == SIGSEGV)) || ((user_debug & UDBG_BUS) && (sig == SIGBUS))) { @@ -230,14 +233,10 @@ void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs) * If we are in kernel mode at this point, we * have no context to handle this fault with. */ - if (user_mode(regs)) { - if (addr >= TASK_SIZE) - harden_branch_predictor(); - local_irq_enable(); + if (user_mode(regs)) __do_user_fault(addr, fsr, SIGSEGV, SEGV_MAPERR, regs); - } else { + else __do_kernel_fault(mm, addr, fsr, regs); - } } #ifdef CONFIG_MMU @@ -272,6 +271,7 @@ do_kernel_address_page_fault(struct mm_struct *mm, unsigned long addr, * should not be faulting in kernel space, which includes the * vector/khelper page. Handle the branch predictor hardening * while interrupts are still disabled, then send a SIGSEGV. + * Note that __do_user_fault() will enable interrupts. */ harden_branch_predictor(); __do_user_fault(addr, fsr, SIGSEGV, SEGV_MAPERR, regs); @@ -314,11 +314,8 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) return do_kernel_address_page_fault(mm, addr, fsr, regs); /* Enable interrupts if they were enabled in the parent context. */ - if (interrupts_enabled(regs)) { - if (user_mode(regs) && addr >= TASK_SIZE) - harden_branch_predictor(); + if (interrupts_enabled(regs)) local_irq_enable(); - } /* * If we're in an interrupt or have no user From a3c2a803731b7b3d139c45a019cf43c8759fcc93 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Mon, 29 Jun 2026 16:52:33 +0200 Subject: [PATCH 56/56] v6.18.37-rt6 Signed-off-by: Sebastian Andrzej Siewior --- localversion-rt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localversion-rt b/localversion-rt index 0efe7ba1930e1..8fc605d806670 100644 --- a/localversion-rt +++ b/localversion-rt @@ -1 +1 @@ --rt5 +-rt6