From: Greg Kroah-Hartman Date: Thu, 30 Jul 2026 11:41:03 +0000 (+0200) Subject: 7.1-stable patches X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=01bbf469d82129cc24de6b75008fcbd2ae40d513;p=thirdparty%2Fkernel%2Fstable-queue.git 7.1-stable patches 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 --- 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 index 0000000000..1e70807143 --- /dev/null +++ b/queue-7.1/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch @@ -0,0 +1,364 @@ +From stable+bounces-280758-greg=kroah.com@vger.kernel.org Tue Jul 21 23:11:42 2026 +From: Sasha Levin +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 , stable@kernel.org, Waiman Long , Richard Guy Briggs , Nathan Chancellor , Paul Moore , Sasha Levin +Message-ID: <20260721180149.286443-2-sashal@kernel.org> + +From: Ricardo Robaina + +[ 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: + + 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 + + +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 +Acked-by: Richard Guy Briggs +Signed-off-by: Nathan Chancellor +Signed-off-by: Ricardo Robaina +[PM: move link metadata into the msg, apply fix from NC] +Signed-off-by: Paul Moore +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..05dbf7d70c --- /dev/null +++ b/queue-7.1/audit-use-unsigned-int-instead-of-unsigned.patch @@ -0,0 +1,202 @@ +From stable+bounces-280757-greg=kroah.com@vger.kernel.org Tue Jul 21 23:11:44 2026 +From: Sasha Levin +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 , Paul Moore , Sasha Levin +Message-ID: <20260721180149.286443-1-sashal@kernel.org> + +From: Ricardo Robaina + +[ 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 +Signed-off-by: Paul Moore +Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + +-unsigned compat_dir_class[] = { ++unsigned int compat_dir_class[] = { + #include + ~0U + }; + +-unsigned compat_read_class[] = { ++unsigned int compat_read_class[] = { + #include + ~0U + }; + +-unsigned compat_write_class[] = { ++unsigned int compat_write_class[] = { + #include + ~0U + }; + +-unsigned compat_chattr_class[] = { ++unsigned int compat_chattr_class[] = { + #include + ~0U + }; + +-unsigned compat_signal_class[] = { ++unsigned int compat_signal_class[] = { + #include + ~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 index 0000000000..31bcc21a30 --- /dev/null +++ b/queue-7.1/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch @@ -0,0 +1,97 @@ +From stable+bounces-290075-greg=kroah.com@vger.kernel.org Tue Jul 28 19:10:17 2026 +From: Sasha Levin +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 , "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260728161043.36478-2-sashal@kernel.org> + +From: Breno Leitao + +[ 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 +Signed-off-by: Masami Hiramatsu (Google) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..77176b423a --- /dev/null +++ b/queue-7.1/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch @@ -0,0 +1,165 @@ +From stable+bounces-290074-greg=kroah.com@vger.kernel.org Tue Jul 28 18:58:25 2026 +From: Sasha Levin +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 , "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260728161043.36478-1-sashal@kernel.org> + +From: Breno Leitao + +[ 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 +Signed-off-by: Masami Hiramatsu (Google) +Stable-dep-of: dec4d8118c17 ("bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..e7c12db728 --- /dev/null +++ b/queue-7.1/fuse-uring-fix-race-between-registration-and-connection-abortion.patch @@ -0,0 +1,100 @@ +From stable+bounces-287812-greg=kroah.com@vger.kernel.org Wed Jul 22 13:34:34 2026 +From: Sasha Levin +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 , Bernd Schubert , Miklos Szeredi , Sasha Levin +Message-ID: <20260722112431.1115514-1-sashal@kernel.org> + +From: Joanne Koong + +[ 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 +Signed-off-by: Joanne Koong +Signed-off-by: Miklos Szeredi +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..e59b2ae1f3 --- /dev/null +++ b/queue-7.1/mm-sparse-vmemmap-fix-dax-vmemmap-accounting-with-optimization.patch @@ -0,0 +1,112 @@ +From stable+bounces-288837-greg=kroah.com@vger.kernel.org Fri Jul 24 13:00:29 2026 +From: Sasha Levin +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 , "Mike Rapoport (Microsoft)" , Oscar Salvador , "David Hildenbrand (Arm)" , "Liam R. Howlett" , "Aneesh Kumar K.V" , Joao Martins , Lorenzo Stoakes , Madhavan Srinivasan , Michael Ellerman , Michal Hocko , Nicholas Piggin , Suren Baghdasaryan , Vlastimil Babka , Andrew Morton , Sasha Levin +Message-ID: <20260724104906.3974254-4-sashal@kernel.org> + +From: Muchun Song + +[ 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 +Acked-by: Mike Rapoport (Microsoft) +Acked-by: Oscar Salvador +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..dd95c1f819 --- /dev/null +++ b/queue-7.1/mm-sparse-vmemmap-pass-pgmap-argument-to-memory-deactivation-paths.patch @@ -0,0 +1,324 @@ +From stable+bounces-288835-greg=kroah.com@vger.kernel.org Fri Jul 24 12:58:55 2026 +From: Sasha Levin +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 , "Mike Rapoport (Microsoft)" , Oscar Salvador , "David Hildenbrand (Arm)" , "Liam R. Howlett" , "Aneesh Kumar K.V" , Joao Martins , Lorenzo Stoakes , Madhavan Srinivasan , Michael Ellerman , Michal Hocko , Nicholas Piggin , Suren Baghdasaryan , Vlastimil Babka , Andrew Morton , Sasha Levin +Message-ID: <20260724104906.3974254-3-sashal@kernel.org> + +From: Muchun Song + +[ 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 +Acked-by: Mike Rapoport (Microsoft) +Reviewed-by: Oscar Salvador +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Signed-off-by: Andrew Morton +Stable-dep-of: 721a73e30c9e ("mm/sparse-vmemmap: fix DAX vmemmap accounting with optimization") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..380594c36d --- /dev/null +++ b/queue-7.1/net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch @@ -0,0 +1,253 @@ +From stable+bounces-289855-greg=kroah.com@vger.kernel.org Tue Jul 28 05:51:06 2026 +From: Sasha Levin +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 , Erni Sri Satya Vennela , Haiyang Zhang , Simon Horman , Yury Norov , Jakub Kicinski , Sasha Levin +Message-ID: <20260728034439.3162982-1-sashal@kernel.org> + +From: Shradha Gupta + +[ 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 +Signed-off-by: Erni Sri Satya Vennela +Signed-off-by: Shradha Gupta +Reviewed-by: Haiyang Zhang +Reviewed-by: Simon Horman +Reviewed-by: Yury Norov +Link: https://patch.msgid.link/20260624072138.1632849-1-shradhagupta@linux.microsoft.com +Signed-off-by: Jakub Kicinski +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..001bd90eec --- /dev/null +++ b/queue-7.1/sched_ext-move-shared-helpers-from-ext.c-into-internal.h-and-cid.h.patch @@ -0,0 +1,141 @@ +From stable+bounces-290832-greg=kroah.com@vger.kernel.org Wed Jul 29 18:35:11 2026 +From: Sasha Levin +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 , Peter Zijlstra , Andrea Righi , Sasha Levin +Message-ID: <20260729162433.3523095-1-sashal@kernel.org> + +From: Tejun Heo + +[ 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 +Signed-off-by: Tejun Heo +Reviewed-by: Andrea Righi +Stable-dep-of: 18d62044cda7 ("sched_ext: Preserve rq tracking across local DSQ dispatch") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..12e527437c --- /dev/null +++ b/queue-7.1/sched_ext-preserve-rq-tracking-across-local-dsq-dispatch.patch @@ -0,0 +1,141 @@ +From stable+bounces-290831-greg=kroah.com@vger.kernel.org Wed Jul 29 18:35:11 2026 +From: Sasha Levin +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 , Tejun Heo , Sasha Levin +Message-ID: <20260729162433.3523095-2-sashal@kernel.org> + +From: Andrea Righi + +[ 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 +Signed-off-by: Tejun Heo +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } diff --git a/queue-7.1/series b/queue-7.1/series index 1c654fce68..e4d1b7e959 100644 --- a/queue-7.1/series +++ b/queue-7.1/series @@ -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 index 0000000000..86b3a86b73 --- /dev/null +++ b/queue-7.1/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch @@ -0,0 +1,279 @@ +From stable+bounces-288461-greg=kroah.com@vger.kernel.org Thu Jul 23 19:47:54 2026 +From: Sasha Levin +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 , Jeff Layton , Anna Schumaker , Sasha Levin +Message-ID: <20260723173538.3416792-1-sashal@kernel.org> + +From: Chuck Lever + +[ 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 +Acked-by: Anna Schumaker +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..377799c239 --- /dev/null +++ b/queue-7.1/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch @@ -0,0 +1,165 @@ +From stable+bounces-288463-greg=kroah.com@vger.kernel.org Thu Jul 23 19:37:10 2026 +From: Sasha Levin +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 , Chris Mason , Jeff Layton , Sasha Levin +Message-ID: <20260723173538.3416792-3-sashal@kernel.org> + +From: Chuck Lever + +[ 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 +Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly") +Cc: stable@vger.kernel.org +Reviewed-by: Jeff Layton +Signed-off-by: Chuck Lever +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..2530717565 --- /dev/null +++ b/queue-7.1/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch @@ -0,0 +1,42 @@ +From stable+bounces-289101-greg=kroah.com@vger.kernel.org Sat Jul 25 16:12:32 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260725141222.3814514-1-sashal@kernel.org> + +From: Mika Westerberg + +[ 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 +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..ab87522b85 --- /dev/null +++ b/queue-7.1/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch @@ -0,0 +1,90 @@ +From stable+bounces-289104-greg=kroah.com@vger.kernel.org Sat Jul 25 16:13:42 2026 +From: Sasha Levin +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 , Mika Westerberg , Sasha Levin +Message-ID: <20260725141222.3814514-4-sashal@kernel.org> + +From: Michael Bommarito + +[ 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 +Signed-off-by: Mika Westerberg +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..49469d5484 --- /dev/null +++ b/queue-7.1/thunderbolt-remove-service-debugfs-entries-during-unregister.patch @@ -0,0 +1,76 @@ +From stable+bounces-289102-greg=kroah.com@vger.kernel.org Sat Jul 25 16:12:32 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260725141222.3814514-2-sashal@kernel.org> + +From: Mika Westerberg + +[ 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 +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..d2658453c4 --- /dev/null +++ b/queue-7.1/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch @@ -0,0 +1,337 @@ +From stable+bounces-289103-greg=kroah.com@vger.kernel.org Sat Jul 25 16:12:40 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260725141222.3814514-3-sashal@kernel.org> + +From: Mika Westerberg + +[ 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 +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..1075435a4e --- /dev/null +++ b/queue-7.1/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch @@ -0,0 +1,429 @@ +From stable+bounces-287813-greg=kroah.com@vger.kernel.org Wed Jul 22 13:25:36 2026 +From: Sasha Levin +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" , Christoph Hellwig , Carlos Maiolino , Sasha Levin +Message-ID: <20260722112448.1117201-1-sashal@kernel.org> + +From: "Darrick J. Wong" + +[ 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" +Assisted-by: LOLLM # finding obvious bugs +Reviewed-by: Christoph Hellwig +Signed-off-by: Carlos Maiolino +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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,