From 405c00e43a9c72a0b9b318cc2d2e298386ffb58e Mon Sep 17 00:00:00 2001 From: DAI RENJIE Date: Fri, 21 Aug 2026 14:08:17 +0000 Subject: [PATCH 001/134] resource: fix lost wakeup when waiting for a muxed region A task waiting for a muxed region can sleep forever in TASK_UNINTERRUPTIBLE even though the region it waits for is already free. __request_region_locked() queues itself on muxed_resource_wait and drops resource_lock before setting TASK_UNINTERRUPTIBLE, while __release_region() wakes the queue after dropping the same lock. A wakeup landing in between finds TASK_RUNNING, does not match TASK_NORMAL and is discarded; callers hold a muxed region only across a bounded transaction, so no further release is coming. The task is unkillable and its caller never returns. The window is one store wide, but an interrupt is enough to hold the waiter in it, and the machine this was seen on runs PREEMPT_DYNAMIC in its voluntary default. Since v6.11 spd5118 exports the DDR5 sensors of AMD boards through i2c-piix4, which takes a muxed region per SMBus transaction; a third of the in-tree users of request_muxed_region() are hwmon drivers, so reading a world-readable attribute is all an unprivileged user needs to drive the contention. The blocked task sleeps holding the i2c adapter bus lock, and 27 more piled up behind it. Reproduced by building a kernel with the two orderings selectable at runtime and a 2ms delay inside the window. Switching only that knob, a two-thread barriered reproducer loses the wakeup 200 times out of 200 before the fix and 0 out of 200 after it; without the delay it goes 20000 times through the wait path and loses none. Fix it by setting the task state before dropping resource_lock, as prepare_to_wait() does: the releasing side needs resource_lock to unlink the resource, so it cannot reach the wakeup before the state is published. Link: https://lore.kernel.org/20260821-b4-resource-muxed-lost-wakeup-v1-1-37eb6473a76c@gmail.com Fixes: 8b6d043b7ee2 ("resource: shared I/O region support") Signed-off-by: DAI RENJIE Signed-off-by: Andrew Morton Reviewed-by: Bradley Morgan Reviewed-by: Andrew Morton Assisted-by: Claude:claude-opus-5 Cc: Mark Brown Cc: Kees Cook Cc: Bjorn Helgaas Cc: --- kernel/resource.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/resource.c b/kernel/resource.c index e60539a55541..54d7199695fb 100644 --- a/kernel/resource.c +++ b/kernel/resource.c @@ -1350,8 +1350,8 @@ static int __request_region_locked(struct resource *res, struct resource *parent } if (conflict->flags & flags & IORESOURCE_MUXED) { add_wait_queue(&muxed_resource_wait, &wait); - write_unlock(&resource_lock); set_current_state(TASK_UNINTERRUPTIBLE); + write_unlock(&resource_lock); schedule(); remove_wait_queue(&muxed_resource_wait, &wait); write_lock(&resource_lock); From 63c4b67ee163b2a67a7561314bf2106d6c79c53c Mon Sep 17 00:00:00 2001 From: OGAWA Hirofumi Date: Tue, 25 Aug 2026 21:11:32 +0900 Subject: [PATCH 002/134] fat: fix fat_ent_write() for reverting the value commit 64d9183203ee ("fat: restore original value when fat_ent_write failed") try to revert the fatent value to old value when got the error on mirror FAT. However it didn't work if the error is when writing the fatent bh. In that case, the bh is cleared the uptodate flag, so reuse bh is invalid. Fix this by reverting the fatent only if got the error on mirror FAT. Link: https://lore.kernel.org/87ik4yz9fv.fsf_-_@mail.parknet.co.jp Fixes: 64d9183203ee ("fat: restore original value when fat_ent_write failed") Signed-off-by: OGAWA Hirofumi Signed-off-by: Andrew Morton Reported-by: syzbot+e64c6472a3d96a75172a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e64c6472a3d96a75172a Reported-by: syzbot+26461e903494e689c24f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=26461e903494e689c24f Cc: Yemu Lu Cc: Ren Wei Cc: Yuan Tan Cc: Yifan Wu Cc: Juefei Pu Cc: Xin Liu Cc: --- fs/fat/fat.h | 2 +- fs/fat/fatent.c | 21 ++++++++++++++++++--- fs/fat/file.c | 3 ++- fs/fat/misc.c | 6 ++---- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/fs/fat/fat.h b/fs/fat/fat.h index 61338413d9f3..fbd207c55859 100644 --- a/fs/fat/fat.h +++ b/fs/fat/fat.h @@ -392,7 +392,7 @@ extern void fat_ent_access_init(struct super_block *sb); extern int fat_ent_read(struct inode *inode, struct fat_entry *fatent, int entry); extern int fat_ent_write(struct inode *inode, struct fat_entry *fatent, - int new, int wait); + int new, int old, int wait); extern int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster); extern int fat_free_clusters(struct inode *inode, int cluster); diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c index f0801d99dd62..df23fc85f313 100644 --- a/fs/fat/fatent.c +++ b/fs/fat/fatent.c @@ -413,7 +413,7 @@ static int fat_mirror_bhs(struct super_block *sb, struct buffer_head **bhs, } int fat_ent_write(struct inode *inode, struct fat_entry *fatent, - int new, int wait) + int new, int old, int wait) { struct super_block *sb = inode->i_sb; const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops; @@ -422,10 +422,25 @@ int fat_ent_write(struct inode *inode, struct fat_entry *fatent, ops->ent_put(fatent, new); if (wait) { err = fat_sync_bhs(fatent->bhs, fatent->nr_bhs); - if (err) + if (err) { + /* + * bhs are not uptodate after I/O error. So we + * can't simply re-dirty to revert. And it + * would not have value to write again on I/O + * error. + */ return err; + } } - return fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs); + + err = fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs); + if (err) { + /* Try to revert if got the error on mirror FAT */ + ops->ent_put(fatent, old); + if (wait) + fat_sync_bhs(fatent->bhs, fatent->nr_bhs); + } + return err; } static inline int fat_ent_next(struct msdos_sb_info *sbi, diff --git a/fs/fat/file.c b/fs/fat/file.c index 1c835ca5f21a..6c475c53334c 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -363,7 +363,8 @@ static int fat_free(struct inode *inode, int skip) __func__, MSDOS_I(inode)->i_pos); ret = -EIO; } else if (ret > 0) { - err = fat_ent_write(inode, &fatent, FAT_ENT_EOF, wait); + err = fat_ent_write(inode, &fatent, FAT_ENT_EOF, ret, + wait); if (err) ret = err; } diff --git a/fs/fat/misc.c b/fs/fat/misc.c index e79762cf1975..c44296756eae 100644 --- a/fs/fat/misc.c +++ b/fs/fat/misc.c @@ -133,11 +133,9 @@ int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster) ret = fat_ent_read(inode, &fatent, last); if (ret >= 0) { int wait = inode_needs_sync(inode); - int old = ret; - ret = fat_ent_write(inode, &fatent, new_dclus, wait); - if (ret < 0) - fat_ent_write(inode, &fatent, old, wait); + ret = fat_ent_write(inode, &fatent, new_dclus, ret, + wait); fatent_brelse(&fatent); } if (ret < 0) From ceb43516a9aae0d969a4ec5ab40d45bd09e64229 Mon Sep 17 00:00:00 2001 From: Michael Liang Date: Fri, 21 Aug 2026 12:15:27 -0600 Subject: [PATCH 003/134] fault-inject: fix dentry leak fault_create_debugfs_attr() has always taken an extra dentry reference on the created directory (attr->dname = dget(dir)) so that fail_dump() could print the name via %pd from any context. Nothing anywhere in the tree ever calls dput() on attr->dname. For callers with a matching teardown, that unmatched reference causes one dentry plus its attached inode to leak per fault_create_debugfs_attr / debugfs_remove_recursive cycle. simple_recursive_removal() drops debugfs's own +1 ref on the child dentry, but the dget()'d ref keeps its refcount at 1: the dentry ends up unhashed but pinned, and its inode is never freed. Boot-once callers (mm/failslab, block/blk-core, etc.) leak exactly once at init and never destroy the tree, so the impact there is bounded. But per-lifecycle callers (drivers/nvme, drivers/infiniband/hw/hfi1, drivers/mmc, drivers/iommu/iommufd, drivers/media, drivers/misc, drivers/gpu/drm/msm, drivers/crypto, net/sunrpc) leak on every create/destroy cycle. We observed this in production: an NVMe/RDMA host repeatedly reconnecting to a target that rejected the CRTO Property Get went through ~50 nvme controller create/destroy cycles per second, and dentry and inode_cache grew by ~13k pinned objects per 240 s -- unrecoverable through drop_caches. Byte math matched a per-cycle 1-dentry / 1-inode leak from the "fault_inject" directory dentry. Fix this by not holding any external reference in fault_attr. Embed the directory name as a fixed-size char array (FAULT_ATTR_DNAME_LEN, 64 bytes) inside struct fault_attr, copied by strscpy() at fault_create_debugfs_attr() time. fail_dump() prints it via %s. Advantages of an embedded array over kstrdup() + kfree() paired with a new destroy API: - Zero API footprint. No new export and no caller changes required: callers already own their fault_attr's memory and free it when they are done, and now that suffices. - No allocation on the create path. - fault_create_debugfs_attr() cannot fail from the name-copy step. - No lifetime coupling between attr->dname and debugfs; the string is valid for exactly as long as the containing struct. The 64-byte length accommodates every in-tree caller with generous headroom (the longest current name is "fail_dma_array_full", 19 chars). The user-visible fail_dump() format changes from "name %pd" to "name %s", but the printed content is identical -- %pd on the created directory renders the same string that was passed in as @name. drivers/infiniband/hw/hfi1/fault.c drops a now-invalid "attr.dname = NULL" statement; the surrounding kzalloc() already zero-initialises the array. Link: https://lore.kernel.org/20260821181527.3271414-1-mliang@purestorage.com Fixes: 6adc4a22f20b ("fault-inject: add ratelimit option") Signed-off-by: Michael Liang Signed-off-by: Andrew Morton Reviewed-by: Andrew Morton Cc: Akinbou Mita Cc: Dennis Dalessandro Cc: Jason Gunthorpe Cc: Leon Romanovsky Cc: Vlastimil Babka Cc: --- drivers/infiniband/hw/hfi1/fault.c | 1 - include/linux/fault-inject.h | 10 ++++++++-- lib/fault-inject.c | 7 +++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/hfi1/fault.c b/drivers/infiniband/hw/hfi1/fault.c index 4ab72ef03ba1..941a0b96590b 100644 --- a/drivers/infiniband/hw/hfi1/fault.c +++ b/drivers/infiniband/hw/hfi1/fault.c @@ -216,7 +216,6 @@ int hfi1_fault_init_debugfs(struct hfi1_ibdev *ibd) ibd->fault->attr.interval = 1; ibd->fault->attr.require_end = ULONG_MAX; ibd->fault->attr.stacktrace_depth = 32; - ibd->fault->attr.dname = NULL; ibd->fault->attr.verbose = 0; ibd->fault->enable = false; ibd->fault->opcode = false; diff --git a/include/linux/fault-inject.h b/include/linux/fault-inject.h index 58fd14c82270..5c74748a53f3 100644 --- a/include/linux/fault-inject.h +++ b/include/linux/fault-inject.h @@ -18,6 +18,13 @@ enum fault_flags { #include #include +/* + * Length of the debugfs directory name embedded in struct fault_attr. + * Chosen to accommodate every in-tree caller of fault_create_debugfs_attr() + * (the longest is "fail_dma_array_full", 19 chars) with generous headroom. + */ +#define FAULT_ATTR_DNAME_LEN 64 + /* * For explanation of the elements of this struct, see * Documentation/fault-injection/fault-injection.rst @@ -37,7 +44,7 @@ struct fault_attr { unsigned long count; struct ratelimit_state ratelimit_state; - struct dentry *dname; + char dname[FAULT_ATTR_DNAME_LEN]; }; #define FAULT_ATTR_INITIALIZER { \ @@ -47,7 +54,6 @@ struct fault_attr { .stacktrace_depth = 32, \ .ratelimit_state = RATELIMIT_STATE_INIT_DISABLED, \ .verbose = 2, \ - .dname = NULL, \ } #define DECLARE_FAULT_ATTR(name) struct fault_attr name = FAULT_ATTR_INITIALIZER diff --git a/lib/fault-inject.c b/lib/fault-inject.c index 999053fa133e..02916ef2761c 100644 --- a/lib/fault-inject.c +++ b/lib/fault-inject.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ static void fail_dump(struct fault_attr *attr) { if (attr->verbose > 0 && __ratelimit(&attr->ratelimit_state)) { printk(KERN_NOTICE "FAULT_INJECTION: forcing a failure.\n" - "name %pd, interval %lu, probability %lu, " + "name %s, interval %lu, probability %lu, " "space %d, times %d\n", attr->dname, attr->interval, attr->probability, atomic_read(&attr->space), @@ -261,7 +262,9 @@ struct dentry *fault_create_debugfs_attr(const char *name, debugfs_create_xul("reject-end", mode, dir, &attr->reject_end); #endif /* CONFIG_FAULT_INJECTION_STACKTRACE_FILTER */ - attr->dname = dget(dir); + if (strscpy(attr->dname, name, sizeof(attr->dname)) == -E2BIG) + pr_warn("FAULT_INJECTION: name '%s' truncated to '%s'\n", + name, attr->dname); return dir; } EXPORT_SYMBOL_GPL(fault_create_debugfs_attr); From d41ebc7f9e2cc80cb57139c05e71415a79582ab0 Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Mon, 10 Aug 2026 18:11:59 +0200 Subject: [PATCH 004/134] checkpatch: skip CamelCase cache for --no-tree without root Running outside tree (--no-tree) without git root (--root DIR) is not doable because we have no include/ directory which could be cached. But 3445686af721 expected that we are always in Linux tree (w/a git). But --no-tree does not require --root. Therefore skip whole caching in that case. This fixes perl and find errors when running checkpatch.pl *with* --no-tree --strict and *without* --root: No structs that should be const will be found - file 'scripts/const_structs.checkpatch': No such file or directory Use of uninitialized value $root in concatenation (.) or string at scripts/checkpatch.pl line 1213. find: `/include': No such file or directory Link: https://lore.kernel.org/20260810161159.1044160-1-pvorel@suse.cz Fixes: 3445686af721 ("checkpatch: ignore existing CamelCase uses from include/...") Signed-off-by: Petr Vorel Signed-off-by: Andrew Morton Cc: Andy Whitcroft Cc: Dwaipayan Ray Cc: Joe Perches Cc: Lukas Bulwahn --- scripts/checkpatch.pl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 8a7787d228a6..f424dafce5bc 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -1206,6 +1206,8 @@ sub seed_camelcase_includes { my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`; chomp $git_last_include_commit; $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; + } elsif (not defined $root) { + return; } else { my $last_mod_date = 0; $files = `find $root/include -name "*.h"`; From a156148976eac47bee7210097afbb609cafe88b6 Mon Sep 17 00:00:00 2001 From: Zi Yan Date: Thu, 20 Aug 2026 09:19:12 -0400 Subject: [PATCH 005/134] USB: gadgetfs: do not WARN about excessively large memory allocations GadgetFS passes an excessively large user input len to kmalloc and kmalloc gives a WARN (see below for details). Suppress it by passing __GFP_NOWARN to kmalloc used by both ep_write_iter() and ep_read_iter(). Follow the same method as commit 4f2629ea67e72 ("USB: usbfs: Don't WARN about excessively large memory allocations"). kmalloc is used to allocate physically contiguous memory for kernel allocations. For requests larger than KMALLOC_MAX_CACHE_SIZE, kmalloc uses the page allocator and can only support up to KMALLOC_MAX_SIZE. For request sizes bigger than KMALLOC_MAX_SIZE, the page allocator can emit a WARN because kmalloc allocates an order greater than MAX_PAGE_ORDER. Link: https://lore.kernel.org/DKTTMAS94IMH.2C6ERY0ZIVWVZ@nvidia.com Fixes: b3c466ce5129 ("page allocator: do not sanity check order in the fast path") Signed-off-by: Zi Yan Signed-off-by: Andrew Morton Reported-by: syzbot+805630f1453e490427fa@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a820ebc.9ebadd4d.20b15e.001b.GAE@google.com/ Tested-by: syzbot+805630f1453e490427fa@syzkaller.appspotmail.com Acked-by: Alan Stern Acked-by: Vlastimil Babka (SUSE) Cc: Greg Kroah-Hartman Cc: --- drivers/usb/gadget/legacy/inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index 67c6ffaf4f72..7e383e3e0f66 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -613,7 +613,7 @@ ep_read_iter(struct kiocb *iocb, struct iov_iter *to) return -EBADMSG; } - buf = kmalloc(len, GFP_KERNEL); + buf = kmalloc(len, GFP_KERNEL | __GFP_NOWARN); if (unlikely(!buf)) { mutex_unlock(&epdata->lock); return -ENOMEM; @@ -675,7 +675,7 @@ ep_write_iter(struct kiocb *iocb, struct iov_iter *from) return -EBADMSG; } - buf = kmalloc(len, GFP_KERNEL); + buf = kmalloc(len, GFP_KERNEL | __GFP_NOWARN); if (unlikely(!buf)) { mutex_unlock(&epdata->lock); return -ENOMEM; From 83d3d7618421b40730e268a94bab33151fa88417 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Tue, 25 Aug 2026 15:11:57 +0000 Subject: [PATCH 006/134] mailmap: update email address for Bradley Morgan I switched to brads@mainlining.org for kernel work, so map the old include@grrlz.net address over to keep shortlog and blame from splitting commits between the two. Link: https://lore.kernel.org/20260825151157.4533-1-brads@mainlining.org Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index b8d0dc185714..0494c22a3bae 100644 --- a/.mailmap +++ b/.mailmap @@ -171,6 +171,7 @@ Boris Brezillon Boris Brezillon Boris Brezillon Boris Brezillon +Bradley Morgan Brendan Higgins Brendan Jackman Brian Avery From b6bec5b63a5b812145e53aaad1e63661c7214f93 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 26 Aug 2026 12:09:03 +0200 Subject: [PATCH 007/134] init: fix early boot crash with bare hostname parameter When a bare hostname parameter is specified on the kernel command line without the '=' separator, early parameter parsing passes NULL to early_hostname(), which dereferences it in strscpy() and can crash the system during early boot. Reject NULL values in early_hostname() and return -EINVAL instead. Link: https://lore.kernel.org/20260826100904.296151-2-blum@kernel.org Fixes: 5a704629f2c1 ("init: add "hostname" kernel parameter") Signed-off-by: Thorsten Blum Signed-off-by: Andrew Morton Cc: Dan Moulding Cc: --- init/version.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/init/version.c b/init/version.c index 94c96f6fbfe6..0bd5c45aabc4 100644 --- a/init/version.c +++ b/init/version.c @@ -23,6 +23,9 @@ static int __init early_hostname(char *arg) size_t maxlen = bufsize - 1; ssize_t arglen; + if (!arg) + return -EINVAL; + arglen = strscpy(init_uts_ns.name.nodename, arg, bufsize); if (arglen < 0) { pr_warn("hostname parameter exceeds %zd characters and will be truncated", From c204bf5ac3eeb66648904d0374d6f7b84d843aac Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Wed, 26 Aug 2026 19:26:59 +0800 Subject: [PATCH 008/134] ocfs2: fix deadlock in inline-data truncate transactions Updating an inode xattr can cause an ABBA deadlock with inline file truncation: ocfs2_truncate_file() down_write(&oi->ip_alloc_sem) ocfs2_truncate_inline() ocfs2_start_trans() ocfs2_xattr_set() ocfs2_start_trans() ocfs2_xattr_ibody_set() down_write(&oi->ip_alloc_sem) The xattr set path starts the merged transaction before the inode-body xattr helper acquires ip_alloc_sem, reversing the ip_alloc_sem -> transaction order used by the allocation and truncate paths. The transaction merge in commit 85db90e77806 ("ocfs2/xattr: Merge xattr set transaction.") introduced this ordering. Fix it by acquiring ip_alloc_sem once in ocfs2_xattr_set(), before xattr preparation, allocation reservations and ocfs2_start_trans(), and removing the per-helper acquisition from ocfs2_xattr_ibody_find(), ocfs2_xattr_ibody_set() and ocfs2_xattr_create_index_block(). These helpers now assert via lockdep that the caller holds ip_alloc_sem. ocfs2_xattr_set_handle(), which only sets initial ACL or security xattrs on unpublished inodes inside the create transaction, takes ip_alloc_sem under a dedicated lockdep subclass so that the assertions hold without creating a transaction -> ip_alloc_sem cycle against the ip_alloc_sem -> transaction order. The inode is unpublished, so the acquisition can never contend. This keeps the established ip_alloc_sem -> transaction order and makes the locking unconditional, so lockdep can verify a single plain ordering instead of conditional acquisitions. Link: https://lore.kernel.org/20260826112659.246574-1-joseph.qi@linux.alibaba.com Fixes: 85db90e77806 ("ocfs2/xattr: Merge xattr set transaction.") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: ZhengYuan Huang Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao --- fs/ocfs2/xattr.c | 79 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index bfafe059bedf..3f83a75d29a6 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -2909,6 +2909,9 @@ static int ocfs2_xattr_has_space_inline(struct inode *inode, * * Find extended attribute in inode block and * fill search info into struct ocfs2_xattr_search. + * + * The inline free-space check races with truncate and allocation, so + * callers must hold ip_alloc_sem for writing. */ static int ocfs2_xattr_ibody_find(struct inode *inode, int name_index, @@ -2920,13 +2923,13 @@ static int ocfs2_xattr_ibody_find(struct inode *inode, int ret; int has_space = 0; + lockdep_assert_held_write(&oi->ip_alloc_sem); + if (inode->i_sb->s_blocksize == OCFS2_MIN_BLOCKSIZE) return 0; if (!(oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL)) { - down_read(&oi->ip_alloc_sem); has_space = ocfs2_xattr_has_space_inline(inode, di); - up_read(&oi->ip_alloc_sem); if (!has_space) return 0; } @@ -3007,6 +3010,7 @@ static int ocfs2_xattr_ibody_init(struct inode *inode, * * Set, replace or remove an extended attribute into inode block. * + * Callers must hold ip_alloc_sem for writing. */ static int ocfs2_xattr_ibody_set(struct inode *inode, struct ocfs2_xattr_info *xi, @@ -3017,16 +3021,17 @@ static int ocfs2_xattr_ibody_set(struct inode *inode, struct ocfs2_inode_info *oi = OCFS2_I(inode); struct ocfs2_xa_loc loc; + lockdep_assert_held_write(&oi->ip_alloc_sem); + if (inode->i_sb->s_blocksize == OCFS2_MIN_BLOCKSIZE) return -ENOSPC; - down_write(&oi->ip_alloc_sem); if (!(oi->ip_dyn_features & OCFS2_INLINE_XATTR_FL)) { ret = ocfs2_xattr_ibody_init(inode, xs->inode_bh, ctxt); if (ret) { if (ret != -ENOSPC) mlog_errno(ret); - goto out; + return ret; } } @@ -3036,13 +3041,10 @@ static int ocfs2_xattr_ibody_set(struct inode *inode, if (ret) { if (ret != -ENOSPC) mlog_errno(ret); - goto out; + return ret; } xs->here = loc.xl_entry; -out: - up_write(&oi->ip_alloc_sem); - return ret; } @@ -3686,6 +3688,18 @@ static int __ocfs2_xattr_set_handle(struct inode *inode, return ret; } +/* + * ip_alloc_sem subclass for inodes being initialized before publication. + * ocfs2_xattr_set_handle() runs inside the create transaction, so taking + * ip_alloc_sem there adds a transaction -> ip_alloc_sem order that would + * form a lockdep cycle with the ip_alloc_sem -> transaction order used + * elsewhere, if not for this separate subclass. The inode is unpublished + * so the acquisition can never contend. + */ +enum { + OCFS2_IP_ALLOC_SEM_UNPUBLISHED = 1, +}; + /* * This helper is only for setting initial ACL or security xattrs on an inode * that is still unpublished, unhashed, and unattached to a dentry. @@ -3747,6 +3761,13 @@ int ocfs2_xattr_set_handle(handle_t *handle, xis.inode_bh = xbs.inode_bh = di_bh; di = (struct ocfs2_dinode *)di_bh->b_data; + /* + * The inode is unpublished and cannot contend, but take the + * semaphore anyway so the helpers' lockdep assertions hold. + */ + down_write_nested(&OCFS2_I(inode)->ip_alloc_sem, + OCFS2_IP_ALLOC_SEM_UNPUBLISHED); + ret = ocfs2_xattr_ibody_find(inode, name_index, name, &xis); if (ret) goto cleanup; @@ -3759,6 +3780,7 @@ int ocfs2_xattr_set_handle(handle_t *handle, ret = __ocfs2_xattr_set_handle(inode, di, &xi, &xis, &xbs, &ctxt); cleanup: + up_write(&OCFS2_I(inode)->ip_alloc_sem); brelse(xbs.xattr_bh); ocfs2_xattr_bucket_free(xbs.bucket); @@ -3827,30 +3849,38 @@ int ocfs2_xattr_set(struct inode *inode, di = (struct ocfs2_dinode *)di_bh->b_data; down_write(&OCFS2_I(inode)->ip_xattr_sem); + /* + * The allocation and truncate paths take ip_alloc_sem before + * starting a transaction, so take it here before xattr + * preparation, allocation reservations and ocfs2_start_trans() + * to keep that order. The xattr helpers below no longer take + * it themselves. + */ + down_write(&OCFS2_I(inode)->ip_alloc_sem); /* * Scan inode and external block to find the same name * extended attribute and collect search information. */ ret = ocfs2_xattr_ibody_find(inode, name_index, name, &xis); if (ret) - goto cleanup; + goto out_free_ac; if (xis.not_found) { ret = ocfs2_xattr_block_find(inode, name_index, name, &xbs); if (ret) - goto cleanup; + goto out_free_ac; } if (xis.not_found && xbs.not_found) { ret = -ENODATA; if (flags & XATTR_REPLACE) - goto cleanup; + goto out_free_ac; ret = 0; if (!value) - goto cleanup; + goto out_free_ac; } else { ret = -EEXIST; if (flags & XATTR_CREATE) - goto cleanup; + goto out_free_ac; } /* Check whether the value is refcounted and do some preparation. */ @@ -3861,7 +3891,7 @@ int ocfs2_xattr_set(struct inode *inode, &ref_meta, &ref_credits); if (ret) { mlog_errno(ret); - goto cleanup; + goto out_free_ac; } } @@ -3872,7 +3902,7 @@ int ocfs2_xattr_set(struct inode *inode, if (ret < 0) { inode_unlock(tl_inode); mlog_errno(ret); - goto cleanup; + goto out_free_ac; } } inode_unlock(tl_inode); @@ -3881,7 +3911,7 @@ int ocfs2_xattr_set(struct inode *inode, &xbs, &ctxt, ref_meta, &credits); if (ret) { mlog_errno(ret); - goto cleanup; + goto out_free_ac; } /* we need to update inode's ctime field, so add credit for it. */ @@ -3899,6 +3929,7 @@ int ocfs2_xattr_set(struct inode *inode, ocfs2_commit_trans(osb, ctxt.handle); out_free_ac: + up_write(&OCFS2_I(inode)->ip_alloc_sem); if (ctxt.data_ac) ocfs2_free_alloc_context(ctxt.data_ac); if (ctxt.meta_ac) @@ -3907,7 +3938,6 @@ int ocfs2_xattr_set(struct inode *inode, ocfs2_schedule_truncate_log_flush(osb, 1); ocfs2_run_deallocs(osb, &ctxt.dealloc); -cleanup: if (ref_tree) ocfs2_unlock_refcount_tree(osb, ref_tree, 1); up_write(&OCFS2_I(inode)->ip_xattr_sem); @@ -4508,6 +4538,10 @@ static void ocfs2_xattr_update_xattr_search(struct inode *inode, xs->here = &xs->header->xh_entries[i]; } +/* + * Caller must hold ip_alloc_sem for writing, since a new xattr block + * is allocated and the xattr block header is rewritten. + */ static int ocfs2_xattr_create_index_block(struct inode *inode, struct ocfs2_xattr_search *xs, struct ocfs2_xattr_set_ctxt *ctxt) @@ -4523,19 +4557,14 @@ static int ocfs2_xattr_create_index_block(struct inode *inode, struct ocfs2_xattr_tree_root *xr; u16 xb_flags = le16_to_cpu(xb->xb_flags); + lockdep_assert_held_write(&oi->ip_alloc_sem); + trace_ocfs2_xattr_create_index_block_begin( (unsigned long long)xb_bh->b_blocknr); BUG_ON(xb_flags & OCFS2_XATTR_INDEXED); BUG_ON(!xs->bucket); - /* - * XXX: - * We can use this lock for now, and maybe move to a dedicated mutex - * if performance becomes a problem later. - */ - down_write(&oi->ip_alloc_sem); - ret = ocfs2_journal_access_xb(handle, INODE_CACHE(inode), xb_bh, OCFS2_JOURNAL_ACCESS_WRITE); if (ret) { @@ -4597,8 +4626,6 @@ static int ocfs2_xattr_create_index_block(struct inode *inode, ocfs2_journal_dirty(handle, xb_bh); out: - up_write(&oi->ip_alloc_sem); - return ret; } From 415ad9147484a9994ad7963c46aaf56e14dd3b49 Mon Sep 17 00:00:00 2001 From: ZhengYuan Huang Date: Thu, 6 Aug 2026 16:50:12 +0800 Subject: [PATCH 009/134] ocfs2: reject inconsistent local xattr entries [BUG] A corrupt OCFS2 xattr entry can set OCFS2_XATTR_ENTRY_LOCAL while keeping xe_value_size larger than OCFS2_XATTR_INLINE_SIZE. When that entry reaches namevalue_size_xe(), the filesystem hits its BUG_ON: kernel BUG at fs/ocfs2/xattr.c:231! Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI RIP: 0010:namevalue_size_xe fs/ocfs2/xattr.c:231 [inline] RIP: 0010:ocfs2_xa_block_wipe_namevalue+0x2e4/0x330 fs/ocfs2/xattr.c:1638 Call Trace: ocfs2_xa_wipe_namevalue fs/ocfs2/xattr.c:1470 [inline] ocfs2_xa_remove_entry+0xae/0x1d0 fs/ocfs2/xattr.c:1941 ocfs2_xa_remove fs/ocfs2/xattr.c:2043 [inline] ocfs2_xa_set+0x11a8/0x30a0 fs/ocfs2/xattr.c:2247 ocfs2_xattr_ibody_set+0x302/0xc50 fs/ocfs2/xattr.c:2795 __ocfs2_xattr_set_handle+0x7e6/0xdb0 fs/ocfs2/xattr.c:3416 ocfs2_xattr_set+0x1447/0x2610 fs/ocfs2/xattr.c:3650 ocfs2_xattr_security_set+0x37/0x50 fs/ocfs2/xattr.c:7241 __vfs_removexattr+0x14d/0x1d0 fs/xattr.c:518 cap_inode_killpriv+0x29/0x50 security/commoncap.c:355 security_inode_killpriv+0x105/0x220 security/security.c:2724 setattr_prepare+0x147/0x8a0 fs/attr.c:219 ocfs2_setattr+0x504/0x1fd0 fs/ocfs2/file.c:1148 notify_change+0x4b5/0x1030 fs/attr.c:546 do_truncate+0x1d2/0x230 fs/open.c:68 handle_truncate fs/namei.c:3596 [inline] do_open fs/namei.c:3979 [inline] path_openat+0x260f/0x2ce0 fs/namei.c:4134 do_filp_open+0x1f6/0x430 fs/namei.c:4161 do_sys_openat2+0x117/0x1c0 fs/open.c:1437 ... [CAUSE] namevalue_size_xe() assumes that local entries contain an inline value no larger than OCFS2_XATTR_INLINE_SIZE. Existing xattr metadata validation only checks whether the value fits the storage region, and cached entries can reach lookup and bucket maintenance paths without a semantic check. A corrupt entry can therefore be passed to namevalue_size_xe(). [FIX] Validate the local/value-size invariant in the existing flat and bucket metadata validators and before accepting matched entries or traversing bucket entries in paths that call namevalue_size_xe(). Return an OCFS2 corruption error instead of firing the assertion. Link: https://lore.kernel.org/20260806085012.2650042-1-gality369@gmail.com Signed-off-by: ZhengYuan Huang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao --- fs/ocfs2/xattr.c | 53 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 3f83a75d29a6..740d4bb3890f 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -237,6 +237,21 @@ static int namevalue_size_xe(struct ocfs2_xattr_entry *xe) return namevalue_size(xe->xe_name_len, value_len); } +static int ocfs2_validate_xattr_entry(struct super_block *sb, u64 blkno, + struct ocfs2_xattr_entry *xe) +{ + u64 value_len = le64_to_cpu(xe->xe_value_size); + + if (value_len > OCFS2_XATTR_INLINE_SIZE && + ocfs2_xattr_is_local(xe)) + return ocfs2_error(sb, + "Invalid local xattr in block %llu: value size %llu\n", + (unsigned long long)blkno, + (unsigned long long)value_len); + + return 0; +} + static int ocfs2_xattr_bucket_get_name_value(struct super_block *sb, struct ocfs2_xattr_header *xh, @@ -989,7 +1004,7 @@ static int ocfs2_validate_xattr_entries_flat(struct super_block *sb, u64 blkno, size_t entries_limit = region_size; size_t nv_limit = region_size; size_t max_entries; - int i; + int i, ret; if (region_size < sizeof(*xh)) return ocfs2_error(sb, @@ -1009,6 +1024,11 @@ static int ocfs2_validate_xattr_entries_flat(struct super_block *sb, u64 blkno, struct ocfs2_xattr_entry *xe = &xh->xh_entries[i]; size_t name_offset = le16_to_cpu(xe->xe_name_offset); size_t value_offset; + u64 value_len = le64_to_cpu(xe->xe_value_size); + + ret = ocfs2_validate_xattr_entry(sb, blkno, xe); + if (ret) + return ret; if (name_offset > nv_limit || xe->xe_name_len > nv_limit - name_offset) @@ -1023,8 +1043,7 @@ static int ocfs2_validate_xattr_entries_flat(struct super_block *sb, u64 blkno, (unsigned long long)blkno, i); if (ocfs2_xattr_is_local(xe)) { - if (le64_to_cpu(xe->xe_value_size) > - nv_limit - value_offset) + if (value_len > nv_limit - value_offset) return ocfs2_error(sb, "Invalid xattr in block %llu: entry %d value is out of bounds\n", (unsigned long long)blkno, @@ -1109,7 +1128,7 @@ static int ocfs2_validate_xattr_bucket(struct ocfs2_xattr_bucket *bucket, size_t entries_limit = sb->s_blocksize; size_t nv_limit = sb->s_blocksize; size_t max_entries; - int i; + int i, ret; if (region_size < sizeof(*xh)) return ocfs2_error(sb, @@ -1137,6 +1156,11 @@ static int ocfs2_validate_xattr_bucket(struct ocfs2_xattr_bucket *bucket, size_t block_off = name_offset >> sb->s_blocksize_bits; size_t block_offset = name_offset % nv_limit; size_t value_offset; + u64 value_len = le64_to_cpu(xe->xe_value_size); + + ret = ocfs2_validate_xattr_entry(sb, blkno, xe); + if (ret) + return ret; if (name_offset >= region_size || block_off >= bucket->bu_blocks) return ocfs2_error(sb, @@ -1155,8 +1179,7 @@ static int ocfs2_validate_xattr_bucket(struct ocfs2_xattr_bucket *bucket, (unsigned long long)blkno, i); if (ocfs2_xattr_is_local(xe)) { - if (le64_to_cpu(xe->xe_value_size) > - nv_limit - value_offset) + if (value_len > nv_limit - value_offset) return ocfs2_error(sb, "Invalid xattr bucket %llu: entry %d value is out of bounds\n", (unsigned long long)blkno, @@ -1304,7 +1327,7 @@ static int ocfs2_xattr_find_entry(struct inode *inode, int name_index, { struct ocfs2_xattr_entry *entry; size_t name_len; - int i, name_offset, cmp = 1; + int i, name_offset, cmp = 1, ret; if (name == NULL) return -EINVAL; @@ -1327,6 +1350,12 @@ static int ocfs2_xattr_find_entry(struct inode *inode, int name_index, return -EFSCORRUPTED; } cmp = memcmp(name, (xs->base + name_offset), name_len); + if (!cmp) { + ret = ocfs2_validate_xattr_entry(inode->i_sb, + OCFS2_I(inode)->ip_blkno, entry); + if (ret) + return ret; + } } if (cmp == 0) break; @@ -4068,6 +4097,10 @@ static int ocfs2_find_xe_in_bucket(struct inode *inode, xe_name = bucket_block(bucket, block_off) + new_offset; if (!memcmp(name, xe_name, name_len)) { + ret = ocfs2_validate_xattr_entry(inode->i_sb, + OCFS2_I(inode)->ip_blkno, xe); + if (ret) + break; *xe_index = i; *found = 1; ret = 0; @@ -4705,6 +4738,9 @@ static int ocfs2_defrag_xattr_bucket(struct inode *inode, xe = xh->xh_entries; end = OCFS2_XATTR_BUCKET_SIZE; for (i = 0; i < le16_to_cpu(xh->xh_count); i++, xe++) { + ret = ocfs2_validate_xattr_entry(inode->i_sb, blkno, xe); + if (ret) + goto out; offset = le16_to_cpu(xe->xe_name_offset); len = namevalue_size_xe(xe); @@ -4987,6 +5023,9 @@ static int ocfs2_divide_xattr_bucket(struct inode *inode, name_value_len = 0; for (i = 0; i < start; i++) { xe = &xh->xh_entries[i]; + ret = ocfs2_validate_xattr_entry(inode->i_sb, blk, xe); + if (ret) + goto out; name_value_len += namevalue_size_xe(xe); if (le16_to_cpu(xe->xe_name_offset) < name_offset) name_offset = le16_to_cpu(xe->xe_name_offset); From cba342dde142494a2e4a8a875bb027a38cf95dd3 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 24 Aug 2026 17:14:01 +0200 Subject: [PATCH 010/134] raid/kunit: enable RAID6 PQ and XOR benchmarks if KUNIT_ALL_TESTS=m Enabling the (possibly long-running benchmarks) by default may cause a big delay in boot time in case of built-in tests. However, they can still safely be enabled by default if all tests are modular, as they would only run when requested explicitly by the system administrator. Link: https://lore.kernel.org/64c6e0191bd8ccef0074ffbb09bd0584680d710b.1787584360.git.geert@linux-m68k.org Signed-off-by: Geert Uytterhoeven Signed-off-by: Andrew Morton Reviewed-by: Christoph Hellwig Acked-by: Ard Biesheuvel Cc: Eric Biggers --- lib/raid/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/raid/Kconfig b/lib/raid/Kconfig index 01f007b2522c..563a178aa930 100644 --- a/lib/raid/Kconfig +++ b/lib/raid/Kconfig @@ -32,6 +32,7 @@ config XOR_KUNIT_TEST config XOR_BENCHMARK bool "Benchmark for xor_gen" depends on XOR_KUNIT_TEST + default y if KUNIT_ALL_TESTS=m help Include benchmarks in the KUnit test suite for xor_gen. @@ -63,6 +64,7 @@ config RAID6_PQ_KUNIT_TEST config RAID6_PQ_KUNIT_BENCHMARK bool "Benchmark for RAID6 PQ" depends on RAID6_PQ_KUNIT_TEST + default y if KUNIT_ALL_TESTS=m help Include benchmarks in the KUnit test suite for raid P/Q generation. From 34dbdb7e2384308714c870094629e7cbeb725b7c Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Mon, 24 Aug 2026 13:23:59 +0900 Subject: [PATCH 011/134] ipc/mqueue: release notification resources during inode eviction mqueue_flush_file() removes an mq_notify() registration only when the closing task belongs to the thread group stored in notify_owner. A task in a separate thread group created with CLONE_FILES can register SIGEV_THREAD notification and exit without closing the shared file table. If another thread group then unlinks and last-closes the queue, ->flush() skips the registration and inode eviction loses the only pointers to its resources. The orphaned registration permanently retains the notification skb, its netlink socket, a pid reference and a user namespace reference. An unprivileged process can repeat the sequence with new queues and sockets. No inode users remain during eviction. Remove any stale registration there after dropping info->lock, since netlink_sendskb() may release the final socket reference. This bug creates an unkillable kernel resource leak by failing to free netlink socket, PID, and user namespace references when a POSIX message queue is evicted. An unprivileged process can exploit this leak repeatedly to cause kernel memory exhaustion and lead to a DoS. Link: https://lore.kernel.org/20260824042359.925145-1-4ncienth@gmail.com Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Signed-off-by: Andrew Morton Assisted-by: LLM Cc: Davidlohr Bueso Cc: Al Viro Cc: Christian Brauner Cc: Jan Kara Cc: Manfred Spraul Cc: NeilBrown Cc: --- ipc/mqueue.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ipc/mqueue.c b/ipc/mqueue.c index d1dd36a651b0..d1a1965c9811 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -528,6 +528,13 @@ static void mqueue_evict_inode(struct inode *inode) list_add_tail(&msg->m_list, &tmp_msg); kfree(info->node_cache); spin_unlock(&info->lock); + /* + * A shared file table can let the notification owner exit without + * running ->flush(). No users of the inode remain during eviction, so + * tear down any stale notification after dropping info->lock because + * netlink_sendskb() may release the final socket reference. + */ + remove_notification(info); list_for_each_entry_safe(msg, nmsg, &tmp_msg, m_list) { list_del(&msg->m_list); From 732e3e10234cbe302d0dcbaa98c91c66628209cb Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 27 Aug 2026 06:17:08 +0200 Subject: [PATCH 012/134] klist: avoid accesses after waking klist_remove() klist_remove() waits until a node is unreferenced so that its caller can free the containing object. klist_release() currently publishes waiter->woken and wakes the waiter before its final accesses to the waiter and node. klist_remove() can then return, allowing its stack waiter and the containing object to be freed or reused while klist_release() is still running. In particular, bus_remove_driver() can free drv->p while __device_attach() walks the same bus klist with bus_for_each_drv(). On an arm64 Cortex-A72 system, an unpatched 7.2.0-rc3 kernel with CONFIG_PREEMPT_RT=y and CONFIG_KASAN=y reproduced the bug through the in-tree I2C/at24 path. KASAN reported a use-after-free in klist_dec_and_del() reached from klist_next()/bus_for_each_drv() while at24 was being unregistered. Clear n_klist and take a task reference before publishing woken. Use release/acquire accesses for that publication and wake the referenced task. The task reference keeps the waiter task alive if it returns and exits before wake_up_process(). Link: https://lore.kernel.org/20260827041708.31682-1-kmehltretter@gmail.com Fixes: 8b0c250be489 ("[PATCH] add klist_node_attached() to determine if a node is on a list or not.") Fixes: 210272a28465 ("driver core: Remove completion from struct klist_node") Signed-off-by: Karl Mehltretter Signed-off-by: Andrew Morton Assisted-by: LLM Cc: Danilo Krummrich Cc: Greg Kroah-Hartman Cc: Matthew Wilcox (Oracle) Cc: "Rafael J. Wysocki" Cc: --- lib/klist.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/klist.c b/lib/klist.c index 332a4fbf18ff..f133740b1c2c 100644 --- a/lib/klist.c +++ b/lib/klist.c @@ -36,6 +36,7 @@ #include #include #include +#include /* * Use the lowest bit of n_klist to mark deleted nodes and exclude @@ -187,18 +188,24 @@ static void klist_release(struct kref *kref) WARN_ON(!knode_dead(n)); list_del(&n->n_node); + knode_set_klist(n, NULL); spin_lock(&klist_remove_lock); list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) { + struct task_struct *p; + if (waiter->node != n) continue; + p = waiter->process; + get_task_struct(p); list_del(&waiter->list); - waiter->woken = 1; + /* Publish only after the final waiter and n accesses */ + smp_store_release(&waiter->woken, 1); mb(); - wake_up_process(waiter->process); + wake_up_process(p); + put_task_struct(p); } spin_unlock(&klist_remove_lock); - knode_set_klist(n, NULL); } static int klist_dec_and_del(struct klist_node *n) @@ -250,7 +257,8 @@ void klist_remove(struct klist_node *n) for (;;) { set_current_state(TASK_UNINTERRUPTIBLE); - if (waiter.woken) + /* Pairs with the release store in klist_release() */ + if (smp_load_acquire(&waiter.woken)) break; schedule(); } From 7857840b125a2bf2e9374ed9d3eadb307e72d4ae Mon Sep 17 00:00:00 2001 From: Chris Gellermann Date: Mon, 3 Aug 2026 14:48:59 +0200 Subject: [PATCH 013/134] selftests/membarrier: introduce helper to get membarrier command registrations Patch series "selftests/membarrier: Skip an unregistered memory barrier test on Musl". The membarrier test "membarrier MEMBARRIER_CMD_PRIVATE_EXPEDITED not registered failure" fails in the multithreaded test scenario when using Musl libc as the command gets preregistered implicitly during thread creation. Skip the test if command registration is detected. This patch (of 2): Add a new membarrier_get_registrations() for reusage. Link: https://lore.kernel.org/20260803124900.3328789-1-christian.gellermann@codasip.com Link: https://lore.kernel.org/20260803124900.3328789-2-christian.gellermann@codasip.com Signed-off-by: Chris Gellermann Signed-off-by: Andrew Morton Tested-by: Michael Jeanson Cc: Ben Segall Cc: Dietmar Eggemann Cc: Ingo Molnar Cc: Juri Lelli Cc: K Prateek Nayak Cc: Mathieu Desnoyers Cc: Mel Gorman Cc: "Paul E . McKenney" Cc: Peter Zijlstra Cc: Shuah Khan Cc: Steven Rostedt Cc: Valentin Schneider Cc: Vincent Guittot Cc: Wei Yang --- tools/testing/selftests/membarrier/membarrier_test_impl.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/membarrier/membarrier_test_impl.h b/tools/testing/selftests/membarrier/membarrier_test_impl.h index f6d7c44b2288..29aac3bc4988 100644 --- a/tools/testing/selftests/membarrier/membarrier_test_impl.h +++ b/tools/testing/selftests/membarrier/membarrier_test_impl.h @@ -16,6 +16,11 @@ static int sys_membarrier(int cmd, int flags) return syscall(__NR_membarrier, cmd, flags); } +static int membarrier_get_registrations(void) +{ + return sys_membarrier(MEMBARRIER_CMD_GET_REGISTRATIONS, 0); +} + static int test_membarrier_get_registrations(int cmd) { int ret, flags = 0; @@ -24,7 +29,7 @@ static int test_membarrier_get_registrations(int cmd) registrations |= cmd; - ret = sys_membarrier(MEMBARRIER_CMD_GET_REGISTRATIONS, 0); + ret = membarrier_get_registrations(); if (ret < 0) { ksft_exit_fail_msg( "%s test: flags = %d, errno = %d\n", From 276abb3ee627e01fef3eb1782dbb20765019f0e4 Mon Sep 17 00:00:00 2001 From: Chris Gellermann Date: Mon, 3 Aug 2026 14:49:00 +0200 Subject: [PATCH 014/134] selftests/membarrier: skip unpermitted membarrier command test if preregistered by libc On thread creation, Musl registers the private expedited memory barrier, see pthread_create [1]. Thus, invoking the barrier command will no longer be rejected by the kernel with EPERM. The test checking this will fail. Check if the memory barrier command has been registered and skip the test in this case. Link: https://git.musl-libc.org/cgit/musl/tree/src/thread/pthread_create.c#n260 [1] Link: https://lore.kernel.org/20260803124900.3328789-3-christian.gellermann@codasip.com Signed-off-by: Chris Gellermann Signed-off-by: Andrew Morton Tested-by: Michael Jeanson Cc: Ben Segall Cc: Dietmar Eggemann Cc: Ingo Molnar Cc: Juri Lelli Cc: K Prateek Nayak Cc: Mathieu Desnoyers Cc: Mel Gorman Cc: "Paul E . McKenney" Cc: Peter Zijlstra Cc: Shuah Khan Cc: Steven Rostedt Cc: Valentin Schneider Cc: Vincent Guittot Cc: Wei Yang --- .../selftests/membarrier/membarrier_test_impl.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/testing/selftests/membarrier/membarrier_test_impl.h b/tools/testing/selftests/membarrier/membarrier_test_impl.h index 29aac3bc4988..b4dcbb32538d 100644 --- a/tools/testing/selftests/membarrier/membarrier_test_impl.h +++ b/tools/testing/selftests/membarrier/membarrier_test_impl.h @@ -113,6 +113,16 @@ static int test_membarrier_private_expedited_fail(void) int cmd = MEMBARRIER_CMD_PRIVATE_EXPEDITED, flags = 0; const char *test_name = "sys membarrier MEMBARRIER_CMD_PRIVATE_EXPEDITED not registered failure"; + /* + * Some C libraries, like Musl, register the private expedited barrier + * command when creating a thread. Expecting an EPERM on an unregistered + * command will therefore no longer work. Skip the test in this case. + */ + if (MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED & membarrier_get_registrations()) { + ksft_test_result_skip("%s test: Command already registered\n", test_name); + return 0; + } + if (sys_membarrier(cmd, flags) != -1) { ksft_exit_fail_msg( "%s test: flags = %d. Should fail, but passed\n", From 4238e66c82b6ee888a768cb298c85083b4566726 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 28 Aug 2026 17:54:07 +0200 Subject: [PATCH 015/134] selftests/epoll: fix race condition in multi-waiter wakeup tests In tests with multiple concurrent waiters on edge-triggered epoll instances where an emitter writes to multiple sockets (epoll16, epoll56, epoll58): When the emitter performs its first write(), ep_poll_callback() fires and wakes up both waiters because one waiter uses epoll_wait() and the other one uses poll(). This translates to different wait queues, ep->wq for epoll and ep->poll_wait for poll/select, which are both awoken by the kernel because of that single write. Next, both waiter threads invoke epoll_wait(), but since there is only one event, only one epoll_wait() will return non-zero because of the edge-triggered mode being used (in level-triggered mode, the kernel would re-queue the event because of remaining unread data). Since the second waiter sees an empty ready list, it does not increment ctx.count and the test fails spuriously with ctx.count == 1 instead of 2. Emitter (CPU 0) Thread 0 (CPU 1) Thread 1 (CPU 2) =============== ================ ================ epoll_wait(e0, -1) poll(e0, -1) [on e0->wq] [on e0->poll_wait] write(sfd[1]) | +--(Kernel wakes BOTH e0->wq and e0->poll_wait via callback)--+ | | | wakes up wakes up | | epoll_wait() reaps e1 poll() returns 1 | | (e1 removed via ET) (wants event) | | e0->rdllist is EMPTY | | | count++ (count = 1) v | | epoll_wait(e0, 0) | | sees EMPTY list! | | returns 0! | | thread exits | v | write(sfd[3]) | (event arrives too late!) v EXPECT_EQ(count, 2) <-- SPURIOUS FAILURE! Introduce waiter_entry1ap_loop() to retry poll() if the initial epoll_wait(..., 0) yielded no events. This ensures the thread waits for the subsequent write rather than failing immediately. Apply this helper in epoll16, epoll56, and for both waiter threads in epoll58. Link: https://lore.kernel.org/20260828-selftest-epoll-fix-race-v2-1-953ab57fd60a@codasip.com Fixes: f2728fe80cef ("selftests: add epoll selftests") Signed-off-by: Florian Schmaus Signed-off-by: Andrew Morton Cc: Heiher Cc: Roman Penyaev Cc: Shuah Khan Cc: Christian Brauner --- .../filesystems/epoll/epoll_wakeup_test.c | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c b/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c index 81a994943e12..b4dcbcd79773 100644 --- a/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c +++ b/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c @@ -74,6 +74,24 @@ static void *waiter_entry1ap(void *data) return NULL; } +static void *waiter_entry1ap_loop(void *data) +{ + struct pollfd pfd; + struct epoll_event e; + struct epoll_mtcontext *ctx = data; + + pfd.fd = ctx->efd[0]; + pfd.events = POLLIN; + while (poll(&pfd, 1, 2000) > 0) { + if (epoll_wait(ctx->efd[0], &e, 1, 0) > 0) { + __sync_fetch_and_add(&ctx->count, 1); + break; + } + } + + return NULL; +} + static void *waiter_entry1o(void *data) { struct epoll_event e; @@ -809,7 +827,7 @@ TEST(epoll16) ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[2], events), 0); ctx.main = pthread_self(); - ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0); + ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap_loop, &ctx), 0); ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0); if (epoll_wait(ctx.efd[0], events, 1, -1) > 0) @@ -2925,7 +2943,7 @@ TEST(epoll56) ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[2], &e), 0); ctx.main = pthread_self(); - ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0); + ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap_loop, &ctx), 0); ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0); if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0) @@ -3030,7 +3048,6 @@ TEST(epoll57) TEST(epoll58) { pthread_t emitter; - struct pollfd pfd; struct epoll_event e; struct epoll_mtcontext ctx = { 0 }; @@ -3061,15 +3078,10 @@ TEST(epoll58) ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[2], &e), 0); ctx.main = pthread_self(); - ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0); + ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap_loop, &ctx), 0); ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0); - pfd.fd = ctx.efd[0]; - pfd.events = POLLIN; - if (poll(&pfd, 1, -1) > 0) { - if (epoll_wait(ctx.efd[0], &e, 1, 0) > 0) - __sync_fetch_and_add(&ctx.count, 1); - } + waiter_entry1ap_loop(&ctx); ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0); EXPECT_EQ(ctx.count, 2); From 6163a859649e9432e9c79c541f36dbf3bba9172f Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 28 Aug 2026 19:28:23 +0800 Subject: [PATCH 016/134] ocfs2: exit recovery thread on mount error path When a mount fails after the cluster connection has been established, e.g. in ocfs2_mount_volume(), ocfs2_fill_super() unwinds via out_debugfs/out_super and frees the osb without disabling recovery. A node failure event can concurrently launch the recovery thread, which blocks in __ocfs2_wait_on_mount() waiting for the volume state to become VOLUME_MOUNTED or VOLUME_DISABLED. As the mount error path neither sets VOLUME_DISABLED nor wakes osb_mount_event, the thread can never make progress: the kthread leaks and stays blocked on the wait queue embedded in the freed osb, which may then be accessed as freed memory. Fix it by setting VOLUME_DISABLED and waking osb_mount_event on this path so the thread bails out, and replace the plain kfree(osb->recovery_map) with ocfs2_recovery_exit(), which waits for a running recovery thread to exit before the recovery map is freed. Link: https://lore.kernel.org/20260828112825.666097-1-joseph.qi@linux.alibaba.com Fixes: f1e75d128b46 ("ocfs2: rewrite error handling of ocfs2_fill_super") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Cc: Changwei Ge Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh Cc: --- fs/ocfs2/super.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/super.c b/fs/ocfs2/super.c index c62e389d4dd6..f785c39d1fb8 100644 --- a/fs/ocfs2/super.c +++ b/fs/ocfs2/super.c @@ -1169,8 +1169,17 @@ static int ocfs2_fill_super(struct super_block *sb, struct fs_context *fc) out_debugfs: debugfs_remove_recursive(osb->osb_debug_root); out_super: + /* + * A recovery thread launched by a node failure event may still be + * waiting for the volume to be mounted. Set VOLUME_DISABLED and + * wake it up, then wait for it to exit before osb is freed, + * otherwise the kthread would leak and stay blocked on the wait + * queue embedded in the freed osb. + */ + atomic_set(&osb->vol_state, VOLUME_DISABLED); + wake_up(&osb->osb_mount_event); ocfs2_release_system_inodes(osb); - kfree(osb->recovery_map); + ocfs2_recovery_exit(osb); ocfs2_delete_osb(osb); kfree(osb); out: From 1ddadc6d41c1a29fd8fad1891bdcd2956e08b7f0 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 28 Aug 2026 19:28:24 +0800 Subject: [PATCH 017/134] ocfs2: free replay slots in ocfs2_recovery_exit() Commit ce2fcf1516d6 ("ocfs2: fix memory leak in ocfs2_mount_volume()") added ocfs2_free_replay_slots() calls to the mount error paths out_dismount and out_check_volume to fix a leak of osb->replay_map. However these calls are unlocked while the bail path of the recovery thread, which is woken up by out_dismount right before the call, also frees the replay slots under osb->recovery_lock. Both sides can thus observe a non-NULL osb->replay_map and trigger a double free. Fix this by moving ocfs2_free_replay_slots() into ocfs2_recovery_exit() after ocfs2_recovery_disable(), which waits for a running recovery thread to exit under osb->recovery_lock, and drop the unlocked call sites. Both ocfs2_dismount_volume() and the out_super path of ocfs2_fill_super() call ocfs2_recovery_exit(), so the replay slots are freed on every path. Since super.c no longer references it, make ocfs2_free_replay_slots() static again. Link: https://lore.kernel.org/20260828112825.666097-2-joseph.qi@linux.alibaba.com Fixes: ce2fcf1516d6 ("ocfs2: fix memory leak in ocfs2_mount_volume()") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Cc: Changwei Ge Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh Cc: --- fs/ocfs2/journal.c | 3 ++- fs/ocfs2/journal.h | 1 - fs/ocfs2/super.c | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/fs/ocfs2/journal.c b/fs/ocfs2/journal.c index d8afbc1a76bb..a3938a03e93b 100644 --- a/fs/ocfs2/journal.c +++ b/fs/ocfs2/journal.c @@ -156,7 +156,7 @@ static void ocfs2_queue_replay_slots(struct ocfs2_super *osb, replay_map->rm_state = REPLAY_DONE; } -void ocfs2_free_replay_slots(struct ocfs2_super *osb) +static void ocfs2_free_replay_slots(struct ocfs2_super *osb) { struct ocfs2_replay_map *replay_map = osb->replay_map; @@ -243,6 +243,7 @@ void ocfs2_recovery_exit(struct ocfs2_super *osb) /* XXX: Should we bug if there are dirty entries? */ kfree(rm); + ocfs2_free_replay_slots(osb); } static int __ocfs2_recovery_map_test(struct ocfs2_super *osb, diff --git a/fs/ocfs2/journal.h b/fs/ocfs2/journal.h index f8b3b2a3d630..19fc920d26b1 100644 --- a/fs/ocfs2/journal.h +++ b/fs/ocfs2/journal.h @@ -151,7 +151,6 @@ void ocfs2_recovery_exit(struct ocfs2_super *osb); void ocfs2_recovery_disable_quota(struct ocfs2_super *osb); int ocfs2_compute_replay_slots(struct ocfs2_super *osb); -void ocfs2_free_replay_slots(struct ocfs2_super *osb); /* * Journal Control: * Initialize, Load, Shutdown, Wipe a journal. diff --git a/fs/ocfs2/super.c b/fs/ocfs2/super.c index f785c39d1fb8..6a8092b65bb5 100644 --- a/fs/ocfs2/super.c +++ b/fs/ocfs2/super.c @@ -1162,7 +1162,6 @@ static int ocfs2_fill_super(struct super_block *sb, struct fs_context *fc) out_dismount: atomic_set(&osb->vol_state, VOLUME_DISABLED); wake_up(&osb->osb_mount_event); - ocfs2_free_replay_slots(osb); ocfs2_dismount_volume(sb, 1); goto out; @@ -1776,14 +1775,12 @@ static int ocfs2_mount_volume(struct super_block *sb) status = ocfs2_truncate_log_init(osb); if (status < 0) { mlog_errno(status); - goto out_check_volume; + goto out_system_inodes; } ocfs2_super_unlock(osb, 1); return 0; -out_check_volume: - ocfs2_free_replay_slots(osb); out_system_inodes: if (osb->local_alloc_state == OCFS2_LA_ENABLED) ocfs2_shutdown_local_alloc(osb); From b4cc0016f8ead77c823f2d55f4e0a0e61dd924b3 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 28 Aug 2026 19:28:25 +0800 Subject: [PATCH 018/134] ocfs2: defer suballocator block group reclaim to workqueue When the last bit in a suballocator block group is freed, _ocfs2_free_suballoc_bits() reclaims the group back to the global bitmap. The reclaim takes inode_lock() on the global bitmap inode while running inside the freeing transaction, adding a lock dependency of j_trans_barrier -> global bitmap inode i_rwsem This forms a circular dependency with paths such as ocfs2_shutdown_local_alloc(), which take the global bitmap inode lock before starting a transaction: Task1 (dealloc): ocfs2_run_deallocs ocfs2_free_cached_blocks ocfs2_start_trans down_read(j_trans_barrier) _ocfs2_free_suballoc_bits _ocfs2_reclaim_suballoc_to_main inode_lock(main_bm_inode) <- wait on Task2 Task2 (dismount): ocfs2_shutdown_local_alloc inode_lock(main_bm_inode) ocfs2_start_trans down_read(j_trans_barrier) <- wait on Task3 Task3 (ocfs2cmt): ocfs2_commit_cache down_write(j_trans_barrier) <- wait on Task1's handle jbd2_journal_flush Task1 waits for Task2's inode_lock(), Task2 waits for the j_trans_barrier down_write() held by ocfs2cmt, and ocfs2cmt waits for Task1's running transaction to commit - a real deadlock, observed with aio-stress direct IO writes racing dismount. Fix it by deferring the reclaim to the per-superblock ocfs2_wq workqueue, so the freeing transaction no longer takes the global bitmap inode lock. The worker re-checks under the suballocator locks that the block group is still fully freed (it may have been allocated from again in the meantime), takes the global bitmap inode locks before starting its own transaction, and performs the same suballocator cleanup and space return. The inode lock order (suballocator inode -> global bitmap inode) is consistent with the existing "inode lock before transaction" order, breaking the cycle. Reclaim work can still be queued late in dismount, e.g. when the truncate log is flushed or orphan dir recovery frees inode bits, so both ocfs2_dismount_volume() and the mount error path flush ocfs2_wq right before the system inodes are released, while the journal is still alive, to make sure no reclaim work is left running. The worker also bails out if the journal is already gone. Tested with the ocfs2 testsuite (including aio-stress direct IO) and umount/mount cycles on a CONFIG_PROVE_LOCKING kernel: the circular locking dependency is gone and freed block groups are still returned to the global bitmap. Link: https://lore.kernel.org/20260828112825.666097-3-joseph.qi@linux.alibaba.com Fixes: 4a54331616b3 ("ocfs2: give ocfs2 the ability to reclaim suballocator free bg") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Assisted-by: Qoder:Qwen3.8-Max Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao --- fs/ocfs2/ocfs2.h | 5 ++ fs/ocfs2/suballoc.c | 202 +++++++++++++++++++++++++++++++++++++------- fs/ocfs2/suballoc.h | 2 +- fs/ocfs2/super.c | 16 ++++ 4 files changed, 195 insertions(+), 30 deletions(-) diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h index 62cad6522c7a..b747cdec1787 100644 --- a/fs/ocfs2/ocfs2.h +++ b/fs/ocfs2/ocfs2.h @@ -502,6 +502,11 @@ struct ocfs2_super */ struct workqueue_struct *ocfs2_wq; + /* deferred reclaim of fully freed suballocator block groups */ + spinlock_t os_suballoc_reclaim_lock; + struct list_head os_suballoc_reclaim_list; + struct work_struct os_suballoc_reclaim_work; + /* sysfs directory per partition */ struct kset *osb_dev_kset; diff --git a/fs/ocfs2/suballoc.c b/fs/ocfs2/suballoc.c index 20c3aec6b987..453b56be9624 100644 --- a/fs/ocfs2/suballoc.c +++ b/fs/ocfs2/suballoc.c @@ -2687,16 +2687,24 @@ static int ocfs2_block_group_clear_bits(handle_t *handle, * cleanup rec/alloc_inode job, then switches to the main bitmap * to reclaim released space. * + * Callers must hold inode_lock() and ocfs2_inode_lock() on + * main_bm_inode, i.e. the global bitmap inode locks must be taken + * before starting the transaction. + * * handle: The transaction handle * alloc_inode: The suballoc inode * alloc_bh: The buffer_head of suballoc inode * group_bh: The group descriptor buffer_head of suballocator managed. - * Caller should release the input group_bh. + * This function takes ownership of it and will release it. + * main_bm_inode: The global bitmap inode + * main_bm_bh: The buffer_head of the global bitmap inode */ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle, struct inode *alloc_inode, struct buffer_head *alloc_bh, - struct buffer_head *group_bh) + struct buffer_head *group_bh, + struct inode *main_bm_inode, + struct buffer_head *main_bm_bh) { int idx, status = 0; int i, next_free_rec, len = 0; @@ -2706,8 +2714,6 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle, u64 bg_blkno, start_blk; unsigned int count; struct ocfs2_chain_rec *rec; - struct buffer_head *main_bm_bh = NULL; - struct inode *main_bm_inode = NULL; struct ocfs2_super *osb = OCFS2_SB(alloc_inode->i_sb); struct ocfs2_dinode *fe = (struct ocfs2_dinode *) alloc_bh->b_data; struct ocfs2_chain_list *cl = &fe->id2.i_chain; @@ -2794,24 +2800,12 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle, ocfs2_remove_from_cache(INODE_CACHE(alloc_inode), group_bh); memset(group, 0, sizeof(struct ocfs2_group_desc)); - /* prepare job for reclaim clusters */ - main_bm_inode = ocfs2_get_system_file_inode(osb, - GLOBAL_BITMAP_SYSTEM_INODE, - OCFS2_INVALID_SLOT); - if (!main_bm_inode) - goto bail; /* ignore the error in reclaim path */ - - inode_lock(main_bm_inode); - - status = ocfs2_inode_lock(main_bm_inode, &main_bm_bh, 1); - if (status < 0) - goto free_bm_inode; /* ignore the error in reclaim path */ - ocfs2_block_to_cluster_group(main_bm_inode, start_blk, &bg_blkno, &start_bit); fe = (struct ocfs2_dinode *) main_bm_bh->b_data; cl = &fe->id2.i_chain; - /* reuse group_bh, caller will release the input group_bh */ + /* release the suballocator group descriptor before reuse */ + brelse(group_bh); group_bh = NULL; /* reclaim clusters to global_bitmap */ @@ -2819,7 +2813,7 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle, &group_bh); if (status < 0) { mlog_errno(status); - goto free_bm_bh; + goto bail; } group = (struct ocfs2_group_desc *) group_bh->b_data; @@ -2827,7 +2821,7 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle, ocfs2_error(alloc_inode->i_sb, "reclaim length (%d) beyands block group length (%d)", count + start_bit, le16_to_cpu(group->bg_bits)); - goto free_group_bh; + goto bail; } old_bg_contig_free_bits = group->bg_contig_free_bits; @@ -2837,7 +2831,7 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle, _ocfs2_clear_bit); if (status < 0) { mlog_errno(status); - goto free_group_bh; + goto bail; } status = ocfs2_journal_access_di(handle, INODE_CACHE(main_bm_inode), @@ -2847,7 +2841,7 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle, ocfs2_block_group_set_bits(handle, main_bm_inode, group, group_bh, start_bit, count, le16_to_cpu(old_bg_contig_free_bits), 1); - goto free_group_bh; + goto bail; } idx = le16_to_cpu(group->bg_chain); @@ -2858,19 +2852,168 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle, fe->id1.bitmap1.i_used = cpu_to_le32(tmp_used - count); ocfs2_journal_dirty(handle, main_bm_bh); -free_group_bh: +bail: brelse(group_bh); + return status; +} -free_bm_bh: +/* + * When a suballocator block group becomes fully freed, its space is + * reclaimed back to the global bitmap. Taking the global bitmap inode + * lock inside the freeing transaction would create a lock dependency + * of "j_trans_barrier -> global bitmap inode i_rwsem", which forms a + * circular dependency with paths like ocfs2_shutdown_local_alloc() that + * take the inode lock before starting a transaction, and can lead to a + * real deadlock with the ocfs2cmt journal commit thread. So queue the + * reclaim to the workqueue and let it run outside the freeing + * transaction. + */ +struct ocfs2_suballoc_reclaim_work { + struct list_head list; + struct inode *alloc_inode; + u64 bg_blkno; +}; + +static void ocfs2_queue_suballoc_reclaim(struct ocfs2_super *osb, + struct inode *alloc_inode, + u64 bg_blkno) +{ + struct ocfs2_suballoc_reclaim_work *reclaim_work; + + reclaim_work = kmalloc_obj(*reclaim_work, GFP_NOFS); + if (!reclaim_work) { + /* + * Reclaim is only a space return optimization. If we can't + * queue it, the freed block group just stays owned by the + * suballocator. + */ + return; + } + + igrab(alloc_inode); + reclaim_work->alloc_inode = alloc_inode; + reclaim_work->bg_blkno = bg_blkno; + + spin_lock(&osb->os_suballoc_reclaim_lock); + list_add_tail(&reclaim_work->list, &osb->os_suballoc_reclaim_list); + spin_unlock(&osb->os_suballoc_reclaim_lock); + + queue_work(osb->ocfs2_wq, &osb->os_suballoc_reclaim_work); +} + +static void ocfs2_do_suballoc_reclaim(struct ocfs2_super *osb, + struct ocfs2_suballoc_reclaim_work *reclaim_work) +{ + int status, i; + handle_t *handle; + struct inode *alloc_inode = reclaim_work->alloc_inode; + struct inode *main_bm_inode; + struct buffer_head *alloc_bh = NULL, *group_bh = NULL; + struct buffer_head *main_bm_bh = NULL; + struct ocfs2_dinode *fe; + struct ocfs2_chain_list *cl; + struct ocfs2_chain_rec *rec; + + /* journal already gone, e.g. during dismount cleanup */ + if (!osb->journal) + return; + + inode_lock(alloc_inode); + status = ocfs2_inode_lock(alloc_inode, &alloc_bh, 1); + if (status < 0) + goto out_alloc; + + fe = (struct ocfs2_dinode *) alloc_bh->b_data; + cl = &fe->id2.i_chain; + + /* + * The block group may have been allocated from again since the + * reclaim work was queued, re-check that it is still fully freed. + * A stale work item can also reference a group that is no longer + * chained, whose descriptor would fail validation and trigger a + * spurious ocfs2_error(), so verify chain membership first. + */ + for (i = 0; i < le16_to_cpu(cl->cl_next_free_rec); i++) { + rec = &cl->cl_recs[i]; + if (le64_to_cpu(rec->c_blkno) == reclaim_work->bg_blkno) + break; + } + if (i == le16_to_cpu(cl->cl_next_free_rec) || + ocfs2_is_cluster_bitmap(alloc_inode) || + (le32_to_cpu(rec->c_free) != (le32_to_cpu(rec->c_total) - 1)) || + (le16_to_cpu(cl->cl_next_free_rec) == 1)) + goto out_alloc_unlock; + + status = ocfs2_read_group_descriptor(alloc_inode, fe, + reclaim_work->bg_blkno, &group_bh); + if (status < 0) + goto out_alloc_unlock; + + main_bm_inode = ocfs2_get_system_file_inode(osb, + GLOBAL_BITMAP_SYSTEM_INODE, + OCFS2_INVALID_SLOT); + if (!main_bm_inode) + goto out_group; + + inode_lock(main_bm_inode); + status = ocfs2_inode_lock(main_bm_inode, &main_bm_bh, 1); + if (status < 0) + goto out_main; + + handle = ocfs2_start_trans(osb, OCFS2_SUBALLOC_FREE); + if (IS_ERR(handle)) { + status = PTR_ERR(handle); + mlog_errno(status); + goto out_main_unlock; + } + + status = _ocfs2_reclaim_suballoc_to_main(handle, alloc_inode, + alloc_bh, group_bh, + main_bm_inode, main_bm_bh); + /* group_bh ownership passed to _ocfs2_reclaim_suballoc_to_main() */ + group_bh = NULL; + if (status < 0) + mlog_errno(status); + + ocfs2_commit_trans(osb, handle); + +out_main_unlock: ocfs2_inode_unlock(main_bm_inode, 1); brelse(main_bm_bh); - -free_bm_inode: +out_main: inode_unlock(main_bm_inode); iput(main_bm_inode); +out_group: + brelse(group_bh); +out_alloc_unlock: + ocfs2_inode_unlock(alloc_inode, 1); + brelse(alloc_bh); +out_alloc: + inode_unlock(alloc_inode); +} -bail: - return status; +void ocfs2_suballoc_reclaim_worker(struct work_struct *work) +{ + struct ocfs2_super *osb = container_of(work, struct ocfs2_super, + os_suballoc_reclaim_work); + struct ocfs2_suballoc_reclaim_work *reclaim_work; + + while (1) { + spin_lock(&osb->os_suballoc_reclaim_lock); + if (list_empty(&osb->os_suballoc_reclaim_list)) { + spin_unlock(&osb->os_suballoc_reclaim_lock); + break; + } + reclaim_work = list_first_entry(&osb->os_suballoc_reclaim_list, + struct ocfs2_suballoc_reclaim_work, + list); + list_del(&reclaim_work->list); + spin_unlock(&osb->os_suballoc_reclaim_lock); + + ocfs2_do_suballoc_reclaim(osb, reclaim_work); + iput(reclaim_work->alloc_inode); + kfree(reclaim_work); + } } /* @@ -2955,7 +3098,8 @@ static int _ocfs2_free_suballoc_bits(handle_t *handle, goto bail; } - _ocfs2_reclaim_suballoc_to_main(handle, alloc_inode, alloc_bh, group_bh); + ocfs2_queue_suballoc_reclaim(OCFS2_SB(alloc_inode->i_sb), alloc_inode, + bg_blkno); bail: brelse(group_bh); diff --git a/fs/ocfs2/suballoc.h b/fs/ocfs2/suballoc.h index bcf2ed4a8631..6042abc032f9 100644 --- a/fs/ocfs2/suballoc.h +++ b/fs/ocfs2/suballoc.h @@ -206,7 +206,7 @@ int ocfs2_lock_allocators(struct inode *inode, struct ocfs2_extent_tree *et, int ocfs2_test_inode_bit(struct ocfs2_super *osb, u64 blkno, int *res); - +void ocfs2_suballoc_reclaim_worker(struct work_struct *work); /* * The following two interfaces are for ocfs2_create_inode_in_orphan(). diff --git a/fs/ocfs2/super.c b/fs/ocfs2/super.c index 6a8092b65bb5..1e76b1d9fe0a 100644 --- a/fs/ocfs2/super.c +++ b/fs/ocfs2/super.c @@ -1784,6 +1784,9 @@ static int ocfs2_mount_volume(struct super_block *sb) out_system_inodes: if (osb->local_alloc_state == OCFS2_LA_ENABLED) ocfs2_shutdown_local_alloc(osb); + /* Drain pending suballoc reclaim work before the journal goes away */ + if (osb->ocfs2_wq) + flush_workqueue(osb->ocfs2_wq); ocfs2_release_system_inodes(osb); /* before journal shutdown, we should release slot_info */ ocfs2_free_slot_info(osb); @@ -1854,6 +1857,14 @@ static void ocfs2_dismount_volume(struct super_block *sb, int mnt_err) if (osb->cconn) ocfs2_super_unlock(osb, 1); + /* + * Drain pending suballoc reclaim work while the system inodes and + * the journal are still alive, since the worker needs to look up + * the global bitmap inode and start a transaction. + */ + if (osb->ocfs2_wq) + flush_workqueue(osb->ocfs2_wq); + ocfs2_release_system_inodes(osb); ocfs2_journal_shutdown(osb); @@ -2140,6 +2151,11 @@ static int ocfs2_initialize_super(struct super_block *sb, INIT_WORK(&osb->dquot_drop_work, ocfs2_drop_dquot_refs); init_llist_head(&osb->dquot_drop_list); + spin_lock_init(&osb->os_suballoc_reclaim_lock); + INIT_LIST_HEAD(&osb->os_suballoc_reclaim_list); + INIT_WORK(&osb->os_suballoc_reclaim_work, + ocfs2_suballoc_reclaim_worker); + /* get some pseudo constants for clustersize bits */ osb->s_clustersize_bits = le32_to_cpu(di->id2.i_super.s_clustersize_bits); From 37ee144a07c0d652bea6986ec587eafa22df32c4 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 28 Aug 2026 19:43:37 +0200 Subject: [PATCH 019/134] init: simplify early_hostname() Inline the strscpy() check and remove the redundant arglen variable. Use %zu to format the unsigned maxlen argument and add a newline after the truncation warning. Link: https://lore.kernel.org/20260828174337.609333-2-blum@kernel.org Signed-off-by: Thorsten Blum Signed-off-by: Andrew Morton --- init/version.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/init/version.c b/init/version.c index 0bd5c45aabc4..cbae11b6f768 100644 --- a/init/version.c +++ b/init/version.c @@ -21,16 +21,14 @@ static int __init early_hostname(char *arg) { size_t bufsize = sizeof(init_uts_ns.name.nodename); size_t maxlen = bufsize - 1; - ssize_t arglen; if (!arg) return -EINVAL; - arglen = strscpy(init_uts_ns.name.nodename, arg, bufsize); - if (arglen < 0) { - pr_warn("hostname parameter exceeds %zd characters and will be truncated", + if (strscpy(init_uts_ns.name.nodename, arg, bufsize) < 0) + pr_warn("hostname parameter exceeds %zu characters and will be truncated\n", maxlen); - } + return 0; } early_param("hostname", early_hostname); From e614d360f3877bca8ef3d821fbef828f3628f40d Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Sun, 4 Oct 2026 12:36:09 -0400 Subject: [PATCH 020/134] hung_task: reset warning budget when problem gets resolved Patch series "hung_task: Improve warning budget handling and task reporting", v11. The hung_task watchdog detects tasks stuck in TASK_UNINTERRUPTIBLE (D) state for longer than CONFIG_DEFAULT_HUNG_TASK_TIMEOUT seconds. To prevent log spam during system spikes, sysctl_hung_task_warnings enforces a budget on the number of logged warnings. However, the current implementation has two major limitations: 1. Permanent exhaustion of warning budget sysctl_hung_task_warnings is decremented directly when printing warnings. Once this budget hits zero, no further warnings are reported until an administrator manually updates the sysctl value or reboots the system. Consequently, a single temporary hang episode permanently blinds the kernel watchdog to any subsequent hung tasks after system recovery. 2. Total log suppression when budget is exhausted Once the warning budget reaches zero, hung_task_info() completely suppresses all output, including the basic single-line alert. While suppressing verbose stack dumps and lock debugging is desirable to prevent dmesg flooding, hiding basic task alerts leaves administrators entirely unaware that tasks are hanging. This patch series resolves both limitations by decoupling the configured warning limit from the active runtime budget, automatically resetting the budget upon system recovery or sysctl updates, and emitting a single aggregate summary line when hung tasks are detected under an exhausted warning budget. Patch 1 separates the configured sysctl hung_task_warnings from the runtime budget, making khungtaskd the sole owner of runtime budget updates. The budget is reloaded directly when a scan finds zero hung tasks, or via an atomic reset request published on sysctl write. Patch 2 prevents dmesg flooding during system-wide hangs by keeping non-panic per-task stack dumps budgeted, while providing ongoing visibility by logging a single aggregate summary line at the end of each scan iteration when the warning budget is exhausted. This patch (of 2): The sysctl hung_task_warnings currently holds both the configured warning limit and the remaining budget. Each detailed report decrements the sysctl, so once it reaches zero, the configured limit is lost and cannot be restored automatically. Keep sysctl_hung_task_warnings as the configured warning limit and make khungtaskd the sole owner of the remaining budget. A check that finds no hung tasks reloads the budget directly from the configured limit. A successful sysctl write publishes an atomic reset request, which khungtaskd consumes at the start of the next check. Link: https://lore.kernel.org/20261004163610.413506-1-atomlin@atomlin.com Link: https://lore.kernel.org/20261004163610.413506-2-atomlin@atomlin.com Signed-off-by: Aaron Tomlin Signed-off-by: Andrew Morton Suggested-by: Petr Mladek Suggested-by: Lance Yang Tested-by: Lance Yang Reviewed-by: Lance Yang Reviewed-by: Bradley Morgan Cc: Masami Hiramatsu Cc: David Laight Cc: Daniel Vacek Cc: Sean Ashe Cc: Cc: Cc: Cc: --- Documentation/admin-guide/sysctl/kernel.rst | 5 ++- kernel/hung_task.c | 49 +++++++++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst index ffea61d448eb..c03369e234a9 100644 --- a/Documentation/admin-guide/sysctl/kernel.rst +++ b/Documentation/admin-guide/sysctl/kernel.rst @@ -459,8 +459,9 @@ hung_task_warnings ================== The maximum number of warnings to report. During a check interval -if a hung task is detected, this value is decreased by 1. -When this value reaches 0, no more warnings will be reported. +if a hung task is detected, the internal warning budget is decreased by 1. +When this budget reaches 0, no more detailed warnings will be reported. The +warning budget is reset to the configured limit when no hung task is found. This file shows up if ``CONFIG_DETECT_HUNG_TASK`` is enabled. -1: report an infinite number of warnings. diff --git a/kernel/hung_task.c b/kernel/hung_task.c index 6fcc94ce4ca9..a5043188456d 100644 --- a/kernel/hung_task.c +++ b/kernel/hung_task.c @@ -57,8 +57,20 @@ unsigned long __read_mostly sysctl_hung_task_timeout_secs = CONFIG_DEFAULT_HUNG_ */ static unsigned long __read_mostly sysctl_hung_task_check_interval_secs; +/* + * Limit the number of printed hung tasks to prevent printing + * the same or similar backtraces repeatedly. + */ static int __read_mostly sysctl_hung_task_warnings = 10; +/* + * The number of hung tasks which still can be reported. + * The budget gets restored to the original limit when + * the previous stall is resolved. + */ +static int hung_task_warnings_budget = 10; +static atomic_t reset_hung_task_warnings = ATOMIC_INIT(0); + static int __read_mostly did_panic; static bool hung_task_call_panic; @@ -245,11 +257,11 @@ static void hung_task_info(struct task_struct *t, unsigned long timeout, /* * The given task did not get scheduled for more than * CONFIG_DEFAULT_HUNG_TASK_TIMEOUT. Therefore, complain - * accordingly + * accordingly with full details if the budget is not exhausted. */ - if (sysctl_hung_task_warnings || hung_task_call_panic) { - if (sysctl_hung_task_warnings > 0) - sysctl_hung_task_warnings--; + if (hung_task_warnings_budget || hung_task_call_panic) { + if (hung_task_warnings_budget > 0) + hung_task_warnings_budget--; pr_err("INFO: task %s:%d blocked%s for more than %ld seconds.\n", t->comm, t->pid, t->in_iowait ? " in I/O wait" : "", (jiffies - t->last_switch_time) / HZ); @@ -264,7 +276,7 @@ static void hung_task_info(struct task_struct *t, unsigned long timeout, sched_show_task(t); debug_show_blocker(t, timeout); - if (!sysctl_hung_task_warnings) + if (!hung_task_warnings_budget) pr_info("Future hung task reports are suppressed, see sysctl kernel.hung_task_warnings\n"); } @@ -304,7 +316,7 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout) unsigned long last_break = jiffies; struct task_struct *g, *t; unsigned long this_round_count; - int need_warning = sysctl_hung_task_warnings; + int need_warning; unsigned long si_mask = hung_task_si_mask; /* @@ -314,6 +326,11 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout) if (test_taint(TAINT_DIE) || did_panic) return; + if (atomic_xchg_acquire(&reset_hung_task_warnings, 0)) + hung_task_warnings_budget = + READ_ONCE(sysctl_hung_task_warnings); + need_warning = hung_task_warnings_budget; + this_round_count = 0; rcu_read_lock(); for_each_process_thread(g, t) { @@ -340,8 +357,11 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout) unlock: rcu_read_unlock(); - if (!this_round_count) + if (!this_round_count) { + hung_task_warnings_budget = + READ_ONCE(sysctl_hung_task_warnings); return; + } if (need_warning || hung_task_call_panic) { si_mask |= SYS_INFO_LOCKS; @@ -425,6 +445,19 @@ static int proc_dohung_task_timeout_secs(const struct ctl_table *table, int writ return ret; } +static int proc_dohung_task_warnings(const struct ctl_table *table, int write, + void *buffer, + size_t *lenp, loff_t *ppos) +{ + int ret; + + ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + if (!ret && write) + atomic_set_release(&reset_hung_task_warnings, 1); + + return ret; +} + /* * This is needed for proc_doulongvec_minmax of sysctl_hung_task_timeout_secs * and hung_task_check_interval_secs @@ -480,7 +513,7 @@ static const struct ctl_table hung_task_sysctls[] = { .data = &sysctl_hung_task_warnings, .maxlen = sizeof(int), .mode = 0644, - .proc_handler = proc_dointvec_minmax, + .proc_handler = proc_dohung_task_warnings, .extra1 = SYSCTL_NEG_ONE, }, { From 36dd57d0ae9605cf7c91e6de04296400f8e2ef27 Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Sun, 4 Oct 2026 12:36:10 -0400 Subject: [PATCH 021/134] hung_task: log summary line when warning budget is exhausted Once the warning budget is exhausted, hung_task_info() normally stops printing per-task details. When panic is triggered, full details are still printed so diagnostics remain available before panic. To retain visibility without restoring per-task output after budget exhaustion, emit a single aggregate summary line at the end of each watchdog scan that detects hung tasks with an exhausted budget. This keeps non-panic per-task reports budgeted during system-wide hangs. Link: https://lore.kernel.org/20261004163610.413506-3-atomlin@atomlin.com Signed-off-by: Aaron Tomlin Signed-off-by: Andrew Morton Suggested-by: Petr Mladek Suggested-by: Lance Yang Reviewed-by: Petr Mladek Reviewed-by: Lance Yang Reviewed-by: Bradley Morgan Cc: Masami Hiramatsu Cc: David Laight Cc: Daniel Vacek Cc: Sean Ashe Cc: Cc: Cc: Cc: --- kernel/hung_task.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel/hung_task.c b/kernel/hung_task.c index a5043188456d..b8554aedc589 100644 --- a/kernel/hung_task.c +++ b/kernel/hung_task.c @@ -276,8 +276,8 @@ static void hung_task_info(struct task_struct *t, unsigned long timeout, sched_show_task(t); debug_show_blocker(t, timeout); - if (!hung_task_warnings_budget) - pr_info("Future hung task reports are suppressed, see sysctl kernel.hung_task_warnings\n"); + if (!hung_task_warnings_budget && !hung_task_call_panic) + pr_info("hung_task: further per-task details suppressed until warning budget is reset (see sysctl kernel.hung_task_warnings)\n"); } touch_nmi_watchdog(); @@ -363,6 +363,10 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout) return; } + if (!hung_task_warnings_budget && !hung_task_call_panic) + pr_info("hung_task: %lu hung tasks detected, warning budget exhausted\n", + this_round_count); + if (need_warning || hung_task_call_panic) { si_mask |= SYS_INFO_LOCKS; From 09835b11e5b1c0b659e44959b639c6426d977827 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Mon, 17 Aug 2026 20:16:13 +0800 Subject: [PATCH 022/134] minmax.h: update the stale 'x' versus 'ux' comment Commit b280bb27a9f7 ("minmax.h: reduce the #define expansion of min(), max() and clamp()") made __sign_use(), __is_nonneg() and __types_ok() take only 'ux', and commit a5743f32baec ("minmax.h: use BUILD_BUG_ON_MSG() for the lo < hi test in clamp()") did the same for the clamp() limit test. The comment describing the old split was added one patch earlier and was never updated. 'ux' now carries the value check too, since __is_nonneg() tests it rather than the original expression, and nothing here looks at the value of 'x' any more: it is expanded only to initialise 'ux' and in the error message, as the first of those changes intended. Link: https://lore.kernel.org/20260817121613.3846511-1-zhanxusheng@xiaomi.com Signed-off-by: Zhan Xusheng Signed-off-by: Andrew Morton Cc: David Laight Cc: "H. Peter Anvin" --- include/linux/minmax.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/minmax.h b/include/linux/minmax.h index a0158db54a04..5ef4d58c0c42 100644 --- a/include/linux/minmax.h +++ b/include/linux/minmax.h @@ -38,9 +38,9 @@ * Note that 'x' is the original expression, and 'ux' is the unique variable * that contains the value. * - * We use 'ux' for pure type checking, and 'x' for when we need to look at the - * value (but without evaluating it for side effects! - * Careful to only ever evaluate it with sizeof() or __builtin_constant_p() etc). + * We use 'ux' for both the type and the value checks, so 'x' itself is only + * expanded twice: once to initialise 'ux', and once quoted in the error + * message. * * Pointers end up being checked by the normal C type rules at the actual * comparison, and these expressions only need to be careful to not cause From 456b17ccaaf313a65652e51a11884f669c7c3929 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Mon, 17 Aug 2026 22:03:11 +0100 Subject: [PATCH 023/134] lib: cleanup "fake" tristates in Kconfig These 7 DECOMPRESS_ options (e.g. DECOMPRESS_GZIP) currently have the tristate type, but can never be set to M. Their only valid values are Y and N, making them effectively booleans. Let's make their types more accurate by changing them to 'bool'. Note that this is only a code cleanup, there is no functional change. These bistates were found by kconfirm, a static analysis tool for Kconfig. Link: https://lore.kernel.org/20260817210311.2142999-1-julianbraha@gmail.com Signed-off-by: Julian Braha Signed-off-by: Andrew Morton Cc: Arnd Bergmann Cc: Jani Nikula Cc: Julia Lawall --- lib/Kconfig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/Kconfig b/lib/Kconfig index 4e6b34c3346d..1e42f5167c68 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -219,29 +219,29 @@ source "lib/xz/Kconfig" # config DECOMPRESS_GZIP select ZLIB_INFLATE - tristate + bool config DECOMPRESS_BZIP2 - tristate + bool config DECOMPRESS_LZMA - tristate + bool config DECOMPRESS_XZ select XZ_DEC - tristate + bool config DECOMPRESS_LZO select LZO_DECOMPRESS - tristate + bool config DECOMPRESS_LZ4 select LZ4_DECOMPRESS - tristate + bool config DECOMPRESS_ZSTD select ZSTD_DECOMPRESS - tristate + bool # # Generic allocator support is selected if needed From b82ed0e3a1dafe41577b095318a3ea6c0eec6f46 Mon Sep 17 00:00:00 2001 From: Wilson Felipe Pereira Date: Tue, 18 Aug 2026 04:53:46 +0000 Subject: [PATCH 024/134] init/main: fix off-by-one in argv_init cleanup Patch series "init: fix array boundary bugs in boot parameter parsing". This series fixes two distinct boundary logic edge-case bugs in `init/main.c` related to parsing boot command-line arguments and environment variables. Both bugs have been present since the early git history (Linux-2.6.12-rc2). 1. The first patch fixes an off-by-one error in `init_setup()` where the final slot of the `argv_init` array was left uncleared. This allowed a stale kernel parameter to leak into the `init` process's user-space command line if exactly `MAX_INIT_ARGS` unknown parameters were passed. 2. The second patch fixes a false-positive kernel panic in `unknown_bootoption()`. If a user filled the environment variable array up to its exact limit (32) and then attempted to overwrite the final variable, the kernel would panic before evaluating whether it was a harmless duplicate. Exact QEMU reproduction steps for both edge cases are documented inside their respective commit descriptions. This patch (of 2): When cleaning up argv_init in init_setup() and rdinit_setup(), the loop terminates one element early due to using '<' instead of '<='. Since argv_init is sized MAX_INIT_ARGS+2, index MAX_INIT_ARGS is a valid element that should be cleared to NULL. If exactly MAX_INIT_ARGS unknown arguments are passed before 'init=', the uncleared argv_init[MAX_INIT_ARGS] can act as a ghost argument to /sbin/init or cause a spurious kernel panic when later appended to. To verify the argument leak, boot a VM into a shell with 32 unknown kernel arguments, the init parameter, and 31 user arguments: STALE_ARGS=$(for i in {1..32}; do echo -n "stale$i "; done) USER_ARGS=$(for i in {1..31}; do echo -n "user$i "; done) qemu-system-x86_64 -kernel bzImage \ -append "$STALE_ARGS init=/bin/sh $USER_ARGS" Running `cat /proc/1/cmdline` inside the shell reveals that the 32nd kernel argument ('stale32') incorrectly leaked into the init process's command line. This patch zeroes the final slot, cleanly terminating the array. Link: https://lore.kernel.org/20260818045357.4123784-1-wfelipe@google.com Link: https://lore.kernel.org/20260818045357.4123784-2-wfelipe@google.com Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Fixes: ffdfc40976dd ("[PATCH] Add rdinit parameter to pick early userspace init") Signed-off-by: Wilson Felipe Pereira Signed-off-by: Andrew Morton --- init/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/init/main.c b/init/main.c index 31f2bf54976a..517b76447950 100644 --- a/init/main.c +++ b/init/main.c @@ -581,7 +581,7 @@ static int __init init_setup(char *str) * the shell think it should execute a script with such name. * So we ignore all arguments entered _before_ init=... [MJ] */ - for (i = 1; i < MAX_INIT_ARGS; i++) + for (i = 1; i <= MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } @@ -594,7 +594,7 @@ static int __init rdinit_setup(char *str) ramdisk_execute_command = str; ramdisk_execute_command_set = true; /* See "auto" comment in init_setup */ - for (i = 1; i < MAX_INIT_ARGS; i++) + for (i = 1; i <= MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } From 51e766bcd35e706d8df29dd0fac98871fdced261 Mon Sep 17 00:00:00 2001 From: Wilson Felipe Pereira Date: Tue, 18 Aug 2026 04:53:47 +0000 Subject: [PATCH 025/134] init/main: fix false-positive kernel panic on environment variable overwrite In unknown_bootoption(), the limit checking for environment variables sets panic_later *before* checking if the variable already exists in envp_init. If a user passes exactly MAX_INIT_ENVS custom variables and then overwrites the final variable by matching its key, it causes a false-positive hard panic on boot despite not actually exceeding the array bounds or increasing the total variable count. Swapping the order of these checks allows the duplicate check to break out of the loop before the panic flag is erroneously latched. To verify, boot a VM with 31 custom variables (filling the array up to its limit of 32) and then overwrite the very last variable: ENV_VARS=$(for i in {1..31}; do echo -n "var$i=$i "; done) qemu-system-x86_64 -kernel bzImage -append "$ENV_VARS var31=overwrite" Without this patch, the kernel crashes instantly with: Kernel panic - not syncing: Too many boot env vars at 'var31=overwrite' With this patch, the kernel safely overwrites the variable and boots. Link: https://lore.kernel.org/20260818045357.4123784-3-wfelipe@google.com Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Wilson Felipe Pereira Signed-off-by: Andrew Morton --- init/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/init/main.c b/init/main.c index 517b76447950..d1cd8efb8238 100644 --- a/init/main.c +++ b/init/main.c @@ -548,12 +548,12 @@ static int __init unknown_bootoption(char *param, char *val, /* Environment option */ unsigned int i; for (i = 0; envp_init[i]; i++) { + if (!strncmp(param, envp_init[i], len+1)) + break; if (i == MAX_INIT_ENVS) { panic_later = "env"; panic_param = param; } - if (!strncmp(param, envp_init[i], len+1)) - break; } envp_init[i] = param; } else { From 0a27f795030ce6a2c71525b0544e3b13bba8828e Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Sun, 16 Aug 2026 16:12:15 +0000 Subject: [PATCH 026/134] proc: report SIGEV_NONE in /proc/pid/timers if target task has died When a posix timer is created targeting a specific thread (using SIGEV_SIGNAL | SIGEV_THREAD_ID), it takes a reference to the target struct pid in timer->it_pid. If the target thread subsequently terminates, its numeric tid is freed and can be recycled for an unrelated task. However, the timer holds its reference to the original struct pid. show_timer() in /proc/[pid]/timers previously called pid_nr_ns() directly on timer->it_pid without checking whether any task remained attached to that struct pid. As a result: 1. It reported the stale tid, which could mistakenly refer to a recycled pid. 2. In the kernel, expired signals for dead target threads are dropped by posixtimer_send_sigqueue() because posixtimer_get_target() returns NULL, so the timer functionally acts as SIGEV_NONE. 3. Checkpoint/restore tools (CRIU) parsing /proc/[pid]/timers would try to restore a timer with SIGEV_SIGNAL | SIGEV_THREAD_ID targeting a non-existent or unrelated thread. Check pid_has_task(timer->it_pid, timer->it_pid_type) in show_timer(). If the target task has died, override notify to SIGEV_NONE and report PID 0 (e.g., 'notify: none/pid.0'). Link: https://lore.kernel.org/20260816161216.984580-1-avagin@google.com Fixes: 57b8015e07a7 ("posix-timers: Show sigevent info in proc file") Signed-off-by: Andrei Vagin Signed-off-by: Andrew Morton Reviewed-by: Pavel Tikhomirov Cc: Thomas Gleixner --- fs/proc/base.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/proc/base.c b/fs/proc/base.c index 6a39de424f62..1455de58e53c 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -2519,17 +2519,23 @@ static int show_timer(struct seq_file *m, void *v) struct k_itimer *timer = hlist_entry((struct hlist_node *)v, struct k_itimer, list); struct timers_private *tp = m->private; int notify = timer->it_sigev_notify; + pid_t nr = 0; guard(spinlock_irq)(&timer->it_lock); if (!posixtimer_valid(timer)) return 0; + if (timer->it_pid && pid_has_task(timer->it_pid, timer->it_pid_type)) + nr = pid_nr_ns(timer->it_pid, tp->ns); + else + notify = SIGEV_NONE; + seq_printf(m, "ID: %d\n", timer->it_id); seq_printf(m, "signal: %d/%px\n", timer->sigq.info.si_signo, timer->sigq.info.si_value.sival_ptr); seq_printf(m, "notify: %s/%s.%d\n", nstr[notify & ~SIGEV_THREAD_ID], (notify & SIGEV_THREAD_ID) ? "tid" : "pid", - pid_nr_ns(timer->it_pid, tp->ns)); + nr); seq_printf(m, "ClockID: %d\n", timer->it_clock); return 0; From df0b7e0ea59c43708cff8c7e34da5e2f8d430e5e Mon Sep 17 00:00:00 2001 From: Konstantin Khorenko Date: Fri, 14 Aug 2026 18:57:09 +0200 Subject: [PATCH 027/134] selftests/core: fix unshare_test with large fs.nr_open The test assumes fs.nr_open is close to the default 1048576, but some systems set it much higher (e.g. 1073741816). This is systemd's doing: since systemd v240 (2018), PID 1 bumps fs.nr_open and fs.file-max to their largest possible values on boot, as file descriptors are already accounted for by memcg [1]. In that case, dup2() to nr_open + 64 requires the kernel to allocate a file descriptor table with ~1 billion entries, which fails with ENOMEM. On a kernel that already carries 04a2c4b4511d1, dup2() no longer fails with ENOMEM. The allocation is now rejected up front and the caller gets EMFILE instead, without the WARNING, but the test still fails. Cap the nr_open value used for the test's own arithmetic to a known reasonable base value (1048576) and restore the true original value once the test has completed. Link: https://lore.kernel.org/20260814165709.513263-1-khorenko@virtuozzo.com Link: https://github.com/systemd/systemd/commit/a8b627aaed409a15260c25988970c795bf963812 [1] Signed-off-by: Konstantin Khorenko Signed-off-by: Eva Kurchatova Signed-off-by: Andrew Morton Cc: Shuah Khan Cc: Wei Yang Cc: --- tools/testing/selftests/core/unshare_test.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/core/unshare_test.c b/tools/testing/selftests/core/unshare_test.c index ffce75a6c228..d40e963dd520 100644 --- a/tools/testing/selftests/core/unshare_test.c +++ b/tools/testing/selftests/core/unshare_test.c @@ -40,6 +40,14 @@ TEST(unshare_EMFILE) ASSERT_EQ(sscanf(buf, "%d", &nr_open), 1); + /* + * Cap nr_open for the duration of the test to avoid ENOMEM from a + * huge fd table allocation; buf/n keep the real original value so + * fs.nr_open can be restored to it once the test is done. + */ + if (nr_open > 1024 * 1024) + nr_open = 1024 * 1024; + ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlimit)); /* bump fs.nr_open */ @@ -73,10 +81,13 @@ TEST(unshare_EMFILE) if (pid == 0) { int err; + char buf3[32]; + ssize_t n3; - /* restore fs.nr_open */ + /* restore fs.nr_open to the (possibly capped) test baseline */ + n3 = sprintf(buf3, "%d\n", nr_open); lseek(fd, 0, SEEK_SET); - write(fd, buf, n); + write(fd, buf3, n3); /* ... and now unshare(CLONE_FILES) must fail with EMFILE */ err = unshare(CLONE_FILES); EXPECT_EQ(err, -1) @@ -89,6 +100,10 @@ TEST(unshare_EMFILE) EXPECT_EQ(waitpid(pid, &status, 0), pid); EXPECT_EQ(true, WIFEXITED(status)); EXPECT_EQ(0, WEXITSTATUS(status)); + + /* restore the real fs.nr_open value */ + lseek(fd, 0, SEEK_SET); + write(fd, buf, n); } TEST_HARNESS_MAIN From a4747846dfa6f09080d2252d9d442e7dbe333fb2 Mon Sep 17 00:00:00 2001 From: Thomas Maarseveen Date: Wed, 12 Aug 2026 20:55:33 +0200 Subject: [PATCH 028/134] lib/tests: add KUnit tests for errseq The errseq_t infrastructure (lib/errseq.c) underpins writeback error reporting but has no regression tests. Its semantics are subtle enough to have needed fixing before: commit b4678df184b3 ("errseq: Always report a writeback error once") changed how unseen errors reach new samplers. Add a KUnit suite covering the documented single-threaded semantics: - a zeroed errseq_t is the "no error yet" epoch - errors are recorded, overwrite one another, and both ends of the valid errno range round-trip exactly - an error nobody has seen samples as zero, so a check against a fresh sample still reports it - errseq_check_and_advance() reports a given error exactly once per cursor and leaves the cursor in place when nothing has changed - once an error has been seen, a fresh sample is current and a check against it reports nothing - the same error recorded again after being seen is reported again, even to a cursor that consumed the first occurrence while another cursor marked the repeat as seen - independent cursors each observe each error The lockless behaviour of errseq_t under concurrent updates and the WARN path for invalid error values are deliberately out of scope. Tested with ./tools/testing/kunit/kunit.py run, with a kunitconfig enabling CONFIG_KUNIT=y and CONFIG_ERRSEQ_KUNIT_TEST=y; all 13 tests pass under ARCH=um. Link: https://lore.kernel.org/20260812-errseq-kunit-v1-1-312be4c3aa0d@gmail.com Signed-off-by: Thomas Maarseveen Signed-off-by: Andrew Morton Acked-by: Jeff Layton Cc: David Gow --- MAINTAINERS | 1 + lib/Kconfig.debug | 15 +++ lib/tests/Makefile | 1 + lib/tests/errseq_kunit.c | 237 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 254 insertions(+) create mode 100644 lib/tests/errseq_kunit.c diff --git a/MAINTAINERS b/MAINTAINERS index 7235a92ff879..d642d4168c28 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9701,6 +9701,7 @@ M: Jeff Layton S: Maintained F: include/linux/errseq.h F: lib/errseq.c +F: lib/tests/errseq_kunit.c ESD CAN NETWORK DRIVERS M: Stefan Mätje diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 134b15a44625..c3f448f3b8f1 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2797,6 +2797,21 @@ config SYSCTL_KUNIT_TEST If unsure, say N. +config ERRSEQ_KUNIT_TEST + tristate "KUnit test for errseq" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + This builds the errseq KUnit test suite. + It tests the documented semantics of the errseq_t error-tracking + infrastructure (lib/errseq.c), which underpins writeback error + reporting. + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + config KFIFO_KUNIT_TEST tristate "KUnit Test for the generic kernel FIFO implementation" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/lib/tests/Makefile b/lib/tests/Makefile index 3cac3b63a752..8e11b125433b 100644 --- a/lib/tests/Makefile +++ b/lib/tests/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_BLACKHOLE_DEV_KUNIT_TEST) += blackhole_dev_kunit.o obj-$(CONFIG_CHECKSUM_KUNIT) += checksum_kunit.o obj-$(CONFIG_CMDLINE_KUNIT_TEST) += cmdline_kunit.o obj-$(CONFIG_CPUMASK_KUNIT_TEST) += cpumask_kunit.o +obj-$(CONFIG_ERRSEQ_KUNIT_TEST) += errseq_kunit.o obj-$(CONFIG_FFS_KUNIT_TEST) += ffs_kunit.o CFLAGS_fortify_kunit.o += $(call cc-disable-warning, unsequenced) CFLAGS_fortify_kunit.o += $(call cc-disable-warning, stringop-overread) diff --git a/lib/tests/errseq_kunit.c b/lib/tests/errseq_kunit.c new file mode 100644 index 000000000000..8f39ebc4a248 --- /dev/null +++ b/lib/tests/errseq_kunit.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the errseq_t error-tracking infrastructure. + * + * These exercise the documented single-threaded semantics of the errseq + * API (see Documentation/core-api/errseq.rst and lib/errseq.c): error + * recording and overwriting, the "seen" handoff between errseq_sample() + * and errseq_check_and_advance(), and the re-reporting of an error that + * is recorded again after it has been seen. + * + * The lockless properties of errseq_t under concurrent updates are + * outside the scope of these deterministic tests, as is the WARN path + * for invalid error values. + */ +#include + +#include +#include +#include + +/* + * A zeroed errseq_t is the "no error has ever occurred" epoch: it + * samples as zero and no check against it reports anything. + */ +static void errseq_test_zero_epoch_reports_no_error(struct kunit *test) +{ + errseq_t eseq = 0; + errseq_t since = 0; + + KUNIT_EXPECT_EQ(test, errseq_sample(&eseq), 0); + KUNIT_EXPECT_EQ(test, errseq_check(&eseq, 0), 0); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), 0); + KUNIT_EXPECT_EQ(test, since, 0); +} + +static void errseq_test_set_records_error(struct kunit *test) +{ + errseq_t eseq = 0; + + /* errseq_set() returns the previous value; the epoch is zero. */ + KUNIT_EXPECT_EQ(test, errseq_set(&eseq, -EIO), 0); + KUNIT_EXPECT_EQ(test, errseq_check(&eseq, 0), -EIO); +} + +/* Any error set always overwrites an existing error. */ +static void errseq_test_set_overwrites_error(struct kunit *test) +{ + errseq_t eseq = 0; + + errseq_set(&eseq, -EIO); + errseq_set(&eseq, -ENOSPC); + KUNIT_EXPECT_EQ(test, errseq_check(&eseq, 0), -ENOSPC); +} + +/* Both ends of the valid error range are recorded exactly. */ +static void errseq_test_errno_range_extremes(struct kunit *test) +{ + errseq_t lo = 0; + errseq_t hi = 0; + + errseq_set(&lo, -1); + KUNIT_EXPECT_EQ(test, errseq_check(&lo, 0), -1); + + errseq_set(&hi, -MAX_ERRNO); + KUNIT_EXPECT_EQ(test, errseq_check(&hi, 0), -MAX_ERRNO); +} + +/* + * An error nobody has seen yet samples as zero, so that a check against + * the sample still reports it (see commit b4678df184b3 ("errseq: Always + * report a writeback error once")). + */ +static void errseq_test_sample_of_unseen_error_is_zero(struct kunit *test) +{ + errseq_t eseq = 0; + + errseq_set(&eseq, -EIO); + KUNIT_EXPECT_EQ(test, errseq_sample(&eseq), 0); +} + +static void errseq_test_new_sampler_sees_unseen_error(struct kunit *test) +{ + errseq_t eseq = 0; + errseq_t since; + + errseq_set(&eseq, -EIO); + since = errseq_sample(&eseq); + KUNIT_EXPECT_EQ(test, errseq_check(&eseq, since), -EIO); +} + +/* A given error is reported exactly once per advancing cursor. */ +static void errseq_test_check_and_advance_reports_once(struct kunit *test) +{ + errseq_t eseq = 0; + errseq_t since = errseq_sample(&eseq); + + errseq_set(&eseq, -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), 0); +} + +/* + * Once an error has been seen, a fresh sample is non-zero and checking + * against it reports nothing: handled errors do not reach new samplers. + */ +static void errseq_test_sample_after_seen_is_current(struct kunit *test) +{ + errseq_t eseq = 0; + errseq_t since = 0; + errseq_t sample; + + errseq_set(&eseq, -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO); + + sample = errseq_sample(&eseq); + KUNIT_EXPECT_NE(test, sample, 0); + KUNIT_EXPECT_EQ(test, errseq_check(&eseq, sample), 0); +} + +static void errseq_test_new_error_after_advance(struct kunit *test) +{ + errseq_t eseq = 0; + errseq_t since = 0; + + errseq_set(&eseq, -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO); + + errseq_set(&eseq, -ENOSPC); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -ENOSPC); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), 0); +} + +/* + * Recording the same error again after it has been seen must bump the + * sequence, so cursors that consumed the first occurrence see the + * second one too. + */ +static void errseq_test_same_error_reported_again_after_seen(struct kunit *test) +{ + errseq_t eseq = 0; + errseq_t since = 0; + errseq_t seen_cursor; + + errseq_set(&eseq, -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO); + + seen_cursor = since; + errseq_set(&eseq, -EIO); + KUNIT_EXPECT_EQ(test, errseq_check(&eseq, since), -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO); + /* The repeat must advance the sequence, not just re-toggle "seen". */ + KUNIT_EXPECT_NE(test, since, seen_cursor); +} + +/* + * A cursor that consumed an error must still observe a repeat of that + * error even when another cursor has already marked the repeat seen: + * recording over a seen value must advance the sequence. + */ +static void errseq_test_repeat_error_visible_to_all_cursors(struct kunit *test) +{ + errseq_t eseq = 0; + errseq_t cursor_a = 0; + errseq_t cursor_b = 0; + + errseq_set(&eseq, -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), -EIO); + + errseq_set(&eseq, -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_b), -EIO); + + KUNIT_EXPECT_EQ(test, errseq_check(&eseq, cursor_a), -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), 0); +} + +/* An advance with no new error reports nothing and leaves the cursor put. */ +static void errseq_test_advance_stable_when_unchanged(struct kunit *test) +{ + errseq_t eseq = 0; + errseq_t since = 0; + errseq_t cursor; + + errseq_set(&eseq, -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), -EIO); + + cursor = since; + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &since), 0); + KUNIT_EXPECT_EQ(test, since, cursor); +} + +/* + * Cursors are independent: one subscriber consuming an error does not + * consume it for another, and each subscriber sees each error once. + */ +static void errseq_test_two_subscribers_independent(struct kunit *test) +{ + errseq_t eseq = 0; + errseq_t cursor_a = errseq_sample(&eseq); + errseq_t cursor_b = errseq_sample(&eseq); + + errseq_set(&eseq, -EIO); + + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), -EIO); + KUNIT_EXPECT_EQ(test, errseq_check(&eseq, cursor_b), -EIO); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_b), -EIO); + + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_a), 0); + KUNIT_EXPECT_EQ(test, errseq_check_and_advance(&eseq, &cursor_b), 0); +} + +static struct kunit_case errseq_test_cases[] = { + KUNIT_CASE(errseq_test_zero_epoch_reports_no_error), + KUNIT_CASE(errseq_test_set_records_error), + KUNIT_CASE(errseq_test_set_overwrites_error), + KUNIT_CASE(errseq_test_errno_range_extremes), + KUNIT_CASE(errseq_test_sample_of_unseen_error_is_zero), + KUNIT_CASE(errseq_test_new_sampler_sees_unseen_error), + KUNIT_CASE(errseq_test_check_and_advance_reports_once), + KUNIT_CASE(errseq_test_sample_after_seen_is_current), + KUNIT_CASE(errseq_test_new_error_after_advance), + KUNIT_CASE(errseq_test_same_error_reported_again_after_seen), + KUNIT_CASE(errseq_test_repeat_error_visible_to_all_cursors), + KUNIT_CASE(errseq_test_advance_stable_when_unchanged), + KUNIT_CASE(errseq_test_two_subscribers_independent), + {} +}; + +static struct kunit_suite errseq_test_suite = { + .name = "errseq", + .test_cases = errseq_test_cases, +}; + +kunit_test_suite(errseq_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the errseq infrastructure"); +MODULE_LICENSE("GPL"); From 7b8fc26c40d6cf4c6751a045c9e42bbb7e78d81b Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 31 Aug 2026 14:22:48 -0700 Subject: [PATCH 029/134] xor: add missing vzeroupper to AVX code Since the AVX optimized XOR code uses YMM registers, execute vzeroupper before returning from it. This is needed to avoid degrading the performance of any later SSE code that may happen to be executed. Link: https://lore.kernel.org/20260831212248.213805-1-ebiggers@kernel.org Fixes: ea4d26ae24e5 ("raid5: add AVX optimized RAID5 checksumming") Signed-off-by: Eric Biggers Signed-off-by: Andrew Morton Cc: Christoph Hellwig Cc: --- lib/raid/xor/x86/xor-avx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/raid/xor/x86/xor-avx.c b/lib/raid/xor/x86/xor-avx.c index f7777d7aa269..95b21e7225e8 100644 --- a/lib/raid/xor/x86/xor-avx.c +++ b/lib/raid/xor/x86/xor-avx.c @@ -147,6 +147,7 @@ static void xor_gen_avx(void *dest, void **srcs, unsigned int src_cnt, { kernel_fpu_begin(); xor_gen_avx_inner(dest, srcs, src_cnt, bytes); + asm volatile("vzeroupper"); kernel_fpu_end(); } From 24f1241cdb056fd630e6710a916e0f7c8c910a70 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 31 Aug 2026 14:23:08 -0700 Subject: [PATCH 030/134] raid6: add missing vzeroupper to AVX2 code Since the AVX2 optimized RAID6 code uses YMM registers, execute vzeroupper before returning from it. This is needed to avoid degrading the performance of any later SSE code that may happen to be executed. Link: https://lore.kernel.org/20260831212308.213855-1-ebiggers@kernel.org Fixes: 2c935842bdb4 ("lib/raid6: Add AVX2 optimized gen_syndrome functions") Fixes: 7056741fd9fc ("lib/raid6: Add AVX2 optimized recovery functions") Signed-off-by: Eric Biggers Signed-off-by: Andrew Morton Cc: Christoph Hellwig Cc: --- lib/raid/raid6/x86/avx2.c | 6 ++++++ lib/raid/raid6/x86/recov_avx2.c | 2 ++ 2 files changed, 8 insertions(+) diff --git a/lib/raid/raid6/x86/avx2.c b/lib/raid/raid6/x86/avx2.c index 7d829c669ea7..3cc2fe7ac42c 100644 --- a/lib/raid/raid6/x86/avx2.c +++ b/lib/raid/raid6/x86/avx2.c @@ -67,6 +67,7 @@ static void raid6_avx21_gen_syndrome(int disks, size_t bytes, void **ptrs) } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -115,6 +116,7 @@ static void raid6_avx21_xor_syndrome(int disks, int start, int stop, } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -175,6 +177,7 @@ static void raid6_avx22_gen_syndrome(int disks, size_t bytes, void **ptrs) } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -243,6 +246,7 @@ static void raid6_avx22_xor_syndrome(int disks, int start, int stop, } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -334,6 +338,7 @@ static void raid6_avx24_gen_syndrome(int disks, size_t bytes, void **ptrs) } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -444,6 +449,7 @@ static void raid6_avx24_xor_syndrome(int disks, int start, int stop, asm volatile("vmovntdq %%ymm14,%0" : "=m" (q[d+96])); } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } diff --git a/lib/raid/raid6/x86/recov_avx2.c b/lib/raid/raid6/x86/recov_avx2.c index a714a780a2d8..820871046e30 100644 --- a/lib/raid/raid6/x86/recov_avx2.c +++ b/lib/raid/raid6/x86/recov_avx2.c @@ -176,6 +176,7 @@ static void raid6_2data_recov_avx2(int disks, size_t bytes, int faila, #endif } + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -293,6 +294,7 @@ static void raid6_datap_recov_avx2(int disks, size_t bytes, int faila, #endif } + asm volatile("vzeroupper"); kernel_fpu_end(); } From b82cf9e49c1be7cf1fd8e1bd806b0cdd25ab8127 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 31 Aug 2026 14:23:16 -0700 Subject: [PATCH 031/134] raid6: add missing vzeroupper to AVX-512 code Since the AVX-512 optimized RAID6 code uses ZMM registers, execute vzeroupper before returning from it. This is needed to avoid degrading the performance of any later SSE code that may happen to be executed. Link: https://lore.kernel.org/20260831212316.213896-1-ebiggers@kernel.org Fixes: e0a491c12968 ("lib/raid6: Add AVX512 optimized gen_syndrome functions") Fixes: 13c520b2993c ("lib/raid6: Add AVX512 optimized recovery functions") Signed-off-by: Eric Biggers Signed-off-by: Andrew Morton Cc: Christoph Hellwig Cc: --- lib/raid/raid6/x86/avx512.c | 6 ++++++ lib/raid/raid6/x86/recov_avx512.c | 2 ++ 2 files changed, 8 insertions(+) diff --git a/lib/raid/raid6/x86/avx512.c b/lib/raid/raid6/x86/avx512.c index e671eb5bde63..772bfc4af6df 100644 --- a/lib/raid/raid6/x86/avx512.c +++ b/lib/raid/raid6/x86/avx512.c @@ -78,6 +78,7 @@ static void raid6_avx5121_gen_syndrome(int disks, size_t bytes, void **ptrs) } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -137,6 +138,7 @@ static void raid6_avx5121_xor_syndrome(int disks, int start, int stop, } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -208,6 +210,7 @@ static void raid6_avx5122_gen_syndrome(int disks, size_t bytes, void **ptrs) } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -292,6 +295,7 @@ static void raid6_avx5122_xor_syndrome(int disks, int start, int stop, } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -396,6 +400,7 @@ static void raid6_avx5124_gen_syndrome(int disks, size_t bytes, void **ptrs) } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -529,6 +534,7 @@ static void raid6_avx5124_xor_syndrome(int disks, int start, int stop, "m" (q[d+128]), "m" (q[d+192])); } asm volatile("sfence" : : : "memory"); + asm volatile("vzeroupper"); kernel_fpu_end(); } const struct raid6_calls raid6_avx512x4 = { diff --git a/lib/raid/raid6/x86/recov_avx512.c b/lib/raid/raid6/x86/recov_avx512.c index ec72d5a30c01..299a3f044d61 100644 --- a/lib/raid/raid6/x86/recov_avx512.c +++ b/lib/raid/raid6/x86/recov_avx512.c @@ -211,6 +211,7 @@ static void raid6_2data_recov_avx512(int disks, size_t bytes, int faila, #endif } + asm volatile("vzeroupper"); kernel_fpu_end(); } @@ -353,6 +354,7 @@ static void raid6_datap_recov_avx512(int disks, size_t bytes, int faila, #endif } + asm volatile("vzeroupper"); kernel_fpu_end(); } From aabe047f8b8055b66cf55a88c688c6660a3f6aca Mon Sep 17 00:00:00 2001 From: Hrushiraj Gandhi Date: Mon, 31 Aug 2026 20:19:53 +0530 Subject: [PATCH 032/134] gcov: use strscpy() instead of strcpy() in init_node() node->name is a flexible array member sized to exactly strlen(name) + 1 bytes at allocation time in new_node(), so this copy can never actually overflow. Still, prefer the bounded strscpy() over strcpy() on general principle; pass the same strlen(name) + 1 bound the allocation used, since sizeof() cannot be applied to a flexible array member. No functional change. Link: https://lore.kernel.org/20260831144953.324441-1-hrushirajg23@gmail.com Signed-off-by: Hrushiraj Gandhi Signed-off-by: Andrew Morton Reviewed-by: Bradley Morgan Cc: Peter Oberparleiter --- kernel/gcov/fs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/gcov/fs.c b/kernel/gcov/fs.c index 1d19b1be207a..764918570de1 100644 --- a/kernel/gcov/fs.c +++ b/kernel/gcov/fs.c @@ -529,7 +529,7 @@ static void init_node(struct gcov_node *node, struct gcov_info *info, } node->parent = parent; if (name) - strcpy(node->name, name); + strscpy(node->name, name, strlen(name) + 1); } /* From 97c358198e73c4d3feff561ebf813e84cfb1ef5a Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Mon, 31 Aug 2026 11:57:15 +0200 Subject: [PATCH 033/134] panic: introduce arch_do_panic Patch series "Introduce arch_do_panic", v6. Replace architecture-specific ifdef sections in vpanic() with a clean arch_do_panic() hook. Currently s390 and sparc embed their panic handlers directly in vpanic() using preprocessor conditionals, making the common code path harder to maintain. Introduce arch_do_panic() as an architecture extension point called at the end of vpanic(). Architectures can use this hook to implement their specific panic handling without polluting the generic panic code. Remove s390s ifdef block in vpanic() and move the corresponding code block to s390s own arch_do_panic() implementation in architecture specific code. Move sparc panic handling from ifdef blocks to arch_do_panic(). Remove the preprocessor conditionals from vpanic() and place the Stop-A enablement code in architecture-specific files where it belongs. Stop-A enablement markers are now printed after "end Kernel panic" line. To me, there are no better alternatives other than setup.c to put sparc's arch_do_panic() implementation. The other files under arch/sparc/kernel are either divided to *_32.c and *_64.c variants, which mean code duplication, or unrelated. The cleanup reduces vpanic() complexity and establishes a pattern for other architectures needing custom panic behavior. No functional changes, only minor print order changes. This patch (of 3): Introduce a hook for architectures to put their specific panic handlers. s390 and sparc already have ifdef preprocessor checks to execute architecture specific code. Pave the way for vpanic() cleanup. Link: https://lore.kernel.org/20260831-arch_do_panic-v6-1-a1e170a9e7fd@linux.ibm.com Link: https://lore.kernel.org/all/20260730-arch_do_panic-v3-0-d5401e683cdb@linux.ibm.com/ [1] Signed-off-by: Mete Durlu Signed-off-by: Andrew Morton Reviewed-by: Bradley Morgan Suggested-by: Sven Schnelle Reviewed-by: Andrew Morton Cc: Alexander Gordeev Cc: Andreas Larsson Cc: Christian Borntraeger Cc: David S. Miller Cc: Heiko Carstens Cc: Petr Mladek Cc: Vasily Gorbik --- include/linux/panic.h | 2 ++ kernel/panic.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/include/linux/panic.h b/include/linux/panic.h index f1dd417e54b2..98dd7dfd27de 100644 --- a/include/linux/panic.h +++ b/include/linux/panic.h @@ -110,4 +110,6 @@ extern void add_taint(unsigned flag, enum lockdep_ok); extern int test_taint(unsigned flag); extern unsigned long get_taint(void); +void arch_do_panic(void); + #endif /* _LINUX_PANIC_H */ diff --git a/kernel/panic.c b/kernel/panic.c index 213725b612aa..726a97842232 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -567,6 +567,8 @@ static void panic_other_cpus_shutdown(bool crash_kexec) crash_smp_send_stop(); } +void __weak arch_do_panic(void) {} + /** * vpanic - halt the system * @fmt: The text string to print @@ -756,6 +758,7 @@ void vpanic(const char *fmt, va_list args) #endif pr_emerg("---[ end Kernel panic - not syncing: %s ]---\n", buf); + arch_do_panic(); /* Do not scroll important messages printed above */ suppress_printk = 1; From ed26d6fcf5631330a06ad120d6e8a262efa65dde Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Mon, 31 Aug 2026 11:57:16 +0200 Subject: [PATCH 034/134] s390: implement arch_do_panic Implement s390 specific arch_do_panic() instead of using s390 specific ifdef sections in vpanic() code. disabled_wait() is now called after "end Kernel panic" marker. No functional changes. Link: https://lore.kernel.org/20260831-arch_do_panic-v6-2-a1e170a9e7fd@linux.ibm.com Signed-off-by: Mete Durlu Signed-off-by: Andrew Morton Acked-by: Heiko Carstens Reviewed-by: Bradley Morgan Reviewed-by: Andrew Morton Cc: Alexander Gordeev Cc: Andreas Larsson Cc: Christian Borntraeger Cc: David S. Miller Cc: Petr Mladek Cc: Sven Schnelle Cc: Vasily Gorbik --- arch/s390/kernel/traps.c | 7 +++++++ kernel/panic.c | 3 --- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c index b6ba4465f59d..115cb3373247 100644 --- a/arch/s390/kernel/traps.c +++ b/arch/s390/kernel/traps.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include "entry.h" struct pgm_stat { @@ -283,6 +285,11 @@ static void monitor_event_exception(struct pt_regs *regs) } } +void arch_do_panic(void) +{ + disabled_wait(); +} + void kernel_stack_invalid(struct pt_regs *regs) { /* diff --git a/kernel/panic.c b/kernel/panic.c index 726a97842232..ee6e3f9e3900 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -752,9 +752,6 @@ void vpanic(const char *fmt, va_list args) pr_emerg("Press Stop-A (L1-A) from sun keyboard or send break\n" "twice on console to return to the boot prom\n"); } -#endif -#if defined(CONFIG_S390) - disabled_wait(); #endif pr_emerg("---[ end Kernel panic - not syncing: %s ]---\n", buf); From 1d4408578567d1b3c7086be37986164982791f19 Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Mon, 31 Aug 2026 11:57:17 +0200 Subject: [PATCH 035/134] sparc: implement arch_do_panic Implement sparc specific arch_do_panic() instead of using sparc specific ifdef sections in vpanic() code. Reorder arch specific panic handling, sparc's Stop-A messages are now printed after "end Kernel panic" marker. Link: https://lore.kernel.org/20260831-arch_do_panic-v6-3-a1e170a9e7fd@linux.ibm.com Signed-off-by: Mete Durlu Signed-off-by: Andrew Morton Reviewed-by: Bradley Morgan Reviewed-by: Andrew Morton Cc: Alexander Gordeev Cc: Andreas Larsson Cc: Christian Borntraeger Cc: David S. Miller Cc: Heiko Carstens Cc: Petr Mladek Cc: Sven Schnelle Cc: Vasily Gorbik --- arch/sparc/kernel/setup.c | 9 +++++++++ kernel/panic.c | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/arch/sparc/kernel/setup.c b/arch/sparc/kernel/setup.c index 4975867d9001..5f43cef80638 100644 --- a/arch/sparc/kernel/setup.c +++ b/arch/sparc/kernel/setup.c @@ -2,6 +2,8 @@ #include #include +#include +#include static const struct ctl_table sparc_sysctl_table[] = { { @@ -36,6 +38,13 @@ static const struct ctl_table sparc_sysctl_table[] = { #endif }; +void arch_do_panic(void) +{ + /* Make sure the user can actually press Stop-A (L1-A) */ + stop_a_enabled = 1; + pr_emerg("Press Stop-A (L1-A) from sun keyboard or send break\n" + "twice on console to return to the boot prom\n"); +} static int __init init_sparc_sysctls(void) { diff --git a/kernel/panic.c b/kernel/panic.c index ee6e3f9e3900..7dda841c16f9 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -744,15 +744,6 @@ void vpanic(const char *fmt, va_list args) reboot_mode = panic_reboot_mode; emergency_restart(); } -#ifdef __sparc__ - { - extern int stop_a_enabled; - /* Make sure the user can actually press Stop-A (L1-A) */ - stop_a_enabled = 1; - pr_emerg("Press Stop-A (L1-A) from sun keyboard or send break\n" - "twice on console to return to the boot prom\n"); - } -#endif pr_emerg("---[ end Kernel panic - not syncing: %s ]---\n", buf); arch_do_panic(); From aa4b0525ffbe163ae1fc4c73b9ed68165f74c077 Mon Sep 17 00:00:00 2001 From: Ivy Lopez Date: Mon, 31 Aug 2026 19:41:38 -0600 Subject: [PATCH 036/134] lib: decompress_unxz: make it obvious that there is no memory leak Calling __decompress() or unxz() with fill == NULL && flush == NULL && in == NULL is invalid, thus there were no memory leaks even though it might have looked like that. Move the conditional free() calls so that it's obvious that there are no leaks. Link: https://lore.kernel.org/20260901014138.22699-1-skunkolee@gmail.com Link: https://lore.kernel.org/lkml/20241006072542.66442-2-t.v.s10123@gmail.com/T/ Link: https://lore.kernel.org/lkml/20260825191333.34276-1-skunkolee@gmail.com/T/ Signed-off-by: Ivy Lopez Signed-off-by: Andrew Morton Closes: https://bugzilla.kernel.org/show_bug.cgi?id=207113 Reviewed-by: Lasse Collin --- lib/decompress_unxz.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/decompress_unxz.c b/lib/decompress_unxz.c index 05d5cb490a44..9ccded9934c6 100644 --- a/lib/decompress_unxz.c +++ b/lib/decompress_unxz.c @@ -342,14 +342,14 @@ STATIC int INIT unxz(unsigned char *in, long in_size, b.out_pos = 0; } } while (ret == XZ_OK); - - if (must_free_in) - free(in); - - if (flush != NULL) - free(b.out); } + if (must_free_in) + free(in); + + if (flush != NULL) + free(b.out); + if (in_used != NULL) *in_used += b.in_pos; From 8cdae75cebe06b8abea9cd3402442d47cf50b485 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Mon, 31 Aug 2026 00:00:16 +0100 Subject: [PATCH 037/134] arch/Kconfig: fix dead conditions by removing dead options These two 'int' options: ARCH_MMAP_RND_BITS_DEFAULT ARCH_MMAP_RND_COMPAT_BITS_DEFAULT are used directly as conditions for defaults. 'int' options should not be used as conditions, because they will always evaluate to false. Let's remove these options, because they are not used anywhere else. This dead code was found by kconfirm, a static analysis tool for Kconfig. Link: https://lore.kernel.org/20260830230016.2730093-1-julianbraha@gmail.com Signed-off-by: Julian Braha Signed-off-by: Andrew Morton Reviewed-by: Arnd Bergmann Reviewed-by: Jinjie Ruan --- arch/Kconfig | 8 -------- 1 file changed, 8 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index 45c657772362..72890200d049 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -1238,13 +1238,9 @@ config ARCH_MMAP_RND_BITS_MIN config ARCH_MMAP_RND_BITS_MAX int -config ARCH_MMAP_RND_BITS_DEFAULT - int - config ARCH_MMAP_RND_BITS int "Number of bits to use for ASLR of mmap base address" if EXPERT range ARCH_MMAP_RND_BITS_MIN ARCH_MMAP_RND_BITS_MAX - default ARCH_MMAP_RND_BITS_DEFAULT if ARCH_MMAP_RND_BITS_DEFAULT default ARCH_MMAP_RND_BITS_MIN depends on HAVE_ARCH_MMAP_RND_BITS help @@ -1272,13 +1268,9 @@ config ARCH_MMAP_RND_COMPAT_BITS_MIN config ARCH_MMAP_RND_COMPAT_BITS_MAX int -config ARCH_MMAP_RND_COMPAT_BITS_DEFAULT - int - config ARCH_MMAP_RND_COMPAT_BITS int "Number of bits to use for ASLR of mmap base address for compatible applications" if EXPERT range ARCH_MMAP_RND_COMPAT_BITS_MIN ARCH_MMAP_RND_COMPAT_BITS_MAX - default ARCH_MMAP_RND_COMPAT_BITS_DEFAULT if ARCH_MMAP_RND_COMPAT_BITS_DEFAULT default ARCH_MMAP_RND_COMPAT_BITS_MIN depends on HAVE_ARCH_MMAP_RND_COMPAT_BITS help From 9decb604dc064099a6249150f0c4909525ed657c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 10 Jul 2026 14:39:57 +0200 Subject: [PATCH 038/134] fork: honor task_struct's declared alignment Since commit cb7ca40a3882 ("x86/fpu: Make task_struct::thread constant size"), struct task_struct is declared __attribute__((aligned(64))) on all architectures. But fork_init() still sets the task_struct slab cache's alignment to align = max(L1_CACHE_BYTES, ARCH_MIN_TASKALIGN) which is smaller than 64 on architectures whose cache lines are below 64 bytes: e.g. 32 on ARMv5. In practice plain SLUB happens to hand out 64-byte-aligned objects anyway. With CONFIG_SLUB_DEBUG_ON the red-zone padding shifts objects to the requested alignment. With CONFIG_UBSAN_ALIGNMENT=y a boot on QEMU versatilepb (ARM926EJ-S, v7.2-rc2, gcc 13.3) floods the console with reports like: UBSAN: misaligned-access in include/linux/sched.h:2087:9 member access within misaligned address c295d7e0 for type 'struct task_struct' which requires 64 byte alignment CPU: 0 UID: 0 PID: 15 Comm: pr/ttyAMA-1 Not tainted 7.2.0-rc2 #1 VOLUNTARY Set the slab alignment to at least the type's declared alignment. Link: https://lore.kernel.org/20260710123957.31774-1-kmehltretter@gmail.com Fixes: cb7ca40a3882 ("x86/fpu: Make task_struct::thread constant size") Signed-off-by: Karl Mehltretter Signed-off-by: Andrew Morton Reviewed-by: Bradley Morgan Assisted-by: Claude:claude-fable-5 Cc: Ingo Molnar Cc: Kees Cook Cc: Peter Zijlstra Cc: Vlastimil Babka Cc: --- kernel/fork.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/fork.c b/kernel/fork.c index 10f2d05d816a..4558150fd133 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -858,7 +858,8 @@ void __init fork_init(void) #ifndef ARCH_MIN_TASKALIGN #define ARCH_MIN_TASKALIGN 0 #endif - int align = max_t(int, L1_CACHE_BYTES, ARCH_MIN_TASKALIGN); + int align = max3(L1_CACHE_BYTES, ARCH_MIN_TASKALIGN, + __alignof__(struct task_struct)); unsigned long useroffset, usersize; /* create a slab on which task_structs can be allocated */ From cc2356e17ff2320ed9cd9970317fb2265a633d9e Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sat, 1 Aug 2026 22:58:44 +0000 Subject: [PATCH 039/134] taskstats: fold the two pid/tgid handlers into one cmd_attr_pid() and cmd_attr_tgid() are copy paste. fold them into one handler that takes the attr type and fill function as parameters, same pattern as the cpumask fold in 59c0bc949c5e. No functional change. Link: https://lore.kernel.org/20260801225845.23855-1-include@grrlz.net Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton Reviewed-by: Andrew Morton Cc: Balbir Singh Cc: Balbir Singh --- kernel/taskstats.c | 47 ++++++++++++---------------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/kernel/taskstats.c b/kernel/taskstats.c index f31df72f0e9d..5edd759a163e 100644 --- a/kernel/taskstats.c +++ b/kernel/taskstats.c @@ -473,7 +473,8 @@ static size_t taskstats_packet_size(void) return size; } -static int cmd_attr_pid(struct genl_info *info) +static int cmd_attr_pid_tgid(struct genl_info *info, int attr, + int (*fill)(pid_t, struct taskstats *)) { struct taskstats *stats; struct sk_buff *rep_skb; @@ -488,41 +489,15 @@ static int cmd_attr_pid(struct genl_info *info) return rc; rc = -EINVAL; - pid = nla_get_u32(info->attrs[TASKSTATS_CMD_ATTR_PID]); - stats = mk_reply(rep_skb, TASKSTATS_TYPE_PID, pid); + pid = nla_get_u32(info->attrs[attr]); + stats = mk_reply(rep_skb, + attr == TASKSTATS_CMD_ATTR_PID + ? TASKSTATS_TYPE_PID : TASKSTATS_TYPE_TGID, + pid); if (!stats) goto err; - rc = fill_stats_for_pid(pid, stats); - if (rc < 0) - goto err; - return send_reply(rep_skb, info); -err: - nlmsg_free(rep_skb); - return rc; -} - -static int cmd_attr_tgid(struct genl_info *info) -{ - struct taskstats *stats; - struct sk_buff *rep_skb; - size_t size; - u32 tgid; - int rc; - - size = taskstats_packet_size(); - - rc = prepare_reply(info, TASKSTATS_CMD_NEW, &rep_skb, size); - if (rc < 0) - return rc; - - rc = -EINVAL; - tgid = nla_get_u32(info->attrs[TASKSTATS_CMD_ATTR_TGID]); - stats = mk_reply(rep_skb, TASKSTATS_TYPE_TGID, tgid); - if (!stats) - goto err; - - rc = fill_stats_for_tgid(tgid, stats); + rc = fill(pid, stats); if (rc < 0) goto err; return send_reply(rep_skb, info); @@ -542,9 +517,11 @@ static int taskstats_user_cmd(struct sk_buff *skb, struct genl_info *info) TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK, DEREGISTER); else if (info->attrs[TASKSTATS_CMD_ATTR_PID]) - return cmd_attr_pid(info); + return cmd_attr_pid_tgid(info, TASKSTATS_CMD_ATTR_PID, + fill_stats_for_pid); else if (info->attrs[TASKSTATS_CMD_ATTR_TGID]) - return cmd_attr_tgid(info); + return cmd_attr_pid_tgid(info, TASKSTATS_CMD_ATTR_TGID, + fill_stats_for_tgid); else return -EINVAL; } From 25bf9e549360b656a6763232e0e9e58a00c3aff7 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 1 Sep 2026 20:52:18 +0800 Subject: [PATCH 040/134] ocfs2: restrict OCFS2_INVALID_SLOT suballoc slot to system inodes Patch series "ocfs2: validate suballoc slot and bit of metadata blocks", v3. The ocfs2 metadata validators trust the on-disk suballoc slot and bit without checking them against the slot range of the mounted filesystem and the capacity of the block group bitmap. A corrupted image can carry OCFS2_INVALID_SLOT or another out-of-range slot, or a suballoc bit beyond the bitmap, and once the corresponding inode, extent block, xattr block, dir index root or refcount block gets freed, the bad value goes straight into ocfs2_get_system_file_inode() or _ocfs2_free_suballoc_bits() and hits a BUG_ON() or runs off the end of local_system_inodes[]. This series rejects such values at read time, in the existing validators, so corrupted objects fail with -EROFS (and a read-only remount) instead of crashing: patch 1 restricts OCFS2_INVALID_SLOT dinodes to system inodes, completing fe7a283b3916 ("ocfs2: add suballoc slot check in ocfs2_validate_inode_block()"), and turns the "system file state is ambiguous" BUG_ON() in ocfs2_read_locked_inode() into an ocfs2_error(); patch 2 rejects oversized dinode suballoc bits; patch 3 validates the suballoc slot and bit of xattr and dir index blocks; patch 4 validates the suballoc slot and bit of extent and refcount blocks. The checks only enforce what the kernel and mkfs.ocfs2 already write: a valid slot from meta_ac->ac_alloc_slot and a bit within the block group bitmap, with system inodes carrying OCFS2_INVALID_SLOT plus OCFS2_SYSTEM_FL and extent blocks using slot 0. Nothing changes for healthy filesystems. Each new check was exercised under QEMU by corrupting the field in question with an out-of-range value; with the series applied the access fails with -EROFS and the filesystem remounts read-only instead of hitting the BUG_ON(). This patch (of 4): ocfs2_validate_inode_block() currently permits i_suballoc_slot to be OCFS2_INVALID_SLOT for any dinode. Only system inodes created by mkfs.ocfs2 are allocated from the global allocator and thus legitimately carry this value; regular inodes are always allocated from a per-slot suballocator and hence must have a valid slot. If a corrupted regular inode with OCFS2_INVALID_SLOT is accepted, ocfs2_remove_inode() will pass the slot to ocfs2_get_system_file_inode() and get_local_system_inode() will hit BUG_ON(slot == OCFS2_INVALID_SLOT) when the inode is deleted. This can be triggered by an unprivileged user unlinking such a corrupted file. Reject OCFS2_INVALID_SLOT for non-system dinodes during validation, while still accepting it for system inodes. Note that a crafted dinode carrying OCFS2_SYSTEM_FL passes the check above, yet a plain lookup of it still used to BUG() in ocfs2_read_locked_inode() ("system file state is ambiguous"). Since i_flags comes from disk, handle that mismatch with ocfs2_error() instead of BUG_ON() as well. Link: https://lore.kernel.org/20260901125221.1634686-1-joseph.qi@linux.alibaba.com Link: https://lore.kernel.org/20260901125221.1634686-2-joseph.qi@linux.alibaba.com Fixes: fe7a283b3916 ("ocfs2: add suballoc slot check in ocfs2_validate_inode_block()") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh Cc: --- fs/ocfs2/inode.c | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index 180107a11046..9228d6ef23c2 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -638,14 +638,18 @@ static int ocfs2_read_locked_inode(struct inode *inode, fe = (struct ocfs2_dinode *) bh->b_data; /* - * This is a code bug. Right now the caller needs to - * understand whether it is asking for a system file inode or - * not so the proper lock names can be built. + * The caller must know whether it is asking for a system file inode + * or not so the proper lock names can be built. Since i_flags comes + * from disk, a mismatch is filesystem corruption instead of a code + * bug, so handle it with ocfs2_error() rather than BUG_ON(). */ - mlog_bug_on_msg(!!(fe->i_flags & cpu_to_le32(OCFS2_SYSTEM_FL)) != - !!(args->fi_flags & OCFS2_FI_FLAG_SYSFILE), - "Inode %llu: system file state is ambiguous\n", - (unsigned long long)args->fi_blkno); + if (!!(fe->i_flags & cpu_to_le32(OCFS2_SYSTEM_FL)) != + !!(args->fi_flags & OCFS2_FI_FLAG_SYSFILE)) { + status = ocfs2_error(osb->sb, + "Inode %llu: system file state is ambiguous\n", + (unsigned long long)args->fi_blkno); + goto bail; + } if (S_ISCHR(le16_to_cpu(fe->i_mode)) || S_ISBLK(le16_to_cpu(fe->i_mode))) @@ -1520,8 +1524,23 @@ int ocfs2_validate_inode_block(struct super_block *sb, goto bail; } - if (le16_to_cpu(di->i_suballoc_slot) != (u16)OCFS2_INVALID_SLOT && - (u32)le16_to_cpu(di->i_suballoc_slot) > OCFS2_SB(sb)->max_slots - 1) { + /* + * Only system inodes created by mkfs.ocfs2 are allocated from the + * global allocator and thus legitimately carry OCFS2_INVALID_SLOT. + * Regular inodes are always allocated from a per-slot suballocator. + * If a regular inode with OCFS2_INVALID_SLOT was accepted here, + * deleting it would pass the slot to get_local_system_inode() via + * ocfs2_remove_inode() and trigger BUG_ON(slot == OCFS2_INVALID_SLOT). + */ + if (le16_to_cpu(di->i_suballoc_slot) == (u16)OCFS2_INVALID_SLOT) { + if (!(le32_to_cpu(di->i_flags) & OCFS2_SYSTEM_FL)) { + rc = ocfs2_error(sb, + "Invalid dinode %llu: suballoc slot %u for non-system inode\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(di->i_suballoc_slot)); + goto bail; + } + } else if ((u32)le16_to_cpu(di->i_suballoc_slot) > OCFS2_SB(sb)->max_slots - 1) { rc = ocfs2_error(sb, "Invalid dinode %llu: suballoc slot %u\n", (unsigned long long)bh->b_blocknr, le16_to_cpu(di->i_suballoc_slot)); From e706b89c6a83a52f077e7683e602d992309a5751 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 1 Sep 2026 20:52:19 +0800 Subject: [PATCH 041/134] ocfs2: validate suballoc bit during inode read i_suballoc_bit of a dinode is currently not validated at all. A corrupted dinode can carry an abnormally large i_suballoc_bit, which bypasses ocfs2_validate_inode_block(). When the inode is deleted, ocfs2_remove_inode() calls ocfs2_free_dinode(), which passes the unvalidated bit to _ocfs2_free_suballoc_bits() and triggers BUG_ON((count + start_bit) > ocfs2_bits_per_group(cl)). A suballocator block group bitmap is contained in a single block and starts after the group descriptor header, so a valid suballoc bit must be smaller than the number of bits fitting in the remaining space. Reject oversized i_suballoc_bit values during dinode validation. The bound is derived from ocfs2_group_bitmap_size() so it is also tight when discontig_bg caps the suballocator bitmap at OCFS2_MAX_BG_BITMAP_SIZE. Note the above check alone is not sufficient since the freeing path compares the bit against ocfs2_bits_per_group(), which is derived from cl_cpg/cl_bpc of the allocator dinode that is not validated against the actual group capacity and can be artificially smaller on a corrupted image. Convert this BUG_ON in _ocfs2_free_suballoc_bits() to ocfs2_error() as well. Link: https://lore.kernel.org/20260901125221.1634686-3-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh --- fs/ocfs2/inode.c | 16 ++++++++++++++++ fs/ocfs2/ocfs2.h | 12 ++++++++++++ fs/ocfs2/suballoc.c | 18 +++++++++++++++--- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index 9228d6ef23c2..92f3450010fb 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -1547,6 +1547,22 @@ int ocfs2_validate_inode_block(struct super_block *sb, goto bail; } + /* + * A suballocator block group bitmap is contained in a single block + * and starts after the group descriptor header, so a valid suballoc + * bit can never exceed ocfs2_suballoc_bits_per_block(). Otherwise + * deleting the inode will pass the oversized bit to + * _ocfs2_free_suballoc_bits() via ocfs2_free_dinode() and trigger + * BUG_ON((count + start_bit) > ocfs2_bits_per_group(cl)), since any + * group holds at most ocfs2_suballoc_bits_per_block() bits. + */ + if (le16_to_cpu(di->i_suballoc_bit) >= ocfs2_suballoc_bits_per_block(sb)) { + rc = ocfs2_error(sb, "Invalid dinode %llu: suballoc bit %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(di->i_suballoc_bit)); + goto bail; + } + if ((le32_to_cpu(di->i_flags) & OCFS2_ORPHANED_FL) && le16_to_cpu(di->i_orphaned_slot) >= OCFS2_SB(sb)->max_slots) { rc = ocfs2_error(sb, "Invalid dinode %llu: orphaned slot %u\n", diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h index b747cdec1787..e3bb3cc0b25a 100644 --- a/fs/ocfs2/ocfs2.h +++ b/fs/ocfs2/ocfs2.h @@ -593,6 +593,18 @@ static inline int ocfs2_supports_discontig_bg(struct ocfs2_super *osb) return 0; } +/* + * A suballocator block group bitmap starts right after the group + * descriptor header, so a suballoc bit can never exceed this number + * of bits. Derive it from ocfs2_group_bitmap_size() which also caps + * it at OCFS2_MAX_BG_BITMAP_SIZE when discontig_bg is enabled. + */ +static inline u32 ocfs2_suballoc_bits_per_block(struct super_block *sb) +{ + return ocfs2_group_bitmap_size(sb, 1, + OCFS2_SB(sb)->s_feature_incompat) * 8; +} + static inline unsigned int ocfs2_link_max(struct ocfs2_super *osb) { if (ocfs2_supports_indexed_dirs(osb)) diff --git a/fs/ocfs2/suballoc.c b/fs/ocfs2/suballoc.c index 453b56be9624..ce22d0c3d287 100644 --- a/fs/ocfs2/suballoc.c +++ b/fs/ocfs2/suballoc.c @@ -3040,10 +3040,22 @@ static int _ocfs2_free_suballoc_bits(handle_t *handle, /* The alloc_bh comes from ocfs2_free_dinode() or * ocfs2_free_clusters(). The callers have all locked the * allocator and gotten alloc_bh from the lock call. This - * validates the dinode buffer. Any corruption that has happened - * is a code bug. */ + * validates the dinode buffer. */ BUG_ON(!OCFS2_IS_VALID_DINODE(fe)); - BUG_ON((count + start_bit) > ocfs2_bits_per_group(cl)); + + /* + * ocfs2_bits_per_group() is derived from cl_cpg and cl_bpc of the + * allocator dinode, which are not validated against the volume + * geometry. A corrupted image can carry a suballoc bit beyond it, + * so error out instead of crashing. + */ + if ((count + start_bit) > ocfs2_bits_per_group(cl)) { + return ocfs2_error(alloc_inode->i_sb, + "Allocator #%llu: freeing bits %u+%u exceeds bits per group %u\n", + (unsigned long long)le64_to_cpu(fe->i_blkno), + count, start_bit, + ocfs2_bits_per_group(cl)); + } trace_ocfs2_free_suballoc_bits( (unsigned long long)OCFS2_I(alloc_inode)->ip_blkno, From 268fcde8576b96cffa5f57cbdae146d56d1a069d Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 1 Sep 2026 20:52:20 +0800 Subject: [PATCH 042/134] ocfs2: validate suballoc slot and bit of xattr and dir index blocks ocfs2_validate_xattr_block() and ocfs2_validate_dx_root() do not validate xb_suballoc_slot, xb_suballoc_bit, dr_suballoc_slot and dr_suballoc_bit at all. Since xattr blocks and dir index root blocks are allocated from a per-slot suballocator at runtime, their suballoc slots must be within range and their suballoc bits must fit in a block group bitmap. Otherwise a corrupted image can carry an out-of-range slot. When the xattr block or dir index is removed, ocfs2_xattr_block_remove() or ocfs2_dx_dir_remove_index() passes the unvalidated slot to ocfs2_get_system_file_inode() and get_local_system_inode() will either hit BUG_ON(slot == OCFS2_INVALID_SLOT) or compute an out-of-bounds index into the local_system_inodes array. Similarly an oversized suballoc bit will error out the filesystem in _ocfs2_free_suballoc_bits(). Furthermore ocfs2_validate_dx_root() does not verify dr_blkno against the physical block number like the extent and xattr block validators do, so a misplaced dir index root block can pass validation. Reject misplaced dir index root blocks, out-of-range suballoc slots and oversized suballoc bits during validation. Link: https://lore.kernel.org/20260901125221.1634686-4-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh --- fs/ocfs2/dir.c | 35 +++++++++++++++++++++++++++++++++++ fs/ocfs2/xattr.c | 25 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 0075e1624310..6bb6aa133f01 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -605,6 +605,41 @@ static int ocfs2_validate_dx_root(struct super_block *sb, goto bail; } + if (le64_to_cpu(dx_root->dr_blkno) != bh->b_blocknr) { + ret = ocfs2_error(sb, + "Dir Index Root # %llu has an invalid dr_blkno of %llu\n", + (unsigned long long)bh->b_blocknr, + (unsigned long long)le64_to_cpu(dx_root->dr_blkno)); + goto bail; + } + + /* + * Dir index root blocks are allocated from a per-slot suballocator, + * so the slot must be in range. Otherwise removing the index passes + * it to get_local_system_inode(), which hits BUG_ON() for + * OCFS2_INVALID_SLOT or computes an out-of-bounds index otherwise. + */ + if ((u32)le16_to_cpu(dx_root->dr_suballoc_slot) >= OCFS2_SB(sb)->max_slots) { + ret = ocfs2_error(sb, + "Dir Index Root # %llu has invalid dr_suballoc_slot %u\n", + (unsigned long long)le64_to_cpu(dx_root->dr_blkno), + le16_to_cpu(dx_root->dr_suballoc_slot)); + goto bail; + } + + /* + * Similarly the suballoc bit must fit in a block group bitmap. + * Otherwise removing the index will pass the oversized bit to + * _ocfs2_free_suballoc_bits() and trigger ocfs2_error() there. + */ + if (le16_to_cpu(dx_root->dr_suballoc_bit) >= ocfs2_suballoc_bits_per_block(sb)) { + ret = ocfs2_error(sb, + "Dir Index Root # %llu has invalid dr_suballoc_bit %u\n", + (unsigned long long)le64_to_cpu(dx_root->dr_blkno), + le16_to_cpu(dx_root->dr_suballoc_bit)); + goto bail; + } + if (!(dx_root->dr_flags & OCFS2_DX_FLAG_INLINE)) { struct ocfs2_extent_list *el = &dx_root->dr_list; diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 740d4bb3890f..e27f5f925df5 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -532,6 +532,31 @@ static int ocfs2_validate_xattr_block(struct super_block *sb, le32_to_cpu(xb->xb_fs_generation)); } + /* + * Xattr blocks are allocated from a per-slot suballocator, so the + * slot must be in range. Otherwise freeing the block passes it to + * get_local_system_inode(), which hits BUG_ON() for + * OCFS2_INVALID_SLOT or computes an out-of-bounds index otherwise. + */ + if ((u32)le16_to_cpu(xb->xb_suballoc_slot) >= OCFS2_SB(sb)->max_slots) { + return ocfs2_error(sb, + "Extended attribute block #%llu has an invalid xb_suballoc_slot of %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(xb->xb_suballoc_slot)); + } + + /* + * Similarly the suballoc bit must fit in a block group bitmap. + * Otherwise freeing the block will pass the oversized bit to + * _ocfs2_free_suballoc_bits() and trigger ocfs2_error() there. + */ + if (le16_to_cpu(xb->xb_suballoc_bit) >= ocfs2_suballoc_bits_per_block(sb)) { + return ocfs2_error(sb, + "Extended attribute block #%llu has an invalid xb_suballoc_bit of %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(xb->xb_suballoc_bit)); + } + if (!(le16_to_cpu(xb->xb_flags) & OCFS2_XATTR_INDEXED)) { size_t region_offset = offsetof(struct ocfs2_xattr_block, xb_attrs.xb_header); From 7c1abe66f86c2e6af14908152c938a9ecec689d4 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 1 Sep 2026 20:52:21 +0800 Subject: [PATCH 043/134] ocfs2: validate suballoc slot and bit of extent and refcount blocks ocfs2_validate_extent_block() and ocfs2_validate_refcount_block() do not validate h_suballoc_slot, h_suballoc_bit, rf_suballoc_slot and rf_suballoc_bit at all. Since extent blocks and refcount blocks are allocated from a per-slot suballocator at runtime, their suballoc slots must be within range and their suballoc bits must fit in a block group bitmap. Otherwise a corrupted image can carry an out-of-range slot. When the extent block is freed, ocfs2_cache_extent_block_free() caches it and ocfs2_free_cached_blocks() later passes the unvalidated slot to ocfs2_get_system_file_inode(); when the refcount block is freed, ocfs2_remove_refcount_extent() passes it via ocfs2_cache_block_dealloc(). get_local_system_inode() will then either hit BUG_ON(slot == OCFS2_INVALID_SLOT) or compute an out-of-bounds index into the local_system_inodes array. Similarly an oversized suballoc bit will error out the filesystem in _ocfs2_free_suballoc_bits(). Furthermore group descriptor validation only guarantees bg_bits within the physical bitmap size, so a corrupted image can still carry a suballoc bit beyond bg_bits, which would let ocfs2_block_group_clear_bits() clear bits beyond bg_bitmap. Convert the remaining BUG_ON against group->bg_bits in _ocfs2_free_suballoc_bits() to ocfs2_error() as well. Reject out-of-range suballoc slots and oversized suballoc bits during validation. Link: https://lore.kernel.org/20260901125221.1634686-5-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao --- fs/ocfs2/alloc.c | 27 +++++++++++++++++++++++++++ fs/ocfs2/refcounttree.c | 27 +++++++++++++++++++++++++++ fs/ocfs2/suballoc.c | 15 ++++++++++++++- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/alloc.c b/fs/ocfs2/alloc.c index be09e766ac1f..2fdc5403b10b 100644 --- a/fs/ocfs2/alloc.c +++ b/fs/ocfs2/alloc.c @@ -925,6 +925,33 @@ static int ocfs2_validate_extent_block(struct super_block *sb, goto bail; } + /* + * Extent blocks are allocated from a per-slot suballocator, so the + * slot must be in range. Otherwise freeing the block passes it to + * get_local_system_inode(), which hits BUG_ON() for + * OCFS2_INVALID_SLOT or computes an out-of-bounds index otherwise. + */ + if ((u32)le16_to_cpu(eb->h_suballoc_slot) >= OCFS2_SB(sb)->max_slots) { + rc = ocfs2_error(sb, + "Extent block #%llu has an invalid h_suballoc_slot of %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(eb->h_suballoc_slot)); + goto bail; + } + + /* + * Similarly the suballoc bit must fit in a block group bitmap. + * Otherwise freeing the block will pass the oversized bit to + * _ocfs2_free_suballoc_bits() and trigger ocfs2_error() there. + */ + if (le16_to_cpu(eb->h_suballoc_bit) >= ocfs2_suballoc_bits_per_block(sb)) { + rc = ocfs2_error(sb, + "Extent block #%llu has an invalid h_suballoc_bit of %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(eb->h_suballoc_bit)); + goto bail; + } + if (le16_to_cpu(eb->h_list.l_count) != ocfs2_extent_recs_per_eb(sb)) { rc = ocfs2_error(sb, "Extent block #%llu has invalid l_count %u (expected %u)\n", diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c index d9f22b4a2654..3e9cccf06e48 100644 --- a/fs/ocfs2/refcounttree.c +++ b/fs/ocfs2/refcounttree.c @@ -117,6 +117,33 @@ static int ocfs2_validate_refcount_block(struct super_block *sb, goto out; } + /* + * Refcount blocks are allocated from a per-slot suballocator, so the + * slot must be in range. Otherwise freeing the block passes it to + * get_local_system_inode(), which hits BUG_ON() for + * OCFS2_INVALID_SLOT or computes an out-of-bounds index otherwise. + */ + if ((u32)le16_to_cpu(rb->rf_suballoc_slot) >= OCFS2_SB(sb)->max_slots) { + rc = ocfs2_error(sb, + "Refcount block #%llu has an invalid rf_suballoc_slot of %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(rb->rf_suballoc_slot)); + goto out; + } + + /* + * Similarly the suballoc bit must fit in a block group bitmap. + * Otherwise freeing the block will pass the oversized bit to + * _ocfs2_free_suballoc_bits() and trigger ocfs2_error() there. + */ + if (le16_to_cpu(rb->rf_suballoc_bit) >= ocfs2_suballoc_bits_per_block(sb)) { + rc = ocfs2_error(sb, + "Refcount block #%llu has an invalid rf_suballoc_bit of %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(rb->rf_suballoc_bit)); + goto out; + } + /* * rf_records (rl_count/rl_used/rl_recs[]) is only meaningful when * this block is not an interior tree block (OCFS2_REFCOUNT_TREE_FL); diff --git a/fs/ocfs2/suballoc.c b/fs/ocfs2/suballoc.c index ce22d0c3d287..624152e4f7fe 100644 --- a/fs/ocfs2/suballoc.c +++ b/fs/ocfs2/suballoc.c @@ -3070,7 +3070,20 @@ static int _ocfs2_free_suballoc_bits(handle_t *handle, } group = (struct ocfs2_group_desc *) group_bh->b_data; - BUG_ON((count + start_bit) > le16_to_cpu(group->bg_bits)); + /* + * Group descriptor validation only guarantees bg_bits within the + * physical bitmap size, so double check the freeing range here. + * Otherwise ocfs2_block_group_clear_bits() would clear bits beyond + * bg_bitmap. + */ + if ((count + start_bit) > le16_to_cpu(group->bg_bits)) { + status = ocfs2_error(alloc_inode->i_sb, + "Group descriptor #%llu has %u bits, cannot free bits %u+%u\n", + (unsigned long long)le64_to_cpu(group->bg_blkno), + le16_to_cpu(group->bg_bits), + count, start_bit); + goto bail; + } if (ocfs2_is_cluster_bitmap(alloc_inode)) old_bg_contig_free_bits = group->bg_contig_free_bits; From 033abd1e9bf99f3004ed1932d4880c3c941c3834 Mon Sep 17 00:00:00 2001 From: Feng Tang Date: Wed, 2 Sep 2026 19:48:51 +0800 Subject: [PATCH 044/134] panic: remove the unneeded panic_print_get() panic_print_get() was introduced in commit 2683df6539cb ("panic: add note that 'panic_print' parameter is deprecated") to print out warning message of the deprecation of 'panic_print' on read access. Since commit 90f3c123247e ("panic: only warn about deprecated panic_print on write access"), panic_print_get() wrapper is not needed anymore for read access, so remove it and use param_get_ulong() instead. Link: https://lore.kernel.org/20260902114851.77062-1-feng.tang@linux.alibaba.com Signed-off-by: Feng Tang Signed-off-by: Andrew Morton Reviewed-by: Bradley Morgan Reviewed-by: Andrew Morton Reviewed-by: Petr Mladek --- kernel/panic.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/kernel/panic.c b/kernel/panic.c index 7dda841c16f9..50715f14cf04 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -1216,14 +1216,9 @@ static int panic_print_set(const char *val, const struct kernel_param *kp) return param_set_ulong(val, kp); } -static int panic_print_get(char *val, const struct kernel_param *kp) -{ - return param_get_ulong(val, kp); -} - static const struct kernel_param_ops panic_print_ops = { .set = panic_print_set, - .get = panic_print_get, + .get = param_get_ulong, }; __core_param_cb(panic_print, &panic_print_ops, &panic_print, 0644); From 4975fb231fcffcd3095b9993e015f5b59b1a3c53 Mon Sep 17 00:00:00 2001 From: Nick Desaulniers Date: Wed, 2 Sep 2026 14:01:53 -0700 Subject: [PATCH 045/134] scripts/checkstack.pl: support llvm-objdump disassembly for x86 When running `make LLVM=1 checkstack`, OBJDUMP is set to llvm-objdump. llvm-objdump outputs disassembly with instruction size suffixes (such as subq/addq/subl/addl), tabs/whitespace differences, spaces after commas, and trailing comments (e.g. `# imm = 0x...`). Because scripts/checkstack.pl used rigid regular expressions specifically tuned to GNU objdump format (e.g. requiring exactly four spaces, no suffix, and no space after comma), checkstack.pl failed to match any stack adjustment instructions and yielded no output when using llvm-objdump on x86. Update the regular expressions for x86 to match optional suffixes, variable whitespace, and trailing comments. Link: https://lore.kernel.org/20260902-checkstack_llvm_objdump-v1-1-edb4eca5f163@google.com Signed-off-by: Nick Desaulniers Signed-off-by: Andrew Morton Assisted-by: LLM Gemini Cc: Bill Wendling Cc: Justin Stitt Cc: Nathan Chancellor --- scripts/checkstack.pl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/checkstack.pl b/scripts/checkstack.pl index 14ce31f732ee..f8011e789a9b 100755 --- a/scripts/checkstack.pl +++ b/scripts/checkstack.pl @@ -66,8 +66,10 @@ my (@stack, $re, $dre, $sub, $x, $xs, $funcre, $min_stack); #c0105234: 81 ec ac 05 00 00 sub $0x5ac,%esp # or # 2f60: 48 81 ec e8 05 00 00 sub $0x5e8,%rsp - $re = qr/^.*[as][du][db] \$(0x$x{1,8}),\%(e|r)sp$/o; - $dre = qr/^.*[as][du][db] (%.*),\%(e|r)sp$/o; + # or + # 0: 48 81 ec e8 05 00 00 subq $0x5e8, %rsp + $re = qr/^.*[as][du][db][ql]?\s+\$(0x$x{1,8}),\s*\%(e|r)sp/o; + $dre = qr/^.*[as][du][db][ql]?\s+(%.*),\s*\%(e|r)sp/o; } elsif ($arch eq 'm68k') { # 2b6c: 4e56 fb70 linkw %fp,#-1168 # 1df770: defc ffe4 addaw #-28,%sp From 78cce2082d22d3c56bd3a6146ef0dcf7cd661a33 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Thu, 3 Sep 2026 21:13:12 +0800 Subject: [PATCH 046/134] ocfs2: allow xattr bucket entries to span multiple blocks Patch series "ocfs2: xattr bucket validation fixes", v2. This series fixes two problems around xattr bucket validation. Patch 1 fixes a false-corruption failure on blocksize-512 volumes: the bucket validator limited the entry array to the first bucket block while the write path stores entries across the whole 4096-byte bucket region, so a legitimately written, fsck-clean bucket could be rejected and force the filesystem read-only. It also adds an alignment check on the bucket block number, since the entry array is accessed as one contiguous region and a corrupted xattr tree could otherwise point a bucket at blocks straddling a page boundary. Patch 2 converts two mlog_bug_on_msg() checks in the bucket defrag path to ocfs2_error() returns, so that a corrupt bucket holding overlapping entries or an inflated xh_free_start marks the filesystem read-only and fails the setxattr instead of panicking the kernel. Both patches have been tested in QEMU: the blocksize-512 reproducer (40 xattrs with 100-byte values, previously failing with "entry count 32 exceeds maximum 31") now passes with a clean fsck.ocfs2 result, and the ocfs2 testsuite xattr tests pass 48/48 across blocksize combinations. This patch (of 2): ocfs2_validate_xattr_bucket() limits the entry array to the first bucket block, but the write path stores entries across the whole OCFS2_XATTR_BUCKET_SIZE region. With 512-byte blocks a bucket spans eight blocks, and a bucket filled with small xattrs places its last entries past offset 512. Reading such a bucket back errors out: OCFS2: ERROR (device loop0): ocfs2_validate_xattr_bucket: Invalid xattr bucket 86072: entry count 32 exceeds maximum 31 On-disk corruption discovered. Please run fsck.ocfs2 once the filesystem is unmounted. OCFS2: File system is now read-only. This is reproducible by setting ~33 xattrs with 100-byte values on a file on a blocksize-512 volume; fsck.ocfs2 reports the resulting image clean. Check the entry count against the full bucket region instead. The per-block bounds checks for names and values stay as they are, since ocfs2_bucket_align_free_start() keeps each name+value pair within a single block. The entry array is one contiguous region, so a bucket from a corrupted xattr tree whose first block is not aligned to OCFS2_XATTR_BUCKET_SIZE could straddle a page and make the validation loop read out of bounds. Buckets allocated within clusters are always aligned, so reject any other block number while validating. Link: https://lore.kernel.org/20260903131313.2396208-1-joseph.qi@linux.alibaba.com Link: https://lore.kernel.org/20260903131313.2396208-2-joseph.qi@linux.alibaba.com Fixes: 2cf82b46d5e4 ("ocfs2: validate external xattr entries when reading metadata") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao --- fs/ocfs2/xattr.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index e27f5f925df5..5f65ae6ebb92 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -1150,11 +1150,30 @@ static int ocfs2_validate_xattr_bucket(struct ocfs2_xattr_bucket *bucket, struct ocfs2_xattr_header *xh = bucket_xh(bucket); u16 xattr_count = le16_to_cpu(xh->xh_count); size_t region_size = (size_t)sb->s_blocksize * bucket->bu_blocks; - size_t entries_limit = sb->s_blocksize; + /* + * The entry array grows up from the header across the whole + * bucket region, so it may extend beyond the first bucket block + * when the blocksize is smaller than OCFS2_XATTR_BUCKET_SIZE. + * Name/value pairs, however, always live within a single block. + */ + size_t entries_limit = region_size; size_t nv_limit = sb->s_blocksize; size_t max_entries; int i, ret; + /* + * The entry array is one contiguous region that may span the + * bucket's buffer_heads. Buckets are allocated within clusters, + * so their first block is always aligned to + * OCFS2_XATTR_BUCKET_SIZE and the whole bucket fits in one page. + * A corrupted xattr tree can point a bucket at blocks straddling + * a page, so reject it before touching the entry array. + */ + if (blkno & (bucket->bu_blocks - 1)) + return ocfs2_error(sb, + "Invalid xattr bucket %llu: unaligned block number\n", + (unsigned long long)blkno); + if (region_size < sizeof(*xh)) return ocfs2_error(sb, "Invalid xattr bucket %llu: region size %zu is too small\n", From bd40b997f04542869afeabe94f2cf1a016fc9bfa Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Thu, 3 Sep 2026 21:13:13 +0800 Subject: [PATCH 047/134] ocfs2: reject inconsistent xattr bucket during defrag ocfs2_defrag_xattr_bucket() has two mlog_bug_on_msg() checks that assume the name/value pairs in a bucket are disjoint and that xh_free_start is not below the compacted region. ocfs2_validate_xattr_bucket() only checks each entry in isolation, so a corrupt bucket holding overlapping entries, or one with an inflated xh_free_start, passes validation and then hits BUG() in defrag when a setxattr triggers it. Defrag works on a linear copy of the bucket and does not touch the real blocks before the copy back, so the checks can return an error instead of calling BUG(). Link: https://lore.kernel.org/20260903131313.2396208-3-joseph.qi@linux.alibaba.com Fixes: 012255961c9e ("ocfs2: Enable xattr set in index btree") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh --- fs/ocfs2/xattr.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 5f65ae6ebb92..a2d6c3d0f2e8 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -4801,16 +4801,22 @@ static int ocfs2_defrag_xattr_bucket(struct inode *inode, memmove(bucket_buf + end - len, bucket_buf + offset, len); xe->xe_name_offset = cpu_to_le16(end - len); + } else if (end < offset + len) { + ret = ocfs2_error(inode->i_sb, + "Defrag check failed for bucket %llu\n", + (unsigned long long)blkno); + goto out; } - mlog_bug_on_msg(end < offset + len, "Defrag check failed for " - "bucket %llu\n", (unsigned long long)blkno); - end -= len; } - mlog_bug_on_msg(xh_free_start > end, "Defrag check failed for " - "bucket %llu\n", (unsigned long long)blkno); + if (xh_free_start > end) { + ret = ocfs2_error(inode->i_sb, + "Defrag check failed for bucket %llu\n", + (unsigned long long)blkno); + goto out; + } if (xh_free_start == end) goto out; From ec5e024c86c28774e2fe935a6baead00c6d4e9b1 Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Wed, 2 Sep 2026 13:01:15 -0400 Subject: [PATCH 048/134] fat: calculate data area start without overflow On 32-bit architectures, sbi->fat_length, sbi->dir_start and sbi->data_start are unsigned long. The number of FATs is an 8-bit BPB field, while the FAT32 length is a 32-bit BPB field. Therefore, the calculation sbi->fat_start + sbi->fats * sbi->fat_length can wrap before data_start is checked against total_sectors. For example, with fat_start=32, fats=2 and fat_length=0x80000001, the unwrapped data area start is 0x100000022 (4294967330), but the calculation wraps to 34 on i386. With total_sectors=36, the validation then incorrectly passes. The following script creates an image that demonstrates the problem: python3 - <<'PY' import struct S = 512 b = bytearray(36 * S) def p(off, fmt, value): struct.pack_into(fmt, b, off, value) # FAT32 BPB b[0:3] = b'\xeb\x58\x90' b[3:11] = b'MSWIN4.1' p(11, ' Signed-off-by: Andrew Morton Acked-by: OGAWA Hirofumi --- fs/fat/inode.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/fs/fat/inode.c b/fs/fat/inode.c index f775a004cae1..0b0bbe777842 100644 --- a/fs/fat/inode.c +++ b/fs/fat/inode.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -1577,6 +1578,7 @@ int fat_fill_super(struct super_block *sb, struct fs_context *fc, struct msdos_sb_info *sbi; u16 logical_sector_size; u32 total_sectors, total_clusters, fat_clusters, rootdir_sectors; + u32 dir_start, data_start; long error; char buf[50]; struct timespec64 ts; @@ -1752,7 +1754,6 @@ int fat_fill_super(struct super_block *sb, struct fs_context *fc, sbi->dir_per_block = sb->s_blocksize / sizeof(struct msdos_dir_entry); sbi->dir_per_block_bits = ffs(sbi->dir_per_block) - 1; - sbi->dir_start = sbi->fat_start + sbi->fats * sbi->fat_length; sbi->dir_entries = bpb.fat_dir_entries; if (sbi->dir_entries & (sbi->dir_per_block - 1)) { if (!silent) @@ -1763,20 +1764,30 @@ int fat_fill_super(struct super_block *sb, struct fs_context *fc, rootdir_sectors = sbi->dir_entries * sizeof(struct msdos_dir_entry) / sb->s_blocksize; - sbi->data_start = sbi->dir_start + rootdir_sectors; + if (check_mul_overflow(sbi->fats, sbi->fat_length, &dir_start) || + check_add_overflow(sbi->fat_start, dir_start, &dir_start) || + check_add_overflow(dir_start, rootdir_sectors, &data_start)) { + if (!silent) + fat_msg(sb, KERN_ERR, + "overflow of root dir or data layout"); + goto out_invalid; + } + total_sectors = bpb.fat_sectors; if (total_sectors == 0) total_sectors = bpb.fat_total_sect; - if (total_sectors < sbi->data_start) { + if (total_sectors < data_start) { if (!silent) fat_msg(sb, KERN_ERR, - "data area starts beyond volume (%lu > %u)", - sbi->data_start, total_sectors); + "data area starts beyond volume (%u > %u)", + data_start, total_sectors); goto out_invalid; } - total_clusters = (total_sectors - sbi->data_start) / sbi->sec_per_clus; + sbi->dir_start = dir_start; + sbi->data_start = data_start; + total_clusters = (total_sectors - data_start) / sbi->sec_per_clus; if (!is_fat32(sbi)) sbi->fat_bits = (total_clusters > MAX_FAT12) ? 16 : 12; From 6a3ed6b290513859bd27d9fcb67739347d6315fa Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Thu, 3 Sep 2026 18:47:44 +0800 Subject: [PATCH 049/134] ocfs2: skip uninitialized lockres in ocfs2_mark_lockres_freeing() A hard readonly mount skips ocfs2_dlm_init(), so the per-osb lock resources are never initialized and osb->cconn stays NULL. Before commit 550842cc60987 ("ocfs2: fix freeing uninitialized resource on ocfs2_dlm_shutdown") ocfs2_dismount_volume() only called ocfs2_dlm_shutdown() when osb->cconn was set. It now calls it unconditionally, so unmounting a hard readonly mount drops the osb locks and takes the never initialized l_lock in ocfs2_mark_lockres_freeing(). With lockdep enabled this triggers: INFO: trying to register non-static key. The code is fine but needs lockdep annotation, or maybe you didn't initialize this object before use? turning off the locking correctness validator. ocfs2_drop_lock() and ocfs2_lock_res_free() already skip lock resources without OCFS2_LOCK_INITIALIZED. Add the same check to ocfs2_mark_lockres_freeing(), which is reachable before them through ocfs2_simple_drop_lockres(), so an uninitialized lockres is never touched. Link: https://lore.kernel.org/20260903104744.2164235-1-joseph.qi@linux.alibaba.com Fixes: 550842cc6098 ("ocfs2: fix freeing uninitialized resource on ocfs2_dlm_shutdown") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reported-by: ZW Tang Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: --- fs/ocfs2/dlmglue.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/ocfs2/dlmglue.c b/fs/ocfs2/dlmglue.c index a23dd8f86c89..cf3318b0d3a8 100644 --- a/fs/ocfs2/dlmglue.c +++ b/fs/ocfs2/dlmglue.c @@ -3525,6 +3525,10 @@ void ocfs2_mark_lockres_freeing(struct ocfs2_super *osb, struct ocfs2_mask_waiter mw; unsigned long flags, flags2; + /* We didn't get anywhere near actually using this lockres. */ + if (!(lockres->l_flags & OCFS2_LOCK_INITIALIZED)) + return; + ocfs2_init_mask_waiter(&mw); spin_lock_irqsave(&lockres->l_lock, flags); From 43c91b1522435429a63d175f5a953e6f727a4f88 Mon Sep 17 00:00:00 2001 From: Adam Harshbarger Date: Thu, 3 Sep 2026 17:24:56 -0500 Subject: [PATCH 050/134] lib/plist: fix plist_requeue() corrupting order in the last bucket plist_requeue() is meant to move a node to the end of its own priority run. When the node heads the *last* priority bucket it is instead placed at the head of the whole list, leaving the plist unsorted: built: A(prio 0) B(prio 1) C(prio 1) requeue(B): B(prio 1) A(prio 0) C(prio 1) expected: A(prio 0) C(prio 1) B(prio 1) prio_list is a *headless* circular ring of the nodes that lead each priority bucket. The shortcut added by commit 95d4b3450ebe ("lib/plist.c: add shortcut for plist_requeue()") takes iter = list_entry(iter->prio_list.next, struct plist_node, prio_list); node_next = &iter->node_list; which from the last bucket wraps round to the *first* bucket, so node_next ends up pointing at the head of the list rather than at its end. The plist_for_each_continue() loop immediately below it computes the correct answer (&head->node_list) for that case. With any bucket after it the shortcut is correct, which is why this went unnoticed: the benchmark in that commit measured elapsed time and never checked the resulting order. Keep the shortcut -- it is a real win -- but exclude the case where iter's bucket is the last one, which is exactly when its ring successor is the first bucket again. Reachable from mm/swapfile.c, which rotates swap_avail_heads[] with plist_requeue(). It takes three or more swap devices: at least two distinct priorities, so that a later bucket exists for the ring to wrap round from, and two or more devices sharing the lowest priority, so that plist_requeue() does not return early. One device per priority returns early at the node->prio != iter->prio test. A single priority is also safe, but for a different reason worth stating: with one bucket no node is ever linked onto prio_list at all -- plist_add() skips it for the first node and for every node whose predecessor shares its priority -- so list_empty(&iter->prio_list) holds and the shortcut is never entered. Tested by driving three implementations -- the pre-95d4b3450ebe code, current mainline, and this patch -- through 1,084,492 identical random add/del/requeue operations over 24 nodes and 1..5 distinct priorities, comparing the resulting node_list node for node after every operation: variant differs from pre-95d4b3450ebe left list unsorted pre-95d4b3450ebe -- (reference) 0 mainline 289,297 276,660 this patch 0 0 Link: https://lore.kernel.org/20260903222456.1881786-1-handyhandyman.adam@gmail.com Fixes: 95d4b3450ebe ("lib/plist.c: add shortcut for plist_requeue()") Signed-off-by: Adam Harshbarger Signed-off-by: Andrew Morton Reviewed-by: Nickolai Zeldovich Assisted-by: Claude:claude-opus-5 Cc: I Hsin Cheng Cc: # v6.15+ --- lib/plist.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/plist.c b/lib/plist.c index a5bef38add43..b0273533c04d 100644 --- a/lib/plist.c +++ b/lib/plist.c @@ -174,8 +174,15 @@ void plist_requeue(struct plist_node *node, struct plist_head *head) /* * After plist_del(), iter is the replacement of the node. If the node * was on prio_list, take shortcut to find node_next instead of looping. + * + * prio_list is a headless ring, so from the LAST bucket ->next wraps + * round to the first one; in that case node_next is the list head. */ if (!list_empty(&iter->prio_list)) { + struct plist_node *first = plist_first(head); + + if (iter->prio_list.next == &first->prio_list) + goto queue; iter = list_entry(iter->prio_list.next, struct plist_node, prio_list); node_next = &iter->node_list; From 8b2e216d1ef7a7fb2801c52fffebad8b984c8f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Ahmet=20Memi=C5=9F?= Date: Sat, 5 Sep 2026 22:49:39 +0300 Subject: [PATCH 051/134] =?UTF-8?q?mailmap:=20update=20email=20address=20f?= =?UTF-8?q?or=20Ali=20Ahmet=20Memi=C5=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'm switching to my disroot address for kernel contributions. Map the address my earlier patches were sent from to it, so that get_maintainer.pl stops offering the old one as a recipient. Also switch to the proper spelling of my name with diacritics, which is what I use with the new address. Link: https://lore.kernel.org/20260905195000.556185-1-aliamemis@disroot.org Signed-off-by: Ali Ahmet MemiÅŸ Signed-off-by: Andrew Morton --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 0494c22a3bae..1bcc2dcd28ee 100644 --- a/.mailmap +++ b/.mailmap @@ -67,6 +67,7 @@ Alex Hung Alex Shi Alex Shi Alex Shi +Ali Ahmet MemiÅŸ Alice Mikityanska Alice Mikityanska Alice Mikityanska From 6abf404bdb033135d25616113cb9b56bbc7430ee Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Sat, 5 Sep 2026 22:21:43 +0800 Subject: [PATCH 052/134] ocfs2: validate dr_fs_generation of dir index root blocks ocfs2_validate_dx_root() does not verify dr_fs_generation against the superblock generation, unlike the extent and xattr block validators which check h_fs_generation and xb_fs_generation respectively. The field is documented as "Must match super block". Without the check, a stale dir index root block left on the device from a previously formatted filesystem at the same physical block number can pass validation as long as its signature, dr_blkno and checksum match. Its index entries and suballocator information would then be used in the new filesystem context. Reject dir index root blocks whose dr_fs_generation does not match the mounted filesystem, like the extent and xattr block validators do. Link: https://lore.kernel.org/20260905142144.2869105-1-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Cc: Changwei Ge Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh --- fs/ocfs2/dir.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 6bb6aa133f01..329680b46227 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -613,6 +613,14 @@ static int ocfs2_validate_dx_root(struct super_block *sb, goto bail; } + if (le32_to_cpu(dx_root->dr_fs_generation) != OCFS2_SB(sb)->fs_generation) { + ret = ocfs2_error(sb, + "Dir Index Root # %llu has an invalid dr_fs_generation of #%u\n", + (unsigned long long)bh->b_blocknr, + le32_to_cpu(dx_root->dr_fs_generation)); + goto bail; + } + /* * Dir index root blocks are allocated from a per-slot suballocator, * so the slot must be in range. Otherwise removing the index passes From 93091c32a78df570540d707a51adeb87fb8de7bc Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Sat, 5 Sep 2026 22:21:44 +0800 Subject: [PATCH 053/134] ocfs2: validate dl_blkno and dl_fs_generation of dir index leaf blocks ocfs2_validate_dx_leaf() checks the checksum, the signature and the entry list counts, but it never checks dl_blkno or dl_fs_generation. The inode, extent block, xattr block, refcount block and dir index root validators all check the on-disk block number against bh->b_blocknr and the generation against the superblock, and both dir index leaf fields are documented as "Must match super block". Without the checks, a stale dir index leaf block left on the device from a previously formatted filesystem at the same physical block number can pass validation as long as its signature, entry counts and checksum match. Its index entries would then be used in the new filesystem context. Both fields are written unconditionally when a leaf block is formatted in ocfs2_dx_dir_format_cluster(), from the live superblock generation and the real block number, so a correctly formatted filesystem cannot trip the new checks. The leaf block number read back here comes from on-disk dir index root extent records. Reject dir index leaf blocks whose dl_blkno or dl_fs_generation does not match, like the dir index root validator does. Link: https://lore.kernel.org/20260905142144.2869105-2-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao --- fs/ocfs2/dir.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 329680b46227..55c4a305a282 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -733,6 +733,18 @@ static int ocfs2_validate_dx_leaf(struct super_block *sb, return ocfs2_error(sb, "Dir Index Leaf has bad signature %.*s\n", 7, dx_leaf->dl_signature); + if (le64_to_cpu(dx_leaf->dl_blkno) != bh->b_blocknr) + return ocfs2_error(sb, + "Dir Index Leaf # %llu has an invalid dl_blkno of %llu\n", + (unsigned long long)bh->b_blocknr, + (unsigned long long)le64_to_cpu(dx_leaf->dl_blkno)); + + if (le32_to_cpu(dx_leaf->dl_fs_generation) != OCFS2_SB(sb)->fs_generation) + return ocfs2_error(sb, + "Dir Index Leaf # %llu has an invalid dl_fs_generation of #%u\n", + (unsigned long long)bh->b_blocknr, + le32_to_cpu(dx_leaf->dl_fs_generation)); + if (le16_to_cpu(dx_leaf->dl_list.de_count) != ocfs2_dx_entries_per_leaf(sb)) return ocfs2_error(sb, From 15c6173efb9238795e0632b081912c060701a553 Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Fri, 4 Sep 2026 13:02:27 +0200 Subject: [PATCH 054/134] checkpatch: add more userspace directories to is_userspace() Patch series "checkpatch: userspace improvements", v6. Few improvements for user space code + --userspace option for projects which vendored checkpatch.pl. There could be probably more checks which are kernel space only. This patch (of 4): arch/ directory contains subdirectories with userspace tools (at least arch/*/tools/ and arch/*/boot/tools/). Add check to consider any arch/.*/tools/ subdirectory as userspace tools directory. This helps not only to strscpy() checks but also to CamelCase checks in the next commit to be more precise. This is a follow-up to 99b70ece33d8 ("checkpatch: suppress strscpy warnings for userspace tools"). Link: https://lore.kernel.org/20260904110230.1219037-1-pvorel@suse.cz Link: https://lore.kernel.org/20260904110230.1219037-2-pvorel@suse.cz Signed-off-by: Petr Vorel Signed-off-by: Andrew Morton Cc: Andy Whitcroft Cc: Dwaipayan Ray Cc: Joe Perches Cc: Lukas Bulwahn --- scripts/checkpatch.pl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index f424dafce5bc..b8702f6bc9b5 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -2667,7 +2667,9 @@ sub exclude_global_initialisers { sub is_userspace { my ($realfile) = @_; - return ($realfile =~ m@^tools/@ || $realfile =~ m@^scripts/@); + return ($realfile =~ m@^tools/@ || + $realfile =~ m@^scripts/@ || + $realfile =~ m@^arch/.*/tools/@); } sub process { From 3dc89677fbba48e10653cff2c7a8972b62eb22fd Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Fri, 4 Sep 2026 13:02:28 +0200 Subject: [PATCH 055/134] checkpatch: ignore format macros for userspace tools Constants from are used only in userspace tools, they are from ISO C99, let's don't report it: arch/mips/boot/tools/relocs.c:572: CHECK: Avoid CamelCase: arch/s390/tools/relocs.c:52: CHECK: Avoid CamelCase: tools/testing/selftests/mm/vm_util.c:244: CHECK: Avoid CamelCase: Link: https://lore.kernel.org/20260904110230.1219037-3-pvorel@suse.cz Signed-off-by: Petr Vorel Signed-off-by: Andrew Morton Cc: Andy Whitcroft Cc: Dwaipayan Ray Cc: Joe Perches Cc: Lukas Bulwahn --- scripts/checkpatch.pl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index b8702f6bc9b5..b458c7f22684 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -5950,6 +5950,8 @@ sub process { #Ignore SI style variants like nS, mV and dB #(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && +#Ignore format macros (e.g. PRIu64, SCNu64) + (is_userspace($realfile) ? $var !~ /^(?:PRI|SCN)[dioux][A-Z0-9]+$/ : 1) && #Ignore some three character SI units explicitly, like MiB and KHz $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { while ($var =~ m{\b($Ident)}g) { From 4b5b44b161dbc64b89b85b91dfb63e8ad3f6ae98 Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Fri, 4 Sep 2026 13:02:29 +0200 Subject: [PATCH 056/134] checkpatch: add --userspace to force userspace rules Also allow to use --no-userspace for userspace projects which vendored checkpatch.pl and use --userspace globally to be able switch it off for files with kernel code. Link: https://lore.kernel.org/20260904110230.1219037-4-pvorel@suse.cz Signed-off-by: Petr Vorel Signed-off-by: Andrew Morton Cc: Andy Whitcroft Cc: Dwaipayan Ray Cc: Joe Perches Cc: Lukas Bulwahn --- scripts/checkpatch.pl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index b458c7f22684..ead35e6abba7 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -63,6 +63,7 @@ my $env_config_dir = 'CHECKPATCH_CONFIG_DIR'; my $max_line_length = 100; my $ignore_perl_version = 0; my $spdx_cxx_comments = 0; +my $userspace; my $minimum_perl_version = 5.10.0; my $min_conf_desc_length = 4; my $spelling_file = "$D/spelling.txt"; @@ -143,6 +144,7 @@ Options: (required by old toolchains), allow also C++ comments (//). NOTE: it should *not* be used for Linux mainline. + --userspace Force rules specific for userspace. --codespell Use the codespell dictionary for spelling/typos (default:$codespellfile) --codespellfile Use this codespell dictionary @@ -358,6 +360,7 @@ GetOptions( 'codespell!' => \$codespell, 'codespellfile=s' => \$user_codespellfile, 'typedefsfile=s' => \$typedefsfile, + 'userspace!' => \$userspace, 'color=s' => \$color, 'no-color' => \$color, #keep old behaviors of -nocolor 'nocolor' => \$color, #keep old behaviors of -nocolor @@ -2667,6 +2670,9 @@ sub exclude_global_initialisers { sub is_userspace { my ($realfile) = @_; + + return $userspace if (defined $userspace); + return ($realfile =~ m@^tools/@ || $realfile =~ m@^scripts/@ || $realfile =~ m@^arch/.*/tools/@); From 8fcc6355feed4a9175da1300da73cce47527e42a Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Fri, 4 Sep 2026 13:02:30 +0200 Subject: [PATCH 057/134] checkpatch: skip kernel specific checks for userspace These check are kernel specific, do not warn about it when testing userspace code: * BIT_MACRO * LONG_UDELAY * MSLEEP * PREFER_KERNEL_TYPES * USLEEP_RANGE This is a follow-up to 99b70ece33d8 ("checkpatch: suppress strscpy warnings for userspace tools"). Link: https://lore.kernel.org/20260904110230.1219037-5-pvorel@suse.cz Signed-off-by: Petr Vorel Signed-off-by: Andrew Morton Cc: Andy Whitcroft Cc: Dwaipayan Ray Cc: Joe Perches Cc: Lukas Bulwahn --- scripts/checkpatch.pl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index ead35e6abba7..3614cfe4dcbb 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -6696,7 +6696,8 @@ sub process { } # prefer usleep_range over udelay - if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { + if (!is_userspace($realfile) && + $line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { my $delay = $1; # ignore udelay's < 10, however if (! ($delay < 10) ) { @@ -6710,7 +6711,8 @@ sub process { } # warn about unexpectedly long msleep's - if ($line =~ /\bmsleep\s*\((\d+)\);/) { + if (!is_userspace($realfile) && + $line =~ /\bmsleep\s*\((\d+)\);/) { if ($1 < 20) { WARN("MSLEEP", "msleep < 20ms can sleep for up to 20ms; see function description of msleep().\n" . $herecurr); @@ -6932,7 +6934,7 @@ sub process { # check for c99 types like uint8_t used outside of uapi/ and tools/ if ($realfile !~ m@\binclude/uapi/@ && - $realfile !~ m@\btools/@ && + !is_userspace($realfile) && $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { my $type = $1; if ($type =~ /\b($typeC99Typedefs)\b/) { @@ -7182,6 +7184,7 @@ sub process { # check usleep_range arguments if ($perl_version_ok && defined $stat && + !is_userspace($realfile) && $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { my $min = $1; my $max = $7; @@ -7425,6 +7428,7 @@ sub process { # check for #defines like: 1 << that could be BIT(digit), it is not exported to uapi if ($realfile !~ m@^include/uapi/@ && + !is_userspace($realfile) && $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { my $ull = ""; $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); From 17f035e4f11e7decf57ee9cf94ed44dc0918bbfe Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 11 Sep 2026 01:30:48 +0900 Subject: [PATCH 058/134] bootconfig: remove redundant assignment in xbc_parse_array() After the loop, node is the last array value that xbc_add_child() added, and xbc_parse_array() sets node->child to 0. xbc_init_node() already set node->child to 0 when the value was added, and node->child is not modified after that, so the assignment is redundant. So remove the assignment. No functional change. Link: https://lore.kernel.org/20260910163051.1973775-1-ekffu200098@gmail.com Signed-off-by: Sang-Heon Jeon Signed-off-by: Andrew Morton Acked-by: Masami Hiramatsu --- lib/bootconfig.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/bootconfig.c b/lib/bootconfig.c index 89c88e359179..f248b5770291 100644 --- a/lib/bootconfig.c +++ b/lib/bootconfig.c @@ -836,7 +836,6 @@ static int __init xbc_parse_array(char **__v) return -ENOMEM; *__v = next; } while (c == ','); - node->child = 0; return c; } From d6f97fcebd0e9544d41a0a662890a59520a6ee0e Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 11 Sep 2026 23:13:06 +0900 Subject: [PATCH 059/134] bootconfig: reject unexpected data after null character Patch series "bootconfig: Reject unexpected data after null character and cleanups", v2. Make bootconfig reject unexpected config data after null character and implement other cleanups including tools/bootconfig to consolidate bootconfig initialization with errors, and to skip internal tree sanity checks in kernel. This patch (of 4): If a bootconfig buffer contains an intermediate null character in the middle of the configuration, xbc_parse_tree() stops at the null character because string delimiter searches (e.g. strpbrk()) stop at '\0', and cleanly breaks out of the loop without error. As a result, any configuration data following the intermediate null character is silently ignored, allowing unparsed or potentially malicious data to be hidden after an early termination. Fix this in xbc_parse_tree() by checking that no non-null data remains between the parser termination point and the end of the input buffer. Trailing null characters (such as alignment padding in initrd) continue to be accepted as valid. Also update apply_xbc() in tools/bootconfig/main.c to calculate the buffer size based on the loaded file size rather than strlen(), so that files with intermediate null characters are not truncated before validation. Link: https://lore.kernel.org/178913597653.248794.1237187523153227751.stgit@devnote2 Link: https://lore.kernel.org/178913598628.248794.1048773774471250986.stgit@devnote2 Signed-off-by: Masami Hiramatsu (Google) Signed-off-by: Andrew Morton Reviewed-by: Sang-Heon Jeon Assisted-by: Antigravity:gemini-3.8-flash --- lib/bootconfig.c | 7 +++++++ tools/bootconfig/main.c | 4 +++- tools/bootconfig/test-bootconfig.sh | 12 ++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/bootconfig.c b/lib/bootconfig.c index f248b5770291..b6adf8b27474 100644 --- a/lib/bootconfig.c +++ b/lib/bootconfig.c @@ -1118,6 +1118,13 @@ static int __init xbc_parse_tree(void) } } while (!ret); + if (!ret) { + while (p < xbc_data + xbc_data_size - 1 && *p == '\0') + p++; + if (p < xbc_data + xbc_data_size - 1) + ret = xbc_parse_error("Unexpected data after null character", p); + } + return ret; } diff --git a/tools/bootconfig/main.c b/tools/bootconfig/main.c index 17d971d47f87..aff169ba75b8 100644 --- a/tools/bootconfig/main.c +++ b/tools/bootconfig/main.c @@ -433,7 +433,9 @@ static int apply_xbc(const char *path, const char *xbc_path) pr_err("Failed to load %s : %d\n", xbc_path, ret); return ret; } - size = strlen(buf) + 1; + size = ret; + if (size == 0 || buf[size - 1] != '\0') + size++; csum = xbc_calc_checksum(buf, size); /* Backup the bootconfig data */ diff --git a/tools/bootconfig/test-bootconfig.sh b/tools/bootconfig/test-bootconfig.sh index fc69f815ce4a..530ce7e28d63 100755 --- a/tools/bootconfig/test-bootconfig.sh +++ b/tools/bootconfig/test-bootconfig.sh @@ -180,6 +180,18 @@ EOF $BOOTCONF -a $TEMPCONF $INITRD 2> $OUTFILE xpass grep -q "1:1" $OUTFILE +echo "Intermediate null character test" +printf "key = value\n\0extra = data\n" > $TEMPCONF +xfail $BOOTCONF -a $TEMPCONF $INITRD +$BOOTCONF -a $TEMPCONF $INITRD 2> $OUTFILE +xpass grep -q "Unexpected" $OUTFILE + +echo "Trailing null character test" +printf "key = value\n\0" > $TEMPCONF +xpass $BOOTCONF -a $TEMPCONF $INITRD +$BOOTCONF $INITRD > $OUTFILE +xpass grep -q "value" $OUTFILE + echo "=== expected failure cases ===" for i in samples/bad-* ; do xfail $BOOTCONF -a $i $INITRD From 5c45831072bb89d11b5c04d9e7ce3b1adbe46d4f Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 11 Sep 2026 23:13:24 +0900 Subject: [PATCH 060/134] bootconfig: skip internal tree sanity checks in kernel In xbc_verify_tree(), the loop iterating through all nodes to check that xbc_nodes[i].next < xbc_node_num and xbc_nodes[i].child < xbc_node_num is a defensive sanity check against implementation regressions (such an out-of-bounds index cannot be produced by malformed input). Running this check in the kernel adds unnecessary boot-time overhead. Split this check out into xbc_sanity_check_tree() for userspace, so that it continues to run during userspace bootconfig validation (e.g. when applying or testing bootconfig with tools/bootconfig), but is omitted in the kernel to speed up initialization. Link: https://lore.kernel.org/178913600409.248794.12941798680046327623.stgit@devnote2 Signed-off-by: Masami Hiramatsu (Google) Signed-off-by: Andrew Morton Reported-by: Sang-Heon Jeon Closes: https://lore.kernel.org/all/20260905141637.1547429-1-ekffu200098@gmail.com/ Reviewed-by: Sang-Heon Jeon --- lib/bootconfig.c | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/lib/bootconfig.c b/lib/bootconfig.c index b6adf8b27474..192a60a9f833 100644 --- a/lib/bootconfig.c +++ b/lib/bootconfig.c @@ -1002,9 +1002,30 @@ static int __init xbc_close_brace(char **k, char *n) return __xbc_close_brace(n - 1); } +#ifndef __KERNEL__ +/* Sanity check for regression: node indices must be within bounds */ +static int __init xbc_sanity_check_tree(void) +{ + int i; + + for (i = 0; i < xbc_node_num; i++) { + if (xbc_nodes[i].next >= xbc_node_num) { + return xbc_parse_error("No closing brace", + xbc_node_get_data(xbc_nodes + i)); + } + if (xbc_nodes[i].child >= xbc_node_num) { + return xbc_parse_error("Broken child node", + xbc_node_get_data(xbc_nodes + i)); + } + } + + return 0; +} +#endif + static int __init xbc_verify_tree(void) { - int i, depth; + int depth; size_t len, wlen; struct xbc_node *n, *m; @@ -1021,17 +1042,6 @@ static int __init xbc_verify_tree(void) return -ENOENT; } - for (i = 0; i < xbc_node_num; i++) { - if (xbc_nodes[i].next >= xbc_node_num) { - return xbc_parse_error("No closing brace", - xbc_node_get_data(xbc_nodes + i)); - } - if (xbc_nodes[i].child >= xbc_node_num) { - return xbc_parse_error("Broken child node", - xbc_node_get_data(xbc_nodes + i)); - } - } - /* Key tree limitation check */ n = &xbc_nodes[0]; depth = 1; @@ -1202,6 +1212,10 @@ int __init xbc_init(const char *data, size_t size, const char **emsg, int *epos) ret = xbc_parse_tree(); if (!ret) ret = xbc_verify_tree(); +#ifndef __KERNEL__ + if (!ret) + ret = xbc_sanity_check_tree(); +#endif if (ret < 0) { if (epos) From 392fe36dd27032468d22e9faff8fc240ee16d7a7 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 14:43:24 +0530 Subject: [PATCH 061/134] scripts/spelling.txt: keep British spelling for "initalise" These are the only two entries in the file that map a British spelling to an American one: initalised||initialized initalise||initialize doc-guide/checkpatch.rst asks that British and American spellings be left alone, so correcting the missing "i" should not also change the variety of English. Following checkpatch here silently rewrites "initalised" to "initialized" in files that otherwise use British forms; arch/arm64 alone has 58 uses of "initialised" against 132 of "initialized", so both are clearly in use. Map them to the British forms instead, so the misspelling is still caught while the spelling variety is left to the author. Link: https://lore.kernel.org/20260907091324.32211-1-hemanth.selam@gmail.com Signed-off-by: Hemanth Selam Signed-off-by: Andrew Morton Suggested-by: Randy Dunlap Acked-by: Randy Dunlap Assisted-by: Cursor:claude-opus-5 Cc: Colin Ian King Cc: Joe Perches Cc: Jonathan Corbet --- scripts/spelling.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/spelling.txt b/scripts/spelling.txt index 3372873cd7bb..9ec2529bc584 100644 --- a/scripts/spelling.txt +++ b/scripts/spelling.txt @@ -872,8 +872,8 @@ infromation||information ingore||ignore inheritence||inheritance inital||initial -initalised||initialized -initalise||initialize +initalised||initialised +initalise||initialise initalized||initialized initalize||initialize initation||initiation From 69919be2b93ef3922f646729ff42b4bd9a30846b Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 12 Sep 2026 14:17:35 -0400 Subject: [PATCH 062/134] lib/decompress_bunzip2: fix off-by-one in run-length bounds check The run-length path rejects a block when dbufCount+t equals dbufSize, but the loop that follows writes exactly t bytes starting at dbufCount, so a block that fills the buffer exactly is legal. bzip2 allows it too: its decompressor bounds a block at 100000 * blockSize100k and checks that limit per byte appended. Use > instead of >=. bzip2's encoder stops filling a block 19 bytes early, so nothing it produces ever reaches the limit and the bug stays hidden. Compressors that use the full block size do reach it: an lbzip2 -9 image whose block ends on a run fails to decode, and a self-extracting kernel built that way does not boot. This code came from busybox, which fixed the same line in 2013 in commit 932e233a491b ("bunzip2: fix off-by-one check"). I ran into this because my system uses lbzip2 as /bin/bzip2 -- a common thing on Gentoo I believe. As far as I can tell, anyone using lbzip2 as their system bzip2 would run into this and it's only because it's very uncommon these days to compress a kernel with bzip2 that no one has noticed. I only noticed because I was adding support for various compression formats on alpha. Link: https://lore.kernel.org/20260912-b4-bunzip2-blocksize-fix-v1-1-c7384bbfc954@gmail.com Fixes: bc22c17e12c1 ("bzip2/lzma: library support for gzip, bzip2 and lzma decompression") Signed-off-by: Matt Turner Signed-off-by: Andrew Morton Cc: Alain Knaff Cc: "H. Peter Anvin" Cc: --- lib/decompress_bunzip2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/decompress_bunzip2.c b/lib/decompress_bunzip2.c index 1288f146661f..aaa75404250e 100644 --- a/lib/decompress_bunzip2.c +++ b/lib/decompress_bunzip2.c @@ -439,7 +439,7 @@ static int INIT get_next_block(struct bunzip_data *bd) array.) */ if (runPos) { runPos = 0; - if (dbufCount+t >= dbufSize) + if (dbufCount+t > dbufSize) return RETVAL_DATA_ERROR; uc = symToByte[mtfSymbol[0]]; From 9cf77180133aa4ed6e874ae68af427828249aec3 Mon Sep 17 00:00:00 2001 From: Andrey Golovko Date: Sat, 12 Sep 2026 19:26:30 +0300 Subject: [PATCH 063/134] mailmap: update email address for Andrey Golovko Patches sent from andrey.golovko@gmail.com carry the authorship of my commits, but some of the tags I gave on other people's patches use andrey@golovko.me. Map the second address onto the first so the two do not look like two different contributors. Link: https://lore.kernel.org/20260912162630.7731-1-andrey.golovko@gmail.com Signed-off-by: Andrey Golovko Signed-off-by: Andrew Morton --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 1bcc2dcd28ee..8e3ed5275156 100644 --- a/.mailmap +++ b/.mailmap @@ -92,6 +92,7 @@ Andrew Morton Andrew Murray Andrew Murray Andrew Vasquez +Andrey Golovko Andrey Konovalov Andrey Ryabinin Andrey Ryabinin From 0f2cd84858e67cf8e02438bf28243bcdc67aa49a Mon Sep 17 00:00:00 2001 From: James Kim Date: Mon, 14 Sep 2026 13:23:27 +0900 Subject: [PATCH 064/134] rapidio: mport_cdev: fix use-after-free in mport_mm_close() A use-after-free vulnerability was identified in mport_mm_close() in drivers/rapidio/devices/rio_mport_cdev.c, identical to the pattern previously addressed in dma_req_free(). This is observable from userspace when an application creates an mmap mapping via the RapidIO character device and subsequently unmaps it (or terminates, triggering exit_mmap()). During munmap, mport_mm_close() is invoked and drops the mapping reference via kref_put(). If kref_put() drops the last reference, mport_release_mapping() is called, which frees the underlying rio_mport_mapping structure. The subsequent mutex_unlock() then dereferences map->md to unlock buf_mutex, leading to a use-after-free: mport_mm_close() -> mutex_lock(&map->md->buf_mutex); ... -> kref_put(&map->ref, mport_release_mapping); /* map is freed */ -> mutex_unlock(&map->md->buf_mutex); /* UAF: map used */ Fix this by caching map->md before kref_put() and using the cached pointer for mutex unlocking, ensuring that freed memory is not accessed. Link: https://lore.kernel.org/20260914042327.49798-1-james010kim@gmail.com Fixes: e8de370188d0 ("rapidio: add mport char device driver") Signed-off-by: James Kim Signed-off-by: Andrew Morton Cc: Alexandre Bounine Cc: Dan Carpenter Cc: Greg Kroah-Hartman Cc: Matt Porter Cc: --- drivers/rapidio/devices/rio_mport_cdev.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/rapidio/devices/rio_mport_cdev.c b/drivers/rapidio/devices/rio_mport_cdev.c index ad82c2108a56..47ba34b4afb2 100644 --- a/drivers/rapidio/devices/rio_mport_cdev.c +++ b/drivers/rapidio/devices/rio_mport_cdev.c @@ -2167,11 +2167,12 @@ static void mport_mm_open(struct vm_area_struct *vma) static void mport_mm_close(struct vm_area_struct *vma) { struct rio_mport_mapping *map = vma->vm_private_data; + struct mport_dev *md = map->md; rmcd_debug(MMAP, "%pad", &map->phys_addr); - mutex_lock(&map->md->buf_mutex); + mutex_lock(&md->buf_mutex); kref_put(&map->ref, mport_release_mapping); - mutex_unlock(&map->md->buf_mutex); + mutex_unlock(&md->buf_mutex); } static const struct vm_operations_struct vm_ops = { From 138a84817df01eda67bb23032ab49c23fe2ffc16 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 12 Sep 2026 21:32:17 +0800 Subject: [PATCH 065/134] lib: validate in-memory LZ4 chunk length We found and validated an issue in lib/decompress_unlz4.c. The bug is reachable by a root user through kexec_file_load() with a crafted external initrd. unlz4() reads the compressed chunk length from an in-memory initrd and passes it to LZ4_decompress_safe(). It only checks the chunk length against the allocation size when the input is filled by a callback. Reject an in-memory chunk that extends past the remaining input before calling the LZ4 decoder. This prevents malformed initrds from making the decoder read past the mapped archive. Link: https://lore.kernel.org/59c6555c27aa7ba18ee227f9f02c78d15a37605f.1789219453.git.zhilinz@nebusec.ai Fixes: e76e1fdfa8f8 ("lib: add support for LZ4-compressed kernel") Signed-off-by: Zhiling Zou Signed-off-by: Andrew Morton Reported-by: VEGA Assisted-by: LLM Cc: Kyungsik Lee --- lib/decompress_unlz4.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/decompress_unlz4.c b/lib/decompress_unlz4.c index c0dbb3cea915..86e9aaec04f6 100644 --- a/lib/decompress_unlz4.c +++ b/lib/decompress_unlz4.c @@ -139,6 +139,10 @@ STATIC inline int INIT unlz4(u8 *input, long in_len, if (!fill) { inp += 4; size -= 4; + if (chunksize > size) { + error("data corrupted"); + goto exit_2; + } } else { if (chunksize > LZ4_compressBound(uncomp_chunksize)) { error("chunk length is longer than allocated"); From 17b429239dac374b62d827b424dae35f9df1ef77 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Tue, 15 Sep 2026 18:06:23 +0000 Subject: [PATCH 066/134] taskstats: drop the unused CPU_DONT_CARE enum member CPU_DONT_CARE was added by the original listener cpumask code in 2006 (f9fd8914c1ac) and has never had a single user, not even in the commit that introduced it. It survived every refactor of the listener path since, including the recent handler folds. Kill it, twenty years is enough. Link: https://lore.kernel.org/20260915180623.22058-1-brads@mainlining.org Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton Reviewed-by: Balbir Singh --- kernel/taskstats.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/taskstats.c b/kernel/taskstats.c index 5edd759a163e..bf517788b840 100644 --- a/kernel/taskstats.c +++ b/kernel/taskstats.c @@ -59,8 +59,7 @@ static DEFINE_PER_CPU(struct listener_list, listener_array); enum actions { REGISTER, - DEREGISTER, - CPU_DONT_CARE + DEREGISTER }; static int prepare_reply(struct genl_info *info, u8 cmd, struct sk_buff **skbp, From 4effccfd50baee4f325694964aeee997dca7bd96 Mon Sep 17 00:00:00 2001 From: Jiaming Zhang Date: Mon, 14 Sep 2026 11:49:40 +0800 Subject: [PATCH 067/134] ocfs2: fix chunk number of the first chunk in a local quota file Mounting a crafted OCFS2 image can corrupt kernel memory. When the local quota file header of such an image claims zero chunks, the first quota entry allocated during the mount gets a chunk number taken from a kernel pointer, and releasing that entry clears a bit far outside the chunk bitmap. The write will corrupt the data stored at that address, and the corruption may lead to a system crash or damage unrelated data. Mounting requires CAP_SYS_ADMIN, so it takes an untrusted image, such as removable media or a loop mount of a file from elsewhere. Since the chunk number comes from a kernel pointer, the address of the bit that gets cleared differs from boot to boot, and the issue has been reported under several titles: - BUG: unable to handle kernel paging request in ocfs2_local_release_dquot - KASAN: use-after-free Write in ocfs2_local_release_dquot - KASAN: slab-out-of-bounds Write in ocfs2_local_release_dquot - KASAN: slab-use-after-free Write in ocfs2_local_release_dquot - KFENCE: use-after-free write in ocfs2_local_release_dquot Note that none of these is a use-after-free in the quota code: the chunk and its buffer head are alive. The bit that gets cleared lies far outside the bitmap. KASAN and KFENCE name each report based on the object occupying that address, which explains why the same issue is reported under so many different titles. In the report I sent, the address fell in a free page, which KASAN labels use-after-free. There are known reproducers. Both a syzkaller and a C reproducer are available from the Google Drive link [1] in my report thread. The issue was found by our modified syzkaller, not a theoretical thing found by an LLM. I have not seen it outside fuzzing, so it is not affecting me in the real world. The local quota file in OCFS2 is divided into chunks, and each chunk begins with a header block holding a bitmap of the quota entries that chunk has handed out. Chunks are numbered from zero, and that number is used to convert the file offset of an entry back into a bit position in the bitmap. ocfs2_local_quota_add_chunk() appends a new chunk to the in-memory list and numbers it one past the chunk that was last: list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk); chunk->qc_num = list_entry(chunk->qc_chunk.prev, struct ocfs2_quota_chunk, qc_chunk)->qc_num + 1; The predecessor is looked up after the new chunk is added to the list, so if the list was empty, the prev pointer is the list head itself. The head is the dqi_chunk member of struct ocfs2_mem_dqinfo and is not a chunk, so reading qc_num through it lands 16 bytes past the start of the head, on the dqi_gqinode pointer that follows it. The first chunk of the file is then numbered with the lower half of a kernel pointer instead of 0. The list is empty when the local quota file header claims the file has no chunks. ocfs2_local_read_info() takes dqi_chunks from that header without validating it, so an image with dqi_chunks == 0 takes this path when the first quota entry is allocated. ocfs2_create_local_dquot() turns the bad number into a file offset with ol_dqblk_off(), which shifts a 32-bit block number left by the block size bits, so the top bits of such a large block number are lost. ocfs2_local_release_dquot() turns the offset back into a bit index with ol_dqblk_chunk_off(), using the full chunk number, so the lost bits push that index far outside the bitmap, and clearing it corrupts unrelated memory. Compute the chunk number before putting the chunk on the list, and use 0 when the list is empty. Link: https://lore.kernel.org/20260914034940.4070970-1-r772577952@gmail.com Link: https://drive.google.com/drive/folders/1-LzPTgOALEc3eOjmgM6oOfRKkYXJ-bnO?usp=drive_link [1] Fixes: 9e33d69f553a ("ocfs2: Implementation of local and global quota file handling") Signed-off-by: Jiaming Zhang Signed-off-by: Andrew Morton Closes: https://lore.kernel.org/lkml/CANypQFZ05tpth0Xc33gmP6jgPnkY4VuezyHcsajV-SqCsmN_gg@mail.gmail.com/ Reviewed-by: Joseph Qi Assisted-by: Claude Code:claude-opus-5 Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: --- fs/ocfs2/quota_local.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c index f55810c59b1b..d351cda9211f 100644 --- a/fs/ocfs2/quota_local.c +++ b/fs/ocfs2/quota_local.c @@ -1071,10 +1071,13 @@ static struct ocfs2_quota_chunk *ocfs2_local_quota_add_chunk( goto out; } + if (list_empty(&oinfo->dqi_chunk)) + chunk->qc_num = 0; + else + chunk->qc_num = list_entry(oinfo->dqi_chunk.prev, + struct ocfs2_quota_chunk, + qc_chunk)->qc_num + 1; list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk); - chunk->qc_num = list_entry(chunk->qc_chunk.prev, - struct ocfs2_quota_chunk, - qc_chunk)->qc_num + 1; chunk->qc_headerbh = bh; *offset = 0; return chunk; From 6838e20f66bd19416a481988b76209720f4fe32a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 15 Sep 2026 10:53:53 +0200 Subject: [PATCH 068/134] resource: replace open coded resource_overlaps() In iomem_map_sanity_check() a piece of code resembles the content of the resource_overlaps(). Replace open coded piece with the call to the existing helper. Link: https://lore.kernel.org/20260915085353.3518153-1-andriy.shevchenko@linux.intel.com Signed-off-by: Andy Shevchenko Signed-off-by: Andrew Morton Reviewed-by: Bradley Morgan Cc: Bjorn Helgaas --- kernel/resource.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/kernel/resource.c b/kernel/resource.c index 54d7199695fb..cfc1a00e86aa 100644 --- a/kernel/resource.c +++ b/kernel/resource.c @@ -1833,7 +1833,7 @@ __setup("reserve=", reserve_setup); */ int iomem_map_sanity_check(resource_size_t addr, unsigned long size) { - resource_size_t end = addr + size - 1; + struct resource mem = DEFINE_RES_MEM(addr, size); struct resource *p; int err = 0; @@ -1843,12 +1843,10 @@ int iomem_map_sanity_check(resource_size_t addr, unsigned long size) * We can probably skip the resources without * IORESOURCE_IO attribute? */ - if (p->start > end) + if (!resource_overlaps(p, &mem)) continue; - if (p->end < addr) - continue; - if (PFN_DOWN(p->start) <= PFN_DOWN(addr) && - PFN_DOWN(p->end) >= PFN_DOWN(end)) + if (PFN_DOWN(p->start) <= PFN_DOWN(mem.start) && + PFN_DOWN(p->end) >= PFN_DOWN(mem.end)) continue; /* * if a resource is "BUSY", it's not a hardware resource @@ -1859,8 +1857,8 @@ int iomem_map_sanity_check(resource_size_t addr, unsigned long size) if (p->flags & IORESOURCE_BUSY) continue; - pr_debug("resource sanity check: requesting [mem %pa-%pa], which spans more than %s %pR\n", - &addr, &end, p->name, p); + pr_debug("resource sanity check: requesting %pR, which spans more than %s %pR\n", + &mem, p->name, p); err = -1; break; } From 97b93dd3fde0af7972d6e721c74e6ea1d053e434 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 15 Sep 2026 10:42:45 +0200 Subject: [PATCH 069/134] CREDITS: fix the ordering and other issues I assume that the ordering should be done in default (C) locale as other gives more lines shuffled. Fix the ordering accordingly. With that being said, clearly make at the top header how it should be sorted. Note, I took some empirical assumptions that people usually put their names in accordance with the First name(s) Last Name(s) split based on the records around (before and after the move). I hope I made no or little amount of mistakes. In any case it's just a dozen of names to update. Link: https://lore.kernel.org/20260915084248.3460260-1-andriy.shevchenko@linux.intel.com Signed-off-by: Andy Shevchenko Signed-off-by: Andrew Morton Cc: AngeloGiaocchino Del Regno Cc: Mathias Brugger --- CREDITS | 181 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 91 insertions(+), 90 deletions(-) diff --git a/CREDITS b/CREDITS index a0bd941ca5dd..3ae05ff1a092 100644 --- a/CREDITS +++ b/CREDITS @@ -1,6 +1,7 @@ This is at least a partial credits-file of people that have - contributed to the Linux project. It is sorted by name and - formatted to allow easy grepping and beautification by + contributed to the Linux project. It is sorted by family name, + assuming the used locale is default, e.g. by setting LC_ALL=C, + and formatted to allow easy grepping and beautification by scripts. The fields are: name (N), email (E), web-address (W), PGP key ID and fingerprint (P), description (D), and snail-mail address (S). @@ -344,6 +345,14 @@ D: Hardware spinlock (hwspinlock) subsystem D: OMAP hwspinlock driver D: OMAP remoteproc driver +N: Muli Ben-Yehuda +E: mulix@mulix.org +E: muli@il.ibm.com +W: http://www.mulix.org +D: trident OSS sound driver, x86-64 dma-ops and Calgary IOMMU, +D: KVM and Xen bits and other misc. hackery. +S: Haifa, Israel + N: Krzysztof Benedyczak E: golbi@mat.uni.torun.pl W: http://www.mat.uni.torun.pl/~golbi @@ -362,14 +371,6 @@ S: 2322 37th Ave SW S: Seattle, Washington 98126-2010 S: USA -N: Muli Ben-Yehuda -E: mulix@mulix.org -E: muli@il.ibm.com -W: http://www.mulix.org -D: trident OSS sound driver, x86-64 dma-ops and Calgary IOMMU, -D: KVM and Xen bits and other misc. hackery. -S: Haifa, Israel - N: Johannes Berg E: johannes@sipsolutions.net W: https://johannes.sipsolutions.net/ @@ -393,14 +394,14 @@ S: 1549 Hiironen Rd. S: Brimson, MN 55602 S: USA -N: Arnd Bergmann -D: Maintainer of Cell Broadband Engine Architecture - N: Hennus Bergman P: 1024/77D50909 76 99 FD 31 91 E1 96 1C 90 BB 22 80 62 F6 BD 63 D: Author and maintainer of the QIC-02 tape driver S: The Netherlands +N: Arnd Bergmann +D: Maintainer of Cell Broadband Engine Architecture + N: Tomas Berndtsson E: tomas@nocrew.org W: http://tomas.nocrew.org/ @@ -492,10 +493,6 @@ D: Various fixes (mostly networking) S: Montreal, Quebec S: Canada -N: Zoltán Böszörményi -E: zboszor@mail.externet.hu -D: MTRR emulation with Cyrix style ARR registers, Athlon MTRR support - N: John Boyd E: boyd@cis.ohio-state.edu D: Co-author of wd7000 SCSI driver @@ -589,9 +586,6 @@ N: Zach Brown E: zab@zabbo.net D: maestro pci sound -N: Zefan Li -D: Contribution to control group stuff - N: David Brownell D: Kernel engineer, mentor, and friend. Maintained USB EHCI and D: gadget layers, SPI subsystem, GPIO subsystem, and more than a few @@ -632,6 +626,10 @@ S: Ravenhorst 58 S: 2317 AK Leiden S: The Netherlands +N: Zoltán Böszörményi +E: zboszor@mail.externet.hu +D: MTRR emulation with Cyrix style ARR registers, Athlon MTRR support + N: Michael Callahan E: callahan@maths.ox.ac.uk D: PPP for Linux @@ -703,6 +701,10 @@ S: Tamsui town, Taipei county, S: Taiwan 251 S: Republic of China +N: Landen Chao +E: Landen.Chao@mediatek.com +D: MT7531 Ethernet switch support + N: Michael Elizabeth Chastain E: mec@shout.net D: Configure, Menuconfig, xconfig @@ -715,10 +717,6 @@ D: Media subsystem (V4L/DVB) drivers and core D: EDAC drivers and EDAC 3.0 core rework S: Brazil -N: Landen Chao -E: Landen.Chao@mediatek.com -D: MT7531 Ethernet switch support - N: Raymond Chen E: raymondc@microsoft.com D: Author of Configure script @@ -1447,6 +1445,10 @@ D: Made support for modules, ramdisk, generic-serial, etc. optional. D: Transformed old user space bdflush into 1st kernel thread - kflushd. D: Many other patches, documentation files, mini kernels, utilities, ... +N: Andy Gospodarek +E: andy@greyhouse.net +D: Maintenance and contributions to the network interface bonding driver. + N: Masanori GOTO E: gotom@debian.or.jp D: Workbit NinjaSCSI-32Bi/UDE driver @@ -1459,10 +1461,6 @@ S: 8124 Constitution Apt. 7 S: Sterling Heights, Michigan 48313 S: USA -N: Andy Gospodarek -E: andy@greyhouse.net -D: Maintenance and contributions to the network interface bonding driver. - N: Vivek Goyal E: vgoyal@redhat.com D: KDUMP, KEXEC, and VIRTIO FILE SYSTEM @@ -1535,6 +1533,14 @@ S: 44 St. Joseph Street, Suite 506 S: Toronto, Ontario, M4Y 2W4 S: Canada +N: Nitin Gupta +E: ngupta@vflare.org +D: zsmalloc memory allocator and zram block device driver + +N: Justin Guyett +E: jguyett@andrew.cmu.edu +D: via-rhine net driver hacking + N: Richard Günther E: rguenth@tat.physik.uni-tuebingen.de W: http://www.tat.physik.uni-tuebingen.de/~rguenth @@ -1543,14 +1549,6 @@ D: binfmt_misc S: 72074 Tübingen S: Germany -N: Justin Guyett -E: jguyett@andrew.cmu.edu -D: via-rhine net driver hacking - -N: Nitin Gupta -E: ngupta@vflare.org -D: zsmalloc memory allocator and zram block device driver - N: Danny ter Haar E: dth@cistron.nl D: /proc/cpuinfo, reboot on panic , kernel pre-patch tester ;) @@ -1945,16 +1943,6 @@ E: sjenning@redhat.com D: Creation and maintenance of zswap D: Creation and maintenace of the zbud allocator -N: Jeremy Kerr -D: Maintainer of SPU File System - -N: Michael Kerrisk -E: mtk.manpages@gmail.com -W: https://man7.org/ -P: 4096R/3A35CE5E E522 595B 52ED A4E6 BFCC CB5E 8561 9911 3A35 CE5E -D: Maintainer of the Linux man-pages project -D: Linux man pages online, at - N: Niels Kristian Bech Jensen E: nkbj1970@hotmail.com D: Miscellaneous kernel updates and fixes. @@ -2099,6 +2087,16 @@ S: Keplerstr. 6 S: 4050 Traun S: Austria +N: Jeremy Kerr +D: Maintainer of SPU File System + +N: Michael Kerrisk +E: mtk.manpages@gmail.com +W: https://man7.org/ +P: 4096R/3A35CE5E E522 595B 52ED A4E6 BFCC CB5E 8561 9911 3A35 CE5E +D: Maintainer of the Linux man-pages project +D: Linux man pages online, at + N: Karl Keyte E: karl@koft.com D: Disk usage statistics and modifications to line printer driver @@ -2483,6 +2481,9 @@ D: and much more. He was the maintainer of MD from 2016 to 2018. Shaohua D: passed away late 2018, he will be greatly missed. W: https://www.spinics.net/lists/raid/msg61993.html +N: Zefan Li +D: Contribution to control group stuff + N: Stephan Linz E: linz@mazet.de E: Stephan.Linz@gmx.de @@ -2542,11 +2543,6 @@ D: misc. kernel hacking and debugging S: Cambridge, MA 02139 S: USA -N: Martin von Löwis -E: loewis@informatik.hu-berlin.de -D: script binary format -D: NTFS driver - N: H.J. Lu E: hjl@gnu.ai.mit.edu D: GCC + libraries hacker @@ -2575,6 +2571,11 @@ S: Puistokaari 1 E 18 S: 00200 Helsinki S: Finland +N: Martin von Löwis +E: loewis@informatik.hu-berlin.de +D: script binary format +D: NTFS driver + N: Daniel J. Maas E: dmaas@dcine.com W: https://www.maasdigital.com @@ -2625,10 +2626,6 @@ S: PO BOX 220, HFX. CENTRAL S: Halifax, Nova Scotia S: Canada B3J 3C8 -N: Kai Mäkisara -E: Kai.Makisara@kolumbus.fi -D: SCSI Tape Driver - N: Asit Mallick E: asit.k.mallick@intel.com D: Linux/IA-64 @@ -2912,13 +2909,6 @@ S: 12725 SW Millikan Way, Suite 400 S: Beaverton, Oregon 97005 S: USA -N: Eberhard Mönkeberg -E: emoenke@gwdg.de -D: CDROM driver "sbpcd" (Matsushita/Panasonic/Soundblaster) -S: Ruhstrathöhe 2 b. -S: D-37085 Göttingen -S: Germany - N: Thomas Molina E: tmolina@cablespeed.com D: bug fixes, documentation, minor hackery @@ -3005,6 +2995,17 @@ S: Dragonvagen 1 A 13 S: FIN-00330 Helsingfors S: Finland +N: Kai Mäkisara +E: Kai.Makisara@kolumbus.fi +D: SCSI Tape Driver + +N: Eberhard Mönkeberg +E: emoenke@gwdg.de +D: CDROM driver "sbpcd" (Matsushita/Panasonic/Soundblaster) +S: Ruhstrathöhe 2 b. +S: D-37085 Göttingen +S: Germany + N: Matija Nalis E: mnalis@jagor.srce.hr E: mnalis@voyager.hr @@ -3200,13 +3201,6 @@ S: RR #5, 497 Pole Line Road S: Thunder Bay, Ontario S: CANADA P7C 5M9 -N: Inaky Perez-Gonzalez -E: inaky.perez-gonzalez@intel.com -E: linux-wimax@intel.com -E: inakypg@yahoo.com -D: WiMAX stack -D: Intel Wireless WiMAX Connection 2400 driver - N: Yuri Per E: yuri@pts.mipt.ru D: Some smbfs fixes @@ -3214,6 +3208,13 @@ S: Demonstratsii 8-382 S: Tula 300000 S: Russia +N: Inaky Perez-Gonzalez +E: inaky.perez-gonzalez@intel.com +E: linux-wimax@intel.com +E: inakypg@yahoo.com +D: WiMAX stack +D: Intel Wireless WiMAX Connection 2400 driver + N: Thomas Petazzoni E: thomas.petazzoni@bootlin.com D: Driver for the Marvell Armada 370/XP network unit. @@ -3328,14 +3329,14 @@ N: Frederic Potter E: fpotter@cirpack.com D: Some PCI kernel support -N: Rui Prior -E: rprior@inescn.pt -D: ATM device driver for NICStAR based cards - N: Roopa Prabhu E: roopa@nvidia.com D: Bridge co-maintainer, vxlan and networking contributor +N: Rui Prior +E: rprior@inescn.pt +D: ATM device driver for NICStAR based cards + N: Stefan Probst E: sp@caldera.de D: The Linux Support Team Erlangen, 1993-97 @@ -4023,12 +4024,6 @@ S: C/ Federico Garcia Lorca 1 10-A S: Sevilla 41005 S: Spain -N: Björn Töpel -E: bjorn@kernel.org -D: AF_XDP -S: Gothenburg -S: Sweden - N: Linus Torvalds E: torvalds@linux-foundation.org D: Original kernel hacker @@ -4082,17 +4077,6 @@ D: Linux-Workshop Köln (aka LUG Cologne, Germany), Installfests S: Tacitusstr. 6 S: D-50968 Köln -N: Tsu-Sheng Tsao -E: tsusheng@scf.usc.edu -D: IGMP (Internet Group Management Protocol) version 2 -S: 2F 14 ALY 31 LN 166 SEC 1 SHIH-PEI RD -S: Taipei -S: Taiwan 112 -S: Republic of China -S: 24335 Delta Drive -S: Diamond Bar, California 91765 -S: USA - N: Theodore Ts'o E: tytso@mit.edu D: Random Linux hacker @@ -4109,6 +4093,17 @@ S: 1 Amherst Street S: Cambridge, Massachusetts 02139 S: USA +N: Tsu-Sheng Tsao +E: tsusheng@scf.usc.edu +D: IGMP (Internet Group Management Protocol) version 2 +S: 2F 14 ALY 31 LN 166 SEC 1 SHIH-PEI RD +S: Taipei +S: Taiwan 112 +S: Republic of China +S: 24335 Delta Drive +S: Diamond Bar, California 91765 +S: USA + N: Luben Tuikov E: Luben Tuikov D: Maintainer of the DRM GPU Scheduler @@ -4135,6 +4130,12 @@ S: 44 Campbell Park Crescent S: Edinburgh EH13 0HT S: United Kingdom +N: Björn Töpel +E: bjorn@kernel.org +D: AF_XDP +S: Gothenburg +S: Sweden + N: Thomas Uhl E: uhl@sun1.rz.fh-heilbronn.de D: Application programmer From 73554d8db4951d2a486405a2759cc0ffd282c0b2 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Wed, 16 Sep 2026 18:29:52 +0000 Subject: [PATCH 070/134] panic: fix redirect CPU race in panic_try_force_cpu() Patch series "panic: fix panic_force_cpu= redirect races and NMI bypass", v7. The panic_force_cpu= parameter redirects a panic to a specific CPU so the crash kernel runs there. The redirect code in panic_try_force_cpu() had two races and an NMI bypass, all found by Sashiko. This series closes them and kills one more hack that the rework turned up. Panic from NMI still goes through the redirect path first, so the crash kernel ends up on the CPU the admin asked for. The redirect buffer is a static 1KB now, no initcall, no fallback message. This patch (of 6): The cmpxchg() in panic_try_force_cpu() makes sure that only one CPU tries to redirect panic() to the requested CPU. It is similar to the cmpxchg() in panic_try_start() which makes sure that only one CPU does the panic(). In both situations, only the winner of cmpxchg() should proceed further. Other CPUs should go offline. There is a bug because the cmpxchg loser returns false and falls through into vpanic(). Two non-target CPUs A and B panic, the requested CPU is C: cpu A cpu B ---------- ---------- panic() panic() vpanic() vpanic() panic_try_force_cpu() panic_try_force_cpu() cmpxchg wins cmpxchg fails redirect = A old_cpu = A IPI -> C return false <- BUG return true panic_try_start() wins panic_smp_self_stop() __crash_kexec() on B (A stops) (target C bypassed) The loser must stop, not fall through. It cannot just return true, though. A CPU that already won the redirect cmpxchg can reenter panic_try_force_cpu() on the same CPU, for example a nested NMI during the message formatting, before the IPI is sent: cpu A (1st) cpu A (nested) ---------- ---------- panic() vpanic() panic_try_force_cpu() cmpxchg wins (redirect = A) vsnprintf(msg) ... <-- NMI, nested panic --> panic() vpanic() panic_try_force_cpu() cmpxchg fails old_cpu == A (this CPU) return true <- would halt panic_smp_self_stop() (IPI never sent, panic abandoned) Check old_cpu against this_cpu so a second call from the same CPU returns false and falls through to panic_try_start() instead. Also fix the panic_in_progress() check. We must not redirect when panic_cpu is already assigned. Return true to stop when the panic is on another CPU, false to proceed when it is this one. Update the panic_try_force_cpu() doc comment for the new return value semantics. Link: https://lore.kernel.org/20260916182957.7788-1-brads@mainlining.org Link: https://lore.kernel.org/20260916182957.7788-2-brads@mainlining.org Fixes: 2e171ab29f91 ("panic: add panic_force_cpu= parameter to redirect panic to a specific CPU") Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260705164123.18746-1-include@grrlz.net Closes: https://sashiko.dev/#/patchset/20260707172252.4842-1-include@grrlz.net Reviewed-by: Petr Mladek Cc: Guenetr Roeck Cc: Wang Jinchao Cc: Wim Van Sebroeck Cc: --- kernel/panic.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/kernel/panic.c b/kernel/panic.c index 50715f14cf04..08072bfae422 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -371,8 +371,9 @@ int __weak panic_smp_redirect_cpu(int target_cpu, void *msg) * for the crash kernel to function correctly. This function redirects * panic handling to the CPU specified via the panic_force_cpu= boot parameter. * - * Returns false if panic should proceed on current CPU. - * Returns true if panic was redirected. + * Returns true when this CPU must stop: the panic was redirected or is + * already running on another CPU. + * Returns false when panic() should proceed on this CPU. */ __printf(1, 0) static bool panic_try_force_cpu(const char *fmt, va_list args) @@ -396,16 +397,20 @@ static bool panic_try_force_cpu(const char *fmt, va_list args) return false; } - /* Another panic already in progress */ + /* + * Don't redirect when a panic is already in progress. Stop this + * CPU when it's another one, proceed when it's this one. + */ if (panic_in_progress()) - return false; + return panic_on_other_cpu(); /* - * Only one CPU can do the redirect. Use atomic cmpxchg to ensure - * we don't race with another CPU also trying to redirect. + * Only one CPU can do the redirection. Others should go offline. + * Continue with panic() when we already tried the redirection + * from this CPU before, for example via nmi_panic(). */ if (!atomic_try_cmpxchg(&panic_redirect_cpu, &old_cpu, this_cpu)) - return false; + return old_cpu != this_cpu; /* * Use dynamically allocated buffer if available, otherwise From 75eb687ff2bcec9fba31bbe266e5f91e5c283efe Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Wed, 16 Sep 2026 18:29:53 +0000 Subject: [PATCH 071/134] panic: flatten nmi_panic control flow panic() is __noreturn, so the else after panic_try_start() is dead. Drop it so the force_cpu path can be added cleanly on top. Link: https://lore.kernel.org/20260916182957.7788-3-brads@mainlining.org Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton Reviewed-by: Petr Mladek Cc: Guenetr Roeck Cc: Sashiko Cc: Wang Jinchao Cc: Wim Van Sebroeck Cc: --- kernel/panic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/panic.c b/kernel/panic.c index 08072bfae422..5646d4fb82b7 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -517,7 +517,8 @@ void nmi_panic(struct pt_regs *regs, const char *msg) { if (panic_try_start()) panic("%s", msg); - else if (panic_on_other_cpu()) + + if (panic_on_other_cpu()) nmi_panic_self_stop(regs); } EXPORT_SYMBOL(nmi_panic); From 245001a347f1effc09c233d1712e1efeedab59ee Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Wed, 16 Sep 2026 18:29:54 +0000 Subject: [PATCH 072/134] panic: fix va_list reuse in panic_try_force_cpu() vsnprintf() consumes the caller's va_list. When the redirect fails, vpanic() reuses it for the panic message, which is undefined behavior. Use va_copy(). Link: https://lore.kernel.org/20260916182957.7788-4-brads@mainlining.org Fixes: 2e171ab29f91 ("panic: add panic_force_cpu= parameter to redirect panic to a specific CPU") Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton Reviewed-by: Petr Mladek Cc: Guenetr Roeck Cc: Sashiko Cc: Wang Jinchao Cc: Wim Van Sebroeck Cc: --- kernel/panic.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/panic.c b/kernel/panic.c index 5646d4fb82b7..7388eb81a1c4 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -417,7 +417,12 @@ static bool panic_try_force_cpu(const char *fmt, va_list args) * fall back to static message for early boot panics or allocation failure. */ if (panic_force_buf) { - vsnprintf(panic_force_buf, PANIC_MSG_BUFSZ, fmt, args); + va_list ap; + + /* Do not consume args, the caller reuses it if we fail */ + va_copy(ap, args); + vsnprintf(panic_force_buf, PANIC_MSG_BUFSZ, fmt, ap); + va_end(ap); msg = panic_force_buf; } else { msg = "Redirected panic (buffer unavailable)"; From ed9e4c1ff15d5b935f467811541ca0aa6e49c5d0 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Wed, 16 Sep 2026 18:29:55 +0000 Subject: [PATCH 073/134] panic: restore variable arguments to nmi_panic() nmi_panic() used to accept variable arguments until commit ebc41f20d77f ("panic: change nmi_panic from macro to function") flattened it to a final message string. vpanic() did not exist back then, so the function had to format through panic("%s", msg). Bring the variable arguments back and format with vpanic() directly. The next patch makes nmi_panic() try the panic_force_cpu= redirect before claiming panic_cpu. panic_try_force_cpu() needs to print the message but it is used also by panic() which accepts variable argument list. The string could be formatted only when a CPU gets assigned to process the redirection. Otherwise, there might be a race when writing to the `panic_force_buf`. No current caller passes a string with format specifiers. The closest one is hpwdt_pretimeout(), which builds panic_msg with hex_byte_pack() and has only two variants, both plain strings. But the new __printf() annotation on nmi_panic() would warn with -Wformat-security there because the buffer is passed directly as the format argument, so switch it to nmi_panic(regs, "%s", panic_msg). [pmladek@suse.com: changelog fix] Link: https://lore.kernel.org/arJxFybmtD7OBIcL@pathway.suse.cz Link: https://lore.kernel.org/20260916182957.7788-5-brads@mainlining.org Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton Suggested-by: Petr Mladek Reviewed-by: Petr Mladek Cc: Guenetr Roeck Cc: Sashiko Cc: Wang Jinchao Cc: Wim Van Sebroeck Cc: --- drivers/watchdog/hpwdt.c | 2 +- include/linux/panic.h | 3 ++- kernel/panic.c | 10 ++++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 8af1fad2de0b..78227d200afe 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -199,7 +199,7 @@ static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) } hex_byte_pack(panic_msg, nmistat); - nmi_panic(regs, panic_msg); + nmi_panic(regs, "%s", panic_msg); return NMI_HANDLED; } diff --git a/include/linux/panic.h b/include/linux/panic.h index 98dd7dfd27de..17e61b61c45f 100644 --- a/include/linux/panic.h +++ b/include/linux/panic.h @@ -13,7 +13,8 @@ __printf(1, 2) void panic(const char *fmt, ...) __noreturn __cold; __printf(1, 0) void vpanic(const char *fmt, va_list args) __noreturn __cold; -void nmi_panic(struct pt_regs *regs, const char *msg); +__printf(2, 3) +void nmi_panic(struct pt_regs *regs, const char *fmt, ...); void check_panic_on_warn(const char *origin); extern void oops_enter(void); extern void oops_exit(void); diff --git a/kernel/panic.c b/kernel/panic.c index 7388eb81a1c4..e240ca06faab 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -518,13 +518,19 @@ EXPORT_SYMBOL(panic_on_other_cpu); * nmi_panic_self_stop() which can provide architecture dependent code such * as saving register state for crash dump. */ -void nmi_panic(struct pt_regs *regs, const char *msg) +void nmi_panic(struct pt_regs *regs, const char *fmt, ...) { + va_list args; + + va_start(args, fmt); + if (panic_try_start()) - panic("%s", msg); + vpanic(fmt, args); if (panic_on_other_cpu()) nmi_panic_self_stop(regs); + + va_end(args); } EXPORT_SYMBOL(nmi_panic); From 36c55a9625bb9ab7eb428ff8facc6d3d0a743180 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Wed, 16 Sep 2026 18:29:56 +0000 Subject: [PATCH 074/134] panic: allow force_cpu redirect from an NMI nmi_panic() claims panic_cpu via panic_try_start() before calling panic(). When the panic later reaches panic_try_force_cpu(), the panic_in_progress() check sees panic_cpu set and refuses to redirect. The crash kernel runs on the CPU that took the NMI instead of the CPU requested with panic_force_cpu=: nmi_panic() panic_try_start() wins, panic_cpu = X panic("%s", msg) vpanic() panic_try_force_cpu() panic_in_progress() true, panic_cpu is X return false redirect bypassed panic_try_start() already won __crash_kexec() on X, not the requested CPU Try the redirect before claiming panic_cpu instead, as suggested by Petr Mladek. nmi_panic() now calls panic_try_force_cpu() first and claims panic_cpu only when no redirect happened. The requested CPU claims panic_cpu itself when it runs panic(), so panic_cpu does not need to be handed off. panic_try_force_cpu() copies the arguments before formatting (patch 3), so nmi_panic() can pass them to vpanic() again when no redirect happens. The redirect IPI is sent with smp_call_function_single_async(), which is not guaranteed to work from NMI context. Treat it as best effort. It is worth the risk because the redirection is only used when the crash kernel would not work on the panicking CPU anyway. Keep returning when the panic is already running on this CPU. A nested NMI, for example with unknown_nmi_panic while this CPU is inside panic(), must return and let the interrupted panic() continue instead of parking the CPU in nmi_panic_self_stop(). Mark the redirecting CPU offline before stopping it, like vpanic() does, so that panic_other_cpus_shutdown() on the target CPU does not wait for it. Link: https://lore.kernel.org/20260916182957.7788-6-brads@mainlining.org Fixes: 2e171ab29f91 ("panic: add panic_force_cpu= parameter to redirect panic to a specific CPU") Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260708164312.19044-1-include@grrlz.net Reviewed-by: Petr Mladek Cc: Guenetr Roeck Cc: Wang Jinchao Cc: Wim Van Sebroeck Cc: --- kernel/panic.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/kernel/panic.c b/kernel/panic.c index e240ca06faab..29c981926f5f 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -513,10 +513,11 @@ bool panic_on_other_cpu(void) EXPORT_SYMBOL(panic_on_other_cpu); /* - * A variant of panic() called from NMI context. We return if we've already - * panicked on this CPU. If another CPU already panicked, loop in - * nmi_panic_self_stop() which can provide architecture dependent code such - * as saving register state for crash dump. + * A variant of panic() called from NMI context. The panic is first + * redirected to the CPU requested via panic_force_cpu=, when configured. + * We return if we've already panicked on this CPU. If another CPU already + * panicked, loop in nmi_panic_self_stop() which can provide architecture + * dependent code for saving register state for crash dump. */ void nmi_panic(struct pt_regs *regs, const char *fmt, ...) { @@ -524,6 +525,16 @@ void nmi_panic(struct pt_regs *regs, const char *fmt, ...) va_start(args, fmt); + /* Try to redirect to the requested CPU before claiming panic_cpu. */ + if (panic_try_force_cpu(fmt, args)) { + /* + * Mark ourselves offline so panic_other_cpus_shutdown() won't + * wait for us on architectures that check num_online_cpus(). + */ + set_cpu_online(raw_smp_processor_id(), false); + nmi_panic_self_stop(regs); + } + if (panic_try_start()) vpanic(fmt, args); From 68d956e9e235c2e125f77a9e98831e297b624a75 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Wed, 16 Sep 2026 18:29:57 +0000 Subject: [PATCH 075/134] panic: kill the "buffer unavailable" redirect fallback The redirect buffer is a disgusting terrible hack. panic_force_buf is kmalloc'ed in a late_initcall, and until then the redirect delivers this as the panic message: Redirected panic (buffer unavailable) The whole point of the redirect is to hand the panic message to the target CPU, so the crash kernel boots knowing it panicked and not why. And that window is the entire boot, from the early_param to the late_initcall, which is exactly when you most want the message. Make it a static 1KB buffer and kill the initcall. The cost is 1KB of .bss in SMP crash dump builds, and it is only ever touched when panic_force_cpu= is set anyway. The local msg variable is gone too, the buffer goes directly to panic_smp_redirect_cpu(). Link: https://lore.kernel.org/20260916182957.7788-7-brads@mainlining.org Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton Suggested-by: Petr Mladek Reviewed-by: Petr Mladek Cc: Guenetr Roeck Cc: Sashiko Cc: Wang Jinchao Cc: Wim Van Sebroeck Cc: --- kernel/panic.c | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/kernel/panic.c b/kernel/panic.c index 29c981926f5f..170744163fc2 100644 --- a/kernel/panic.c +++ b/kernel/panic.c @@ -307,7 +307,7 @@ atomic_t panic_cpu = ATOMIC_INIT(PANIC_CPU_INVALID); atomic_t panic_redirect_cpu = ATOMIC_INIT(PANIC_CPU_INVALID); #if defined(CONFIG_SMP) && defined(CONFIG_CRASH_DUMP) -static char *panic_force_buf; +static char panic_force_buf[PANIC_MSG_BUFSZ]; static int __init panic_force_cpu_setup(char *str) { @@ -326,17 +326,6 @@ static int __init panic_force_cpu_setup(char *str) } early_param("panic_force_cpu", panic_force_cpu_setup); -static int __init panic_force_cpu_late_init(void) -{ - if (panic_force_cpu < 0) - return 0; - - panic_force_buf = kmalloc(PANIC_MSG_BUFSZ, GFP_KERNEL); - - return 0; -} -late_initcall(panic_force_cpu_late_init); - static void do_panic_on_target_cpu(void *info) { panic("%s", (char *)info); @@ -380,7 +369,7 @@ static bool panic_try_force_cpu(const char *fmt, va_list args) { int this_cpu = raw_smp_processor_id(); int old_cpu = PANIC_CPU_INVALID; - const char *msg; + va_list ap; /* Feature not enabled via boot parameter */ if (panic_force_cpu < 0) @@ -413,20 +402,11 @@ static bool panic_try_force_cpu(const char *fmt, va_list args) return old_cpu != this_cpu; /* - * Use dynamically allocated buffer if available, otherwise - * fall back to static message for early boot panics or allocation failure. + * Do not consume args, the caller reuses them if we fail. */ - if (panic_force_buf) { - va_list ap; - - /* Do not consume args, the caller reuses it if we fail */ - va_copy(ap, args); - vsnprintf(panic_force_buf, PANIC_MSG_BUFSZ, fmt, ap); - va_end(ap); - msg = panic_force_buf; - } else { - msg = "Redirected panic (buffer unavailable)"; - } + va_copy(ap, args); + vsnprintf(panic_force_buf, PANIC_MSG_BUFSZ, fmt, ap); + va_end(ap); console_verbose(); bust_spinlocks(1); @@ -441,7 +421,7 @@ static bool panic_try_force_cpu(const char *fmt, va_list args) dump_stack(); } - if (panic_smp_redirect_cpu(panic_force_cpu, (void *)msg) != 0) { + if (panic_smp_redirect_cpu(panic_force_cpu, panic_force_buf) != 0) { atomic_set(&panic_redirect_cpu, PANIC_CPU_INVALID); pr_warn("panic: failed to redirect to CPU %d, continuing on CPU %d\n", panic_force_cpu, this_cpu); From b822ad8d702df0be9fe3f24f71caeb0522d72319 Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Wed, 16 Sep 2026 09:53:51 +0100 Subject: [PATCH 076/134] lib: decompress_bunzip2: fix integer overflow in run-length decoding The RUNA/RUNB decoder in get_next_block() accumulates a run length into the signed int t with no bound. runPos doubles per symbol, so t grows as at least 2^n-1 and reaches INT_MAX after 31 RUNA symbols. The guard that follows, dbufCount+t >= dbufSize, is itself a signed addition, and as the kernel builds with -fno-strict-overflow it wraps negative once dbufCount is nonzero, so the guard is skipped. while (t--) dbuf[dbufCount++] = uc then writes INT_MAX entries into a buffer holding at most 900000. One literal symbol ahead of the run is enough to make dbufCount nonzero. Bound t to the block size as it is accumulated. t only grows within a run, so the value tested never exceeds the final run length; any stream that decodes today satisfies dbufCount+t < dbufSize at the flush, hence t < dbufSize throughout, and no such stream is rejected. Because t grows as at least 2^n-1 the bound trips by the 20th symbol, leaving runPos at most 2^19, so the following runPos <<= 1 cannot overflow either. Link: https://lore.kernel.org/AS8P251MB00010FE5E38D253CF98A652AC8B92@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Fixes: bc22c17e12c1 ("bzip2/lzma: library support for gzip, bzip2 and lzma decompression") Signed-off-by: Aamir Ahmed Signed-off-by: Andrew Morton Assisted-by: LLM Cc: Alain Knaff Cc: "H. Peter Anvin" Cc: --- lib/decompress_bunzip2.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/decompress_bunzip2.c b/lib/decompress_bunzip2.c index aaa75404250e..a9236d3e9678 100644 --- a/lib/decompress_bunzip2.c +++ b/lib/decompress_bunzip2.c @@ -428,6 +428,10 @@ static int INIT get_next_block(struct bunzip_data *bd) t += (runPos << nextSym); /* +runPos if RUNA; +2*runPos if RUNB */ + /* Bound the run so t and runPos cannot overflow. */ + if (t >= dbufSize) + return RETVAL_DATA_ERROR; + runPos <<= 1; continue; } From cd6920b6f3e5c6402d49e1128d0e53ee1c7c9b55 Mon Sep 17 00:00:00 2001 From: Su Yue Date: Wed, 16 Sep 2026 10:47:50 +0800 Subject: [PATCH 077/134] ocfs2: update xattr count before moving bucket entries Since commit 2f26f58df041 ("ocfs2: annotate flexible array members with __counted_by_le()"), the xh_entries array is annotated with __counted_by_le(xh_count), so FORTIFY uses xh_count to determine its bounds. When inserting an entry into a bucket, ocfs2_xa_bucket_add_entry() shifts existing entries before incrementing xh_count. The destination therefore extends one entry past the bounds described by the old count. With CONFIG_CC_HAS_COUNTED_BY and CONFIG_FORTIFY_SOURCE enabled, ocfs2-test: single_run-WIP.sh -t reflink triggers the following failure while adding an extended attribute: [ 150.156484] memmove: detected buffer overflow: 352 byte write of buffer size 336 [ 150.156487] WARNING: lib/string_helpers.c:1036 at __fortify_report+0x3d/0x50, CPU#15: reflink_test/2336 [ 150.160496] RIP: 0010:__fortify_report+0x40/0x50 [ 150.164031] Call Trace: [ 150.164141] [ 150.164232] __fortify_panic+0x9/0xb [ 150.164383] ocfs2_xa_bucket_add_entry.cold+0x17/0x28 [ocfs2] [ 150.164661] ocfs2_xa_set+0x8fb/0xf40 [ocfs2] Increment xh_count before memmove() so the destination bounds include the new entry. Keep the local count unchanged to calculate the insertion position and move length from the original number of entries. The caller has already checked that there is enough space for the new entry. Link: https://lore.kernel.org/20260916024750.9450-1-glass.su@suse.com Fixes: 2f26f58df041 ("ocfs2: annotate flexible array members with __counted_by_le()") Signed-off-by: Su Yue Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: --- fs/ocfs2/xattr.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index a2d6c3d0f2e8..9292cf26e276 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -2146,12 +2146,17 @@ static void ocfs2_xa_bucket_add_entry(struct ocfs2_xa_loc *loc, u32 name_hash) } } + /* + * Increment xh_count before memmove() so __counted_by_le(xh_count) + * includes the new entry in the destination bounds. + */ + le16_add_cpu(&xh->xh_count, 1); + if (low != count) memmove(&xh->xh_entries[low + 1], &xh->xh_entries[low], ((count - low) * sizeof(struct ocfs2_xattr_entry))); - le16_add_cpu(&xh->xh_count, 1); loc->xl_entry = &xh->xh_entries[low]; memset(loc->xl_entry, 0, sizeof(struct ocfs2_xattr_entry)); } From d637475617d8468c2740a3436428681bcc8d3572 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Thu, 17 Sep 2026 09:48:25 +0800 Subject: [PATCH 078/134] ocfs2: retain all security xattrs during inode creation When creating a new inode, ocfs2_init_security_get() stores the security xattr returned by the LSM framework in ocfs2_security_xattr_info so that it can be written later within the same transaction. But ocfs2_initxattrs() only ever looks at the first element of the xattr array, and it keeps the name as a bare pointer into memory owned by the security layer instead of copying it. The value, by contrast, is already duplicated with kmemdup(). With stacked LSMs, security_inode_init_security() provides one xattr per LSM, so all labels but the first are silently dropped and never written to disk, leaving newly created files without the security xattrs the other LSMs rely on. The borrowed name is not an outright bug with the LSMs in tree today: security_inode_init_security() frees only ->value once the callback returns, and documents ->name as the attribute name suffix, so the pointer stays valid for as long as ocfs2 uses it. It is fragile though, as an LSM that allocates the name together with the value would turn it into a use-after-free, so the rework below copies the names as well. Fix this by storing copies of all the security xattrs: allocate an array in ocfs2_initxattrs(), copy the names and values, account for all of them in the credit calculations, and write each one in ocfs2_init_security_set(). While at it, switch the allocations from GFP_KERNEL to GFP_NOFS. The callback runs in the inode creation path with the parent directory locked, and the security layer allocates the very same xattr array with GFP_NOFS, so direct reclaim must not be allowed to recurse into the filesystem from here. Link: https://lore.kernel.org/20260917014825.678947-1-joseph.qi@linux.alibaba.com Fixes: de3004c874e7 ("ocfs2: Switch to security_inode_init_security()") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Daniel Borkmann Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao --- fs/ocfs2/namei.c | 6 +-- fs/ocfs2/xattr.c | 133 +++++++++++++++++++++++++++++++++++++---------- fs/ocfs2/xattr.h | 12 +++-- 3 files changed, 116 insertions(+), 35 deletions(-) diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 58c6061ed983..ef03f90e7265 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -248,7 +248,6 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, int want_meta = 0; int xattr_credits = 0; struct ocfs2_security_xattr_info si = { - .name = NULL, .enable = 1, }; int did_quota_inode = 0; @@ -475,7 +474,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, brelse(new_fe_bh); brelse(parent_fe_bh); - kfree(si.value); + ocfs2_free_security_xattrs(&si); ocfs2_acl_init_release(&acl_state); @@ -1831,7 +1830,6 @@ static int ocfs2_symlink(struct mnt_idmap *idmap, int want_clusters = 0; int xattr_credits = 0; struct ocfs2_security_xattr_info si = { - .name = NULL, .enable = 1, }; int did_quota = 0, did_quota_inode = 0; @@ -2063,7 +2061,7 @@ static int ocfs2_symlink(struct mnt_idmap *idmap, brelse(new_fe_bh); brelse(parent_fe_bh); - kfree(si.value); + ocfs2_free_security_xattrs(&si); ocfs2_free_dir_lookup_result(&lookup); if (inode_ac) ocfs2_free_alloc_context(inode_ac); diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 9292cf26e276..c86b15cf17a6 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -643,10 +643,15 @@ int ocfs2_calc_security_init(struct inode *dir, int *xattr_credits, struct ocfs2_alloc_context **xattr_ac) { + int i; int ret = 0; struct ocfs2_super *osb = OCFS2_SB(dir->i_sb); - int s_size = ocfs2_xattr_entry_real_size(strlen(si->name), - si->value_len); + int s_size = 0; + + for (i = 0; i < si->count; i++) + s_size += ocfs2_xattr_entry_real_size( + strlen(si->xattrs[i].name), + si->xattrs[i].value_len); /* * The max space of security xattr taken inline is @@ -663,14 +668,27 @@ int ocfs2_calc_security_init(struct inode *dir, *xattr_credits += OCFS2_XATTR_BLOCK_CREATE_CREDITS; } - /* reserve clusters for xattr value which will be set in B tree*/ - if (si->value_len > OCFS2_XATTR_INLINE_SIZE) { - int new_clusters = ocfs2_clusters_for_bytes(dir->i_sb, - si->value_len); + /* + * when blocksize = 512, the security xattrs may not fit in the + * single block reserved above and a bucket has to be allocated + * for them, so reserve the cluster it needs as well. + */ + if (dir->i_sb->s_blocksize == OCFS2_MIN_BLOCKSIZE && + s_size > OCFS2_XATTR_FREE_IN_BLOCK(dir)) { + *want_clusters += 1; + *xattr_credits += ocfs2_blocks_per_xattr_bucket(dir->i_sb); + } - *xattr_credits += ocfs2_clusters_to_blocks(dir->i_sb, - new_clusters); - *want_clusters += new_clusters; + /* reserve clusters for xattr value which will be set in B tree*/ + for (i = 0; i < si->count; i++) { + if (si->xattrs[i].value_len > OCFS2_XATTR_INLINE_SIZE) { + int new_clusters = ocfs2_clusters_for_bytes(dir->i_sb, + si->xattrs[i].value_len); + + *xattr_credits += ocfs2_clusters_to_blocks(dir->i_sb, + new_clusters); + *want_clusters += new_clusters; + } } return ret; } @@ -680,12 +698,16 @@ void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, int *want_clusters, int *xattr_credits, int *want_meta, struct ocfs2_acl_state *acl_state) { + int i; struct ocfs2_super *osb = OCFS2_SB(dir->i_sb); int s_size = 0, a_size = 0, acl_len = 0, new_clusters; - if (si->enable) - s_size = ocfs2_xattr_entry_real_size(strlen(si->name), - si->value_len); + if (si->enable) { + for (i = 0; i < si->count; i++) + s_size += ocfs2_xattr_entry_real_size( + strlen(si->xattrs[i].name), + si->xattrs[i].value_len); + } if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) { if (acl_state->default_acl && S_ISDIR(mode)) { @@ -732,12 +754,14 @@ void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, * reserve credits and clusters for xattrs which has large value * and have to be set outside */ - if (si->enable && si->value_len > OCFS2_XATTR_INLINE_SIZE) { - new_clusters = ocfs2_clusters_for_bytes(dir->i_sb, - si->value_len); - *xattr_credits += ocfs2_clusters_to_blocks(dir->i_sb, - new_clusters); - *want_clusters += new_clusters; + for (i = 0; si->enable && i < si->count; i++) { + if (si->xattrs[i].value_len > OCFS2_XATTR_INLINE_SIZE) { + new_clusters = ocfs2_clusters_for_bytes(dir->i_sb, + si->xattrs[i].value_len); + *xattr_credits += ocfs2_clusters_to_blocks(dir->i_sb, + new_clusters); + *want_clusters += new_clusters; + } } if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) { if (acl_state->default_acl && S_ISDIR(mode)) { @@ -7635,17 +7659,46 @@ static int ocfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array { struct ocfs2_security_xattr_info *si = fs_info; const struct xattr *xattr; + struct ocfs2_security_xattr *xattrs; + int count = 0, i; int err = 0; if (si) { - si->value = kmemdup(xattr_array->value, xattr_array->value_len, - GFP_KERNEL); - if (!si->value) + for (xattr = xattr_array; xattr->name != NULL; xattr++) + count++; + + xattrs = kcalloc(count, sizeof(*xattrs), GFP_NOFS); + if (!xattrs) return -ENOMEM; - si->name = xattr_array->name; - si->value_len = xattr_array->value_len; + for (i = 0; i < count; i++) { + xattrs[i].name = kstrdup(xattr_array[i].name, GFP_NOFS); + if (!xattrs[i].name) { + err = -ENOMEM; + goto out_err; + } + + xattrs[i].value = kmemdup(xattr_array[i].value, + xattr_array[i].value_len, + GFP_NOFS); + if (!xattrs[i].value) { + err = -ENOMEM; + goto out_err; + } + xattrs[i].value_len = xattr_array[i].value_len; + } + + si->xattrs = xattrs; + si->count = count; return 0; + +out_err: + for (; i >= 0; i--) { + kfree(xattrs[i].name); + kfree(xattrs[i].value); + } + kfree(xattrs); + return err; } for (xattr = xattr_array; xattr->name != NULL; xattr++) { @@ -7658,6 +7711,19 @@ static int ocfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array return err; } +void ocfs2_free_security_xattrs(struct ocfs2_security_xattr_info *si) +{ + int i; + + for (i = 0; i < si->count; i++) { + kfree(si->xattrs[i].name); + kfree(si->xattrs[i].value); + } + kfree(si->xattrs); + si->xattrs = NULL; + si->count = 0; +} + int ocfs2_init_security_get(struct inode *inode, struct inode *dir, const struct qstr *qstr, @@ -7675,7 +7741,7 @@ int ocfs2_init_security_get(struct inode *inode, * security_inode_init_security() does not return -EOPNOTSUPP, * we have to check the xattr ourselves. */ - if (!ret && !si->name) + if (!ret && !si->count) si->enable = 0; return ret; @@ -7692,10 +7758,21 @@ int ocfs2_init_security_set(handle_t *handle, struct ocfs2_alloc_context *xattr_ac, struct ocfs2_alloc_context *data_ac) { - return ocfs2_xattr_set_handle(handle, inode, di_bh, - OCFS2_XATTR_INDEX_SECURITY, - si->name, si->value, si->value_len, 0, - xattr_ac, data_ac); + int i; + int ret = 0; + + for (i = 0; i < si->count; i++) { + ret = ocfs2_xattr_set_handle(handle, inode, di_bh, + OCFS2_XATTR_INDEX_SECURITY, + si->xattrs[i].name, + si->xattrs[i].value, + si->xattrs[i].value_len, 0, + xattr_ac, data_ac); + if (ret) + break; + } + + return ret; } const struct xattr_handler ocfs2_xattr_security_handler = { diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h index 887cc1a18b1a..1dd08f979e3c 100644 --- a/fs/ocfs2/xattr.h +++ b/fs/ocfs2/xattr.h @@ -20,13 +20,18 @@ enum ocfs2_xattr_type { OCFS2_XATTR_MAX }; -struct ocfs2_security_xattr_info { - int enable; - const char *name; +struct ocfs2_security_xattr { + char *name; void *value; size_t value_len; }; +struct ocfs2_security_xattr_info { + int enable; + struct ocfs2_security_xattr *xattrs; + int count; +}; + extern const struct xattr_handler ocfs2_xattr_user_handler; extern const struct xattr_handler ocfs2_xattr_trusted_handler; extern const struct xattr_handler ocfs2_xattr_security_handler; @@ -49,6 +54,7 @@ int ocfs2_xattr_remove(struct inode *, struct buffer_head *); int ocfs2_init_security_get(struct inode *, struct inode *, const struct qstr *, struct ocfs2_security_xattr_info *); +void ocfs2_free_security_xattrs(struct ocfs2_security_xattr_info *); int ocfs2_init_security_set(handle_t *, struct inode *, struct buffer_head *, struct ocfs2_security_xattr_info *, From f5263bee9eb84d2388fd7555a18d518e17226894 Mon Sep 17 00:00:00 2001 From: Fang Xieyan Date: Thu, 17 Sep 2026 18:43:06 +0800 Subject: [PATCH 079/134] kcov: ignore an out-of-range comparison count in write_comp_data() write_comp_data() reads the comparison record count from area[0] and uses it to index the coverage buffer: area = (u64 *)t->kcov_area; max_pos = t->kcov_size * sizeof(unsigned long); count = READ_ONCE(area[0]); /* Every record is KCOV_WORDS_PER_CMP 64-bit words. */ start_index = 1 + count * KCOV_WORDS_PER_CMP; end_pos = (start_index + KCOV_WORDS_PER_CMP) * sizeof(u64); if (likely(end_pos <= max_pos)) { The buffer is mmap'd writable into the collecting process, so count is under its control and end_pos <= max_pos is its only bound. A count that wraps the u64 multiply leaves end_pos below max_pos, so the check passes while the record store lands 24 bytes before the buffer, in the unmapped vmalloc guard page, and faults: BUG: unable to handle page fault for address: ffa0000000b60fe8 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page Oops: 0002 [#1] SMP KASAN NOPTI RIP: 0010:write_comp_data+0x7e/0xa0 ... Kernel panic - not syncing: Fatal exception Bound count first: only max_pos / (sizeof(u64) * KCOV_WORDS_PER_CMP) records fit, so a larger count is not a valid index and is dropped. kcov_move_area() bounds the same untrusted count this way, and no count the end_pos <= max_pos check accepts reaches that limit, so no valid record is lost. kcov is a root-only debugfs file (debugfs_create_file_unsafe("kcov", 0600, ...)) and write_comp_data() exists only under CONFIG_KCOV_ENABLE_COMPARISONS, so this is a local, debug-kernel robustness fix: the process corrupts its own buffer and the kernel oopses. It crosses no privilege boundary. Additional details at [1] Link: https://lore.kernel.org/20260917104306.22145-1-fangxy@xiaopeng.com [1] Fixes: ded97d2c2b2c ("kcov: support comparison operands collection") Signed-off-by: Fang Xieyan Signed-off-by: Andrew Morton Reviewed-by: Alexander Potapenko Assisted-by: Hawkeye:GLM-5.3-flash Assisted-by: Qoder:Qwen3.8-Max Cc: Andrey Konovalov Cc: Dmitry Vyukov Cc: Marco Elver Cc: Victor Chibotaru Cc: --- kernel/kcov.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/kcov.c b/kernel/kcov.c index 35420f0ac524..54eaae97bfd6 100644 --- a/kernel/kcov.c +++ b/kernel/kcov.c @@ -253,6 +253,14 @@ static void notrace write_comp_data(u64 type, u64 arg1, u64 arg2, u64 ip) count = READ_ONCE(area[0]); + /* + * area[0] is writable by the collecting process, so count cannot be + * trusted. Bound it to the records that fit, as kcov_move_area() + * does, so the end_pos multiply below cannot wrap past its check. + */ + if (count >= max_pos / (sizeof(u64) * KCOV_WORDS_PER_CMP)) + return; + /* Every record is KCOV_WORDS_PER_CMP 64-bit words. */ start_index = 1 + count * KCOV_WORDS_PER_CMP; end_pos = (start_index + KCOV_WORDS_PER_CMP) * sizeof(u64); From 858e9d1a8bfe9ba11d099a496f48c22dd5f0c692 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 17 Sep 2026 16:50:12 -0400 Subject: [PATCH 080/134] rapidio: use dmaengine_get_dma_device() instead of chan->device->dev Replace direct dma_chan::device::dev access with the proper dmaengine_get_dma_device() for consumer API. chan->device->dev is not always the device used for DMA mapping. Some DMA engines support per-channel IOMMU mappings, so different channels may use different DMA devices. dmaengine_get_dma_device() returns the correct device for each channel. This also prepares for making the DMA engine provider data structures private. DMA consumers should not access DMA engine internals directly. Link: https://lore.kernel.org/20260917205016.1293317-1-Frank.Li@oss.nxp.com Signed-off-by: Frank Li Signed-off-by: Andrew Morton Assisted-by: LLM Cc: Alexandre Bounine Cc: Dan Carpenter Cc: Kees Cook Cc: Matt Porter Cc: Vinod Koul --- drivers/rapidio/devices/rio_mport_cdev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rapidio/devices/rio_mport_cdev.c b/drivers/rapidio/devices/rio_mport_cdev.c index 47ba34b4afb2..40d2953a9a7f 100644 --- a/drivers/rapidio/devices/rio_mport_cdev.c +++ b/drivers/rapidio/devices/rio_mport_cdev.c @@ -555,7 +555,7 @@ static void dma_req_free(struct kref *ref) refcount); struct mport_cdev_priv *priv = req->priv; - dma_unmap_sg(req->dmach->device->dev, + dma_unmap_sg(dmaengine_get_dma_device(req->dmach), req->sgt.sgl, req->sgt.nents, req->dir); sg_free_table(&req->sgt); if (req->page_list) { @@ -916,7 +916,7 @@ rio_dma_transfer(struct file *filp, u32 transfer_mode, xfer->offset, xfer->length); } - nents = dma_map_sg(chan->device->dev, + nents = dma_map_sg(dmaengine_get_dma_device(chan), req->sgt.sgl, req->sgt.nents, dir); if (nents == 0) { rmcd_error("Failed to map SG list"); From 8535c259c8fb9768cbba38d50273080bf071744b Mon Sep 17 00:00:00 2001 From: "Jose A. Perez de Azpillaga" Date: Mon, 21 Sep 2026 23:55:04 +0200 Subject: [PATCH 081/134] percpu_counter: annotate lockless read in _limited_add() syzbot reports a data race on fbc->count between the lockless read in the fast path of __percpu_counter_limited_add() and the locked update in percpu_counter_add_batch(), reached via shmem's used_blocks counter: the alloc path reads it through percpu_counter_limited_add() while the free path updates it through percpu_counter_sub(). Annotate the read rather than change the logic: the lockless read is deliberate, only the annotation is missing. It is an approximation, not a conservative bound. A concurrent flush moves value between fbc->count and a per-cpu counter, and other CPUs' locked slow paths add to fbc->count too, so a stale low value can let the fast path proceed where the slow path would refuse. The error is bounded by the per-cpu slack (unknown = batch * num_online_cpus()). This is existing behavior, no functional change intended. Read it with data_race(READ_ONCE(fbc->count)): data_race() tells KCSAN the race is intended (READ_ONCE() alone still triggers the report, because KCSAN reports against watchpoints set up by plain accesses), while READ_ONCE() stops the compiler from refetching the value, as in percpu_counter_read_positive(). The lock-protected accesses stay plain, so future buggy lockless writes are still caught. Link: https://lore.kernel.org/20260921215508.141641-1-azpijr@gmail.com Signed-off-by: Jose A. Perez de Azpillaga Signed-off-by: Andrew Morton Reported-by: syzbot+a3c71b9db9c11c270f59@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a3c71b9db9c11c270f59 Reviewed-by: Andrew Morton Cc: Dennis Zhou Cc: Tejun Heo Cc: Christoph Lameter Cc: Hugh Dickins Cc: Baolin Wang --- lib/percpu_counter.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/percpu_counter.c b/lib/percpu_counter.c index 2891f94a11c6..87b261b87b0b 100644 --- a/lib/percpu_counter.c +++ b/lib/percpu_counter.c @@ -328,6 +328,7 @@ bool __percpu_counter_limited_add(struct percpu_counter *fbc, s64 limit, s64 amount, s32 batch) { s64 count; + s64 gcount; s64 unknown; unsigned long flags; bool good = false; @@ -338,11 +339,16 @@ bool __percpu_counter_limited_add(struct percpu_counter *fbc, local_irq_save(flags); unknown = batch * num_online_cpus(); count = __this_cpu_read(*fbc->counters); + /* + * Lockless on purpose: gcount may be stale, so this is only an + * approximation, bounded by the per-cpu slack ("unknown"). + */ + gcount = data_race(READ_ONCE(fbc->count)); /* Skip taking the lock when safe */ if (abs(count + amount) <= batch && - ((amount > 0 && fbc->count + unknown <= limit) || - (amount < 0 && fbc->count - unknown >= limit))) { + ((amount > 0 && gcount + unknown <= limit) || + (amount < 0 && gcount - unknown >= limit))) { this_cpu_add(*fbc->counters, amount); local_irq_restore(flags); return true; From aabe6e1b36cee51c778e5acfbc824bbb81c9967a Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Tue, 22 Sep 2026 22:26:27 +0900 Subject: [PATCH 082/134] init/main.c: remove unreachable check in obsolete_checksetup() Since commit 1ecfea06386c ("init.h: remove long-dead __setup_null_param() macro"), .init.setup entries cannot have a NULL setup_func. So remove the NULL check. No functional change. Link: https://lore.kernel.org/20260922132630.177941-1-ekffu200098@gmail.com Signed-off-by: Sang-Heon Jeon Signed-off-by: Andrew Morton --- init/main.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/init/main.c b/init/main.c index d1cd8efb8238..008a0a137f51 100644 --- a/init/main.c +++ b/init/main.c @@ -215,10 +215,6 @@ static bool __init obsolete_checksetup(char *line) * params and __setups of same names 8( */ if (line[n] == '\0' || line[n] == '=') had_early_param = true; - } else if (!p->setup_func) { - pr_warn("Parameter %s is obsolete, ignored\n", - p->str); - return true; } else if (p->setup_func(line + n)) return true; } From 60f50cdd5ac54bcbc21a4b01b1c2f6409d4563a7 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 22 Sep 2026 17:42:52 +0800 Subject: [PATCH 083/134] ocfs2: restore -ERANGE check in ocfs2_xattr_tree_list_index_block() ocfs2_xattr_list_entry() returns -ERANGE when the buffer handed in by the caller is too small to hold the whole xattr name list. That is the normal listxattr(2) protocol, userspace simply retries with a larger buffer, so it is not an ocfs2 error and must not be logged as one. Commit a46fa684fcb700 ("ocfs2: Don't printk the error when listing too many xattrs.") already knew this and silenced two sites, quoting (27738,0):ocfs2_iterate_xattr_buckets:3158 ERROR: status = -34 (27738,0):ocfs2_xattr_tree_list_index_block:3264 ERROR: status = -34 The refactoring in commit 47bca4950bc40f ("ocfs2: Abstract ocfs2 xattr tree extend rec iteration process.") then rewrote ocfs2_xattr_tree_list_index_block() around the new ocfs2_iterate_xattr_index_block() helper and dropped that guard, so the second message has been emitted ever since: ocfs2_xattr_tree_list_index_block:4513 ERROR: status = -34 The exemption in ocfs2_iterate_xattr_buckets() survived because that function was left alone, and the new helper does check for -ERANGE itself, which is why only the outermost caller still logs it. Restore the missing check. Only the log line changes: -ERANGE is still returned to the VFS untouched, so listxattr(2) behaves exactly as before. Link: https://lore.kernel.org/20260922094252.971631-1-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi --- fs/ocfs2/xattr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index c86b15cf17a6..4555e7296fcb 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -4526,7 +4526,8 @@ static int ocfs2_xattr_tree_list_index_block(struct inode *inode, ret = ocfs2_iterate_xattr_index_block(inode, blk_bh, ocfs2_list_xattr_tree_rec, &xl); if (ret) { - mlog_errno(ret); + if (ret != -ERANGE) + mlog_errno(ret); goto out; } From 169bc4d16a8efa7852b101fdaa4c028505cd6750 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 22 Sep 2026 17:34:37 +0800 Subject: [PATCH 084/134] ocfs2: fix possible deadlock between nfs_sync_rwlock and fs_reclaim syzbot detected a circular locking dependency on &osb->nfs_sync_rwlock: CPU0 CPU1 ---- ---- lock(fs_reclaim); lock(&ocfs2_sysfile_lock_key[INODE_ALLOC_SYSTEM_INODE]); lock(fs_reclaim); rlock(&osb->nfs_sync_rwlock); *** DEADLOCK *** Chain exists of: &osb->nfs_sync_rwlock --> &ocfs2_sysfile_lock_key[INODE_ALLOC_SYSTEM_INODE] --> fs_reclaim CPU0 is kswapd. The dentry shrinker runs under fs_reclaim and reaches ocfs2_delete_inode() through ->evict_inode(): kswapd balance_pgdat shrink_node shrink_slab super_cache_scan prune_dcache_sb shrink_dentry_list dentry_kill ocfs2_dentry_iput evict ocfs2_evict_inode ocfs2_delete_inode ocfs2_nfs_sync_lock down_read(&osb->nfs_sync_rwlock) //C0: grabbing CPU1 is a task deleting an inode. It takes nfs_sync_rwlock first, then the orphan dir and inode alloc system inode i_rwsems, and allocates the metadata reservation with GFP_KERNEL while holding them: evict ocfs2_evict_inode ocfs2_delete_inode + ocfs2_nfs_sync_lock | down_read(&osb->nfs_sync_rwlock) //C1: hold + ocfs2_wipe_inode + inode_lock(orphan_dir_inode) //C1: hold + ocfs2_truncate_for_delete | ocfs2_commit_truncate | ocfs2_remove_btree_range | ocfs2_reserve_blocks_for_rec_trunc | ocfs2_reserve_new_metadata_blocks | kzalloc_obj() //C1: grabbing | // GFP_KERNEL -> might_alloc -> fs_reclaim_acquire + ocfs2_remove_inode inode_lock(inode_alloc_inode) jbd2 pins allocations to GFP_NOFS for the lifetime of a transaction handle, but these reservations are made before ocfs2_start_trans(), so fs_reclaim is acquired with ocfs2 locks still held and the cycle closes. The same cycle is reachable from ocfs2_get_dentry() and ocfs2_get_parent(), which hold nfs_sync_rwlock for write across ocfs2_test_inode_bit() and ocfs2_iget(). This is more than a lockdep artifact. A GFP_KERNEL allocation under the orphan dir i_rwsem enters direct reclaim, which runs the shrinkers, which can evict another ocfs2 inode and re-enter ocfs2_wipe_inode() on the same task; the second inode_lock() on the singleton orphan dir inode then self-deadlocks, since i_rwsem is not recursive. Establish a GFP_NOFS allocation context for the whole nfs_sync_rwlock critical section so that nothing holding it can recurse into filesystem reclaim. Do it in ocfs2_nfs_sync_lock()/ocfs2_nfs_sync_unlock() rather than at the call sites, so that future callers cannot forget it. Link: https://lore.kernel.org/20260922093437.954127-1-joseph.qi@linux.alibaba.com Fixes: 6ca497a83e59 ("ocfs2: fix rare stale inode errors when exporting via nfs") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reported-by: syzbot+a68ce48df87b8e36e915@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a68ce48df87b8e36e915 Cc: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi --- fs/ocfs2/dlmglue.c | 29 +++++++++++++++++++++++++---- fs/ocfs2/dlmglue.h | 5 +++-- fs/ocfs2/export.c | 10 ++++++---- fs/ocfs2/inode.c | 5 +++-- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/fs/ocfs2/dlmglue.c b/fs/ocfs2/dlmglue.c index cf3318b0d3a8..6bc4a21cf5f0 100644 --- a/fs/ocfs2/dlmglue.c +++ b/fs/ocfs2/dlmglue.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -2861,21 +2862,34 @@ void ocfs2_rename_unlock(struct ocfs2_super *osb) ocfs2_cluster_unlock(osb, lockres, DLM_LOCK_EX); } -int ocfs2_nfs_sync_lock(struct ocfs2_super *osb, int ex) +int ocfs2_nfs_sync_lock(struct ocfs2_super *osb, int ex, unsigned int *nofs_flag) { int status; + unsigned int flags; struct ocfs2_lock_res *lockres = &osb->osb_nfs_sync_lockres; if (ocfs2_is_hard_readonly(osb)) return -EROFS; + /* + * ocfs2_delete_inode() takes this lock from ->evict_inode(), which the + * dentry shrinker reaches while holding fs_reclaim. Anything allocated + * under the lock must therefore stay out of filesystem reclaim, or + * reclaim recurses back into the shrinker and tries to take this lock + * again. Cover the whole critical section, including the cluster lock + * and the sysfile inode locks the callers take below it. + */ + flags = memalloc_nofs_save(); + if (ex) down_write(&osb->nfs_sync_rwlock); else down_read(&osb->nfs_sync_rwlock); - if (ocfs2_mount_local(osb)) + if (ocfs2_mount_local(osb)) { + *nofs_flag = flags; return 0; + } status = ocfs2_cluster_lock(osb, lockres, ex ? LKM_EXMODE : LKM_PRMODE, 0, 0); @@ -2886,12 +2900,17 @@ int ocfs2_nfs_sync_lock(struct ocfs2_super *osb, int ex) up_write(&osb->nfs_sync_rwlock); else up_read(&osb->nfs_sync_rwlock); + memalloc_nofs_restore(flags); + return status; } - return status; + *nofs_flag = flags; + + return 0; } -void ocfs2_nfs_sync_unlock(struct ocfs2_super *osb, int ex) +void ocfs2_nfs_sync_unlock(struct ocfs2_super *osb, int ex, + unsigned int nofs_flag) { struct ocfs2_lock_res *lockres = &osb->osb_nfs_sync_lockres; @@ -2902,6 +2921,8 @@ void ocfs2_nfs_sync_unlock(struct ocfs2_super *osb, int ex) up_write(&osb->nfs_sync_rwlock); else up_read(&osb->nfs_sync_rwlock); + + memalloc_nofs_restore(nofs_flag); } int ocfs2_trim_fs_lock(struct ocfs2_super *osb, diff --git a/fs/ocfs2/dlmglue.h b/fs/ocfs2/dlmglue.h index a3ebd7303ea2..faa58ed8699b 100644 --- a/fs/ocfs2/dlmglue.h +++ b/fs/ocfs2/dlmglue.h @@ -161,8 +161,9 @@ void ocfs2_orphan_scan_unlock(struct ocfs2_super *osb, u32 seqno); int ocfs2_rename_lock(struct ocfs2_super *osb); void ocfs2_rename_unlock(struct ocfs2_super *osb); -int ocfs2_nfs_sync_lock(struct ocfs2_super *osb, int ex); -void ocfs2_nfs_sync_unlock(struct ocfs2_super *osb, int ex); +int ocfs2_nfs_sync_lock(struct ocfs2_super *osb, int ex, unsigned int *nofs_flag); +void ocfs2_nfs_sync_unlock(struct ocfs2_super *osb, int ex, + unsigned int nofs_flag); void ocfs2_trim_fs_lock_res_init(struct ocfs2_super *osb); void ocfs2_trim_fs_lock_res_uninit(struct ocfs2_super *osb); int ocfs2_trim_fs_lock(struct ocfs2_super *osb, diff --git a/fs/ocfs2/export.c b/fs/ocfs2/export.c index 9c2665dd24e2..90b9e510e700 100644 --- a/fs/ocfs2/export.c +++ b/fs/ocfs2/export.c @@ -37,6 +37,7 @@ static struct dentry *ocfs2_get_dentry(struct super_block *sb, struct inode *inode; struct ocfs2_super *osb = OCFS2_SB(sb); u64 blkno = handle->ih_blkno; + unsigned int nofs_flag = 0; int status, set; struct dentry *result; @@ -59,7 +60,7 @@ static struct dentry *ocfs2_get_dentry(struct super_block *sb, * This will synchronize us against ocfs2_delete_inode() on * all nodes */ - status = ocfs2_nfs_sync_lock(osb, 1); + status = ocfs2_nfs_sync_lock(osb, 1, &nofs_flag); if (status < 0) { mlog(ML_ERROR, "getting nfs sync lock(EX) failed %d\n", status); goto check_err; @@ -90,7 +91,7 @@ static struct dentry *ocfs2_get_dentry(struct super_block *sb, inode = ocfs2_iget(osb, blkno, 0, 0); unlock_nfs_sync: - ocfs2_nfs_sync_unlock(osb, 1); + ocfs2_nfs_sync_unlock(osb, 1, nofs_flag); check_err: if (status < 0) { @@ -133,12 +134,13 @@ static struct dentry *ocfs2_get_parent(struct dentry *child) u64 blkno; struct dentry *parent; struct inode *dir = d_inode(child); + unsigned int nofs_flag = 0; int set; trace_ocfs2_get_parent(child, child->d_name.len, child->d_name.name, (unsigned long long)OCFS2_I(dir)->ip_blkno); - status = ocfs2_nfs_sync_lock(OCFS2_SB(dir->i_sb), 1); + status = ocfs2_nfs_sync_lock(OCFS2_SB(dir->i_sb), 1, &nofs_flag); if (status < 0) { mlog(ML_ERROR, "getting nfs sync lock(EX) failed %d\n", status); parent = ERR_PTR(status); @@ -183,7 +185,7 @@ static struct dentry *ocfs2_get_parent(struct dentry *child) ocfs2_inode_unlock(dir, 0); unlock_nfs_sync: - ocfs2_nfs_sync_unlock(OCFS2_SB(dir->i_sb), 1); + ocfs2_nfs_sync_unlock(OCFS2_SB(dir->i_sb), 1, nofs_flag); bail: trace_ocfs2_get_parent_end(parent); diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index 92f3450010fb..80c36c60a8ce 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -1109,6 +1109,7 @@ static void ocfs2_delete_inode(struct inode *inode) { int wipe, status; sigset_t oldset; + unsigned int nofs_flag = 0; struct buffer_head *di_bh = NULL; struct ocfs2_dinode *di = NULL; @@ -1143,7 +1144,7 @@ static void ocfs2_delete_inode(struct inode *inode) * shared mode so that all nodes can still concurrently * process deletes. */ - status = ocfs2_nfs_sync_lock(OCFS2_SB(inode->i_sb), 0); + status = ocfs2_nfs_sync_lock(OCFS2_SB(inode->i_sb), 0, &nofs_flag); if (status < 0) { mlog(ML_ERROR, "getting nfs sync lock(PR) failed %d\n", status); ocfs2_cleanup_delete_inode(inode, 0); @@ -1215,7 +1216,7 @@ static void ocfs2_delete_inode(struct inode *inode) brelse(di_bh); bail_unlock_nfs_sync: - ocfs2_nfs_sync_unlock(OCFS2_SB(inode->i_sb), 0); + ocfs2_nfs_sync_unlock(OCFS2_SB(inode->i_sb), 0, nofs_flag); bail_unblock: ocfs2_unblock_signals(&oldset); From b10ef5d2c2083ea4c40e963c10a73a154793c5d4 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sun, 20 Sep 2026 21:07:10 +0700 Subject: [PATCH 085/134] ocfs2: validate chunk and block counts of the local quota file ocfs2_local_read_info() trusts dqi_chunks and dqi_blocks from the local quota file header. If dqi_blocks is too small, ocfs2_extend_local_quota_file() computes a bogus chunk length, extends the file, and maps logical block dqi_blocks, which is already in the inode's metadata cache (the header or a chunk header read at mount). sb_getblk() returns that cached buffer and ocfs2_set_new_buffer_uptodate() hits BUG_ON(ocfs2_buffer_cached()): kernel BUG at fs/ocfs2/uptodate.c:509! ocfs2_extend_local_quota_file+0x45c/0x1100 ocfs2_create_local_dquot+0x8ac/0xb50 ocfs2_acquire_dquot+0x614/0xae0 ocfs2_get_init_inode+0xe9/0x1b0 ocfs2_mkdir+0x174/0x430 ocfs2_local_quota_add_chunk() has the same exposure. Reject a header whose counts don't fit the chunk layout or exceed i_size. Reproduced with a crafted image (dqi_blocks = 1, chunk 0 dqc_free = 0); syzbot has no reproducer for this report. Link: https://lore.kernel.org/20260920140710.43938-1-ngocthang2710.1999@gmail.com Fixes: 9e33d69f553a ("ocfs2: Implementation of local and global quota file handling") Signed-off-by: Nguyen Ngoc Thang Signed-off-by: Andrew Morton Reported-by: syzbot+03aaa576f1daa1c1f2f0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=03aaa576f1daa1c1f2f0 Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Heming Zhao --- fs/ocfs2/quota_local.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c index d351cda9211f..76e7dd5aecc8 100644 --- a/fs/ocfs2/quota_local.c +++ b/fs/ocfs2/quota_local.c @@ -245,6 +245,25 @@ static void ocfs2_release_local_quota_bitmaps(struct list_head *head) } } +/* Check that the on-disk chunk and block counts match the file layout */ +static int ocfs2_check_local_quota_info(struct inode *inode, + unsigned int chunks, + unsigned int blocks) +{ + u64 chunk_len = ol_chunk_blocks(inode->i_sb) + 1; + u64 min_blocks = chunks ? 1 + chunk_len * (chunks - 1) + 1 : 1; + u64 max_blocks = 1 + chunk_len * chunks; + + if (blocks >= min_blocks && blocks <= max_blocks && + blocks <= i_size_read(inode) >> inode->i_sb->s_blocksize_bits) + return 0; + + return ocfs2_error(inode->i_sb, + "Quota file %llu has bad info: %u chunks, %u blocks\n", + (unsigned long long)OCFS2_I(inode)->ip_blkno, + chunks, blocks); +} + /* Load quota bitmaps into memory */ static int ocfs2_load_local_quota_bitmaps(struct inode *inode, struct ocfs2_local_disk_dqinfo *ldinfo, @@ -733,6 +752,11 @@ static int ocfs2_local_read_info(struct super_block *sb, int type) oinfo->dqi_blocks = le32_to_cpu(ldinfo->dqi_blocks); oinfo->dqi_libh = bh; + status = ocfs2_check_local_quota_info(lqinode, oinfo->dqi_chunks, + oinfo->dqi_blocks); + if (status < 0) + goto out_err; + /* We crashed when using local quota file? */ if (!(oinfo->dqi_flags & OLQF_CLEAN)) { rec = OCFS2_SB(sb)->quota_rec; From 6193ce7896afb52a321a0063c8b6a45e477a84e2 Mon Sep 17 00:00:00 2001 From: Giorgi Kobakhia Date: Tue, 22 Sep 2026 13:21:59 -0700 Subject: [PATCH 086/134] ocfs2: fix array out of bound access in __ocfs2_find_path() __ocfs2_find_path() walks down the extent tree and records path by calling find_path_ins(), which appends entry to path->p_node[]. It only has 5 spots. A corrupted ocfs2 image whose extent block is pointing to itself causes __ocfs2_find_path() descent endlessly, writing past the end of path->p_node[] array. UBSAN: array-index-out-of-bounds in fs/ocfs2/alloc.c:677:14 index 5 is out of range for type 'ocfs2_path_item [5]' Call Trace: find_path_ins (fs/ocfs2/alloc.c:677 fs/ocfs2/alloc.c:1914) __ocfs2_find_path.constprop.0 (fs/ocfs2/alloc.c:1882) ocfs2_commit_truncate (fs/ocfs2/alloc.c:1924 fs/ocfs2/alloc.c:7286) ocfs2_truncate_file (fs/ocfs2/file.c:515) ocfs2_setattr (fs/ocfs2/file.c:1224) notify_change (fs/attr.c:556) do_truncate (fs/open.c:68) do_ftruncate (fs/open.c:194 (discriminator 1)) ksys_ftruncate (fs/open.c:206) __x64_sys_ftruncate (fs/open.c:211 fs/open.c:209 fs/open.c:209) Commit a406aff8c051 ("ocfs2: validate l_tree_depth to avoid out-of-bounds access") already restricts el->l_tree_depth to be less than OCFS2_MAX_PATH_DEPTH, which is equal to 5. However, does not handle the infinite descent case. Check if the el->l_tree_depth decreases on each descent. Maximum descents are restricted to 4 and the path->p_node[] array does not overflow. Link: https://lore.kernel.org/20260922202159.2421642-1-gkobakhi@asu.edu Fixes: dcd0538ff4e8 ("ocfs2: sparse b-tree support") Signed-off-by: Giorgi Kobakhia Signed-off-by: Andrew Morton Tested-by: Xiang Mei Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: --- fs/ocfs2/alloc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/ocfs2/alloc.c b/fs/ocfs2/alloc.c index 2fdc5403b10b..fc949e594626 100644 --- a/fs/ocfs2/alloc.c +++ b/fs/ocfs2/alloc.c @@ -1844,6 +1844,7 @@ static int __ocfs2_find_path(struct ocfs2_caching_info *ci, int i, ret = 0; u32 range; u64 blkno; + u32 prev_depth = OCFS2_MAX_PATH_DEPTH; struct buffer_head *bh = NULL; struct ocfs2_extent_block *eb; struct ocfs2_extent_list *el; @@ -1851,14 +1852,16 @@ static int __ocfs2_find_path(struct ocfs2_caching_info *ci, el = root_el; while (el->l_tree_depth) { - if (unlikely(le16_to_cpu(el->l_tree_depth) >= OCFS2_MAX_PATH_DEPTH)) { + if (unlikely(le16_to_cpu(el->l_tree_depth) >= prev_depth)) { ocfs2_error(ocfs2_metadata_cache_get_super(ci), - "Owner %llu has invalid tree depth %u in extent list\n", + "Owner %llu has invalid tree depth %u in extent list (max %u)\n", (unsigned long long)ocfs2_metadata_cache_owner(ci), - le16_to_cpu(el->l_tree_depth)); + le16_to_cpu(el->l_tree_depth), prev_depth - 1); ret = -EROFS; goto out; } + prev_depth = le16_to_cpu(el->l_tree_depth); + if (!el->l_next_free_rec || !el->l_count) { ocfs2_error(ocfs2_metadata_cache_get_super(ci), "Owner %llu has empty extent list at depth %u\n" From aa2e38aabaae87236290e486a2b77cfa3adfbebe Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 23 Sep 2026 03:01:49 +0200 Subject: [PATCH 087/134] ocfs2/cluster: hold a reference on the heartbeat thread Since commit 688bc88e2046 ("ocfs2/cluster: keep heartbeat local node stable"), o2hb_thread() leaves its loop and returns when the local node changes, for example after "echo 0 > node//local". The thread was started with kthread_run() and nothing holds a reference to its task_struct, so the task is freed once it exits, while reg->hr_task still points to it. Reading the region's pid attribute then reads the freed task, and removing the region calls kthread_stop() on it: BUG: KASAN: slab-use-after-free in o2hb_region_pid_show+0xb3/0xc0 refcount_t: addition on 0; use-after-free. Oops: Oops: 0000 [#1] SMP KASAN NOPTI RIP: 0010:kthread_stop+0xb1/0x390 The thread could already return by itself before, when heartbeat start was aborted or on an unclean stop, but the local node change makes it reachable from userspace at any time. Create the thread, take a reference on it and only then wake it, so the reference cannot race with the thread exiting. Drop it with kthread_stop_put(). Link: https://lore.kernel.org/20260923010149.14391-1-kmehltretter@gmail.com Fixes: 688bc88e2046 ("ocfs2/cluster: keep heartbeat local node stable") Signed-off-by: Karl Mehltretter Signed-off-by: Andrew Morton Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260616074931.3774929-1-zzzccc427%40gmail.com Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Cen Zhang Cc: Junxiao Bi Cc: Heming Zhao --- fs/ocfs2/cluster/heartbeat.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index 1c3def99bb07..a4c8ea695f5c 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -1966,18 +1966,22 @@ static ssize_t o2hb_region_dev_store(struct config_item *item, atomic_set(®->hr_unsteady_iterations, (live_threshold * 3)); o2hb_set_region_stopping(reg, false); - hb_task = kthread_run(o2hb_thread, reg, "o2hb-%s", - reg->hr_item.ci_name); + hb_task = kthread_create(o2hb_thread, reg, "o2hb-%s", + reg->hr_item.ci_name); if (IS_ERR(hb_task)) { ret = PTR_ERR(hb_task); mlog_errno(ret); goto out; } + /* The thread may exit on its own, so pin it before it can run. */ + get_task_struct(hb_task); spin_lock(&o2hb_live_lock); reg->hr_task = hb_task; spin_unlock(&o2hb_live_lock); + wake_up_process(hb_task); + ret = wait_event_interruptible(o2hb_steady_queue, atomic_read(®->hr_steady_iterations) == 0 || reg->hr_node_deleted); @@ -2022,7 +2026,7 @@ static ssize_t o2hb_region_dev_store(struct config_item *item, spin_unlock(&o2hb_live_lock); if (hb_task) - kthread_stop(hb_task); + kthread_stop_put(hb_task); o2hb_unmap_slot_data(reg); @@ -2208,7 +2212,7 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group, spin_unlock(&o2hb_live_lock); if (hb_task) - kthread_stop(hb_task); + kthread_stop_put(hb_task); if (o2hb_global_heartbeat_active()) { spin_lock(&o2hb_live_lock); From d25f15a76c7a7f0e20a2b4b36f5108ef115a6d1f Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Thu, 24 Sep 2026 11:39:23 +0800 Subject: [PATCH 088/134] checkpatch: don't flag ACQUIRE_ERR() assignments in if conditions ACQUIRE_ERR() and its wrappers, PM_RUNTIME_ACQUIRE_ERR() and IIO_DEV_ACQUIRE_FAILED(), report whether a conditional cleanup.h guard was acquired, and drivers consume the result directly in an if condition: if ((rc = ACQUIRE_ERR(mutex_intr, &lock))) return rc; That combined form is the established style at the 49 in-tree call sites under drivers/cxl and drivers/pci/tsm.c, so ASSIGN_IN_IF fires there only as a false positive, and every patch touching those lines carries noise that reviewers have to wave off manually. Skip the check only when every assignment in the condition assigns the result of such a call, matched by the *_ACQUIRE_ERR() / *_ACQUIRE_FAILED() naming convention of its wrappers. Plain assignments, mixed conditions and near-miss identifiers still get flagged. Link: https://lore.kernel.org/20260924033923.4140210-1-kanie@linux.alibaba.com Signed-off-by: Guixin Liu Signed-off-by: Andrew Morton Suggested-by: Alison Schofield Acked-by: Joe Perches Assisted-by: LLM Cc: Andy Whitcroft Cc: Jonathan Cameron --- scripts/checkpatch.pl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 3614cfe4dcbb..598e3ed743bd 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -5787,7 +5787,21 @@ sub process { my ($s, $c) = ($stat, $cond); my $fixed_assign_in_if = 0; + # ACQUIRE_ERR() and its wrappers, e.g. PM_RUNTIME_ACQUIRE_ERR() + # and IIO_DEV_ACQUIRE_FAILED(), are meant to be evaluated in an + # if condition, with the error assigned in the condition: + # if ((rc = ACQUIRE_ERR(name, &lock))) + # Allow that only when every assignment in the condition assigns + # the result of such a call, so that a mixed condition keeps + # getting flagged: + # if ((rc = regular_function()) || (ret = ACQUIRE_ERR(name, &lock))) + my $assign_in_if = 0; if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { + my $has_assignment = $c =~ /\b$Lval\s*=\s*[^,)&|=]+/; + my $has_other_assignment = $c =~ /\b$Lval\s*=\s*(?!\s*\w*ACQUIRE_(?:ERR|FAILED)\s*\()[^,)&|=]+/; + $assign_in_if = !$has_assignment || $has_other_assignment; + } + if ($assign_in_if) { if (ERROR("ASSIGN_IN_IF", "do not use assignment in if condition\n" . $herecurr) && $fix && $perl_version_ok) { From d05de2360a3e25aa3ec24f460802755a95e8e6e9 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Fri, 25 Sep 2026 15:55:02 -0500 Subject: [PATCH 089/134] kselftest/filelock: plan for the five ofdlocks tests ofdlocks reports five results but plans for four, so even when they all pass it ends with # Planned tests != run tests (4 != 5) and exits with KSFT_FAIL, which run_kselftest.sh reports as a failure. Link: https://lore.kernel.org/20260925205502.115327-1-danishkhateeb03@gmail.com Fixes: 33d5b13098fb ("kselftest/filelock: report each test in oftlocks separately") Signed-off-by: Danish Khateeb Signed-off-by: Andrew Morton Assisted-by: LLM Cc: Shuah Khan Cc: Mark Brown Cc: Jeff Layton --- tools/testing/selftests/filelock/ofdlocks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/filelock/ofdlocks.c b/tools/testing/selftests/filelock/ofdlocks.c index 68bac28b234b..0ab484cb075b 100644 --- a/tools/testing/selftests/filelock/ofdlocks.c +++ b/tools/testing/selftests/filelock/ofdlocks.c @@ -40,7 +40,7 @@ int main(void) int fd2 = open("/tmp/aa", O_RDONLY); ksft_print_header(); - ksft_set_plan(4); + ksft_set_plan(5); unlink("/tmp/aa"); assert(fd != -1); From 67b509c23fa98fc87793b08474fabc8edf2b0836 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Mon, 28 Sep 2026 06:49:58 +0800 Subject: [PATCH 090/134] kdev_t: shift in dev_t in MKDEV() MKDEV(ma, mi) evaluates ((ma) << MINORBITS) in whatever type the caller passes, usually int. For majors >= 2048 (legal: majors go up to 4095) the result exceeds INT_MAX, which C11 leaves undefined for a signed left shift; shifting a negative ma is undefined regardless of the major's value. In practice, on the compilers and two's complement targets the kernel supports, both cases wrap to the intended bit pattern; this patch makes the macro well defined regardless by casting the major to dev_t before shifting. For major and minor numbers in range, the device number it produces is unchanged. isofs feeds the Rock Ridge 'PN' entry's dev_high and dev_low into MKDEV() while holding them in int variables (fs/isofs/rock.c), so a crafted image can drive this exact shift. Fixing the macro covers every caller instead of just that one. Link: https://lore.kernel.org/20260927224958.508964-1-matthias.goergens@gmail.com Signed-off-by: Matthias Goergens Signed-off-by: Andrew Morton Reviewed-by: Jan Kara Cc: Greg Kroah-Hartman --- include/linux/kdev_t.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/kdev_t.h b/include/linux/kdev_t.h index 4856706fbfeb..2dbbd47f1e68 100644 --- a/include/linux/kdev_t.h +++ b/include/linux/kdev_t.h @@ -9,7 +9,7 @@ #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) -#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) +#define MKDEV(ma, mi) (((dev_t)(ma) << MINORBITS) | (mi)) #define print_dev_t(buffer, dev) \ sprintf((buffer), "%u:%u\n", MAJOR(dev), MINOR(dev)) From 7c3f4d699f34535285d0ceb184927d71543d0064 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 26 Sep 2026 02:26:51 +0200 Subject: [PATCH 091/134] resource, kunit: stop selecting GET_FREE_REGION RESOURCE_KUNIT_TEST selects GET_FREE_REGION even when no other option needs it. Remove the selection to follow the dependency rule in Documentation/dev-tools/kunit/style.rst. Skip resource_test_region_intersects() when GET_FREE_REGION is disabled. GET_FREE_REGION has no prompt, so configurations without a production consumer cannot enable it. Most configurations will therefore skip this case; this is intentional. The union and intersection tests remain available. Since kunit_skip() does not return, the compiler drops the reference to the unavailable alloc_free_mem_region(), as in the CONFIG_OF_ADDRESS check in drivers/of/of_test.c. Link: https://lore.kernel.org/20260926002651.87267-1-kmehltretter@gmail.com Fixes: 99185c10d5d9 ("resource, kunit: add test case for region_intersects()") Signed-off-by: Karl Mehltretter Signed-off-by: Andrew Morton Reviewed-by: Bradley Morgan Tested-by: Bradley Morgan # Power10 Assisted-by: LLM Cc: Ying Huang Cc: Geert Uytterhoeven Cc: Brendan Higgins Cc: David Gow Cc: Rae Moar Cc: Andy Shevchenko --- kernel/resource_kunit.c | 3 +++ lib/Kconfig.debug | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/resource_kunit.c b/kernel/resource_kunit.c index 42785796f1db..9eeb2b1a85c0 100644 --- a/kernel/resource_kunit.c +++ b/kernel/resource_kunit.c @@ -225,6 +225,9 @@ static void resource_test_region_intersects(struct kunit *test) struct resource *parent; resource_size_t start; + if (!IS_ENABLED(CONFIG_GET_FREE_REGION)) + kunit_skip(test, "CONFIG_GET_FREE_REGION is disabled"); + /* Find an iomem_resource hole to hold test resources */ parent = alloc_free_mem_region(&iomem_resource, RES_TEST_TOTAL_SIZE, SZ_1M, "test resources"); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index c3f448f3b8f1..56228eefdb4a 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2776,7 +2776,6 @@ config RESOURCE_KUNIT_TEST tristate "KUnit test for resource API" if !KUNIT_ALL_TESTS depends on KUNIT default KUNIT_ALL_TESTS - select GET_FREE_REGION help This builds the resource API unit test. Tests the logic of API provided by resource.c and ioport.h. From a7f476e2626098cf3c1af4f1038df8914a19fa13 Mon Sep 17 00:00:00 2001 From: Yuntao Wang Date: Wed, 30 Sep 2026 09:57:48 +0800 Subject: [PATCH 092/134] init: simplify initramfs.o build rule CONFIG_BLK_DEV_INITRD is known to be enabled in the else branch, so there is no need to use obj-$(CONFIG_BLK_DEV_INITRD). Use obj-y directly to make the build rule clearer. Also, reorder the condition to make the code easier to follow. No functional change. Link: https://lore.kernel.org/20260930015748.311366-1-yuntao.wang@linux.dev Signed-off-by: Yuntao Wang Signed-off-by: Andrew Morton Reviewed-by: Nicolas Schier Cc: Nathan Chancellor --- init/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/init/Makefile b/init/Makefile index d6f75d8907e0..9102fc34c0cc 100644 --- a/init/Makefile +++ b/init/Makefile @@ -6,10 +6,10 @@ ccflags-y := -fno-function-sections -fno-data-sections obj-y := main.o version.o mounts.o -ifneq ($(CONFIG_BLK_DEV_INITRD),y) -obj-y += noinitramfs.o +ifeq ($(CONFIG_BLK_DEV_INITRD),y) +obj-y += initramfs.o else -obj-$(CONFIG_BLK_DEV_INITRD) += initramfs.o +obj-y += noinitramfs.o endif obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o obj-$(CONFIG_INITRAMFS_TEST) += initramfs_test.o From 4719c9264131b88fef209dc3342c23d3ac5356bd Mon Sep 17 00:00:00 2001 From: Konstantin Khorenko Date: Thu, 1 Oct 2026 18:57:11 +0200 Subject: [PATCH 093/134] kbuild: use $(CFLAGS_GCOV) in the prefer-atomic try-run test Patch series "kbuild: GCOV cleanups following the prefer-atomic fix". Two cleanups Peter Oberparleiter suggested while reviewing commit 56cb9b7d96b2 ("gcov: use atomic counter updates to fix concurrent access crashes"): https://lore.kernel.org/e1b6e768-51af-468b-a729-0ed3fceef2f1@linux.ibm.com Patch 1 makes the prefer-atomic try-run test reuse $(CFLAGS_GCOV) instead of spelling the flags out again, so the test compiles with the exact GCOV flags, -fno-tree-loop-im included. Patch 2 moves the CFLAGS_GCOV block into scripts/Makefile.gcov, included via include-$(CONFIG_GCOV_KERNEL) like the other instrumentation makefiles. The try-run also stops running on builds without GCOV. This patch (of 2): Reference $(CFLAGS_GCOV) in the try-run test introduced by commit 56cb9b7d96b2 ("gcov: use atomic counter updates to fix concurrent access crashes"), otherwise it can miss potential future changes in the list of flags used for GCOV profiling like it misses -fno-tree-loop-im at the moment. The outcome of the test is unchanged: -fno-tree-loop-im does not affect the undefined symbols the test compares. Link: https://lore.kernel.org/20261001-b4-prep-gcov-v1-0-794792e06f24@virtuozzo.com Link: https://lore.kernel.org/20261001-b4-prep-gcov-v1-1-794792e06f24@virtuozzo.com Signed-off-by: Konstantin Khorenko Signed-off-by: Andrew Morton Suggested-by: Peter Oberparleiter Cc: Nathan Chancellor Cc: Nicolas Schier Cc: Masahiro Yamada Cc: Arnd Bergmann Cc: Mikhail Zaslonko --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 6b8b812f23b9..3b69b973f759 100644 --- a/Makefile +++ b/Makefile @@ -1184,11 +1184,11 @@ CFLAGS_GCOV += -fno-tree-loop-im # undefined symbols (e.g. libatomic calls that the kernel cannot link). CFLAGS_GCOV += $(call try-run,\ echo 'long long x; void f(void){x++;}' | \ - $(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) -w -fprofile-arcs \ - -ftest-coverage -x c - -c -o "$$TMP.base" && \ + $(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) -w $(CFLAGS_GCOV) \ + -x c - -c -o "$$TMP.base" && \ echo 'long long x; void f(void){x++;}' | \ - $(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) -w -fprofile-arcs \ - -ftest-coverage -fprofile-update=prefer-atomic \ + $(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) -w $(CFLAGS_GCOV) \ + -fprofile-update=prefer-atomic \ -x c - -c -o "$$TMP" && \ $(NM) "$$TMP.base" | grep ' U ' > "$$TMP.ubase" || true ; \ $(NM) "$$TMP" | grep ' U ' > "$$TMP.utest" || true ; \ From f541906e89f9d08ef29f058b6cef1db071cd8163 Mon Sep 17 00:00:00 2001 From: Konstantin Khorenko Date: Thu, 1 Oct 2026 18:57:12 +0200 Subject: [PATCH 094/134] kbuild: move GCOV flags to scripts/Makefile.gcov * split the CFLAGS_GCOV block out of the top-level Makefile into scripts/Makefile.gcov * include it via include-$(CONFIG_GCOV_KERNEL) like neighbour KASAN/KCOV/UBSAN makefiles do * keep the include before scripts/Makefile.gcc-plugins: the try-run must not get -fplugin= options, plugins do not exist on clean builds CFLAGS_GCOV is no longer defined when CONFIG_GCOV_KERNEL is off: * its user in scripts/Makefile.lib is guarded by the CONFIG_GCOV_KERNEL * in i915/xe header an empty value is harmless The try-run compiler invocations also stop running on builds without GCOV. Link: https://lore.kernel.org/20261001-b4-prep-gcov-v1-2-794792e06f24@virtuozzo.com Signed-off-by: Konstantin Khorenko Signed-off-by: Andrew Morton Suggested-by: Peter Oberparleiter Cc: Nathan Chancellor Cc: Nicolas Schier Cc: Masahiro Yamada Cc: Arnd Bergmann Cc: Mikhail Zaslonko --- MAINTAINERS | 1 + Makefile | 22 +--------------------- scripts/Makefile.gcov | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+), 21 deletions(-) create mode 100644 scripts/Makefile.gcov diff --git a/MAINTAINERS b/MAINTAINERS index d642d4168c28..bd6619bc107d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10899,6 +10899,7 @@ M: Peter Oberparleiter S: Maintained F: Documentation/dev-tools/gcov.rst F: kernel/gcov/ +F: scripts/Makefile.gcov GDB KERNEL DEBUGGING HELPER SCRIPTS M: Jan Kiszka diff --git a/Makefile b/Makefile index 3b69b973f759..f3ea94ceb1ce 100644 --- a/Makefile +++ b/Makefile @@ -1176,27 +1176,6 @@ endif # Ensure compilers do not transform certain loops into calls to wcslen() KBUILD_CFLAGS += -fno-builtin-wcslen -CFLAGS_GCOV := -fprofile-arcs -ftest-coverage -ifdef CONFIG_CC_IS_GCC -CFLAGS_GCOV += -fno-tree-loop-im -# Use atomic counter updates to avoid concurrent-access crashes in GCOV. -# Only enable if -fprofile-update=prefer-atomic does not introduce new -# undefined symbols (e.g. libatomic calls that the kernel cannot link). -CFLAGS_GCOV += $(call try-run,\ - echo 'long long x; void f(void){x++;}' | \ - $(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) -w $(CFLAGS_GCOV) \ - -x c - -c -o "$$TMP.base" && \ - echo 'long long x; void f(void){x++;}' | \ - $(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) -w $(CFLAGS_GCOV) \ - -fprofile-update=prefer-atomic \ - -x c - -c -o "$$TMP" && \ - $(NM) "$$TMP.base" | grep ' U ' > "$$TMP.ubase" || true ; \ - $(NM) "$$TMP" | grep ' U ' > "$$TMP.utest" || true ; \ - cmp -s "$$TMP.ubase" "$$TMP.utest",\ - -fprofile-update=prefer-atomic) -endif -export CFLAGS_GCOV - # change __FILE__ to the relative path to the source directory ifdef building_out_of_srctree KBUILD_CPPFLAGS += -fmacro-prefix-map=$(srcroot)/= @@ -1214,6 +1193,7 @@ include-$(CONFIG_KCSAN) += scripts/Makefile.kcsan include-$(CONFIG_KMSAN) += scripts/Makefile.kmsan include-$(CONFIG_UBSAN) += scripts/Makefile.ubsan include-$(CONFIG_KCOV) += scripts/Makefile.kcov +include-$(CONFIG_GCOV_KERNEL) += scripts/Makefile.gcov include-$(CONFIG_RANDSTRUCT) += scripts/Makefile.randstruct include-$(CONFIG_KSTACK_ERASE) += scripts/Makefile.kstack_erase include-$(CONFIG_AUTOFDO_CLANG) += scripts/Makefile.autofdo diff --git a/scripts/Makefile.gcov b/scripts/Makefile.gcov new file mode 100644 index 000000000000..16c5b876440c --- /dev/null +++ b/scripts/Makefile.gcov @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only +CFLAGS_GCOV := -fprofile-arcs -ftest-coverage +ifdef CONFIG_CC_IS_GCC +CFLAGS_GCOV += -fno-tree-loop-im +# Use atomic counter updates to avoid concurrent-access crashes in GCOV. +# Only enable if -fprofile-update=prefer-atomic does not introduce new +# undefined symbols (e.g. libatomic calls that the kernel cannot link). +CFLAGS_GCOV += $(call try-run,\ + echo 'long long x; void f(void){x++;}' | \ + $(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) -w $(CFLAGS_GCOV) \ + -x c - -c -o "$$TMP.base" && \ + echo 'long long x; void f(void){x++;}' | \ + $(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) -w $(CFLAGS_GCOV) \ + -fprofile-update=prefer-atomic \ + -x c - -c -o "$$TMP" && \ + $(NM) "$$TMP.base" | grep ' U ' > "$$TMP.ubase" || true ; \ + $(NM) "$$TMP" | grep ' U ' > "$$TMP.utest" || true ; \ + cmp -s "$$TMP.ubase" "$$TMP.utest",\ + -fprofile-update=prefer-atomic) +endif +export CFLAGS_GCOV From 5d9f31ed855fa89e533b222d0365da81ad3b10cc Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 2 Oct 2026 20:13:57 +0200 Subject: [PATCH 095/134] kcov: report spurious PCs in the interrupt selftest The KCOV interrupt selftest enables KCOV_MODE_TRACE_PC without a coverage area so that spurious coverage causes a fault. If the fault path is instrumented, the coverage callback faults recursively. Observed failure modes include a stack overflow on x86_64 and arm32 getting stuck in abort handling, neither of which identifies the original coverage event. The recursive failure is not new, but commit 9a79524d1420 ("kcov: use WRITE_ONCE() for selftest mode stores") made the selftest effective on configurations where the compiler had previously removed the mode store, exposing it more broadly. Replace the open-coded kcov_mode stores with kcov_start() and kcov_stop(), passing a 16-word buffer that records up to 15 spurious PCs, with the first word holding the count. Keep KCOV enabled for the original 300 ms window, then disable it, print all recorded PCs and panic if any were recorded. Once the buffer fills, further PCs are discarded by the existing coverage callback. This preserves the selftest's hard failure while avoiding the recursive fault path. Add decanonicalize_ip() as the inverse of canonicalize_ip(), which subtracts kaslr_offset() from recorded PCs. Restore that offset before printing the PCs with %pB, since KCOV records return addresses. Link: https://lore.kernel.org/20261002181357.14293-1-kmehltretter@gmail.com Fixes: 6cd0dd934b03 ("kcov: Add interrupt handling self test") Signed-off-by: Karl Mehltretter Signed-off-by: Andrew Morton Tested-by: Alexander Potapenko Reviewed-by: Alexander Potapenko Tested-by: Bradley Morgan # Power10 Reviewed-by: Bradley Morgan Assisted-by: LLM Cc: Andrey Konovalov Cc: Dmitry Vyukov Cc: Marco Elver Cc: Bradley Morgan Cc: Thomas Gleixner --- kernel/kcov.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/kernel/kcov.c b/kernel/kcov.c index 54eaae97bfd6..9402227bd7ba 100644 --- a/kernel/kcov.c +++ b/kernel/kcov.c @@ -198,6 +198,16 @@ static notrace unsigned long canonicalize_ip(unsigned long ip) return ip; } +#ifdef CONFIG_KCOV_SELFTEST +static unsigned long __init decanonicalize_ip(unsigned long ip) +{ +#ifdef CONFIG_RANDOMIZE_BASE + ip += kaslr_offset(); +#endif + return ip; +} +#endif + /* * Entry point from instrumented code. * This is called once per basic-block/edge. @@ -1107,9 +1117,11 @@ struct kcov_common_handle_id kcov_common_handle(void) EXPORT_SYMBOL(kcov_common_handle); #ifdef CONFIG_KCOV_SELFTEST +static unsigned long selftest_area[16] __initdata; + static void __init selftest(void) { - unsigned long start; + unsigned long start, i, ip, count; pr_err("running self test\n"); /* @@ -1119,15 +1131,26 @@ static void __init selftest(void) * leaks out of that section and leads to spurious coverage. * It's hard to call the actual interrupt handler directly, * so we just loop here for a bit waiting for a timer interrupt. - * We set kcov_mode to enable tracing, but don't setup the area, - * so any attempt to trace will crash. Note: we must not call any + * A small coverage area records the PCs of any leaks. The first word + * holds the count, leaving room for 15 PCs. Note: we must not call any * potentially traced functions in this region. */ + kcov_start(current, NULL, ARRAY_SIZE(selftest_area), + selftest_area, KCOV_MODE_TRACE_PC, 0); start = jiffies; - WRITE_ONCE(current->kcov_mode, KCOV_MODE_TRACE_PC); while ((jiffies - start) * MSEC_PER_SEC / HZ < 300) - ; - WRITE_ONCE(current->kcov_mode, 0); + cpu_relax(); + kcov_stop(current); + + count = READ_ONCE(selftest_area[0]); + if (count) { + pr_err("spurious coverage detected during interrupt selftest:\n"); + for (i = 1; i <= count; i++) { + ip = decanonicalize_ip(selftest_area[i]); + pr_err(" %pB\n", (void *)ip); + } + panic("kcov: interrupt selftest detected spurious coverage"); + } pr_err("done running self test\n"); } #endif From b682d597aa273cad6e99e665e0fd2a7eb8aa2cef Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sat, 3 Oct 2026 11:02:00 +0000 Subject: [PATCH 096/134] watchdog/perf: one digit too short in the raw event config copy Commit 6164be01f179 ("watchdog/perf: optimize bytes copied and remove manual NUL-termination") swapped strscpy(buf, str, sizeof(buf)) plus a manual buf[len] = 0 for strscpy(buf, str, len), and that count is one short. strscpy() keeps the last byte of the destination for the NUL, so the config loses its final digit, nmi_watchdog=r300,panic for example ends up with buf = "30" and arms the raw event with the wrong config. The empty case falls over too, strscpy() with a zero count writes nothing at all, so nmi_watchdog=r,1 leaves buf uninitialized and kstrtoull() reads whatever stack garbage is sitting there. The old code was safe on both by accident, it filled the whole buffer first and buf[len] = 0 then stomped the comma position, so the worst you got was a truncated parse failure. Pass len + 1 so the copy includes the character the NUL replaces, and reject len >= sizeof(buf) like it was before the optimization, which also turns an empty config back into a clean parse failure. Link: https://lore.kernel.org/20261003110200.3-brads@mainlining.org Fixes: 6164be01f179 ("watchdog/perf: optimize bytes copied and remove manual NUL-termination") Signed-off-by: Bradley Morgan Signed-off-by: Andrew Morton Reviewed-by: Douglas Anderson Cc: Thorsten Blum Cc: Thomas Gleixner --- kernel/watchdog_perf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/watchdog_perf.c b/kernel/watchdog_perf.c index cf05775a96d3..a4f677c16b20 100644 --- a/kernel/watchdog_perf.c +++ b/kernel/watchdog_perf.c @@ -301,10 +301,10 @@ void __init hardlockup_config_perf_event(const char *str) } else { unsigned int len = comma - str; - if (len > sizeof(buf)) + if (len >= sizeof(buf)) return; - strscpy(buf, str, len); + strscpy(buf, str, len + 1); if (kstrtoull(buf, 16, &config)) return; } From f439d79e8cd19f89e04cbb3b532347edacd72c79 Mon Sep 17 00:00:00 2001 From: Kazuki Hanai Date: Fri, 2 Oct 2026 17:13:03 +0900 Subject: [PATCH 097/134] tmpfs: fix unicode_map leaks in casefold option handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shmem_parse_opt_casefold() stores the unicode_map returned by utf8_load() in ctx->encoding. A filesystem context can receive the casefold parameter more than once, and a second successful parse overwrites the stored map without releasing it. The map is also leaked when an unmounted filesystem context is freed. This includes the temporary context used for remount, because shmem_reconfigure() does not take ownership of ctx->encoding. Reject a second casefold setting, clear ctx->encoding after transferring ownership to the superblock, and release any remaining map from shmem_free_fc(). An unprivileged user can repeatedly create tmpfs filesystem contexts, set the casefold parameter, and close them from a user namespace. This causes unbounded kernel memory consumption and can result in a local denial of service. Link: https://lore.kernel.org/20261002081306.637148-1-hnkz.64@gmail.com Fixes: 58e55efd6c72 ("tmpfs: Add casefold lookup support") Signed-off-by: Kazuki Hanai Signed-off-by: Andrew Morton Suggested-by: Gabriel Krisman Bertazi Cc: Hugh Dickins Cc: Baolin Wang Cc: André Almeida Cc: Christian Brauner Cc: --- mm/shmem.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mm/shmem.c b/mm/shmem.c index 848316eaa7f4..1bd004c3dde2 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -4509,6 +4509,9 @@ static int shmem_parse_opt_casefold(struct fs_context *fc, struct fs_parameter * struct unicode_map *encoding; char *version_str = param->string + 5; + if (ctx->encoding) + return invalfc(fc, "casefold parameter cannot be specified twice\n"); + if (!latest_version) { if (strncmp(param->string, "utf8-", 5)) return invalfc(fc, "Only UTF-8 encodings are supported " @@ -4998,6 +5001,7 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc) if (ctx->encoding) { sb->s_encoding = ctx->encoding; + ctx->encoding = NULL; set_default_d_op(sb, &shmem_ci_dentry_ops); if (ctx->strict_encoding) sb->s_encoding_flags = SB_ENC_STRICT_MODE_FL; @@ -5095,6 +5099,9 @@ static void shmem_free_fc(struct fs_context *fc) struct shmem_options *ctx = fc->fs_private; if (ctx) { +#if IS_ENABLED(CONFIG_UNICODE) + utf8_unload(ctx->encoding); +#endif mpol_put(ctx->mpol); kfree(ctx); } From 8ad5fa0c11246d260d7b12c9672e604b1f431b2e Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Sat, 10 Oct 2026 19:34:52 +0800 Subject: [PATCH 098/134] ocfs2: reserve metadata for a new xattr block and its value tree Patch series "ocfs2: fix metadata reservation for xattr value trees". ocfs2_calc_xattr_set_need() reserves too little metadata for a new xattr value's extent tree on three paths. The reservation cannot be topped up once the transaction has started, so ocfs2_add_clusters_in_btree() gives up with RESTART_META when the value root fills and nothing is left. ocfs2_xattr_extend_allocation() turns that into -ENOSPC, after ocfs2_xa_cleanup_value_truncate() has dropped the entry and leaked the clusters already handed out; before commit 0cdc7dde00ec ("ocfs2: fix missing metadata reservation for large xattrs") it was a BUG_ON() instead. It takes a fragmented cluster allocator to get there, since the first extent always fits in the root. Patch 1 fixes the create side of meta_guess: commit 3ed2be719eb9 ("ocfs2: allow for more than one data extent when creating xattr") replaced the block reserved for the new xattr block with the two the value tree needs instead of adding them together. Patch 2 fixes the arm that moves an xattr back into the inode, which charges credits for a value tree but no metadata for one. Patch 3 drops a second copy of the same reservation on the path that falls through to meta_guess. That one is cleanup rather than a fix and is not for stable, and it must not be backported without patch 1: the duplicate it removes is what currently covers the shortfall patch 1 fixes. This patch (of 3): The create side of meta_guess in ocfs2_calc_xattr_set_need() reserves metadata for the new value's extent tree and nothing for the xattr block that will hold it: } else { credits += OCFS2_XATTR_BLOCK_CREATE_CREDITS; if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE) { struct ocfs2_extent_list *el = &def_xv.xv.xr_list; meta_add += ocfs2_extend_meta_needed(el); ... Commit 3ed2be719eb9 ("ocfs2: allow for more than one data extent when creating xattr") meant to add that tree on top of the meta_add += 1 the branch already had, but it moved the 1 into the else arm instead, so the external-value case lost it. The 1 is not optional: it is what ocfs2_create_xattr_block() takes from meta_ac through ocfs2_claim_metadata(), and it is spent before the value tree exists. ocfs2_extend_meta_needed() on the empty def_xv list returns 2, so the branch reserves 2 where 3 are needed. The xattr block consumes one, and when the cluster allocator is fragmented enough that the value needs more than one extent, ocfs2_add_clusters_in_btree() comes back around with a full value root and ocfs2_alloc_context_bits_left() of 1 against a threshold of 2. It answers RESTART_META, which ocfs2_xattr_extend_allocation() turns into a BUG_ON(). Commit 0cdc7dde00ec ("ocfs2: fix missing metadata reservation for large xattrs") downgraded that to -ENOSPC, at the cost of leaking the clusters already handed out: ocfs2_xa_cleanup_value_truncate() takes its !orig_clusters arm, drops the entry and leaves the clusters for fsck.ocfs2 to reclaim. Reserve for the block and the tree separately. Link: https://lore.kernel.org/20261010113454.1420939-1-joseph.qi@linux.alibaba.com Link: https://lore.kernel.org/20261010113454.1420939-2-joseph.qi@linux.alibaba.com Fixes: 3ed2be719eb9 ("ocfs2: allow for more than one data extent when creating xattr") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker Cc: --- fs/ocfs2/xattr.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 4555e7296fcb..5e3a9602c15f 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -3580,7 +3580,13 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode, credits += OCFS2_XATTR_BLOCK_CREATE_CREDITS; if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE) { struct ocfs2_extent_list *el = &def_xv.xv.xr_list; - meta_add += ocfs2_extend_meta_needed(el); + /* + * One block for the xattr block we may have to + * allocate, the rest for growing the value tree it + * would hold. If ocfs2_xattr_ibody_set() succeeds + * instead, that first block goes unused. + */ + meta_add += 1 + ocfs2_extend_meta_needed(el); credits += ocfs2_calc_extend_credits(inode->i_sb, el); } else { From f5aee8ae5f85f8dfa78160e5c4963494319cdd64 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Sat, 10 Oct 2026 19:34:53 +0800 Subject: [PATCH 099/134] ocfs2: reserve value tree metadata when moving an xattr into the inode ocfs2_calc_xattr_set_need() charges no metadata for the case where an xattr living in an xattr block or bucket moves back into the inode: if (old_in_xb) { if (ocfs2_xattr_can_be_in_inode(inode, xi, xis)) { clusters_add += new_clusters; credits += ...; if (!ocfs2_xattr_is_local(xe)) credits += ocfs2_calc_extend_credits(...); goto out; } } The credits for extending a value tree are there but the matching blocks are not, so meta_add stays 0, ocfs2_init_xattr_set_ctxt() skips ocfs2_reserve_new_metadata_blocks() and ctxt->meta_ac is left NULL. This is the shape commit 0cdc7dde00ec ("ocfs2: fix missing metadata reservation for large xattrs") fixed for the xattr block case, and it is reached the same way: namevalue_size() charges a value larger than OCFS2_XATTR_INLINE_SIZE as only OCFS2_XATTR_ROOT_SIZE, so ocfs2_xattr_can_be_in_inode() accepts a new value that will be stored outside. ocfs2_xa_prepare_entry() installs a value root from def_xv and grows it, and once the cluster allocator is fragmented enough to need a second extent ocfs2_add_clusters_in_btree() finds the root full with no meta_ac at all and answers RESTART_META. ocfs2_xattr_extend_allocation() turns that into -ENOSPC, or into a BUG_ON() without that commit, and ocfs2_xa_cleanup_value_truncate() takes its !orig_clusters arm, dropping the entry and leaking the clusters already handed out until fsck.ocfs2 reclaims them. Reserve for the value tree here too. The entry moves back into the inode rather than into a new xattr block, so unlike the create side of meta_guess this needs no block of its own. Link: https://lore.kernel.org/20261010113454.1420939-3-joseph.qi@linux.alibaba.com Fixes: 78f30c314a74 ("ocfs2/xattr: Reserve meta/data at the beginning of ocfs2_xattr_set.") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker Cc: --- fs/ocfs2/xattr.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 5e3a9602c15f..c13e82a2d4df 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -3480,6 +3480,14 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode, credits += ocfs2_calc_extend_credits( inode->i_sb, &def_xv.xv.xr_list); + /* + * A value too big to stay inline is only charged as + * OCFS2_XATTR_ROOT_SIZE above, so it can still need a + * value tree of its own. No xattr block is allocated + * on this path, so the tree is all we reserve for. + */ + if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE) + meta_add += ocfs2_extend_meta_needed(&def_xv.xv.xr_list); goto out; } } From 5365f1f417b36627f825aacf9844ae24b671b525 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Sat, 10 Oct 2026 19:34:54 +0800 Subject: [PATCH 100/134] ocfs2: reserve xattr value tree metadata only once ocfs2_calc_xattr_set_need() asks for the new value's extent tree twice when it replaces an inline value smaller than a value root with one large enough to be stored outside. The "stored outside" branch reserves the tree and then falls through to meta_guess instead of going out: if (!ocfs2_xattr_is_local(xe)) { ... xv = (struct ocfs2_xattr_value_root *) (base + name_offset + name_len); value_size = OCFS2_XATTR_ROOT_SIZE; } else xv = &def_xv.xv; if (old_clusters >= new_clusters) { ... goto out; } else { meta_add += ocfs2_extend_meta_needed(&xv->xr_list); ... if (value_size >= OCFS2_XATTR_ROOT_SIZE) goto out; } On the fall-through xv is def_xv, so what gets added here is the two blocks ocfs2_extend_meta_needed() wants for an empty value root -- and both sides of meta_guess reserve that same tree again. The create side has done so since commit 3ed2be719eb9 ("ocfs2: allow for more than one data extent when creating xattr"), which ported this reservation to the create case without noticing it was already on the way in, and the existing-block side since commit 0cdc7dde00ec ("ocfs2: fix missing metadata reservation for large xattrs"). Both were chasing the same RESTART_META failure in ocfs2_xattr_extend_allocation(), for xattr create and then for xattr update. ocfs2_init_xattr_set_ctxt() hands the total straight to ocfs2_reserve_new_metadata_blocks(), so the surplus is two blocks held for the whole transaction. A setxattr that would otherwise fit can come back -ENOSPC on a filesystem whose metadata allocator is nearly exhausted. Move the reservation into the branch that stops there, leaving meta_guess as the only place that reserves for a value tree the fall through is about to create. The paths that do go out keep reserving against the value root they already have, which for an external old value is the on-disk one and so can legitimately ask for more than two blocks. Both sides of meta_guess have to come out even on their own now that the copy covering for them is gone. The existing-block side only started reserving the value tree in the commit this one is tagged against. The create side has reserved the tree since 3ed2be719eb9, but only the tree: the block ocfs2_create_xattr_block() claims came out of the same two, and on the fall-through it was the duplicate removed here that made up the difference. So "ocfs2: reserve metadata for a new xattr block and its value tree" has to land first -- without it the create side reserves 2 where 3 are needed and the value tree is back to RESTART_META. That one fixes a shortfall of its own on the path that reaches meta_guess directly, where there was never a duplicate to cover it, so it is worth taking alone; this patch is not. Link: https://lore.kernel.org/20261010113454.1420939-4-joseph.qi@linux.alibaba.com Fixes: 0cdc7dde00ec ("ocfs2: fix missing metadata reservation for large xattrs") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Cc: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker --- fs/ocfs2/xattr.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index c13e82a2d4df..db6df633965d 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -3509,12 +3509,21 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode, credits += ocfs2_remove_extent_credits(inode->i_sb); goto out; } else { - meta_add += ocfs2_extend_meta_needed(&xv->xr_list); clusters_add += new_clusters - old_clusters; credits += ocfs2_calc_extend_credits(inode->i_sb, &xv->xr_list); - if (value_size >= OCFS2_XATTR_ROOT_SIZE) + /* + * The old value occupies at least as much room as a + * value root, whether it sat outside or inline, so the + * new root fits where it was and no new xattr block or + * bucket is needed -- only the metadata to extend the + * tree hanging off it. A smaller one is replaced + * outright, which meta_guess below reserves for. + */ + if (value_size >= OCFS2_XATTR_ROOT_SIZE) { + meta_add += ocfs2_extend_meta_needed(&xv->xr_list); goto out; + } } } else { /* @@ -3566,6 +3575,10 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode, * Reserve metadata for the new xattr's value extent tree. * The not_found path above adds credits for this tree but * omits meta_add, leaving meta_ac NULL for large values. + * + * This is all of meta_ac on this side beyond the xattr tree + * above: no xattr block is allocated here, and a new bucket or + * index block is paid for out of data_ac. */ if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE) meta_add += ocfs2_extend_meta_needed(&def_xv.xv.xr_list); From 3ccd24c3f7a577b84a532cb027f1378385b82ef6 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Fri, 9 Oct 2026 13:21:42 +0800 Subject: [PATCH 101/134] ocfs2/heartbeat: unlink live slots when their node is missing o2hb_shutdown_slot() returns immediately when its node is no longer in the configfs registry. A slot can still be linked in o2hb_live_slots when that lookup fails. Region teardown or startup-error cleanup then frees the slot array without removing its embedded list member. With the same peer live in another region, the next list update can follow a predecessor into the freed array. Reusing the node number can also make a later insertion encounter that stale member. Always remove the slot under o2hb_live_lock, even when node lookup fails. Preserve aggregate membership accounting and the last-region DOWN callback: the existing callback interface explicitly permits a NULL node for DOWN events. Only drop the node reference when lookup returned one. This closes both normal release and startup-abort paths without changing the publication or callback locking protocol. Also require a configured node before admitting a new live slot in o2hb_check_slot(). The early live-bitmap check is not held through admission: the last live slot in another region can be removed in between. Without a node, admission can then queue an UP event with a NULL argument and hit the event helper's BUG_ON. Existing live slots can still leave and generate NULL-node DOWN events. A controlled probe-instrumented test reproduced this while stopping one of two regions after removing the peer from configfs. Its KASAN report includes: BUG: KASAN: slab-use-after-free in __list_del_entry_valid_or_report+0x1e0/0x210 Read of size 8 at addr ffff888106a4ac60 by task o2hb-pmbd0058_b/497 Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 kasan_report+0xe0/0x110 __list_del_entry_valid_or_report+0x1e0/0x210 o2hb_do_disk_heartbeat+0x1591/0x2c50 o2hb_thread+0x1ed/0xe70 kthread+0x351/0x460 ret_from_fork+0x659/0x940 ret_from_fork_asm+0x1a/0x30 Allocated by task 491: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_noprof+0x2de/0x780 o2hb_region_dev_store+0x7c2/0x1b60 configfs_write_iter+0x2f9/0x4f0 vfs_write+0x639/0x1010 ksys_write+0x111/0x200 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 491: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x308/0x580 o2hb_unmap_slot_data+0x1db/0x330 o2hb_region_release+0x14b/0x4f0 config_item_cleanup+0x14b/0x230 config_item_put+0x79/0x90 configfs_rmdir+0x58a/0x910 vfs_rmdir+0x2e8/0x820 filename_rmdir+0x3b1/0x520 __x64_sys_rmdir+0x4b/0x70 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0058-v2-9446113c2b16c817c538@gmail.com Fixes: a7f6a5fb4bde ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Yufan Chen Cc: Kees Cook Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/cluster/heartbeat.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index a4c8ea695f5c..8e6ead433228 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -880,8 +880,11 @@ static void o2hb_shutdown_slot(struct o2hb_disk_slot *slot) int queued = 0; node = o2nm_get_node_by_num(slot->ds_node_num); - if (!node) - return; + /* + * The node may have been removed from the node table while this + * region's slot is still linked. The list member must still be + * removed before the region releases its slot array. + */ spin_lock(&o2hb_live_lock); if (!list_empty(&slot->ds_live_item)) { @@ -903,7 +906,8 @@ static void o2hb_shutdown_slot(struct o2hb_disk_slot *slot) if (queued) o2hb_run_event_list(&event); - o2nm_node_put(node); + if (node) + o2nm_node_put(node); } static void o2hb_set_quorum_device(struct o2hb_region *reg) @@ -1038,7 +1042,7 @@ static int o2hb_check_slot(struct o2hb_region *reg, fire_callbacks: /* dead nodes only come to life after some number of * changes at any time during their dead time */ - if (list_empty(&slot->ds_live_item) && + if (node && list_empty(&slot->ds_live_item) && slot->ds_changed_samples >= O2HB_LIVE_THRESHOLD) { mlog(ML_HEARTBEAT, "Node %d (id 0x%llx) joined my region\n", slot->ds_node_num, (long long)slot->ds_last_generation); From 5d72266218b1c4dd2917f1df2b86f3b0e0a605c9 Mon Sep 17 00:00:00 2001 From: Armaan Sandhu Date: Mon, 5 Oct 2026 17:39:45 -0400 Subject: [PATCH 102/134] lib/cmdline: fix get_options() count and overflow with large ranges get_range() stops writing once the array is full, but get_options() still advances its index by the whole range. So "1-100" parsed into four ints reports 99 numbers, and a range like "0-2147483647" wraps the index negative and writes outside the array. Stop once a range fills the array, bail out in validation mode before the count overflows, and saturate the range length in get_range(). Add KUnit cases; without the fix "0-2147483647" panics the test kernel. Only root can supply this input, so this is a robustness fix. [akpm@linux-foundation.org: add additional test case from Leizhen] Link: https://lore.kernel.org/20261005213945.359905-1-armaan.sandhu0504@gmail.com Fixes: 22f2e2801799 ("[PATCH] get_options to allow a hypenated range for isolcpus") Signed-off-by: Armaan Sandhu Signed-off-by: Andrew Morton Reported-by: Leizhen Zhang Reviewed-by: Leizhen Zhang Cc: Andy Shevchenko Cc: --- lib/cmdline.c | 13 ++++++++++++- lib/tests/cmdline_kunit.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/cmdline.c b/lib/cmdline.c index 16cce6621cec..40b98d943a9a 100644 --- a/lib/cmdline.c +++ b/lib/cmdline.c @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -26,7 +27,9 @@ static int get_range(char **str, int *pint, int n) (*str)++; upper_range = simple_strtol((*str), NULL, 0); - inc_counter = upper_range - *pint; + /* Keep the sign of the result when the difference doesn't fit */ + if (check_sub_overflow(upper_range, *pint, &inc_counter)) + inc_counter = upper_range < *pint ? -1 : INT_MAX; for (x = *pint; n && x < upper_range; x++, n--) *pint++ = x; return inc_counter; @@ -122,6 +125,14 @@ char *get_options(const char *str, int nints, int *ints) range_nums = get_range((char **)&str, pint, n); if (range_nums < 0) break; + /* The range didn't fit, so the array is full */ + if (!validate && range_nums > n) { + i = nints; + break; + } + /* Leave room for the upper number of the range */ + if (range_nums >= INT_MAX - i) + break; /* * Decrement the result by one to leave out the * last number in the range. The next iteration diff --git a/lib/tests/cmdline_kunit.c b/lib/tests/cmdline_kunit.c index 3f61ff8d3178..12b85f255b65 100644 --- a/lib/tests/cmdline_kunit.c +++ b/lib/tests/cmdline_kunit.c @@ -140,6 +140,44 @@ static void cmdline_test_range(struct kunit *test) } while (++i < ARRAY_SIZE(cmdline_test_range_strings)); } +static const struct { + const char *in; + int parsed[4]; + int validated; +} cmdline_test_range_overflow_cases[] = { + { "1-100", { 3, 1, 2, 3, }, 100, }, + { "1,5-100,7", { 3, 1, 5, 6, }, 98, }, + { "1-2147483646", { 3, 1, 2, 3, }, 2147483646, }, + { "0-2147483647", { 3, 0, 1, 2, }, 0, }, + { "-5-2147483647", { 3, -5, -4, -3, }, 0, }, + { "2147483647--5", { 0, 2147483647, }, 0, }, + { "1,0-2147483647", { 3, 1, 0, 1, }, 1, }, +}; + +static void cmdline_test_range_overflow(struct kunit *test) +{ + unsigned int i, j; + + for (i = 0; i < ARRAY_SIZE(cmdline_test_range_overflow_cases); i++) { + const char *in = cmdline_test_range_overflow_cases[i].in; + const int *e = cmdline_test_range_overflow_cases[i].parsed; + /* Two guard elements past the array handed to get_options() */ + int r[ARRAY_SIZE(cmdline_test_range_overflow_cases[0].parsed) + 2]; + int n; + + memset(r, 0, sizeof(r)); + get_options(in, ARRAY_SIZE(r) - 2, r); + for (j = 0; j < ARRAY_SIZE(r) - 2; j++) + KUNIT_EXPECT_EQ_MSG(test, r[j], e[j], "Pattern: %s at %u", in, j); + for (; j < ARRAY_SIZE(r); j++) + KUNIT_EXPECT_EQ_MSG(test, r[j], 0, "Pattern: %s out of bound at %u", in, j); + + get_options(in, 0, &n); + KUNIT_EXPECT_EQ_MSG(test, n, cmdline_test_range_overflow_cases[i].validated, + "Pattern: %s (validated)", in); + } +} + static void cmdline_test_next_arg_quoted_value(struct kunit *test) { char in[] = "foo=\"bar baz\" qux=1"; @@ -258,6 +296,7 @@ static struct kunit_case cmdline_test_cases[] = { KUNIT_CASE(cmdline_test_lead_int), KUNIT_CASE(cmdline_test_tail_int), KUNIT_CASE(cmdline_test_range), + KUNIT_CASE(cmdline_test_range_overflow), KUNIT_CASE(cmdline_test_next_arg_quoted_value), KUNIT_CASE(cmdline_test_next_arg_bare_quote_regression), KUNIT_CASE(cmdline_test_next_arg_mixed_tokens), From d1b49d5ea1bf43cce2b0e249a7f7a30dde133907 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 9 Oct 2026 16:30:00 +0800 Subject: [PATCH 103/134] ocfs2: deal with legacy signed dir index name hash values Patch series "ocfs2: deal with legacy signed name hash values", v2. Commit 3bc753c06dd0 ("kbuild: treat char as always unsigned") turned on -funsigned-char globally, which changed the result of two naked 'char' loads in ocfs2 hashing code: str2hashbuf() val = msg[i] + (val << 8); ocfs2_xattr_name_hash() hash = (hash << 5) ^ (hash >> 27) ^ *name++; A name byte >= 0x80 used to sign-extend and now zero-extends. Both results are written to disk, the first into the dx leaf when a directory entry is indexed and the second into xe_name_hash when an xattr is stored, so anything written by a pre-6.2 kernel is looked up under a different hash and is no longer found. readdir and listxattr still list those names because neither of them hashes, which makes this look like the filesystem losing entries rather than like a lookup bug. Anderson Ferneda reported it that way for directories with non-ASCII names [1]. Patches 1 and 3 do what ext4 did in commit f3bbac32475b2 ("ext4: deal with legacy signed xattr name hash values"): look up with the current unsigned hash, retry with the legacy signed one on a miss, and always store new entries under the unsigned hash. The retry is skipped when the two hashes come out equal, so an ASCII name does not walk the index twice. Both hash functions now spell out the signedness instead of leaving it to -funsigned-char, as commit 854f0912f813 ("ext4: make xattr char unsignedness in hash explicit") did, so a backport to a kernel without the flag still computes both variants correctly. Patch 2 is a separate fix, placed before patch 3 so that every commit in the series is correct on its own. ocfs2_check_xattr_bucket_collision() recomputes the hash of the name to decide whether splitting a full bucket can make room for the entry being set. That is right for a new entry and wrong for one that already exists, since an update leaves xe_name_hash alone. Against a bucket holding legacy hashes the comparison does not match, so the split goes ahead and cannot help: a bucket whose entries all share one hash has no divide position. How that ends depends on where the unsigned hash sorts. Below the legacy ones, the set fails with -ENOSPC after growing the tree for nothing. Above them, it lands in the empty bucket the split just appended and stores a second copy of the entry there while the original stays behind, so listxattr reports the name twice. Using the stored hash closes both. Nothing retires a legacy hash at runtime. Deleting or updating an entry does not rewrite it, and the only code that rehashes existing dirents is ocfs2_expand_inline_dir() on the inline to extent conversion, so the retry stays for as long as the legacy entries do. Rebuilding the index offline is what removes it for good. This patch (of 3): Commit 3bc753c06dd0 ("kbuild: treat char as always unsigned") set -funsigned-char globally, which changed the result of the naked 'char' load in str2hashbuf(): val = msg[i] + (val << 8); A name byte >= 0x80 used to sign-extend and now zero-extends, so ocfs2_dx_dir_name_hash() computes a different hash pair for every name containing one. The pair is written into the dx leaf when the entry is created, so an index built by an older kernel no longer matches and ocfs2_dx_dir_search() returns -ENOENT for a name that readdir still lists. Search with the current unsigned hash and, on a miss, retry with the legacy signed one, as ext4 does in commit f3bbac32475b2 ("ext4: deal with legacy signed xattr name hash values"). New entries are always indexed under the unsigned hash. Skip the retry when the two hashes are equal, so that a miss on an ASCII name does not walk the index twice. The retry costs a second walk of the dx tree, so a negative lookup of a name containing a byte >= 0x80 does twice the work. That is bounded and worth it, and directories whose dx root is still inline pay nothing extra, since there the retry only rescans the root block that is already loaded. Nothing retires it at runtime: a legacy entry keeps its legacy hash, because neither deleting nor updating an entry rewrites the dx hash, and the only code that rehashes existing dirents is ocfs2_expand_inline_dir() on the inline to extent conversion. Rebuilding the index offline is what removes the cost for good. Also spell out the signedness in str2hashbuf() instead of leaving the current hash to -funsigned-char, as commit 854f0912f813 ("ext4: make xattr char unsignedness in hash explicit") did, so that both variants stay correct if this is backported to a kernel without the flag. Link: https://lore.kernel.org/20261009083002.2621201-1-joseph.qi@linux.alibaba.com Link: https://lore.kernel.org/20261009083002.2621201-2-joseph.qi@linux.alibaba.com Fixes: 3bc753c06dd0 ("kbuild: treat char as always unsigned") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reported-by: Anderson Ferneda Closes: https://lore.kernel.org/ocfs2-devel/CP5P284MB2780AC2C3C2AB2CF2B6CB7AFBE942@CP5P284MB2780.BRAP284.PROD.OUTLOOK.COM/ Reviewed-by: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker Cc: # 6.2+ --- fs/ocfs2/dir.c | 76 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 55c4a305a282..3f159a20a248 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -221,7 +221,8 @@ static void TEA_transform(__u32 buf[4], __u32 const in[]) buf[1] += b1; } -static void str2hashbuf(const char *msg, int len, __u32 *buf, int num) +static void str2hashbuf(const char *msg, int len, __u32 *buf, int num, + bool legacy_signed) { __u32 pad, val; int i; @@ -235,7 +236,10 @@ static void str2hashbuf(const char *msg, int len, __u32 *buf, int num) for (i = 0; i < len; i++) { if ((i % 4) == 0) val = pad; - val = msg[i] + (val << 8); + if (legacy_signed) + val = (signed char)msg[i] + (val << 8); + else + val = (unsigned char)msg[i] + (val << 8); if ((i % 4) == 3) { *buf++ = val; val = pad; @@ -248,8 +252,9 @@ static void str2hashbuf(const char *msg, int len, __u32 *buf, int num) *buf++ = pad; } -static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len, - struct ocfs2_dx_hinfo *hinfo) +static void __ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, + int len, struct ocfs2_dx_hinfo *hinfo, + bool legacy_signed) { struct ocfs2_super *osb = OCFS2_SB(dir->i_sb); const char *p; @@ -279,7 +284,7 @@ static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len, p = name; while (len > 0) { - str2hashbuf(p, len, in, 4); + str2hashbuf(p, len, in, 4, legacy_signed); TEA_transform(buf, in); len -= 16; p += 16; @@ -290,6 +295,19 @@ static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len, hinfo->minor_hash = buf[1]; } +static void ocfs2_dx_dir_name_hash(struct inode *dir, const char *name, int len, + struct ocfs2_dx_hinfo *hinfo) +{ + __ocfs2_dx_dir_name_hash(dir, name, len, hinfo, false); +} + +static void ocfs2_dx_dir_name_hash_signed(struct inode *dir, const char *name, + int len, + struct ocfs2_dx_hinfo *hinfo) +{ + __ocfs2_dx_dir_name_hash(dir, name, len, hinfo, true); +} + /* * bh passed here can be an inode block or a dir data block, depending * on the inode inline data flag. @@ -1021,10 +1039,10 @@ static int ocfs2_dx_dir_lookup(struct inode *inode, return ret; } -static int ocfs2_dx_dir_search(const char *name, int namelen, - struct inode *dir, - struct ocfs2_dx_root_block *dx_root, - struct ocfs2_dir_lookup_result *res) +static int __ocfs2_dx_dir_search(const char *name, int namelen, + struct inode *dir, + struct ocfs2_dx_root_block *dx_root, + struct ocfs2_dir_lookup_result *res) { int ret, i, found; u64 phys; @@ -1037,8 +1055,6 @@ static int ocfs2_dx_dir_search(const char *name, int namelen, struct ocfs2_extent_list *dr_el; struct ocfs2_dx_entry_list *entry_list; - ocfs2_dx_dir_name_hash(dir, name, namelen, &res->dl_hinfo); - if (ocfs2_dx_root_inline(dx_root)) { entry_list = &dx_root->dr_entries; goto search; @@ -1135,6 +1151,44 @@ static int ocfs2_dx_dir_search(const char *name, int namelen, return ret; } +static int ocfs2_dx_dir_search(const char *name, int namelen, + struct inode *dir, + struct ocfs2_dx_root_block *dx_root, + struct ocfs2_dir_lookup_result *res) +{ + struct ocfs2_dx_hinfo legacy; + int ret; + + ocfs2_dx_dir_name_hash(dir, name, namelen, &res->dl_hinfo); + + ret = __ocfs2_dx_dir_search(name, namelen, dir, dx_root, res); + if (ret != -ENOENT) + return ret; + + /* + * Nothing under the current hash. The entry may have been indexed by + * an older kernel, which sign-extended the name bytes when hashing. + * New entries are always indexed under the unsigned hash, so only fall + * back to the legacy signed one when it can actually differ: an ASCII + * name hashes the same either way, and a genuine miss on one should + * not have to walk the index twice. + */ + ocfs2_dx_dir_name_hash_signed(dir, name, namelen, &legacy); + if (legacy.major_hash == res->dl_hinfo.major_hash && + legacy.minor_hash == res->dl_hinfo.minor_hash) + return ret; + + res->dl_hinfo = legacy; + + ret = __ocfs2_dx_dir_search(name, namelen, dir, dx_root, res); + if (ret) + return ret; + + pr_warn_once("ocfs2: directory index with signed name hash\n"); + + return 0; +} + static int ocfs2_find_entry_dx(const char *name, int namelen, struct inode *dir, struct ocfs2_dir_lookup_result *lookup) From e39baff18b85cf8956aee138d7944b6581e8ba36 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 9 Oct 2026 16:30:01 +0800 Subject: [PATCH 104/134] ocfs2: use the stored hash when checking xattr bucket collision ocfs2_check_xattr_bucket_collision() decides whether splitting a full bucket can make room for the entry being set by recomputing the hash of the name: u32 name_hash = ocfs2_xattr_name_hash(inode, name, strlen(name)); if (name_hash != le32_to_cpu(xh->xh_entries[0].xe_name_hash)) return 0; For a new entry that is the hash it will be stored under, so comparing it is correct. An existing entry keeps the hash it already has: an update never rewrites xe_name_hash, since ocfs2_xa_add_entry() is the only writer of that field for a real entry and ocfs2_xa_prepare_entry() calls it only when loc->xl_entry is NULL. An entry stored by a kernel that sign-extended the name bytes when hashing can still be updated, because ocfs2_xattr_find_entry() searches a non-indexed xattr block by memcmp on the name and never looks at xe_name_hash. Growing it past the space left in the block converts the block into a tree, ocfs2_cp_xattr_block_to_bucket() fills the bucket in stored hash order, and the update runs out of room in the bucket too. The collision check then compares the unsigned hash against the legacy hashes in the bucket and reports no collision. ocfs2_xattr_set_entry_index_block() goes on to allocate a bucket that ocfs2_divide_xattr_bucket() cannot fill: a bucket whose entries all share one hash has no divide position, so all it does is append an empty bucket with a sentinel hash one above the last entry's. The re-search that follows depends on where the unsigned hash sorts. Below the legacy ones, it comes back to the full bucket and the set fails with -ENOSPC, having grown the tree for nothing. Above them, it lands on the new empty bucket, which ocfs2_xattr_bucket_find() handles explicitly and ocfs2_find_xe_in_bucket() scans zero times, so the set stores a second copy of the entry under the unsigned hash and returns success. The original stays in the full bucket, listxattr reports the name twice and getxattr returns the new copy. Pass the hash the entry is stored under instead: the stored one for an existing entry, the unsigned one for a new entry. A tree whose entries are all stored under the unsigned hash sees no change, since there the two are the same value. Link: https://lore.kernel.org/20261009083002.2621201-3-joseph.qi@linux.alibaba.com Fixes: 3bc753c06dd0 ("kbuild: treat char as always unsigned") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Cc: Anderson Ferneda Cc: Mark Fasheh Cc: Joel Becker Cc: # 6.2+ --- fs/ocfs2/xattr.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index db6df633965d..2b5618b0137a 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -5923,16 +5923,14 @@ static int ocfs2_rm_xattr_cluster(struct inode *inode, /* * check whether the xattr bucket is filled up with the same hash value. - * If we want to insert the xattr with the same hash, return -ENOSPC. - * If we want to insert a xattr with different hash value, go ahead - * and ocfs2_divide_xattr_bucket will handle this. + * If the entry being set carries that same hash, return -ENOSPC, since + * ocfs2_divide_xattr_bucket() has no divide position to work with. + * Otherwise go ahead and ocfs2_divide_xattr_bucket() will handle this. */ -static int ocfs2_check_xattr_bucket_collision(struct inode *inode, - struct ocfs2_xattr_bucket *bucket, - const char *name) +static int ocfs2_check_xattr_bucket_collision(struct ocfs2_xattr_bucket *bucket, + u32 name_hash) { struct ocfs2_xattr_header *xh = bucket_xh(bucket); - u32 name_hash = ocfs2_xattr_name_hash(inode, name, strlen(name)); if (name_hash != le32_to_cpu(xh->xh_entries[0].xe_name_hash)) return 0; @@ -6001,6 +5999,7 @@ static int ocfs2_xattr_set_entry_index_block(struct inode *inode, struct ocfs2_xattr_search *xs, struct ocfs2_xattr_set_ctxt *ctxt) { + u32 name_hash; int ret; trace_ocfs2_xattr_set_entry_index_block(xi->xi_name); @@ -6020,10 +6019,18 @@ static int ocfs2_xattr_set_entry_index_block(struct inode *inode, * the maximum number of collisions we will allow for then is * one bucket's worth, so check it here whether we need to * add a new bucket for the insert. + * + * An existing entry keeps the hash it was stored under, and that is + * the hash a split has to work with. A new entry is stored under the + * unsigned one, which is what ocfs2_xa_add_entry() will write. */ - ret = ocfs2_check_xattr_bucket_collision(inode, - xs->bucket, - xi->xi_name); + if (xs->not_found) + name_hash = ocfs2_xattr_name_hash(inode, xi->xi_name, + xi->xi_name_len); + else + name_hash = le32_to_cpu(xs->here->xe_name_hash); + + ret = ocfs2_check_xattr_bucket_collision(xs->bucket, name_hash); if (ret) { mlog_errno(ret); goto out; From 824d26d11448b4d4f19677bce27c7fe5e962f4c6 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 9 Oct 2026 16:30:02 +0800 Subject: [PATCH 105/134] ocfs2: deal with legacy signed xattr name hash values Commit 3bc753c06dd0 ("kbuild: treat char as always unsigned") set -funsigned-char globally, which changed the result of the naked 'char' load in ocfs2_xattr_name_hash(): hash = (hash << OCFS2_HASH_SHIFT) ^ (hash >> (8*sizeof(hash) - OCFS2_HASH_SHIFT)) ^ *name++; A name byte >= 0x80 used to sign-extend and now zero-extends, so the hash no longer matches the xe_name_hash an older kernel stored. An indexed xattr tree is searched by that hash alone, and both the bucket binary search and the entry scan within it stop as soon as the wanted hash falls below an entry's, so the entry is never reached: getxattr, setxattr and removexattr return -ENODATA for a name that listxattr still lists. Search with the current unsigned hash and, on a miss, retry with the legacy signed one, as ext4 does in commit f3bbac32475b2 ("ext4: deal with legacy signed xattr name hash values"). New entries are always stored under the unsigned hash. Skip the retry when the two hashes are equal, so that a miss on an ASCII name does not walk the tree twice. A miss is not empty handed: ocfs2_xattr_bucket_find() leaves xs->bucket holding the bucket a new entry would go into. So after a double miss drop it and search once more with the unsigned hash, otherwise a new entry would be placed by its legacy hash and stored under its unsigned one, breaking the ordering the search relies on. Only indexed trees are affected; inline xattrs and non-indexed xattr blocks compare names with memcmp and never look at the hash. Also spell out the signedness instead of leaving the current hash to -funsigned-char, as commit 854f0912f813 ("ext4: make xattr char unsignedness in hash explicit") did, so that both variants stay correct if this is backported to a kernel without the flag. Link: https://lore.kernel.org/20261009083002.2621201-4-joseph.qi@linux.alibaba.com Fixes: 3bc753c06dd0 ("kbuild: treat char as always unsigned") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Cc: Anderson Ferneda Cc: Mark Fasheh Cc: Joel Becker Cc: # 6.2+ --- fs/ocfs2/xattr.c | 93 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 11 deletions(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 2b5618b0137a..52e46d1b8763 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -601,9 +601,10 @@ static inline const char *ocfs2_xattr_prefix(int name_index) return handler ? xattr_prefix(handler) : NULL; } -static u32 ocfs2_xattr_name_hash(struct inode *inode, - const char *name, - int name_len) +static u32 __ocfs2_xattr_name_hash(struct inode *inode, + const char *name, + int name_len, + bool legacy_signed) { /* Get hash value of uuid from super block */ u32 hash = OCFS2_SB(inode->i_sb)->uuid_hash; @@ -612,13 +613,30 @@ static u32 ocfs2_xattr_name_hash(struct inode *inode, /* hash extended attribute name */ for (i = 0; i < name_len; i++) { hash = (hash << OCFS2_HASH_SHIFT) ^ - (hash >> (8*sizeof(hash) - OCFS2_HASH_SHIFT)) ^ - *name++; + (hash >> (8*sizeof(hash) - OCFS2_HASH_SHIFT)); + if (legacy_signed) + hash ^= (signed char)name[i]; + else + hash ^= (unsigned char)name[i]; } return hash; } +static u32 ocfs2_xattr_name_hash(struct inode *inode, + const char *name, + int name_len) +{ + return __ocfs2_xattr_name_hash(inode, name, name_len, false); +} + +static u32 ocfs2_xattr_name_hash_signed(struct inode *inode, + const char *name, + int name_len) +{ + return __ocfs2_xattr_name_hash(inode, name, name_len, true); +} + static int ocfs2_xattr_entry_real_size(int name_len, size_t value_len) { return namevalue_size(name_len, value_len) + @@ -4331,11 +4349,12 @@ static int ocfs2_xattr_bucket_find(struct inode *inode, return ret; } -static int ocfs2_xattr_index_block_find(struct inode *inode, - struct buffer_head *root_bh, - int name_index, - const char *name, - struct ocfs2_xattr_search *xs) +static int __ocfs2_xattr_index_block_find(struct inode *inode, + struct buffer_head *root_bh, + int name_index, + const char *name, + u32 name_hash, + struct ocfs2_xattr_search *xs) { int ret; struct ocfs2_xattr_block *xb = @@ -4344,7 +4363,6 @@ static int ocfs2_xattr_index_block_find(struct inode *inode, struct ocfs2_extent_list *el = &xb_root->xt_list; u64 p_blkno = 0; u32 first_hash, num_clusters = 0; - u32 name_hash = ocfs2_xattr_name_hash(inode, name, strlen(name)); if (le16_to_cpu(el->l_next_free_rec) == 0) return -ENODATA; @@ -4375,6 +4393,59 @@ static int ocfs2_xattr_index_block_find(struct inode *inode, return ret; } +static int ocfs2_xattr_index_block_find(struct inode *inode, + struct buffer_head *root_bh, + int name_index, + const char *name, + struct ocfs2_xattr_search *xs) +{ + u32 name_hash, legacy_hash; + int name_len = strlen(name); + int ret; + + name_hash = ocfs2_xattr_name_hash(inode, name, name_len); + + ret = __ocfs2_xattr_index_block_find(inode, root_bh, name_index, name, + name_hash, xs); + if (ret != -ENODATA) + return ret; + + /* + * Nothing under the current hash. The entry may have been stored by + * an older kernel, which sign-extended the name bytes when hashing. + * Skip the retry when the two hashes are equal, so that a name made + * only of ASCII does not have to walk the tree twice. + */ + legacy_hash = ocfs2_xattr_name_hash_signed(inode, name, name_len); + if (legacy_hash == name_hash) + return ret; + + /* + * A miss still leaves xs->bucket holding the bucket a new entry would + * be inserted into, so drop it before searching again. + */ + ocfs2_xattr_bucket_relse(xs->bucket); + + ret = __ocfs2_xattr_index_block_find(inode, root_bh, name_index, name, + legacy_hash, xs); + if (!ret) { + pr_warn_once("ocfs2: xattr tree with signed name hash\n"); + return ret; + } + if (ret != -ENODATA) + return ret; + + /* + * Not under either hash. Restore the unsigned placement, since that + * is where a new entry is stored: leaving the bucket where the legacy + * hash put it would break the ordering the search relies on. + */ + ocfs2_xattr_bucket_relse(xs->bucket); + + return __ocfs2_xattr_index_block_find(inode, root_bh, name_index, name, + name_hash, xs); +} + static int ocfs2_iterate_xattr_buckets(struct inode *inode, u64 blkno, u32 clusters, From f6bfa6c458288d72948af95391b205f3eb775721 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Fri, 9 Oct 2026 09:43:43 +0800 Subject: [PATCH 106/134] ocfs2: initialize status waiters before publishing them o2net_prep_nsw() publishes its stack waiter in nn_status_idr and nn_status_list under nn_lock, then initializes the wait queue and completion status after releasing the lock. A concurrent disconnect can find that waiter through o2net_complete_nodes_nsw() and call wake_up() before the wait queue has a valid lock and list head. The sender can also overwrite a completion status with its initial value. Initialize the wait queue and status before publishing the waiter. nn_lock then orders publication against completion, and no reader can observe a partially initialized object. Leave the IDR allocation and its failure handling unchanged. A controlled case-modified test kernel used a synthetic connection to enter the real send path and overlap publication with disconnect. The candidate completion and wake-up code were unchanged. Its Oops includes: KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: 0010:__wake_up_common+0xa0/0x1f0 Call Trace: __wake_up+0x36/0x60 o2net_complete_nsw_locked+0x222/0x370 o2net_set_nn_state+0x917/0xea0 o2net_disconnect_node+0xd0/0x190 o2net_validate_control_write+0x454/0x500 full_proxy_write+0x11f/0x180 vfs_write+0x25a/0x1010 ksys_write+0x111/0x200 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f Modules linked in: Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0066-v1-437a0422801b292a2879@gmail.com Fixes: 98211489d414 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Kees Cook Cc: Jakub Kicinski Cc: Kurt Hackel Cc: Jia-Ju Bai Cc: --- fs/ocfs2/cluster/tcp.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/ocfs2/cluster/tcp.c b/fs/ocfs2/cluster/tcp.c index 474fe1414cee..8b3830b601ba 100644 --- a/fs/ocfs2/cluster/tcp.c +++ b/fs/ocfs2/cluster/tcp.c @@ -301,6 +301,11 @@ static int o2net_prep_nsw(struct o2net_node *nn, struct o2net_status_wait *nsw) { int ret; + /* Initialize all completion-visible state before publishing the waiter. */ + init_waitqueue_head(&nsw->ns_wq); + nsw->ns_sys_status = O2NET_ERR_NONE; + nsw->ns_status = 0; + spin_lock(&nn->nn_lock); ret = idr_alloc(&nn->nn_status_idr, nsw, 0, 0, GFP_ATOMIC); if (ret >= 0) { @@ -311,9 +316,6 @@ static int o2net_prep_nsw(struct o2net_node *nn, struct o2net_status_wait *nsw) if (ret < 0) return ret; - init_waitqueue_head(&nsw->ns_wq); - nsw->ns_sys_status = O2NET_ERR_NONE; - nsw->ns_status = 0; return 0; } From e062baf5680845705d21df415c8f0751c96b0792 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 13 Oct 2026 09:58:58 +0800 Subject: [PATCH 107/134] ocfs2: publish the filecheck owner before adding sysfs attributes ocfs2_filecheck_create_sysfs() initializes the filecheck allocation before calling kobject_init_and_add(), but assigns entry->fs_fcheck only after that call returns. The default attribute group can already be visible while registration is in progress. A concurrent read can therefore enter ocfs2_filecheck_attr_show() with a NULL fs_fcheck and try to lock its fc_lock. Assign the initialized owner before adding the kobject, so every activated attribute has valid state. Clear the pointer when addition fails, preserving the existing teardown guard and error cleanup. A controlled probe-instrumented test kernel with the original OCFS2 registration ordering produced this Oops during a concurrent sysfs read: KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f] RIP: 0010:kasan_byte_accessible+0x15/0x30 Call Trace: __kasan_check_byte+0x13/0x50 lock_acquire+0x113/0x300 _raw_spin_lock+0x31/0x40 ocfs2_filecheck_attr_show+0x138/0x5f0 ocfs2_filecheck_show+0x5d/0x90 sysfs_kf_seq_show+0x1d3/0x360 seq_read_iter+0x480/0x1110 kernfs_fop_read_iter+0x419/0x5a0 vfs_read+0x7d2/0xc20 ksys_read+0x111/0x200 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f Modules linked in: Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0086-v4-1b3a3c5fd05c1fb41df9@gmail.com Fixes: 5f483c4abb50 ("ocfs2: add kobject for online file check") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Gang He Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/filecheck.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/filecheck.c b/fs/ocfs2/filecheck.c index be713d7d24a7..75334e4bd082 100644 --- a/fs/ocfs2/filecheck.c +++ b/fs/ocfs2/filecheck.c @@ -179,17 +179,20 @@ int ocfs2_filecheck_create_sysfs(struct ocfs2_super *osb) fcheck->fc_size = 0; fcheck->fc_done = 0; + /* Publish the initialized owner before activating its sysfs callbacks. */ + entry->fs_fcheck = fcheck; entry->fs_kobj.kset = osb->osb_dev_kset; init_completion(&entry->fs_kobj_unregister); ret = kobject_init_and_add(&entry->fs_kobj, &ocfs2_ktype_filecheck, NULL, "filecheck"); if (ret) { + /* Mount failure still calls ocfs2_filecheck_remove_sysfs(). */ + entry->fs_fcheck = NULL; kobject_put(&entry->fs_kobj); kfree(fcheck); return ret; } - entry->fs_fcheck = fcheck; return 0; } From e60b8f7a1d050d5400684f270faf773a1b27e085 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 13 Oct 2026 09:21:10 +0200 Subject: [PATCH 108/134] lib: kstrtox: use isdigit() in _parse_integer_limit() Use isdigit() to replace the open-coded implementation. Link: https://lore.kernel.org/20261013072108.4407-3-blum@kernel.org Signed-off-by: Thorsten Blum Signed-off-by: Andrew Morton Reviewed-by: Andrew Morton --- lib/kstrtox.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/kstrtox.c b/lib/kstrtox.c index bac1c057e1b0..1b3f0d3fff37 100644 --- a/lib/kstrtox.c +++ b/lib/kstrtox.c @@ -69,7 +69,7 @@ unsigned int _parse_integer_limit(const char *s, unsigned int base, unsigned lon unsigned int lc = _tolower(c); unsigned int val; - if ('0' <= c && c <= '9') + if (isdigit(c)) val = c - '0'; else if ('a' <= lc && lc <= 'f') val = lc - 'a' + 10; From edb528227f55e7564febcf68ed2f1bf8045559a9 Mon Sep 17 00:00:00 2001 From: zhoumin Date: Mon, 12 Oct 2026 21:20:06 +0800 Subject: [PATCH 109/134] fat: roll back the new entry when create/mkdir fails to build the inode vfat_create(), vfat_mkdir(), msdos_create() and msdos_mkdir() add the on-disk entry before calling fat_build_inode() to set up the VFS inode. If fat_build_inode() fails, the error is returned but the entry stays on disk and the file or directory shows up after remount. Remove the entry in that case. For mkdir, also free the directory cluster and decrement the parent's link count. [akpm@linux-foundation.org" export _fat_msg() to modules] Link: https://lore.kernel.org/tencent_4EDD66523CED299DB0C99498B4D8C939A009@qq.com Signed-off-by: zhoumin Signed-off-by: Andrew Morton Acked-by: OGAWA Hirofumi --- fs/fat/misc.c | 1 + fs/fat/namei_msdos.c | 22 ++++++++++++++++++---- fs/fat/namei_vfat.c | 22 ++++++++++++++++++---- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/fs/fat/misc.c b/fs/fat/misc.c index c44296756eae..0a1ee9035327 100644 --- a/fs/fat/misc.c +++ b/fs/fat/misc.c @@ -62,6 +62,7 @@ void _fat_msg(struct super_block *sb, const char *level, const char *fmt, ...) _printk(FAT_PRINTK_PREFIX "%pV\n", level, sb->s_id, &vaf); va_end(args); } +EXPORT_SYMBOL_GPL(_fat_msg); /* Flushes the number of free clusters on FAT32 */ /* XXX: Need to write one per FSINFO block. Currently only writes 1 */ diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index d46d1a3851f2..7f2c68149efd 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -292,11 +292,17 @@ static int msdos_create(struct mnt_idmap *idmap, struct inode *dir, if (err) goto out; inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); - brelse(sinfo.bh); if (IS_ERR(inode)) { + int err2; + err = PTR_ERR(inode); + err2 = fat_remove_entries(dir, &sinfo); + if (err2) + fat_msg(sb, KERN_WARNING, + "failed to roll back new entry (%d)", err2); goto out; } + brelse(sinfo.bh); fat_truncate_time(inode, &ts, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME); /* timestamp is already written, so mark_inode_dirty() is unneeded. */ @@ -381,12 +387,20 @@ static struct dentry *msdos_mkdir(struct mnt_idmap *idmap, struct inode *dir, inc_nlink(dir); inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); - brelse(sinfo.bh); if (IS_ERR(inode)) { + int err2; + err = PTR_ERR(inode); - /* the directory was completed, just return a error */ - goto out; + err2 = fat_remove_entries(dir, &sinfo); + if (err2) { + fat_msg(sb, KERN_WARNING, + "failed to roll back new entry (%d)", err2); + goto out; + } + drop_nlink(dir); + goto out_free; } + brelse(sinfo.bh); set_nlink(inode, 2); fat_truncate_time(inode, &ts, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME); /* timestamp is already written, so mark_inode_dirty() is unneeded. */ diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index da3e89c0b16a..b366e0163272 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -771,11 +771,17 @@ static int vfat_create(struct mnt_idmap *idmap, struct inode *dir, inode_inc_iversion(dir); inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); - brelse(sinfo.bh); if (IS_ERR(inode)) { + int err2; + err = PTR_ERR(inode); + err2 = fat_remove_entries(dir, &sinfo); + if (err2) + fat_msg(sb, KERN_WARNING, + "failed to roll back new entry (%d)", err2); goto out; } + brelse(sinfo.bh); inode_inc_iversion(inode); d_instantiate(dentry, inode); @@ -870,12 +876,20 @@ static struct dentry *vfat_mkdir(struct mnt_idmap *idmap, struct inode *dir, inc_nlink(dir); inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos); - brelse(sinfo.bh); if (IS_ERR(inode)) { + int err2; + err = PTR_ERR(inode); - /* the directory was completed, just return a error */ - goto out; + err2 = fat_remove_entries(dir, &sinfo); + if (err2) { + fat_msg(sb, KERN_WARNING, + "failed to roll back new entry (%d)", err2); + goto out; + } + drop_nlink(dir); + goto out_free; } + brelse(sinfo.bh); inode_inc_iversion(inode); set_nlink(inode, 2); From 930256458a8a53b89466a3f90c82dbe554efeced Mon Sep 17 00:00:00 2001 From: Matheus Alves de Almeida Date: Mon, 12 Oct 2026 14:11:04 -0300 Subject: [PATCH 110/134] fat: remove cluster allocation size limit Patch series "fat: allocate multiple clusters in fallocate", v2. This series adds support for allocating multiple clusters in a single fat_add_clusters() call in fat_fallocate(). The multi-cluster TODO in inode.c is not implemented, as doing so would require speculative preallocation or an iomap conversion. This patch (of 3): fat_alloc_clusters() currently BUG()s when asked to allocate more than MAX_BUF_PER_PAGE / 2 clusters. This limit comes from the buffer_head array used to defer syncing and mirroring. Remove the allocation size limit by doing buffer_head batching like fat_free_clusters() does. Link: https://lore.kernel.org/20261012171106.146243-1-matheus.aalmeida@inf.ufrgs.br Link: https://lore.kernel.org/20261012171106.146243-2-matheus.aalmeida@inf.ufrgs.br Signed-off-by: Matheus Alves de Almeida Signed-off-by: Andrew Morton Acked-by: OGAWA Hirofumi --- fs/fat/fatent.c | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c index df23fc85f313..e008fb4e72e0 100644 --- a/fs/fat/fatent.c +++ b/fs/fat/fatent.c @@ -483,6 +483,26 @@ static void fat_collect_bhs(struct buffer_head **bhs, int *nr_bhs, } } +static int fat_flush_bhs(struct super_block *sb, struct buffer_head **bhs, + int *nr_bhs, bool sync) +{ + int err, i; + + if (sync) { + err = fat_sync_bhs(bhs, *nr_bhs); + if (err) + return err; + } + err = fat_mirror_bhs(sb, bhs, *nr_bhs); + if (err) + return err; + for (i = 0; i < *nr_bhs; i++) + brelse(bhs[i]); + *nr_bhs = 0; + + return 0; +} + int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster) { struct super_block *sb = inode->i_sb; @@ -492,8 +512,6 @@ int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster) struct buffer_head *bhs[MAX_BUF_PER_PAGE]; int i, count, err, nr_bhs, idx_clus; - BUG_ON(nr_cluster > (MAX_BUF_PER_PAGE / 2)); /* fixed limit */ - lock_fat(sbi); if (sbi->free_clusters != -1 && sbi->free_clus_valid && sbi->free_clusters < nr_cluster) { @@ -524,14 +542,21 @@ int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster) if (prev_ent.nr_bhs) ops->ent_put(&prev_ent, entry); - fat_collect_bhs(bhs, &nr_bhs, &fatent); - sbi->prev_free = entry; if (sbi->free_clusters != -1) sbi->free_clusters--; cluster[idx_clus] = entry; idx_clus++; + + if (nr_bhs + fatent.nr_bhs > MAX_BUF_PER_PAGE) { + err = fat_flush_bhs(sb, bhs, &nr_bhs, + inode_needs_sync(inode)); + if (err) + goto out; + } + fat_collect_bhs(bhs, &nr_bhs, &fatent); + if (idx_clus == nr_cluster) goto out; @@ -621,17 +646,10 @@ int fat_free_clusters(struct inode *inode, int cluster) } if (nr_bhs + fatent.nr_bhs > MAX_BUF_PER_PAGE) { - if (sb->s_flags & SB_SYNCHRONOUS) { - err = fat_sync_bhs(bhs, nr_bhs); - if (err) - goto error; - } - err = fat_mirror_bhs(sb, bhs, nr_bhs); + err = fat_flush_bhs(sb, bhs, &nr_bhs, + sb->s_flags & SB_SYNCHRONOUS); if (err) goto error; - for (i = 0; i < nr_bhs; i++) - brelse(bhs[i]); - nr_bhs = 0; } fat_collect_bhs(bhs, &nr_bhs, &fatent); } while (cluster != FAT_ENT_EOF); From d2febfdc2b7f77d409f19af8d289a0cf7ff63dbc Mon Sep 17 00:00:00 2001 From: Matheus Alves de Almeida Date: Mon, 12 Oct 2026 14:11:05 -0300 Subject: [PATCH 111/134] fat: support adding multiple clusters fat_add_cluster() currently adds only one cluster to a file's chain, requiring multiple calls when adding more clusters. Add a fat_alloc_chain() helper that allocates a cluster chain but only returns the first cluster. Extend fat_add_cluster() to add multiple clusters in a single call and rename it to fat_add_clusters() to reflect the new behavior. Link: https://lore.kernel.org/20261012171106.146243-3-matheus.aalmeida@inf.ufrgs.br Signed-off-by: Matheus Alves de Almeida Signed-off-by: Andrew Morton Acked-by: OGAWA Hirofumi --- fs/fat/fat.h | 3 ++- fs/fat/fatent.c | 18 ++++++++++++++++-- fs/fat/file.c | 2 +- fs/fat/inode.c | 8 ++++---- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/fs/fat/fat.h b/fs/fat/fat.h index fbd207c55859..5784ca9061b6 100644 --- a/fs/fat/fat.h +++ b/fs/fat/fat.h @@ -395,6 +395,7 @@ extern int fat_ent_write(struct inode *inode, struct fat_entry *fatent, int new, int old, int wait); extern int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster); +int fat_alloc_chain(struct inode *inode, int *cluster, int nr_cluster); extern int fat_free_clusters(struct inode *inode, int cluster); extern int fat_count_free_clusters(struct super_block *sb); extern int fat_trim_fs(struct inode *inode, struct fstrim_range *range); @@ -440,7 +441,7 @@ static inline unsigned long fat_dir_hash(int logstart) { return hash_32(logstart, FAT_HASH_BITS); } -extern int fat_add_cluster(struct inode *inode); +extern int fat_add_clusters(struct inode *inode, int nr_cluster); /* fat/misc.c */ extern __printf(3, 4) __cold diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c index e008fb4e72e0..2d10a815a579 100644 --- a/fs/fat/fatent.c +++ b/fs/fat/fatent.c @@ -503,7 +503,8 @@ static int fat_flush_bhs(struct super_block *sb, struct buffer_head **bhs, return 0; } -int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster) +static int __fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster, + bool return_all) { struct super_block *sb = inode->i_sb; struct msdos_sb_info *sbi = MSDOS_SB(sb); @@ -546,7 +547,10 @@ int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster) if (sbi->free_clusters != -1) sbi->free_clusters--; - cluster[idx_clus] = entry; + if (return_all) + cluster[idx_clus] = entry; + else if (!idx_clus) + *cluster = entry; idx_clus++; if (nr_bhs + fatent.nr_bhs > MAX_BUF_PER_PAGE) { @@ -596,6 +600,16 @@ int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster) return err; } +int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster) +{ + return __fat_alloc_clusters(inode, cluster, nr_cluster, true); +} + +int fat_alloc_chain(struct inode *inode, int *cluster, int nr_cluster) +{ + return __fat_alloc_clusters(inode, cluster, nr_cluster, false); +} + int fat_free_clusters(struct inode *inode, int cluster) { struct super_block *sb = inode->i_sb; diff --git a/fs/fat/file.c b/fs/fat/file.c index 6c475c53334c..fc77d20e8fe7 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -292,7 +292,7 @@ static long fat_fallocate(struct file *file, int mode, /* Start the allocation.We are not zeroing out the clusters */ while (nr_cluster-- > 0) { - err = fat_add_cluster(inode); + err = fat_add_clusters(inode, 1); if (err) goto error; } diff --git a/fs/fat/inode.c b/fs/fat/inode.c index 0b0bbe777842..d85c8fbdcca4 100644 --- a/fs/fat/inode.c +++ b/fs/fat/inode.c @@ -102,16 +102,16 @@ static struct fat_floppy_defaults { }, }; -int fat_add_cluster(struct inode *inode) +int fat_add_clusters(struct inode *inode, int nr_cluster) { int err, cluster; - err = fat_alloc_clusters(inode, &cluster, 1); + err = fat_alloc_chain(inode, &cluster, nr_cluster); if (err) return err; /* FIXME: this cluster should be added after data of this * cluster is writed */ - err = fat_chain_add(inode, cluster, 1); + err = fat_chain_add(inode, cluster, nr_cluster); if (err) fat_free_clusters(inode, cluster); return err; @@ -153,7 +153,7 @@ static inline int __fat_get_block(struct inode *inode, sector_t iblock, */ if (!offset && !(iblock < last_block)) { /* TODO: multiple cluster allocation would be desirable. */ - err = fat_add_cluster(inode); + err = fat_add_clusters(inode, 1); if (err) return err; } From 8d40831419c451252e2fd328f59e1a54ed0061b7 Mon Sep 17 00:00:00 2001 From: Matheus Alves de Almeida Date: Mon, 12 Oct 2026 14:11:06 -0300 Subject: [PATCH 112/134] fat: allocate multiple clusters in fallocate The fat_fallocate() code currently does many 1-cluster allocations when growing a file in the FALLOC_FL_KEEP_SIZE path. Make it allocate everything with one call instead. Benchmarking was done under QEMU/KVM with a virtio-blk device using cache=none. Each result is the arithmetic mean of the listed samples. start = now_ns(); for (unsigned int i = 0; i < count; i++) fallocate(fds[i], FALLOC_FL_KEEP_SIZE, 0, bytes); elapsed = now_ns() - start; FAT32 with 512-byte clusters using count=16 files: length mounted sync? before after improvement samples 256MiB no 2.328s 0.175s 13.2x 10 16MiB no 149.659ms 14.036ms 10.7x 10 16MiB yes 747.993s 3.445s 217.1x 3 1MiB yes 46.63s 0.333s 140.1x 10 FAT32 with 512-byte clusters using count=128 files: length mounted sync? before after improvement samples 1MiB no 90.873ms 4.686ms 19.4x 10 16KiB no 1.287ms 0.241ms 5.3x 10 128KiB yes 47.284s 0.350s 135x 10 4KiB yes 1.98s 0.144s 13.7x 10 FAT32 with 4096-byte clusters using count=16 files: length mounted sync? before after improvement samples 256MiB no 295.392ms 24.733ms 11.9x 10 16MiB no 20.509ms 1.404ms 14.6x 10 16MiB yes 92.489s 0.348s 265.4x 10 1MiB yes 5.837s 0.048s 121.3x 10 The improvement is especially large on synchronous mounts, where the new code can batch FAT updates instead of synchronously handling each cluster allocation separately. Link: https://lore.kernel.org/20261012171106.146243-4-matheus.aalmeida@inf.ufrgs.br Signed-off-by: Matheus Alves de Almeida Signed-off-by: Andrew Morton Acked-by: OGAWA Hirofumi --- fs/fat/file.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fs/fat/file.c b/fs/fat/file.c index fc77d20e8fe7..c08135cdda66 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -290,12 +290,10 @@ static long fat_fallocate(struct file *file, int mode, nr_cluster = (mm_bytes + (sbi->cluster_size - 1)) >> sbi->cluster_bits; - /* Start the allocation.We are not zeroing out the clusters */ - while (nr_cluster-- > 0) { - err = fat_add_clusters(inode, 1); - if (err) - goto error; - } + /* Allocate the clusters. We are not zeroing them out */ + err = fat_add_clusters(inode, nr_cluster); + if (err) + goto error; } else { if ((offset + len) <= i_size_read(inode)) goto error; From 71743471204e21113295c786e5cfa570294234a4 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 13 Oct 2026 23:39:29 +0800 Subject: [PATCH 113/134] ocfs2/dlm: hold a lock reference across grant AST enqueue A newly granted remote lock must remain alive until its grant AST is queued. dlmlock_master() publishes the lock on res->granted with its base and queue references, then drops res->spinlock before calling dlm_queue_ast(). The resource's reserved AST count does not retain the lock itself. This can fail when a peer dies with an immediately grantable CREATE buffered on its socket. O2NET's node-down callback flushes receive work before DLM cleanup, but the flush only waits for work queued on entry. A data-ready callback that already selected the socket container can queue new receive work after the flush advances its work color. Socket shutdown does not discard bytes already queued for TCP receive. The late receive work and DLM node-down cleanup can interleave as follows (the lock reference operations shown are before this fix): Socket data-ready Node-down thread Receive worker ----------------- ---------------- -------------- o2net_data_ready() select sc under callback read lock o2net_disconnect_node() queue shutdown begin flush; new color queue sc_rx_work in the new color release callback lock shutdown socket flush returns read buffered CREATE dlmlock_master() lock res->spinlock add lock to granted unlock res->spinlock DLM node-down cleanup lock res->spinlock dlm_free_dead_locks() unlink lock put queue/base refs free lock unlock res->spinlock dlm_queue_ast() __dlm_queue_ast() read lock->lockres (use-after-free) DLM cleanup holds res->spinlock while dlm_free_dead_locks() drops the queue and base references. The CREATE handler no longer holds that spinlock when __dlm_queue_ast() reads the lock, before taking its AST list reference. Take a temporary lock reference under res->spinlock when a grant AST will be queued, and drop it after dlm_queue_ast(). This keeps the lock alive across cleanup until the AST path has taken its own reference, without reversing the existing AST/resource lock order. KASAN report as below: BUG: KASAN: slab-use-after-free in __dlm_queue_ast+0x8a4/0xc00 Read of size 8 at addr ffff88810ead5940 by task kworker/u16:3/52 CPU: 3 UID: 0 PID: 52 Comm: kworker/u16:3 Not tainted 7.3.0-rc4-next-20260921-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: o2net o2net_rx_until_empty Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? __dlm_queue_ast+0x8a4/0xc00 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20e/0x420 ? __dlm_queue_ast+0x8a4/0xc00 kasan_report+0xe0/0x110 ? __dlm_queue_ast+0x8a4/0xc00 __dlm_queue_ast+0x8a4/0xc00 ? srso_alias_return_thunk+0x5/0xfbef5 ? srso_alias_return_thunk+0x5/0xfbef5 ? do_raw_spin_lock+0x130/0x270 ? __pfx___dlm_queue_ast+0x10/0x10 ? __pfx_do_raw_spin_lock+0x10/0x10 ? _raw_spin_unlock_irqrestore+0x45/0x60 ? srso_alias_return_thunk+0x5/0xfbef5 ? lockdep_hardirqs_on_prepare+0xeb/0x1a0 dlm_queue_ast+0x46/0x70 dlmlock_master+0x51f/0xf50 ? __pfx_dlmlock_master+0x10/0x10 ? dlm_create_lock_handler+0x2b2/0x870 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x290 dlm_create_lock_handler+0x35b/0x870 ? __pfx_dlm_create_lock_handler+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? srso_alias_return_thunk+0x5/0xfbef5 ? trace_hardirqs_on+0x18/0x160 ? srso_alias_return_thunk+0x5/0xfbef5 ? kvm_clock_get_cycles+0x31/0x60 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_dlm_create_lock_handler+0x10/0x10 o2net_rx_until_empty+0x1dae/0x32c0 ? reacquire_held_locks+0xdd/0x200 ? __pfx_o2net_rx_until_empty+0x10/0x10 ? lock_acquire+0x190/0x300 ? process_one_work+0x935/0x1b40 ? process_one_work+0x834/0x1b40 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x290 ? srso_alias_return_thunk+0x5/0xfbef5 process_one_work+0x9a8/0x1b40 ? __pfx_process_one_work+0x10/0x10 ? lock_acquire+0x190/0x300 ? lock_is_held_type+0x8f/0x100 ? srso_alias_return_thunk+0x5/0xfbef5 worker_thread+0x65c/0xe40 ? srso_alias_return_thunk+0x5/0xfbef5 ? __kthread_parkme+0x177/0x220 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_worker_thread+0x10/0x10 kthread+0x351/0x460 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x659/0x940 ? __pfx_ret_from_fork+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __switch_to+0x74f/0xf70 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Allocated by task 52: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_slab_alloc+0x89/0x90 kmem_cache_alloc_noprof+0x20b/0x5c0 dlm_new_lock+0x39/0x5f0 dlm_create_lock_handler+0x16e/0x870 o2net_rx_until_empty+0x1dae/0x32c0 process_one_work+0x9a8/0x1b40 worker_thread+0x65c/0xe40 kthread+0x351/0x460 ret_from_fork+0x659/0x940 ret_from_fork_asm+0x1a/0x30 Freed by task 603: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kmem_cache_free+0x242/0x580 dlm_lock_release+0x1df/0x2f0 dlm_lock_put+0x51/0x80 __dlm_hb_node_down+0x15be/0x2710 dlm_hb_node_down_cb+0x6c/0xa0 o2hb_run_event_list+0x34a/0x500 o2hb_do_disk_heartbeat+0x1a07/0x2950 o2hb_thread+0x1ed/0xdc0 kthread+0x351/0x460 ret_from_fork+0x659/0x940 ret_from_fork_asm+0x1a/0x30 The buggy address belongs to the object at ffff88810ead5900 which belongs to the cache o2dlm_lock of size 184 The buggy address is located 64 bytes inside of freed 184-byte region [ffff88810ead5900, ffff88810ead59b8) [Page and shadow memory dumps omitted.] Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0043-v5-1ad07df761ff49638789@gmail.com Fixes: 2c5c54aca9d0 ("ocfs2/dlm: Add missing dlm_lock_put()s") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Kees Cook Cc: Sunil Mushran Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/dlm/dlmlock.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/ocfs2/dlm/dlmlock.c b/fs/ocfs2/dlm/dlmlock.c index 46120eb75d90..660237c0d7cd 100644 --- a/fs/ocfs2/dlm/dlmlock.c +++ b/fs/ocfs2/dlm/dlmlock.c @@ -138,6 +138,8 @@ static enum dlm_status dlmlock_master(struct dlm_ctxt *dlm, res->lockname.len)) { kick_thread = 1; call_ast = 1; + /* Keep the lock alive through AST enqueue. */ + dlm_lock_get(lock); } else { mlog(0, "%s: returning DLM_NORMAL to " "node %u for reco lock\n", dlm->name, @@ -166,10 +168,12 @@ static enum dlm_status dlmlock_master(struct dlm_ctxt *dlm, wake_up(&res->wq); /* either queue the ast or release it */ - if (call_ast) + if (call_ast) { dlm_queue_ast(dlm, lock); - else + dlm_lock_put(lock); + } else { dlm_lockres_release_ast(dlm, res); + } dlm_lockres_calc_usage(dlm, res); if (kick_thread) From 1dd1f005026d52642b994d63baa16b0b31f3b14f Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 14 Oct 2026 11:43:40 +0800 Subject: [PATCH 114/134] ocfs2: cancel quota sync work before freeing quota info OCFS2 quota info owns dqi_sync_work and must outlive both its timer and qsync_work_fn(). ocfs2_local_free_info() frees that owner without cancelling the work, relying on the quota suspend/disable wrappers. Loading quotas on a read-only to read-write remount can successfully queue the work in ocfs2_global_read_info() and then fail while attaching a dquot to an existing inode in add_dquot_ref(). The quota core unwinds that failure directly through dquot_disable(), bypassing the OCFS2 wrapper: Remount task (s_umount write) Timer/workqueue ocfs2_global_read_info() schedules dqi_sync_work add_dquot_ref() returns an error dquot_disable() ocfs2_local_free_info() frees oinfo delayed-work timer expires qsync_work_fn() reads oinfo The timer core can then write to the freed embedded timer, or qsync_work_fn() can read oinfo->dqi_gqinode before its s_umount trylock. The outer ocfs2_disable_quotas() cleanup skips the failed type because dquot_disable() has already cleared its loaded state. Cancel dqi_sync_work synchronously at the start of ocfs2_local_free_info(), before dropping the global inode or freeing any owner state. The work is initialized whenever read_file_info() succeeds. dquot_disable() holds s_umount for write, so the callback skips its quota scan via down_read_trylock() and can finish without waiting for teardown. The synchronous cancel also handles its self-rearm. KASAN report as below: BUG: KASAN: slab-use-after-free in __run_timers+0x9e9/0xa10 Write of size 8 at addr ffff888111b0aad0 by task swapper/3/0 Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? __run_timers+0x9e9/0xa10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20e/0x420 ? __run_timers+0x9e9/0xa10 kasan_report+0xe0/0x110 ? __run_timers+0x9e9/0xa10 __run_timers+0x9e9/0xa10 ? __pfx___run_timers+0x10/0x10 ? __lock_acquire+0x5a3/0x2140 ? __pfx_do_raw_spin_lock+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 timer_expire_remote+0xfa/0x160 ? __pfx_timer_expire_remote+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? srso_alias_return_thunk+0x5/0xfbef5 ? trace_hardirqs_on+0x18/0x160 tmigr_handle_remote_up+0x4ba/0x9d0 ? __pfx_tmigr_handle_remote_up+0x10/0x10 ? tmigr_handle_remote+0x21a/0x340 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x290 ? __pfx_tmigr_handle_remote_up+0x10/0x10 __walk_groups_from+0x5a/0x160 tmigr_handle_remote+0x24f/0x340 ? __pfx_tmigr_handle_remote+0x10/0x10 ? lock_acquire+0x190/0x300 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x290 run_timer_softirq+0x222/0x270 ? __pfx_run_timer_softirq+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? srso_alias_return_thunk+0x5/0xfbef5 ? lockdep_hardirqs_on_prepare+0xb8/0x1a0 ? srso_alias_return_thunk+0x5/0xfbef5 handle_softirqs+0x1fa/0x8d0 ? __pfx_handle_softirqs+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? _raw_spin_unlock+0x23/0x40 ? srso_alias_return_thunk+0x5/0xfbef5 ? __hrtimer_rearm_deferred+0x1fb/0x5d0 __irq_exit_rcu+0x113/0x1e0 irq_exit_rcu+0xe/0x20 sysvec_apic_timer_interrupt+0x6c/0x80 asm_sysvec_apic_timer_interrupt+0x1a/0x20 ? ct_kernel_exit.constprop.0+0xc5/0xf0 ? do_idle+0x3ae/0x650 default_idle+0x9/0x10 default_idle_call+0x6c/0xb0 do_idle+0x3ae/0x650 ? cpuhp_online_idle+0x9c/0xb0 ? __pfx_do_idle+0x10/0x10 ? __entry_text_end+0x1020f6/0x1020f9 ? lockdep_hardirqs_on_prepare+0xeb/0x1a0 ? srso_alias_return_thunk+0x5/0xfbef5 ? trace_hardirqs_on+0x18/0x160 ? srso_alias_return_thunk+0x5/0xfbef5 cpu_startup_entry+0x54/0x60 start_secondary+0x223/0x2d0 ? __pfx_start_secondary+0x10/0x10 common_startup_64+0x13e/0x158 Allocated by task 523: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_cache_noprof+0x28d/0x610 ocfs2_local_read_info+0x181/0x1580 dquot_load_quota_sb+0x69e/0x11c0 dquot_load_quota_inode+0x227/0x3e0 ocfs2_enable_quotas+0x27c/0x520 ocfs2_reconfigure+0x964/0xd00 reconfigure_super+0x3ca/0xa70 path_mount+0x141e/0x1e80 __x64_sys_mount+0x24b/0x2d0 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 523: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x308/0x580 ocfs2_local_free_info+0x47b/0x800 dquot_disable+0x5d9/0x17a0 dquot_load_quota_sb+0xd3c/0x11c0 dquot_load_quota_inode+0x227/0x3e0 ocfs2_enable_quotas+0x27c/0x520 ocfs2_reconfigure+0x964/0xd00 reconfigure_super+0x3ca/0xa70 path_mount+0x141e/0x1e80 __x64_sys_mount+0x24b/0x2d0 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888111b0a800 which belongs to the cache kmalloc-1k of size 1024 The buggy address is located 720 bytes inside of freed 1024-byte region [ffff888111b0a800, ffff888111b0ac00) Host details, register and page dumps omitted. Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0052-v3-b4904bd6c85daf033adf@gmail.com Fixes: 1a6152d36dee ("quota: propagate error from __dquot_initialize") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Reviewed-by: Jan Kara Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Chao Yu Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/quota_local.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c index 76e7dd5aecc8..a2ce25a33607 100644 --- a/fs/ocfs2/quota_local.c +++ b/fs/ocfs2/quota_local.c @@ -840,6 +840,9 @@ static int ocfs2_local_free_info(struct super_block *sb, int type) int mark_clean = 1, len; int status = 0; + /* Quota-core error paths can bypass ocfs2_disable_quotas(). */ + cancel_delayed_work_sync(&oinfo->dqi_sync_work); + iput(oinfo->dqi_gqinode); ocfs2_simple_drop_lockres(OCFS2_SB(sb), &oinfo->dqi_gqlock); ocfs2_lock_res_free(&oinfo->dqi_gqlock); From 125fe08ff6cfa064e1212f586f01993ee45c0330 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Wed, 14 Oct 2026 19:23:21 +0800 Subject: [PATCH 115/134] ocfs2/dlm: hold the lookup reference across convert AST enqueue dlm_convert_lock_handler() drops the reference it took when it found the lock on res->granted at its leave label, one statement before it calls dlm_queue_ast(). dlm_free_dead_locks() takes res->spinlock and drops the list and base references, so a peer death landing in that gap leaves the handler's own put as the one that frees the lock, and __dlm_queue_ast() then reads lock->lockres from freed memory. The handler is registered next to dlm_create_lock_handler(), so a convert message buffered on a dying peer's socket arrives on the same o2net receive work that o2net_disconnect_node() does not wait for. The two can interleave as follows, with the reference counts as they are before this fix: Socket data-ready Node-down thread Receive worker ----------------- ---------------- -------------- o2net_data_ready() select sc under callback read lock o2net_disconnect_node() queue shutdown begin flush; new color queue sc_rx_work in the new color release callback lock shutdown socket flush returns read buffered convert_lock dlm_convert_lock_handler() find lock on res->granted dlm_lock_get() refs = 3 lock res->spinlock __dlmconvert_master() call_ast = 1 unlock res->spinlock DLM node-down cleanup lock res->spinlock dlm_free_dead_locks() unlink lock put list ref refs = 2 put base ref refs = 1 unlock res->spinlock dlm_lock_put() refs = 0 free lock dlm_queue_ast() __dlm_queue_ast() read lock->lockres (use-after-free) __dlmconvert_master() only moves the lock between lists, so the count entering the gap is base, list and lookup. Drop the lookup reference after the AST is queued instead of before it and the race ends with the one __dlm_queue_ast() took, which dlm_flush_asts() releases later with ast_pending cleared and ast_list unlinked. No extra get is needed, and the normal case ends with the same count as before. dlmconvert_master() has the same shape but is only reached from dlmlock() for a lock this node owns, which dlm_free_dead_locks() never matches. Link: https://lore.kernel.org/20261014112321.2955584-1-joseph.qi@linux.alibaba.com Fixes: 2c5c54aca9d0 ("ocfs2/dlm: Add missing dlm_lock_put()s") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker Cc: --- fs/ocfs2/dlm/dlmconvert.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/ocfs2/dlm/dlmconvert.c b/fs/ocfs2/dlm/dlmconvert.c index 450d46eefab3..8a828448ba95 100644 --- a/fs/ocfs2/dlm/dlmconvert.c +++ b/fs/ocfs2/dlm/dlmconvert.c @@ -534,15 +534,19 @@ int dlm_convert_lock_handler(struct o2net_msg *msg, u32 len, void *data, } leave: - if (lock) - dlm_lock_put(lock); - /* either queue the ast or release it, if reserved */ if (call_ast) dlm_queue_ast(dlm, lock); else if (ast_reserved) dlm_lockres_release_ast(dlm, res); + /* + * Keep the lock alive through AST enqueue: a concurrent + * dlm_free_dead_locks() drops the list and base references. + */ + if (lock) + dlm_lock_put(lock); + if (kick_thread) dlm_kick_thread(dlm, res); From 0d0be10a74f7a76435dfdf9bbc010e3978c2f287 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Wed, 14 Oct 2026 18:58:56 +0800 Subject: [PATCH 116/134] ocfs2: validate the xattr tree root of an indexed xattr block ocfs2_validate_xattr_block() checks an xattr block's signature, blkno, generation and suballocator fields, and for a non-indexed block the entry array it carries. An indexed block is passed straight through, so the extent list in its xattr tree root is never looked at. Both fields that describe the list are bounded. The two places that create a root set l_count from ocfs2_xattr_recs_per_xb() and nothing rewrites it, and the insert path asserts in ocfs2_rotate_leaf() that l_next_free_rec stays within l_count. Check both when the block is first read, so a root that disagrees with the block size, or claims more records than it has room for, is caught before anything indexes into l_recs[]. An empty root is deliberately not rejected. Emptying the tree leaves it that way: ocfs2_rm_xattr_cluster() removes one extent per transaction, and the last one ends in ocfs2_truncate_rec(), whose closing ocfs2_rotate_tree_left() reaches ocfs2_remove_empty_extent() on the depth-zero root and takes l_next_free_rec to zero. That transaction commits before ocfs2_xattr_free_block() frees the block, and before ocfs2_xattr_remove() clears i_xattr_loc, in two later transactions, so a crash in between leaves an orphan inode pointing at an xattr block whose root has no extents. Both readers already cope with it: __ocfs2_xattr_index_block_find() answers -ENODATA, and ocfs2_iterate_xattr_index_block() returns without iterating. Link: https://lore.kernel.org/20261014105856.2909777-1-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reviewed-by: Heming Zhao Cc: Mark Fasheh Cc: Joel Becker --- fs/ocfs2/xattr.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 52e46d1b8763..8b7a382dc2ff 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -491,6 +491,7 @@ static int ocfs2_validate_xattr_block(struct super_block *sb, struct buffer_head *bh) { int rc; + struct ocfs2_extent_list *el; struct ocfs2_xattr_block *xb = (struct ocfs2_xattr_block *)bh->b_data; @@ -573,6 +574,30 @@ static int ocfs2_validate_xattr_block(struct super_block *sb, region_offset); } + /* + * An indexed xattr block carries the root of the xattr tree. Its + * record count is fixed by the block size and l_next_free_rec never + * exceeds it. A zero l_next_free_rec is valid: emptying the tree on + * the inode deletion path leaves the root with no extents. + */ + el = &xb->xb_attrs.xb_root.xt_list; + + if (le16_to_cpu(el->l_count) != ocfs2_xattr_recs_per_xb(sb)) { + return ocfs2_error(sb, + "Xattr tree root #%llu has invalid l_count %u (expected %u)\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(el->l_count), + ocfs2_xattr_recs_per_xb(sb)); + } + + if (le16_to_cpu(el->l_next_free_rec) > le16_to_cpu(el->l_count)) { + return ocfs2_error(sb, + "Xattr tree root #%llu has l_next_free_rec %u past the end of its list (l_count %u)\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(el->l_next_free_rec), + le16_to_cpu(el->l_count)); + } + return 0; } From 8c714a074074af7ae59233c1fa6ef9d7aa710462 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 14 Oct 2026 11:47:15 +0800 Subject: [PATCH 117/134] ocfs2: pin refcount trees before dropping the lookup lock A refcount tree returned to ocfs2_lock_refcount_tree() must stay alive until that caller releases its operation reference. However, ocfs2_get_refcount_tree() returns the cached pointer after dropping osb_lock, and the caller only takes its kref afterwards. The registry reference does not protect this gap from concurrent removal. This can happen when another node frees a refcount root and reuses its block with a different generation while this mount retains the old cached tree. Two local operations on different inodes sharing the new root, such as data CoW and an external xattr update, can then look up the same stale tree. Their inode locks are independent, and both the REFCOUNT DLM lock and rf_sem are acquired after the first operation reference. The following ordering is possible, with no other operation references: Data CoW Xattr update -------- ------------ Look up the old tree under osb_lock, then drop osb_lock Look up the same tree and take a kref Acquire the tree locks and read the new on-disk generation Erase the old tree under osb_lock Unlock and drop the operation kref Drop the registry kref, freeing tree Take a kref through the freed tree pointer The final kref_get() accesses freed rf_getcnt storage, and subsequent accesses in ocfs2_lock_refcount_tree() also use the freed allocation. Take the operation reference in ocfs2_get_refcount_tree() before releasing osb_lock, including when an insertion loses to another lookup. Removal must acquire the same lock, so it cannot drop the registry reference before the lookup has pinned the tree. Keep the existing unlock and error path puts. The two callers that already hold the refcount tree lock keep their borrowed lookup behavior and do not acquire an extra reference. KASAN report as below: BUG: KASAN: slab-use-after-free in ocfs2_lock_refcount_tree+0x94a/0x9b0 Read of size 4 at addr ffff888117212820 by task python3/497 [Kernel and hardware identification omitted.] Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? ocfs2_lock_refcount_tree+0x94a/0x9b0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20e/0x420 ? ocfs2_lock_refcount_tree+0x94a/0x9b0 kasan_report+0xe0/0x110 ? ocfs2_lock_refcount_tree+0x94a/0x9b0 ocfs2_lock_refcount_tree+0x94a/0x9b0 ? __pfx_ocfs2_lock_refcount_tree+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? ocfs2_refcount_cow+0x3b6/0xd90 ? srso_alias_return_thunk+0x5/0xfbef5 ? __kmalloc_cache_noprof+0x28d/0x610 ? ocfs2_refcount_cow+0x3b6/0xd90 ocfs2_refcount_cow+0x410/0xd90 ? __pfx_ocfs2_refcount_cow+0x10/0x10 ? __pfx___might_resched+0x10/0x10 ? down_write+0x11a/0x1f0 ? __pfx_down_write+0x10/0x10 ? ocfs2_inode_unlock+0x1a9/0x260 ? __pfx_ocfs2_inode_unlock+0x10/0x10 ? ocfs2_file_write_iter+0x1413/0x1d30 ocfs2_file_write_iter+0x157b/0x1d30 ? __pfx_ocfs2_file_write_iter+0x10/0x10 ? lock_acquire+0x190/0x300 ? __x64_sys_pwrite64+0x1b7/0x220 ? __pfx___might_resched+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? security_file_permission+0x26/0x80 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_is_held_type+0x8f/0x100 vfs_write+0x639/0x1010 ? __pfx_ocfs2_file_write_iter+0x10/0x10 ? lock_vma_under_rcu+0x154/0x4a0 ? __pfx_vfs_write+0x10/0x10 ? lock_release+0x121/0x290 ? srso_alias_return_thunk+0x5/0xfbef5 __x64_sys_pwrite64+0x1b7/0x220 ? __pfx___x64_sys_pwrite64+0x10/0x10 ? irqentry_exit+0xbe/0x820 ? srso_alias_return_thunk+0x5/0xfbef5 ? trace_hardirqs_on_prepare+0x11d/0x150 ? srso_alias_return_thunk+0x5/0xfbef5 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f [Register dump omitted.] Allocated by task 491: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_cache_noprof+0x28d/0x610 ocfs2_allocate_refcount_tree+0x49/0x1a0 ocfs2_create_refcount_tree+0x5d7/0x1630 ocfs2_attach_refcount_tree+0x57d/0x1100 __ocfs2_reflink.isra.0+0x1c4/0x1940 ocfs2_reflink+0x501/0x9b0 ocfs2_reflink_ioctl+0x6b2/0x920 ocfs2_ioctl+0x538/0x640 __x64_sys_ioctl+0x163/0x1d0 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 498: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x308/0x580 ocfs2_lock_refcount_tree+0x672/0x9b0 ocfs2_xattr_set+0x72c/0x2530 ocfs2_xattr_user_set+0xaf/0xf0 __vfs_setxattr+0x143/0x1b0 __vfs_setxattr_noperm+0x112/0x5b0 __vfs_setxattr_locked+0xfe/0x2b0 vfs_setxattr+0x13c/0x370 do_setxattr+0x128/0x170 filename_setxattr+0x147/0x190 path_setxattrat+0x1ac/0x260 __x64_sys_setxattr+0xcb/0x140 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888117212800 which belongs to the cache kmalloc-1k of size 1024 The buggy address is located 32 bytes inside of freed 1024-byte region [ffff888117212800, ffff888117212c00) [Page and memory dumps omitted.] Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0037-v4-9f1a261dfbc341721689@gmail.com Fixes: 374a263e790c ("ocfs2: Add refcount tree lock mechanism.") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Tao Ma Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/refcounttree.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c index 3e9cccf06e48..4f13b5a6faa7 100644 --- a/fs/ocfs2/refcounttree.c +++ b/fs/ocfs2/refcounttree.c @@ -375,8 +375,10 @@ ocfs2_allocate_refcount_tree(struct ocfs2_super *osb, u64 rf_blkno) return new; } +/* Callers without the refcount tree lock must request a reference. */ static int ocfs2_get_refcount_tree(struct ocfs2_super *osb, u64 rf_blkno, - struct ocfs2_refcount_tree **ret_tree) + struct ocfs2_refcount_tree **ret_tree, + bool get_ref) { int ret = 0; struct ocfs2_refcount_tree *tree, *new = NULL; @@ -433,6 +435,8 @@ static int ocfs2_get_refcount_tree(struct ocfs2_super *osb, u64 rf_blkno, new = NULL; out: + if (get_ref) + ocfs2_refcount_tree_get(tree); *ret_tree = tree; osb->osb_ref_tree_lru = tree; @@ -506,14 +510,12 @@ int ocfs2_lock_refcount_tree(struct ocfs2_super *osb, struct ocfs2_refcount_block *rb; again: - ret = ocfs2_get_refcount_tree(osb, ref_blkno, &tree); + ret = ocfs2_get_refcount_tree(osb, ref_blkno, &tree, true); if (ret) { mlog_errno(ret); return ret; } - ocfs2_refcount_tree_get(tree); - ret = __ocfs2_lock_refcount_tree(osb, tree, rw); if (ret) { mlog_errno(ret); @@ -2355,7 +2357,8 @@ int ocfs2_decrease_refcount(struct inode *inode, goto out; } - ret = ocfs2_get_refcount_tree(OCFS2_SB(inode->i_sb), ref_blkno, &tree); + ret = ocfs2_get_refcount_tree(OCFS2_SB(inode->i_sb), ref_blkno, + &tree, false); if (ret) { mlog_errno(ret); goto out; @@ -2590,7 +2593,7 @@ int ocfs2_prepare_refcount_change_for_del(struct inode *inode, BUG_ON(!ocfs2_is_refcount_inode(inode)); ret = ocfs2_get_refcount_tree(OCFS2_SB(inode->i_sb), - refcount_loc, &tree); + refcount_loc, &tree, false); if (ret) { mlog_errno(ret); goto out; From c4d9005e7e1ee6ce277f47cf53d44fc5b90b287b Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Mon, 12 Oct 2026 07:53:29 -0400 Subject: [PATCH 118/134] selftests/proc: proc-pid-vm: fix unused variable warning on non-x86 g_vsyscall is only used by the x86_64 test, so clang builds for other architectures get: proc-pid-vm.c:224:21: warning: unused variable 'g_vsyscall' [-Wunused-variable] The vsyscall strings next to it had the same problem and were marked __maybe_unused for it. Move all of the vsyscall variables into the __x86_64__ block that uses them instead, and drop those annotations along with the local __maybe_unused fallback that was added for them. Link: https://lore.kernel.org/20261012115330.3777115-1-sashal@kernel.org Fixes: 17415606f9d8 ("tools/testing/selftests/proc/proc-pid-vm.c: test with vsyscall in mind") Signed-off-by: Sasha Levin Signed-off-by: Andrew Morton Suggested-by: Andrew Morton Assisted-by: LLM Cc: Shuah Khan Cc: Alexey Dobriyan --- tools/testing/selftests/proc/proc-pid-vm.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/proc/proc-pid-vm.c b/tools/testing/selftests/proc/proc-pid-vm.c index 4e6a3e53f975..98e882e56815 100644 --- a/tools/testing/selftests/proc/proc-pid-vm.c +++ b/tools/testing/selftests/proc/proc-pid-vm.c @@ -47,10 +47,6 @@ #include #include -#ifndef __maybe_unused -#define __maybe_unused __attribute__((__unused__)) -#endif - #include "kselftest.h" static inline long sys_execveat(int dirfd, const char *pathname, char **argv, char **envp, int flags) @@ -214,7 +210,6 @@ static int make_exe(const uint8_t *payload, size_t len) return fd1; } -#endif /* * 0: vsyscall VMA doesn't exist vsyscall=none @@ -222,15 +217,14 @@ static int make_exe(const uint8_t *payload, size_t len) * 2: vsyscall VMA is r-xp vsyscall=emulate */ static volatile int g_vsyscall; -static const char *str_vsyscall __maybe_unused; +static const char *str_vsyscall; -static const char str_vsyscall_0[] __maybe_unused = ""; -static const char str_vsyscall_1[] __maybe_unused = +static const char str_vsyscall_0[] = ""; +static const char str_vsyscall_1[] = "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]\n"; -static const char str_vsyscall_2[] __maybe_unused = +static const char str_vsyscall_2[] = "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]\n"; -#ifdef __x86_64__ static void sigaction_SIGSEGV(int _, siginfo_t *__, void *___) { _exit(g_vsyscall); From c5b7a30a5172317e1e3cba61d8c9f9c588c497bb Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Fri, 16 Oct 2026 00:58:38 +0800 Subject: [PATCH 119/134] ocfs2/cluster: initialize heartbeat regions before publishing them A region on o2hb_all_regions must have an initialized config item and name. o2hb_heartbeat_group_make_item() currently links the zeroed region under o2hb_live_lock, drops the lock, and only then initializes hr_item. The lock therefore protects the list without protecting initialization from readers of the published item. With global heartbeat enabled, a DLM_QUERY_REGION from the current joining node can pass the existing-domain and protocol checks while a configfs mkdir creates another region. The DLM locks are not taken by the constructor, and the network worker does not take the configfs subsystem mutex. The following ordering is possible: Configfs mkdir O2net receive worker -------------- -------------------- Allocate reg Lock o2hb_live_lock Link into o2hb_all_regions Unlock o2hb_live_lock dlm_query_region_handler() dlm_match_regions() o2hb_get_all_regions() Lock o2hb_live_lock Copy 32 bytes from NULL name Unlock o2hb_live_lock config_item_init_type_name() The query thus dereferences NULL. Initialize hr_item before linking the region so the existing list lock also orders name initialization before any reader. Reject a failed name allocation, since the initialization helper does not propagate that error. Use config_item_put() on initialized constructor failures to release the name and respect references retained by o2hb_region_pin(). Initialize the list heads, mutex and delayed works needed by o2hb_region_release() before any such put, and leave the list node self-linked after rollback. Keep the existing handler drain before removing a failed published item. KASAN report as below: ================================================================== BUG: KASAN: null-ptr-deref in o2hb_get_all_regions+0xbb/0x1f0 Read of size 32 at addr 0000000000000000 by task kworker/u16:1/14 CPU: 2 UID: 0 PID: 14 Comm: kworker/u16:1 Not tainted 7.3.0-rc4-next-20260921-pmb-ocfs2-functional-v1+ #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: o2net o2net_rx_until_empty Call Trace: dump_stack_lvl+0x93/0xd0 kasan_report+0xe0/0x110 ? o2hb_get_all_regions+0xbb/0x1f0 kasan_check_range+0x105/0x1b0 __asan_memcpy+0x23/0x60 o2hb_get_all_regions+0xbb/0x1f0 dlm_query_region_handler+0x4c2/0xc20 ? __pfx_dlm_query_region_handler+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? srso_alias_return_thunk+0x5/0xfbef5 ? trace_hardirqs_on+0x18/0x160 ? srso_alias_return_thunk+0x5/0xfbef5 ? kvm_clock_get_cycles+0x31/0x60 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_dlm_query_region_handler+0x10/0x10 o2net_rx_until_empty+0x1a50/0x3200 ? reacquire_held_locks+0xdd/0x200 ? __pfx_o2net_rx_until_empty+0x10/0x10 ? lock_acquire+0x190/0x300 ? process_one_work+0x935/0x1b40 ? process_one_work+0x834/0x1b40 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x290 ? srso_alias_return_thunk+0x5/0xfbef5 process_one_work+0x9a8/0x1b40 ? __pfx_process_one_work+0x10/0x10 ? lock_acquire+0x190/0x300 ? lock_is_held_type+0x8f/0x100 ? srso_alias_return_thunk+0x5/0xfbef5 worker_thread+0x65c/0xe40 ? srso_alias_return_thunk+0x5/0xfbef5 ? __kthread_parkme+0x177/0x220 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_worker_thread+0x10/0x10 kthread+0x351/0x460 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x659/0x940 ? __pfx_ret_from_fork+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __switch_to+0x74f/0xf70 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 ================================================================== Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0079-v5-f931a29339cadc0a2a70@gmail.com Fixes: 536f0741f324 ("ocfs2/cluster: Track number of global heartbeat regions") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Yufan Chen Cc: Christophe JAILLET Cc: Kees Cook Cc: Karl Mehltretter Cc: Sunil Mushran Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/cluster/heartbeat.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index 8e6ead433228..9a4703bdc4e8 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -2140,6 +2140,18 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g } reg->hr_node_num = O2NM_MAX_NODES; + INIT_LIST_HEAD(®->hr_all_item); + mutex_init(®->hr_arming_mutex); + reg->hr_stopping = true; + INIT_DELAYED_WORK(®->hr_write_timeout_work, o2hb_write_timeout); + INIT_DELAYED_WORK(®->hr_nego_timeout_work, o2hb_nego_timeout); + INIT_LIST_HEAD(®->hr_handler_list); + config_item_init_type_name(®->hr_item, name, &o2hb_region_type); + if (!config_item_name(®->hr_item)) { + ret = -ENOMEM; + goto put_item; + } + spin_lock(&o2hb_live_lock); reg->hr_region_num = 0; if (o2hb_global_heartbeat_active()) { @@ -2148,26 +2160,19 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g if (reg->hr_region_num >= O2NM_MAX_REGIONS) { spin_unlock(&o2hb_live_lock); ret = -EFBIG; - goto free; + goto put_item; } set_bit(reg->hr_region_num, o2hb_region_bitmap); } list_add_tail(®->hr_all_item, &o2hb_all_regions); spin_unlock(&o2hb_live_lock); - config_item_init_type_name(®->hr_item, name, &o2hb_region_type); - /* this is the same way to generate msg key as dlm, for local heartbeat, * name is also the same, so make initial crc value different to avoid * message key conflict. */ reg->hr_key = crc32_le(reg->hr_region_num + O2NM_MAX_REGIONS, name, strlen(name)); - mutex_init(®->hr_arming_mutex); - reg->hr_stopping = true; - INIT_DELAYED_WORK(®->hr_write_timeout_work, o2hb_write_timeout); - INIT_DELAYED_WORK(®->hr_nego_timeout_work, o2hb_nego_timeout); - INIT_LIST_HEAD(®->hr_handler_list); ret = o2net_register_handler(O2HB_NEGO_TIMEOUT_MSG, reg->hr_key, sizeof(struct o2hb_nego_msg), o2hb_nego_timeout_handler, @@ -2190,10 +2195,13 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g o2net_unregister_and_flush_handler_list(®->hr_handler_list); remove_item: spin_lock(&o2hb_live_lock); - list_del(®->hr_all_item); + list_del_init(®->hr_all_item); if (o2hb_global_heartbeat_active()) clear_bit(reg->hr_region_num, o2hb_region_bitmap); spin_unlock(&o2hb_live_lock); +put_item: + config_item_put(®->hr_item); + return ERR_PTR(ret); free: kfree(reg); return ERR_PTR(ret); From 26d7e34b4226709084461a07bf28d9066fa96d8c Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Fri, 16 Oct 2026 09:17:13 +0800 Subject: [PATCH 120/134] ocfs2: pin owning modules for open debugfs files The OCFS2 debugfs file operation tables do not set owner. The full debugfs proxy obtains real_fops with fops_get() at open and keeps it until final release. Without an owner, this does not acquire a reference to the module containing the table and its callbacks. Removing the debugfs entries does not drain retained open files. An open connected_nodes file can therefore outlive ocfs2_nodemanager and make full_proxy_release() read real_fops->release from freed module memory. Heming also reproduced the issue with fs_state and asked for the other OCFS2 debugfs operation tables to be checked. Set owner to THIS_MODULE in all eleven explicit debugfs operation tables: five in ocfs2_nodemanager, four in ocfs2_dlm, and two in ocfs2. The existing debugfs get/put pair then holds the appropriate module until final release. Leave the open, read and release callbacks unchanged. The blockcheck table already sets owner through DEFINE_DEBUGFS_ATTRIBUTE(), so it needs no change. The original controlled test used an unloadable OCFS2 module and case-local probe filters without changing the candidate open, unload or release logic. Closing a retained connected_nodes file after normal unload produced: BUG: unable to handle page fault for address: fffffbfff804cfef Oops: Oops: 0000 [#1] SMP KASAN NOPTI RIP: 0010:full_proxy_release+0xbf/0x190 Call Trace: __fput+0x39a/0xa60 fput_close_sync+0xff/0x200 __x64_sys_close+0x8c/0xf0 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f Modules linked in: [last unloaded: ocfs2_nodemanager] Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0089-v6-f7fda843f80887488145@gmail.com Fixes: 2309e9e040fe ("ocfs2/net: Add debug interface to o2net") Fixes: 87d3d3f3931f ("ocfs2/hb: Expose the list of heartbeating nodes via debugfs") Fixes: 3ba169ccec1c ("ocfs2/cluster: Add new function o2net_fill_node_map()") Fixes: 50397507e856 ("ocfs2: Expose the file system state via debugfs") Fixes: ccd979bdbce9 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Fixes: 7209300a9b98 ("ocfs2/dlm: Dumps the purgelist into a debugfs file") Fixes: d0129aceaecc ("ocfs2/dlm: Dumps the mles into a debugfs file") Fixes: 4e3d24ed1a12 ("ocfs2/dlm: Dumps the lockres' into a debugfs file") Fixes: 007dce53a29c ("ocfs2/dlm: Dump the dlm state in a debugfs file") Fixes: db02754c8a12 ("ocfs2/cluster: Show o2net timing statistics") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Suggested-by: Heming Zhao Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Kees Cook Cc: Karl Mehltretter Cc: Christophe JAILLET Cc: Yufan Chen Cc: Mike Rapoport Cc: Zhang Cen Cc: Christian Brauner Cc: Sunil Mushran Cc: Kurt Hackel Cc: Jan Kara Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/cluster/heartbeat.c | 1 + fs/ocfs2/cluster/netdebug.c | 4 ++++ fs/ocfs2/dlm/dlmdebug.c | 5 +++++ fs/ocfs2/dlmglue.c | 2 ++ fs/ocfs2/super.c | 1 + 5 files changed, 13 insertions(+) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index 9a4703bdc4e8..b45adc39aacc 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -1447,6 +1447,7 @@ static ssize_t o2hb_debug_read(struct file *file, char __user *buf, #endif /* CONFIG_DEBUG_FS */ static const struct file_operations o2hb_debug_fops = { + .owner = THIS_MODULE, .open = o2hb_debug_open, .release = o2hb_debug_release, .read = o2hb_debug_read, diff --git a/fs/ocfs2/cluster/netdebug.c b/fs/ocfs2/cluster/netdebug.c index f7ab66e70799..5d373002297d 100644 --- a/fs/ocfs2/cluster/netdebug.c +++ b/fs/ocfs2/cluster/netdebug.c @@ -183,6 +183,7 @@ static int nst_fop_release(struct inode *inode, struct file *file) } static const struct file_operations nst_seq_fops = { + .owner = THIS_MODULE, .open = nst_fop_open, .read = seq_read, .llseek = seq_lseek, @@ -415,6 +416,7 @@ static int stats_fop_open(struct inode *inode, struct file *file) } static const struct file_operations stats_seq_fops = { + .owner = THIS_MODULE, .open = stats_fop_open, .read = seq_read, .llseek = seq_lseek, @@ -427,6 +429,7 @@ static int sc_fop_open(struct inode *inode, struct file *file) } static const struct file_operations sc_seq_fops = { + .owner = THIS_MODULE, .open = sc_fop_open, .read = seq_read, .llseek = seq_lseek, @@ -476,6 +479,7 @@ static ssize_t o2net_debug_read(struct file *file, char __user *buf, } static const struct file_operations nodes_fops = { + .owner = THIS_MODULE, .open = nodes_fop_open, .release = o2net_debug_release, .read = o2net_debug_read, diff --git a/fs/ocfs2/dlm/dlmdebug.c b/fs/ocfs2/dlm/dlmdebug.c index ce6d301cb942..8919eb14df22 100644 --- a/fs/ocfs2/dlm/dlmdebug.c +++ b/fs/ocfs2/dlm/dlmdebug.c @@ -7,6 +7,7 @@ * Copyright (C) 2004, 2008 Oracle. All rights reserved. */ +#include #include #include #include @@ -339,6 +340,7 @@ static int debug_purgelist_open(struct inode *inode, struct file *file) } static const struct file_operations debug_purgelist_fops = { + .owner = THIS_MODULE, .open = debug_purgelist_open, .release = debug_release, .read = debug_read, @@ -394,6 +396,7 @@ static int debug_mle_open(struct inode *inode, struct file *file) } static const struct file_operations debug_mle_fops = { + .owner = THIS_MODULE, .open = debug_mle_open, .release = debug_release, .read = debug_read, @@ -601,6 +604,7 @@ static int debug_lockres_release(struct inode *inode, struct file *file) } static const struct file_operations debug_lockres_fops = { + .owner = THIS_MODULE, .open = debug_lockres_open, .release = debug_lockres_release, .read = seq_read, @@ -791,6 +795,7 @@ static int debug_state_open(struct inode *inode, struct file *file) } static const struct file_operations debug_state_fops = { + .owner = THIS_MODULE, .open = debug_state_open, .release = debug_release, .read = debug_read, diff --git a/fs/ocfs2/dlmglue.c b/fs/ocfs2/dlmglue.c index 6bc4a21cf5f0..88d946466083 100644 --- a/fs/ocfs2/dlmglue.c +++ b/fs/ocfs2/dlmglue.c @@ -7,6 +7,7 @@ * Copyright (C) 2003, 2004 Oracle. All rights reserved. */ +#include #include #include #include @@ -3332,6 +3333,7 @@ static int ocfs2_dlm_debug_open(struct inode *inode, struct file *file) } static const struct file_operations ocfs2_dlm_debug_fops = { + .owner = THIS_MODULE, .open = ocfs2_dlm_debug_open, .release = ocfs2_dlm_debug_release, .read = seq_read, diff --git a/fs/ocfs2/super.c b/fs/ocfs2/super.c index 1e76b1d9fe0a..0af2245ae303 100644 --- a/fs/ocfs2/super.c +++ b/fs/ocfs2/super.c @@ -387,6 +387,7 @@ static ssize_t ocfs2_debug_read(struct file *file, char __user *buf, #endif /* CONFIG_DEBUG_FS */ static const struct file_operations ocfs2_osb_debug_fops = { + .owner = THIS_MODULE, .open = ocfs2_osb_debug_open, .release = ocfs2_debug_release, .read = ocfs2_debug_read, From 6b30c39a5e333cfb1475acf0f73de6ef954eea8a Mon Sep 17 00:00:00 2001 From: Hungyu Lin Date: Tue, 20 Oct 2026 21:01:57 +0000 Subject: [PATCH 121/134] compat: avoid double rounding in times() do_sys_times() already converts CPU time to USER_HZ ticks. The compat path converts each value back to jiffies before converting it to COMPAT_USER_HZ. This round trip can lose precision even when the two userspace rates match. With HZ=250 and USER_HZ=COMPAT_USER_HZ=100, one user tick becomes two jiffies and then zero compat ticks. Every odd tick count loses one tick, so any of the four tms fields can underreport CPU time by an additional 10 ms. Return the value directly when the native and compat units match. Keep the existing conversion for differing rates. Link: https://lore.kernel.org/20261020210157.52057-1-dennylin0707@gmail.com Fixes: f06febc96ba8 ("timers: fix itimer/many thread hang") Signed-off-by: Hungyu Lin Signed-off-by: Andrew Morton Reviewed-by: Andrew Morton Cc: Arnd Bergmann Cc: Thomas Gleixner Cc: --- kernel/sys.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/sys.c b/kernel/sys.c index 35b538ba843c..755bfa5ccd92 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -1077,6 +1077,9 @@ SYSCALL_DEFINE1(times, struct tms __user *, tbuf) #ifdef CONFIG_COMPAT static compat_clock_t clock_t_to_compat_clock_t(clock_t x) { + if (USER_HZ == COMPAT_USER_HZ) + return x; + return compat_jiffies_to_clock_t(clock_t_to_jiffies(x)); } From 66644b992f25f6a33775788c8b69a8bb3d41ad67 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Tue, 20 Oct 2026 09:05:59 +0800 Subject: [PATCH 122/134] kernel/reboot: avoid potential deadlocks during suspension Invoking reboot with LINUX_REBOOT_CMD_SW_SUSPEND causes kernel/reboot.c to acquire system_transition_mutex and invoke hibernate(). hibernate() calls lock_system_sleep(), which sets PF_NOFREEZE on current and attempts to acquire system_transition_mutex again, potentially hitting: WARNING: possible recursive locking detected -------------------------------------------- syz.3.5755/5950 is trying to acquire lock: ffffffff8fc04760 (system_transition_mutex){+.+.}-{4:4}, at: lock_system_sleep+0x49/0x70 kernel/power/main.c:71 but task is already holding lock: ffffffff8fc04760 (system_transition_mutex){+.+.}-{4:4}, at: __do_sys_reboot kernel/reboot.c:774 [inline] ffffffff8fc04760 (system_transition_mutex){+.+.}-{4:4}, at: __se_sys_reboot+0x223/0x460 kernel/reboot.c:738 other info that might help us debug this: Possible unsafe locking scenario: CPU0 ---- lock(system_transition_mutex); lock(system_transition_mutex); *** DEADLOCK *** May be due to missing lock nesting notation locks held by syz.3.5755/5950: 1, last CPU#0: #0: ffffffff8fc04760 (system_transition_mutex){+.+.}-{4:4}, at: __do_sys_reboot kernel/reboot.c:774 [inline] #0: ffffffff8fc04760 (system_transition_mutex){+.+.}-{4:4}, at: __se_sys_reboot+0x223/0x460 kernel/reboot.c:738 Call Trace: lock_system_sleep+0x49/0x70 kernel/power/main.c:71 hibernate+0x162/0x680 kernel/power/hibernate.c:784 __do_sys_reboot kernel/reboot.c:816 [inline] Move the suspension operation outside the scope protected by lock system_transition_mutex. Link: https://lore.kernel.org/20261020010559.23031-1-eadavis@sina.com Fixes: 55f2503c3b69 ("PM / reboot: Eliminate race between reboot and suspend") Signed-off-by: Edward Adam Davis Signed-off-by: Andrew Morton Reported-by: Closes: https://syzkaller.appspot.com/bug?extid=1f0fbc2561b3e5d51b91 Tested-by: Reviewed-by: Bradley Morgan Cc: Pingfan Liu Cc: "Rafael J. Wysocki" --- kernel/reboot.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/reboot.c b/kernel/reboot.c index d177d89fcc33..77c1521cf32b 100644 --- a/kernel/reboot.c +++ b/kernel/reboot.c @@ -804,8 +804,8 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, #ifdef CONFIG_HIBERNATION case LINUX_REBOOT_CMD_SW_SUSPEND: - ret = hibernate(); - break; + mutex_unlock(&system_transition_mutex); + return hibernate(); #endif default: From b1d68946a08313895f352af5336623d29739c299 Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Tue, 20 Oct 2026 13:12:24 +0300 Subject: [PATCH 123/134] profiling: fix sampling step on 64-bit big-endian systems read_profile() stores the sampling step in an unsigned long but copies only four bytes to userspace. On 64-bit big-endian systems (not common obviously), this copies the upper 32 bits and discards the rest, causing /proc/profile to report zero for normal shift values. Cast to unsigned int so we get the correct 4 bytes, while keeping the shift calculation in unsigned long. A small C reproducer under qemu-s390x reproduced this and proved the fix works for prof_shift=2. Link: https://lore.kernel.org/20261020101225.917959-1-thnkslprpt@gmail.com Fixes: 2d186afd04d6 ("profiling: fix shift-out-of-bounds bugs") Signed-off-by: Avi Weiss Signed-off-by: Andrew Morton Reviewed-by: Bradley Morgan --- kernel/profile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/profile.c b/kernel/profile.c index 1fcf1adcf4eb..e7defa90a34c 100644 --- a/kernel/profile.c +++ b/kernel/profile.c @@ -156,7 +156,7 @@ read_profile(struct file *file, char __user *buf, size_t count, loff_t *ppos) unsigned long p = *ppos; ssize_t read; char *pnt; - unsigned long sample_step = 1UL << prof_shift; + unsigned int sample_step = (unsigned int)(1UL << prof_shift); if (p >= (prof_len+1)*sizeof(unsigned int)) return 0; From 6fd3ecb724868f826b0b919b70f9037275ad4da4 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Wed, 7 Oct 2026 05:17:26 +0000 Subject: [PATCH 124/134] watchdog: force console verbose when reporting lockups When a hard or soft lockup is detected, the watchdog prints a header at KERN_EMERG (<0>), followed by diagnostic details (modules, registers, and backtraces) at KERN_WARNING/KERN_DEFAULT (<4>) and KERN_INFO (<6>). If console_loglevel is quiet (<= 4), the console skips those <4>/<6> diagnostic messages before vpanic() has a chance to call console_verbose(). With the buddy detector, this leaves only the detector CPU's panic stack on the console and nothing about the locked CPU: <0>[ 1535.991833][ C6] watchdog: Watchdog detected hard LOCKUP on cpu 7 <0>[ 1535.995960][ C6] Kernel panic - not syncing: Hard LOCKUP <4>[ 1535.995970][ C6] CPU: 6 UID: 0 PID: 0 Comm: swapper/6 ... <4>[ 1535.995997][ C6] Call trace: ... <4>[ 1535.996063][ C6] watchdog_hardlockup_check+0x1e0/0x288 <4>[ 1535.996075][ C6] watchdog_buddy_check_hardlockup+0xb4/0xc0 Similar to commit 168e06f7937d ("kernel/hung_task.c: force console verbose before panic"), call console_verbose() before printing the lockup report so the diagnostic output is not suppressed. Link: https://lore.kernel.org/20261007051726.4154849-1-tzungbi@kernel.org Signed-off-by: Tzung-Bi Shih Signed-off-by: Andrew Morton Reviewed-by: Sergey Senozhatsky Reviewed-by: Bradley Morgan Tested-by: Bradley Morgan # Power10 / ARM64 Reviewed-by: Douglas Anderson Cc: Petr Mladek Cc: Tomasz Figa --- kernel/watchdog.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/kernel/watchdog.c b/kernel/watchdog.c index e5134ad7b663..64da2e500144 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -255,6 +255,9 @@ void watchdog_hardlockup_check(unsigned int cpu, struct pt_regs *regs) return; } + if (hardlockup_panic) + console_verbose(); + /* * NOTE: we call printk_cpu_sync_get_irqsave() after printing * the lockup message. While it would be nice to serialize @@ -880,6 +883,10 @@ static enum hrtimer_restart watchdog_timer_fn(struct hrtimer *hrtimer) /* Start period for the next softlockup warning. */ update_report_ts(); + thresh_count = duration / get_softlockup_thresh(); + if (softlockup_panic && thresh_count >= softlockup_panic) + console_verbose(); + printk_cpu_sync_get_irqsave(flags); pr_emerg("BUG: soft lockup - CPU#%d stuck for %us! [%s:%d]\n", smp_processor_id(), duration, @@ -901,7 +908,6 @@ static enum hrtimer_restart watchdog_timer_fn(struct hrtimer *hrtimer) add_taint(TAINT_SOFTLOCKUP, LOCKDEP_STILL_OK); sys_info(softlockup_si_mask & ~SYS_INFO_ALL_BT); - thresh_count = duration / get_softlockup_thresh(); if (softlockup_panic && thresh_count >= softlockup_panic) panic("softlockup: hung tasks"); From b584c45b6066fd9799c44ffb9badcb5db351c8a6 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 20 Oct 2026 11:45:04 +0800 Subject: [PATCH 125/134] ocfs2: drain quorum work before removing the cluster The configured cluster must remain available until an in-flight quorum decision has finished using its fencing method. o2quo_make_decision() drops qs_lock before calling o2quo_fence_self(), which marks heartbeat regions for unclean stop and then reads o2nm_single_cluster->cl_fence_method. Cluster removal clears the singleton without waiting for this independent system-workqueue item. When a failed-quorum decision overlaps removal of the heartbeat regions, nodes and cluster through configfs, the worker can be preempted after marking the regions and resume after the singleton has been cleared: Quorum worker Configfs removal select fence; drop qs_lock o2hb_stop_all_regions() remove regions; join heartbeat threads remove nodes; stop o2net clear singleton; release cluster read cl_fence_method The resumed read dereferences NULL instead of applying the configured fencing method. The heartbeat thread's dependency protects the local node only until the thread exits, and draining o2net does not wait for qs_work. Add o2quo_flush_work() to drain pending or running quorum decisions before clearing the singleton in o2nm_cluster_group_drop_item(). By this point the heartbeat regions and nodes have been removed, so their work publishers are stopped. The worker can finish while the cluster and its configured fencing method remain available. It does not acquire the configfs mutex held by the removal path. Keep o2quo_exit() as the counterpart of o2quo_init() for module teardown and initialization failure, and have it call the same drain helper. Cluster removal uses only the explicitly named drain operation. KASAN report as below: Oops: general protection fault, probably for non-canonical address 0xdffffc000000011b: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x00000000000008d8-0x00000000000008df] CPU: 0 UID: 0 PID: 72 Comm: kworker/0:2 Not tainted 7.3.0-rc4-next-20260921-pmb-ocfs2-functional-v1+ #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: events o2quo_make_decision RIP: 0010:o2quo_fence_self+0x65/0x200 Register dump omitted. Call Trace: o2quo_make_decision+0x345/0x3a0 ? __pfx_o2quo_make_decision+0x10/0x10 ? process_one_work+0x834/0x1b40 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x290 ? srso_alias_return_thunk+0x5/0xfbef5 process_one_work+0x9a8/0x1b40 ? __pfx_process_one_work+0x10/0x10 ? lock_acquire+0x190/0x300 ? lock_is_held_type+0x8f/0x100 ? srso_alias_return_thunk+0x5/0xfbef5 worker_thread+0x65c/0xe40 ? srso_alias_return_thunk+0x5/0xfbef5 ? __kthread_parkme+0x177/0x220 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_worker_thread+0x10/0x10 kthread+0x351/0x460 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x659/0x940 ? __pfx_ret_from_fork+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __switch_to+0x74f/0xf70 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Modules linked in: ---[ end trace 0000000000000000 ]--- Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0064-v5-e3870bf6571b80b07b52@gmail.com Fixes: f6656d26d17b ("ocfs2/cluster: Make fence method configurable - v2") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/cluster/nodemanager.c | 3 +++ fs/ocfs2/cluster/quorum.c | 7 ++++++- fs/ocfs2/cluster/quorum.h | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/cluster/nodemanager.c b/fs/ocfs2/cluster/nodemanager.c index e08850a5d736..ced4fe6d3b59 100644 --- a/fs/ocfs2/cluster/nodemanager.c +++ b/fs/ocfs2/cluster/nodemanager.c @@ -12,6 +12,7 @@ #include "tcp.h" #include "nodemanager.h" #include "heartbeat.h" +#include "quorum.h" #include "masklog.h" #include "sys.h" @@ -740,6 +741,8 @@ static void o2nm_cluster_group_drop_item(struct config_group *group, struct conf struct o2nm_cluster *cluster = to_o2nm_cluster(item); BUG_ON(o2nm_single_cluster != cluster); + /* Drain quorum work before it loses access to the cluster. */ + o2quo_flush_work(); o2nm_single_cluster = NULL; configfs_remove_default_groups(&cluster->cl_group); diff --git a/fs/ocfs2/cluster/quorum.c b/fs/ocfs2/cluster/quorum.c index bfb8b456876c..ab5b3d4fd0e8 100644 --- a/fs/ocfs2/cluster/quorum.c +++ b/fs/ocfs2/cluster/quorum.c @@ -318,9 +318,14 @@ void o2quo_init(void) INIT_WORK(&qs->qs_work, o2quo_make_decision); } -void o2quo_exit(void) +void o2quo_flush_work(void) { struct o2quo_state *qs = &o2quo_state; flush_work(&qs->qs_work); } + +void o2quo_exit(void) +{ + o2quo_flush_work(); +} diff --git a/fs/ocfs2/cluster/quorum.h b/fs/ocfs2/cluster/quorum.h index d64bf4482a4a..1ce5fc2a8ca9 100644 --- a/fs/ocfs2/cluster/quorum.h +++ b/fs/ocfs2/cluster/quorum.h @@ -8,6 +8,7 @@ void o2quo_init(void); void o2quo_exit(void); +void o2quo_flush_work(void); void o2quo_hb_up(u8 node); void o2quo_hb_down(u8 node); From 8f56ea8e0706d92774603260779c7032f03ef3ac Mon Sep 17 00:00:00 2001 From: Caleb Kan Date: Thu, 22 Oct 2026 09:33:30 +0100 Subject: [PATCH 126/134] stackdepot: stop preallocating after the final pool depot_init_pool() decides whether another pool is needed before it increments pools_num. When it registers the last allowed pool, pools_num is still one below stack_max_pools, so the existing comparison clears new_pool. With new_pool cleared, stack_depot_save_flags() can allocate another order-2 pool after a lookup miss. depot_keep_new_pool() retains its pointer in the global new_pool, so the allocation is not lost. However, pools_num has already reached stack_max_pools, and depot_init_pool() rejects every attempt to register the pool. It remains allocated and unusable until reboot. The non-NULL pointer prevents later saves from allocating more spare pools. Account for the pool being registered in the limit check so registering the final pool installs STACK_DEPOT_POISON and prevents the extra allocation. Link: https://lore.kernel.org/20261022083330.3159793-1-matt@readmodwrite.com Fixes: 31639fd6cebd ("stackdepot: use variable size records for non-evictable entries") Signed-off-by: Caleb Kan Signed-off-by: Matt Fleming Signed-off-by: Andrew Morton Reviewed-by: Marco Elver Cc: Andrey Konovalov Cc: Mikhail Gavrilov Cc: Caleb Kan Cc: Alexander Potapenko Cc: Dmitry Vyukov Cc: Vlastimil Babka Cc: --- lib/stackdepot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stackdepot.c b/lib/stackdepot.c index dd2717ff94bf..90c52f2e0d3f 100644 --- a/lib/stackdepot.c +++ b/lib/stackdepot.c @@ -323,7 +323,7 @@ static bool depot_init_pool(void **prealloc) * NULL; do not reset to NULL if we have reached the maximum number of * pools. */ - if (pools_num < stack_max_pools) + if (pools_num + 1 < stack_max_pools) WRITE_ONCE(new_pool, NULL); else WRITE_ONCE(new_pool, STACK_DEPOT_POISON); From e2e6b2457d11397d5759dcc99fde94e82db5e777 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 22 Oct 2026 17:45:32 +0200 Subject: [PATCH 127/134] mailmap: add entries for Andrew Jones Use my linux.dev address for kernel correspondence, including for contributions made with my other email addresses. Link: https://lore.kernel.org/20261022154532.1374661-1-andrew.jones@linux.dev Signed-off-by: Andrew Jones Signed-off-by: Andrew Morton --- .mailmap | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.mailmap b/.mailmap index 8e3ed5275156..ebb7d13b996f 100644 --- a/.mailmap +++ b/.mailmap @@ -88,6 +88,10 @@ Andrej Shadura Andrew Donnellan Andrew Donnellan Andrew Donnellan +Andrew Jones +Andrew Jones +Andrew Jones +Andrew Jones Andrew Morton Andrew Murray Andrew Murray From bd15b1a295fe65aee66c8feb06ccfe33dd2f5617 Mon Sep 17 00:00:00 2001 From: Markus Mayer Date: Thu, 22 Oct 2026 14:19:48 -0700 Subject: [PATCH 128/134] MAINTAINERS: update Markus's e-mail address Classic Broadcom e-mail addresses were recently phased out. Update MAINTAINERS to the new format. Also add an entry to .mailmap for said address. Finally, since I am already touching .mailmap, I may as well map an ancient Linaro address, as well. Link: https://lore.kernel.org/20261022211949.42939-1-markus.mayer@broadcom.com Signed-off-by: Markus Mayer Signed-off-by: Andrew Morton Cc: Jakub Kicinski Cc: Alexander Sverdlin Cc: Chuck Lever Cc: Martin Kepplinger --- .mailmap | 2 ++ MAINTAINERS | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.mailmap b/.mailmap index ebb7d13b996f..876ca846cfe3 100644 --- a/.mailmap +++ b/.mailmap @@ -597,6 +597,8 @@ Marek Lindner Marek Lindner Mark Brown Mark Starovoytov +Markus Mayer +Markus Mayer Markus Schneider-Pargmann Mark Yao Martin K. Petersen diff --git a/MAINTAINERS b/MAINTAINERS index bd6619bc107d..f81d581bdbf8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5296,7 +5296,7 @@ F: Documentation/devicetree/bindings/usb/brcm,bdc.yaml F: drivers/usb/gadget/udc/bdc/ BROADCOM BMIPS CPUFREQ DRIVER -M: Markus Mayer +M: Markus Mayer R: Broadcom internal kernel review list L: linux-pm@vger.kernel.org S: Maintained @@ -5571,7 +5571,7 @@ F: drivers/spi/spi-brcmstb-qspi.c F: drivers/spi/spi-iproc-qspi.c BROADCOM STB AVS CPUFREQ DRIVER -M: Markus Mayer +M: Markus Mayer R: Broadcom internal kernel review list L: linux-pm@vger.kernel.org S: Maintained @@ -5579,7 +5579,7 @@ F: Documentation/devicetree/bindings/cpufreq/brcm,stb-avs-cpu-freq.txt F: drivers/cpufreq/brcmstb* BROADCOM STB AVS TMON DRIVER -M: Markus Mayer +M: Markus Mayer R: Broadcom internal kernel review list L: linux-pm@vger.kernel.org S: Maintained @@ -5587,7 +5587,7 @@ F: Documentation/devicetree/bindings/thermal/brcm,avs-tmon.yaml F: drivers/thermal/broadcom/brcmstb* BROADCOM STB DPFE DRIVER -M: Markus Mayer +M: Markus Mayer R: Broadcom internal kernel review list L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained From 28f366d84023651e50e32e29931401ba0df3e679 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Thu, 22 Oct 2026 17:09:10 +0800 Subject: [PATCH 129/134] ocfs2/dlm: drain join handlers before releasing module text The code for a registered DLM handler must remain resident until its last invocation finishes. dlm_unregister_net_handlers() only removes dlm_join_handlers from the o2net registry. o2net_handler_get() takes a kref on the wrapper, but that reference does not pin ocfs2_dlm text. o2net_process_message() calls nh_func after dropping o2net_handler_lock. With CONFIG_OCFS2_FS_O2CB=m, ocfs2_dlm can be loaded independently of ocfs2_stack_o2cb while nodemanager continues handling messages from a configured, heartbeating peer. If no dependent module or module reference prevents normal unload, the following ordering is possible: Receive worker Module unload -------------- ------------- o2net_handler_get() take wrapper kref drop handler lock dlm_exit() unregister handlers release DLM module text call saved nh_func in released module text Use o2net_unregister_and_flush_handler_list() in the shared DLM unregister helper. It first prevents new lookups, then waits for the ordered o2net workqueue to drain, including a concurrent destruction of that queue. The acquired invocation therefore finishes before dlm_exit() returns and the loader releases the callback code. The same helper covers partial registration and dlm_init() error unwind before module memory is released. Skip the drain when the private join-handler list is empty. Early dlm_init() failures can reach this helper before any handler has been published. Partial-registration failure also reaches it a second time after the first call has already unregistered and drained the list. Neither case needs to take o2net_wq_mutex or drain unrelated work. The list is changed only by serialized module init/exit paths; a nonempty list still uses the full unregister-and-flush helper. Oops report as below: Oops: int3: 0000 [#1] SMP KASAN NOPTI CPU: 2 UID: 0 PID: 54 Comm: kworker/u16:3 Not tainted 7.3.0-rc4-next-20260921-pmb-ocfs2-functional-v1+ #1 PREEMPT(lazy) Hardware identification omitted. Workqueue: o2net o2net_rx_until_empty [ocfs2_nodemanager] RIP: 0010:0xffffffffc0423871 [Instruction byte dump omitted.] Register dump omitted. Call Trace: ? o2net_rx_until_empty+0x19b1/0x3300 [ocfs2_nodemanager] ? __pfx_o2net_rx_until_empty+0x10/0x10 [ocfs2_nodemanager] ? lock_acquire+0x190/0x300 ? process_one_work+0x935/0x1b40 ? process_one_work+0x834/0x1b40 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x290 ? srso_alias_return_thunk+0x5/0xfbef5 ? process_one_work+0x9a8/0x1b40 ? __pfx_process_one_work+0x10/0x10 ? lock_acquire+0x190/0x300 ? lock_is_held_type+0x8f/0x100 ? srso_alias_return_thunk+0x5/0xfbef5 ? worker_thread+0x65c/0xe40 ? srso_alias_return_thunk+0x5/0xfbef5 ? __kthread_parkme+0x177/0x220 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_worker_thread+0x10/0x10 ? kthread+0x351/0x460 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_kthread+0x10/0x10 ? ret_from_fork+0x659/0x940 ? __pfx_ret_from_fork+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __switch_to+0x74f/0xf70 ? __pfx_kthread+0x10/0x10 ? ret_from_fork_asm+0x1a/0x30 Modules linked in: ocfs2_nodemanager [last unloaded: ocfs2_dlm] ---[ end trace 0000000000000000 ]--- Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0076-v6-12de5f4628f57fba3be3@gmail.com Fixes: 6714d8e86bf4 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/dlm/dlmdomain.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/dlm/dlmdomain.c b/fs/ocfs2/dlm/dlmdomain.c index 97bb9400e24b..a7dc3da6dfc7 100644 --- a/fs/ocfs2/dlm/dlmdomain.c +++ b/fs/ocfs2/dlm/dlmdomain.c @@ -2215,7 +2215,8 @@ static LIST_HEAD(dlm_join_handlers); static void dlm_unregister_net_handlers(void) { - o2net_unregister_handler_list(&dlm_join_handlers); + if (!list_empty(&dlm_join_handlers)) + o2net_unregister_and_flush_handler_list(&dlm_join_handlers); } static int dlm_register_net_handlers(void) From 8cd3ff30659775a6831c66687a69d2b83d163591 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Thu, 22 Oct 2026 17:08:41 +0800 Subject: [PATCH 130/134] ocfs2: snapshot dentry names in file and setattr traces Patch series "ocfs2: stabilize dentry names in file and setattr traces", v4. Snapshot names at all six users of ocfs2__file_ops and at the separate setattr event. Patch 1 handles the five ordinary file-operation callers and setattr. Patch 2 handles release separately to preserve its locked open-count sample and take the snapshot outside that critical section. The release explanation distinguishes its existing RCU lifetime coverage from name/length consistency. It does not claim that the ordinary callers' external-name UAF interleaving crosses the held inode spinlock. The release source change is unchanged from v3. Patch 1 also removes the read callback's redundant NULL-inode check. The two patches apply independently and leave trace formats unchanged. This patch (of 2): The open, fsync, read, write and splice-read trace calls pass the live dentry name and length to the shared ocfs2__file_ops event class. ocfs2_setattr() passes the same unprotected arguments to its own event. Both events size and copy a __string field separately. A reference to the dentry does not pin the external storage backing an individual name. With an event enabled, a caller can be preempted after loading the name pointer. A long-to-short rename can then replace the name and reclaim its old external allocation before the trace callback sizes or copies it, provided no other snapshot retains that allocation. The setattr inode lock does not exclude every rename: a same-directory rename of a source directory need not take that directory's inode lock. It can therefore change the dentry name while setattr records its event. Inline names and separately loaded lengths can also change during trace record sizing and copying. Take a dentry name snapshot around each of these six trace calls and pass both the string and length from the snapshot. Check event enablement before taking a snapshot. Keep the event classes and formats unchanged. Also remove the redundant NULL-inode check in the read callback. file_inode() returns the open file's inode, which is already used by the trace call above the check. The historical report below is from the read path. KASAN report as below: BUG: KASAN: slab-use-after-free in strlen+0x85/0xa0 Read of size 1 at addr ffff88810a5b2b18 by task python3/496 [CPU and host details omitted.] Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? strlen+0x85/0xa0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20e/0x420 ? strlen+0x85/0xa0 kasan_report+0xe0/0x110 ? strlen+0x85/0xa0 strlen+0x85/0xa0 trace_event_raw_event_ocfs2__file_ops+0xf0/0x480 ? __pfx_trace_event_raw_event_ocfs2__file_ops+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? pmbd_probe_hit_cookie+0xe7/0x1c0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_pmbd_probe_hit_cookie+0x10/0x10 ocfs2_file_read_iter+0x6cd/0xb30 ? __pfx_ocfs2_file_read_iter+0x10/0x10 ? selinux_file_permission+0x11d/0x540 ? srso_alias_return_thunk+0x5/0xfbef5 ? security_file_permission+0x26/0x80 vfs_read+0x7d2/0xc20 ? __pfx_vfs_read+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __pfx_do_sys_openat2+0x10/0x10 ksys_read+0x111/0x200 ? __pfx_ksys_read+0x10/0x10 ? irqentry_exit+0xbe/0x820 ? srso_alias_return_thunk+0x5/0xfbef5 ? trace_hardirqs_on_prepare+0x11d/0x150 ? srso_alias_return_thunk+0x5/0xfbef5 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f [User register dump omitted.] Allocated by task 489: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_noprof+0x2de/0x780 __d_alloc+0x5fc/0x8c0 d_alloc_parallel+0x106/0x11d0 lookup_open+0x3ff/0x13e0 path_openat+0x95e/0x2020 do_file_open+0x21e/0x470 do_sys_openat2+0xf4/0x1b0 __x64_sys_openat+0x136/0x1e0 do_syscall_64+0x114/0x620 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 0: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 __rcu_free_sheaf_prepare+0x65/0x2a0 rcu_free_sheaf_nobarn+0x1d/0x50 rcu_core+0x6be/0x1ed0 handle_softirqs+0x1fa/0x8d0 __irq_exit_rcu+0x113/0x1e0 irq_exit_rcu+0xe/0x20 sysvec_apic_timer_interrupt+0x6c/0x80 asm_sysvec_apic_timer_interrupt+0x1a/0x20 The buggy address belongs to the object at ffff88810a5b2b00 which belongs to the cache kmalloc-rcl-192 of size 192 The buggy address is located 24 bytes inside of freed 192-byte region [ffff88810a5b2b00, ffff88810a5b2bc0) [Page and shadow-memory dumps omitted.] Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0088-v7-6324344256f0c3b1c3d8-0@gmail.com Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0088-v7-6324344256f0c3b1c3d8-1@gmail.com Fixes: 468eedde23d6 ("ocfs2: Remove mlog(0) from fs/ocfs2/file.c") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/file.c | 100 +++++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 36 deletions(-) diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c index d6e977ba6565..c6e7606ff9e1 100644 --- a/fs/ocfs2/file.c +++ b/fs/ocfs2/file.c @@ -85,10 +85,17 @@ static int ocfs2_file_open(struct inode *inode, struct file *file) int mode = file->f_flags; struct ocfs2_inode_info *oi = OCFS2_I(inode); - trace_ocfs2_file_open(inode, file, file->f_path.dentry, - (unsigned long long)oi->ip_blkno, - file->f_path.dentry->d_name.len, - file->f_path.dentry->d_name.name, mode); + if (trace_ocfs2_file_open_enabled()) { + struct dentry *dentry = file->f_path.dentry; + struct name_snapshot name; + + take_dentry_name_snapshot(&name, dentry); + trace_ocfs2_file_open(inode, file, dentry, + oi->ip_blkno, + name.name.len, name.name.name, + mode); + release_dentry_name_snapshot(&name); + } if (file->f_mode & FMODE_WRITE) { status = dquot_initialize(inode); @@ -174,11 +181,17 @@ static int ocfs2_sync_file(struct file *file, loff_t start, loff_t end, tid_t commit_tid; bool needs_barrier = false; - trace_ocfs2_sync_file(inode, file, file->f_path.dentry, - oi->ip_blkno, - file->f_path.dentry->d_name.len, - file->f_path.dentry->d_name.name, - (unsigned long long)datasync); + if (trace_ocfs2_sync_file_enabled()) { + struct dentry *dentry = file->f_path.dentry; + struct name_snapshot name; + + take_dentry_name_snapshot(&name, dentry); + trace_ocfs2_sync_file(inode, file, dentry, + oi->ip_blkno, + name.name.len, name.name.name, + (unsigned long long)datasync); + release_dentry_name_snapshot(&name); + } if (unlikely(ocfs2_emergency_state(osb))) return -EROFS; @@ -1132,15 +1145,19 @@ int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry, int had_lock; struct ocfs2_lock_holder oh; - trace_ocfs2_setattr(inode, dentry, - (unsigned long long)OCFS2_I(inode)->ip_blkno, - dentry->d_name.len, dentry->d_name.name, - attr->ia_valid, - attr->ia_valid & ATTR_MODE ? attr->ia_mode : 0, - attr->ia_valid & ATTR_UID ? + if (trace_ocfs2_setattr_enabled()) { + struct name_snapshot name; + + take_dentry_name_snapshot(&name, dentry); + trace_ocfs2_setattr(inode, dentry, OCFS2_I(inode)->ip_blkno, + name.name.len, name.name.name, attr->ia_valid, + attr->ia_valid & ATTR_MODE ? attr->ia_mode : 0, + attr->ia_valid & ATTR_UID ? from_kuid(&init_user_ns, attr->ia_uid) : 0, - attr->ia_valid & ATTR_GID ? + attr->ia_valid & ATTR_GID ? from_kgid(&init_user_ns, attr->ia_gid) : 0); + release_dentry_name_snapshot(&name); + } status = ocfs2_emergency_state(osb); if (unlikely(status)) { @@ -2392,11 +2409,17 @@ static ssize_t ocfs2_file_write_iter(struct kiocb *iocb, int direct_io = iocb->ki_flags & IOCB_DIRECT ? 1 : 0; int nowait = iocb->ki_flags & IOCB_NOWAIT ? 1 : 0; - trace_ocfs2_file_write_iter(inode, file, file->f_path.dentry, - (unsigned long long)OCFS2_I(inode)->ip_blkno, - file->f_path.dentry->d_name.len, - file->f_path.dentry->d_name.name, - (unsigned int)from->nr_segs); /* GRRRRR */ + if (trace_ocfs2_file_write_iter_enabled()) { + struct dentry *dentry = file->f_path.dentry; + struct name_snapshot name; + + take_dentry_name_snapshot(&name, dentry); + trace_ocfs2_file_write_iter(inode, file, dentry, + OCFS2_I(inode)->ip_blkno, + name.name.len, name.name.name, + (unsigned int)from->nr_segs); + release_dentry_name_snapshot(&name); + } if (!direct_io && nowait) return -EOPNOTSUPP; @@ -2542,17 +2565,16 @@ static ssize_t ocfs2_file_read_iter(struct kiocb *iocb, int direct_io = iocb->ki_flags & IOCB_DIRECT ? 1 : 0; int nowait = iocb->ki_flags & IOCB_NOWAIT ? 1 : 0; - trace_ocfs2_file_read_iter(inode, filp, filp->f_path.dentry, - (unsigned long long)OCFS2_I(inode)->ip_blkno, - filp->f_path.dentry->d_name.len, - filp->f_path.dentry->d_name.name, - to->nr_segs); /* GRRRRR */ + if (trace_ocfs2_file_read_iter_enabled()) { + struct dentry *dentry = filp->f_path.dentry; + struct name_snapshot name; - - if (!inode) { - ret = -EINVAL; - mlog_errno(ret); - goto bail; + take_dentry_name_snapshot(&name, dentry); + trace_ocfs2_file_read_iter(inode, filp, dentry, + OCFS2_I(inode)->ip_blkno, + name.name.len, name.name.name, + to->nr_segs); + release_dentry_name_snapshot(&name); } if (!direct_io && nowait) @@ -2624,11 +2646,17 @@ static ssize_t ocfs2_file_splice_read(struct file *in, loff_t *ppos, ssize_t ret = 0; int lock_level = 0; - trace_ocfs2_file_splice_read(inode, in, in->f_path.dentry, - (unsigned long long)OCFS2_I(inode)->ip_blkno, - in->f_path.dentry->d_name.len, - in->f_path.dentry->d_name.name, - flags); + if (trace_ocfs2_file_splice_read_enabled()) { + struct dentry *dentry = in->f_path.dentry; + struct name_snapshot name; + + take_dentry_name_snapshot(&name, dentry); + trace_ocfs2_file_splice_read(inode, in, dentry, + OCFS2_I(inode)->ip_blkno, + name.name.len, name.name.name, + flags); + release_dentry_name_snapshot(&name); + } /* * We're fine letting folks race truncates and extending writes with From 5898987d4a92b70ae73fd8355cc3a388b4dfa4e6 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Thu, 22 Oct 2026 17:08:42 +0800 Subject: [PATCH 131/134] ocfs2: snapshot the dentry name while tracing file release ocfs2_file_release() passes the live dentry name and length to a trace event while holding oi->ip_lock. This lock protects the open count and delays RCU reclamation of external names, but it does not serialize name changes during rename. The dentry can switch between inline and external names, and a separately loaded length can describe a different name. The ocfs2__file_ops event sizes and copies its __string separately. A concurrently rewritten inline name need not remain consistent between those operations. External names can also be selected at this call site; the existing spinlock critical section protects their allocation lifetime, while a snapshot additionally pairs a stable name with its length. Take a name snapshot before acquiring oi->ip_lock and release it after dropping the lock. Keep the trace call and open-count argument inside the original critical section. Cache the event-enabled check so the snapshot stays paired even if tracing is toggled meanwhile. Link: https://lore.kernel.org/pm-ocfs2-objects-candidate-0088-v7-6324344256f0c3b1c3d8-2@gmail.com Fixes: 468eedde23d6 ("ocfs2: Remove mlog(0) from fs/ocfs2/file.c") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/file.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c index c6e7606ff9e1..b3a37b9d259a 100644 --- a/fs/ocfs2/file.c +++ b/fs/ocfs2/file.c @@ -141,18 +141,26 @@ static int ocfs2_file_open(struct inode *inode, struct file *file) static int ocfs2_file_release(struct inode *inode, struct file *file) { struct ocfs2_inode_info *oi = OCFS2_I(inode); + struct dentry *dentry = file->f_path.dentry; + struct name_snapshot name; + bool trace_enabled = trace_ocfs2_file_release_enabled(); + + if (trace_enabled) + take_dentry_name_snapshot(&name, dentry); spin_lock(&oi->ip_lock); if (!--oi->ip_open_count) oi->ip_flags &= ~OCFS2_INODE_OPEN_DIRECT; - trace_ocfs2_file_release(inode, file, file->f_path.dentry, - oi->ip_blkno, - file->f_path.dentry->d_name.len, - file->f_path.dentry->d_name.name, - oi->ip_open_count); + if (trace_enabled) + trace_ocfs2_file_release(inode, file, dentry, + oi->ip_blkno, name.name.len, + name.name.name, oi->ip_open_count); spin_unlock(&oi->ip_lock); + if (trace_enabled) + release_dentry_name_snapshot(&name); + ocfs2_free_file_private(inode, file); return 0; From 5dd57e77eff4ba3e85b3df126a55417c88245708 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 20 Oct 2026 21:12:50 +0800 Subject: [PATCH 132/134] ocfs2/cluster: remove zero-address nodes from IP tree on teardown o2nm_node_ipv4_address_store() accepts 0.0.0.0. It sets O2NM_NODE_ATTR_ADDRESS and links nd_ip_node into cl_node_ip_tree under cl_nodes_lock, but o2nm_node_group_drop_item() uses nd_ipv4_address as a membership test. A zero address therefore leaves the rbtree node linked when config_item_put() releases and frees struct o2nm_node. A later address store or IP lookup can then walk the stale node and dereference freed memory. During teardown, drop_item takes cl_nodes_lock, skips rb_erase() for the zero key, and then calls config_item_put(). A later o2nm_node_ipv4_address_store() duplicate check or o2nm_get_node_by_ip() lookup takes the same lock and walks the stale cl_node_ip_tree entry. Use O2NM_NODE_ATTR_ADDRESS for teardown so every published nd_ip_node is erased before configfs can release the node, without rejecting 0.0.0.0 or changing lookup semantics. Link: https://lore.kernel.org/pm-ocfs2-node-ip-rbtree-zero-address-uaf-v5-e7f64be16b7c3b0988d1@gmail.com Fixes: 0c83ed8eeb28 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Cen Zhang Signed-off-by: Andrew Morton Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Jia-Ju Bai Cc: Cc: --- fs/ocfs2/cluster/nodemanager.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/ocfs2/cluster/nodemanager.c b/fs/ocfs2/cluster/nodemanager.c index ced4fe6d3b59..91f8fcc6193f 100644 --- a/fs/ocfs2/cluster/nodemanager.c +++ b/fs/ocfs2/cluster/nodemanager.c @@ -627,8 +627,11 @@ static void o2nm_node_group_drop_item(struct config_group *group, write_lock(&cluster->cl_nodes_lock); - /* XXX sloppy */ - if (node->nd_ipv4_address) + /* + * 0.0.0.0 is a valid configured address, so nd_ipv4_address does + * not tell us whether nd_ip_node was published in the IP tree. + */ + if (test_bit(O2NM_NODE_ATTR_ADDRESS, &node->nd_set_attributes)) rb_erase(&node->nd_ip_node, &cluster->cl_node_ip_tree); /* nd_num might be 0 if the node number hasn't been set.. */ From 66d190b072b4e261aff35debb53872ace9b80628 Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Sun, 11 Oct 2026 10:30:43 +0800 Subject: [PATCH 133/134] scripts/gdb: fix radix tree lookup pointer handling Patch series "scripts/gdb: fix radix tree lookup and stack depot diagnostic", v2. Two scripts/gdb fixes. Patch 1 fixes $lx_radix_tree_lookup(), which dereferenced slot contents as if they were node pointers. v2 also handles the XArray sibling, retry and zero entries that Sashiko pointed out: they carry the internal tag but are not node pointers, and the v1 patch raised a GDB MemoryError on them. Large folios in the page cache make sibling entries common, so this was worth fixing rather than documenting. Patch 2 is unchanged from v1 apart from being resent with git send-email, since the earlier copy was word-wrapped by the mail client. Sorry about that. Both patches are tested against standalone GCC-built fixtures that use the kernel struct layouts (details below the --- line of each patch), not against a running kernel. This patch (of 2): lookup() casts each slot to a pointer to a node pointer and then dereferences it. Since the slot already contains an entry pointer, this reads the entry's contents as another pointer instead of returning the entry itself. Internal entries also keep their tag bits when descending, so multi-level trees are read from the wrong address and $lx_radix_tree_lookup() fails with a memory error. Sibling, retry and zero entries carry the same internal tag but are small integers rather than node pointers, so the tag alone must not decide whether to descend. A page cache populated with large folios has a sibling entry in every slot but the first of each folio. Walk the tree the way xas_load() does: descend only through entries that xa_is_node() would accept, follow sibling entries to the canonical slot of a multi-index entry, and treat retry and zero entries like empty slots since they hold no data. Remove the explicit shift counter, as traversal now follows the entry type. Link: https://lore.kernel.org/20261011023044.1722-1-ue081723@gmail.com Link: https://lore.kernel.org/20261011023044.1722-2-ue081723@gmail.com Fixes: b7235d6bb516 ("scripts/gdb: add a Radix Tree Parser") Signed-off-by: Ting-Han Hou Signed-off-by: Andrew Morton Assisted-by: LLM Cc: Jan Kiszka Cc: Kieran Bingham Cc: Kuan-Ying Lee --- scripts/gdb/linux/radixtree.py | 72 ++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/scripts/gdb/linux/radixtree.py b/scripts/gdb/linux/radixtree.py index bc2954e45c32..8ce9936dfa50 100644 --- a/scripts/gdb/linux/radixtree.py +++ b/scripts/gdb/linux/radixtree.py @@ -27,6 +27,24 @@ def entry_to_node(node): indirect_ptr = node.cast(long_type) & ~constants.LX_RADIX_TREE_INTERNAL_NODE return indirect_ptr.cast(radix_tree_node_type.get_type().pointer()) +def is_node(entry): + # Like xa_is_node(): internal entries below 4096 are sibling, retry + # or zero entries rather than pointers to a struct xa_node. + ulong_type = utils.get_ulong_type() + return is_internal_node(entry) and entry.cast(ulong_type) > 4096 + +def is_sibling(entry): + # Like xa_is_sibling(): a multi-index entry occupies several slots, + # and all but the first hold a sibling entry that encodes the offset + # of the first one. + ulong_type = utils.get_ulong_type() + return is_internal_node(entry) and entry.cast(ulong_type) < \ + xa_mk_internal(constants.LX_RADIX_TREE_MAP_SIZE - 1) + +def sibling_offset(entry): + ulong_type = utils.get_ulong_type() + return int(entry.cast(ulong_type)) >> 2 + def node_maxindex(node): return (constants.LX_RADIX_TREE_MAP_SIZE << node['shift']) - 1 @@ -40,39 +58,35 @@ def resolve_root(root): def lookup(root, index): root = resolve_root(root) - node = root['xa_head'] - if node == 0: + entry = root['xa_head'] + + if is_node(entry): + node = entry_to_node(entry) + if index > node_maxindex(node): + return None + + # Walk down the tree like xas_load() does. + while True: + offset = (index >> node['shift']) & constants.LX_RADIX_TREE_MAP_MASK + entry = node['slots'][offset] + + while is_sibling(entry): + entry = node['slots'][sibling_offset(entry)] + if node['shift'] and is_node(entry): + # xas_descend() turns this into a retry entry. + return None + + if not is_node(entry) or node['shift'] == 0: + break + node = entry_to_node(entry) + elif index > 0: return None - if not (is_internal_node(node)): - if (index > 0): - return None - return node - - node = entry_to_node(node) - maxindex = node_maxindex(node) - - if (index > maxindex): + # Empty slots and retry or zero entries hold no data. + if entry == 0 or is_internal_node(entry): return None - shift = node['shift'] + constants.LX_RADIX_TREE_MAP_SHIFT - - while True: - offset = (index >> node['shift']) & constants.LX_RADIX_TREE_MAP_MASK - slot = node['slots'][offset] - - if slot == 0: - return None - - node = slot.cast(node.type.pointer()).dereference() - if node == 0: - return None - - shift -= constants.LX_RADIX_TREE_MAP_SHIFT - if (shift <= 0): - break - - return node + return entry def descend(parent, index): offset = (index >> int(parent["shift"])) & constants.LX_RADIX_TREE_MAP_MASK From b6232ca8d194c60ee603586e42b5ea584d4b7be9 Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Sun, 11 Oct 2026 10:30:44 +0800 Subject: [PATCH 134/134] scripts/gdb: fix stack depot out-of-bounds diagnostic The pool_index field was renamed to pool_index_plus_1, but the out-of-bounds diagnostic in stack_depot_fetch() still accesses the old field. Looking up a handle whose pool index is outside stack_pools therefore raises "There is no member named pool_index" instead of printing the diagnostic and returning an empty result. Use the already decoded pool_index variable in the diagnostic. Link: https://lore.kernel.org/20261011023044.1722-3-ue081723@gmail.com Fixes: 9d938f40b228 ("scripts/gdb: rename pool_index to pool_index_plus_1") Signed-off-by: Ting-Han Hou Signed-off-by: Andrew Morton Assisted-by: LLM Cc: Jan Kiszka Cc: Kieran Bingham Cc: Kuan-Ying Lee --- scripts/gdb/linux/stackdepot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gdb/linux/stackdepot.py b/scripts/gdb/linux/stackdepot.py index 37313a5a51a0..aaa18af25cd1 100644 --- a/scripts/gdb/linux/stackdepot.py +++ b/scripts/gdb/linux/stackdepot.py @@ -39,7 +39,7 @@ def stack_depot_fetch(handle): pool_index = parts['pool_index_plus_1'] - 1 if pool_index >= pools_num: - gdb.write("pool index %d out of bounds (%d) for stack id 0x%08x\n" % (parts['pool_index'], pools_num, handle)) + gdb.write("pool index %d out of bounds (%d) for stack id 0x%08x\n" % (pool_index, pools_num, handle)) return gdb.Value(0), 0 stack_pools = gdb.parse_and_eval('stack_pools')