]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
7.1-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 30 Jul 2026 11:41:03 +0000 (13:41 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 30 Jul 2026 11:41:03 +0000 (13:41 +0200)
added patches:
audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch
audit-use-unsigned-int-instead-of-unsigned.patch
bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
fuse-uring-fix-race-between-registration-and-connection-abortion.patch
mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch
mm-sparse-vmemmap-pass-pgmap-argument-to-memory-deactivation-paths.patch
net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch
sched_ext-move-shared-helpers-from-ext.c-into-internal.h-and-cid.h.patch
sched_ext-preserve-rq-tracking-across-local-dsq-dispatch.patch
sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch
sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch
thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch
thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch
thunderbolt-remove-service-debugfs-entries-during-unregister.patch
thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch
xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch

18 files changed:
queue-7.1/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch [new file with mode: 0644]
queue-7.1/audit-use-unsigned-int-instead-of-unsigned.patch [new file with mode: 0644]
queue-7.1/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch [new file with mode: 0644]
queue-7.1/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch [new file with mode: 0644]
queue-7.1/fuse-uring-fix-race-between-registration-and-connection-abortion.patch [new file with mode: 0644]
queue-7.1/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch [new file with mode: 0644]
queue-7.1/mm-sparse-vmemmap-pass-pgmap-argument-to-memory-deactivation-paths.patch [new file with mode: 0644]
queue-7.1/net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch [new file with mode: 0644]
queue-7.1/sched_ext-move-shared-helpers-from-ext.c-into-internal.h-and-cid.h.patch [new file with mode: 0644]
queue-7.1/sched_ext-preserve-rq-tracking-across-local-dsq-dispatch.patch [new file with mode: 0644]
queue-7.1/series
queue-7.1/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch [new file with mode: 0644]
queue-7.1/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch [new file with mode: 0644]
queue-7.1/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch [new file with mode: 0644]
queue-7.1/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch [new file with mode: 0644]
queue-7.1/thunderbolt-remove-service-debugfs-entries-during-unregister.patch [new file with mode: 0644]
queue-7.1/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch [new file with mode: 0644]
queue-7.1/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch [new file with mode: 0644]

diff --git a/queue-7.1/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch b/queue-7.1/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch
new file mode 100644 (file)
index 0000000..1e70807
--- /dev/null
@@ -0,0 +1,364 @@
+From stable+bounces-280758-greg=kroah.com@vger.kernel.org Tue Jul 21 23:11:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 14:01:49 -0400
+Subject: audit: fix recursive locking deadlock in audit_dupe_exe()
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, stable@kernel.org, Waiman Long <longman@redhat.com>, Richard Guy Briggs <rgb@redhat.com>, Nathan Chancellor <nathan@kernel.org>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721180149.286443-2-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 81905b5acbe77284734438df3fbec1158e6429a3 ]
+
+A deadlock occurs in the audit subsystem when duplicating
+executable-related rules.
+
+When a file is moved (e.g., via do_renameat2()), the VFS layer locks
+the parent directory (I_MUTEX_PARENT), which synchronously triggers an
+fsnotify_move event. If an existing executable audit rule matches the
+file being moved, the audit subsystem catches this event and calls
+audit_dupe_exe() to duplicate the watch and update the rule. Then,
+audit_alloc_mark() would call kern_path_parent() to resolve the path,
+leading to a blind attempt to acquire the exact same I_MUTEX_PARENT lock
+already held by the task, resulting in the following recursive locking
+deadlock:
+
+ ============================================
+ WARNING: possible recursive locking detected
+ 6.12.0-55.27.1.el10_0.x86_64+debug #1 Not tainted
+ --------------------------------------------
+ mv/5099 is trying to acquire lock:
+ ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3},
+ at: __kern_path_locked+0x10a/0x2f0
+
+ but task is already holding lock:
+ ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3},
+ at: lock_two_directories+0x13f/0x2b0
+
+ other info that might help us debug this:
+  Possible unsafe locking scenario:
+
+        CPU0
+        ----
+   lock(&inode->i_sb->s_type->i_mutex_dir_key/1);
+   lock(&inode->i_sb->s_type->i_mutex_dir_key/1);
+
+  *** DEADLOCK ***
+
+  May be due to missing lock nesting notation
+
+  6 locks held by mv/5099:
+  #0: ffff888112a9c440 (sb_writers#13)
+  at: do_renameat2+0x34c/0xbc0
+  #1: ffff888112a9c790 (&type->s_vfs_rename_key#3)
+  at: do_renameat2+0x415/0xbc0
+  #2: ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1)
+  at: lock_two_directories+0x13f/0x2b0
+  #3: ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/5)
+  at: lock_two_directories+0x175/0x2b0
+  #4: ffffffffb3a1fb10 (&fsnotify_mark_srcu)
+  at: fsnotify+0x454/0x28a0
+  #5: ffffffffaf886230 (audit_filter_mutex)
+  at: audit_update_watch+0x36/0x11e0
+
+ stack backtrace:
+ Call Trace:
+  <TASK>
+  dump_stack_lvl+0x6f/0xb0
+  print_deadlock_bug.cold+0xbd/0xca
+  validate_chain+0x83a/0xf00
+  __lock_acquire+0xcac/0x1d20
+  lock_acquire.part.0+0x11b/0x360
+  down_write_nested+0x9f/0x230
+  __kern_path_locked+0x10a/0x2f0
+  kern_path_locked+0x26/0x40
+  audit_alloc_mark+0xfb/0x4f0
+  audit_dupe_exe+0x6c/0xe0
+  audit_dupe_rule+0x6c2/0xc00
+  audit_update_watch+0x4cc/0x11e0
+  audit_watch_handle_event+0x12c/0x1b0
+  send_to_group+0x5d0/0x8b0
+  fsnotify+0x615/0x28a0
+  fsnotify_move+0x1d8/0x630
+  vfs_rename+0xdcd/0x1df0
+  do_renameat2+0x9d4/0xbc0
+  __x64_sys_renameat+0x192/0x260
+  do_syscall_64+0x92/0x180
+  entry_SYSCALL_64_after_hwframe+0x76/0x7e
+ RIP: 0033:0x7f0491fe8c4e
+ Code: 0f 1f 40 00 48 8b 15 c1 e1 16 00 f7 d8 64 89 02 b8 ff ff ff ff
+ c3 66 0f 1f 44 00 00 f3 0f 1e fa 49 89 ca b8 08 01 00 00 0f 05 <48>
+ 3d 00 f0 ff ff 77 0a c3 66 0f 1f 84 00 00 00 00 00 48 8b 15 89
+ RSP: 002b:00007ffc7210bf38 EFLAGS: 00000246 ORIG_RAX: 0000000000000108
+ RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f0491fe8c4e
+ RDX: 0000000000000003 RSI: 00007ffc7210e6c8 RDI: 00000000ffffff9c
+ RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000001
+ R10: 00005575eb2dae2a R11: 0000000000000246 R12: 00005575eb2dae2a
+ R13: 00007ffc7210e6c8 R14: 0000000000000003 R15: 00000000ffffff9c
+  </TASK>
+
+The aforementioned deadlock can be consistently reproduced by running
+the script below:
+
+ audit-dupe-exe-deadlock.sh
+ --------------------------
+ #!/bin/bash
+ auditctl -D
+ mkdir -p /tmp/foo
+ touch /tmp/file
+ auditctl -a always,exit -F exe=/tmp/file -F path=/tmp/file -S all -k dr
+ mv /tmp/file /tmp/foo/file
+ rm -Rf /tmp/foo
+
+This patch fixes the issue by introducing struct audit_watch_ctx to pass
+the fsnotify event context down to audit_alloc_mark(). By utilizing the
+already-resolved directory inode provided by the event, we bypass the
+kern_path_parent() path resolution entirely, safely avoiding the
+recursive lock. Furthermore, it explicitly allows duplicate fsnotify
+marks (allow_dups = 1) during the rename update, allowing the new rule's
+mark to safely coexist with the old rule's mark until the old rule is
+freed.
+
+P.S.: This issue was identified and reproduced during a comprehensive
+code coverage analysis of the audit subsystem. The full report is
+available at the link below:
+
+https://people.redhat.com/rrobaina/audit-code-coverage-analysis.pdf
+
+P.P.S: With the permission of both Ricardo and Nathan, I've squashed a
+fixup patch from Nathan that addresses a compile time error when
+CONFIG_AUDITSYSCALL=n.
+
+Cc: stable@kernel.org
+Fixes: 34d99af52ad4 ("audit: implement audit by executable")
+Acked-by: Waiman Long <longman@redhat.com>
+Acked-by: Richard Guy Briggs <rgb@redhat.com>
+Signed-off-by: Nathan Chancellor <nathan@kernel.org>
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+[PM: move link metadata into the msg, apply fix from NC]
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/audit.h          |   17 ++++++++++++-----
+ kernel/audit_fsnotify.c |   32 +++++++++++++++++++++++---------
+ kernel/audit_watch.c    |   25 +++++++++++++++++--------
+ kernel/auditfilter.c    |    9 +++++----
+ 4 files changed, 57 insertions(+), 26 deletions(-)
+
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -256,8 +256,13 @@ extern int audit_del_rule(struct audit_e
+ extern void audit_free_rule_rcu(struct rcu_head *head);
+ extern struct list_head audit_filter_list[];
+-extern struct audit_entry *audit_dupe_rule(struct audit_krule *old);
++struct audit_watch_ctx {
++      struct inode *dir;
++      struct inode *child;
++};
++extern struct audit_entry *audit_dupe_rule(struct audit_krule *old,
++                                         struct audit_watch_ctx *ctx);
+ extern void audit_log_d_path_exe(struct audit_buffer *ab,
+                                struct mm_struct *mm);
+@@ -280,13 +285,15 @@ extern char *audit_watch_path(struct aud
+ extern int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev);
+ extern struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule,
+-                                                  char *pathname, int len);
++                                                  char *pathname, int len,
++                                                  struct audit_watch_ctx *ctx);
+ extern char *audit_mark_path(struct audit_fsnotify_mark *mark);
+ extern void audit_remove_mark(struct audit_fsnotify_mark *audit_mark);
+ extern void audit_remove_mark_rule(struct audit_krule *krule);
+ extern int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino,
+                             dev_t dev);
+-extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old);
++extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old,
++                        struct audit_watch_ctx *ctx);
+ extern int audit_exe_compare(struct task_struct *tsk,
+                            struct audit_fsnotify_mark *mark);
+@@ -317,13 +324,13 @@ extern struct list_head *audit_killed_tr
+ #define audit_watch_path(w) ""
+ #define audit_watch_compare(w, i, d) 0
+-#define audit_alloc_mark(k, p, l) (ERR_PTR(-EINVAL))
++#define audit_alloc_mark(k, p, l, c) (ERR_PTR(-EINVAL))
+ #define audit_mark_path(m) ""
+ #define audit_remove_mark(m) do { } while (0)
+ #define audit_remove_mark_rule(k) do { } while (0)
+ #define audit_mark_compare(m, i, d) 0
+ #define audit_exe_compare(t, m) (-EINVAL)
+-#define audit_dupe_exe(n, o) (-EINVAL)
++#define audit_dupe_exe(n, o, c) (-EINVAL)
+ #define audit_remove_tree_rule(rule) BUG()
+ #define audit_add_tree_rule(rule) -EINVAL
+--- a/kernel/audit_fsnotify.c
++++ b/kernel/audit_fsnotify.c
+@@ -71,19 +71,30 @@ static void audit_update_mark(struct aud
+       audit_mark->ino = inode ? inode->i_ino : AUDIT_INO_UNSET;
+ }
+-struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname, int len)
++struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname,
++                                           int len, struct audit_watch_ctx *ctx)
+ {
+       struct audit_fsnotify_mark *audit_mark;
+       struct path path;
+       struct dentry *dentry;
+-      int ret;
++      struct inode *dir, *child;
++      int ret, allow_dups;
+       if (pathname[0] != '/' || pathname[len-1] == '/')
+               return ERR_PTR(-EINVAL);
+-      dentry = kern_path_parent(pathname, &path);
+-      if (IS_ERR(dentry))
+-              return ERR_CAST(dentry); /* returning an error */
++      if (!ctx) {
++              dentry = kern_path_parent(pathname, &path);
++              if (IS_ERR(dentry))
++                      return ERR_CAST(dentry); /* returning an error */
++              dir = d_inode(path.dentry);
++              child = d_inode(dentry);
++              allow_dups = 0;
++      } else {
++              dir = ctx->dir;
++              child = ctx->child;
++              allow_dups = 1;
++      }
+       audit_mark = kzalloc_obj(*audit_mark);
+       if (unlikely(!audit_mark)) {
+@@ -94,18 +105,21 @@ struct audit_fsnotify_mark *audit_alloc_
+       fsnotify_init_mark(&audit_mark->mark, audit_fsnotify_group);
+       audit_mark->mark.mask = AUDIT_FS_EVENTS;
+       audit_mark->path = pathname;
+-      audit_update_mark(audit_mark, dentry->d_inode);
+       audit_mark->rule = krule;
+-      ret = fsnotify_add_inode_mark(&audit_mark->mark, path.dentry->d_inode, 0);
++      audit_update_mark(audit_mark, child);
++      ret = fsnotify_add_inode_mark(&audit_mark->mark, dir, allow_dups);
++
+       if (ret < 0) {
+               audit_mark->path = NULL;
+               fsnotify_put_mark(&audit_mark->mark);
+               audit_mark = ERR_PTR(ret);
+       }
+ out:
+-      dput(dentry);
+-      path_put(&path);
++      if (!ctx) {
++              dput(dentry);
++              path_put(&path);
++      }
+       return audit_mark;
+ }
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -244,7 +244,8 @@ static void audit_watch_log_rule_change(
+ /* Update inode info in audit rules based on filesystem event. */
+ static void audit_update_watch(struct audit_parent *parent,
+                              const struct qstr *dname, dev_t dev,
+-                             u64 ino, unsigned int invalidating)
++                             u64 ino, unsigned int invalidating,
++                             struct audit_watch_ctx *ctx)
+ {
+       struct audit_watch *owatch, *nwatch, *nextw;
+       struct audit_krule *r, *nextr;
+@@ -280,7 +281,7 @@ static void audit_update_watch(struct au
+                       list_del(&oentry->rule.rlist);
+                       list_del_rcu(&oentry->list);
+-                      nentry = audit_dupe_rule(&oentry->rule);
++                      nentry = audit_dupe_rule(&oentry->rule, ctx);
+                       if (IS_ERR(nentry)) {
+                               list_del(&oentry->rule.list);
+                               audit_panic("error updating watch, removing");
+@@ -479,10 +480,17 @@ static int audit_watch_handle_event(stru
+       if (WARN_ON_ONCE(inode_mark->group != audit_watch_group))
+               return 0;
+-      if (mask & (FS_CREATE|FS_MOVED_TO) && inode)
+-              audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0);
+-      else if (mask & (FS_DELETE|FS_MOVED_FROM))
+-              audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1);
++      if (mask & (FS_CREATE|FS_MOVED_TO) && inode) {
++              struct audit_watch_ctx ctx = { .dir = dir, .child = inode };
++
++              audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0,
++                                 &ctx);
++      } else if (mask & (FS_DELETE|FS_MOVED_FROM)) {
++              struct audit_watch_ctx ctx = { .dir = dir, .child = NULL };
++
++              audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1,
++                                 &ctx);
++      }
+       else if (mask & (FS_DELETE_SELF|FS_UNMOUNT|FS_MOVE_SELF))
+               audit_remove_parent_watches(parent);
+@@ -505,7 +513,8 @@ static int __init audit_watch_init(void)
+ }
+ device_initcall(audit_watch_init);
+-int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old)
++int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old,
++                 struct audit_watch_ctx *ctx)
+ {
+       struct audit_fsnotify_mark *audit_mark;
+       char *pathname;
+@@ -514,7 +523,7 @@ int audit_dupe_exe(struct audit_krule *n
+       if (!pathname)
+               return -ENOMEM;
+-      audit_mark = audit_alloc_mark(new, pathname, strlen(pathname));
++      audit_mark = audit_alloc_mark(new, pathname, strlen(pathname), ctx);
+       if (IS_ERR(audit_mark)) {
+               kfree(pathname);
+               return PTR_ERR(audit_mark);
+--- a/kernel/auditfilter.c
++++ b/kernel/auditfilter.c
+@@ -589,7 +589,7 @@ static struct audit_entry *audit_data_to
+                               err = PTR_ERR(str);
+                               goto exit_free;
+                       }
+-                      audit_mark = audit_alloc_mark(&entry->rule, str, f_val);
++                      audit_mark = audit_alloc_mark(&entry->rule, str, f_val, NULL);
+                       if (IS_ERR(audit_mark)) {
+                               kfree(str);
+                               err = PTR_ERR(audit_mark);
+@@ -816,7 +816,8 @@ static inline int audit_dupe_lsm_field(s
+  * rule with the new rule in the filterlist, then free the old rule.
+  * The rlist element is undefined; list manipulations are handled apart from
+  * the initial copy. */
+-struct audit_entry *audit_dupe_rule(struct audit_krule *old)
++struct audit_entry *audit_dupe_rule(struct audit_krule *old,
++                                  struct audit_watch_ctx *ctx)
+ {
+       u32 fcount = old->field_count;
+       struct audit_entry *entry;
+@@ -875,7 +876,7 @@ struct audit_entry *audit_dupe_rule(stru
+                               new->filterkey = fk;
+                       break;
+               case AUDIT_EXE:
+-                      err = audit_dupe_exe(new, old);
++                      err = audit_dupe_exe(new, old, ctx);
+                       break;
+               }
+               if (err) {
+@@ -1414,7 +1415,7 @@ static int update_lsm_rule(struct audit_
+       if (!security_audit_rule_known(r))
+               return 0;
+-      nentry = audit_dupe_rule(r);
++      nentry = audit_dupe_rule(r, NULL);
+       if (entry->rule.exe)
+               audit_remove_mark(entry->rule.exe);
+       if (IS_ERR(nentry)) {
diff --git a/queue-7.1/audit-use-unsigned-int-instead-of-unsigned.patch b/queue-7.1/audit-use-unsigned-int-instead-of-unsigned.patch
new file mode 100644 (file)
index 0000000..05dbf7d
--- /dev/null
@@ -0,0 +1,202 @@
+From stable+bounces-280757-greg=kroah.com@vger.kernel.org Tue Jul 21 23:11:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 14:01:48 -0400
+Subject: audit: use 'unsigned int' instead of 'unsigned'
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721180149.286443-1-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 8b226771014beab1292081151a99530886ce54b4 ]
+
+Address checkpatch.pl warning below, across the audit subsystem:
+
+  WARNING: Prefer 'unsigned int' to bare use of 'unsigned'
+
+Minor cleanup, no functional changes.
+
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/audit.h      |    4 ++--
+ include/linux/audit_arch.h |   12 ++++++------
+ kernel/audit.c             |    2 +-
+ kernel/audit.h             |    2 +-
+ kernel/audit_tree.c        |    2 +-
+ kernel/audit_watch.c       |    2 +-
+ kernel/auditfilter.c       |    8 ++++----
+ kernel/auditsc.c           |    2 +-
+ lib/compat_audit.c         |   12 ++++++------
+ 9 files changed, 23 insertions(+), 23 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -133,8 +133,8 @@ enum audit_nfcfgop {
+       AUDIT_NFT_OP_INVALID,
+ };
+-extern int __init audit_register_class(int class, unsigned *list);
+-extern int audit_classify_syscall(int abi, unsigned syscall);
++extern int __init audit_register_class(int class, unsigned int *list);
++extern int audit_classify_syscall(int abi, unsigned int syscall);
+ extern int audit_classify_arch(int arch);
+ /* audit_names->type values */
+--- a/include/linux/audit_arch.h
++++ b/include/linux/audit_arch.h
+@@ -21,13 +21,13 @@ enum auditsc_class_t {
+       AUDITSC_NVALS /* count */
+ };
+-extern int audit_classify_compat_syscall(int abi, unsigned syscall);
++extern int audit_classify_compat_syscall(int abi, unsigned int syscall);
+ /* only for compat system calls */
+-extern unsigned compat_write_class[];
+-extern unsigned compat_read_class[];
+-extern unsigned compat_dir_class[];
+-extern unsigned compat_chattr_class[];
+-extern unsigned compat_signal_class[];
++extern unsigned int compat_write_class[];
++extern unsigned int compat_read_class[];
++extern unsigned int compat_dir_class[];
++extern unsigned int compat_chattr_class[];
++extern unsigned int compat_signal_class[];
+ #endif
+--- a/kernel/audit.c
++++ b/kernel/audit.c
+@@ -2035,7 +2035,7 @@ void audit_log_vformat(struct audit_buff
+                * here and AUDIT_BUFSIZ is at least 1024, then we can
+                * log everything that printk could have logged. */
+               avail = audit_expand(ab,
+-                      max_t(unsigned, AUDIT_BUFSIZ, 1+len-avail));
++                      max_t(unsigned int, AUDIT_BUFSIZ, 1+len-avail));
+               if (!avail)
+                       goto out_va_end;
+               len = vsnprintf(skb_tail_pointer(skb), avail, fmt, args2);
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -233,7 +233,7 @@ static inline int audit_hash_ino(u64 ino
+ /* Indicates that audit should log the full pathname. */
+ #define AUDIT_NAME_FULL -1
+-extern int audit_match_class(int class, unsigned syscall);
++extern int audit_match_class(int class, unsigned int syscall);
+ extern int audit_comparator(const u32 left, const u32 op, const u32 right);
+ extern int audit_uid_comparator(kuid_t left, u32 op, kuid_t right);
+ extern int audit_gid_comparator(kgid_t left, u32 op, kgid_t right);
+--- a/kernel/audit_tree.c
++++ b/kernel/audit_tree.c
+@@ -33,7 +33,7 @@ struct audit_chunk {
+       struct audit_node {
+               struct list_head list;
+               struct audit_tree *owner;
+-              unsigned index;         /* index; upper bit indicates 'will prune' */
++              unsigned int index;     /* index; upper bit indicates 'will prune' */
+       } owners[] __counted_by(count);
+ };
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -244,7 +244,7 @@ static void audit_watch_log_rule_change(
+ /* Update inode info in audit rules based on filesystem event. */
+ static void audit_update_watch(struct audit_parent *parent,
+                              const struct qstr *dname, dev_t dev,
+-                             u64 ino, unsigned invalidating)
++                             u64 ino, unsigned int invalidating)
+ {
+       struct audit_watch *owatch, *nwatch, *nextw;
+       struct audit_krule *r, *nextr;
+--- a/kernel/auditfilter.c
++++ b/kernel/auditfilter.c
+@@ -165,13 +165,13 @@ static inline int audit_to_inode(struct
+ static __u32 *classes[AUDIT_SYSCALL_CLASSES];
+-int __init audit_register_class(int class, unsigned *list)
++int __init audit_register_class(int class, unsigned int *list)
+ {
+       __u32 *p = kcalloc(AUDIT_BITMASK_SIZE, sizeof(__u32), GFP_KERNEL);
+       if (!p)
+               return -ENOMEM;
+       while (*list != ~0U) {
+-              unsigned n = *list++;
++              unsigned int n = *list++;
+               if (n >= AUDIT_BITMASK_SIZE * 32 - AUDIT_SYSCALL_CLASSES) {
+                       kfree(p);
+                       return -EINVAL;
+@@ -186,7 +186,7 @@ int __init audit_register_class(int clas
+       return 0;
+ }
+-int audit_match_class(int class, unsigned syscall)
++int audit_match_class(int class, unsigned int syscall)
+ {
+       if (unlikely(syscall >= AUDIT_BITMASK_SIZE * 32))
+               return 0;
+@@ -237,7 +237,7 @@ static int audit_match_signal(struct aud
+ /* Common user-space to kernel rule translation. */
+ static inline struct audit_entry *audit_to_entry_common(struct audit_rule_data *rule)
+ {
+-      unsigned listnr;
++      unsigned int listnr;
+       struct audit_entry *entry;
+       int i, err;
+--- a/kernel/auditsc.c
++++ b/kernel/auditsc.c
+@@ -150,7 +150,7 @@ static const struct audit_nfcfgop_tab au
+ static int audit_match_perm(struct audit_context *ctx, int mask)
+ {
+-      unsigned n;
++      unsigned int n;
+       if (unlikely(!ctx))
+               return 0;
+--- a/lib/compat_audit.c
++++ b/lib/compat_audit.c
+@@ -4,32 +4,32 @@
+ #include <linux/audit_arch.h>
+ #include <asm/unistd32.h>
+-unsigned compat_dir_class[] = {
++unsigned int compat_dir_class[] = {
+ #include <asm-generic/audit_dir_write.h>
+ ~0U
+ };
+-unsigned compat_read_class[] = {
++unsigned int compat_read_class[] = {
+ #include <asm-generic/audit_read.h>
+ ~0U
+ };
+-unsigned compat_write_class[] = {
++unsigned int compat_write_class[] = {
+ #include <asm-generic/audit_write.h>
+ ~0U
+ };
+-unsigned compat_chattr_class[] = {
++unsigned int compat_chattr_class[] = {
+ #include <asm-generic/audit_change_attr.h>
+ ~0U
+ };
+-unsigned compat_signal_class[] = {
++unsigned int compat_signal_class[] = {
+ #include <asm-generic/audit_signal.h>
+ ~0U
+ };
+-int audit_classify_compat_syscall(int abi, unsigned syscall)
++int audit_classify_compat_syscall(int abi, unsigned int syscall)
+ {
+       switch (syscall) {
+ #ifdef __NR_open
diff --git a/queue-7.1/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch b/queue-7.1/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
new file mode 100644 (file)
index 0000000..31bcc21
--- /dev/null
@@ -0,0 +1,97 @@
+From stable+bounces-290075-greg=kroah.com@vger.kernel.org Tue Jul 28 19:10:17 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 12:10:43 -0400
+Subject: bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()
+To: stable@vger.kernel.org
+Cc: Breno Leitao <leitao@debian.org>, "Masami Hiramatsu (Google)" <mhiramat@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728161043.36478-2-sashal@kernel.org>
+
+From: Breno Leitao <leitao@debian.org>
+
+[ Upstream commit dec4d8118c179b3d12bca7e609054c6011c4f2ce ]
+
+xbc_snprint_cmdline() is meant to be called twice: first with
+buf=NULL, size=0 to probe the rendered length, then with a real
+buffer to fill it (the standard snprintf() two-pass pattern). The
+probe call makes the function compute "buf + size" (NULL + 0) and,
+on every iteration, advance "buf += ret" from that NULL base and
+pass the result back into snprintf().
+
+Pointer arithmetic on a NULL pointer is undefined behavior. It is
+harmless in the in-kernel callers today, but the follow-up patches
+run this same code in the userspace tools/bootconfig parser at kernel
+build time, where host UBSan / FORTIFY_SOURCE abort the build.
+
+Track a running written length (size_t) instead of mutating @buf, and
+only form "buf + len" when @buf is non-NULL. snprintf(NULL, 0, ...)
+is itself well defined and returns the would-be length, so the
+two-pass "probe then fill" usage returns identical byte counts.
+
+Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-1-24ab72139c29@debian.org/
+
+Fixes: 51887d03aca1 ("bootconfig: init: Allow admin to use bootconfig for kernel command line")
+Cc: stable@vger.kernel.org
+Signed-off-by: Breno Leitao <leitao@debian.org>
+Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ lib/bootconfig.c |   23 ++++++++++++++++-------
+ 1 file changed, 16 insertions(+), 7 deletions(-)
+
+--- a/lib/bootconfig.c
++++ b/lib/bootconfig.c
+@@ -427,10 +427,18 @@ static char xbc_namebuf[XBC_KEYLEN_MAX]
+ int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root)
+ {
+       struct xbc_node *knode, *vnode;
+-      char *end = buf + size;
+       const char *val, *q;
++      size_t len = 0;
+       int ret;
++      /*
++       * Track the running written length rather than advancing @buf, so we
++       * never form "buf + size" or "buf += ret" while @buf is NULL (the
++       * size-probe call passes buf=NULL, size=0). NULL pointer arithmetic
++       * is undefined behavior and trips host UBSan / FORTIFY_SOURCE when
++       * this renderer runs at kernel build time. snprintf(NULL, 0, ...)
++       * itself is well defined and returns the would-be length.
++       */
+       xbc_node_for_each_key_value(root, knode, val) {
+               ret = xbc_node_compose_key_after(root, knode,
+                                       xbc_namebuf, XBC_KEYLEN_MAX);
+@@ -439,10 +447,11 @@ int __init xbc_snprint_cmdline(char *buf
+               vnode = xbc_node_get_child(knode);
+               if (!vnode) {
+-                      ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
++                      ret = snprintf(buf ? buf + len : NULL, rest(len, size),
++                                     "%s ", xbc_namebuf);
+                       if (ret < 0)
+                               return ret;
+-                      buf += ret;
++                      len += ret;
+                       continue;
+               }
+               xbc_array_for_each_value(vnode, val) {
+@@ -452,15 +461,15 @@ int __init xbc_snprint_cmdline(char *buf
+                        * whitespace.
+                        */
+                       q = strpbrk(val, " \t\r\n") ? "\"" : "";
+-                      ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
+-                                     xbc_namebuf, q, val, q);
++                      ret = snprintf(buf ? buf + len : NULL, rest(len, size),
++                                     "%s=%s%s%s ", xbc_namebuf, q, val, q);
+                       if (ret < 0)
+                               return ret;
+-                      buf += ret;
++                      len += ret;
+               }
+       }
+-      return buf - (end - size);
++      return len;
+ }
+ #undef rest
diff --git a/queue-7.1/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch b/queue-7.1/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
new file mode 100644 (file)
index 0000000..77176b4
--- /dev/null
@@ -0,0 +1,165 @@
+From stable+bounces-290074-greg=kroah.com@vger.kernel.org Tue Jul 28 18:58:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 12:10:42 -0400
+Subject: bootconfig: move xbc_snprint_cmdline() to lib/bootconfig.c
+To: stable@vger.kernel.org
+Cc: Breno Leitao <leitao@debian.org>, "Masami Hiramatsu (Google)" <mhiramat@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728161043.36478-1-sashal@kernel.org>
+
+From: Breno Leitao <leitao@debian.org>
+
+[ Upstream commit 5a643e4623238e14b03d75ca0d4eda0645720cee ]
+
+Move xbc_snprint_cmdline() from init/main.c to lib/bootconfig.c so the
+function (and its xbc_namebuf scratch buffer) becomes part of the shared
+parser library. tools/bootconfig already compiles lib/bootconfig.c
+directly, which lets a follow-up patch reuse the same renderer in the
+userspace tool to convert a bootconfig file into a flat cmdline string
+at build time.
+
+No functional change.
+
+Link: https://lore.kernel.org/all/20260508-bootconfig_using_tools-v1-1-1132219aa773@debian.org/
+
+Signed-off-by: Breno Leitao <leitao@debian.org>
+Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Stable-dep-of: dec4d8118c17 ("bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/bootconfig.h |    3 ++
+ init/main.c                |   45 ------------------------------------
+ lib/bootconfig.c           |   56 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 59 insertions(+), 45 deletions(-)
+
+--- a/include/linux/bootconfig.h
++++ b/include/linux/bootconfig.h
+@@ -265,6 +265,9 @@ static inline struct xbc_node * __init x
+ int __init xbc_node_compose_key_after(struct xbc_node *root,
+                       struct xbc_node *node, char *buf, size_t size);
++/* Render key/value pairs under @root as a flat cmdline string */
++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root);
++
+ /**
+  * xbc_node_compose_key() - Compose full key string of the XBC node
+  * @node: An XBC node.
+--- a/init/main.c
++++ b/init/main.c
+@@ -324,51 +324,6 @@ static void * __init get_boot_config_fro
+ #ifdef CONFIG_BOOT_CONFIG
+-static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata;
+-
+-#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
+-
+-static int __init xbc_snprint_cmdline(char *buf, size_t size,
+-                                    struct xbc_node *root)
+-{
+-      struct xbc_node *knode, *vnode;
+-      char *end = buf + size;
+-      const char *val, *q;
+-      int ret;
+-
+-      xbc_node_for_each_key_value(root, knode, val) {
+-              ret = xbc_node_compose_key_after(root, knode,
+-                                      xbc_namebuf, XBC_KEYLEN_MAX);
+-              if (ret < 0)
+-                      return ret;
+-
+-              vnode = xbc_node_get_child(knode);
+-              if (!vnode) {
+-                      ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
+-                      if (ret < 0)
+-                              return ret;
+-                      buf += ret;
+-                      continue;
+-              }
+-              xbc_array_for_each_value(vnode, val) {
+-                      /*
+-                       * For prettier and more readable /proc/cmdline, only
+-                       * quote the value when necessary, i.e. when it contains
+-                       * whitespace.
+-                       */
+-                      q = strpbrk(val, " \t\r\n") ? "\"" : "";
+-                      ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
+-                                     xbc_namebuf, q, val, q);
+-                      if (ret < 0)
+-                              return ret;
+-                      buf += ret;
+-              }
+-      }
+-
+-      return buf - (end - size);
+-}
+-#undef rest
+-
+ /* Make an extra command line under given key word */
+ static char * __init xbc_make_cmdline(const char *key)
+ {
+--- a/lib/bootconfig.c
++++ b/lib/bootconfig.c
+@@ -408,6 +408,62 @@ const char * __init xbc_node_find_next_k
+               return "";      /* No value key */
+ }
++static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata;
++
++#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
++
++/**
++ * xbc_snprint_cmdline() - Render bootconfig keys under @root as a cmdline string
++ * @buf: Destination buffer (may be NULL when @size is 0 to query the length)
++ * @size: Size of @buf in bytes
++ * @root: Subtree root whose key=value pairs should be rendered
++ *
++ * Walk all key/value pairs under @root and emit them as a space-separated
++ * cmdline string into @buf. Values containing whitespace are quoted with
++ * double quotes. Returns the number of bytes that would be written if @buf
++ * were large enough (matching snprintf semantics), or a negative errno on
++ * failure.
++ */
++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root)
++{
++      struct xbc_node *knode, *vnode;
++      char *end = buf + size;
++      const char *val, *q;
++      int ret;
++
++      xbc_node_for_each_key_value(root, knode, val) {
++              ret = xbc_node_compose_key_after(root, knode,
++                                      xbc_namebuf, XBC_KEYLEN_MAX);
++              if (ret < 0)
++                      return ret;
++
++              vnode = xbc_node_get_child(knode);
++              if (!vnode) {
++                      ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
++                      if (ret < 0)
++                              return ret;
++                      buf += ret;
++                      continue;
++              }
++              xbc_array_for_each_value(vnode, val) {
++                      /*
++                       * For prettier and more readable /proc/cmdline, only
++                       * quote the value when necessary, i.e. when it contains
++                       * whitespace.
++                       */
++                      q = strpbrk(val, " \t\r\n") ? "\"" : "";
++                      ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
++                                     xbc_namebuf, q, val, q);
++                      if (ret < 0)
++                              return ret;
++                      buf += ret;
++              }
++      }
++
++      return buf - (end - size);
++}
++#undef rest
++
+ /* XBC parse and tree build */
+ static int __init xbc_init_node(struct xbc_node *node, char *data, uint16_t flag)
diff --git a/queue-7.1/fuse-uring-fix-race-between-registration-and-connection-abortion.patch b/queue-7.1/fuse-uring-fix-race-between-registration-and-connection-abortion.patch
new file mode 100644 (file)
index 0000000..e7c12db
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-287812-greg=kroah.com@vger.kernel.org Wed Jul 22 13:34:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:24:31 -0400
+Subject: fuse-uring: fix race between registration and connection abortion
+To: stable@vger.kernel.org
+Cc: Joanne Koong <joannelkoong@gmail.com>, Bernd Schubert <bernd@bsbernd.com>, Miklos Szeredi <mszeredi@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722112431.1115514-1-sashal@kernel.org>
+
+From: Joanne Koong <joannelkoong@gmail.com>
+
+[ Upstream commit 952b5d36f6a298f57c52a59e72076c69386a8aaf ]
+
+This fixes this race:
+- thread a: io_uring_enter -> register sqe ->
+  fuse_uring_create_ring_ent -> allocate ent but doesn't grab queue_ref
+  yet
+- thread b: fuse_conn_destroy() -> fuse_chan_abort() ->
+  fuse_uring_abort() is a no-op due to queue ref being 0
+- thread a: grabs the queue_ref, queue_ref is now 1, rest of
+  fuse_uring_do_register() logic executes
+- thread b: fuse_chan_abort() returns, fuse_chan_wait_aborted() now runs
+  and calls
+  "wait_event(ring->stop_waitq, atomic_read(&ring->queue_refs) == 0);"
+The abort/unmount thread will hang indefinitely in unkillable state as
+nothing will decrement queue_refs or wake stop_waitq, and the ring,
+queue, and ent are leaked.
+
+Fix this by checking fch->connected under fch->lock after the created
+ent has grabbed a ref count on the queue. This ensures that in the
+scenario above, it is guaranteed that we either release the queue ref
+and wake up stop_waitq (in case fuse_chan_wait_aborted() is already
+waiting) in fuse_uring_do_register() when we detect !fch->connected, or
+if the connection is aborted after the check, it is guaranteed that the
+async teardown worker will be running in the background cleaning up ents
+and decrementing the ent's ref on the queue, which will unblock the
+eventual queue and ring teardown.
+
+Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands")
+Cc: stable@vger.kernel.org
+Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
+Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+[ changed fch->lock/fch->connected references to fc->lock/fc->connected since struct fuse_chan does not exist in this tree ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c |   22 ++++++++++++++++------
+ 1 file changed, 16 insertions(+), 6 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -995,15 +995,26 @@ static bool is_ring_ready(struct fuse_ri
+ /*
+  * fuse_uring_req_fetch command handling
+  */
+-static void fuse_uring_do_register(struct fuse_ring_ent *ent,
+-                                 struct io_uring_cmd *cmd,
+-                                 unsigned int issue_flags)
++static int fuse_uring_do_register(struct fuse_ring_ent *ent,
++                                struct io_uring_cmd *cmd,
++                                unsigned int issue_flags)
+ {
+       struct fuse_ring_queue *queue = ent->queue;
+       struct fuse_ring *ring = queue->ring;
+       struct fuse_conn *fc = ring->fc;
+       struct fuse_iqueue *fiq = &fc->iq;
++      spin_lock(&fc->lock);
++      /* abort teardown path is running or has run */
++      if (!fc->connected) {
++              spin_unlock(&fc->lock);
++              if (atomic_dec_and_test(&ring->queue_refs))
++                      wake_up_all(&ring->stop_waitq);
++              kfree(ent);
++              return -ECONNABORTED;
++      }
++      spin_unlock(&fc->lock);
++
+       fuse_uring_prepare_cancel(cmd, issue_flags, ent);
+       spin_lock(&queue->lock);
+@@ -1020,6 +1031,7 @@ static void fuse_uring_do_register(struc
+                       wake_up_all(&fc->blocked_waitq);
+               }
+       }
++      return 0;
+ }
+ /*
+@@ -1136,9 +1148,7 @@ static int fuse_uring_register(struct io
+       if (IS_ERR(ent))
+               return PTR_ERR(ent);
+-      fuse_uring_do_register(ent, cmd, issue_flags);
+-
+-      return 0;
++      return fuse_uring_do_register(ent, cmd, issue_flags);
+ }
+ /*
diff --git a/queue-7.1/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch b/queue-7.1/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch
new file mode 100644 (file)
index 0000000..e59b2ae
--- /dev/null
@@ -0,0 +1,112 @@
+From stable+bounces-288837-greg=kroah.com@vger.kernel.org Fri Jul 24 13:00:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 06:49:06 -0400
+Subject: mm/sparse-vmemmap: fix DAX vmemmap accounting with optimization
+To: stable@vger.kernel.org
+Cc: Muchun Song <songmuchun@bytedance.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Oscar Salvador <osalvador@suse.de>, "David Hildenbrand (Arm)" <david@kernel.org>, "Liam R. Howlett" <liam@infradead.org>, "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>, Joao Martins <joao.m.martins@oracle.com>, Lorenzo Stoakes <ljs@kernel.org>, Madhavan Srinivasan <maddy@linux.ibm.com>, Michael Ellerman <mpe@ellerman.id.au>, Michal Hocko <mhocko@suse.com>, Nicholas Piggin <npiggin@gmail.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724104906.3974254-4-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit 721a73e30c9e3e8fcffe1725bcede1bbd20b4918 ]
+
+When vmemmap optimization is enabled for DAX, the nr_memmap_pages counter
+in /proc/vmstat is incorrect.  The current code always accounts for the
+full, non-optimized vmemmap size, but vmemmap optimization reduces the
+actual number of vmemmap pages by reusing tail pages.  This causes the
+system to overcount vmemmap usage, leading to inaccurate page statistics
+in /proc/vmstat.
+
+Fix this by introducing section_nr_vmemmap_pages(), which returns the
+exact vmemmap page count for a given pfn range based on whether
+optimization is in effect.
+
+Link: https://lore.kernel.org/20260428081855.1249045-5-songmuchun@bytedance.com
+Fixes: 15995a352474 ("mm: report per-page metadata information")
+Signed-off-by: Muchun Song <songmuchun@bytedance.com>
+Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Acked-by: Oscar Salvador <osalvador@suse.de>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Acked-by: Liam R. Howlett <liam@infradead.org>
+Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
+Cc: Joao Martins <joao.m.martins@oracle.com>
+Cc: Lorenzo Stoakes <ljs@kernel.org>
+Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
+Cc: Michael Ellerman <mpe@ellerman.id.au>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/sparse-vmemmap.c |   34 ++++++++++++++++++++++++++++++----
+ 1 file changed, 30 insertions(+), 4 deletions(-)
+
+--- a/mm/sparse-vmemmap.c
++++ b/mm/sparse-vmemmap.c
+@@ -652,6 +652,31 @@ void offline_mem_sections(unsigned long
+       }
+ }
++static int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
++              struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
++{
++      const unsigned int order = pgmap ? pgmap->vmemmap_shift : 0;
++      const unsigned long pages_per_compound = 1UL << order;
++
++      VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
++      VM_WARN_ON_ONCE(nr_pages > PAGES_PER_SECTION);
++
++      if (!vmemmap_can_optimize(altmap, pgmap))
++              return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
++
++      if (order < PFN_SECTION_SHIFT) {
++              VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, pages_per_compound));
++              return VMEMMAP_RESERVE_NR * nr_pages / pages_per_compound;
++      }
++
++      VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION));
++
++      if (IS_ALIGNED(pfn, pages_per_compound))
++              return VMEMMAP_RESERVE_NR;
++
++      return 0;
++}
++
+ static struct page * __meminit populate_section_memmap(unsigned long pfn,
+               unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
+               struct dev_pagemap *pgmap)
+@@ -659,7 +684,7 @@ static struct page * __meminit populate_
+       struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap,
+                                                     pgmap);
+-      memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
++      memmap_pages_add(section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+       return page;
+ }
+@@ -670,7 +695,7 @@ static void depopulate_section_memmap(un
+       unsigned long start = (unsigned long) pfn_to_page(pfn);
+       unsigned long end = start + nr_pages * sizeof(struct page);
+-      memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
++      memmap_pages_add(-section_nr_vmemmap_pages(pfn, nr_pages, altmap, pgmap));
+       vmemmap_free(start, end, altmap);
+ }
+@@ -678,9 +703,10 @@ static void free_map_bootmem(struct page
+ {
+       unsigned long start = (unsigned long)memmap;
+       unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
++      unsigned long pfn = page_to_pfn(memmap);
+-      memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
+-                                                PAGE_SIZE)));
++      memmap_boot_pages_add(-section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
++                                                      NULL, NULL));
+       vmemmap_free(start, end, NULL);
+ }
diff --git a/queue-7.1/mm-sparse-vmemmap-pass-pgmap-argument-to-memory-deactivation-paths.patch b/queue-7.1/mm-sparse-vmemmap-pass-pgmap-argument-to-memory-deactivation-paths.patch
new file mode 100644 (file)
index 0000000..dd95c1f
--- /dev/null
@@ -0,0 +1,324 @@
+From stable+bounces-288835-greg=kroah.com@vger.kernel.org Fri Jul 24 12:58:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 06:49:05 -0400
+Subject: mm/sparse-vmemmap: pass @pgmap argument to memory deactivation paths
+To: stable@vger.kernel.org
+Cc: Muchun Song <songmuchun@bytedance.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Oscar Salvador <osalvador@suse.de>, "David Hildenbrand (Arm)" <david@kernel.org>, "Liam R. Howlett" <liam@infradead.org>, "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>, Joao Martins <joao.m.martins@oracle.com>, Lorenzo Stoakes <ljs@kernel.org>, Madhavan Srinivasan <maddy@linux.ibm.com>, Michael Ellerman <mpe@ellerman.id.au>, Michal Hocko <mhocko@suse.com>, Nicholas Piggin <npiggin@gmail.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724104906.3974254-3-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit 3bbc54dd1b62f1a4b218c70aafbeceeba7c90c5d ]
+
+Currently, the memory hot-remove call chain -- arch_remove_memory(),
+__remove_pages(), sparse_remove_section() and section_deactivate() -- does
+not carry the struct dev_pagemap pointer.  This prevents the lower levels
+from knowing whether the section was originally populated with vmemmap
+optimizations (e.g., DAX with vmemmap optimization enabled).
+
+Without this information, we cannot call vmemmap_can_optimize() to
+determine if the vmemmap pages were optimized.  As a result, the vmemmap
+page accounting during teardown will mistakenly assume a non-optimized
+allocation, leading to incorrect memmap statistics.
+
+To lay the groundwork for fixing the vmemmap page accounting, we need to
+pass the @pgmap pointer down to the deactivation location.  Plumb the
+@pgmap argument through the APIs of arch_remove_memory(), __remove_pages()
+and sparse_remove_section(), mirroring the corresponding *_activate()
+paths.
+
+Link: https://lore.kernel.org/20260428081855.1249045-4-songmuchun@bytedance.com
+Signed-off-by: Muchun Song <songmuchun@bytedance.com>
+Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Reviewed-by: Oscar Salvador <osalvador@suse.de>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Acked-by: Liam R. Howlett <liam@infradead.org>
+Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
+Cc: Joao Martins <joao.m.martins@oracle.com>
+Cc: Lorenzo Stoakes <ljs@kernel.org>
+Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
+Cc: Michael Ellerman <mpe@ellerman.id.au>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Stable-dep-of: 721a73e30c9e ("mm/sparse-vmemmap: fix DAX vmemmap accounting with optimization")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/arm64/mm/mmu.c            |    5 +++--
+ arch/loongarch/mm/init.c       |    5 +++--
+ arch/powerpc/mm/mem.c          |    5 +++--
+ arch/riscv/mm/init.c           |    5 +++--
+ arch/s390/mm/init.c            |    5 +++--
+ arch/x86/mm/init_64.c          |    5 +++--
+ include/linux/memory_hotplug.h |    8 +++++---
+ mm/memory_hotplug.c            |   13 +++++++------
+ mm/memremap.c                  |    4 ++--
+ mm/sparse-vmemmap.c            |   12 ++++++------
+ 10 files changed, 38 insertions(+), 29 deletions(-)
+
+--- a/arch/arm64/mm/mmu.c
++++ b/arch/arm64/mm/mmu.c
+@@ -2032,12 +2032,13 @@ err:
+       return ret;
+ }
+-void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                      struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+       __remove_pgd_mapping(swapper_pg_dir, __phys_to_virt(start), size);
+ }
+--- a/arch/loongarch/mm/init.c
++++ b/arch/loongarch/mm/init.c
+@@ -119,12 +119,13 @@ int arch_add_memory(int nid, u64 start,
+       return ret;
+ }
+-void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                      struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+ }
+ #endif
+--- a/arch/powerpc/mm/mem.c
++++ b/arch/powerpc/mm/mem.c
+@@ -158,12 +158,13 @@ int __ref arch_add_memory(int nid, u64 s
+       return rc;
+ }
+-void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                            struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+       arch_remove_linear_mapping(start, size);
+ }
+ #endif
+--- a/arch/riscv/mm/init.c
++++ b/arch/riscv/mm/init.c
+@@ -1742,9 +1742,10 @@ int __ref arch_add_memory(int nid, u64 s
+       return ret;
+ }
+-void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                            struct dev_pagemap *pgmap)
+ {
+-      __remove_pages(start >> PAGE_SHIFT, size >> PAGE_SHIFT, altmap);
++      __remove_pages(start >> PAGE_SHIFT, size >> PAGE_SHIFT, altmap, pgmap);
+       remove_linear_mapping(start, size);
+       flush_tlb_all();
+ }
+--- a/arch/s390/mm/init.c
++++ b/arch/s390/mm/init.c
+@@ -276,12 +276,13 @@ int arch_add_memory(int nid, u64 start,
+       return rc;
+ }
+-void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                      struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+       vmem_remove_mapping(start, size);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
+--- a/arch/x86/mm/init_64.c
++++ b/arch/x86/mm/init_64.c
+@@ -1300,12 +1300,13 @@ kernel_physical_mapping_remove(unsigned
+       remove_pagetable(start, end, true, NULL);
+ }
+-void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap)
++void __ref arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                            struct dev_pagemap *pgmap)
+ {
+       unsigned long start_pfn = start >> PAGE_SHIFT;
+       unsigned long nr_pages = size >> PAGE_SHIFT;
+-      __remove_pages(start_pfn, nr_pages, altmap);
++      __remove_pages(start_pfn, nr_pages, altmap, pgmap);
+       kernel_physical_mapping_remove(start, start + size);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
+--- a/include/linux/memory_hotplug.h
++++ b/include/linux/memory_hotplug.h
+@@ -135,9 +135,10 @@ static inline bool movable_node_is_enabl
+       return movable_node_enabled;
+ }
+-extern void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap);
++extern void arch_remove_memory(u64 start, u64 size, struct vmem_altmap *altmap,
++                             struct dev_pagemap *pgmap);
+ extern void __remove_pages(unsigned long start_pfn, unsigned long nr_pages,
+-                         struct vmem_altmap *altmap);
++                         struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
+ /* reasonably generic interface to expand the physical pages */
+ extern int __add_pages(int nid, unsigned long start_pfn, unsigned long nr_pages,
+@@ -307,7 +308,8 @@ extern int sparse_add_section(int nid, u
+               unsigned long nr_pages, struct vmem_altmap *altmap,
+               struct dev_pagemap *pgmap);
+ extern void sparse_remove_section(unsigned long pfn, unsigned long nr_pages,
+-                                struct vmem_altmap *altmap);
++                                struct vmem_altmap *altmap,
++                                struct dev_pagemap *pgmap);
+ extern struct zone *zone_for_pfn_range(enum mmop online_type,
+               int nid, struct memory_group *group, unsigned long start_pfn,
+               unsigned long nr_pages);
+--- a/mm/memory_hotplug.c
++++ b/mm/memory_hotplug.c
+@@ -576,6 +576,7 @@ void remove_pfn_range_from_zone(struct z
+  * @pfn: starting pageframe (must be aligned to start of a section)
+  * @nr_pages: number of pages to remove (must be multiple of section size)
+  * @altmap: alternative device page map or %NULL if default memmap is used
++ * @pgmap: device page map or %NULL if not ZONE_DEVICE
+  *
+  * Generic helper function to remove section mappings and sysfs entries
+  * for the section of the memory we are removing. Caller needs to make
+@@ -583,7 +584,7 @@ void remove_pfn_range_from_zone(struct z
+  * calling offline_pages().
+  */
+ void __remove_pages(unsigned long pfn, unsigned long nr_pages,
+-                  struct vmem_altmap *altmap)
++                  struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+ {
+       const unsigned long end_pfn = pfn + nr_pages;
+       unsigned long cur_nr_pages;
+@@ -598,7 +599,7 @@ void __remove_pages(unsigned long pfn, u
+               /* Select all remaining pages up to the next section boundary */
+               cur_nr_pages = min(end_pfn - pfn,
+                                  SECTION_ALIGN_UP(pfn + 1) - pfn);
+-              sparse_remove_section(pfn, cur_nr_pages, altmap);
++              sparse_remove_section(pfn, cur_nr_pages, altmap, pgmap);
+       }
+ }
+@@ -1427,7 +1428,7 @@ static void remove_memory_blocks_and_alt
+               remove_memory_block_devices(cur_start, memblock_size);
+-              arch_remove_memory(cur_start, memblock_size, altmap);
++              arch_remove_memory(cur_start, memblock_size, altmap, NULL);
+               /* Verify that all vmemmap pages have actually been freed. */
+               WARN(altmap->alloc, "Altmap not fully unmapped");
+@@ -1470,7 +1471,7 @@ static int create_altmaps_and_memory_blo
+               ret = create_memory_block_devices(cur_start, memblock_size, nid,
+                                                 params.altmap, group);
+               if (ret) {
+-                      arch_remove_memory(cur_start, memblock_size, params.altmap);
++                      arch_remove_memory(cur_start, memblock_size, params.altmap, NULL);
+                       kfree(params.altmap);
+                       goto out;
+               }
+@@ -1556,7 +1557,7 @@ int add_memory_resource(int nid, struct
+               /* create memory block devices after memory was added */
+               ret = create_memory_block_devices(start, size, nid, NULL, group);
+               if (ret) {
+-                      arch_remove_memory(start, size, params.altmap);
++                      arch_remove_memory(start, size, params.altmap, NULL);
+                       goto error;
+               }
+       }
+@@ -2268,7 +2269,7 @@ static int try_remove_memory(u64 start,
+                * No altmaps present, do the removal directly
+                */
+               remove_memory_block_devices(start, size);
+-              arch_remove_memory(start, size, NULL);
++              arch_remove_memory(start, size, NULL, NULL);
+       } else {
+               /* all memblocks in the range have altmaps */
+               remove_memory_blocks_and_altmaps(start, size);
+--- a/mm/memremap.c
++++ b/mm/memremap.c
+@@ -97,10 +97,10 @@ static void pageunmap_range(struct dev_p
+                                  PHYS_PFN(range_len(range)));
+       if (pgmap->type == MEMORY_DEVICE_PRIVATE) {
+               __remove_pages(PHYS_PFN(range->start),
+-                             PHYS_PFN(range_len(range)), NULL);
++                             PHYS_PFN(range_len(range)), NULL, pgmap);
+       } else {
+               arch_remove_memory(range->start, range_len(range),
+-                              pgmap_altmap(pgmap));
++                              pgmap_altmap(pgmap), pgmap);
+               kasan_remove_zero_shadow(__va(range->start), range_len(range));
+       }
+       mem_hotplug_done();
+--- a/mm/sparse-vmemmap.c
++++ b/mm/sparse-vmemmap.c
+@@ -665,7 +665,7 @@ static struct page * __meminit populate_
+ }
+ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
+-              struct vmem_altmap *altmap)
++              struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+ {
+       unsigned long start = (unsigned long) pfn_to_page(pfn);
+       unsigned long end = start + nr_pages * sizeof(struct page);
+@@ -746,7 +746,7 @@ static int fill_subsection_map(unsigned
+  * usage map, but still need to free the vmemmap range.
+  */
+ static void section_deactivate(unsigned long pfn, unsigned long nr_pages,
+-              struct vmem_altmap *altmap)
++              struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+ {
+       struct mem_section *ms = __pfn_to_section(pfn);
+       bool section_is_early = early_section(ms);
+@@ -784,7 +784,7 @@ static void section_deactivate(unsigned
+        * section_activate() and pfn_valid() .
+        */
+       if (!section_is_early)
+-              depopulate_section_memmap(pfn, nr_pages, altmap);
++              depopulate_section_memmap(pfn, nr_pages, altmap, pgmap);
+       else if (memmap)
+               free_map_bootmem(memmap);
+@@ -828,7 +828,7 @@ static struct page * __meminit section_a
+       memmap = populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap);
+       if (!memmap) {
+-              section_deactivate(pfn, nr_pages, altmap);
++              section_deactivate(pfn, nr_pages, altmap, pgmap);
+               return ERR_PTR(-ENOMEM);
+       }
+@@ -889,13 +889,13 @@ int __meminit sparse_add_section(int nid
+ }
+ void sparse_remove_section(unsigned long pfn, unsigned long nr_pages,
+-                         struct vmem_altmap *altmap)
++              struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+ {
+       struct mem_section *ms = __pfn_to_section(pfn);
+       if (WARN_ON_ONCE(!valid_section(ms)))
+               return;
+-      section_deactivate(pfn, nr_pages, altmap);
++      section_deactivate(pfn, nr_pages, altmap, pgmap);
+ }
+ #endif /* CONFIG_MEMORY_HOTPLUG */
diff --git a/queue-7.1/net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch b/queue-7.1/net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch
new file mode 100644 (file)
index 0000000..380594c
--- /dev/null
@@ -0,0 +1,253 @@
+From stable+bounces-289855-greg=kroah.com@vger.kernel.org Tue Jul 28 05:51:06 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 23:44:39 -0400
+Subject: net: mana: Optimize irq affinity for low vcpu configs
+To: stable@vger.kernel.org
+Cc: Shradha Gupta <shradhagupta@linux.microsoft.com>, Erni Sri Satya Vennela <ernis@linux.microsoft.com>, Haiyang Zhang <haiyangz@microsoft.com>, Simon Horman <horms@kernel.org>, Yury Norov <ynorov@nvidia.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728034439.3162982-1-sashal@kernel.org>
+
+From: Shradha Gupta <shradhagupta@linux.microsoft.com>
+
+[ Upstream commit 5316394b1752f6cf3f9901e7fefdec1cd1d97fd3 ]
+
+Before the commit 755391121038 ("net: mana: Allocate MSI-X vectors
+dynamically"), all the MANA IRQs were assigned statically and together
+during early driver load.
+
+After this commit, the IRQ allocation for MANA was done in two phases.
+HWC IRQ allocated earlier and then, queue IRQs dynamically added at a
+later point. By this time, the IRQ weights on vCPUs can become imbalanced
+and if IRQ count is greater than the vCPU count the topology aware IRQ
+distribution logic in MANA can cause multiple MANA IRQs to land on the
+same vCPUs, while other sibling vCPUs have none (case 1).
+
+On SMP enabled, low-vCPU systems, this becomes a bigger problem as the
+softIRQ handling overhead of two IRQs on the same vCPUs becomes much more
+than their overheads if they were spread across sibling vCPUs.
+
+In such cases when many parallel TCP connections are tested, the
+throughput drops significantly.
+
+Fix the affinity assignment logic, in cases where the IRQ count is greater
+than the vCPU count and when IRQs are added dynamically, by utilizing all
+the vCPUs irrespective of their NUMA/core bindings (case 2).
+
+The results of setting the affinity and hint to NULL were also studied,
+and we observed that, with this logic if there are pre-existing IRQs
+allocated on the VM (apart from MANA), during MANA IRQs allocation, it
+leads to clustering of the MANA queue IRQs again (case 3).
+
+=======================================================
+Case 1: without this patch
+=======================================================
+4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+       TYPE            effective vCPU aff
+=======================================================
+IRQ0:  HWC             0
+IRQ1:  mana_q1         0
+IRQ2:  mana_q2         2
+IRQ3:  mana_q3         0
+IRQ4:  mana_q4         3
+
+%soft on each vCPU(mpstat -P ALL 1) on receiver
+vCPU           0       1       2       3
+=======================================================
+pass 1:                38.85   0.03    24.89   24.65
+pass 2:                39.15   0.03    24.57   25.28
+pass 3:                40.36   0.03    23.20   23.17
+
+=======================================================
+Case 2: with this patch
+=======================================================
+4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+        TYPE            effective vCPU aff
+=======================================================
+IRQ0:   HWC             0
+IRQ1:   mana_q1         0
+IRQ2:   mana_q2         1
+IRQ3:   mana_q3         2
+IRQ4:   mana_q4         3
+
+%soft on each vCPU(mpstat -P ALL 1) on receiver
+vCPU            0       1       2       3
+=======================================================
+pass 1:         15.42  15.85   14.99   14.51
+pass 2:         15.53  15.94   15.81   15.93
+pass 3:         16.41  16.35   16.40   16.36
+
+=======================================================
+Case 3: with affinity set to NULL
+=======================================================
+4 vCPU(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+       TYPE            effective vCPU aff
+=======================================================
+IRQ0:  HWC                     0
+IRQ1:  mana_q1                 2
+IRQ2:  mana_q2                 3
+IRQ3:  mana_q3                 2
+IRQ4:  mana_q4                 3
+
+=======================================================
+Throughput Impact(in Gbps, same env)
+=======================================================
+TCP conn       with patch      w/o patch       aff NULL
+20480          15.65           7.73            5.25
+10240          15.63           8.93            5.77
+8192           15.64           9.69            7.16
+6144           15.64           13.16           9.33
+4096           15.69           15.75           13.50
+2048           15.69           15.83           13.61
+1024           15.71           15.28           13.60
+
+Fixes: 755391121038 ("net: mana: Allocate MSI-X vectors dynamically")
+Cc: stable@vger.kernel.org
+Co-developed-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
+Signed-off-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
+Signed-off-by: Shradha Gupta <shradhagupta@linux.microsoft.com>
+Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Reviewed-by: Yury Norov <ynorov@nvidia.com>
+Link: https://patch.msgid.link/20260624072138.1632849-1-shradhagupta@linux.microsoft.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ Kept 7.1's `int *irqs, irq, err, i;` declaration (minus the deleted `skip_first_cpu`) instead of upstream's `msi` variant, as this tree lacks the `mana_gd_get_gic()` refactor. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/microsoft/mana/gdma_main.c |   78 +++++++++++++++++++-----
+ 1 file changed, 64 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
+@@ -197,6 +197,8 @@ static int mana_gd_query_max_resources(s
+       } else {
+               /* If dynamic allocation is enabled we have already allocated
+                * hwc msi
++               * Also, we make sure in this case the following is always true
++               * (num_msix_usable - 1 HWC) <= num_online_cpus()
+                */
+               gc->num_msix_usable = min(resp.max_msix, num_online_cpus() + 1);
+       }
+@@ -1667,8 +1669,8 @@ void mana_gd_free_res_map(struct gdma_re
+  * do the same thing.
+  */
+-static int irq_setup(unsigned int *irqs, unsigned int len, int node,
+-                   bool skip_first_cpu)
++static int mana_irq_setup_numa_aware(unsigned int *irqs, unsigned int len,
++                                   int node, bool skip_first_cpu)
+ {
+       const struct cpumask *next, *prev = cpu_none_mask;
+       cpumask_var_t cpus __free(free_cpumask_var);
+@@ -1704,11 +1706,24 @@ done:
+       return 0;
+ }
++/* must be called with cpus_read_lock() held */
++static void mana_irq_setup_linear(unsigned int *irqs, unsigned int len)
++{
++      int cpu;
++
++      for_each_online_cpu(cpu) {
++              if (len == 0)
++                      break;
++
++              irq_set_affinity_and_hint(*irqs++, cpumask_of(cpu));
++              len--;
++      }
++}
++
+ static int mana_gd_setup_dyn_irqs(struct pci_dev *pdev, int nvec)
+ {
+       struct gdma_context *gc = pci_get_drvdata(pdev);
+       struct gdma_irq_context *gic;
+-      bool skip_first_cpu = false;
+       int *irqs, irq, err, i;
+       irqs = kmalloc_objs(int, nvec);
+@@ -1716,10 +1731,12 @@ static int mana_gd_setup_dyn_irqs(struct
+               return -ENOMEM;
+       /*
++       * In this function, num_msix_usable = HWC IRQ + Queue IRQ.
++       * nvec is only Queue IRQ (HWC already setup).
+        * While processing the next pci irq vector, we start with index 1,
+        * as IRQ vector at index 0 is already processed for HWC.
+        * However, the population of irqs array starts with index 0, to be
+-       * further used in irq_setup()
++       * further used in mana_irq_setup_numa_aware()
+        */
+       for (i = 1; i <= nvec; i++) {
+               gic = kzalloc_obj(*gic);
+@@ -1749,18 +1766,51 @@ static int mana_gd_setup_dyn_irqs(struct
+       }
+       /*
+-       * When calling irq_setup() for dynamically added IRQs, if number of
+-       * CPUs is more than or equal to allocated MSI-X, we need to skip the
+-       * first CPU sibling group since they are already affinitized to HWC IRQ
++       * When calling mana_irq_setup_numa_aware() for dynamically added IRQs,
++       * if number of CPUs is more than or equal to allocated MSI-X, we need to
++       * skip the first CPU sibling group since they are already affinitized to
++       * HWC IRQ
+        */
+       cpus_read_lock();
+-      if (gc->num_msix_usable <= num_online_cpus())
+-              skip_first_cpu = true;
++      if (gc->num_msix_usable <= num_online_cpus()) {
++              err = mana_irq_setup_numa_aware(irqs, nvec, gc->numa_node,
++                                              true);
++              if (err) {
++                      cpus_read_unlock();
++                      goto free_irq;
++              }
++      } else {
++              /*
++               * When num_msix_usable are more than num_online_cpus, our
++               * queue IRQs should be equal to num of online vCPUs.
++               * We try to make sure queue IRQs spread across all vCPUs.
++               * In such a case NUMA or CPU core affinity does not matter.
++               * Note: in this case the total mana IRQ should always be
++               * num_online_cpus + 1. The first HWC IRQ is already handled
++               * in HWC setup calls
++               * However, if CPUs went offline since num_msix_usable was
++               * computed, queue IRQs will be more than num_online_cpus().
++               * In such cases remaining extra IRQs will retain their default
++               * affinity.
++               */
++              int first_unassigned = num_online_cpus();
+-      err = irq_setup(irqs, nvec, gc->numa_node, skip_first_cpu);
+-      if (err) {
+-              cpus_read_unlock();
+-              goto free_irq;
++              if (nvec > first_unassigned) {
++                      char buf[32];
++
++                      if (first_unassigned == nvec - 1)
++                              snprintf(buf, sizeof(buf), "%d",
++                                       first_unassigned);
++                      else
++                              snprintf(buf, sizeof(buf), "%d-%d",
++                                       first_unassigned, nvec - 1);
++
++                      dev_dbg(&pdev->dev,
++                              "MANA IRQ indices #%s will retain the default CPU affinity\n",
++                              buf);
++              }
++
++              mana_irq_setup_linear(irqs, nvec);
+       }
+       cpus_read_unlock();
+@@ -1846,7 +1896,7 @@ static int mana_gd_setup_irqs(struct pci
+               nvec -= 1;
+       }
+-      err = irq_setup(irqs, nvec, gc->numa_node, false);
++      err = mana_irq_setup_numa_aware(irqs, nvec, gc->numa_node, false);
+       if (err) {
+               cpus_read_unlock();
+               goto free_irq;
diff --git a/queue-7.1/sched_ext-move-shared-helpers-from-ext.c-into-internal.h-and-cid.h.patch b/queue-7.1/sched_ext-move-shared-helpers-from-ext.c-into-internal.h-and-cid.h.patch
new file mode 100644 (file)
index 0000000..001bd90
--- /dev/null
@@ -0,0 +1,141 @@
+From stable+bounces-290832-greg=kroah.com@vger.kernel.org Wed Jul 29 18:35:11 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 12:24:32 -0400
+Subject: sched_ext: Move shared helpers from ext.c into internal.h and cid.h
+To: stable@vger.kernel.org
+Cc: Tejun Heo <tj@kernel.org>, Peter Zijlstra <peterz@infradead.org>, Andrea Righi <arighi@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729162433.3523095-1-sashal@kernel.org>
+
+From: Tejun Heo <tj@kernel.org>
+
+[ Upstream commit 4437ad129cf5b37c00a5bc9fa5989d1da4d64d07 ]
+
+idle.c and cid.c are included into build_policy.c together with ext.c and
+use helpers that ext.c defines. Because the helpers live in ext.c, the two
+files can not parse as standalone units and clangd reports errors in them.
+
+Move the helpers to the headers they belong to. The op-dispatch macros and
+helpers plus scx_parent() to internal.h, and scx_cpu_arg()/scx_cpu_ret() to
+cid.h. No functional change. idle.c and cid.c now parse clean standalone.
+
+Suggested-by: Peter Zijlstra <peterz@infradead.org>
+Signed-off-by: Tejun Heo <tj@kernel.org>
+Reviewed-by: Andrea Righi <arighi@nvidia.com>
+Stable-dep-of: 18d62044cda7 ("sched_ext: Preserve rq tracking across local DSQ dispatch")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/sched/ext.c          |   29 -----------------------------
+ kernel/sched/ext_internal.h |   30 ++++++++++++++++++++++++++++++
+ 2 files changed, 30 insertions(+), 29 deletions(-)
+
+--- a/kernel/sched/ext.c
++++ b/kernel/sched/ext.c
+@@ -218,8 +218,6 @@ static __printf(4, 5) bool scx_exit(stru
+ #define scx_error(sch, fmt, args...)  scx_exit((sch), SCX_EXIT_ERROR, 0, fmt, ##args)
+ #define scx_verror(sch, fmt, args)    scx_vexit((sch), SCX_EXIT_ERROR, 0, fmt, args)
+-#define SCX_HAS_OP(sch, op)   test_bit(SCX_OP_IDX(op), (sch)->has_op)
+-
+ static long jiffies_delta_msecs(unsigned long at, unsigned long now)
+ {
+       if (time_after(at, now))
+@@ -235,20 +233,6 @@ static bool u32_before(u32 a, u32 b)
+ #ifdef CONFIG_EXT_SUB_SCHED
+ /**
+- * scx_parent - Find the parent sched
+- * @sch: sched to find the parent of
+- *
+- * Returns the parent scheduler or %NULL if @sch is root.
+- */
+-static struct scx_sched *scx_parent(struct scx_sched *sch)
+-{
+-      if (sch->level)
+-              return sch->ancestors[sch->level - 1];
+-      else
+-              return NULL;
+-}
+-
+-/**
+  * scx_next_descendant_pre - find the next descendant for pre-order walk
+  * @pos: the current position (%NULL to initiate traversal)
+  * @root: sched whose descendants to walk
+@@ -295,7 +279,6 @@ static void scx_set_task_sched(struct ta
+       rcu_assign_pointer(p->scx.sched, sch);
+ }
+ #else /* CONFIG_EXT_SUB_SCHED */
+-static struct scx_sched *scx_parent(struct scx_sched *sch) { return NULL; }
+ static struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) { return pos ? NULL : root; }
+ static void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch) {}
+ #endif        /* CONFIG_EXT_SUB_SCHED */
+@@ -455,18 +438,6 @@ static bool rq_is_open(struct rq *rq, u6
+  */
+ DEFINE_PER_CPU(struct rq *, scx_locked_rq_state);
+-static inline void update_locked_rq(struct rq *rq)
+-{
+-      /*
+-       * Check whether @rq is actually locked. This can help expose bugs
+-       * or incorrect assumptions about the context in which a kfunc or
+-       * callback is executed.
+-       */
+-      if (rq)
+-              lockdep_assert_rq_held(rq);
+-      __this_cpu_write(scx_locked_rq_state, rq);
+-}
+-
+ /*
+  * SCX ops can recurse via scx_bpf_sub_dispatch() - the inner call must not
+  * clobber the outer's scx_locked_rq_state. Save it on entry, restore on exit.
+--- a/kernel/sched/ext_internal.h
++++ b/kernel/sched/ext_internal.h
+@@ -1377,6 +1377,20 @@ static inline struct rq *scx_locked_rq(v
+       return __this_cpu_read(scx_locked_rq_state);
+ }
++static inline void update_locked_rq(struct rq *rq)
++{
++      /*
++       * Check whether @rq is actually locked. This can help expose bugs
++       * or incorrect assumptions about the context in which a kfunc or
++       * callback is executed.
++       */
++      if (rq)
++              lockdep_assert_rq_held(rq);
++      __this_cpu_write(scx_locked_rq_state, rq);
++}
++
++#define SCX_HAS_OP(sch, op)   test_bit(SCX_OP_IDX(op), (sch)->has_op)
++
+ static inline bool scx_bypassing(struct scx_sched *sch, s32 cpu)
+ {
+       return unlikely(per_cpu_ptr(sch->pcpu, cpu)->flags &
+@@ -1457,6 +1471,20 @@ static inline struct scx_sched *scx_prog
+       return NULL;
+ }
++
++/**
++ * scx_parent - Find the parent sched
++ * @sch: sched to find the parent of
++ *
++ * Returns the parent scheduler or %NULL if @sch is root.
++ */
++static inline struct scx_sched *scx_parent(struct scx_sched *sch)
++{
++      if (sch->level)
++              return sch->ancestors[sch->level - 1];
++      else
++              return NULL;
++}
+ #else /* CONFIG_EXT_SUB_SCHED */
+ static inline struct scx_sched *scx_task_sched(const struct task_struct *p)
+ {
+@@ -1480,4 +1508,6 @@ static struct scx_sched *scx_prog_sched(
+ {
+       return rcu_dereference_all(scx_root);
+ }
++
++static inline struct scx_sched *scx_parent(struct scx_sched *sch) { return NULL; }
+ #endif        /* CONFIG_EXT_SUB_SCHED */
diff --git a/queue-7.1/sched_ext-preserve-rq-tracking-across-local-dsq-dispatch.patch b/queue-7.1/sched_ext-preserve-rq-tracking-across-local-dsq-dispatch.patch
new file mode 100644 (file)
index 0000000..12e5274
--- /dev/null
@@ -0,0 +1,141 @@
+From stable+bounces-290831-greg=kroah.com@vger.kernel.org Wed Jul 29 18:35:11 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 12:24:33 -0400
+Subject: sched_ext: Preserve rq tracking across local DSQ dispatch
+To: stable@vger.kernel.org
+Cc: Andrea Righi <arighi@nvidia.com>, Tejun Heo <tj@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729162433.3523095-2-sashal@kernel.org>
+
+From: Andrea Righi <arighi@nvidia.com>
+
+[ Upstream commit 18d62044cda7a2b40f59d910659c0b0d6accad37 ]
+
+dispatch_to_local_dsq() can run from scx_bpf_dsq_move_to_local() while
+ops.dispatch() has recorded the current rq. Moving a task to a local DSQ
+may switch to the source or destination rq before synchronously invoking
+ops.dequeue() through the following path:
+
+  SCX_CALL_OP(dispatch, rq)
+    ops.dispatch()
+      scx_bpf_dsq_move_to_local()
+        scx_flush_dispatch_buf()
+          finish_dispatch()
+            dispatch_to_local_dsq()
+              scx_dispatch_enqueue()
+                local_dsq_post_enq()
+                  call_task_dequeue()
+                    SCX_CALL_OP_TASK(dequeue, locked_rq, ...)
+
+The nested callback saves the recorded rq and restores it on return. If
+the rq tracking does not follow the lock switch, update_locked_rq() can
+trigger the following lockdep assertion while restoring an rq which is
+no longer held:
+
+  WARNING: kernel/sched/sched.h:1641 at call_task_dequeue+0x160/0x170
+  Call Trace:
+    scx_dispatch_enqueue+0x2b0/0x460
+    dispatch_to_local_dsq+0x138/0x230
+    scx_flush_dispatch_buf+0x1af/0x220
+    scx_bpf_dsq_move_to_local___v2+0xe2/0x1c0
+    bpf__sched_ext_ops_dispatch+0x4b/0xa7
+    do_pick_task_scx+0x3b6/0x910
+    __pick_next_task+0x105/0x1f0
+    __schedule+0x3e7/0x1980
+
+Introduce switch_rq_lock() to update the tracking state together with
+each rq lock handoff. Use it in dispatch_to_local_dsq(),
+move_remote_task_to_local_dsq() and the in-balance paths of
+scx_dsq_move(), ensuring that scx_locked_rq() consistently refers to the
+rq whose lock is actually held throughout the lock dance.
+
+Fixes: 7fb39e4eb4c3 ("sched_ext: Save and restore scx_locked_rq across SCX_CALL_OP")
+Cc: stable@vger.kernel.org # 7.1+
+Signed-off-by: Andrea Righi <arighi@nvidia.com>
+Signed-off-by: Tejun Heo <tj@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/sched/ext.c |   36 ++++++++++++++++++++----------------
+ 1 file changed, 20 insertions(+), 16 deletions(-)
+
+--- a/kernel/sched/ext.c
++++ b/kernel/sched/ext.c
+@@ -438,6 +438,18 @@ static bool rq_is_open(struct rq *rq, u6
+  */
+ DEFINE_PER_CPU(struct rq *, scx_locked_rq_state);
++static void switch_rq_lock(struct rq *from, struct rq *to)
++{
++      bool tracked = scx_locked_rq() == from;
++
++      if (tracked)
++              update_locked_rq(NULL);
++      raw_spin_rq_unlock(from);
++      raw_spin_rq_lock(to);
++      if (tracked)
++              update_locked_rq(to);
++}
++
+ /*
+  * SCX ops can recurse via scx_bpf_sub_dispatch() - the inner call must not
+  * clobber the outer's scx_locked_rq_state. Save it on entry, restore on exit.
+@@ -2262,8 +2274,7 @@ static void move_remote_task_to_local_ds
+       deactivate_task(src_rq, p, 0);
+       set_task_cpu(p, cpu_of(dst_rq));
+-      raw_spin_rq_unlock(src_rq);
+-      raw_spin_rq_lock(dst_rq);
++      switch_rq_lock(src_rq, dst_rq);
+       /*
+        * We want to pass scx-specific enq_flags but activate_task() will
+@@ -2594,9 +2605,8 @@ static void dispatch_to_local_dsq(struct
+       /* switch to @src_rq lock */
+       if (locked_rq != src_rq) {
+-              raw_spin_rq_unlock(locked_rq);
++              switch_rq_lock(locked_rq, src_rq);
+               locked_rq = src_rq;
+-              raw_spin_rq_lock(src_rq);
+       }
+       /* task_rq couldn't have changed if we're still the holding cpu */
+@@ -2624,10 +2634,8 @@ static void dispatch_to_local_dsq(struct
+       }
+       /* switch back to @rq lock */
+-      if (locked_rq != rq) {
+-              raw_spin_rq_unlock(locked_rq);
+-              raw_spin_rq_lock(rq);
+-      }
++      if (locked_rq != rq)
++              switch_rq_lock(locked_rq, rq);
+ }
+ /**
+@@ -8405,10 +8413,8 @@ static bool scx_dsq_move(struct bpf_iter
+       in_balance = this_rq->scx.flags & SCX_RQ_IN_BALANCE;
+       if (in_balance) {
+-              if (this_rq != src_rq) {
+-                      raw_spin_rq_unlock(this_rq);
+-                      raw_spin_rq_lock(src_rq);
+-              }
++              if (this_rq != src_rq)
++                      switch_rq_lock(this_rq, src_rq);
+       } else {
+               raw_spin_rq_lock(src_rq);
+       }
+@@ -8440,10 +8446,8 @@ static bool scx_dsq_move(struct bpf_iter
+       dispatched = true;
+ out:
+       if (in_balance) {
+-              if (this_rq != locked_rq) {
+-                      raw_spin_rq_unlock(locked_rq);
+-                      raw_spin_rq_lock(this_rq);
+-              }
++              if (this_rq != locked_rq)
++                      switch_rq_lock(locked_rq, this_rq);
+       } else {
+               raw_spin_rq_unlock_irqrestore(locked_rq, flags);
+       }
index 1c654fce689da7190e8049ceaf39f6bb1a3b8ecd..e4d1b7e9596232a13fd348796792be267019ef3d 100644 (file)
@@ -722,3 +722,20 @@ ksmbd-restore-dacl-size-on-check_add_overflow-to-avo.patch
 ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch
 ksmbd-validate-ace-size-against-sid-sub-authorities.patch
 drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch
+audit-use-unsigned-int-instead-of-unsigned.patch
+audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch
+fuse-uring-fix-race-between-registration-and-connection-abortion.patch
+xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch
+sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch
+sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch
+mm-sparse-vmemmap-pass-pgmap-argument-to-memory-deactivation-paths.patch
+mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch
+thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch
+thunderbolt-remove-service-debugfs-entries-during-unregister.patch
+thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch
+thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch
+net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch
+bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
+bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
+sched_ext-move-shared-helpers-from-ext.c-into-internal.h-and-cid.h.patch
+sched_ext-preserve-rq-tracking-across-local-dsq-dispatch.patch
diff --git a/queue-7.1/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch b/queue-7.1/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch
new file mode 100644 (file)
index 0000000..86b3a86
--- /dev/null
@@ -0,0 +1,279 @@
+From stable+bounces-288461-greg=kroah.com@vger.kernel.org Thu Jul 23 19:47:54 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 13:35:36 -0400
+Subject: SUNRPC: Add helpers to convert xdr_buf byte ranges to scatterlists
+To: stable@vger.kernel.org
+Cc: Chuck Lever <chuck.lever@oracle.com>, Jeff Layton <jlayton@kernel.org>, Anna Schumaker <anna.schumaker@hammerspace.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723173538.3416792-1-sashal@kernel.org>
+
+From: Chuck Lever <chuck.lever@oracle.com>
+
+[ Upstream commit e9be933959b581effd426f93b86654f5fbf0c574 ]
+
+The crypto/krb5 library accepts data in scatterlist form, but
+the GSS-API layer presents RPC payloads as struct xdr_buf.
+Bridge that gap with a pair of helper functions:
+
+  xdr_buf_to_sg()        - populate a caller-supplied scatterlist
+                           array from a byte range
+  xdr_buf_to_sg_alloc()  - populate a caller-supplied inline
+                           scatterlist, chaining to a heap-
+                           allocated overflow for large payloads
+
+The inline array (typically stack-allocated at eight entries)
+covers the common case of small RPCs with no heap allocation
+on the encrypt/decrypt path. Only buffers spanning many pages
+incur a kmalloc for the chained extension.
+
+The segment-walking logic follows the same head, page array,
+tail traversal as xdr_process_buf(), but populates a
+scatterlist directly rather than invoking a per-segment
+callback. sg_next() traversal makes the walker safe for
+chained scatterlists. Once subsequent patches reroute all
+per-message crypto operations through crypto/krb5,
+xdr_process_buf() loses its last callers and is removed.
+
+Assisted-by: Claude:claude-opus-4-6
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/sunrpc/xdr.h |   15 +++
+ net/sunrpc/xdr.c           |  199 +++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 214 insertions(+)
+
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -140,6 +140,21 @@ int       xdr_alloc_bvec(struct xdr_buf *buf,
+ void  xdr_free_bvec(struct xdr_buf *buf);
+ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+                            const struct xdr_buf *xdr);
++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
++                unsigned int len, struct scatterlist *sg, unsigned int nsg);
++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
++                      unsigned int len, struct scatterlist *sg_head,
++                      unsigned int sg_head_nents,
++                      struct scatterlist **sg_overflow, gfp_t gfp);
++
++/*
++ * Inline scatterlist entries for xdr_buf_to_sg_alloc().  Sized to cover the
++ * head kvec, tail kvec, and a few page fragments without any heap allocation.
++ */
++enum {
++      XDR_BUF_TO_SG_NENTS     = 8,
++};
++
+ static inline __be32 *xdr_encode_array(__be32 *p, const void *s, unsigned int len)
+ {
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -192,6 +192,205 @@ bvec_overflow:
+ EXPORT_SYMBOL_GPL(xdr_buf_to_bvec);
+ /**
++ * xdr_buf_to_sg - Populate a scatterlist from an xdr_buf range
++ * @buf: xdr_buf to map
++ * @offset: starting byte offset within @buf
++ * @len: number of bytes to cover
++ * @sg: scatterlist array initialized with sg_init_table()
++ * @nsg: number of entries available in @sg
++ *
++ * @sg is traversed with sg_next(), so callers may pass a list
++ * assembled with sg_chain().
++ *
++ * Return: on success, the number of scatterlist entries used; the
++ * last used entry is marked with sg_mark_end().  On failure, a
++ * negative errno.
++ */
++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
++                unsigned int len, struct scatterlist *sg, unsigned int nsg)
++{
++      unsigned int page_len, thislen, page_offset;
++      struct scatterlist *cur = sg, *prev = NULL;
++      int nents = 0;
++      int i;
++
++      if (len == 0)
++              return 0;
++
++      if (offset >= buf->head[0].iov_len) {
++              offset -= buf->head[0].iov_len;
++      } else {
++              thislen = min_t(unsigned int,
++                              buf->head[0].iov_len - offset, len);
++              if (nents >= nsg)
++                      return -ENOSPC;
++              sg_set_buf(cur, buf->head[0].iov_base + offset,
++                         thislen);
++              prev = cur;
++              cur = sg_next(cur);
++              nents++;
++              len -= thislen;
++              offset = 0;
++      }
++      if (len == 0)
++              goto done;
++
++      if (offset >= buf->page_len) {
++              offset -= buf->page_len;
++      } else {
++              page_len = min(buf->page_len - offset, len);
++              len -= page_len;
++              page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
++              i = (offset + buf->page_base) >> PAGE_SHIFT;
++              thislen = PAGE_SIZE - page_offset;
++              do {
++                      if (thislen > page_len)
++                              thislen = page_len;
++                      if (nents >= nsg)
++                              return -ENOSPC;
++                      sg_set_page(cur, buf->pages[i],
++                                  thislen, page_offset);
++                      prev = cur;
++                      cur = sg_next(cur);
++                      nents++;
++                      page_len -= thislen;
++                      i++;
++                      page_offset = 0;
++                      thislen = PAGE_SIZE;
++              } while (page_len != 0);
++              offset = 0;
++      }
++      if (len == 0)
++              goto done;
++
++      if (offset < buf->tail[0].iov_len) {
++              thislen = min_t(unsigned int,
++                              buf->tail[0].iov_len - offset, len);
++              if (nents >= nsg)
++                      return -ENOSPC;
++              sg_set_buf(cur, buf->tail[0].iov_base + offset,
++                         thislen);
++              prev = cur;
++              nents++;
++              len -= thislen;
++      }
++      if (len != 0)
++              return -EINVAL;
++
++done:
++      if (prev)
++              sg_mark_end(prev);
++      return nents;
++}
++EXPORT_SYMBOL_GPL(xdr_buf_to_sg);
++
++/*
++ * Count the scatterlist entries needed to cover [offset, offset + len)
++ * within @buf.  Mirrors the walk in xdr_buf_to_sg() so the caller can
++ * size an allocation that matches the requested sub-range rather than
++ * the full xdr_buf.
++ */
++static unsigned int xdr_buf_sg_nents(const struct xdr_buf *buf,
++                                   unsigned int offset, unsigned int len)
++{
++      unsigned int nsg = 0, thislen, page_offset;
++
++      if (len == 0)
++              return 0;
++
++      if (offset < buf->head[0].iov_len) {
++              thislen = min_t(unsigned int,
++                              buf->head[0].iov_len - offset, len);
++              nsg++;
++              len -= thislen;
++              offset = 0;
++      } else {
++              offset -= buf->head[0].iov_len;
++      }
++      if (len == 0)
++              return nsg;
++
++      if (offset < buf->page_len) {
++              thislen = min(buf->page_len - offset, len);
++              page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
++              nsg += DIV_ROUND_UP(page_offset + thislen, PAGE_SIZE);
++              len -= thislen;
++              offset = 0;
++      } else {
++              offset -= buf->page_len;
++      }
++      if (len == 0)
++              return nsg;
++
++      if (offset < buf->tail[0].iov_len)
++              nsg++;
++      return nsg;
++}
++
++/**
++ * xdr_buf_to_sg_alloc - Populate a scatterlist for an xdr_buf range
++ * @buf: xdr_buf to map
++ * @offset: starting byte offset within @buf
++ * @len: number of bytes to cover
++ * @sg_head: caller-provided scatterlist array (typically stack-allocated)
++ * @sg_head_nents: number of entries in @sg_head
++ * @sg_overflow: OUT: chained extension, or NULL when @sg_head sufficed
++ * @gfp: memory allocation flags for overflow
++ *
++ * Populates @sg_head directly when the xdr_buf fits.  When more
++ * entries are needed, an overflow scatterlist is allocated and
++ * chained from @sg_head so that the result is traversable with
++ * sg_next().
++ *
++ * Return: on success, the number of populated scatterlist entries
++ * (counting only data entries, not chain entries).  @sg_head is
++ * the head of the resulting list.  Caller must kfree @sg_overflow
++ * when done.  On failure, a negative errno.
++ */
++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
++                      unsigned int len, struct scatterlist *sg_head,
++                      unsigned int sg_head_nents,
++                      struct scatterlist **sg_overflow, gfp_t gfp)
++{
++      unsigned int nsg;
++      int ret;
++
++      *sg_overflow = NULL;
++      if (len == 0)
++              return 0;
++
++      nsg = xdr_buf_sg_nents(buf, offset, len);
++      if (nsg == 0)
++              return -EINVAL;
++
++      if (nsg <= sg_head_nents) {
++              sg_init_table(sg_head, nsg);
++      } else {
++              /* +1 replaces the slot sg_chain() consumes as the link. */
++              unsigned int overflow_nents = nsg - sg_head_nents + 1;
++              struct scatterlist *overflow;
++
++              overflow = kmalloc_array(overflow_nents, sizeof(*overflow),
++                                       gfp);
++              if (!overflow)
++                      return -ENOMEM;
++
++              sg_init_table(sg_head, sg_head_nents);
++              sg_init_table(overflow, overflow_nents);
++              sg_chain(sg_head, sg_head_nents, overflow);
++              *sg_overflow = overflow;
++      }
++
++      ret = xdr_buf_to_sg(buf, offset, len, sg_head, nsg);
++      if (ret < 0) {
++              kfree(*sg_overflow);
++              *sg_overflow = NULL;
++      }
++      return ret;
++}
++EXPORT_SYMBOL_GPL(xdr_buf_to_sg_alloc);
++
++/**
+  * xdr_inline_pages - Prepare receive buffer for a large reply
+  * @xdr: xdr_buf into which reply will be placed
+  * @offset: expected offset where data payload will start, in bytes
diff --git a/queue-7.1/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch b/queue-7.1/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch
new file mode 100644 (file)
index 0000000..377799c
--- /dev/null
@@ -0,0 +1,165 @@
+From stable+bounces-288463-greg=kroah.com@vger.kernel.org Thu Jul 23 19:37:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 13:35:38 -0400
+Subject: SUNRPC: Return an error from xdr_buf_to_bvec() on overflow
+To: stable@vger.kernel.org
+Cc: Chuck Lever <chuck.lever@oracle.com>, Chris Mason <clm@meta.com>, Jeff Layton <jlayton@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723173538.3416792-3-sashal@kernel.org>
+
+From: Chuck Lever <chuck.lever@oracle.com>
+
+[ Upstream commit 18c1cc69886192e33536498289d26dba6894e3d5 ]
+
+xdr_buf_to_bvec() returns a slot count even when the caller's bvec
+budget is exhausted partway through the xdr_buf. Callers feed that
+count into iov_iter_bvec() and continue as if the conversion had
+succeeded, silently sending or writing fewer bytes than the data
+length declares. For an NFS WRITE the server reports the truncated
+transfer to the client as full success.
+
+The overflow represents an internal invariant violation: a higher
+layer reserved a bvec budget too small for the xdr_buf it then
+asked the encoder to convert. That is a server-side fault, not a
+media I/O failure and not a malformed client argument.
+
+Change xdr_buf_to_bvec() to return a signed int and have the
+overflow label return -ESERVERFAULT. Update the three callers to
+detect the negative return and fail the request: nfsd_vfs_write()
+folds the error into host_err, which nfserrno() translates to
+nfserr_serverfault for the WRITE reply; svc_udp_sendto() and
+svc_tcp_sendmsg() propagate the error out of the send path.
+
+Reported-by: Chris Mason <clm@meta.com>
+Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly")
+Cc: stable@vger.kernel.org
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/nfsd/vfs.c              |    6 +++++-
+ include/linux/sunrpc/xdr.h |    4 ++--
+ net/sunrpc/svcsock.c       |   14 ++++++++++++--
+ net/sunrpc/xdr.c           |   11 ++++++-----
+ 4 files changed, 25 insertions(+), 10 deletions(-)
+
+--- a/fs/nfsd/vfs.c
++++ b/fs/nfsd/vfs.c
+@@ -1439,7 +1439,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, s
+       unsigned long           exp_op_flags = 0;
+       unsigned int            pflags = current->flags;
+       bool                    restore_flags = false;
+-      unsigned int            nvecs;
++      int                     nvecs;
+       trace_nfsd_write_opened(rqstp, fhp, offset, *cnt);
+@@ -1479,6 +1479,10 @@ nfsd_vfs_write(struct svc_rqst *rqstp, s
+       }
+       nvecs = xdr_buf_to_bvec(rqstp->rq_bvec, rqstp->rq_maxpages, payload);
++      if (nvecs < 0) {
++              host_err = nvecs;
++              goto out_nfserr;
++      }
+       since = READ_ONCE(file->f_wb_err);
+       if (verf)
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -138,8 +138,8 @@ void       xdr_terminate_string(const struct x
+ size_t        xdr_buf_pagecount(const struct xdr_buf *buf);
+ int   xdr_alloc_bvec(struct xdr_buf *buf, gfp_t gfp);
+ void  xdr_free_bvec(struct xdr_buf *buf);
+-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+-                           const struct xdr_buf *xdr);
++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
++                  const struct xdr_buf *xdr);
+ int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
+                 unsigned int len, struct scatterlist *sg, unsigned int nsg);
+ int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
+--- a/net/sunrpc/svcsock.c
++++ b/net/sunrpc/svcsock.c
+@@ -742,7 +742,7 @@ static int svc_udp_sendto(struct svc_rqs
+               .msg_flags      = MSG_SPLICE_PAGES,
+               .msg_controllen = sizeof(buffer),
+       };
+-      unsigned int count;
++      int count;
+       int err;
+       svc_udp_release_ctxt(xprt, rqstp->rq_xprt_ctxt);
+@@ -756,6 +756,10 @@ static int svc_udp_sendto(struct svc_rqs
+               goto out_notconn;
+       count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr);
++      if (count < 0) {
++              err = count;
++              goto out_trace;
++      }
+       iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+                     count, rqstp->rq_res.len);
+@@ -767,6 +771,7 @@ static int svc_udp_sendto(struct svc_rqs
+               err = sock_sendmsg(svsk->sk_sock, &msg);
+       }
++out_trace:
+       trace_svcsock_udp_send(xprt, err);
+       mutex_unlock(&xprt->xpt_mutex);
+@@ -1247,7 +1252,7 @@ static int svc_tcp_sendmsg(struct svc_so
+       struct msghdr msg = {
+               .msg_flags      = MSG_SPLICE_PAGES,
+       };
+-      unsigned int count;
++      int count;
+       void *buf;
+       int ret;
+@@ -1263,10 +1268,15 @@ static int svc_tcp_sendmsg(struct svc_so
+       count = xdr_buf_to_bvec(svsk->sk_bvec + 1, rqstp->rq_maxpages,
+                               &rqstp->rq_res);
++      if (count < 0) {
++              ret = count;
++              goto out;
++      }
+       iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+                     1 + count, sizeof(marker) + rqstp->rq_res.len);
+       ret = sock_sendmsg(svsk->sk_sock, &msg);
++out:
+       page_frag_free(buf);
+       return ret;
+ }
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -139,13 +139,14 @@ xdr_free_bvec(struct xdr_buf *buf)
+ /**
+  * xdr_buf_to_bvec - Copy components of an xdr_buf into a bio_vec array
+  * @bvec: bio_vec array to populate
+- * @bvec_size: element count of @bio_vec
++ * @bvec_size: element count of @bvec
+  * @xdr: xdr_buf to be copied
+  *
+- * Returns the number of entries consumed in @bvec.
++ * Returns the number of entries consumed in @bvec on success, or
++ * -ESERVERFAULT when @xdr does not fit within @bvec_size entries.
+  */
+-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+-                           const struct xdr_buf *xdr)
++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
++                  const struct xdr_buf *xdr)
+ {
+       const struct kvec *head = xdr->head;
+       const struct kvec *tail = xdr->tail;
+@@ -187,7 +188,7 @@ unsigned int xdr_buf_to_bvec(struct bio_
+ bvec_overflow:
+       pr_warn_once("%s: bio_vec array overflow\n", __func__);
+-      return count;
++      return -ESERVERFAULT;
+ }
+ EXPORT_SYMBOL_GPL(xdr_buf_to_bvec);
diff --git a/queue-7.1/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch b/queue-7.1/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch
new file mode 100644 (file)
index 0000000..2530717
--- /dev/null
@@ -0,0 +1,42 @@
+From stable+bounces-289101-greg=kroah.com@vger.kernel.org Sat Jul 25 16:12:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 10:12:19 -0400
+Subject: thunderbolt: Keep XDomain reference during the lifetime of a service
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725141222.3814514-1-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit 8b4060998637f06975fceee9b73845d8672d411e ]
+
+This is needed because we release the service ID in tb_service_release()
+and the ID array is owned by the parent XDomain.
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c |    3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -1012,6 +1012,7 @@ static void tb_service_release(struct de
+       ida_free(&xd->service_ids, svc->id);
+       kfree(svc->key);
+       kfree(svc);
++      tb_xdomain_put(xd);
+ }
+ const struct device_type tb_service_type = {
+@@ -1120,7 +1121,7 @@ static void enumerate_services(struct tb
+               svc->id = id;
+               svc->dev.bus = &tb_bus_type;
+               svc->dev.type = &tb_service_type;
+-              svc->dev.parent = &xd->dev;
++              svc->dev.parent = get_device(&xd->dev);
+               dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id);
+               tb_service_debugfs_init(svc);
diff --git a/queue-7.1/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch b/queue-7.1/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch
new file mode 100644 (file)
index 0000000..ab87522
--- /dev/null
@@ -0,0 +1,90 @@
+From stable+bounces-289104-greg=kroah.com@vger.kernel.org Sat Jul 25 16:13:42 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 10:12:22 -0400
+Subject: thunderbolt: Prevent XDomain delayed work use-after-free on disconnect
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725141222.3814514-4-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit 2c5d2d3c3f70cde2565d7b279b544893a2035842 ]
+
+tb_xdp_handle_request() runs on system_wq and queues
+xd->state_work via queue_delayed_work() in three request handlers:
+PROPERTIES_CHANGED_REQUEST, UUID_REQUEST (via start_handshake),
+and LINK_STATE_CHANGE_REQUEST.  Similarly, update_xdomain() queues
+xd->properties_changed_work when local properties change.
+
+Concurrently, tb_xdomain_remove() calls stop_handshake() which does
+cancel_delayed_work_sync() on both delayed works.  Later,
+tb_xdomain_unregister() calls device_unregister() which eventually
+frees the xdomain.  Since commit 559c1e1e0134 ("thunderbolt: Run
+tb_xdp_handle_request() in system workqueue") moved the request
+handler off tb->wq, the handler and the remove path are no longer
+serialized.  If queue_delayed_work() executes after
+cancel_delayed_work_sync() but before the xdomain is freed, the
+delayed work fires on a freed object.
+
+Add xd->removing that tb_xdomain_remove() sets under xd->lock
+before calling stop_handshake().  Each external queue site holds
+the same lock and checks removing before calling
+queue_delayed_work().  This provides the mutual exclusion needed:
+either the queue site acquires the lock first and queues work that
+the subsequent cancel will see, or the remove path acquires the
+lock first and the queue site observes removing == true and skips
+the queue.
+
+Fixes: 559c1e1e0134 ("thunderbolt: Run tb_xdp_handle_request() in system workqueue")
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-7
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c |   26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -909,6 +909,19 @@ void tb_unregister_service_driver(struct
+ }
+ EXPORT_SYMBOL_GPL(tb_unregister_service_driver);
++static int update_xdomain(struct device *dev, void *data)
++{
++      struct tb_xdomain *xd;
++
++      xd = tb_to_xdomain(dev);
++      if (xd) {
++              queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
++                                 msecs_to_jiffies(50));
++      }
++
++      return 0;
++}
++
+ static ssize_t key_show(struct device *dev, struct device_attribute *attr,
+                       char *buf)
+ {
+@@ -2500,19 +2513,6 @@ bool tb_xdomain_handle_request(struct tb
+       return ret > 0;
+ }
+-static int update_xdomain(struct device *dev, void *data)
+-{
+-      struct tb_xdomain *xd;
+-
+-      xd = tb_to_xdomain(dev);
+-      if (xd) {
+-              queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
+-                                 msecs_to_jiffies(50));
+-      }
+-
+-      return 0;
+-}
+-
+ static void update_all_xdomains(void)
+ {
+       bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain);
diff --git a/queue-7.1/thunderbolt-remove-service-debugfs-entries-during-unregister.patch b/queue-7.1/thunderbolt-remove-service-debugfs-entries-during-unregister.patch
new file mode 100644 (file)
index 0000000..49469d5
--- /dev/null
@@ -0,0 +1,76 @@
+From stable+bounces-289102-greg=kroah.com@vger.kernel.org Sat Jul 25 16:12:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 10:12:20 -0400
+Subject: thunderbolt: Remove service debugfs entries during unregister
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725141222.3814514-2-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit 4d5fc3f4068568dfcb8cbe2852b4adc56394aa26 ]
+
+We add them as part of the register path so to keep it symmetric remove
+them as part of the unregister path. This also removes them even if the
+service itself is not yet released (but is unregistered), thus allowing
+new register with the same service name to happen.
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c |   14 +++++++++++---
+ 1 file changed, 11 insertions(+), 3 deletions(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -1008,7 +1008,6 @@ static void tb_service_release(struct de
+       struct tb_service *svc = container_of(dev, struct tb_service, dev);
+       struct tb_xdomain *xd = tb_service_parent(svc);
+-      tb_service_debugfs_remove(svc);
+       ida_free(&xd->service_ids, svc->id);
+       kfree(svc->key);
+       kfree(svc);
+@@ -1023,6 +1022,14 @@ const struct device_type tb_service_type
+ };
+ EXPORT_SYMBOL_GPL(tb_service_type);
++static void __unregister_service(struct device *dev)
++{
++      struct tb_service *svc = tb_to_service(dev);
++
++      tb_service_debugfs_remove(svc);
++      device_unregister(&svc->dev);
++}
++
+ static int remove_missing_service(struct device *dev, void *data)
+ {
+       struct tb_xdomain *xd = data;
+@@ -1034,7 +1041,7 @@ static int remove_missing_service(struct
+       if (!tb_property_find(xd->remote_properties, svc->key,
+                             TB_PROPERTY_TYPE_DIRECTORY))
+-              device_unregister(dev);
++              __unregister_service(dev);
+       return 0;
+ }
+@@ -1127,6 +1134,7 @@ static void enumerate_services(struct tb
+               tb_service_debugfs_init(svc);
+               if (device_register(&svc->dev)) {
++                      tb_service_debugfs_remove(svc);
+                       put_device(&svc->dev);
+                       break;
+               }
+@@ -2059,7 +2067,7 @@ void tb_xdomain_add(struct tb_xdomain *x
+ static int unregister_service(struct device *dev, void *data)
+ {
+-      device_unregister(dev);
++      __unregister_service(dev);
+       return 0;
+ }
diff --git a/queue-7.1/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch b/queue-7.1/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch
new file mode 100644 (file)
index 0000000..d265845
--- /dev/null
@@ -0,0 +1,337 @@
+From stable+bounces-289103-greg=kroah.com@vger.kernel.org Sat Jul 25 16:12:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 10:12:21 -0400
+Subject: thunderbolt: Remove XDomain from the bus without holding tb->lock
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725141222.3814514-3-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit a8937f35cf39c39c64325aa84d0463d866850857 ]
+
+Currently we call device_unregister() for services and the XDomain
+itself with tb->lock held. This prevents the service drivers from
+calling any functions that may take it. For this reason separate
+removing the XDomain from the topology data structures (where we need
+the lock) from unregistering the device from the bus (where remove
+callbacks of the drivers are being called).
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/debugfs.c |    2 +
+ drivers/thunderbolt/domain.c  |   30 +++++++++++++++++++++
+ drivers/thunderbolt/icm.c     |    5 +++
+ drivers/thunderbolt/switch.c  |   14 +++++++++
+ drivers/thunderbolt/tb.c      |   59 ++++++++++++++++++++----------------------
+ drivers/thunderbolt/tb.h      |    2 +
+ drivers/thunderbolt/xdomain.c |   53 +++++++++++++++++++++++--------------
+ 7 files changed, 115 insertions(+), 50 deletions(-)
+
+--- a/drivers/thunderbolt/debugfs.c
++++ b/drivers/thunderbolt/debugfs.c
+@@ -1786,6 +1786,8 @@ static void margining_port_remove(struct
+       if (!port->usb4)
+               return;
++      if (!port->usb4->margining)
++              return;
+       snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
+       parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
+--- a/drivers/thunderbolt/domain.c
++++ b/drivers/thunderbolt/domain.c
+@@ -850,6 +850,36 @@ int tb_domain_disconnect_all_paths(struc
+       return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain);
+ }
++struct unregister_context {
++      const struct tb *tb;
++      int n;
++};
++
++static int unregister_unplugged_xdomain(struct device *dev, void *data)
++{
++      struct unregister_context *ctx = data;
++      struct tb_xdomain *xd;
++
++      xd = tb_to_xdomain(dev);
++      if (xd && xd->tb == ctx->tb && xd->is_unplugged) {
++              tb_xdomain_unregister(xd);
++              ctx->n++;
++      }
++      return 0;
++}
++
++int tb_domain_unregister_unplugged_xdomains(struct tb *tb)
++{
++      struct unregister_context ctx;
++
++      ctx.tb = tb_domain_get(tb);
++      ctx.n = 0;
++      bus_for_each_dev(&tb_bus_type, NULL, &ctx, unregister_unplugged_xdomain);
++      tb_domain_put(tb);
++
++      return ctx.n;
++}
++
+ int tb_domain_init(void)
+ {
+       int ret;
+--- a/drivers/thunderbolt/icm.c
++++ b/drivers/thunderbolt/icm.c
+@@ -738,6 +738,7 @@ static void remove_xdomain(struct tb_xdo
+       sw = tb_to_switch(xd->dev.parent);
+       tb_port_at(xd->route, sw)->xdomain = NULL;
++      xd->is_unplugged = true;
+       tb_xdomain_remove(xd);
+ }
+@@ -1762,6 +1763,8 @@ static void icm_handle_notification(stru
+       kfree(n->pkg);
+       kfree(n);
++
++      tb_domain_unregister_unplugged_xdomains(tb);
+ }
+ static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
+@@ -2112,6 +2115,8 @@ static void icm_rescan_work(struct work_
+       if (tb->root_switch)
+               icm_free_unplugged_children(tb->root_switch);
+       mutex_unlock(&tb->lock);
++
++      tb_domain_unregister_unplugged_xdomains(tb);
+ }
+ static void icm_complete(struct tb *tb)
+--- a/drivers/thunderbolt/switch.c
++++ b/drivers/thunderbolt/switch.c
+@@ -3625,6 +3625,20 @@ int tb_switch_resume(struct tb_switch *s
+                               tb_port_warn(port,
+                                            "lost during suspend, disconnecting\n");
+                               tb_sw_set_unplugged(port->remote->sw);
++                      } else if (port->xdomain) {
++                              /*
++                               * If the user replaced the XDomain with
++                               * another router, this will succeed in
++                               * which case we must remove the XDomain
++                               * before adding the new router.
++                               */
++                              err = tb_cfg_get_upstream_port(sw->tb->ctl,
++                                                             port->xdomain->route);
++                              if (err > 0) {
++                                      tb_port_warn(port,
++                                                   "XDomain was disconnected\n");
++                                      port->xdomain->is_unplugged = true;
++                              }
+                       }
+               }
+       }
+--- a/drivers/thunderbolt/tb.c
++++ b/drivers/thunderbolt/tb.c
+@@ -2524,6 +2524,8 @@ put_sw:
+ out:
+       mutex_unlock(&tb->lock);
++      tb_domain_unregister_unplugged_xdomains(tb);
++
+       pm_runtime_mark_last_busy(&tb->dev);
+       pm_runtime_put_autosuspend(&tb->dev);
+@@ -3110,6 +3112,24 @@ static void tb_restore_children(struct t
+       }
+ }
++static void tb_free_unplugged_xdomains(struct tb_switch *sw)
++{
++      struct tb_port *port;
++
++      tb_switch_for_each_port(sw, port) {
++              if (tb_is_upstream_port(port))
++                      continue;
++              if (port->xdomain && port->xdomain->is_unplugged) {
++                      tb_retimer_remove_all(port);
++                      tb_xdomain_remove(port->xdomain);
++                      tb_port_unconfigure_xdomain(port);
++                      port->xdomain = NULL;
++              } else if (port->remote) {
++                      tb_free_unplugged_xdomains(port->remote->sw);
++              }
++      }
++}
++
+ static int tb_resume_noirq(struct tb *tb)
+ {
+       struct tb_cm *tcm = tb_priv(tb);
+@@ -3129,6 +3149,7 @@ static int tb_resume_noirq(struct tb *tb
+       tb_switch_resume(tb->root_switch, false);
+       tb_free_invalid_tunnels(tb);
+       tb_free_unplugged_children(tb->root_switch);
++      tb_free_unplugged_xdomains(tb->root_switch);
+       tb_restore_children(tb->root_switch);
+       /*
+@@ -3171,28 +3192,6 @@ static int tb_resume_noirq(struct tb *tb
+       return 0;
+ }
+-static int tb_free_unplugged_xdomains(struct tb_switch *sw)
+-{
+-      struct tb_port *port;
+-      int ret = 0;
+-
+-      tb_switch_for_each_port(sw, port) {
+-              if (tb_is_upstream_port(port))
+-                      continue;
+-              if (port->xdomain && port->xdomain->is_unplugged) {
+-                      tb_retimer_remove_all(port);
+-                      tb_xdomain_remove(port->xdomain);
+-                      tb_port_unconfigure_xdomain(port);
+-                      port->xdomain = NULL;
+-                      ret++;
+-              } else if (port->remote) {
+-                      ret += tb_free_unplugged_xdomains(port->remote->sw);
+-              }
+-      }
+-
+-      return ret;
+-}
+-
+ static int tb_freeze_noirq(struct tb *tb)
+ {
+       struct tb_cm *tcm = tb_priv(tb);
+@@ -3212,14 +3211,14 @@ static int tb_thaw_noirq(struct tb *tb)
+ static void tb_complete(struct tb *tb)
+ {
+       /*
+-       * Release any unplugged XDomains and if there is a case where
++       * Unregister unplugged XDomains and if there is a case where
+        * another domain is swapped in place of unplugged XDomain we
+        * need to run another rescan.
+        */
+-      mutex_lock(&tb->lock);
+-      if (tb_free_unplugged_xdomains(tb->root_switch))
+-              tb_scan_switch(tb->root_switch);
+-      mutex_unlock(&tb->lock);
++      if (tb_domain_unregister_unplugged_xdomains(tb)) {
++              scoped_guard(mutex, &tb->lock)
++                      tb_scan_switch(tb->root_switch);
++      }
+ }
+ static int tb_runtime_suspend(struct tb *tb)
+@@ -3246,11 +3245,11 @@ static void tb_remove_work(struct work_s
+       struct tb *tb = tcm_to_tb(tcm);
+       mutex_lock(&tb->lock);
+-      if (tb->root_switch) {
++      if (tb->root_switch)
+               tb_free_unplugged_children(tb->root_switch);
+-              tb_free_unplugged_xdomains(tb->root_switch);
+-      }
+       mutex_unlock(&tb->lock);
++
++      tb_free_unplugged_xdomains(tb->root_switch);
+ }
+ static int tb_runtime_resume(struct tb *tb)
+--- a/drivers/thunderbolt/tb.h
++++ b/drivers/thunderbolt/tb.h
+@@ -793,6 +793,7 @@ int tb_domain_disconnect_xdomain_paths(s
+                                      int transmit_path, int transmit_ring,
+                                      int receive_path, int receive_ring);
+ int tb_domain_disconnect_all_paths(struct tb *tb);
++int tb_domain_unregister_unplugged_xdomains(struct tb *tb);
+ static inline struct tb *tb_domain_get(struct tb *tb)
+ {
+@@ -1263,6 +1264,7 @@ struct tb_xdomain *tb_xdomain_alloc(stru
+                                   const uuid_t *remote_uuid);
+ void tb_xdomain_add(struct tb_xdomain *xd);
+ void tb_xdomain_remove(struct tb_xdomain *xd);
++void tb_xdomain_unregister(struct tb_xdomain *xd);
+ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
+                                                u8 depth);
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -2072,41 +2072,54 @@ static int unregister_service(struct dev
+ }
+ /**
+- * tb_xdomain_remove() - Remove XDomain from the bus
++ * tb_xdomain_remove() - Remove XDomain
+  * @xd: XDomain to remove
+  *
+- * This will stop all ongoing configuration work and remove the XDomain
+- * along with any services from the bus. When the last reference to @xd
+- * is released the object will be released as well.
++ * This will stop all ongoing configuration work. XDomain is not removed
++ * from the bus if it was added. That needs to be done separately by
++ * calling tb_xdomain_unregister().
++ *
++ * Called with @tb->lock held.
+  */
+ void tb_xdomain_remove(struct tb_xdomain *xd)
+ {
+       tb_xdomain_debugfs_remove(xd);
+-
+       stop_handshake(xd);
+-
+-      device_for_each_child_reverse(&xd->dev, xd, unregister_service);
+-
+       tb_xdomain_link_exit(xd);
+-      /*
+-       * Undo runtime PM here explicitly because it is possible that
+-       * the XDomain was never added to the bus and thus device_del()
+-       * is not called for it (device_del() would handle this otherwise).
+-       */
+-      pm_runtime_disable(&xd->dev);
+-      pm_runtime_put_noidle(&xd->dev);
+-      pm_runtime_set_suspended(&xd->dev);
+-
+       if (!device_is_registered(&xd->dev)) {
++              /*
++               * Undo runtime PM here explicitly because it is
++               * possible that the XDomain was never added to the bus
++               * and thus device_del() is not called for it
++               * (device_del() would handle this otherwise).
++               */
++              pm_runtime_disable(&xd->dev);
++              pm_runtime_put_noidle(&xd->dev);
++              pm_runtime_set_suspended(&xd->dev);
+               put_device(&xd->dev);
+-      } else {
+-              dev_info(&xd->dev, "host disconnected\n");
+-              device_unregister(&xd->dev);
+       }
+ }
+ /**
++ * tb_xdomain_unregister() - Unregister XDomain
++ * @xd: XDomain to unregister
++ *
++ * This will unregister the XDomain along with any services from the
++ * bus. When the last reference to @xd is released the object will be
++ * released as well.
++ */
++void tb_xdomain_unregister(struct tb_xdomain *xd)
++{
++      lockdep_assert_not_held(&xd->tb->lock);
++
++      device_for_each_child_reverse(&xd->dev, xd, unregister_service);
++
++      dev_info(&xd->dev, "host disconnected\n");
++      device_unregister(&xd->dev);
++}
++
++/**
+  * tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain
+  * @xd: XDomain connection
+  *
diff --git a/queue-7.1/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch b/queue-7.1/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch
new file mode 100644 (file)
index 0000000..1075435
--- /dev/null
@@ -0,0 +1,429 @@
+From stable+bounces-287813-greg=kroah.com@vger.kernel.org Wed Jul 22 13:25:36 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:24:48 -0400
+Subject: xfs: don't replace the wrong part of the cow fork
+To: stable@vger.kernel.org
+Cc: "Darrick J. Wong" <djwong@kernel.org>, Christoph Hellwig <hch@lst.de>, Carlos Maiolino <cem@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722112448.1117201-1-sashal@kernel.org>
+
+From: "Darrick J. Wong" <djwong@kernel.org>
+
+[ Upstream commit a1caeeadbf57ff86dfc3454398c46de86056a74e ]
+
+LOLLM points out that xfs_iext_lookup_extent can return a @got where
+got->br_startoff < startoff.  In this case, xrep_cow_replace_range
+replaces the entire mapping instead of just the part that had been
+marked bad in the bitmap, but advances the bitmap cursor in
+xrep_cow_replace by the amount replaced.  As a result, we fail to
+replace the end of the bad range, and replace part of the good range.
+
+Fix this by rewriting the replace method to handle replacing the middle
+of a cow fork mapping.  This we do by returning both the current mapping
+as @got, and the subset of the mapping that we want to replace as @rep,
+using @rep to store the results of the new allocation, and comparing
+@rep to @got to figure out the exact transformations needed.
+
+Cc: stable@vger.kernel.org # v6.8
+Fixes: dbbdbd0086320a ("xfs: repair problems in CoW forks")
+Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
+Assisted-by: LOLLM # finding obvious bugs
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Signed-off-by: Carlos Maiolino <cem@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/xfs/scrub/cow_repair.c |  203 +++++++++++++++++++++++++++++-----------------
+ fs/xfs/scrub/trace.h      |   28 +++---
+ 2 files changed, 148 insertions(+), 83 deletions(-)
+
+--- a/fs/xfs/scrub/cow_repair.c
++++ b/fs/xfs/scrub/cow_repair.c
+@@ -80,12 +80,6 @@ struct xrep_cow {
+       unsigned int            next_bno;
+ };
+-/* CoW staging extent. */
+-struct xrep_cow_extent {
+-      xfs_fsblock_t           fsbno;
+-      xfs_extlen_t            len;
+-};
+-
+ /*
+  * Mark the part of the file range that corresponds to the given physical
+  * space.  Caller must ensure that the physical range is within xc->irec.
+@@ -401,22 +395,21 @@ out_rtg:
+ STATIC int
+ xrep_cow_alloc(
+       struct xfs_scrub        *sc,
+-      xfs_extlen_t            maxlen,
+-      struct xrep_cow_extent  *repl)
++      struct xfs_bmbt_irec    *del)
+ {
+       struct xfs_alloc_arg    args = {
+               .tp             = sc->tp,
+               .mp             = sc->mp,
+               .oinfo          = XFS_RMAP_OINFO_SKIP_UPDATE,
+               .minlen         = 1,
+-              .maxlen         = maxlen,
++              .maxlen         = del->br_blockcount,
+               .prod           = 1,
+               .resv           = XFS_AG_RESV_NONE,
+               .datatype       = XFS_ALLOC_USERDATA,
+       };
+       int                     error;
+-      error = xfs_trans_reserve_more(sc->tp, maxlen, 0);
++      error = xfs_trans_reserve_more(sc->tp, del->br_blockcount, 0);
+       if (error)
+               return error;
+@@ -429,8 +422,8 @@ xrep_cow_alloc(
+       xfs_refcount_alloc_cow_extent(sc->tp, false, args.fsbno, args.len);
+-      repl->fsbno = args.fsbno;
+-      repl->len = args.len;
++      del->br_startblock = args.fsbno;
++      del->br_blockcount = args.len;
+       return 0;
+ }
+@@ -441,10 +434,12 @@ xrep_cow_alloc(
+ STATIC int
+ xrep_cow_alloc_rt(
+       struct xfs_scrub        *sc,
+-      xfs_extlen_t            maxlen,
+-      struct xrep_cow_extent  *repl)
++      struct xfs_bmbt_irec    *del)
+ {
+-      xfs_rtxlen_t            maxrtx = xfs_rtb_to_rtx(sc->mp, maxlen);
++      xfs_fsblock_t           fsbno;
++      xfs_rtxlen_t            maxrtx =
++              min(U32_MAX, xfs_blen_to_rtbxlen(sc->mp, del->br_blockcount));
++      xfs_extlen_t            len;
+       int                     error;
+       error = xfs_trans_reserve_more(sc->tp, 0, maxrtx);
+@@ -452,11 +447,14 @@ xrep_cow_alloc_rt(
+               return error;
+       error = xfs_rtallocate_rtgs(sc->tp, NULLRTBLOCK, 1, maxrtx, 1, false,
+-                      false, &repl->fsbno, &repl->len);
++                      false, &fsbno, &len);
+       if (error)
+               return error;
+-      xfs_refcount_alloc_cow_extent(sc->tp, true, repl->fsbno, repl->len);
++      xfs_refcount_alloc_cow_extent(sc->tp, true, fsbno, len);
++
++      del->br_startblock = fsbno;
++      del->br_blockcount = len;
+       return 0;
+ }
+@@ -470,19 +468,19 @@ static inline int
+ xrep_cow_find_mapping(
+       struct xrep_cow         *xc,
+       struct xfs_iext_cursor  *icur,
+-      xfs_fileoff_t           startoff,
+-      struct xfs_bmbt_irec    *got)
++      xfs_fileoff_t           badoff,
++      xfs_extlen_t            badlen,
++      struct xfs_bmbt_irec    *got,
++      struct xfs_bmbt_irec    *rep)
+ {
+       struct xfs_inode        *ip = xc->sc->ip;
+       struct xfs_ifork        *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
+-      if (!xfs_iext_lookup_extent(ip, ifp, startoff, icur, got))
++      if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got))
+               goto bad;
++      memcpy(rep, got, sizeof(*rep));
+-      if (got->br_startoff > startoff)
+-              goto bad;
+-
+-      if (got->br_blockcount == 0)
++      if (got->br_startoff > badoff)
+               goto bad;
+       if (isnullstartblock(got->br_startblock))
+@@ -491,6 +489,24 @@ xrep_cow_find_mapping(
+       if (xfs_bmap_is_written_extent(got))
+               goto bad;
++      if (got->br_startoff < badoff) {
++              const int64_t   delta = badoff - got->br_startoff;
++
++              rep->br_blockcount -= delta;
++              rep->br_startoff += delta;
++              rep->br_startblock += delta;
++      }
++
++      if (got->br_startoff + got->br_blockcount > badoff + badlen) {
++              const int64_t   delta = (got->br_startoff + got->br_blockcount) -
++                                      (badoff + badlen);
++
++              rep->br_blockcount -= delta;
++      }
++
++      if (got->br_blockcount == 0)
++              goto bad;
++
+       return 0;
+ bad:
+       ASSERT(0);
+@@ -501,46 +517,92 @@ bad:
+ #define REPLACE_RIGHT_SIDE    (1U << 1)
+ /*
+- * Given a CoW fork mapping @got and a replacement mapping @repl, remap the
+- * beginning of @got with the space described by @rep.
++ * Given a CoW fork mapping @got and a replacement mapping @rep, map the space
++ * described by @rep into the cow fork, pushing aside @got as necessary.  @icur
++ * must point to iext tree leaf containing @got.
+  */
+ static inline void
+ xrep_cow_replace_mapping(
+-      struct xfs_inode                *ip,
+-      struct xfs_iext_cursor          *icur,
+-      const struct xfs_bmbt_irec      *got,
+-      const struct xrep_cow_extent    *repl)
++      struct xfs_inode        *ip,
++      struct xfs_iext_cursor  *icur,
++      struct xfs_bmbt_irec    *got,
++      struct xfs_bmbt_irec    *rep)
+ {
+-      struct xfs_bmbt_irec            new = *got; /* struct copy */
++      struct xfs_ifork        *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
++      xfs_fileoff_t           rep_endoff =
++                      rep->br_startoff + rep->br_blockcount;
++      xfs_fileoff_t           got_endoff =
++                      got->br_startoff + got->br_blockcount;
++      uint32_t                state = BMAP_COWFORK;
+-      ASSERT(repl->len > 0);
++      ASSERT(rep->br_blockcount > 0);
+       ASSERT(!isnullstartblock(got->br_startblock));
++      ASSERT(got->br_startoff <= rep->br_startoff);
++      ASSERT(got_endoff >= rep_endoff);
+-      trace_xrep_cow_replace_mapping(ip, got, repl->fsbno, repl->len);
++      trace_xrep_cow_replace_mapping(ip, got, rep);
+-      if (got->br_blockcount == repl->len) {
++      if (got->br_startoff == rep->br_startoff)
++              state |= BMAP_LEFT_FILLING;
++      if (got_endoff == rep_endoff)
++              state |= BMAP_RIGHT_FILLING;
++
++      switch (state & (BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING)) {
++      case BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING:
+               /*
+-               * The new extent is a complete replacement for the existing
+-               * extent.  Update the COW fork record.
++               * Replacement matches the whole mapping, update the record.
+                */
+-              new.br_startblock = repl->fsbno;
+-              xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
+-              return;
+-      }
++              xfs_iext_update_extent(ip, state, icur, rep);
++              break;
++      case BMAP_LEFT_FILLING:
++              /*
++               * Replace the first part of the mapping: Update the cursor
++               * position with the new mapping, then add a record with the
++               * tail of the old mapping.
++               */
++              got->br_startoff = rep_endoff;
++              got->br_blockcount -= rep->br_blockcount;
++              got->br_startblock += rep->br_blockcount;
++
++              xfs_iext_update_extent(ip, state, icur, rep);
++              xfs_iext_next(ifp, icur);
++              xfs_iext_insert(ip, icur, got, state);
++              break;
++      case BMAP_RIGHT_FILLING:
++              /*
++               * Replacing the last part of the mapping.  Shorten the current
++               * mapping then add a record with the new mapping.
++               */
++              got->br_blockcount -= rep->br_blockcount;
+-      /*
+-       * The new extent can replace the beginning of the COW fork record.
+-       * Move the left side of @got upwards, then insert the new record.
+-       */
+-      new.br_startoff += repl->len;
+-      new.br_startblock += repl->len;
+-      new.br_blockcount -= repl->len;
+-      xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
+-
+-      new.br_startoff = got->br_startoff;
+-      new.br_startblock = repl->fsbno;
+-      new.br_blockcount = repl->len;
+-      xfs_iext_insert(ip, icur, &new, BMAP_COWFORK);
++              xfs_iext_update_extent(ip, state, icur, got);
++              xfs_iext_next(ifp, icur);
++              xfs_iext_insert(ip, icur, rep, state);
++              break;
++      case 0:
++              /*
++               * Replacing the middle of the extent.  Shorten the current
++               * mapping, add a new record with the new mapping, and add a
++               * second new record with the tail of the old mapping.
++               */
++              got->br_blockcount = rep->br_startoff - got->br_startoff;
++
++              struct xfs_bmbt_irec    new = {
++                      .br_startoff    = rep_endoff,
++                      .br_blockcount  = got_endoff - rep_endoff,
++                      .br_state       = got->br_state,
++                      .br_startblock  = got->br_startblock +
++                                              rep->br_blockcount +
++                                              got->br_blockcount,
++              };
++
++              xfs_iext_update_extent(ip, state, icur, got);
++              xfs_iext_next(ifp, icur);
++              xfs_iext_insert(ip, icur, rep, state);
++              xfs_iext_next(ifp, icur);
++              xfs_iext_insert(ip, icur, &new, state);
++              break;
++      }
+ }
+ /*
+@@ -554,33 +616,30 @@ xrep_cow_replace_range(
+       xfs_extlen_t            *blockcount)
+ {
+       struct xfs_iext_cursor  icur;
+-      struct xrep_cow_extent  repl;
+-      struct xfs_bmbt_irec    got;
++      struct xfs_bmbt_irec    got, rep;
+       struct xfs_scrub        *sc = xc->sc;
+-      xfs_fileoff_t           nextoff;
+-      xfs_extlen_t            alloc_len;
++      xfs_fsblock_t           old_fsbno;
+       int                     error;
+       /*
+-       * Put the existing CoW fork mapping in @got.  If @got ends before
+-       * @rep, truncate @rep so we only replace one extent mapping at a time.
++       * Put the existing CoW fork mapping in @got, and put in @rep the
++       * contents of @got trimmed to @startoff/@blockcount.  We only want
++       * to replace the bad region, and only one mapping at a time.
+        */
+-      error = xrep_cow_find_mapping(xc, &icur, startoff, &got);
++      error = xrep_cow_find_mapping(xc, &icur, startoff, *blockcount, &got,
++                      &rep);
+       if (error)
+               return error;
+-      nextoff = min(startoff + *blockcount,
+-                    got.br_startoff + got.br_blockcount);
++      old_fsbno = rep.br_startblock;
+       /*
+        * Allocate a replacement extent.  If we don't fill all the blocks,
+        * shorten the quantity that will be deleted in this step.
+        */
+-      alloc_len = min_t(xfs_fileoff_t, XFS_MAX_BMBT_EXTLEN,
+-                        nextoff - startoff);
+       if (XFS_IS_REALTIME_INODE(sc->ip))
+-              error = xrep_cow_alloc_rt(sc, alloc_len, &repl);
++              error = xrep_cow_alloc_rt(sc, &rep);
+       else
+-              error = xrep_cow_alloc(sc, alloc_len, &repl);
++              error = xrep_cow_alloc(sc, &rep);
+       if (error)
+               return error;
+@@ -588,7 +647,7 @@ xrep_cow_replace_range(
+        * Replace the old mapping with the new one, and commit the metadata
+        * changes made so far.
+        */
+-      xrep_cow_replace_mapping(sc->ip, &icur, &got, &repl);
++      xrep_cow_replace_mapping(sc->ip, &icur, &got, &rep);
+       xfs_inode_set_cowblocks_tag(sc->ip);
+       error = xfs_defer_finish(&sc->tp);
+@@ -597,15 +656,15 @@ xrep_cow_replace_range(
+       /* Note the old CoW staging extents; we'll reap them all later. */
+       if (XFS_IS_REALTIME_INODE(sc->ip))
+-              error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks,
+-                              got.br_startblock, repl.len);
++              error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks, old_fsbno,
++                              rep.br_blockcount);
+       else
+-              error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks,
+-                              got.br_startblock, repl.len);
++              error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks, old_fsbno,
++                              rep.br_blockcount);
+       if (error)
+               return error;
+-      *blockcount = repl.len;
++      *blockcount = rep.br_blockcount;
+       return 0;
+ }
+--- a/fs/xfs/scrub/trace.h
++++ b/fs/xfs/scrub/trace.h
+@@ -2672,9 +2672,9 @@ TRACE_EVENT(xrep_cow_mark_file_range,
+ );
+ TRACE_EVENT(xrep_cow_replace_mapping,
+-      TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *irec,
+-               xfs_fsblock_t new_startblock, xfs_extlen_t new_blockcount),
+-      TP_ARGS(ip, irec, new_startblock, new_blockcount),
++      TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got,
++               const struct xfs_bmbt_irec *rep),
++      TP_ARGS(ip, got, rep),
+       TP_STRUCT__entry(
+               __field(dev_t, dev)
+               __field(xfs_ino_t, ino)
+@@ -2682,28 +2682,34 @@ TRACE_EVENT(xrep_cow_replace_mapping,
+               __field(xfs_fileoff_t, startoff)
+               __field(xfs_filblks_t, blockcount)
+               __field(xfs_exntst_t, state)
++              __field(xfs_fileoff_t, new_startoff)
+               __field(xfs_fsblock_t, new_startblock)
+               __field(xfs_extlen_t, new_blockcount)
++              __field(xfs_exntst_t, new_state)
+       ),
+       TP_fast_assign(
+               __entry->dev = ip->i_mount->m_super->s_dev;
+               __entry->ino = ip->i_ino;
+-              __entry->startoff = irec->br_startoff;
+-              __entry->startblock = irec->br_startblock;
+-              __entry->blockcount = irec->br_blockcount;
+-              __entry->state = irec->br_state;
+-              __entry->new_startblock = new_startblock;
+-              __entry->new_blockcount = new_blockcount;
++              __entry->startoff = got->br_startoff;
++              __entry->startblock = got->br_startblock;
++              __entry->blockcount = got->br_blockcount;
++              __entry->state = got->br_state;
++              __entry->new_startoff = rep->br_startoff;
++              __entry->new_startblock = rep->br_startblock;
++              __entry->new_blockcount = rep->br_blockcount;
++              __entry->new_state = rep->br_state;
+       ),
+-      TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startblock 0x%llx new_fsbcount 0x%x",
++      TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startoff 0x%llx new_startblock 0x%llx new_fsbcount 0x%x new_state 0x%x",
+                 MAJOR(__entry->dev), MINOR(__entry->dev),
+                 __entry->ino,
+                 __entry->startoff,
+                 __entry->startblock,
+                 __entry->blockcount,
+                 __entry->state,
++                __entry->new_startoff,
+                 __entry->new_startblock,
+-                __entry->new_blockcount)
++                __entry->new_blockcount,
++                __entry->new_state)
+ );
+ TRACE_EVENT(xrep_cow_free_staging,