From d179ee76aba9141666b367528f81972943ab54fe Mon Sep 17 00:00:00 2001 From: Manuel Quintero Fonseca Date: Fri, 22 May 2026 17:01:31 -0700 Subject: [PATCH 01/95] resource: downgrade "resource sanity check" warning to debug level The "resource sanity check" warning need not mean that there is a functional issue and in the cases when there isn't one, it is just useless and confusing noise. For this reason, downgrade the log level of it from "warn" to "debug". This reduces log clutter while keeping the diagnostic information available for debugging purposes if needed. Link: https://lore.kernel.org/20260523000131.7086-1-sakunix@yahoo.com Signed-off-by: Manuel Quintero Fonseca Cc: Andriy Shevchenko Cc: Bjorn Helgaas Cc: Hans de Goede Cc: Mika Westeberg Cc: "Rafael J. Wysocki" Signed-off-by: Andrew Morton --- kernel/resource.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/resource.c b/kernel/resource.c index 3d17e3196a3e..e60539a55541 100644 --- a/kernel/resource.c +++ b/kernel/resource.c @@ -1859,7 +1859,7 @@ int iomem_map_sanity_check(resource_size_t addr, unsigned long size) if (p->flags & IORESOURCE_BUSY) continue; - pr_warn("resource sanity check: requesting [mem %pa-%pa], which spans more than %s %pR\n", + pr_debug("resource sanity check: requesting [mem %pa-%pa], which spans more than %s %pR\n", &addr, &end, p->name, p); err = -1; break; From 688bc88e2046dd6ce81ce18079b5254cb8dadc0e Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 16 Jun 2026 15:49:31 +0800 Subject: [PATCH 02/95] ocfs2/cluster: keep heartbeat local node stable o2nm_node_local_store() handles local=0 by stopping o2net and setting cl_local_node to O2NM_INVALID_NODE_NUM, but it leaves cl_has_local set. That stale state makes o2nm_this_node() return 255, blocks a later local=1 attempt with -EBUSY, and can feed 255 to heartbeat users that call o2nm_this_node() dynamically. Clearing cl_has_local is required when the local node is reset. But heartbeat threads can still be running at that point. They pin the local node config item at startup, yet o2hb_do_disk_heartbeat() and thread teardown re-read o2nm_this_node() for the local slot and for o2nm_undepend_this_node(). Once local=0 has cleared the live local-node state, those dynamic reads return O2NM_MAX_NODES, which is also the invalid node number 255. Store the local node number in the heartbeat region when the region starts. Use that stable node for heartbeat slot writes/checks, negotiation messages, and the final configfs undepend. Stop the heartbeat loop when the current local node no longer matches the stored node, and clear cl_has_local together with cl_local_node in the local=0 path so nodemanager state matches node removal. Validation reproduced this kernel report: KASAN slab-out-of-bounds in o2hb_do_disk_heartbeat+0x372/0xb30 RIP: 0010:memset+0xf/0x20 Read of size 8 Call trace: dump_stack_lvl+0x66/0xa0 print_report+0xd0/0x630 o2hb_do_disk_heartbeat+0x372/0xb30 (fs/ocfs2/cluster/heartbeat.c:1079) srso_alias_return_thunk+0x5/0xfbef5 __virt_addr_valid+0x188/0x2f0 kasan_report+0xe4/0x120 o2hb_do_disk_heartbeat+0x5/0xb30 (fs/ocfs2/cluster/heartbeat.c:1079) o2hb_thread+0x14e/0x770 kthread_affine_node+0x139/0x180 lockdep_hardirqs_on_prepare+0xda/0x190 trace_hardirqs_on+0x18/0x130 kthread+0x19d/0x1e0 ret_from_fork+0x37a/0x4d0 __switch_to+0x2d5/0x6f0 ret_from_fork_asm+0x1a/0x30 Link: https://lore.kernel.org/20260616074931.3774929-1-zzzccc427@gmail.com Fixes: a7f6a5fb4bde ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Suggested-by: Joseph Qi Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/cluster/heartbeat.c | 43 +++++++++++++++++++++++----------- fs/ocfs2/cluster/nodemanager.c | 19 +++++++++++---- fs/ocfs2/cluster/nodemanager.h | 2 ++ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index d12784aaaa4b..6da96a374fcd 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -203,6 +203,7 @@ struct o2hb_region { /* protected by the hr_callback_sem */ struct task_struct *hr_task; + u8 hr_node_num; unsigned int hr_blocks; unsigned long long hr_start_block; @@ -350,12 +351,12 @@ static void o2hb_disarm_timeout(struct o2hb_region *reg) cancel_delayed_work_sync(®->hr_nego_timeout_work); } -static int o2hb_send_nego_msg(int key, int type, u8 target) +static int o2hb_send_nego_msg(int key, int type, u8 target, u8 node_num) { struct o2hb_nego_msg msg; int status, ret; - msg.node_num = o2nm_this_node(); + msg.node_num = node_num; again: ret = o2net_send_message(type, key, &msg, sizeof(msg), target, &status); @@ -373,8 +374,10 @@ static void o2hb_nego_timeout(struct work_struct *work) unsigned long live_node_bitmap[BITS_TO_LONGS(O2NM_MAX_NODES)]; int master_node, i, ret; struct o2hb_region *reg; + u8 node_num; reg = container_of(work, struct o2hb_region, hr_nego_timeout_work.work); + node_num = reg->hr_node_num; /* don't negotiate timeout if last hb failed since it is very * possible io failed. Should let write timeout fence self. */ @@ -385,10 +388,10 @@ static void o2hb_nego_timeout(struct work_struct *work) /* lowest node as master node to make negotiate decision. */ master_node = find_first_bit(live_node_bitmap, O2NM_MAX_NODES); - if (master_node == o2nm_this_node()) { + if (master_node == node_num) { if (!test_bit(master_node, reg->hr_nego_node_bitmap)) { printk(KERN_NOTICE "o2hb: node %d hb write hung for %ds on region %s (%pg).\n", - o2nm_this_node(), O2HB_NEGO_TIMEOUT_MS/1000, + node_num, O2HB_NEGO_TIMEOUT_MS / 1000, config_item_name(®->hr_item), reg_bdev(reg)); set_bit(master_node, reg->hr_nego_node_bitmap); } @@ -417,7 +420,7 @@ static void o2hb_nego_timeout(struct work_struct *work) mlog(ML_HEARTBEAT, "send NEGO_APPROVE msg to node %d\n", i); ret = o2hb_send_nego_msg(reg->hr_key, - O2HB_NEGO_APPROVE_MSG, i); + O2HB_NEGO_APPROVE_MSG, i, node_num); if (ret) mlog(ML_ERROR, "send NEGO_APPROVE msg to node %d fail %d\n", i, ret); @@ -425,10 +428,10 @@ static void o2hb_nego_timeout(struct work_struct *work) } else { /* negotiate timeout with master node. */ printk(KERN_NOTICE "o2hb: node %d hb write hung for %ds on region %s (%pg), negotiate timeout with node %d.\n", - o2nm_this_node(), O2HB_NEGO_TIMEOUT_MS/1000, config_item_name(®->hr_item), + node_num, O2HB_NEGO_TIMEOUT_MS / 1000, config_item_name(®->hr_item), reg_bdev(reg), master_node); ret = o2hb_send_nego_msg(reg->hr_key, O2HB_NEGO_TIMEOUT_MSG, - master_node); + master_node, node_num); if (ret) mlog(ML_ERROR, "send NEGO_TIMEOUT msg to node %d fail %d\n", master_node, ret); @@ -601,7 +604,9 @@ static int o2hb_issue_node_write(struct o2hb_region *reg, o2hb_bio_wait_init(write_wc); - slot = o2nm_this_node(); + slot = reg->hr_node_num; + if (slot >= O2NM_MAX_NODES) + return -EINVAL; bio = o2hb_setup_one_bio(reg, write_wc, &slot, slot+1, REQ_OP_WRITE | REQ_SYNC); @@ -670,8 +675,12 @@ static int o2hb_check_own_slot(struct o2hb_region *reg) struct o2hb_disk_slot *slot; struct o2hb_disk_heartbeat_block *hb_block; char *errstr; + u8 node_num = reg->hr_node_num; - slot = ®->hr_slots[o2nm_this_node()]; + if (node_num >= O2NM_MAX_NODES) + return 0; + + slot = ®->hr_slots[node_num]; /* Don't check on our 1st timestamp */ if (!slot->ds_last_time) return 0; @@ -712,7 +721,10 @@ static inline void o2hb_prepare_block(struct o2hb_region *reg, struct o2hb_disk_slot *slot; struct o2hb_disk_heartbeat_block *hb_block; - node_num = o2nm_this_node(); + node_num = reg->hr_node_num; + if (node_num >= O2NM_MAX_NODES) + return; + slot = ®->hr_slots[node_num]; hb_block = (struct o2hb_disk_heartbeat_block *)slot->ds_raw_block; @@ -1206,7 +1218,7 @@ static int o2hb_thread(void *data) set_user_nice(current, MIN_NICE); /* Pin node */ - ret = o2nm_depend_this_node(); + ret = o2nm_depend_node(reg->hr_node_num); if (ret) { mlog(ML_ERROR, "Node has been deleted, ret = %d\n", ret); reg->hr_node_deleted = 1; @@ -1215,7 +1227,8 @@ static int o2hb_thread(void *data) } while (!kthread_should_stop() && - !reg->hr_unclean_stop && !reg->hr_aborted_start) { + !reg->hr_unclean_stop && !reg->hr_aborted_start && + o2nm_this_node() == reg->hr_node_num) { /* We track the time spent inside * o2hb_do_disk_heartbeat so that we avoid more than * hr_timeout_ms between disk writes. On busy systems @@ -1264,7 +1277,7 @@ static int o2hb_thread(void *data) } /* Unpin node */ - o2nm_undepend_this_node(); + o2nm_undepend_node(reg->hr_node_num); mlog(ML_HEARTBEAT|ML_KTHREAD, "o2hb thread exiting\n"); @@ -1791,7 +1804,8 @@ static ssize_t o2hb_region_dev_store(struct config_item *item, /* We can't heartbeat without having had our node number * configured yet. */ - if (o2nm_this_node() == O2NM_MAX_NODES) + reg->hr_node_num = o2nm_this_node(); + if (reg->hr_node_num == O2NM_MAX_NODES) return -EINVAL; ret = kstrtol(p, 0, &fd); @@ -2036,6 +2050,7 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g ret = -ENAMETOOLONG; goto free; } + reg->hr_node_num = O2NM_MAX_NODES; spin_lock(&o2hb_live_lock); reg->hr_region_num = 0; diff --git a/fs/ocfs2/cluster/nodemanager.c b/fs/ocfs2/cluster/nodemanager.c index 402563154550..e1f8f577ce5d 100644 --- a/fs/ocfs2/cluster/nodemanager.c +++ b/fs/ocfs2/cluster/nodemanager.c @@ -367,6 +367,7 @@ static ssize_t o2nm_node_local_store(struct config_item *item, const char *page, if (!tmp && cluster->cl_has_local && cluster->cl_local_node == node->nd_num) { o2net_stop_listening(node); + cluster->cl_has_local = 0; cluster->cl_local_node = O2NM_INVALID_NODE_NUM; } @@ -782,12 +783,12 @@ void o2nm_undepend_item(struct config_item *item) configfs_undepend_item(item); } -int o2nm_depend_this_node(void) +int o2nm_depend_node(u8 node_num) { int ret = 0; struct o2nm_node *local_node; - local_node = o2nm_get_node_by_num(o2nm_this_node()); + local_node = o2nm_get_node_by_num(node_num); if (!local_node) { ret = -EINVAL; goto out; @@ -800,17 +801,27 @@ int o2nm_depend_this_node(void) return ret; } -void o2nm_undepend_this_node(void) +void o2nm_undepend_node(u8 node_num) { struct o2nm_node *local_node; - local_node = o2nm_get_node_by_num(o2nm_this_node()); + local_node = o2nm_get_node_by_num(node_num); BUG_ON(!local_node); o2nm_undepend_item(&local_node->nd_item); o2nm_node_put(local_node); } +int o2nm_depend_this_node(void) +{ + return o2nm_depend_node(o2nm_this_node()); +} + +void o2nm_undepend_this_node(void) +{ + o2nm_undepend_node(o2nm_this_node()); +} + static void __exit exit_o2nm(void) { diff --git a/fs/ocfs2/cluster/nodemanager.h b/fs/ocfs2/cluster/nodemanager.h index 3490e77a952d..39006005427b 100644 --- a/fs/ocfs2/cluster/nodemanager.h +++ b/fs/ocfs2/cluster/nodemanager.h @@ -65,6 +65,8 @@ void o2nm_node_put(struct o2nm_node *node); int o2nm_depend_item(struct config_item *item); void o2nm_undepend_item(struct config_item *item); +int o2nm_depend_node(u8 node_num); +void o2nm_undepend_node(u8 node_num); int o2nm_depend_this_node(void); void o2nm_undepend_this_node(void); From 2fdf181afd4301e8044075afe36ccfe24980de19 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sun, 21 Jun 2026 04:42:23 +0530 Subject: [PATCH 03/95] ocfs2: use inode_lock_nested() for orphan dir locking PREEMPT_RT's rtmutex PI chain walker warns about a lock dependency cycle when inode_lock(orphan_dir_inode) is called while holding inode_lock(file_inode): ocfs2_file_write_iter() inode_lock(file_inode) [class 0] ocfs2_dio_end_io_write() ocfs2_del_inode_from_orphan() inode_lock(orphan_dir_inode) [class 0] <- warning! However this is a false positive. write_iter() is never called on a directory, and orphan_dir is always a directory, so these two locks can never actually conflict in practice. Fix by using inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2) in all three places where orphan_dir_inode is locked in namei.c, placing it in a separate lock class so the rtmutex PI chain walker understands these locks have distinct roles and does not warn about their ordering. Link: https://lore.kernel.org/20260620231223.46588-1-kartikey406@gmail.com Signed-off-by: Deepanshu Kartikey Suggested-by: Matthew Wilcox Reported-by: syzbot+ce129763ce7d7e914739@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ce129763ce7d7e914739 Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/namei.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 1277666c77cd..4cfd7b3d3e1a 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -2126,7 +2126,7 @@ static int ocfs2_lookup_lock_orphan_dir(struct ocfs2_super *osb, return ret; } - inode_lock(orphan_dir_inode); + inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2); ret = ocfs2_inode_lock(orphan_dir_inode, &orphan_dir_bh, 1); if (ret < 0) { @@ -2725,7 +2725,7 @@ int ocfs2_del_inode_from_orphan(struct ocfs2_super *osb, goto bail; } - inode_lock(orphan_dir_inode); + inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2); status = ocfs2_inode_lock(orphan_dir_inode, &orphan_dir_bh, 1); if (status < 0) { inode_unlock(orphan_dir_inode); @@ -2838,7 +2838,7 @@ int ocfs2_mv_orphaned_inode_to_new(struct inode *dir, goto leave; } - inode_lock(orphan_dir_inode); + inode_lock_nested(orphan_dir_inode, I_MUTEX_NONDIR2); status = ocfs2_inode_lock(orphan_dir_inode, &orphan_dir_bh, 1); if (status < 0) { From c04cffb8c51618538f0c05c478a931eb6e1a806b Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sun, 21 Jun 2026 12:11:33 +0000 Subject: [PATCH 04/95] lib/string: fix memchr_inv() for large ranges memchr_inv() takes a size_t length but counts 8 byte words in an unsigned int. At 32GiB that count wraps, so the scan can quietly miss most of the range. Use size_t for the word count. Link: https://lore.kernel.org/20260621121133.16460-1-include@grrlz.net Fixes: 798248206b59 ("lib/string.c: introduce memchr_inv()") Signed-off-by: Bradley Morgan Cc: Akinbou Mita Cc: Andy Shevchenko Cc: Christoph Lameer Cc: Joern Engel Cc: Kees Cook Cc: Pekka Enberg Signed-off-by: Andrew Morton --- lib/string.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/string.c b/lib/string.c index a4e8ad23577d..a3778d5aab4a 100644 --- a/lib/string.c +++ b/lib/string.c @@ -821,7 +821,8 @@ void *memchr_inv(const void *start, int c, size_t bytes) { u8 value = c; u64 value64; - unsigned int words, prefix; + size_t words; + unsigned int prefix; if (bytes <= 16) return check_bytes8(start, value, bytes); From b4e28b4db72c8005baf4bacfeb8fcf686b13154e Mon Sep 17 00:00:00 2001 From: Jim Cromie Date: Thu, 18 Jun 2026 13:07:15 -0600 Subject: [PATCH 05/95] kernel/params: fix a pr_debug(" %p ") in parse_one() Inside parse_one(), the core parameter-parsing engine prints the address of the parameter-set callback function using %p: pr_debug("handling %s with %p\n", param, params[i].ops->set); Since the string value of the parameter being parsed (val) is already available, print the parameter name and its value instead, and avoid tainting the kernel by exposing a kernel-ptr. Link: https://lore.kernel.org/20260618190715.3563047-1-jim.cromie@gmail.com Signed-off-by: Jim Cromie Cc: Greg Kroah-Hartman Signed-off-by: Andrew Morton --- kernel/params.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/params.c b/kernel/params.c index a668863a4bb6..5c8a05921e28 100644 --- a/kernel/params.c +++ b/kernel/params.c @@ -136,8 +136,8 @@ static int parse_one(char *param, if (!val && !(params[i].ops->flags & KERNEL_PARAM_OPS_FL_NOARG)) return -EINVAL; - pr_debug("handling %s with %p\n", param, - params[i].ops->set); + pr_debug("handling %s with value '%s'\n", param, + val ? val : "no-arg"); kernel_param_lock(params[i].mod); if (param_check_unsafe(¶ms[i])) err = params[i].ops->set(val, ¶ms[i]); From 5fc2358263e92db7c0436f6d1c3e9276a826986b Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 16 Jun 2026 01:45:57 +0800 Subject: [PATCH 06/95] watchdog/softlockup: fix softlockup typos Fix misspellings of "softlockup" in the watchdog enabled bit definitions and related comments. Also fix a nearby "successful" typo. No functional change. Link: https://lore.kernel.org/20260615174557.1836562-1-edcr1790@gmail.com Signed-off-by: Matthew Chen Reviewed-by: Douglas Anderson Reviewed-by: Petr Mladek Signed-off-by: Andrew Morton --- include/linux/nmi.h | 4 ++-- kernel/watchdog.c | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/linux/nmi.h b/include/linux/nmi.h index bc1162895f35..89c5465e2524 100644 --- a/include/linux/nmi.h +++ b/include/linux/nmi.h @@ -77,9 +77,9 @@ static inline void reset_hung_task_detector(void) { } * detectors are 'suspended' while 'watchdog_thresh' is equal zero. */ #define WATCHDOG_HARDLOCKUP_ENABLED_BIT 0 -#define WATCHDOG_SOFTOCKUP_ENABLED_BIT 1 +#define WATCHDOG_SOFTLOCKUP_ENABLED_BIT 1 #define WATCHDOG_HARDLOCKUP_ENABLED (1 << WATCHDOG_HARDLOCKUP_ENABLED_BIT) -#define WATCHDOG_SOFTOCKUP_ENABLED (1 << WATCHDOG_SOFTOCKUP_ENABLED_BIT) +#define WATCHDOG_SOFTLOCKUP_ENABLED (1 << WATCHDOG_SOFTLOCKUP_ENABLED_BIT) #if defined(CONFIG_HARDLOCKUP_DETECTOR) extern void hardlockup_detector_disable(void); diff --git a/kernel/watchdog.c b/kernel/watchdog.c index 87dd5e0f6968..e5134ad7b663 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -359,14 +359,14 @@ static void lockup_detector_update_enable(void) if (watchdog_hardlockup_available && watchdog_hardlockup_user_enabled) watchdog_enabled |= WATCHDOG_HARDLOCKUP_ENABLED; if (watchdog_softlockup_user_enabled) - watchdog_enabled |= WATCHDOG_SOFTOCKUP_ENABLED; + watchdog_enabled |= WATCHDOG_SOFTLOCKUP_ENABLED; } #ifdef CONFIG_SOFTLOCKUP_DETECTOR /* - * Delay the soflockup report when running a known slow code. - * It does _not_ affect the timestamp of the last successdul reschedule. + * Delay the softlockup report when running a known slow code. + * It does _not_ affect the timestamp of the last successful reschedule. */ #define SOFTLOCKUP_DELAY_REPORT ULONG_MAX @@ -742,7 +742,7 @@ static int is_softlockup(unsigned long touch_ts, unsigned long period_ts, unsigned long now) { - if ((watchdog_enabled & WATCHDOG_SOFTOCKUP_ENABLED) && watchdog_thresh) { + if ((watchdog_enabled & WATCHDOG_SOFTLOCKUP_ENABLED) && watchdog_thresh) { /* * If period_ts has not been updated during a sample_period, then * in the subsequent few sample_periods, period_ts might also not @@ -1098,11 +1098,11 @@ static void proc_watchdog_update(bool thresh_changed) * caller | table->data points to | 'which' * -------------------|----------------------------------|------------------------------- * proc_watchdog | watchdog_user_enabled | WATCHDOG_HARDLOCKUP_ENABLED | - * | | WATCHDOG_SOFTOCKUP_ENABLED + * | | WATCHDOG_SOFTLOCKUP_ENABLED * -------------------|----------------------------------|------------------------------- * proc_nmi_watchdog | watchdog_hardlockup_user_enabled | WATCHDOG_HARDLOCKUP_ENABLED * -------------------|----------------------------------|------------------------------- - * proc_soft_watchdog | watchdog_softlockup_user_enabled | WATCHDOG_SOFTOCKUP_ENABLED + * proc_soft_watchdog | watchdog_softlockup_user_enabled | WATCHDOG_SOFTLOCKUP_ENABLED */ static int proc_watchdog_common(int which, const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) @@ -1136,7 +1136,7 @@ static int proc_watchdog(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return proc_watchdog_common(WATCHDOG_HARDLOCKUP_ENABLED | - WATCHDOG_SOFTOCKUP_ENABLED, + WATCHDOG_SOFTLOCKUP_ENABLED, table, write, buffer, lenp, ppos); } @@ -1159,7 +1159,7 @@ static int proc_nmi_watchdog(const struct ctl_table *table, int write, static int proc_soft_watchdog(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - return proc_watchdog_common(WATCHDOG_SOFTOCKUP_ENABLED, + return proc_watchdog_common(WATCHDOG_SOFTLOCKUP_ENABLED, table, write, buffer, lenp, ppos); } #endif From d19cdc167e696714509e87d3f7ae765b6e164589 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Mon, 22 Jun 2026 20:25:08 +0000 Subject: [PATCH 07/95] signal: avoid shared siginfo namespace rewrites send_signal_locked() rewrites sender ids for the target namespace. Group sends reuse the same siginfo, so one recipient can affect the next. Copy the siginfo before changing it. Link: https://lore.kernel.org/86a8857d58d43ee26a8b365b837fd24830343494.1782159692.git.include@grrlz.net Fixes: 7a0cf094944e ("signal: Correct namespace fixups of si_pid and si_uid") Signed-off-by: Bradley Morgan Acked-by: Oleg Nesterov Cc: "Eric W. Biederman" Cc: Adrian Huang Cc: Aleksandr Nogikh Cc: Christian Brauner Cc: Marco Elver Cc: "Masami Hiramatsu (Google)" Cc: Mathieu Desnoyers Cc: Peter Zijlstra Cc: Steven Rostedt Cc: Signed-off-by: Andrew Morton --- kernel/signal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/signal.c b/kernel/signal.c index bbc0fd4cc4d7..c53f202da9da 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1181,6 +1181,7 @@ static inline bool has_si_pid_and_uid(struct kernel_siginfo *info) int send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type) { + struct kernel_siginfo rewritten; /* Should SIGKILL or SIGSTOP be received by a pid namespace init? */ bool force = false; @@ -1194,6 +1195,9 @@ int send_signal_locked(int sig, struct kernel_siginfo *info, /* SIGKILL and SIGSTOP is special or has ids */ struct user_namespace *t_user_ns; + rewritten = *info; + info = &rewritten; + rcu_read_lock(); t_user_ns = task_cred_xxx(t, user_ns); if (current_user_ns() != t_user_ns) { From 306d71b091908bd898cbc0d7e98130354b62be2e Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Fri, 26 Jun 2026 17:33:08 +0200 Subject: [PATCH 08/95] signal: change sys_kill() to use SEND_SIG_NOINFO prepare_kill_siginfo(PIDTYPE_TGID) fills si_code = SI_USER and sets si_pid/si_uid in the sender's namespace. Then send_signal_locked() translates si_pid/si_uid to the target's namespace. SEND_SIG_NOINFO exists precisely for the case when si_code == SI_USER and si_pid/si_uid are the sender's ids; this is exactly what sys_kill() does via prepare_kill_siginfo(PIDTYPE_TGID). Change sys_kill() to use it directly. SEND_SIG_NOINFO produces the same result: si_code = SI_USER, and __send_signal_locked() computes si_pid/si_uid directly in the target's namespace. The force computation is also the same: both check if the sender is visible in the target's pid namespace. This is just a cleanup and microoptimization (especially with [1]), this skips the has_si_pid_and_uid() block in send_signal_locked() and offloads the namespace translation logic to __send_signal_locked(SEND_SIG_NOINFO) which uses the simpler computations. NOTE: As a "side effect" this also fixes the kill(pid < 0, sig) case where send_signal_locked() rewrites si_pid/si_uid in the shared siginfo, corrupting it for subsequent recipients. But for other group senders like __kill_pgrp_info() we still need the fix from Bradley Morgan [1] who found this problem. TODO: kill prepare_kill_siginfo() and change other users to use SEND_SIG_NOINFO too. This needs trivial changes in __send_signal_locked() and TP_STORE_SIGINFO(). Link: https://lore.kernel.org/aj6btAZqYuv59a8w@redhat.com Link: https://lore.kernel.org/all/20260622164029.11474-1-include@grrlz.net/ [1] Signed-off-by: Oleg Nesterov Reviewed-by: Bradley Morgan Cc: Eric Biederman Signed-off-by: Andrew Morton --- kernel/signal.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/kernel/signal.c b/kernel/signal.c index c53f202da9da..041498ff835e 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -3962,11 +3962,7 @@ static void prepare_kill_siginfo(int sig, struct kernel_siginfo *info, */ SYSCALL_DEFINE2(kill, pid_t, pid, int, sig) { - struct kernel_siginfo info; - - prepare_kill_siginfo(sig, &info, PIDTYPE_TGID); - - return kill_something_info(sig, &info, pid); + return kill_something_info(sig, SEND_SIG_NOINFO, pid); } /* From 571999ccd1224d7a629f2592887750e381c665b3 Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Sat, 4 Jul 2026 16:34:42 +0200 Subject: [PATCH 09/95] signal: avoid unconditional siginfo copy in send_signal_locked() send_signal_locked() unconditionally copies siginfo before the namespace translation to avoid corrupting a shared siginfo. Not that I think this can actually hurt performance-wise, just it doesn't look clean to me; the copy is only needed in the unlikely case when the translation will actually change something. Defer it to the two cases where si_pid/si_uid are rewritten, and while at it add #ifdef's just for completeness. Link: https://lore.kernel.org/akkaAgNfUby5_3nM@redhat.com Signed-off-by: Oleg Nesterov Reviewed-by: Bradley Morgan Cc: Christian Brauner Cc: Eric Biederman Signed-off-by: Andrew Morton --- kernel/signal.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/kernel/signal.c b/kernel/signal.c index 041498ff835e..0f509ee4f42d 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1181,7 +1181,7 @@ static inline bool has_si_pid_and_uid(struct kernel_siginfo *info) int send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type) { - struct kernel_siginfo rewritten; + struct kernel_siginfo __maybe_unused rewritten; /* Should SIGKILL or SIGSTOP be received by a pid namespace init? */ bool force = false; @@ -1193,27 +1193,34 @@ int send_signal_locked(int sig, struct kernel_siginfo *info, force = true; } else if (has_si_pid_and_uid(info)) { /* SIGKILL and SIGSTOP is special or has ids */ +#ifdef CONFIG_USER_NS struct user_namespace *t_user_ns; - - rewritten = *info; - info = &rewritten; + kuid_t uid; rcu_read_lock(); t_user_ns = task_cred_xxx(t, user_ns); if (current_user_ns() != t_user_ns) { - kuid_t uid = make_kuid(current_user_ns(), info->si_uid); - info->si_uid = from_kuid_munged(t_user_ns, uid); + rewritten = *info; + info = &rewritten; + uid = make_kuid(current_user_ns(), info->si_uid); + rewritten.si_uid = from_kuid_munged(t_user_ns, uid); } rcu_read_unlock(); - +#endif /* A kernel generated signal? */ force = (info->si_code == SI_KERNEL); +#ifdef CONFIG_PID_NS /* From an ancestor pid namespace? */ if (!task_pid_nr_ns(current, task_active_pid_ns(t))) { - info->si_pid = 0; + if (info != &rewritten) { + rewritten = *info; + info = &rewritten; + } + rewritten.si_pid = 0; force = true; } +#endif } return __send_signal_locked(sig, info, t, type, force); } From a0275efa65ca8deff937729c5d2982a980f9affe Mon Sep 17 00:00:00 2001 From: Adi Nata Date: Sat, 6 Jun 2026 11:03:09 +0800 Subject: [PATCH 10/95] lib/math: add KUnit test suite for polynomial_calc() Add a KUnit test suite for the polynomial_calc() function, which had no in-kernel test coverage. The tests verify correct evaluation of constant, linear, quadratic, and cubic polynomials, including negative coefficients, negative input data, zero-coefficient terms. The Kconfig entry uses 'select POLYNOMIAL' rather than 'depends on POLYNOMIAL' because POLYNOMIAL is a promptless tristate that cannot be manually enabled on UML without an explicit selector. Link: https://lore.kernel.org/20260606030319.316752-1-adinata.softwareengineer@gmail.com Signed-off-by: Adi Nata Cc: Maxim Kaurkin Cc: Serge Semin Cc: Guenter Roeck Cc: Brendan Higgins Cc: David Gow Cc: Rae Moar Signed-off-by: Andrew Morton --- lib/Kconfig.debug | 17 ++ lib/math/tests/Makefile | 1 + lib/math/tests/polynomial_kunit.c | 270 ++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 lib/math/tests/polynomial_kunit.c diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 1244dcac2294..311b99790416 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -3504,6 +3504,23 @@ config GCD_KUNIT_TEST If unsure, say N +config POLYNOMIAL_KUNIT_TEST + tristate "Polynomial calculation (polynomial_calc) test" if !KUNIT_ALL_TESTS + depends on KUNIT + select POLYNOMIAL + default KUNIT_ALL_TESTS + help + This option enables the KUnit test suite for the polynomial_calc() + function, which evaluates integer polynomials using factor + redistribution to avoid overflow. + + The test suite verifies correctness for constant, linear, and + quadratic polynomials, negative coefficients, per-step dividers, + divider_leftover, total_divider scaling, and a real sensor + N-to-temperature conversion polynomial. + + If unsure, say N + config PRIME_NUMBERS_KUNIT_TEST tristate "Prime number generator test" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/lib/math/tests/Makefile b/lib/math/tests/Makefile index 13dc96e48408..85e1ad59f29d 100644 --- a/lib/math/tests/Makefile +++ b/lib/math/tests/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_GCD_KUNIT_TEST) += gcd_kunit.o obj-$(CONFIG_INT_LOG_KUNIT_TEST) += int_log_kunit.o obj-$(CONFIG_INT_POW_KUNIT_TEST) += int_pow_kunit.o obj-$(CONFIG_INT_SQRT_KUNIT_TEST) += int_sqrt_kunit.o +obj-$(CONFIG_POLYNOMIAL_KUNIT_TEST) += polynomial_kunit.o obj-$(CONFIG_PRIME_NUMBERS_KUNIT_TEST) += prime_numbers_kunit.o obj-$(CONFIG_RATIONAL_KUNIT_TEST) += rational_kunit.o diff --git a/lib/math/tests/polynomial_kunit.c b/lib/math/tests/polynomial_kunit.c new file mode 100644 index 000000000000..ef443b57fc12 --- /dev/null +++ b/lib/math/tests/polynomial_kunit.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +struct polynomial_test_param { + const struct polynomial *poly; + long data; + long expected; + const char *name; +}; + +/* f(x) = 5 */ +static const struct polynomial poly_constant = { + .total_divider = 1, + .terms = { + {0, 5, 1, 1}, + } +}; + +/* f(x) = 2x^2 + 3x + 5 */ +static const struct polynomial poly_simple = { + .total_divider = 1, + .terms = { + {2, 2, 1, 1}, + {1, 3, 1, 1}, + {0, 5, 1, 1}, + } +}; + +/* f(x) = -5x + 100 */ +static const struct polynomial poly_negative_coef = { + .total_divider = 1, + .terms = { + {1, -5, 1, 1}, + {0, 100, 1, 1}, + } +}; + +/* f(x) = (150x + 50) / 10 */ +static const struct polynomial poly_total_divider = { + .total_divider = 10, + .terms = { + {1, 150, 1, 1}, + {0, 50, 1, 1}, + } +}; + +/* + * f(x) = x / 2 + * divider=2 applied once per multiply: mult_frac(coef, data, 2) = coef*data/2 + */ +static const struct polynomial poly_step_divider = { + .total_divider = 1, + .terms = { + {1, 1, 2, 1}, + {0, 0, 1, 1}, + } +}; + +/* + * f(x) = (100/500) * x^2 = 0.2 * x^2 + * Encoded as coef=100, divider=10, divider_leftover=5: + * denom = 10^2 * 5 = 500 + */ +static const struct polynomial poly_leftover = { + .total_divider = 1, + .terms = { + {2, 100, 10, 5}, + {0, 0, 1, 1}, + } +}; + +/* + * f(x) = 2x^3 (single high-degree term, no constant) + * Used to exercise the power loop alone. + */ +static const struct polynomial poly_cubic = { + .total_divider = 1, + .terms = { + {3, 2, 1, 1}, + {0, 0, 1, 1}, + } +}; + +/* + * f(x) = 4x + 1 with a zero-coefficient quadratic term. + * The deg-2 term contributes nothing regardless of input. + */ +static const struct polynomial poly_zero_coef = { + .total_divider = 1, + .terms = { + {2, 0, 1, 1}, + {1, 4, 1, 1}, + {0, 1, 1, 1}, + } +}; + +/* + * f(x) = 9 with total_divider = 0. + * The implementation treats 0 as 1 via `total_divider ?: 1`, so the + * result must equal the constant term unchanged. + */ +static const struct polynomial poly_zero_total_divider = { + .total_divider = 0, + .terms = { + {0, 9, 1, 1}, + } +}; + + +static const struct polynomial_test_param test_params[] = { + { + .poly = &poly_constant, + .data = 0, + .expected = 5, + .name = "Constant polynomial at x=0", + }, + { + .poly = &poly_constant, + .data = 42, + .expected = 5, + .name = "Constant polynomial is independent of input", + }, + { + .poly = &poly_simple, + .data = 0, + .expected = 5, /* zero input collapses all power terms */ + .name = "Zero input yields constant term only", + }, + { + .poly = &poly_simple, + .data = 10, + .expected = 235, /* 2*100 + 3*10 + 5 */ + .name = "Simple quadratic at x=10", + }, + { + .poly = &poly_negative_coef, + .data = 10, + .expected = 50, /* -5*10 + 100 */ + .name = "Negative coefficient at x=10", + }, + { + .poly = &poly_negative_coef, + .data = 20, + .expected = 0, /* -5*20 + 100 = 0 */ + .name = "Negative coefficient result is zero", + }, + { + .poly = &poly_total_divider, + .data = 3, + .expected = 50, /* (150*3 + 50) / 10 = 500/10 */ + .name = "total_divider scales the final sum", + }, + { + .poly = &poly_step_divider, + .data = 100, + .expected = 50, /* 1*100/2 */ + .name = "Per-step divider halves input", + }, + { + .poly = &poly_leftover, + .data = 30, + .expected = 180, /* 100*30^2 / (10^2 * 5) = 90000/500 */ + .name = "divider_leftover with quadratic term", + }, + /* Boundary: unit and negative-unit input */ + { + /* + * data=1: each mult_frac(tmp, 1, divider) strips one factor of + * divider from coef per degree, so coef is left-shifted right + * until intermediate precision is exhausted. + * 2*1 + 3*1 + 5 = 10 + */ + .poly = &poly_simple, + .data = 1, + .expected = 10, + .name = "Boundary: data=1 (unit input)", + }, + { + /* + * data=-1: even degrees produce positive contributions, + * odd degrees produce negative ones. + * 2*(-1)^2 + 3*(-1) + 5 = 2 - 3 + 5 = 4 + */ + .poly = &poly_simple, + .data = -1, + .expected = 4, + .name = "Boundary: data=-1 (negative unit input)", + }, + + /* Boundary: negative non-trivial input */ + { + /* + * 2*(-3)^2 + 3*(-3) + 5 = 18 - 9 + 5 = 14 + * Verifies sign handling for negative data across all degrees. + */ + .poly = &poly_simple, + .data = -3, + .expected = 14, + .name = "Boundary: negative data with quadratic", + }, + + /* Boundary: total_divider = 0 is treated as 1 */ + { + .poly = &poly_zero_total_divider, + .data = 42, + .expected = 9, + .name = "Boundary: total_divider=0 defaults to 1", + }, + + /* Boundary: zero-coefficient high-degree term */ + { + /* + * The deg-2 term has coef=0, so it contributes 0 regardless + * of data. Result: 0 + 4*10 + 1 = 41 + */ + .poly = &poly_zero_coef, + .data = 10, + .expected = 41, + .name = "Boundary: zero-coefficient term is inert", + }, + + /* Boundary: single high-degree term, no constant */ + { + /* 2 * 5^3 = 250; also verifies the loop terminates on deg-0 */ + .poly = &poly_cubic, + .data = 5, + .expected = 250, + .name = "Boundary: single cubic term", + }, + { + /* 2 * (-2)^3 = -16; odd power preserves sign of negative data */ + .poly = &poly_cubic, + .data = -2, + .expected = -16, + .name = "Boundary: single cubic term, negative data", + }, + +}; + +static void get_desc(const struct polynomial_test_param *param, char *desc) +{ + strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(polynomial, test_params, get_desc); + +static void polynomial_calc_test(struct kunit *test) +{ + const struct polynomial_test_param *param = test->param_value; + + KUNIT_EXPECT_EQ(test, polynomial_calc(param->poly, param->data), + param->expected); +} + +static struct kunit_case polynomial_test_cases[] = { + KUNIT_CASE_PARAM(polynomial_calc_test, polynomial_gen_params), + {} +}; + +static struct kunit_suite polynomial_test_suite = { + .name = "math-polynomial", + .test_cases = polynomial_test_cases, +}; + +kunit_test_suites(&polynomial_test_suite); + +MODULE_DESCRIPTION("math.polynomial_calc KUnit test suite"); +MODULE_LICENSE("GPL"); From 64d9183203eebe33de6188b70a8c1e91f52885db Mon Sep 17 00:00:00 2001 From: Yemu Lu Date: Mon, 25 May 2026 16:56:49 +0800 Subject: [PATCH 11/95] fat: restore original value when fat_ent_write failed fat_ent_write() may have committed the new link to the primary FAT but then failed on the mirror copy, leaving the chain pointing to new_dclus even though the caller will free it. Restore the original value to keep the chain consistent. Link: https://lore.kernel.org/20260525085649.781643-1-n05ec@lzu.edu.cn Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Yemu Lu Signed-off-by: Ren Wei Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Acked-by: OGAWA Hirofumi Cc: Christian Brauner Cc: Signed-off-by: Andrew Morton --- fs/fat/misc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/fat/misc.c b/fs/fat/misc.c index 3027ef53af21..2a0fea26a99a 100644 --- a/fs/fat/misc.c +++ b/fs/fat/misc.c @@ -133,7 +133,11 @@ 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); fatent_brelse(&fatent); } if (ret < 0) From b7a8d7589a632c7de115362bbec6d88b10f685f1 Mon Sep 17 00:00:00 2001 From: Andrea Calabrese Date: Wed, 20 May 2026 08:28:50 +0200 Subject: [PATCH 12/95] kernel: refactor: shorten has_pending_signals In has_pending_signals there was a switch/case used for optimizations. However, today's compilers perform loop unrolling efficiently, thus it is not needed anymore. Put i inside the for declaration so we do not risk its escape from the scope. Moreover, i starts now from 0 and counts up, as it is a more usual pattern. Link: https://lore.kernel.org/20260520062849.183621-2-andrea.calabrese@amarulasolutions.com Signed-off-by: Andrea Calabrese Acked-by: Oleg Nesterov Cc: Adrian Huang Cc: Christian Brauner Cc: Marco Elver Cc: Peter Zijlstra Signed-off-by: Andrew Morton --- kernel/signal.c | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/kernel/signal.c b/kernel/signal.c index 0f509ee4f42d..fdee0b012a11 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -130,28 +130,10 @@ static bool sig_ignored(struct task_struct *t, int sig, bool force) */ static inline bool has_pending_signals(sigset_t *signal, sigset_t *blocked) { - unsigned long ready; - long i; - - switch (_NSIG_WORDS) { - default: - for (i = _NSIG_WORDS, ready = 0; --i >= 0 ;) - ready |= signal->sig[i] &~ blocked->sig[i]; - break; - - case 4: ready = signal->sig[3] &~ blocked->sig[3]; - ready |= signal->sig[2] &~ blocked->sig[2]; - ready |= signal->sig[1] &~ blocked->sig[1]; - ready |= signal->sig[0] &~ blocked->sig[0]; - break; - - case 2: ready = signal->sig[1] &~ blocked->sig[1]; - ready |= signal->sig[0] &~ blocked->sig[0]; - break; - - case 1: ready = signal->sig[0] &~ blocked->sig[0]; - } - return ready != 0; + unsigned long ready = 0; + for (long i = 0; i < _NSIG_WORDS; i++) + ready |= signal->sig[i] & ~blocked->sig[i]; + return ready != 0; } #define PENDING(p,b) has_pending_signals(&(p)->signal, (b)) From 997b3de37c268f75d75d7d1f2c31ab9cf2626dea Mon Sep 17 00:00:00 2001 From: Sara Sena Date: Tue, 30 Jun 2026 22:11:53 -0300 Subject: [PATCH 13/95] tools/accounting/delaytop.c: fix typo in PSI header string Link: https://lore.kernel.org/20260701011153.93426-1-sarasena.adr@gmail.com Signed-off-by: Sara Sena Cc: Fan Yu Cc: Wang Yaxin Signed-off-by: Andrew Morton --- tools/accounting/delaytop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index 72cc500b44b1..0a863dfeae8a 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -878,7 +878,7 @@ static void display_results(int psi_ret) suc &= BOOL_FPRINT(out, "\033[H\033[J"); /* PSI output (one-line, no cat style) */ - suc &= BOOL_FPRINT(out, "System Pressure Information: (avg10/avg60vg300/total)\n"); + suc &= BOOL_FPRINT(out, "System Pressure Information: (avg10/avg60/avg300/total)\n"); if (psi_ret) { suc &= BOOL_FPRINT(out, " PSI not found: check if psi=1 enabled in cmdline\n"); } else { From 8700a4761beb219873956666cf91776a2c61e698 Mon Sep 17 00:00:00 2001 From: "Joy H.J. Lee" Date: Thu, 2 Jul 2026 05:06:35 +0900 Subject: [PATCH 14/95] tools/compiler: match glibc 2.42 definition of __attribute_const__ glibc 2.42 added __attribute_const__ to sys/cdefs.h: # define __attribute_const__ __attribute__ ((__const__)) GCC 15 warns when a macro is redefined to a different replacement list (-Wbuiltin-macro-redefined). Since host tool Makefiles (resolve_btfids, objtool) pass -Werror, this conflict becomes fatal. The warning is suppressed on standard native builds because GCC treats /usr/include as a system header path (-isystem), and macro-redefinition warnings from system headers are silently suppressed by GCC. It fires when glibc headers are on a regular include path (-I) instead, which is the case in cross-compilation setups such as NixOS, where the sysroot's glibc is passed explicitly via -I rather than -isystem. Per (C11 6.10.3), identical replacement lists are accepted silently. Match the glibc definition exactly, including the space before "((", so the redefinition is accepted without warning regardless of whether glibc headers are treated as system or non-system includes. Link: https://lore.kernel.org/20260701200635.3992767-1-rkr0k0r@gmail.com Signed-off-by: Joy H.J. Lee Cc: Nathan Chancellor Cc: David Laight Cc: Signed-off-by: Andrew Morton --- tools/include/linux/compiler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/linux/compiler.h b/tools/include/linux/compiler.h index f40bd2b04c29..f2f54b038168 100644 --- a/tools/include/linux/compiler.h +++ b/tools/include/linux/compiler.h @@ -119,7 +119,7 @@ #define __read_mostly #ifndef __attribute_const__ -# define __attribute_const__ +# define __attribute_const__ __attribute__ ((__const__)) #endif #ifndef __maybe_unused From ea5b5609305a8437bc955a0834a530c12246d78f Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 29 Jun 2026 00:01:43 -0500 Subject: [PATCH 15/95] ocfs2: bound namelen in dlm_migrate_request_handler Patch series "ocfs2/dlm: bound peer-controlled lengths in the o2dlm". The o2dlm receive handlers trust u8 length and count fields from the wire without bounding them, so a node in a DLM domain can corrupt or panic any other node with a malformed message. Three defects: - dlm_migrate_request_handler() passes migrate->namelen unchecked to dlm_init_mle(), which memcpy()s it into the 32-byte mname[] of an o2dlm_mle slab object: a heap out-of-bounds write of up to ~215 attacker-controlled bytes. - dlm_mig_lockres_handler() passes mres->lockname_len unchecked to dlm_init_lockres(), which memcpy()s it into the 32-byte o2dlm_lockname slab object: a heap out-of-bounds write of up to ~223 bytes. - the same handler trusts mres->num_locks without checking that the message is large enough to hold that many entries, so dlm_process_recovery_data() walks mres->ml[] past the kmalloc(data_len) copy and trips a BUG_ON (an out-of-bounds read ending in a panic). The other o2dlm receive handlers already reject an oversized name; the migration and recovery handlers have omitted it since the DLM was added (see the Fixes tags). Patch 1 bounds namelen; patch 2 validates lockname_len, num_locks, and the payload size. Conforming recovery and migration traffic is unaffected. o2net authenticates peers only by the DLM domain key, so any node that has joined the domain -- including a compromised or malicious member -- can send these messages. There is no local trigger; the attacker must already be a member of the cluster. Each sink was confirmed under KASAN with an out-of-tree module mirroring it exactly -- a kmem_cache/kmalloc of the real destination size, then the same unclamped memcpy/loop: slab-out-of-bounds Write for the two writes, Read for the recovery walk, and a panic. A userspace AddressSanitizer build faults identically under -m32 and -m64. Scrubbed logs are available on request. I reported this privately to security@kernel.org and the ocfs2 maintainers on 2026-06-20; with no response after the standard embargo period I am posting the fix publicly. I have no embargo requirement. This patch (of 2): A node receiving a DLM_MIGRATE_REQUEST message trusts the peer-supplied name length (migrate->namelen) without bounding it. dlm_init_mle() then copies that many bytes into the fixed DLM_LOCKID_NAME_MAX-byte mname[] array of an o2dlm_mle slab object, so a malformed message from a cluster peer overflows the slab object by up to ~215 bytes: a heap out-of-bounds write of attacker-controlled data, reachable by any node in the domain. Reject an oversized name, the way dlm_master_request_handler() and the other o2dlm receive handlers already do; the migration handler omits the check entirely. Conforming messages are unaffected. Link: https://lore.kernel.org/20260629-b4-disp-94fb6521-v1-0-6953bcc0421f@proton.me Link: https://lore.kernel.org/20260629-b4-disp-94fb6521-v1-1-6953bcc0421f@proton.me Fixes: 6714d8e86bf4 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Bryam Vargas Reviewed-by: Joseph Qi Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/dlm/dlmmaster.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/ocfs2/dlm/dlmmaster.c b/fs/ocfs2/dlm/dlmmaster.c index aee3b4c56dcc..612969867ff9 100644 --- a/fs/ocfs2/dlm/dlmmaster.c +++ b/fs/ocfs2/dlm/dlmmaster.c @@ -3099,6 +3099,12 @@ int dlm_migrate_request_handler(struct o2net_msg *msg, u32 len, void *data, name = migrate->name; namelen = migrate->namelen; + if (namelen > DLM_LOCKID_NAME_MAX) { + mlog(ML_ERROR, "%s: invalid name length %u in migrate request\n", + dlm->name, namelen); + ret = -EINVAL; + goto leave; + } hash = dlm_lockid_hash(name, namelen); /* preallocate.. if this fails, abort */ From b54e03d9b3697d25f4a0063cf717d459c5e3ad94 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 29 Jun 2026 00:01:44 -0500 Subject: [PATCH 16/95] ocfs2: validate lengths in dlm_mig_lockres_handler A node receiving a DLM_MIG_LOCKRES message trusts several fields of the peer-supplied dlm_migratable_lockres without validation. num_locks and lockname_len are bounded only on the sending side, and the message is never checked to actually carry num_locks migratable_lock entries. As a result dlm_process_recovery_data() walks mres->ml[0..num_locks) past the kmalloc(data_len) copy of the message (an out-of-bounds read that ends in a BUG_ON panic), and dlm_init_lockres() copies lockname_len bytes into the fixed 32-byte o2dlm_lockname slab object (a heap out-of-bounds write). Both are reachable by any node in the domain. Validate these fields right after dlm_grab(), before anything uses them -- including the not-joined error path, which already prints mres->lockname with the unbounded lockname_len as a %.*s precision. Reject the message unless lockname_len <= DLM_LOCKID_NAME_MAX, num_locks <= DLM_MAX_MIGRATABLE_LOCKS (the bound the sender already asserts), and the payload is large enough to hold the claimed locks. Conforming recovery and migration messages are unaffected. Link: https://lore.kernel.org/20260629-b4-disp-94fb6521-v1-2-6953bcc0421f@proton.me Fixes: 6714d8e86bf4 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Bryam Vargas Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/dlm/dlmrecovery.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/ocfs2/dlm/dlmrecovery.c b/fs/ocfs2/dlm/dlmrecovery.c index 9b97bf73df22..9d4a2695b959 100644 --- a/fs/ocfs2/dlm/dlmrecovery.c +++ b/fs/ocfs2/dlm/dlmrecovery.c @@ -1357,6 +1357,15 @@ int dlm_mig_lockres_handler(struct o2net_msg *msg, u32 len, void *data, if (!dlm_grab(dlm)) return -EINVAL; + if (mres->lockname_len > DLM_LOCKID_NAME_MAX || + mres->num_locks > DLM_MAX_MIGRATABLE_LOCKS || + be16_to_cpu(msg->data_len) < struct_size(mres, ml, mres->num_locks)) { + mlog(ML_ERROR, "%s: invalid lockres migration message from %u\n", + dlm->name, mres->master); + dlm_put(dlm); + return -EINVAL; + } + if (!dlm_joined(dlm)) { mlog(ML_ERROR, "Domain %s not joined! " "lockres %.*s, master %u\n", From 8fdcbb5b3744a3d5fef632b5e68e28271cb4fca3 Mon Sep 17 00:00:00 2001 From: Jiaming Zhang Date: Thu, 2 Jul 2026 17:05:07 +0800 Subject: [PATCH 17/95] ocfs2: fix hung task in orphan recovery A crafted OCFS2 image with corrupted orphan-directory extent metadata can make umount hang. During unmount, ocfs2_recovery_disable() waits for the ocfs2_complete_recovery work item to finish. The worker scans the orphan directory through ocfs2_queue_orphans() and ocfs2_dir_foreach(). If ocfs2_read_dir_block() fails on a corrupted directory block, ocfs2_dir_foreach_blk_el() skips the block and continues walking. On a badly corrupted directory this can keep orphan recovery busy for a long time, leaving umount blocked while flushing osb->ocfs2_wq. Return the read error immediately for full directory scans and propagate the error from ocfs2_dir_foreach(). When ocfs2_empty_dir() receives such an error, report the directory as non-empty so unlink/rmdir does not proceed on an unreadable directory. Link: https://lore.kernel.org/20260702090507.446517-1-r772577952@gmail.com Closes: https://lore.kernel.org/lkml/CANypQFbWH76Y6LWHEwAvTP7aQL04uMJ=dDyL6YDmxa3fv3Tyjg@mail.gmail.com/ Assisted-by: Codex:gpt-5.5-xhigh Signed-off-by: Jiaming Zhang Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/dir.c | 20 ++++++++++++++------ fs/ocfs2/namei.c | 11 ++++++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index d7fc3cccf2f4..047d1b486f4a 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -1867,6 +1867,7 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode, struct super_block * sb = inode->i_sb; unsigned int ra_sectors = 16; int stored = 0; + int ret; bh = NULL; @@ -1874,9 +1875,13 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode, while (ctx->pos < i_size_read(inode)) { blk = ctx->pos >> sb->s_blocksize_bits; - if (ocfs2_read_dir_block(inode, blk, &bh, 0)) { + ret = ocfs2_read_dir_block(inode, blk, &bh, 0); + if (ret) { + if (persist) + return ret; /* Skip the corrupt dirblock and keep trying */ ctx->pos += sb->s_blocksize - offset; + offset = 0; continue; } @@ -1970,8 +1975,7 @@ static int ocfs2_dir_foreach_blk(struct inode *inode, u64 *f_version, int ocfs2_dir_foreach(struct inode *inode, struct dir_context *ctx) { u64 version = inode_query_iversion(inode); - ocfs2_dir_foreach_blk(inode, &version, ctx, true); - return 0; + return ocfs2_dir_foreach_blk(inode, &version, ctx, true); } /* @@ -2168,7 +2172,7 @@ static int ocfs2_empty_dir_dx(struct inode *inode, /* * routine to check that the specified directory is empty (for rmdir) * - * Returns 1 if dir is empty, zero otherwise. + * Returns 1 if dir is empty, zero if not, and a negative errno on error. * * XXX: This is a performance problem for unindexed directories. */ @@ -2181,8 +2185,10 @@ int ocfs2_empty_dir(struct inode *inode) if (ocfs2_dir_indexed(inode)) { ret = ocfs2_empty_dir_dx(inode, &priv); - if (ret) + if (ret) { mlog_errno(ret); + return ret; + } /* * We still run ocfs2_dir_foreach to get the checks * for "." and "..". @@ -2190,8 +2196,10 @@ int ocfs2_empty_dir(struct inode *inode) } ret = ocfs2_dir_foreach(inode, &priv.ctx); - if (ret) + if (ret) { mlog_errno(ret); + return ret; + } if (!priv.seen_dot || !priv.seen_dot_dot) { mlog(ML_ERROR, "bad directory (dir #%llu) - no `.' or `..'\n", diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 4cfd7b3d3e1a..8368a7f3d4a2 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -945,7 +945,10 @@ static int ocfs2_unlink(struct inode *dir, child_locked = 1; if (S_ISDIR(inode->i_mode)) { - if (inode->i_nlink != 2 || !ocfs2_empty_dir(inode)) { + status = ocfs2_empty_dir(inode); + if (status < 0) + goto leave; + if (inode->i_nlink != 2 || !status) { status = -ENOTEMPTY; goto leave; } @@ -1499,8 +1502,10 @@ static int ocfs2_rename(struct mnt_idmap *idmap, if (target_exists) { if (S_ISDIR(new_inode->i_mode)) { - if (new_inode->i_nlink != 2 || - !ocfs2_empty_dir(new_inode)) { + status = ocfs2_empty_dir(new_inode); + if (status < 0) + goto bail; + if (new_inode->i_nlink != 2 || !status) { status = -ENOTEMPTY; goto bail; } From 93781560b2fdd26fa8499d64db8a95a07e1dc902 Mon Sep 17 00:00:00 2001 From: Michael Byczkowski Date: Mon, 1 Jun 2026 17:44:09 -0700 Subject: [PATCH 18/95] pps: pps-gpio: split IRQ handler into hardirq timestamper + threaded handler Split the pps-gpio interrupt handler into a primary (hardirq) handler that captures the PPS timestamp at interrupt entry, and a threaded handler that processes the event. This produces the same two-part handler structure on both PREEMPT_RT and non-RT kernels. On non-RT kernels the threaded portion runs immediately after the primary, with no behavioral change compared to the previous single-handler implementation. On PREEMPT_RT, where interrupt handlers are force-threaded by default, the previous single-handler implementation captured the timestamp inside the threaded portion, after IRQ-thread scheduling delay. With the split, the timestamp is captured in true hardirq context as it is on non-RT kernels, eliminating a significant source of PPS jitter on RT systems. Link: https://lore.kernel.org/2e32729029fbf6977ecf04665eb00f2efd3e2c17.1780359378.git.calvin@wbinvd.org Signed-off-by: Michael Byczkowski Signed-off-by: Calvin Owens Reviewed-by: Sebastian Andrzej Siewior Tested-by: Michael Byczkowski Tested-by: Calvin Owens Acked-by: Rodolfo Giometti Signed-off-by: Andrew Morton --- drivers/pps/clients/pps-gpio.c | 39 ++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c index 402f910f3e25..7d87481280b2 100644 --- a/drivers/pps/clients/pps-gpio.c +++ b/drivers/pps/clients/pps-gpio.c @@ -34,33 +34,44 @@ struct pps_gpio_device_data { bool capture_clear; unsigned int echo_active_ms; /* PPS echo active duration */ unsigned long echo_timeout; /* timer timeout value in jiffies */ + struct pps_event_time ts; /* timestamp captured in hardirq */ }; /* * Report the PPS event */ -static irqreturn_t pps_gpio_irq_handler(int irq, void *data) +/* + * Primary hardirq handler -- runs in hardirq context even on PREEMPT_RT. + * Only captures the timestamp; all other work is deferred to the thread. + */ +static irqreturn_t pps_gpio_irq_hardirq(int irq, void *data) { - const struct pps_gpio_device_data *info; - struct pps_event_time ts; + struct pps_gpio_device_data *info = data; + + pps_get_ts(&info->ts); + + return IRQ_WAKE_THREAD; +} + +/* + * Threaded handler -- processes the PPS event using the timestamp + * captured in hardirq context above. + */ +static irqreturn_t pps_gpio_irq_thread(int irq, void *data) +{ + struct pps_gpio_device_data *info = data; int rising_edge; - /* Get the time stamp first */ - pps_get_ts(&ts); - - info = data; - - /* Small trick to bypass the check on edge's direction when capture_clear is unset */ rising_edge = info->capture_clear ? gpiod_get_value(info->gpio_pin) : !info->assert_falling_edge; if ((rising_edge && !info->assert_falling_edge) || (!rising_edge && info->assert_falling_edge)) - pps_event(info->pps, &ts, PPS_CAPTUREASSERT, data); + pps_event(info->pps, &info->ts, PPS_CAPTUREASSERT, data); else if (info->capture_clear && ((rising_edge && info->assert_falling_edge) || (!rising_edge && !info->assert_falling_edge))) - pps_event(info->pps, &ts, PPS_CAPTURECLEAR, data); + pps_event(info->pps, &info->ts, PPS_CAPTURECLEAR, data); else dev_warn_ratelimited(&info->pps->dev, "IRQ did not trigger any PPS event\n"); @@ -209,8 +220,10 @@ static int pps_gpio_probe(struct platform_device *pdev) } /* register IRQ interrupt handler */ - ret = request_irq(data->irq, pps_gpio_irq_handler, - get_irqf_trigger_flags(data), data->info.name, data); + ret = request_threaded_irq(data->irq, + pps_gpio_irq_hardirq, pps_gpio_irq_thread, + get_irqf_trigger_flags(data) | IRQF_ONESHOT, + data->info.name, data); if (ret) { pps_unregister_source(data->pps); dev_err(dev, "failed to acquire IRQ %d\n", data->irq); From 108646f7d9ace1521abc09ea863e9c9687c61136 Mon Sep 17 00:00:00 2001 From: Kir Chou Date: Fri, 3 Jul 2026 14:00:59 +0900 Subject: [PATCH 19/95] lib/random32: convert selftest to KUnit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the existing prandom selftest (lib/random32.c) to use the KUnit framework (lib/tests/random32_kunit.c). Unlike typical KUnit tests, this file is directly #included into lib/random32.c. The new test: - Removes the legacy CONFIG_RANDOM32_SELFTEST from lib/random32.c. - Adds CONFIG_PRANDOM_KUNIT_TEST (defaulting to KUNIT_ALL_TESTS). - Moves the test logic to lib/tests/random32_kunit.c. This commit is verified by `./tools/testing/kunit/kunit.py run` with the .kunit/.kunitconfig: CONFIG_KUNIT=y CONFIG_PRANDOM_KUNIT_TEST=y Link: https://lore.kernel.org/20260703050100.23944-1-note351@hotmail.com Signed-off-by: Kir Chou Reviewed-by: David Gow Cc: Brendan Higgins Cc: David S. Miller Cc: Eric Dumazet Cc: Geert Uytterhoeven Cc: Jakub Kacinski Cc: Kuan-Wei Chiu Cc: Paolo Abeni Cc: Simon Horman Cc: Thomas Weißschuh Signed-off-by: Andrew Morton --- lib/Kconfig | 6 -- lib/Kconfig.debug | 9 ++ lib/random32.c | 184 ++----------------------------------- lib/tests/Makefile | 1 + lib/tests/random32_kunit.c | 182 ++++++++++++++++++++++++++++++++++++ 5 files changed, 202 insertions(+), 180 deletions(-) create mode 100644 lib/tests/random32_kunit.c diff --git a/lib/Kconfig b/lib/Kconfig index 55748b68714e..4e6b34c3346d 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -159,12 +159,6 @@ config AUDIT_COMPAT_GENERIC depends on AUDIT_GENERIC && AUDIT_ARCH_COMPAT_GENERIC && COMPAT default y -config RANDOM32_SELFTEST - bool "PRNG perform self test on init" - help - This option enables the 32 bit PRNG library functions to perform a - self test on initialization. - # # compression support is select'ed if needed # diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 311b99790416..009968c44de1 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -3548,6 +3548,15 @@ config GLOB_KUNIT_TEST If unsure, say N +config PRANDOM_KUNIT_TEST + tristate "KUnit test for prandom" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + Enable this option to test the prandom functions at runtime. + + If unsure, say N + endif # RUNTIME_TESTING_MENU config ARCH_USE_MEMTEST diff --git a/lib/random32.c b/lib/random32.c index 24e7acd9343f..dad90219c351 100644 --- a/lib/random32.c +++ b/lib/random32.c @@ -41,6 +41,7 @@ #include #include #include +#include /** * prandom_u32_state - seeded pseudo-random number generator. @@ -92,7 +93,14 @@ void prandom_bytes_state(struct rnd_state *state, void *buf, size_t bytes) } EXPORT_SYMBOL(prandom_bytes_state); -static void prandom_warmup(struct rnd_state *state) +/* + * Only declared here so that it has a prototype when made + * non-static for KUnit testing (avoids -Wmissing-prototypes). + */ +#if IS_ENABLED(CONFIG_KUNIT) +void prandom_warmup(struct rnd_state *state); +#endif +VISIBLE_IF_KUNIT void prandom_warmup(struct rnd_state *state) { /* Calling RNG ten times to satisfy recurrence condition */ prandom_u32_state(state); @@ -106,6 +114,7 @@ static void prandom_warmup(struct rnd_state *state) prandom_u32_state(state); prandom_u32_state(state); } +EXPORT_SYMBOL_IF_KUNIT(prandom_warmup); void prandom_seed_full_state(struct rnd_state __percpu *pcpu_state) { @@ -125,176 +134,3 @@ void prandom_seed_full_state(struct rnd_state __percpu *pcpu_state) } } EXPORT_SYMBOL(prandom_seed_full_state); - -#ifdef CONFIG_RANDOM32_SELFTEST -static struct prandom_test1 { - u32 seed; - u32 result; -} test1[] = { - { 1U, 3484351685U }, - { 2U, 2623130059U }, - { 3U, 3125133893U }, - { 4U, 984847254U }, -}; - -static struct prandom_test2 { - u32 seed; - u32 iteration; - u32 result; -} test2[] = { - /* Test cases against taus113 from GSL library. */ - { 931557656U, 959U, 2975593782U }, - { 1339693295U, 876U, 3887776532U }, - { 1545556285U, 961U, 1615538833U }, - { 601730776U, 723U, 1776162651U }, - { 1027516047U, 687U, 511983079U }, - { 416526298U, 700U, 916156552U }, - { 1395522032U, 652U, 2222063676U }, - { 366221443U, 617U, 2992857763U }, - { 1539836965U, 714U, 3783265725U }, - { 556206671U, 994U, 799626459U }, - { 684907218U, 799U, 367789491U }, - { 2121230701U, 931U, 2115467001U }, - { 1668516451U, 644U, 3620590685U }, - { 768046066U, 883U, 2034077390U }, - { 1989159136U, 833U, 1195767305U }, - { 536585145U, 996U, 3577259204U }, - { 1008129373U, 642U, 1478080776U }, - { 1740775604U, 939U, 1264980372U }, - { 1967883163U, 508U, 10734624U }, - { 1923019697U, 730U, 3821419629U }, - { 442079932U, 560U, 3440032343U }, - { 1961302714U, 845U, 841962572U }, - { 2030205964U, 962U, 1325144227U }, - { 1160407529U, 507U, 240940858U }, - { 635482502U, 779U, 4200489746U }, - { 1252788931U, 699U, 867195434U }, - { 1961817131U, 719U, 668237657U }, - { 1071468216U, 983U, 917876630U }, - { 1281848367U, 932U, 1003100039U }, - { 582537119U, 780U, 1127273778U }, - { 1973672777U, 853U, 1071368872U }, - { 1896756996U, 762U, 1127851055U }, - { 847917054U, 500U, 1717499075U }, - { 1240520510U, 951U, 2849576657U }, - { 1685071682U, 567U, 1961810396U }, - { 1516232129U, 557U, 3173877U }, - { 1208118903U, 612U, 1613145022U }, - { 1817269927U, 693U, 4279122573U }, - { 1510091701U, 717U, 638191229U }, - { 365916850U, 807U, 600424314U }, - { 399324359U, 702U, 1803598116U }, - { 1318480274U, 779U, 2074237022U }, - { 697758115U, 840U, 1483639402U }, - { 1696507773U, 840U, 577415447U }, - { 2081979121U, 981U, 3041486449U }, - { 955646687U, 742U, 3846494357U }, - { 1250683506U, 749U, 836419859U }, - { 595003102U, 534U, 366794109U }, - { 47485338U, 558U, 3521120834U }, - { 619433479U, 610U, 3991783875U }, - { 704096520U, 518U, 4139493852U }, - { 1712224984U, 606U, 2393312003U }, - { 1318233152U, 922U, 3880361134U }, - { 855572992U, 761U, 1472974787U }, - { 64721421U, 703U, 683860550U }, - { 678931758U, 840U, 380616043U }, - { 692711973U, 778U, 1382361947U }, - { 677703619U, 530U, 2826914161U }, - { 92393223U, 586U, 1522128471U }, - { 1222592920U, 743U, 3466726667U }, - { 358288986U, 695U, 1091956998U }, - { 1935056945U, 958U, 514864477U }, - { 735675993U, 990U, 1294239989U }, - { 1560089402U, 897U, 2238551287U }, - { 70616361U, 829U, 22483098U }, - { 368234700U, 731U, 2913875084U }, - { 20221190U, 879U, 1564152970U }, - { 539444654U, 682U, 1835141259U }, - { 1314987297U, 840U, 1801114136U }, - { 2019295544U, 645U, 3286438930U }, - { 469023838U, 716U, 1637918202U }, - { 1843754496U, 653U, 2562092152U }, - { 400672036U, 809U, 4264212785U }, - { 404722249U, 965U, 2704116999U }, - { 600702209U, 758U, 584979986U }, - { 519953954U, 667U, 2574436237U }, - { 1658071126U, 694U, 2214569490U }, - { 420480037U, 749U, 3430010866U }, - { 690103647U, 969U, 3700758083U }, - { 1029424799U, 937U, 3787746841U }, - { 2012608669U, 506U, 3362628973U }, - { 1535432887U, 998U, 42610943U }, - { 1330635533U, 857U, 3040806504U }, - { 1223800550U, 539U, 3954229517U }, - { 1322411537U, 680U, 3223250324U }, - { 1877847898U, 945U, 2915147143U }, - { 1646356099U, 874U, 965988280U }, - { 805687536U, 744U, 4032277920U }, - { 1948093210U, 633U, 1346597684U }, - { 392609744U, 783U, 1636083295U }, - { 690241304U, 770U, 1201031298U }, - { 1360302965U, 696U, 1665394461U }, - { 1220090946U, 780U, 1316922812U }, - { 447092251U, 500U, 3438743375U }, - { 1613868791U, 592U, 828546883U }, - { 523430951U, 548U, 2552392304U }, - { 726692899U, 810U, 1656872867U }, - { 1364340021U, 836U, 3710513486U }, - { 1986257729U, 931U, 935013962U }, - { 407983964U, 921U, 728767059U }, -}; - -static void prandom_state_selftest_seed(struct rnd_state *state, u32 seed) -{ -#define LCG(x) ((x) * 69069U) /* super-duper LCG */ - state->s1 = __seed(LCG(seed), 2U); - state->s2 = __seed(LCG(state->s1), 8U); - state->s3 = __seed(LCG(state->s2), 16U); - state->s4 = __seed(LCG(state->s3), 128U); -} - -static int __init prandom_state_selftest(void) -{ - int i, j, errors = 0, runs = 0; - bool error = false; - - for (i = 0; i < ARRAY_SIZE(test1); i++) { - struct rnd_state state; - - prandom_state_selftest_seed(&state, test1[i].seed); - prandom_warmup(&state); - - if (test1[i].result != prandom_u32_state(&state)) - error = true; - } - - if (error) - pr_warn("prandom: seed boundary self test failed\n"); - else - pr_info("prandom: seed boundary self test passed\n"); - - for (i = 0; i < ARRAY_SIZE(test2); i++) { - struct rnd_state state; - - prandom_state_selftest_seed(&state, test2[i].seed); - prandom_warmup(&state); - - for (j = 0; j < test2[i].iteration - 1; j++) - prandom_u32_state(&state); - - if (test2[i].result != prandom_u32_state(&state)) - errors++; - - runs++; - cond_resched(); - } - - if (errors) - pr_warn("prandom: %d/%d self tests failed\n", errors, runs); - else - pr_info("prandom: %d self tests passed\n", runs); - return 0; -} -core_initcall(prandom_state_selftest); -#endif diff --git a/lib/tests/Makefile b/lib/tests/Makefile index 4ead57602eac..c045b82169bc 100644 --- a/lib/tests/Makefile +++ b/lib/tests/Makefile @@ -43,6 +43,7 @@ CFLAGS_overflow_kunit.o = $(call cc-disable-warning, tautological-constant-out-o obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_kunit.o # GCC < 12.1 can miscompile errptr() test when branch profiling is enabled. CFLAGS_printf_kunit.o += -DDISABLE_BRANCH_PROFILING +obj-$(CONFIG_PRANDOM_KUNIT_TEST) += random32_kunit.o obj-$(CONFIG_PRINTF_KUNIT_TEST) += printf_kunit.o obj-$(CONFIG_RANDSTRUCT_KUNIT_TEST) += randstruct_kunit.o obj-$(CONFIG_SCANF_KUNIT_TEST) += scanf_kunit.o diff --git a/lib/tests/random32_kunit.c b/lib/tests/random32_kunit.c new file mode 100644 index 000000000000..0b4af2b09c01 --- /dev/null +++ b/lib/tests/random32_kunit.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test cases for random32 functions. + */ + +#include +#include + +/* prandom_warmup() is static in lib/random32.c; exposed for testing only. */ +void prandom_warmup(struct rnd_state *state); + +static const struct prandom_test1 { + u32 seed; + u32 result; +} test1[] = { + { 1U, 3484351685U }, + { 2U, 2623130059U }, + { 3U, 3125133893U }, + { 4U, 984847254U }, +}; + +static const struct prandom_test2 { + u32 seed; + u32 iteration; + u32 result; +} test2[] = { + /* Test cases against taus113 from GSL library. */ + { 931557656U, 959U, 2975593782U }, + { 1339693295U, 876U, 3887776532U }, + { 1545556285U, 961U, 1615538833U }, + { 601730776U, 723U, 1776162651U }, + { 1027516047U, 687U, 511983079U }, + { 416526298U, 700U, 916156552U }, + { 1395522032U, 652U, 2222063676U }, + { 366221443U, 617U, 2992857763U }, + { 1539836965U, 714U, 3783265725U }, + { 556206671U, 994U, 799626459U }, + { 684907218U, 799U, 367789491U }, + { 2121230701U, 931U, 2115467001U }, + { 1668516451U, 644U, 3620590685U }, + { 768046066U, 883U, 2034077390U }, + { 1989159136U, 833U, 1195767305U }, + { 536585145U, 996U, 3577259204U }, + { 1008129373U, 642U, 1478080776U }, + { 1740775604U, 939U, 1264980372U }, + { 1967883163U, 508U, 10734624U }, + { 1923019697U, 730U, 3821419629U }, + { 442079932U, 560U, 3440032343U }, + { 1961302714U, 845U, 841962572U }, + { 2030205964U, 962U, 1325144227U }, + { 1160407529U, 507U, 240940858U }, + { 635482502U, 779U, 4200489746U }, + { 1252788931U, 699U, 867195434U }, + { 1961817131U, 719U, 668237657U }, + { 1071468216U, 983U, 917876630U }, + { 1281848367U, 932U, 1003100039U }, + { 582537119U, 780U, 1127273778U }, + { 1973672777U, 853U, 1071368872U }, + { 1896756996U, 762U, 1127851055U }, + { 847917054U, 500U, 1717499075U }, + { 1240520510U, 951U, 2849576657U }, + { 1685071682U, 567U, 1961810396U }, + { 1516232129U, 557U, 3173877U }, + { 1208118903U, 612U, 1613145022U }, + { 1817269927U, 693U, 4279122573U }, + { 1510091701U, 717U, 638191229U }, + { 365916850U, 807U, 600424314U }, + { 399324359U, 702U, 1803598116U }, + { 1318480274U, 779U, 2074237022U }, + { 697758115U, 840U, 1483639402U }, + { 1696507773U, 840U, 577415447U }, + { 2081979121U, 981U, 3041486449U }, + { 955646687U, 742U, 3846494357U }, + { 1250683506U, 749U, 836419859U }, + { 595003102U, 534U, 366794109U }, + { 47485338U, 558U, 3521120834U }, + { 619433479U, 610U, 3991783875U }, + { 704096520U, 518U, 4139493852U }, + { 1712224984U, 606U, 2393312003U }, + { 1318233152U, 922U, 3880361134U }, + { 855572992U, 761U, 1472974787U }, + { 64721421U, 703U, 683860550U }, + { 678931758U, 840U, 380616043U }, + { 692711973U, 778U, 1382361947U }, + { 677703619U, 530U, 2826914161U }, + { 92393223U, 586U, 1522128471U }, + { 1222592920U, 743U, 3466726667U }, + { 358288986U, 695U, 1091956998U }, + { 1935056945U, 958U, 514864477U }, + { 735675993U, 990U, 1294239989U }, + { 1560089402U, 897U, 2238551287U }, + { 70616361U, 829U, 22483098U }, + { 368234700U, 731U, 2913875084U }, + { 20221190U, 879U, 1564152970U }, + { 539444654U, 682U, 1835141259U }, + { 1314987297U, 840U, 1801114136U }, + { 2019295544U, 645U, 3286438930U }, + { 469023838U, 716U, 1637918202U }, + { 1843754496U, 653U, 2562092152U }, + { 400672036U, 809U, 4264212785U }, + { 404722249U, 965U, 2704116999U }, + { 600702209U, 758U, 584979986U }, + { 519953954U, 667U, 2574436237U }, + { 1658071126U, 694U, 2214569490U }, + { 420480037U, 749U, 3430010866U }, + { 690103647U, 969U, 3700758083U }, + { 1029424799U, 937U, 3787746841U }, + { 2012608669U, 506U, 3362628973U }, + { 1535432887U, 998U, 42610943U }, + { 1330635533U, 857U, 3040806504U }, + { 1223800550U, 539U, 3954229517U }, + { 1322411537U, 680U, 3223250324U }, + { 1877847898U, 945U, 2915147143U }, + { 1646356099U, 874U, 965988280U }, + { 805687536U, 744U, 4032277920U }, + { 1948093210U, 633U, 1346597684U }, + { 392609744U, 783U, 1636083295U }, + { 690241304U, 770U, 1201031298U }, + { 1360302965U, 696U, 1665394461U }, + { 1220090946U, 780U, 1316922812U }, + { 447092251U, 500U, 3438743375U }, + { 1613868791U, 592U, 828546883U }, + { 523430951U, 548U, 2552392304U }, + { 726692899U, 810U, 1656872867U }, + { 1364340021U, 836U, 3710513486U }, + { 1986257729U, 931U, 935013962U }, + { 407983964U, 921U, 728767059U }, +}; + +static void prandom_state_test_seed(struct rnd_state *state, u32 seed) +{ +#define LCG(x) ((x) * 69069U) /* super-duper LCG */ + state->s1 = __seed(LCG(seed), 2U); + state->s2 = __seed(LCG(state->s1), 8U); + state->s3 = __seed(LCG(state->s2), 16U); + state->s4 = __seed(LCG(state->s3), 128U); +} + +static void test_prandom_seed_boundary(struct kunit *test) +{ + int i; + struct rnd_state state; + + for (i = 0; i < ARRAY_SIZE(test1); i++) { + prandom_state_test_seed(&state, test1[i].seed); + prandom_warmup(&state); + KUNIT_EXPECT_EQ(test, test1[i].result, prandom_u32_state(&state)); + } +} + +static void test_prandom_taus113(struct kunit *test) +{ + int i, j; + struct rnd_state state; + + for (i = 0; i < ARRAY_SIZE(test2); i++) { + prandom_state_test_seed(&state, test2[i].seed); + prandom_warmup(&state); + + for (j = 0; j < test2[i].iteration - 1; j++) + prandom_u32_state(&state); + + KUNIT_EXPECT_EQ(test, test2[i].result, prandom_u32_state(&state)); + } +} + +static struct kunit_case prandom_test_cases[] = { + KUNIT_CASE(test_prandom_seed_boundary), + KUNIT_CASE(test_prandom_taus113), + {} +}; + +static struct kunit_suite prandom_test_suite = { + .name = "prandom", + .test_cases = prandom_test_cases, +}; + +kunit_test_suite(prandom_test_suite); + +MODULE_DESCRIPTION("KUnit test for prandom"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); From 45217e98987a87ff2372386dbf82fd5325db28ea Mon Sep 17 00:00:00 2001 From: Calvin Owens Date: Fri, 12 Jun 2026 11:52:09 -0700 Subject: [PATCH 20/95] pps: don't try to wait for negative timeouts in PPS_FETCH If userspace passes a negative timeout to PPS_FETCH, it triggers a kernel splat from schedule_timeout(): schedule_timeout: wrong timeout value fffffffffff0bfb4 CPU: 17 UID: 0 PID: 4720 Comm: a.out Not tainted 7.1.0-rc5-x86-kvm-00150-g331d97e36b37 #1 PREEMPT_RT Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-20240910_120124-localhost 04/01/2014 Call Trace: dump_stack_lvl+0x4b/0x70 schedule_timeout+0xb7/0xe0 pps_cdev_pps_fetch.isra.0+0x93/0x150 pps_cdev_ioctl+0x70/0x310 __x64_sys_ioctl+0x7b/0xc0 do_syscall_64+0xb6/0xfc0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Here is a trivial reproducer that works with the PPS_CLIENT_KTIMER test device enabled in the kernel: #include #include #include #include #include #include int main() { struct pps_fdata fdata; int fd; fd = open("/dev/pps0", O_RDWR); if (fd == -1) err(1, "Failed to open /dev/pps0"); fdata.timeout.sec = -1; fdata.timeout.nsec = 0; if (ioctl(fd, PPS_FETCH, &fdata)) err(2, "PPS_FETCH failed"); close(fd); return 0; } Sashiko imagines this to be some sort of security problem, which is obviously really silly. But I think it is still worth fixing, so buggy userspace code can't trigger the splat. Silence the splat by using timespec64_to_jiffies(), which hard limits the timeout to LONG_MAX jiffies. To be safe, explicitly preserve the -ETIMEDOUT return value userspace sees today if it passes a negative timeout. If you really squint, this is still a slight behavior change in that there are "denormalized" combinations of tv_sec and tv_nsec which used to work but will now return -ETIMEDOUT. I can't imagine anybody will care about that... Link: https://lore.kernel.org/c5c97c3b3c9d66010382094fd538e59a38f4aacf.1781289959.git.calvin@wbinvd.org Fixes: eae9d2ba0cfc ("LinuxPPS: core support") Signed-off-by: Calvin Owens Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1779733602.git.calvin%40wbinvd.org?part=3 Acked-by: Rodolfo Giometti Cc: Greg Kroah-Hartman Signed-off-by: Andrew Morton --- drivers/pps/pps.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index de1122bb69ea..7ecdd774a44b 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -66,13 +66,19 @@ static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata) err = wait_event_interruptible(pps->queue, ev != pps->last_ev); else { + struct timespec64 ts; unsigned long ticks; dev_dbg(&pps->dev, "timeout %lld.%09d\n", (long long) fdata->timeout.sec, fdata->timeout.nsec); - ticks = fdata->timeout.sec * HZ; - ticks += fdata->timeout.nsec / (NSEC_PER_SEC / HZ); + + if (fdata->timeout.sec < 0) + return -ETIMEDOUT; + + ts.tv_sec = fdata->timeout.sec; + ts.tv_nsec = fdata->timeout.nsec; + ticks = timespec64_to_jiffies(&ts); if (ticks != 0) { err = wait_event_interruptible_timeout( From 3649f9a6b8972636a33dee369a183da217a3179c Mon Sep 17 00:00:00 2001 From: Calvin Owens Date: Wed, 3 Jun 2026 10:25:44 -0700 Subject: [PATCH 21/95] pps: don't allow PPS_KC_BIND on removed devices If userspace holds its file descriptor open, it can call PPS_KC_BIND on a device which has been unplugged, leaving pps_kc_hardpps_dev as a dangling pointer after close(). After that sequence, PPS_KC_BIND is broken until the system is rebooted, because the pointer comparison in pps_kc_bind() can never be true. calling pps_ktimer_init+0x0/0x1000 [pps_ktimer] @ 1081 initcall pps_ktimer_init+0x0/0x1000 [pps_ktimer] returned 0 after 811 usecs pps pps0: bound kernel consumer: edge=0x1 pps pps0: unbound kernel consumer on device removal pps pps0: bound kernel consumer: edge=0x1 calling pps_ktimer_init+0x0/0x1000 [pps_ktimer] @ 1085 initcall pps_ktimer_init+0x0/0x1000 [pps_ktimer] returned 0 after 340 usecs pps pps0: another kernel consumer is already bound Here is a short reproducer, which uses rmmod of the pps-ktimer testcase to simulate a device being unplugged: #include #include #include #include #include #include #include #include int main(void) { while (1) { int fd; if (system("insmod ./pps-ktimer.ko")) err(1, "insmod failed"); fd = open("/dev/pps0", O_RDWR); if (fd == -1) err(1, "open failed"); struct pps_bind_args args = { .tsformat = PPS_TSFMT_TSPEC, .edge = PPS_CAPTUREASSERT, .consumer = PPS_KC_HARDPPS, }; if (ioctl(fd, PPS_KC_BIND, &args)) err(1, "first PPS_KC_BIND failed"); if (system("rmmod pps-ktimer")) err(1, "rmmod failed"); if (ioctl(fd, PPS_KC_BIND, &args)) { if (errno != ENODEV) err(1, "second PPS_KC_BIND failed"); else puts("Got ENODEV, kernel is patched"); } close(fd); } } Fix this by setting a flag when the device is unplugged, returning -ENODEV from PPS_KC_BIND if the flag is set. For userspace to encounter this new behavior, it must do something which breaks the interface today, so this fix shouldn't cause any observable behavior change for working programs. Link: https://lore.kernel.org/672778c177ac9b6fdcb445e35c97ac4ca7d1149f.1780506611.git.calvin@wbinvd.org Signed-off-by: Calvin Owens Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/cover.1779733602.git.calvin%40wbinvd.org?part=1 Acked-by: Rodolfo Giometti Cc: Greg Kroah-Hartman Signed-off-by: Andrew Morton --- drivers/pps/kc.c | 10 ++++++++++ include/linux/pps_kernel.h | 1 + 2 files changed, 11 insertions(+) diff --git a/drivers/pps/kc.c b/drivers/pps/kc.c index fbd23295afd7..4f8fffa7edd6 100644 --- a/drivers/pps/kc.c +++ b/drivers/pps/kc.c @@ -38,6 +38,14 @@ int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args) /* Check if another consumer is already bound */ spin_lock_irq(&pps_kc_hardpps_lock); + /* + * Don't allow PPS_KC_BIND on a removed device. + */ + if (pps->kc_removed) { + spin_unlock_irq(&pps_kc_hardpps_lock); + return -ENODEV; + } + if (bind_args->edge == 0) if (pps_kc_hardpps_dev == pps) { pps_kc_hardpps_mode = 0; @@ -79,6 +87,8 @@ int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args) void pps_kc_remove(struct pps_device *pps) { spin_lock_irq(&pps_kc_hardpps_lock); + + pps->kc_removed = true; if (pps == pps_kc_hardpps_dev) { pps_kc_hardpps_mode = 0; pps_kc_hardpps_dev = NULL; diff --git a/include/linux/pps_kernel.h b/include/linux/pps_kernel.h index 9f088c9023b1..00b840970d56 100644 --- a/include/linux/pps_kernel.h +++ b/include/linux/pps_kernel.h @@ -60,6 +60,7 @@ struct pps_device { struct device dev; struct fasync_struct *async_queue; /* fasync method */ spinlock_t lock; + bool kc_removed; }; /* From b899e0279f90c3ce4099d68b989dd27861cc5c4f Mon Sep 17 00:00:00 2001 From: Calvin Owens Date: Mon, 6 Jul 2026 10:19:07 -0700 Subject: [PATCH 22/95] pps-gpio: remove dead capture_clear code The capture_clear field is never set, and all code conditional on it being set has been unreachable since the platform data logic was removed from pps-gpio in ee89646619ba ("pps: clients: gpio: Get rid of legacy platform data"). I think the only logical thing to do here is to remove it all, since no in-tree code ever actually used it in the first place, and it has been completely dead code for over five years (since v5.13). Sashiko asked some questions about the gpiod_get_value() call which caused me to look deeper and figure this out, but it did not actually notice capture_clear is never set. Link: https://lore.kernel.org/f70196bafcf75d9782dd36ed784e42345b6e8a1b.1783355507.git.calvin@wbinvd.org Fixes: ee89646619ba ("pps: clients: gpio: Get rid of legacy platform data") Signed-off-by: Calvin Owens Closes: https://sashiko.dev/#/patchset/cover.1779733602.git.calvin%40wbinvd.org?part=1 Acked-by: Rodolfo Giometti Signed-off-by: Andrew Morton --- drivers/pps/clients/pps-gpio.c | 35 +++------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c index 7d87481280b2..73ec2c7335e5 100644 --- a/drivers/pps/clients/pps-gpio.c +++ b/drivers/pps/clients/pps-gpio.c @@ -31,7 +31,6 @@ struct pps_gpio_device_data { struct gpio_desc *echo_pin; struct timer_list echo_timer; /* timer to reset echo active state */ bool assert_falling_edge; - bool capture_clear; unsigned int echo_active_ms; /* PPS echo active duration */ unsigned long echo_timeout; /* timer timeout value in jiffies */ struct pps_event_time ts; /* timestamp captured in hardirq */ @@ -61,19 +60,8 @@ static irqreturn_t pps_gpio_irq_hardirq(int irq, void *data) static irqreturn_t pps_gpio_irq_thread(int irq, void *data) { struct pps_gpio_device_data *info = data; - int rising_edge; - rising_edge = info->capture_clear ? - gpiod_get_value(info->gpio_pin) : !info->assert_falling_edge; - if ((rising_edge && !info->assert_falling_edge) || - (!rising_edge && info->assert_falling_edge)) - pps_event(info->pps, &info->ts, PPS_CAPTUREASSERT, data); - else if (info->capture_clear && - ((rising_edge && info->assert_falling_edge) || - (!rising_edge && !info->assert_falling_edge))) - pps_event(info->pps, &info->ts, PPS_CAPTURECLEAR, data); - else - dev_warn_ratelimited(&info->pps->dev, "IRQ did not trigger any PPS event\n"); + pps_event(info->pps, &info->ts, PPS_CAPTUREASSERT, data); return IRQ_HANDLED; } @@ -89,11 +77,6 @@ static void pps_gpio_echo(struct pps_device *pps, int event, void *data) if (pps->params.mode & PPS_ECHOASSERT) gpiod_set_value(info->echo_pin, 1); break; - - case PPS_CAPTURECLEAR: - if (pps->params.mode & PPS_ECHOCLEAR) - gpiod_set_value(info->echo_pin, 1); - break; } /* fire the timer */ @@ -155,15 +138,8 @@ static int pps_gpio_setup(struct device *dev) static unsigned long get_irqf_trigger_flags(const struct pps_gpio_device_data *data) { - unsigned long flags = data->assert_falling_edge ? - IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; - - if (data->capture_clear) { - flags |= ((flags & IRQF_TRIGGER_RISING) ? - IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING); - } - - return flags; + return data->assert_falling_edge ? IRQF_TRIGGER_FALLING : + IRQF_TRIGGER_RISING; } static int pps_gpio_probe(struct platform_device *pdev) @@ -196,9 +172,6 @@ static int pps_gpio_probe(struct platform_device *pdev) /* initialize PPS specific parts of the bookkeeping data structure. */ data->info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC; - if (data->capture_clear) - data->info.mode |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR | - PPS_ECHOCLEAR; data->info.owner = THIS_MODULE; snprintf(data->info.name, PPS_MAX_NAME_LEN - 1, "%s.%d", pdev->name, pdev->id); @@ -210,8 +183,6 @@ static int pps_gpio_probe(struct platform_device *pdev) /* register PPS source */ pps_default_params = PPS_CAPTUREASSERT | PPS_OFFSETASSERT; - if (data->capture_clear) - pps_default_params |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR; data->pps = pps_register_source(&data->info, pps_default_params); if (IS_ERR(data->pps)) { dev_err(dev, "failed to register IRQ %d as PPS source\n", From 8914a3330b72378136c2c02d6328a826f6abdad7 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sun, 5 Jul 2026 10:53:10 +0800 Subject: [PATCH 23/95] ocfs2: validate inline xattrs during inode block validation Patch series "ocfs2: validate xattr entry bounds", v7. This series validates OCFS2 xattr entry name/value bounds when xattr metadata is read and validated, before getxattr() or listxattr() can walk out-of-range entry arrays or offsets from corrupted metadata. This patch (of 2): ocfs2_validate_inode_block() verifies a dinode before OCFS2 users walk metadata from it, but inline xattr metadata is still checked only in operation-specific consumers. The existing ibody lookup helper validates inline header placement and entry count, but inode block validation does not reject entry name/value bounds. Add a flat xattr entry validator and call it from inode block validation for inline xattrs. Keep the operation paths on their existing header/count lookup checks; the full entry bounds check now runs when the inode block is validated at read time. Reject corrupted inline xattr metadata before ocfs2_xattr_ibody_get() or listxattr() can walk past the inline storage. Validation reproduced this kernel report: BUG: KASAN: use-after-free in ocfs2_xattr_find_entry+0x5a/0x170 Read of size 2 at addr ffff8881242a2000 by task python3/529 Call Trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 kasan_report+0xe0/0x110 ocfs2_xattr_find_entry+0x5a/0x170 ocfs2_xattr_get_nolock+0x20a/0x820 ocfs2_xattr_get+0x10c/0x1e0 __vfs_getxattr+0xe2/0x130 vfs_getxattr+0x185/0x1b0 Link: https://lore.kernel.org/20260705025311.3429854-1-zzzccc427@gmail.com Link: https://lore.kernel.org/20260705025311.3429854-2-zzzccc427@gmail.com Fixes: cf1d6c763fbc ("ocfs2: Add extended attribute support") Signed-off-by: Cen Zhang Assisted-by: Codex:gpt-5.5 Reviewed-by: Joseph Qi Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh Signed-off-by: Andrew Morton --- fs/ocfs2/inode.c | 4 ++ fs/ocfs2/xattr.c | 118 +++++++++++++++++++++++++++++++++++++++++------ fs/ocfs2/xattr.h | 2 + 3 files changed, 111 insertions(+), 13 deletions(-) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index 662dbc845b8b..815bf3f659da 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -1608,6 +1608,10 @@ int ocfs2_validate_inode_block(struct super_block *sb, goto bail; } + rc = ocfs2_validate_inode_xattr(sb, bh->b_blocknr, di); + if (rc) + goto bail; + if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) { struct ocfs2_inline_data *data = &di->id2.i_data; diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index fcddd3c13acd..ca76441625db 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -950,41 +950,133 @@ static int ocfs2_xattr_list_entries(struct inode *inode, return result; } -static int ocfs2_xattr_ibody_lookup_header(struct inode *inode, - struct ocfs2_dinode *di, - struct ocfs2_xattr_header **header) +static int ocfs2_validate_xattr_entries_flat(struct super_block *sb, u64 blkno, + struct ocfs2_xattr_header *xh, + size_t region_size) { + u16 xattr_count = le16_to_cpu(xh->xh_count); + size_t entries_limit = region_size; + size_t nv_limit = region_size; + size_t max_entries; + int i; + + if (region_size < sizeof(*xh)) + return ocfs2_error(sb, + "Invalid xattr in block %llu: region size %zu is too small\n", + (unsigned long long)blkno, region_size); + + max_entries = (entries_limit - sizeof(*xh)) / + sizeof(struct ocfs2_xattr_entry); + + if (xattr_count > max_entries) + return ocfs2_error(sb, + "Invalid xattr in block %llu: entry count %u exceeds maximum %zu\n", + (unsigned long long)blkno, + xattr_count, max_entries); + + for (i = 0; i < xattr_count; i++) { + struct ocfs2_xattr_entry *xe = &xh->xh_entries[i]; + size_t name_offset = le16_to_cpu(xe->xe_name_offset); + size_t value_offset; + + if (name_offset > nv_limit || + xe->xe_name_len > nv_limit - name_offset) + return ocfs2_error(sb, + "Invalid xattr in block %llu: entry %d name is out of bounds\n", + (unsigned long long)blkno, i); + + value_offset = name_offset + OCFS2_XATTR_SIZE(xe->xe_name_len); + if (value_offset > nv_limit) + return ocfs2_error(sb, + "Invalid xattr in block %llu: entry %d value starts out of bounds\n", + (unsigned long long)blkno, i); + + if (ocfs2_xattr_is_local(xe)) { + if (le64_to_cpu(xe->xe_value_size) > + 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, + i); + } else if (sizeof(struct ocfs2_xattr_value_root) > + nv_limit - value_offset) { + return ocfs2_error(sb, + "Invalid xattr in block %llu: entry %d value root is out of bounds\n", + (unsigned long long)blkno, i); + } + } + + return 0; +} + +static int ocfs2_xattr_ibody_lookup_header_raw(struct super_block *sb, + u64 blkno, + struct ocfs2_dinode *di, + struct ocfs2_xattr_header **header, + u16 *inline_size_ret) +{ + struct ocfs2_xattr_header *xh; u16 xattr_count; size_t max_entries; u16 inline_size = le16_to_cpu(di->i_xattr_inline_size); - if (inline_size > inode->i_sb->s_blocksize || + if (inline_size > sb->s_blocksize || inline_size < sizeof(struct ocfs2_xattr_header)) { - ocfs2_error(inode->i_sb, - "Invalid xattr inline size %u in inode %llu\n", - inline_size, - (unsigned long long)OCFS2_I(inode)->ip_blkno); + ocfs2_error(sb, + "Invalid inode %llu: xattr inline size %u\n", + (unsigned long long)blkno, inline_size); return -EFSCORRUPTED; } - *header = (struct ocfs2_xattr_header *) - ((void *)di + inode->i_sb->s_blocksize - inline_size); + xh = (struct ocfs2_xattr_header *) + ((void *)di + sb->s_blocksize - inline_size); - xattr_count = le16_to_cpu((*header)->xh_count); + xattr_count = le16_to_cpu(xh->xh_count); max_entries = (inline_size - sizeof(struct ocfs2_xattr_header)) / sizeof(struct ocfs2_xattr_entry); if (xattr_count > max_entries) { - ocfs2_error(inode->i_sb, + ocfs2_error(sb, "xattr entry count %u exceeds maximum %zu in inode %llu\n", xattr_count, max_entries, - (unsigned long long)OCFS2_I(inode)->ip_blkno); + (unsigned long long)blkno); return -EFSCORRUPTED; } + *header = xh; + if (inline_size_ret) + *inline_size_ret = inline_size; + return 0; } +int ocfs2_validate_inode_xattr(struct super_block *sb, u64 blkno, + struct ocfs2_dinode *di) +{ + struct ocfs2_xattr_header *xh; + u16 inline_size; + int ret; + + if (!(le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_XATTR_FL)) + return 0; + + ret = ocfs2_xattr_ibody_lookup_header_raw(sb, blkno, di, &xh, + &inline_size); + if (ret) + return ret; + + return ocfs2_validate_xattr_entries_flat(sb, blkno, xh, inline_size); +} + +static int ocfs2_xattr_ibody_lookup_header(struct inode *inode, + struct ocfs2_dinode *di, + struct ocfs2_xattr_header **header) +{ + return ocfs2_xattr_ibody_lookup_header_raw(inode->i_sb, + OCFS2_I(inode)->ip_blkno, + di, header, NULL); +} + int ocfs2_has_inline_xattr_value_outside(struct inode *inode, struct ocfs2_dinode *di) { diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h index 65e9aa743919..6b7589941315 100644 --- a/fs/ocfs2/xattr.h +++ b/fs/ocfs2/xattr.h @@ -43,6 +43,8 @@ int ocfs2_xattr_set_handle(handle_t *, struct inode *, struct buffer_head *, struct ocfs2_alloc_context *); int ocfs2_has_inline_xattr_value_outside(struct inode *inode, struct ocfs2_dinode *di); +int ocfs2_validate_inode_xattr(struct super_block *sb, u64 blkno, + struct ocfs2_dinode *di); int ocfs2_xattr_remove(struct inode *, struct buffer_head *); int ocfs2_init_security_get(struct inode *, struct inode *, const struct qstr *, From 2cf82b46d5e43be0dfbaac7fa1073cec2fc1f5e6 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sun, 5 Jul 2026 10:53:11 +0800 Subject: [PATCH 24/95] ocfs2: validate external xattr entries when reading metadata ocfs2_validate_xattr_block() checks the xattr block header before the block reaches higher-level xattr users, but it does not verify that a non-indexed block's xh_count and entry offsets fit inside the block. Indexed buckets likewise reach list/get consumers after ECC without an entry-bounds check. Use the flat xattr entry validator for non-indexed external xattr blocks, and use a bucket-specific validator for indexed buckets at metadata read time. The bucket validator keeps the entry array bounded by the first bucket block while checking name/value offsets against the bucket block they target. Reject corrupted external xattr metadata before listxattr() or getxattr() can walk out-of-range entry arrays or name/value offsets. Validation reproduced this kernel report: BUG: KASAN: use-after-free in ocfs2_xattr_list_entries+0xd7/0x190 Read of size 1 at addr ffff88810a654007 by task ocfs2_xattr_lis/630 Call Trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 kasan_report+0xe0/0x110 ocfs2_xattr_list_entries+0xd7/0x190 ocfs2_listxattr+0x3f6/0x610 listxattr+0x90/0xe0 path_listxattrat+0xed/0x220 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Link: https://lore.kernel.org/20260705025311.3429854-3-zzzccc427@gmail.com Fixes: cf1d6c763fbc ("ocfs2: Add extended attribute support") Fixes: 0c044f0b24b9 ("ocfs2: Add xattr bucket iteration for large numbers of EAs") Signed-off-by: Cen Zhang Assisted-by: Codex:gpt-5.5 Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/xattr.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index ca76441625db..d23364c30e1a 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -390,6 +390,12 @@ static int ocfs2_init_xattr_bucket(struct ocfs2_xattr_bucket *bucket, return rc; } +static int ocfs2_validate_xattr_entries_flat(struct super_block *sb, u64 blkno, + struct ocfs2_xattr_header *xh, + size_t region_size); +static int ocfs2_validate_xattr_bucket(struct ocfs2_xattr_bucket *bucket, + u64 blkno); + /* Read the xattr bucket at xb_blkno */ static int ocfs2_read_xattr_bucket(struct ocfs2_xattr_bucket *bucket, u64 xb_blkno) @@ -408,6 +414,8 @@ static int ocfs2_read_xattr_bucket(struct ocfs2_xattr_bucket *bucket, spin_unlock(&OCFS2_SB(bucket->bu_inode->i_sb)->osb_xattr_lock); if (rc) mlog_errno(rc); + else + rc = ocfs2_validate_xattr_bucket(bucket, xb_blkno); } if (rc) @@ -509,6 +517,22 @@ static int ocfs2_validate_xattr_block(struct super_block *sb, le32_to_cpu(xb->xb_fs_generation)); } + if (!(le16_to_cpu(xb->xb_flags) & OCFS2_XATTR_INDEXED)) { + size_t region_offset = + offsetof(struct ocfs2_xattr_block, xb_attrs.xb_header); + + if (bh->b_size < region_offset) + return ocfs2_error(sb, + "Invalid xattr block %llu: block size %zu is too small\n", + (unsigned long long)bh->b_blocknr, + bh->b_size); + + return ocfs2_validate_xattr_entries_flat(sb, bh->b_blocknr, + &xb->xb_attrs.xb_header, + bh->b_size - + region_offset); + } + return 0; } @@ -1068,6 +1092,79 @@ int ocfs2_validate_inode_xattr(struct super_block *sb, u64 blkno, return ocfs2_validate_xattr_entries_flat(sb, blkno, xh, inline_size); } +static int ocfs2_validate_xattr_bucket(struct ocfs2_xattr_bucket *bucket, + u64 blkno) +{ + struct super_block *sb = bucket->bu_inode->i_sb; + 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; + size_t nv_limit = sb->s_blocksize; + size_t max_entries; + int i; + + if (region_size < sizeof(*xh)) + return ocfs2_error(sb, + "Invalid xattr bucket %llu: region size %zu is too small\n", + (unsigned long long)blkno, region_size); + + if (entries_limit < sizeof(*xh)) + return ocfs2_error(sb, + "Invalid xattr bucket %llu: entries limit %zu is too small\n", + (unsigned long long)blkno, + entries_limit); + + max_entries = (entries_limit - sizeof(*xh)) / + sizeof(struct ocfs2_xattr_entry); + + if (xattr_count > max_entries) + return ocfs2_error(sb, + "Invalid xattr bucket %llu: entry count %u exceeds maximum %zu\n", + (unsigned long long)blkno, + xattr_count, max_entries); + + for (i = 0; i < xattr_count; i++) { + struct ocfs2_xattr_entry *xe = &xh->xh_entries[i]; + size_t name_offset = le16_to_cpu(xe->xe_name_offset); + size_t block_off = name_offset >> sb->s_blocksize_bits; + size_t block_offset = name_offset % nv_limit; + size_t value_offset; + + if (name_offset >= region_size || block_off >= bucket->bu_blocks) + return ocfs2_error(sb, + "Invalid xattr bucket %llu: entry %d name is out of bounds\n", + (unsigned long long)blkno, i); + + if (xe->xe_name_len > nv_limit - block_offset) + return ocfs2_error(sb, + "Invalid xattr bucket %llu: entry %d name crosses block boundary\n", + (unsigned long long)blkno, i); + + value_offset = block_offset + OCFS2_XATTR_SIZE(xe->xe_name_len); + if (value_offset > nv_limit) + return ocfs2_error(sb, + "Invalid xattr bucket %llu: entry %d value starts out of bounds\n", + (unsigned long long)blkno, i); + + if (ocfs2_xattr_is_local(xe)) { + if (le64_to_cpu(xe->xe_value_size) > + nv_limit - value_offset) + return ocfs2_error(sb, + "Invalid xattr bucket %llu: entry %d value is out of bounds\n", + (unsigned long long)blkno, + i); + } else if (sizeof(struct ocfs2_xattr_value_root) > + nv_limit - value_offset) { + return ocfs2_error(sb, + "Invalid xattr bucket %llu: entry %d value root is out of bounds\n", + (unsigned long long)blkno, i); + } + } + + return 0; +} + static int ocfs2_xattr_ibody_lookup_header(struct inode *inode, struct ocfs2_dinode *di, struct ocfs2_xattr_header **header) From 99f62c484208edacd884fe50cc491a270dae0b4d Mon Sep 17 00:00:00 2001 From: Yiyang Chen Date: Tue, 7 Jul 2026 22:52:05 +0800 Subject: [PATCH 25/95] taskstats: remove dead taskstats_exit_mutex declaration The extern declaration of taskstats_exit_mutex has never been defined nor referenced anywhere now. Just remove it. Link: https://lore.kernel.org/98948e69094b73d6dfa63dcf0770067b57f3becf.1783435695.git.cyyzero16@gmail.com Signed-off-by: Yiyang Chen Cc: Balbir Singh Signed-off-by: Andrew Morton --- include/linux/taskstats_kern.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/taskstats_kern.h b/include/linux/taskstats_kern.h index dbb4d124c7d7..995cd19b56c1 100644 --- a/include/linux/taskstats_kern.h +++ b/include/linux/taskstats_kern.h @@ -14,7 +14,6 @@ #ifdef CONFIG_TASKSTATS extern struct kmem_cache *taskstats_cache; -extern struct mutex taskstats_exit_mutex; static inline void taskstats_tgid_free(struct signal_struct *sig) { From dc334b36ede14a40a59c54359500d9bf459e5308 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Tue, 7 Jul 2026 15:55:22 +0200 Subject: [PATCH 26/95] Revert "printf: Compile the kunit test with DISABLE_BRANCH_PROFILING DISABLE_BRANCH_PROFILING" This reverts commit 8901ac9d2c7eb8ed7ae5e749bf13ecb3b6062488. The commit 94bfc7f3b0c7 ("err.h: use __always_inline on all error pointer helpers") fixes the problem with a dead code elimination by always inlining IS_ERR() function. It prevents the related linker failures as well. The workaround with disabling branch profiling is not longer necessary. Link: https://lore.kernel.org/20260707135522.120447-1-pmladek@suse.com Signed-off-by: Petr Mladek Suggested-by: Andy Shevchenko Closes: https://lore.kernel.org/r/ah6WDkwO8eYY5f2a@ashevche-desk.local Reviewed-by: Andy Shevchenko Cc: Aleksander Lobakin Cc: Arnd Bergmann Cc: David Laight Cc: Nathan Chancellor Cc: Rasmus Villemoes Cc: Sergey Senozhatsky Cc: Steven Rostedt Signed-off-by: Andrew Morton --- lib/tests/Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/tests/Makefile b/lib/tests/Makefile index c045b82169bc..3cac3b63a752 100644 --- a/lib/tests/Makefile +++ b/lib/tests/Makefile @@ -41,8 +41,6 @@ obj-$(CONFIG_MEMCPY_KUNIT_TEST) += memcpy_kunit.o obj-$(CONFIG_MIN_HEAP_KUNIT_TEST) += min_heap_kunit.o CFLAGS_overflow_kunit.o = $(call cc-disable-warning, tautological-constant-out-of-range-compare) obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_kunit.o -# GCC < 12.1 can miscompile errptr() test when branch profiling is enabled. -CFLAGS_printf_kunit.o += -DDISABLE_BRANCH_PROFILING obj-$(CONFIG_PRANDOM_KUNIT_TEST) += random32_kunit.o obj-$(CONFIG_PRINTF_KUNIT_TEST) += printf_kunit.o obj-$(CONFIG_RANDSTRUCT_KUNIT_TEST) += randstruct_kunit.o From 1db1573bc59f7c91fa2867e4cbea73494d5b623e Mon Sep 17 00:00:00 2001 From: JianChunfu Date: Tue, 7 Jul 2026 17:23:54 +0800 Subject: [PATCH 27/95] kernel/fork: declare max_threads __read_mostly max_threads is initialized once by fork_init()->set_max_threads(), and thereafter is mostly read in hot path, such as copy_process(). The fact that it is mostly read and not written to makes it candidates for __read_mostly declarations. I have already tested on my machine(arm64,256core,kernel-7.1.3) with 'hackbench -P -l 500 -g 100/500/1000/2000' and the results show that there is an average of 13% improvement in performance. before patch: hackbench -g 100 500 1000 2000 1.094 4.651 13.305 36.322 after patch: hackbench -g 100 500 1000 2000 0.823 4.487 11.107 32.987 Above data are the average values obtained from multiple tests, and there was indeed some fluctuation in the data during the tests. Link: https://lore.kernel.org/20260707092354.465847-1-jansef.jian@hj-micro.com Signed-off-by: JianChunfu Reviewed-by: Lorenzo Stoakes Cc: Dietmar Eggemann Cc: Ingo Molnar Cc: Kees Cook Cc: Liam R. Howlett Cc: Peter Zijlstra Cc: Steven Rostedt Signed-off-by: Andrew Morton --- kernel/fork.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/fork.c b/kernel/fork.c index f0e2e131a9a5..516faec8efb8 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -143,7 +143,7 @@ unsigned long total_forks; /* Handle normal Linux uptimes. */ int nr_threads; /* The idle threads do not count.. */ -static int max_threads; /* tunable limit on nr_threads */ +static int max_threads __read_mostly; /* tunable limit on nr_threads */ #define NAMED_ARRAY_INDEX(x) [x] = __stringify(x) From f9632fe349992faa26c1ca62f595066cbfe9d46f Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Tue, 7 Jul 2026 12:52:25 -0700 Subject: [PATCH 28/95] .get_maintainer.ignore: add Nathan Chancellor I do work all over the tree as part of ClangBuiltLinux and Kbuild, which ultimately results in a lot of irrelevant CCs when submitters run get_maintainer.pl with the default git fallback enabled. Add myself to .get_maintainer.ignore so I only get CC'd through explicitly maintained files/patterns or a submitter intentionally adding me to the list. Link: https://lore.kernel.org/20260707-add-nathan-to-get_maintainers-ignore-v1-1-810926e8eea3@kernel.org Signed-off-by: Nathan Chancellor Signed-off-by: Andrew Morton --- .get_maintainer.ignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.get_maintainer.ignore b/.get_maintainer.ignore index 60b6b2a374cd..5ad082b4dd03 100644 --- a/.get_maintainer.ignore +++ b/.get_maintainer.ignore @@ -5,4 +5,5 @@ Askar Safin Christoph Hellwig Jeff Kirsher Marc Gonzalez +Nathan Chancellor Ralf Baechle From d66bf04b990467a1a30d7c231b458128d2d04e9a Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 7 Jul 2026 15:46:27 +0800 Subject: [PATCH 29/95] checkpatch: don't emit warnings for ID-base USB & PCI DT compatibles The USB and PCI device bindings define some compatible patterns based on device IDs that use the comma to separate vendor and product IDs. These prefix patterns include: - ^usb(if)?[0-9a-f]{1,4}, - ^pci[0-9a-f]{2,4}, - ^pciclass, These are not real vendor prefixes. Don't emit warnings for them. Instead just skip over the DT compatible check altogether, and leave the real check to the DT validator. This avoids false positive warnings about undocumented DT vendor prefixes and compatibles. Note that the script mostly only checks the first compatible string of each node, as it processes the source file line-by-line, and the check only matches on the line with 'compatible = "..."'. Otherwise there would be more warnings from arch/mips/boot/dts/loongson/ls7a-pch.dtsi since that file also includes compatibles like "pciclass0c0310" and "pciclass0c03" which are not accepted either. "pci0014,7a24.0" is not valid either, but this patch leaves the real check to the DT validator. Link: https://lore.kernel.org/20260707074629.3132930-1-wenst@chromium.org Signed-off-by: Chen-Yu Tsai Reviewed-by: Brian Norris Tested-by: Brian Norris Cc: Andy Whitcroft Cc: Dwaipayan Ray Cc: Joe Perches Cc: Lukas Bulwahn Cc: Rob Herring Signed-off-by: Andrew Morton --- scripts/checkpatch.pl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 2b7a42bbdd94..7a846a3ea127 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -3781,6 +3781,12 @@ sub process { my $vp_file = $dt_path . "vendor-prefixes.yaml"; foreach my $compat (@compats) { + # Skip ID-based PCI and USB compatible patterns. + # DT validation will check them properly. + next if $compat =~ /^pciclass,/; + next if $compat =~ /^pci[a-f0-9]{2,4},/; + next if $compat =~ /^usb(if)?[a-f0-9]{1,4},/; + my $compat2 = $compat; $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; my $compat3 = $compat; From 03d0a3bebcb68ff0bdb129aad0d97153d24cee9c Mon Sep 17 00:00:00 2001 From: Charlie Jenkins Date: Thu, 9 Jul 2026 00:31:04 -0700 Subject: [PATCH 30/95] mailmap: add entry for Charlie Jenkins My Rivos email started bouncing so redirect to my personal email. Link: https://lore.kernel.org/20260709-mailmap-v1-1-74d98b8149d6@gmail.com Signed-off-by: Charlie Jenkins Cc: Paul Walmsley Signed-off-by: Andrew Morton --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index a8be42f87e02..bf0a246e0ab9 100644 --- a/.mailmap +++ b/.mailmap @@ -190,6 +190,7 @@ Carlos Bilbao Changbin Du Chao Yu Chao Yu +Charlie Jenkins Chen-Yu Tsai Chester Lin Chris Chiu From d48ace65322001b41bb5322442a21425ef4407d4 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Thu, 9 Jul 2026 09:01:01 +0300 Subject: [PATCH 31/95] ocfs2: do not use make_bad_inode() in ocfs2_read_inode_block_full() This reverts commit 58b6fcd2ab34 ("ocfs2: mark inode bad upon validation failure during read"). Since 'make_bad_inode()' resets inode type to S_IFREG, doing this for directory inode during active VFS lookup is likely to confuse the latter, including VFS_BUG_ON_INODE() triggered in this case. Link: https://lore.kernel.org/20260709060101.207262-1-dmantipov@yandex.ru Fixes: 58b6fcd2ab34 ("ocfs2: mark inode bad upon validation failure during read") Signed-off-by: Dmitry Antipov Suggested-by: Al Viro Reported-by: syzbot+d222f4b7129379c3d5bc@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d222f4b7129379c3d5bc Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/inode.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index 815bf3f659da..41db7dd39ed9 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -1952,8 +1952,6 @@ int ocfs2_read_inode_block_full(struct inode *inode, struct buffer_head **bh, rc = ocfs2_read_blocks(INODE_CACHE(inode), OCFS2_I(inode)->ip_blkno, 1, &tmp, flags, ocfs2_validate_inode_block); - if (rc < 0) - make_bad_inode(inode); /* If ocfs2_read_blocks() got us a new bh, pass it up. */ if (!rc && !*bh) *bh = tmp; From b74030fbf187b43c1f85b66a7082e9946511cb5d Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 15:06:28 +0800 Subject: [PATCH 32/95] rapidio: clear mport->net when rio_add_net() fails rio_alloc_net() stores the newly allocated rio_net in mport->net before rio_scan_alloc_net() registers the device. If rio_add_net() fails, rio_scan_alloc_net() drops the device reference with put_device(), which releases the rio_net through the device release callback. However, mport->net is left pointing at the freed object. A later mport unregister path can then dereference the dangling mport->net pointer and may try to free the same rio_net again. Clear mport->net in the rio_add_net() failure path, matching the cleanup done for the destID table allocation failure path. Link: https://lore.kernel.org/20260708070628.721010-1-lgs201920130244@gmail.com Fixes: e842f9a1edf3 ("rapidio: add check for rio_add_net() in rio_scan_alloc_net()") Signed-off-by: Guangshuo Li Cc: Alexandre Bounine Cc: Matt Porter Cc: Yang yingliang Signed-off-by: Andrew Morton --- drivers/rapidio/rio-scan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/rapidio/rio-scan.c b/drivers/rapidio/rio-scan.c index dcd6619a4b02..3cc25d053451 100644 --- a/drivers/rapidio/rio-scan.c +++ b/drivers/rapidio/rio-scan.c @@ -874,6 +874,7 @@ static struct rio_net *rio_scan_alloc_net(struct rio_mport *mport, net->dev.release = rio_scan_release_dev; if (rio_add_net(net)) { put_device(&net->dev); + mport->net = NULL; net = NULL; } } From 4ca62df6bc0708947b48da3f6a712ecb8e73929c Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Thu, 9 Jul 2026 15:26:09 +0200 Subject: [PATCH 33/95] ocfs2: validate rl_used against rl_count in refcount block validator ocfs2_find_refcount_rec_in_rl() walks the on-disk refcount record array with: for (; i < le16_to_cpu(rb->rf_records.rl_used); i++) { rec = &rb->rf_records.rl_recs[i]; ... rl_recs[] lives in a single metadata block (4096 bytes on the common configuration), so its real capacity is fixed by ocfs2_refcount_recs_per_rb(sb) (247 records for a 4K block with the 16-byte ocfs2_refcount_rec). rl_used and rl_count are both read directly off disk by ocfs2_validate_refcount_block() and are never checked against that capacity, nor against each other, before any refcount/reflink/CoW operation walks the array. A crafted (or corrupted) refcount block with rl_used == 0xffff makes the loop above walk far past the end of the block, dereferencing rl_recs[i] for i up to 65534. The resulting index is then handed to the sibling ocfs2_insert_refcount_rec(), whose insert-shift does: if (index < le16_to_cpu(rf_list->rl_used)) memmove(&rf_list->rl_recs[index + 1], &rf_list->rl_recs[index], (le16_to_cpu(rf_list->rl_used) - index) * sizeof(struct ocfs2_refcount_rec)); i.e. a memmove() of up to (0xffff - index) * 16 bytes (~1 MiB) from an offset already past the block. This is reachable from an ordinary reflink (FICLONE) against a crafted/corrupted ocfs2 image: attaching an extent whose cpos sorts past every real record in the leaf forces the lookup to run off the end instead of returning early on a match. The attacker model is local: CAP_SYS_ADMIN mounting a crafted or corrupted ocfs2 image, or a raw write to the block device backing an already-mounted ocfs2 filesystem. ocfs2_validate_refcount_block() already validates the block's ECC, signature, rf_blkno and rf_fs_generation, but never rl_count/rl_used against the block's actual on-disk capacity. This is the same class of gap that ocfs2_validate_extent_block() (fs/ocfs2/alloc.c) already closes for the sibling extent-list header, which checks both the record capacity and the "used" bound before any code walks h_list.l_recs[]: if (le16_to_cpu(eb->h_list.l_count) != ocfs2_extent_recs_per_eb(sb)) { rc = ocfs2_error(...); goto bail; } if (le16_to_cpu(eb->h_list.l_next_free_rec) > le16_to_cpu(eb->h_list.l_count)) { rc = ocfs2_error(...); goto bail; } Add the equivalent pair of checks to ocfs2_validate_refcount_block(): reject a refcount block whose rl_count does not match the fixed per-block capacity returned by ocfs2_refcount_recs_per_rb(), and reject rl_used > rl_count. Both checks are skipped when OCFS2_REFCOUNT_TREE_FL is set, because in that case the same union bytes hold an ocfs2_extent_list (rf_list), not the refcount record list (rf_records) -- that layout is already validated separately by ocfs2_validate_extent_block() when the referenced extent block is read. This mirrors the existing "!(rb->rf_flags & OCFS2_REFCOUNT_TREE_FL)" guard used elsewhere in this file (e.g. ocfs2_get_refcount_rec()) to decide whether rf_records or rf_list is the live member of the union. With this in place, a forged rl_used/rl_count is caught at block validation time (ocfs2_error()), consistent with every other corruption check in this function, instead of driving an out-of-bounds read in ocfs2_find_refcount_rec_in_rl() and a subsequent out-of-bounds memmove() in ocfs2_insert_refcount_rec(). Verified against a crafted image on a v6.19 KASAN (KASAN_GENERIC) build: replaying the same reflink (FICLONE) reliably hit a KASAN report in __ocfs2_increase_refcount()/ocfs2_insert_refcount_rec() before this patch, and triggers no report once ocfs2_validate_refcount_block() rejects the forged rl_used/rl_count. Link: https://lore.kernel.org/20260709132609.44233-1-security@auditcode.ai Fixes: f2c870e3b12e ("ocfs2: Add ocfs2_read_refcount_block.") Signed-off-by: Ibrahim Hashimov Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Assisted-by: AuditCode-AI:2026.07 Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/refcounttree.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c index 7323bde70caa..63d6cb326e30 100644 --- a/fs/ocfs2/refcounttree.c +++ b/fs/ocfs2/refcounttree.c @@ -116,6 +116,33 @@ static int ocfs2_validate_refcount_block(struct super_block *sb, le32_to_cpu(rb->rf_fs_generation)); 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); + * in that case the same union bytes hold an extent list (rf_list) + * instead, which is validated by ocfs2_validate_extent_block(). + */ + if (!(le32_to_cpu(rb->rf_flags) & OCFS2_REFCOUNT_TREE_FL)) { + if (le16_to_cpu(rb->rf_records.rl_count) != + ocfs2_refcount_recs_per_rb(sb)) { + rc = ocfs2_error(sb, + "Refcount block #%llu has an invalid rl_count of %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(rb->rf_records.rl_count)); + goto out; + } + + if (le16_to_cpu(rb->rf_records.rl_used) > + le16_to_cpu(rb->rf_records.rl_count)) { + rc = ocfs2_error(sb, + "Refcount block #%llu has an invalid rl_used of %u (rl_count %u)\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(rb->rf_records.rl_used), + le16_to_cpu(rb->rf_records.rl_count)); + goto out; + } + } out: return rc; } From b0860c1df2dc3123171146ac5f1e3321289835b4 Mon Sep 17 00:00:00 2001 From: Yiyang Chen Date: Sat, 11 Jul 2026 04:07:36 +0800 Subject: [PATCH 34/95] taskstats: return -EBADF when cgroupstats receives an invalid fd Patch series "taskstats: fix cgroupstats invalid fd handling and add selftests". This series fixes an issue where cgroupstats mishandles invalid file descriptors, and introduces a functional kselftest to prevent regressions. When an invalid file descriptor is passed via CGROUPSTATS_CMD_ATTR_FD, cgroupstats_user_cmd() returns 0 instead of an error code. This leads to two broken behaviors depending on netlink flags: - Callers without NLM_F_ACK block indefinitely on recv(). - Callers with NLM_F_ACK receive a misleading success ACK (errno == 0) but no actual statistics payload. The first patch addresses this by returning -EBADF when the fd cannot be resolved. The second patch adds a comprehensive kselftest covering both the valid cgroup v1 query and the invalid fd paths (with and without NLM_F_ACK) to ensure the fixes work as intended. This patch (of 2): cgroupstats_user_cmd() returns 0 without sending a reply or a netlink error when the fd passed via CGROUPSTATS_CMD_ATTR_FD does not resolve to an open file in the caller's table. As a result: - clients that did not set NLM_F_ACK block on recv() indefinitely waiting for a CGROUPSTATS_CMD_NEW message that is never emitted; - clients that set NLM_F_ACK receive a misleading "success" ACK (errno == 0) with no statistics payload. Return -EBADF instead so the netlink layer propagates the error to userspace as expected. Link: https://lore.kernel.org/cover.1783713230.git.cyyzero16@gmail.com Link: https://lore.kernel.org/b4fd9e288e4a48efebaf41b4ffcdb204b06675c4.1783713230.git.cyyzero16@gmail.com Signed-off-by: Yiyang Chen Acked-by: Balbir Singh Cc: Dr. Thomas Orgis Signed-off-by: Andrew Morton --- kernel/taskstats.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/taskstats.c b/kernel/taskstats.c index 2cd0172d0516..8d115670a3ca 100644 --- a/kernel/taskstats.c +++ b/kernel/taskstats.c @@ -423,7 +423,7 @@ static int cgroupstats_user_cmd(struct sk_buff *skb, struct genl_info *info) fd = nla_get_u32(info->attrs[CGROUPSTATS_CMD_ATTR_FD]); CLASS(fd, f)(fd); if (fd_empty(f)) - return 0; + return -EBADF; size = nla_total_size(sizeof(struct cgroupstats)); From e31d33fc331a9b725426b6573237859606ece621 Mon Sep 17 00:00:00 2001 From: Yiyang Chen Date: Sat, 11 Jul 2026 04:07:37 +0800 Subject: [PATCH 35/95] selftests/acct: add cgroupstats functional test Add a self-contained test for the CGROUPSTATS_CMD_GET genl command that covers three cases: - querying a cgroup v1 hierarchy and verifying the response contains non-zero task counts - rejecting an invalid fd without NLM_F_ACK - rejecting an invalid fd with NLM_F_ACK Link: https://lore.kernel.org/2a56921276f0962f97f00863e8c7d3c3f6893d5a.1783713230.git.cyyzero16@gmail.com Signed-off-by: Yiyang Chen Cc: Balbir Singh Cc: Dr. Thomas Orgis Signed-off-by: Andrew Morton --- tools/testing/selftests/acct/Makefile | 1 + tools/testing/selftests/acct/cgroupstats.c | 354 +++++++++++++++++++++ 2 files changed, 355 insertions(+) create mode 100644 tools/testing/selftests/acct/cgroupstats.c diff --git a/tools/testing/selftests/acct/Makefile b/tools/testing/selftests/acct/Makefile index 083cab5ddb72..db88d65f5581 100644 --- a/tools/testing/selftests/acct/Makefile +++ b/tools/testing/selftests/acct/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 TEST_GEN_PROGS := acct_syscall TEST_GEN_PROGS += taskstats_fill_stats_tgid +TEST_GEN_PROGS += cgroupstats CFLAGS += -Wall LDLIBS += -lpthread diff --git a/tools/testing/selftests/acct/cgroupstats.c b/tools/testing/selftests/acct/cgroupstats.c new file mode 100644 index 000000000000..e2836383ed50 --- /dev/null +++ b/tools/testing/selftests/acct/cgroupstats.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest.h" + +#ifndef NLA_ALIGN +#define NLA_ALIGNTO 4 +#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) +#define NLA_HDRLEN ((int)NLA_ALIGN(sizeof(struct nlattr))) +#endif + +#define RECV_TIMEOUT_SEC 2 + +static void *nla_data(const struct nlattr *na) +{ + return (void *)((char *)na + NLA_HDRLEN); +} + +static int netlink_open(void) +{ + struct timeval tv = { .tv_sec = RECV_TIMEOUT_SEC }; + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + .nl_pid = getpid(), + }; + int fd; + + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); + if (fd < 0) + return -errno; + + /* + * Ensure that a missing kernel reply fails the individual test + * case instead of hanging the whole test binary. + */ + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + int err = -errno; + + close(fd); + return err; + } + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + int err = -errno; + + close(fd); + return err; + } + + return fd; +} + +static int send_request(int fd, void *buf, size_t len) +{ + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + if (sendto(fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) + return -errno; + + return 0; +} + +static int get_family_id(int fd, const char *name) +{ + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char buf[256]; + } req = { 0 }; + char resp[8192]; + struct nlmsghdr *nlh; + struct genlmsghdr *genl; + struct nlattr *na; + int len; + int rem; + int ret; + + req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); + req.nlh.nlmsg_type = GENL_ID_CTRL; + req.nlh.nlmsg_flags = NLM_F_REQUEST; + req.nlh.nlmsg_seq = 1; + req.nlh.nlmsg_pid = getpid(); + + req.genl.cmd = CTRL_CMD_GETFAMILY; + req.genl.version = 1; + + na = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.nlh.nlmsg_len)); + na->nla_type = CTRL_ATTR_FAMILY_NAME; + na->nla_len = NLA_HDRLEN + strlen(name) + 1; + memcpy(nla_data(na), name, strlen(name) + 1); + req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + NLA_ALIGN(na->nla_len); + + ret = send_request(fd, &req, req.nlh.nlmsg_len); + if (ret) + return ret; + + len = recv(fd, resp, sizeof(resp), 0); + if (len < 0) + return -errno; + + for (nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); + nlh = NLMSG_NEXT(nlh, len)) { + if (nlh->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = NLMSG_DATA(nlh); + + return err->error ? err->error : -ENOENT; + } + + genl = (struct genlmsghdr *)NLMSG_DATA(nlh); + rem = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + na = (struct nlattr *)((char *)genl + GENL_HDRLEN); + while (rem >= (int)sizeof(*na) && + na->nla_len >= (int)sizeof(*na) && + na->nla_len <= rem) { + if (na->nla_type == CTRL_ATTR_FAMILY_ID) + return *(uint16_t *)nla_data(na); + rem -= NLA_ALIGN(na->nla_len); + na = (struct nlattr *)((char *)na + NLA_ALIGN(na->nla_len)); + } + } + + return -ENOENT; +} + +static int send_cgroupstats_cmd(int fd, int family_id, uint32_t cgroup_fd, + int flags) +{ + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char buf[256]; + } req = { 0 }; + struct nlattr *na; + + req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); + req.nlh.nlmsg_type = family_id; + req.nlh.nlmsg_flags = NLM_F_REQUEST | flags; + req.nlh.nlmsg_seq = 2; + req.nlh.nlmsg_pid = getpid(); + + req.genl.cmd = CGROUPSTATS_CMD_GET; + req.genl.version = 1; + + na = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.nlh.nlmsg_len)); + na->nla_type = CGROUPSTATS_CMD_ATTR_FD; + na->nla_len = NLA_HDRLEN + sizeof(cgroup_fd); + memcpy(nla_data(na), &cgroup_fd, sizeof(cgroup_fd)); + req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + NLA_ALIGN(na->nla_len); + + return send_request(fd, &req, req.nlh.nlmsg_len); +} + +/* + * Receive and decode a cgroupstats response. + * + * Returns: + * 0 — success, stats filled from CGROUPSTATS_CMD_NEW reply + * <0 — NLMSG_ERROR errno (e.g. -EBADF, -EINVAL) + */ +static int recv_cgroupstats_response(int fd, struct cgroupstats *stats) +{ + char resp[8192]; + struct nlmsghdr *nlh; + struct genlmsghdr *genl; + struct nlattr *na; + int len; + int rem; + + memset(stats, 0, sizeof(*stats)); + + len = recv(fd, resp, sizeof(resp), 0); + if (len < 0) + return -errno; + + for (nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); + nlh = NLMSG_NEXT(nlh, len)) { + if (nlh->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = NLMSG_DATA(nlh); + + return err->error; + } + + genl = (struct genlmsghdr *)NLMSG_DATA(nlh); + if (genl->cmd != CGROUPSTATS_CMD_NEW) + continue; + + rem = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + na = (struct nlattr *)((char *)genl + GENL_HDRLEN); + while (rem >= (int)sizeof(*na) && + na->nla_len >= (int)sizeof(*na) && + na->nla_len <= rem) { + if (na->nla_type == CGROUPSTATS_TYPE_CGROUP_STATS) { + memcpy(stats, nla_data(na), sizeof(*stats)); + return 0; + } + rem -= NLA_ALIGN(na->nla_len); + na = (struct nlattr *)((char *)na + NLA_ALIGN(na->nla_len)); + } + } + + return -EIO; +} + +/* mkdtemp() modifies the template in place, so this cannot be const. */ +static char cg_mountpoint[32]; +static bool cg_mounted; + +static int setup_cgroup_v1(void) +{ + strcpy(cg_mountpoint, "/tmp/cgstats_test_XXXXXX"); + + if (!mkdtemp(cg_mountpoint)) + return -errno; + + if (mount("cgstats_test", cg_mountpoint, "cgroup", 0, + "none,name=cgstats_test") < 0) { + int ret = -errno; + + rmdir(cg_mountpoint); + return ret; + } + + cg_mounted = true; + return 0; +} + +static void cleanup_cgroup_v1(void) +{ + if (!cg_mounted) + return; + umount2(cg_mountpoint, MNT_DETACH); + rmdir(cg_mountpoint); + cg_mounted = false; +} + +int main(void) +{ + struct cgroupstats stats; + uint64_t total_tasks; + int family_id; + int nl_fd; + int cg_fd; + int ret; + + ksft_print_header(); + + nl_fd = netlink_open(); + if (nl_fd < 0) + ksft_exit_skip("failed to open generic netlink socket: %s\n", + strerror(-nl_fd)); + + family_id = get_family_id(nl_fd, TASKSTATS_GENL_NAME); + if (family_id < 0) + ksft_exit_skip("taskstats generic netlink family unavailable: %s\n", + strerror(-family_id)); + + ksft_set_plan(3); + + /* + * Test 1: mount a private cgroup v1 hierarchy, query it, and + * verify the response contains sane task counts. If the test + * environment cannot create a private cgroup v1 mount, skip this + * case and continue with the unprivileged regression checks below. + */ + ret = setup_cgroup_v1(); + if (ret) { + ksft_test_result_skip("cgroupstats query: cannot mount cgroup v1: %s\n", + strerror(-ret)); + } else { + cg_fd = open(cg_mountpoint, O_RDONLY | O_DIRECTORY); + if (cg_fd < 0) { + ksft_test_result_fail("cgroupstats query: open mountpoint: %s\n", + strerror(errno)); + } else { + ret = send_cgroupstats_cmd(nl_fd, family_id, + (uint32_t)cg_fd, 0); + if (ret) { + ksft_test_result_fail("cgroupstats query: send: %s\n", + strerror(-ret)); + } else { + ret = recv_cgroupstats_response(nl_fd, &stats); + if (ret < 0) { + ksft_test_result_fail("cgroupstats query: %s\n", + strerror(-ret)); + } else { + total_tasks = (uint64_t)stats.nr_sleeping + + (uint64_t)stats.nr_running + + (uint64_t)stats.nr_stopped + + (uint64_t)stats.nr_uninterruptible + + (uint64_t)stats.nr_io_wait; + + ksft_print_msg("cgroupstats query: total_tasks=%llu\n", + (unsigned long long)total_tasks); + + ksft_test_result(total_tasks > 0, + "cgroupstats query returns valid stats\n"); + } + } + close(cg_fd); + } + } + cleanup_cgroup_v1(); + + /* + * Test 2: invalid fd without NLM_F_ACK. The kernel should + * return -EBADF via NLMSG_ERROR regardless of whether the + * client requested an explicit ACK. + */ + ret = send_cgroupstats_cmd(nl_fd, family_id, 0xFFFFFFFF, 0); + if (ret) + ksft_exit_fail_msg("send test 2 failed: %s\n", strerror(-ret)); + + ret = recv_cgroupstats_response(nl_fd, &stats); + ksft_print_msg("bad fd (no ACK): response=%d (%s)\n", + ret, ret < 0 ? strerror(-ret) : "unexpected success"); + ksft_test_result(ret == -EBADF, + "cgroupstats rejects bad fd without NLM_F_ACK\n"); + + /* + * Test 3: invalid fd with NLM_F_ACK. Same expectation as + * test 2, but exercised through a different netlink flag + * path in the kernel's ack/error handling. + */ + ret = send_cgroupstats_cmd(nl_fd, family_id, 0xFFFFFFFF, NLM_F_ACK); + if (ret) + ksft_exit_fail_msg("send test 3 failed: %s\n", strerror(-ret)); + + ret = recv_cgroupstats_response(nl_fd, &stats); + ksft_print_msg("bad fd (with ACK): response=%d (%s)\n", + ret, ret < 0 ? strerror(-ret) : "unexpected success"); + ksft_test_result(ret == -EBADF, + "cgroupstats rejects bad fd with NLM_F_ACK\n"); + + close(nl_fd); + ksft_finished(); + return ksft_get_fail_cnt() ? KSFT_FAIL : KSFT_PASS; +} From 609d45af831065d90880a693027d93806ad0b802 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 10 Jul 2026 15:17:55 +0800 Subject: [PATCH 36/95] ocfs2: cluster: use GFP_NOFS for heartbeat bio allocation o2hb_setup_one_bio() allocates the heartbeat bio with GFP_ATOMIC. The disk heartbeat runs in the o2hb kernel thread (o2hb_do_disk_heartbeat), which is process context and can sleep, so there is no atomicity requirement here. GFP_ATOMIC lacks __GFP_DIRECT_RECLAIM, so the allocation is not served from the fs_bio_set mempool reserve and can return NULL under memory pressure. A failed heartbeat allocation aborts the heartbeat and can lead to the local node being fenced, which is exactly what the old comment worried about. Use GFP_NOFS instead. It keeps __GFP_DIRECT_RECLAIM so the allocation is backed by the fs_bio_set mempool and cannot fail, while avoiding recursion back into the filesystem during heartbeat I/O. As the allocation can no longer fail, drop the dead ERR_PTR(-ENOMEM) path in o2hb_setup_one_bio() and the now-redundant IS_ERR() handling in its callers. Link: https://lore.kernel.org/20260710071756.3586797-1-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/cluster/heartbeat.c | 41 +++++++----------------------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index 6da96a374fcd..ef6a11fdef5b 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -522,16 +522,12 @@ static struct bio *o2hb_setup_one_bio(struct o2hb_region *reg, struct bio *bio; struct page *page; - /* Testing has shown this allocation to take long enough under - * GFP_KERNEL that the local node can get fenced. It would be - * nicest if we could pre-allocate these bios and avoid this - * all together. */ - bio = bio_alloc(reg_bdev(reg), 16, opf, GFP_ATOMIC); - if (!bio) { - mlog(ML_ERROR, "Could not alloc slots BIO!\n"); - bio = ERR_PTR(-ENOMEM); - goto bail; - } + /* + * The heartbeat runs in process context and can sleep, so use + * GFP_NOFS. It is backed by the fs_bio_set mempool and thus cannot + * fail, while avoiding recursion back into the filesystem. + */ + bio = bio_alloc(reg_bdev(reg), 16, opf, GFP_NOFS); /* Must put everything in 512 byte sectors for the bio... */ bio->bi_iter.bi_sector = (reg->hr_start_block + cs) << (bits - 9); @@ -556,7 +552,6 @@ static struct bio *o2hb_setup_one_bio(struct o2hb_region *reg, vec_start = 0; } -bail: *current_slot = cs; return bio; } @@ -566,7 +561,6 @@ static int o2hb_read_slots(struct o2hb_region *reg, unsigned int max_slots) { unsigned int current_slot = begin_slot; - int status; struct o2hb_bio_wait_ctxt wc; struct bio *bio; @@ -575,30 +569,18 @@ static int o2hb_read_slots(struct o2hb_region *reg, while(current_slot < max_slots) { bio = o2hb_setup_one_bio(reg, &wc, ¤t_slot, max_slots, REQ_OP_READ); - if (IS_ERR(bio)) { - status = PTR_ERR(bio); - mlog_errno(status); - goto bail_and_wait; - } - atomic_inc(&wc.wc_num_reqs); submit_bio(bio); } - status = 0; - -bail_and_wait: o2hb_wait_on_io(&wc); - if (wc.wc_error && !status) - status = wc.wc_error; - return status; + return wc.wc_error; } static int o2hb_issue_node_write(struct o2hb_region *reg, struct o2hb_bio_wait_ctxt *write_wc) { - int status; unsigned int slot; struct bio *bio; @@ -610,18 +592,11 @@ static int o2hb_issue_node_write(struct o2hb_region *reg, bio = o2hb_setup_one_bio(reg, write_wc, &slot, slot+1, REQ_OP_WRITE | REQ_SYNC); - if (IS_ERR(bio)) { - status = PTR_ERR(bio); - mlog_errno(status); - goto bail; - } atomic_inc(&write_wc->wc_num_reqs); submit_bio(bio); - status = 0; -bail: - return status; + return 0; } static u32 o2hb_compute_block_crc_le(struct o2hb_region *reg, From 041c9d0ab53b52ebd52229ddd9b347a6ccf4b287 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 10 Jul 2026 15:17:56 +0800 Subject: [PATCH 37/95] ocfs2: cluster: use an on-stack bio for the heartbeat write The disk heartbeat write always covers this node's own single slot, i.e. one heartbeat block that lives within a single page. It is submitted by o2hb_issue_node_write() and waited on by the caller before the ctxt goes out of scope, so its lifetime is well bounded. Turn it into an on-stack bio embedded in struct o2hb_bio_wait_ctxt rather than allocating one from the mempool. This removes any allocation from the fence-critical write path entirely: a delayed or blocked heartbeat write is what leads to the local node being fenced, so it should not depend on the state of a shared bio pool. Because the bio is embedded rather than allocated, add a dedicated o2hb_write_bio_end_io() that does not call bio_put(), and tear the bio down with bio_uninit() once the caller has waited on the I/O. The read path still allocates via o2hb_setup_one_bio() with GFP_NOFS, since it issues a variable number of bios in a loop. Link: https://lore.kernel.org/20260710071756.3586797-2-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/cluster/heartbeat.c | 50 ++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index ef6a11fdef5b..29542edbc992 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -272,6 +272,9 @@ struct o2hb_bio_wait_ctxt { atomic_t wc_num_reqs; struct completion wc_io_complete; int wc_error; + /* On-stack bio used by the synchronous write path only. */ + struct bio wc_write_bio; + struct bio_vec wc_write_bvec; }; #define O2HB_NEGO_TIMEOUT_MS (O2HB_MAX_WRITE_TIMEOUT_MS/2) @@ -507,6 +510,23 @@ static void o2hb_bio_end_io(struct bio *bio) bio_put(bio); } +/* + * End I/O for the synchronous write path. The write bio is embedded in + * the wait ctxt rather than allocated, so it must not be freed here; it + * is torn down with bio_uninit() once the caller has waited on it. + */ +static void o2hb_write_bio_end_io(struct bio *bio) +{ + struct o2hb_bio_wait_ctxt *wc = bio->bi_private; + + if (bio->bi_status) { + mlog(ML_ERROR, "IO Error %d\n", bio->bi_status); + wc->wc_error = blk_status_to_errno(bio->bi_status); + } + + o2hb_bio_wait_dec(wc, 1); +} + /* Setup a Bio to cover I/O against num_slots slots starting at * start_slot. */ static struct bio *o2hb_setup_one_bio(struct o2hb_region *reg, @@ -582,7 +602,11 @@ static int o2hb_issue_node_write(struct o2hb_region *reg, struct o2hb_bio_wait_ctxt *write_wc) { unsigned int slot; - struct bio *bio; + unsigned int bits = reg->hr_block_bits; + unsigned int spp = reg->hr_slots_per_page; + unsigned int vec_start, vec_len; + struct page *page; + struct bio *bio = &write_wc->wc_write_bio; o2hb_bio_wait_init(write_wc); @@ -590,8 +614,21 @@ static int o2hb_issue_node_write(struct o2hb_region *reg, if (slot >= O2NM_MAX_NODES) return -EINVAL; - bio = o2hb_setup_one_bio(reg, write_wc, &slot, slot+1, - REQ_OP_WRITE | REQ_SYNC); + /* + * The heartbeat write always covers our own single slot, i.e. one + * block that lives within a single page. Use an on-stack bio (embedded + * in write_wc) so this fence-critical path never has to allocate. + */ + bio_init(bio, reg_bdev(reg), &write_wc->wc_write_bvec, 1, + REQ_OP_WRITE | REQ_SYNC); + bio->bi_iter.bi_sector = (reg->hr_start_block + slot) << (bits - 9); + bio->bi_private = write_wc; + bio->bi_end_io = o2hb_write_bio_end_io; + + page = reg->hr_slot_data[slot / spp]; + vec_start = (slot << bits) % PAGE_SIZE; + vec_len = PAGE_SIZE / spp; + __bio_add_page(bio, page, vec_len, vec_start); atomic_inc(&write_wc->wc_num_reqs); submit_bio(bio); @@ -1133,6 +1170,7 @@ static int o2hb_do_disk_heartbeat(struct o2hb_region *reg) * people we find in our steady state have seen us. */ o2hb_wait_on_io(&write_wc); + bio_uninit(&write_wc.wc_write_bio); if (write_wc.wc_error) { /* Do not re-arm the write timeout on I/O error - we * can't be sure that the new block ever made it to @@ -1245,10 +1283,12 @@ static int o2hb_thread(void *data) if (!reg->hr_unclean_stop && !reg->hr_aborted_start) { o2hb_prepare_block(reg, 0); ret = o2hb_issue_node_write(reg, &write_wc); - if (ret == 0) + if (ret == 0) { o2hb_wait_on_io(&write_wc); - else + bio_uninit(&write_wc.wc_write_bio); + } else { mlog_errno(ret); + } } /* Unpin node */ From 0a2418c32734e5fd657d74b3a9598746e5a1f4f4 Mon Sep 17 00:00:00 2001 From: Yiyang Chen Date: Mon, 13 Jul 2026 01:13:31 +0800 Subject: [PATCH 38/95] selftests/acct: share netlink helpers Extract the duplicated generic netlink boilerplate (netlink_open, send_request, get_family_id, and NLA walker macros) from cgroupstats.c and taskstats_fill_stats_tgid.c into a shared netlink_helper.{h,c}. Link: https://lore.kernel.org/a2adf27308b5cd90d50b59e8519b87da49486bee.1783876192.git.cyyzero16@gmail.com Signed-off-by: Yiyang Chen Suggested-by: Andrew Morton Acked-by: Balbir Singh Signed-off-by: Andrew Morton --- tools/testing/selftests/acct/.gitignore | 1 + tools/testing/selftests/acct/Makefile | 10 ++ tools/testing/selftests/acct/cgroupstats.c | 133 +---------------- tools/testing/selftests/acct/netlink_helper.c | 116 +++++++++++++++ tools/testing/selftests/acct/netlink_helper.h | 44 ++++++ .../acct/taskstats_fill_stats_tgid.c | 134 ++---------------- 6 files changed, 184 insertions(+), 254 deletions(-) create mode 100644 tools/testing/selftests/acct/netlink_helper.c create mode 100644 tools/testing/selftests/acct/netlink_helper.h diff --git a/tools/testing/selftests/acct/.gitignore b/tools/testing/selftests/acct/.gitignore index 9e9c61c5bfd6..fe0896f54e15 100644 --- a/tools/testing/selftests/acct/.gitignore +++ b/tools/testing/selftests/acct/.gitignore @@ -1,4 +1,5 @@ acct_syscall taskstats_fill_stats_tgid +cgroupstats config process_log diff --git a/tools/testing/selftests/acct/Makefile b/tools/testing/selftests/acct/Makefile index db88d65f5581..93a11a28a636 100644 --- a/tools/testing/selftests/acct/Makefile +++ b/tools/testing/selftests/acct/Makefile @@ -3,7 +3,17 @@ TEST_GEN_PROGS := acct_syscall TEST_GEN_PROGS += taskstats_fill_stats_tgid TEST_GEN_PROGS += cgroupstats +NETLINK_HELPER_PROGS := cgroupstats taskstats_fill_stats_tgid + CFLAGS += -Wall LDLIBS += -lpthread include ../lib.mk + +$(NETLINK_HELPER_PROGS): %: %.c netlink_helper.c netlink_helper.h + $(call msg,CC,,$@) + $(Q)$(LINK.c) $< netlink_helper.c $(LDLIBS) -o $@ + +$(addprefix $(OUTPUT)/,$(NETLINK_HELPER_PROGS)): $(OUTPUT)/%: %.c netlink_helper.c netlink_helper.h + $(call msg,CC,,$@) + $(Q)$(LINK.c) $< netlink_helper.c $(LDLIBS) -o $@ diff --git a/tools/testing/selftests/acct/cgroupstats.c b/tools/testing/selftests/acct/cgroupstats.c index e2836383ed50..0b421a4ca72b 100644 --- a/tools/testing/selftests/acct/cgroupstats.c +++ b/tools/testing/selftests/acct/cgroupstats.c @@ -6,139 +6,19 @@ #include #include #include +#include #include #include #include #include +#include #include #include -#include -#include #include +#include "netlink_helper.h" #include "kselftest.h" -#ifndef NLA_ALIGN -#define NLA_ALIGNTO 4 -#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) -#define NLA_HDRLEN ((int)NLA_ALIGN(sizeof(struct nlattr))) -#endif - -#define RECV_TIMEOUT_SEC 2 - -static void *nla_data(const struct nlattr *na) -{ - return (void *)((char *)na + NLA_HDRLEN); -} - -static int netlink_open(void) -{ - struct timeval tv = { .tv_sec = RECV_TIMEOUT_SEC }; - struct sockaddr_nl addr = { - .nl_family = AF_NETLINK, - .nl_pid = getpid(), - }; - int fd; - - fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); - if (fd < 0) - return -errno; - - /* - * Ensure that a missing kernel reply fails the individual test - * case instead of hanging the whole test binary. - */ - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { - int err = -errno; - - close(fd); - return err; - } - - if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - int err = -errno; - - close(fd); - return err; - } - - return fd; -} - -static int send_request(int fd, void *buf, size_t len) -{ - struct sockaddr_nl addr = { - .nl_family = AF_NETLINK, - }; - - if (sendto(fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) - return -errno; - - return 0; -} - -static int get_family_id(int fd, const char *name) -{ - struct { - struct nlmsghdr nlh; - struct genlmsghdr genl; - char buf[256]; - } req = { 0 }; - char resp[8192]; - struct nlmsghdr *nlh; - struct genlmsghdr *genl; - struct nlattr *na; - int len; - int rem; - int ret; - - req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); - req.nlh.nlmsg_type = GENL_ID_CTRL; - req.nlh.nlmsg_flags = NLM_F_REQUEST; - req.nlh.nlmsg_seq = 1; - req.nlh.nlmsg_pid = getpid(); - - req.genl.cmd = CTRL_CMD_GETFAMILY; - req.genl.version = 1; - - na = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.nlh.nlmsg_len)); - na->nla_type = CTRL_ATTR_FAMILY_NAME; - na->nla_len = NLA_HDRLEN + strlen(name) + 1; - memcpy(nla_data(na), name, strlen(name) + 1); - req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + NLA_ALIGN(na->nla_len); - - ret = send_request(fd, &req, req.nlh.nlmsg_len); - if (ret) - return ret; - - len = recv(fd, resp, sizeof(resp), 0); - if (len < 0) - return -errno; - - for (nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); - nlh = NLMSG_NEXT(nlh, len)) { - if (nlh->nlmsg_type == NLMSG_ERROR) { - struct nlmsgerr *err = NLMSG_DATA(nlh); - - return err->error ? err->error : -ENOENT; - } - - genl = (struct genlmsghdr *)NLMSG_DATA(nlh); - rem = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; - na = (struct nlattr *)((char *)genl + GENL_HDRLEN); - while (rem >= (int)sizeof(*na) && - na->nla_len >= (int)sizeof(*na) && - na->nla_len <= rem) { - if (na->nla_type == CTRL_ATTR_FAMILY_ID) - return *(uint16_t *)nla_data(na); - rem -= NLA_ALIGN(na->nla_len); - na = (struct nlattr *)((char *)na + NLA_ALIGN(na->nla_len)); - } - } - - return -ENOENT; -} - static int send_cgroupstats_cmd(int fd, int family_id, uint32_t cgroup_fd, int flags) { @@ -203,15 +83,12 @@ static int recv_cgroupstats_response(int fd, struct cgroupstats *stats) rem = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; na = (struct nlattr *)((char *)genl + GENL_HDRLEN); - while (rem >= (int)sizeof(*na) && - na->nla_len >= (int)sizeof(*na) && - na->nla_len <= rem) { + while (nla_ok(na, rem)) { if (na->nla_type == CGROUPSTATS_TYPE_CGROUP_STATS) { memcpy(stats, nla_data(na), sizeof(*stats)); return 0; } - rem -= NLA_ALIGN(na->nla_len); - na = (struct nlattr *)((char *)na + NLA_ALIGN(na->nla_len)); + na = nla_next(na, &rem); } } diff --git a/tools/testing/selftests/acct/netlink_helper.c b/tools/testing/selftests/acct/netlink_helper.c new file mode 100644 index 000000000000..3ed834f0e770 --- /dev/null +++ b/tools/testing/selftests/acct/netlink_helper.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include + +#include "netlink_helper.h" + +int netlink_open(void) +{ + struct timeval tv = { .tv_sec = ACCT_RCV_TIMEOUT_SEC }; + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + .nl_pid = getpid(), + }; + int fd; + + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); + if (fd < 0) + return -errno; + + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { + int err = -errno; + + close(fd); + return err; + } + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + int err = -errno; + + close(fd); + return err; + } + + return fd; +} + +int send_request(int fd, void *buf, size_t len) +{ + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + if (sendto(fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) + return -errno; + + return 0; +} + +/* + * Resolve the generic netlink family ID for @name. + * Returns the family ID (>= 0) on success, negative errno on failure. + */ +int get_family_id(int fd, const char *name) +{ + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char buf[256]; + } req = { 0 }; + char resp[8192]; + struct nlmsghdr *nlh; + struct genlmsghdr *genl; + struct nlattr *na; + int len; + int rem; + int ret; + + req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); + req.nlh.nlmsg_type = GENL_ID_CTRL; + req.nlh.nlmsg_flags = NLM_F_REQUEST; + req.nlh.nlmsg_seq = 1; + req.nlh.nlmsg_pid = getpid(); + + req.genl.cmd = CTRL_CMD_GETFAMILY; + req.genl.version = 1; + + na = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.nlh.nlmsg_len)); + na->nla_type = CTRL_ATTR_FAMILY_NAME; + na->nla_len = NLA_HDRLEN + strlen(name) + 1; + memcpy(nla_data(na), name, strlen(name) + 1); + req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + NLA_ALIGN(na->nla_len); + + ret = send_request(fd, &req, req.nlh.nlmsg_len); + if (ret) + return ret; + + len = recv(fd, resp, sizeof(resp), 0); + if (len < 0) + return -errno; + + for (nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); + nlh = NLMSG_NEXT(nlh, len)) { + if (nlh->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = NLMSG_DATA(nlh); + + return err->error ? err->error : -ENOENT; + } + + genl = (struct genlmsghdr *)NLMSG_DATA(nlh); + rem = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + na = (struct nlattr *)((char *)genl + GENL_HDRLEN); + while (nla_ok(na, rem)) { + if (na->nla_type == CTRL_ATTR_FAMILY_ID) + return *(uint16_t *)nla_data(na); + na = nla_next(na, &rem); + } + } + + return -ENOENT; +} diff --git a/tools/testing/selftests/acct/netlink_helper.h b/tools/testing/selftests/acct/netlink_helper.h new file mode 100644 index 000000000000..0320729c4c06 --- /dev/null +++ b/tools/testing/selftests/acct/netlink_helper.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Shared generic netlink helpers for the acct selftests. + */ +#ifndef ACSELFTESTS_ACCT_NETLINK_HELPER_H +#define ACSELFTESTS_ACCT_NETLINK_HELPER_H + +#include +#include + +#ifndef NLA_ALIGNTO +#define NLA_ALIGNTO 4 +#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) +#define NLA_HDRLEN ((int)NLA_ALIGN(sizeof(struct nlattr))) +#endif + +/* Fail an individual test case instead of hanging the whole binary. */ +#define ACCT_RCV_TIMEOUT_SEC 2 + +static inline void *nla_data(const struct nlattr *na) +{ + return (void *)((char *)na + NLA_HDRLEN); +} + +static inline bool nla_ok(const struct nlattr *na, int remaining) +{ + return remaining >= (int)sizeof(*na) && + na->nla_len >= sizeof(*na) && + na->nla_len <= remaining; +} + +static inline struct nlattr *nla_next(const struct nlattr *na, int *remaining) +{ + int aligned_len = NLA_ALIGN(na->nla_len); + + *remaining -= aligned_len; + return (struct nlattr *)((char *)na + aligned_len); +} + +int netlink_open(void); +int send_request(int fd, void *buf, size_t len); +int get_family_id(int fd, const char *name); + +#endif /* ACSELFTESTS_ACCT_NETLINK_HELPER_H */ diff --git a/tools/testing/selftests/acct/taskstats_fill_stats_tgid.c b/tools/testing/selftests/acct/taskstats_fill_stats_tgid.c index d6cab4ae26f2..9a4c1554dee3 100644 --- a/tools/testing/selftests/acct/taskstats_fill_stats_tgid.c +++ b/tools/testing/selftests/acct/taskstats_fill_stats_tgid.c @@ -16,14 +16,9 @@ #include #include +#include "netlink_helper.h" #include "kselftest.h" -#ifndef NLA_ALIGN -#define NLA_ALIGNTO 4 -#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) -#define NLA_HDRLEN ((int)NLA_ALIGN(sizeof(struct nlattr))) -#endif - #define BUSY_NS (200ULL * 1000 * 1000) struct worker_ctx { @@ -35,26 +30,6 @@ struct worker_ctx { static unsigned long busy_sink; -static void *taskstats_nla_data(const struct nlattr *na) -{ - return (void *)((char *)na + NLA_HDRLEN); -} - -static bool taskstats_nla_ok(const struct nlattr *na, int remaining) -{ - return remaining >= (int)sizeof(*na) && - na->nla_len >= sizeof(*na) && - na->nla_len <= remaining; -} - -static struct nlattr *taskstats_nla_next(const struct nlattr *na, int *remaining) -{ - int aligned_len = NLA_ALIGN(na->nla_len); - - *remaining -= aligned_len; - return (struct nlattr *)((char *)na + aligned_len); -} - static uint64_t timespec_diff_ns(const struct timespec *start, const struct timespec *end) { @@ -84,99 +59,6 @@ static void burn_cpu_for_ns(uint64_t runtime_ns) busy_sink = acc; } -static int netlink_open(void) -{ - struct sockaddr_nl addr = { - .nl_family = AF_NETLINK, - .nl_pid = getpid(), - }; - int fd; - - fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); - if (fd < 0) - return -errno; - - if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - int err = -errno; - - close(fd); - return err; - } - - return fd; -} - -static int send_request(int fd, void *buf, size_t len) -{ - struct sockaddr_nl addr = { - .nl_family = AF_NETLINK, - }; - - if (sendto(fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) - return -errno; - - return 0; -} - -static int get_family_id(int fd, const char *name) -{ - struct { - struct nlmsghdr nlh; - struct genlmsghdr genl; - char buf[256]; - } req = { 0 }; - char resp[8192]; - struct nlmsghdr *nlh; - struct genlmsghdr *genl; - struct nlattr *na; - int len; - int rem; - int ret; - - req.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); - req.nlh.nlmsg_type = GENL_ID_CTRL; - req.nlh.nlmsg_flags = NLM_F_REQUEST; - req.nlh.nlmsg_seq = 1; - req.nlh.nlmsg_pid = getpid(); - - req.genl.cmd = CTRL_CMD_GETFAMILY; - req.genl.version = 1; - - na = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.nlh.nlmsg_len)); - na->nla_type = CTRL_ATTR_FAMILY_NAME; - na->nla_len = NLA_HDRLEN + strlen(name) + 1; - memcpy(taskstats_nla_data(na), name, strlen(name) + 1); - req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + NLA_ALIGN(na->nla_len); - - ret = send_request(fd, &req, req.nlh.nlmsg_len); - if (ret) - return ret; - - len = recv(fd, resp, sizeof(resp), 0); - if (len < 0) - return -errno; - - for (nlh = (struct nlmsghdr *)resp; NLMSG_OK(nlh, len); - nlh = NLMSG_NEXT(nlh, len)) { - if (nlh->nlmsg_type == NLMSG_ERROR) { - struct nlmsgerr *err = NLMSG_DATA(nlh); - - return err->error ? err->error : -ENOENT; - } - - genl = (struct genlmsghdr *)NLMSG_DATA(nlh); - rem = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; - na = (struct nlattr *)((char *)genl + GENL_HDRLEN); - while (taskstats_nla_ok(na, rem)) { - if (na->nla_type == CTRL_ATTR_FAMILY_ID) - return *(uint16_t *)taskstats_nla_data(na); - na = taskstats_nla_next(na, &rem); - } - } - - return -ENOENT; -} - static int get_taskstats(int fd, int family_id, uint16_t attr_type, uint32_t id, struct taskstats *stats) { @@ -209,7 +91,7 @@ static int get_taskstats(int fd, int family_id, uint16_t attr_type, uint32_t id, na = (struct nlattr *)((char *)&req + NLMSG_ALIGN(req.nlh.nlmsg_len)); na->nla_type = attr_type; na->nla_len = NLA_HDRLEN + sizeof(id); - memcpy(taskstats_nla_data(na), &id, sizeof(id)); + memcpy(nla_data(na), &id, sizeof(id)); req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + NLA_ALIGN(na->nla_len); ret = send_request(fd, &req, req.nlh.nlmsg_len); @@ -231,21 +113,21 @@ static int get_taskstats(int fd, int family_id, uint16_t attr_type, uint32_t id, genl = (struct genlmsghdr *)NLMSG_DATA(nlh); rem = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; na = (struct nlattr *)((char *)genl + GENL_HDRLEN); - while (taskstats_nla_ok(na, rem)) { + while (nla_ok(na, rem)) { if (na->nla_type == TASKSTATS_TYPE_AGGR_PID || na->nla_type == TASKSTATS_TYPE_AGGR_TGID) { - nested = (struct nlattr *)taskstats_nla_data(na); + nested = (struct nlattr *)nla_data(na); nrem = na->nla_len - NLA_HDRLEN; - while (taskstats_nla_ok(nested, nrem)) { + while (nla_ok(nested, nrem)) { if (nested->nla_type == TASKSTATS_TYPE_STATS) { - memcpy(stats, taskstats_nla_data(nested), + memcpy(stats, nla_data(nested), sizeof(*stats)); return 0; } - nested = taskstats_nla_next(nested, &nrem); + nested = nla_next(nested, &nrem); } } - na = taskstats_nla_next(na, &rem); + na = nla_next(na, &rem); } } From bc96ad381f05668123ce6fe760ad3a370fbf35ea Mon Sep 17 00:00:00 2001 From: Ziran Zhang Date: Sun, 12 Apr 2026 14:59:41 +0800 Subject: [PATCH 39/95] FAT: allow 0xE9 near jump in fat_read_static_bpb() fat_read_static_bpb() only accepts a short jump as a valid bootstrap code signature for DOS 1.x volumes when the dos1xfloppy mount option is used. However, according to the Microsoft fatgen103.doc, the BS_jmpBoot field has two allowed forms: 0xEB 0x?? 0x90 (short jump + NOP) and 0xE9 0x?? 0x?? (near jump). The specification explicitly states that either form is acceptable. This patch relaxes the check to also accept 0xE9 as the first byte of the jump instruction. Link: https://lore.kernel.org/20260412070109.5197-1-zhangcoder@yeah.net Signed-off-by: Ziran Zhang Acked-by: OGAWA Hirofumi Cc: Christian Brauner Signed-off-by: Andrew Morton --- fs/fat/inode.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/fat/inode.c b/fs/fat/inode.c index 3aa52481ad5c..d1e6c9b2f9df 100644 --- a/fs/fat/inode.c +++ b/fs/fat/inode.c @@ -1479,8 +1479,9 @@ static int fat_read_static_bpb(struct super_block *sb, int error = -EINVAL; unsigned i; - /* 16-bit DOS 1.x reliably wrote bootstrap short-jmp code */ - if (b->ignored[0] != 0xeb || b->ignored[2] != 0x90) { + /* 16-bit DOS 1.x reliably wrote bootstrap short-jmp or near-jmp code */ + if ((b->ignored[0] != 0xeb || b->ignored[2] != 0x90) && + (b->ignored[0] != 0xe9)) { if (!silent) fat_msg(sb, KERN_ERR, "%s; no bootstrapping code", notdos1x); From b6a1359bbe27a9fdb0c27d9cce962f5b9e53e61d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:47:32 +0200 Subject: [PATCH 40/95] xor: enable lock context analysis Patch series "misc lib/raid/ improvements v2". This series improves the benchmark-based algorithm selection and adds kunit benchmarks for both the XOR and raid6 libraries, and tidies up a few very minor other bits. This patch (of 8): The code doesn't have any locking, so this is trivial. Link: https://lore.kernel.org/20260715144825.95432-1-hch@lst.de Link: https://lore.kernel.org/20260715144825.95432-2-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Eric Biggers Signed-off-by: Andrew Morton --- lib/raid/xor/Makefile | 2 ++ lib/raid/xor/tests/Makefile | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/raid/xor/Makefile b/lib/raid/xor/Makefile index e8ecec3c09f9..9b0fad459cdb 100644 --- a/lib/raid/xor/Makefile +++ b/lib/raid/xor/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 +CONTEXT_ANALYSIS := y + ccflags-y += -I $(src) obj-$(CONFIG_XOR_BLOCKS) += xor.o diff --git a/lib/raid/xor/tests/Makefile b/lib/raid/xor/tests/Makefile index 661e8f6ffd1f..1cce833cd7fd 100644 --- a/lib/raid/xor/tests/Makefile +++ b/lib/raid/xor/tests/Makefile @@ -1,3 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +CONTEXT_ANALYSIS := y + obj-$(CONFIG_XOR_KUNIT_TEST) += xor_kunit.o From 2bfd85fd81cbec449469d3ea9c22d36e0a792bd1 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:47:33 +0200 Subject: [PATCH 41/95] xor: improve the runtime selection benchmark Use plain ktime_get_ns for the timing, use 4 + 1 disks for a realistic load, and report the throughput on the data disks instead of the that on the parity disk, which isn't all that useful. Link: https://lore.kernel.org/20260715144825.95432-3-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Eric Biggers Signed-off-by: Andrew Morton --- lib/raid/xor/xor-core.c | 55 +++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/lib/raid/xor/xor-core.c b/lib/raid/xor/xor-core.c index 50931fbf0324..f9d61b096b11 100644 --- a/lib/raid/xor/xor-core.c +++ b/lib/raid/xor/xor-core.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include "xor_impl.h" @@ -73,59 +72,56 @@ void __init xor_force(struct xor_block_template *tmpl) forced_template = tmpl; } -#define BENCH_SIZE 4096 +#define BENCH_SIZE SZ_4K +#define NR_SRCS 4 #define REPS 800U -static void __init -do_xor_speed(struct xor_block_template *tmpl, void *b1, void *b2) +static void __init do_xor_speed(struct xor_block_template *tmpl, void *dest, + void *srcs[NR_SRCS]) { - int speed; - unsigned long reps; - ktime_t min, start, t0; - void *srcs[1] = { b2 }; + u64 t; + int i; preempt_disable(); - - reps = 0; - t0 = ktime_get(); - /* delay start until time has advanced */ - while ((start = ktime_get()) == t0) - cpu_relax(); - do { + t = ktime_get_ns(); + for (i = 0; i < REPS; i++) { mb(); /* prevent loop optimization */ - tmpl->xor_gen(b1, srcs, 1, BENCH_SIZE); + tmpl->xor_gen(dest, srcs, NR_SRCS, BENCH_SIZE); mb(); - } while (reps++ < REPS || (t0 = ktime_get()) == start); - min = ktime_sub(t0, start); - + } + t = max(ktime_get_ns() - t, 1); preempt_enable(); - // bytes/ns == GB/s, multiply by 1000 to get MB/s [not MiB/s] - speed = (1000 * reps * BENCH_SIZE) / (unsigned int)ktime_to_ns(min); - tmpl->speed = speed; + /* bytes/ns == GB/s, multiply by 1000 to get MB/s [not MiB/s] */ + tmpl->speed = div64_u64((u64)BENCH_SIZE * REPS * NR_SRCS * 1000, t); - pr_info(" %-16s: %5d MB/sec\n", tmpl->name, speed); + pr_info(" %-16s: %5d MB/sec\n", tmpl->name, tmpl->speed); } static int __init calibrate_xor_blocks(void) { - void *b1, *b2; struct xor_block_template *f, *fastest; + void *srcs[NR_SRCS]; + void *buf, *dest; + int i; if (forced_template) return 0; - b1 = kmalloc(PAGE_SIZE * 4, GFP_KERNEL); - if (!b1) { + buf = kmalloc(BENCH_SIZE * (NR_SRCS + 1), GFP_KERNEL); + if (!buf) { pr_warn("xor: Yikes! No memory available.\n"); return -ENOMEM; } - b2 = b1 + 2*PAGE_SIZE + BENCH_SIZE; + get_random_bytes(buf, BENCH_SIZE * (NR_SRCS + 1)); + dest = buf; + for (i = 0; i < NR_SRCS; i++) + srcs[i] = buf + (i + 1) * BENCH_SIZE; pr_info("xor: measuring software checksum speed\n"); fastest = template_list; for (f = template_list; f; f = f->next) { - do_xor_speed(f, b1, b2); + do_xor_speed(f, dest, srcs); if (f->speed > fastest->speed) fastest = f; } @@ -133,9 +129,10 @@ static int __init calibrate_xor_blocks(void) pr_info("xor: using function: %s (%d MB/sec)\n", fastest->name, fastest->speed); - kfree(b1); + kfree(buf); return 0; } +#undef NR_SRCS #ifdef CONFIG_XOR_BLOCKS_ARCH #include "xor_arch.h" /* $SRCARCH/xor_arch.h */ From 75182f8d5732dcd5897665dcc86e7ea17b276a0b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:47:34 +0200 Subject: [PATCH 42/95] xor/kunit: fix a spelling error Link: https://lore.kernel.org/20260715144825.95432-4-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Eric Biggers Signed-off-by: Andrew Morton --- lib/raid/xor/tests/xor_kunit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/raid/xor/tests/xor_kunit.c b/lib/raid/xor/tests/xor_kunit.c index 0c2a3a420bf9..659ae3edbc25 100644 --- a/lib/raid/xor/tests/xor_kunit.c +++ b/lib/raid/xor/tests/xor_kunit.c @@ -85,7 +85,7 @@ static void xor_test(struct kunit *test) xor_generate_random_data(); /* - * If we're not using the entire buffer size, inject randomize + * If we're not using the entire buffer size, inject randomized * alignment into the buffer. */ max_alignment = XOR_KUNIT_MAX_BYTES - len; From 874d2edd07dbc29301bb2d48da66d09d641b5ff5 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:47:35 +0200 Subject: [PATCH 43/95] xor/kunit: add a benchmark Add a benchmark to test the XOR functions for more representative block sizes and numbers of disks. Including 64k would be useful here, but increasing the test buffer size increases the runtime of the functional kunit test too much unfortunately. The runtime numbers are reported in GB/s as the numbers of modern implementations are basically unreadable as MB/s. This means retro-architectures could report 0, but that is an easy tradeoff. Link: https://lore.kernel.org/20260715144825.95432-5-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Eric Biggers Signed-off-by: Andrew Morton --- lib/raid/Kconfig | 6 ++++ lib/raid/xor/tests/xor_kunit.c | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/lib/raid/Kconfig b/lib/raid/Kconfig index 978cd6ba08ac..c4eeb7c716c8 100644 --- a/lib/raid/Kconfig +++ b/lib/raid/Kconfig @@ -29,6 +29,12 @@ config XOR_KUNIT_TEST This is intended to help people writing architecture-specific optimized versions. If unsure, say N. +config XOR_BENCHMARK + bool "Benchmark for xor_gen" + depends on XOR_KUNIT_TEST + help + Include benchmarks in the KUnit test suite for xor_gen. + config RAID6_PQ tristate diff --git a/lib/raid/xor/tests/xor_kunit.c b/lib/raid/xor/tests/xor_kunit.c index 659ae3edbc25..1fb30b2c7c47 100644 --- a/lib/raid/xor/tests/xor_kunit.c +++ b/lib/raid/xor/tests/xor_kunit.c @@ -125,8 +125,70 @@ static void xor_test(struct kunit *test) } } +static void xor_benchmark(struct kunit *test) +{ + static const unsigned int nr_to_test[] = { + 4, 5, 6, 7, 8, 10, 12, 15, 16, 32, + }; + static const unsigned int len_to_test[] = { + SZ_4K, SZ_16K, + }; + unsigned int i, j, l; + u64 t; + + if (!IS_ENABLED(CONFIG_XOR_BENCHMARK)) + kunit_skip(test, "not enabled"); + + /* warm-up */ + for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) { + for (j = 0; j < ARRAY_SIZE(len_to_test); j++) { + for (l = 0; l < 10; l++) { + xor_gen(test_dest, test_buffers, nr_to_test[i], + len_to_test[j]); + } + } + } + + /* + * Preferably this would be a loop over len_to_test, but the kunit + * logging always adds a newline to each logged format string. + */ + static_assert(ARRAY_SIZE(len_to_test) == 2); + kunit_info(test, " \t%5u bytes\t%5u bytes\n", + len_to_test[0], len_to_test[1]); + + for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) { + unsigned int nr = nr_to_test[i]; + u64 speed[ARRAY_SIZE(len_to_test)]; + + KUNIT_ASSERT_LE(test, nr, XOR_KUNIT_MAX_BUFFERS); + + for (j = 0; j < ARRAY_SIZE(len_to_test); j++) { + unsigned int len = len_to_test[j]; + const unsigned long num_iters = 1000; + + KUNIT_ASSERT_GT(test, len, 0); + KUNIT_ASSERT_LE(test, len, XOR_KUNIT_MAX_BYTES); + + preempt_disable(); + t = ktime_get_ns(); + for (l = 0; l < num_iters; l++) + xor_gen(test_dest, test_buffers, nr, len); + t = max(ktime_get_ns() - t, 1); + preempt_enable(); + + speed[j] = div64_u64((u64)len * num_iters * nr, t); + } + + static_assert(ARRAY_SIZE(len_to_test) == 2); + kunit_info(test, "%3u disks:\t%5llu GB/s\t%5llu GB/s\n", + nr, speed[0], speed[1]); + } +} + static struct kunit_case xor_test_cases[] = { KUNIT_CASE(xor_test), + KUNIT_CASE(xor_benchmark), {}, }; From 2083d99bd831014dce561800bf6734c1c5a73828 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:47:36 +0200 Subject: [PATCH 44/95] raid6: enable lock context analysis The code doesn't have any locking, so this is trivial. Link: https://lore.kernel.org/20260715144825.95432-6-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Eric Biggers Signed-off-by: Andrew Morton --- lib/raid/raid6/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/raid/raid6/Makefile b/lib/raid/raid6/Makefile index 038d6c74d1ba..5cdb7223de2a 100644 --- a/lib/raid/raid6/Makefile +++ b/lib/raid/raid6/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 +CONTEXT_ANALYSIS := y + ccflags-y += -I $(src) ifeq ($(CONFIG_RAID6_PQ_ARCH),y) From d999af2192efe4896222b777363c960083617085 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:47:37 +0200 Subject: [PATCH 45/95] raid6: defer implementation selection when built-in Don't hold up early boot and defer the selection just like we've been doing for the RAID5 XOR code since commit 524ccdbdfb52 ("crypto: xor - defer load time benchmark to a later time"). This will also allow full use of the timing subsystem for benchmarking. Link: https://lore.kernel.org/20260715144825.95432-7-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Eric Biggers Signed-off-by: Andrew Morton --- lib/raid/raid6/algos.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/raid/raid6/algos.c b/lib/raid/raid6/algos.c index 6f5c89ab2b17..702e167a543a 100644 --- a/lib/raid/raid6/algos.c +++ b/lib/raid/raid6/algos.c @@ -239,10 +239,6 @@ static int __init raid6_select_algo(void) if (!IS_ENABLED(CONFIG_RAID6_PQ_BENCHMARK) || raid6_nr_algos == 1) { pr_info("raid6: skipped pq benchmark and selected %s\n", raid6_algos[raid6_nr_algos - 1]->name); - static_call_update(raid6_gen_syndrome_impl, - raid6_algos[raid6_nr_algos - 1]->gen_syndrome); - static_call_update(raid6_xor_syndrome_impl, - raid6_algos[raid6_nr_algos - 1]->xor_syndrome); return 0; } @@ -329,13 +325,34 @@ static int __init raid6_init(void) static_call_update(raid6_recov_datap_impl, raid6_recov_algo->datap); pr_info("raid6: using %s recovery algorithm\n", raid6_recov_algo->name); + /* + * Pick the last registered implementation as the temporary default until + * calibration happens. + */ + static_call_update(raid6_gen_syndrome_impl, + raid6_algos[raid6_nr_algos - 1]->gen_syndrome); + static_call_update(raid6_xor_syndrome_impl, + raid6_algos[raid6_nr_algos - 1]->xor_syndrome); + +#ifdef MODULE return raid6_select_algo(); +#else + return 0; +#endif } static void __exit raid6_exit(void) { } +/* + * When built-in we must register the default implementation before md + * initializes, but we don't want calibration to run that early as that + * would delay the boot process. + */ +#ifndef MODULE +device_initcall(raid6_select_algo); +#endif subsys_initcall(raid6_init); module_exit(raid6_exit); MODULE_LICENSE("GPL"); From cfbdbecb29e600007a4a6db9a84c0da27116424e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:47:38 +0200 Subject: [PATCH 46/95] raid6: improve the runtime selection benchmark Use plain ktime_get_ns for the timing, use 8 + 2 disks for a realistic load, and report the throughput on the data disks as that is what storage systems are measured on. Link: https://lore.kernel.org/20260715144825.95432-8-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Eric Biggers Signed-off-by: Andrew Morton --- lib/raid/raid6/algos.c | 77 +++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/lib/raid/raid6/algos.c b/lib/raid/raid6/algos.c index 702e167a543a..011aa9d0eb3c 100644 --- a/lib/raid/raid6/algos.c +++ b/lib/raid/raid6/algos.c @@ -152,40 +152,37 @@ void raid6_recov_datap(int disks, size_t bytes, int faila, void **ptrs) } EXPORT_SYMBOL_GPL(raid6_recov_datap); -#define RAID6_TIME_JIFFIES_LG2 4 -#define RAID6_TEST_DISKS 8 +#define BENCH_SIZE SZ_4K +#define NR_SRCS 8 +#define NR_DISKS (NR_SRCS + 2) +#define REPS 800U -static int raid6_choose_gen(void *(*const dptrs)[RAID6_TEST_DISKS], - const int disks) +static int raid6_choose_gen(void *dptrs[NR_DISKS], const int disks) { - /* work on the second half of the disks */ - int start = (disks >> 1) - 1, stop = disks - 3; const struct raid6_calls *best = NULL; unsigned long bestgenperf = 0; unsigned int i; for (i = 0; i < raid6_nr_algos; i++) { const struct raid6_calls *algo = raid6_algos[i]; - unsigned long perf = 0, j0, j1; + unsigned long perf = 0; + u64 t; + int i; preempt_disable(); - j0 = jiffies; - while ((j1 = jiffies) == j0) - cpu_relax(); - while (time_before(jiffies, - j1 + (1<gen_syndrome(disks, PAGE_SIZE, *dptrs); - perf++; - } + t = ktime_get_ns(); + for (i = 0; i < REPS; i++) + algo->gen_syndrome(disks, BENCH_SIZE, dptrs); + t = max(ktime_get_ns() - t, 1); preempt_enable(); + /* bytes/ns == GB/s, multiply by 1000 to get MB/s [not MiB/s] */ + perf = div64_u64((u64)BENCH_SIZE * REPS * NR_SRCS * 1000, t); if (perf > bestgenperf) { bestgenperf = perf; best = algo; } - pr_info("raid6: %-8s gen() %5ld MB/s\n", algo->name, - (perf * HZ * (disks-2)) >> - (20 - PAGE_SHIFT + RAID6_TIME_JIFFIES_LG2)); + pr_info("raid6: %-8s gen() %5lu MB/s\n", algo->name, perf); } if (!best) { @@ -197,28 +194,24 @@ static int raid6_choose_gen(void *(*const dptrs)[RAID6_TEST_DISKS], static_call_update(raid6_xor_syndrome_impl, best->xor_syndrome); pr_info("raid6: using algorithm %s gen() %ld MB/s\n", - best->name, - (bestgenperf * HZ * (disks - 2)) >> - (20 - PAGE_SHIFT + RAID6_TIME_JIFFIES_LG2)); + best->name, bestgenperf); if (best->xor_syndrome) { - unsigned long perf = 0, j0, j1; + /* work on the second half of the disks */ + int start = (disks / 2) - 1, stop = disks - 3; + u64 t; preempt_disable(); - j0 = jiffies; - while ((j1 = jiffies) == j0) - cpu_relax(); - while (time_before(jiffies, - j1 + (1 << RAID6_TIME_JIFFIES_LG2))) { - best->xor_syndrome(disks, start, stop, - PAGE_SIZE, *dptrs); - perf++; - } + t = ktime_get_ns(); + for (i = 0; i < REPS; i++) + best->xor_syndrome(disks, start, stop, BENCH_SIZE, + dptrs); + t = max(ktime_get_ns() - t, 1); preempt_enable(); - pr_info("raid6: .... xor() %ld MB/s, rmw enabled\n", - (perf * HZ * (disks - 2)) >> - (20 - PAGE_SHIFT + RAID6_TIME_JIFFIES_LG2 + 1)); + pr_info("raid6: .... xor() %llu MB/s, rmw enabled\n", + div64_u64((u64)BENCH_SIZE * REPS * NR_SRCS / 2 * 1000, + t)); } return 0; @@ -230,9 +223,9 @@ static int raid6_choose_gen(void *(*const dptrs)[RAID6_TEST_DISKS], static int __init raid6_select_algo(void) { - const int disks = RAID6_TEST_DISKS; + const int disks = NR_DISKS; + void *dptrs[NR_DISKS]; char *disk_ptr, *p; - void *dptrs[RAID6_TEST_DISKS]; int i, cycle; int error; @@ -243,7 +236,7 @@ static int __init raid6_select_algo(void) } /* prepare the buffer and fill it circularly with gfmul table */ - disk_ptr = kmalloc(PAGE_SIZE * RAID6_TEST_DISKS, GFP_KERNEL); + disk_ptr = kmalloc_array(NR_DISKS, BENCH_SIZE, GFP_KERNEL); if (!disk_ptr) { pr_err("raid6: Yikes! No memory available.\n"); return -ENOMEM; @@ -251,19 +244,19 @@ static int __init raid6_select_algo(void) p = disk_ptr; for (i = 0; i < disks; i++) - dptrs[i] = p + PAGE_SIZE * i; + dptrs[i] = p + BENCH_SIZE * i; - cycle = ((disks - 2) * PAGE_SIZE) / 65536; + cycle = ((disks - 2) * BENCH_SIZE) / 65536; for (i = 0; i < cycle; i++) { memcpy(p, raid6_gfmul, 65536); p += 65536; } - if ((disks - 2) * PAGE_SIZE % 65536) - memcpy(p, raid6_gfmul, (disks - 2) * PAGE_SIZE % 65536); + if ((disks - 2) * BENCH_SIZE % 65536) + memcpy(p, raid6_gfmul, (disks - 2) * BENCH_SIZE % 65536); /* select raid gen_syndrome function */ - error = raid6_choose_gen(&dptrs, disks); + error = raid6_choose_gen(dptrs, disks); kfree(disk_ptr); From 10dab13117fa8a2a47a40804c8b173163a1a3129 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:47:39 +0200 Subject: [PATCH 47/95] raid6/kunit: add a benchmark Add a benchmark to test the RAID 6 P/Q functions for more representative block sizes and numbers of disks. This splits the maximum alloc size used for the benchmark from the max size for the kunit test because recovery is currently limited to a single page at a time. Hopefully this will be fixed soon. The runtime numbers are reported in GB/s as the numbers of modern implementations are basically unreadable as MB/s. This means retro-architectures could report 0, but that is an easy tradeoff. Link: https://lore.kernel.org/20260715144825.95432-9-hch@lst.de Signed-off-by: Christoph Hellwig Cc: Eric Biggers Signed-off-by: Andrew Morton --- lib/raid/Kconfig | 6 +++ lib/raid/raid6/tests/raid6_kunit.c | 66 +++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/lib/raid/Kconfig b/lib/raid/Kconfig index c4eeb7c716c8..01f007b2522c 100644 --- a/lib/raid/Kconfig +++ b/lib/raid/Kconfig @@ -60,6 +60,12 @@ config RAID6_PQ_KUNIT_TEST This is intended to help people writing architecture-specific optimized versions. If unsure, say N. +config RAID6_PQ_KUNIT_BENCHMARK + bool "Benchmark for RAID6 PQ" + depends on RAID6_PQ_KUNIT_TEST + help + Include benchmarks in the KUnit test suite for raid P/Q generation. + config RAID6_PQ_BENCHMARK bool "Automatically choose fastest RAID6 PQ functions" depends on RAID6_PQ diff --git a/lib/raid/raid6/tests/raid6_kunit.c b/lib/raid/raid6/tests/raid6_kunit.c index 9f3e671a1224..7d0a1ec98001 100644 --- a/lib/raid/raid6/tests/raid6_kunit.c +++ b/lib/raid/raid6/tests/raid6_kunit.c @@ -18,6 +18,7 @@ MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); #define RAID6_KUNIT_MAX_BUFFERS 64 /* Including P and Q */ #define RAID6_KUNIT_MAX_FAILURES 2 #define RAID6_KUNIT_MAX_BYTES PAGE_SIZE +#define RAID6_KUNIT_ALLOC_BYTES SZ_16K static struct rnd_state rng; static void *test_buffers[RAID6_KUNIT_MAX_BUFFERS]; @@ -229,6 +230,68 @@ static void raid6_test(struct kunit *test) raid6_test_one(test); } +static void raid6_benchmark(struct kunit *test) +{ + static const unsigned int nr_to_test[] = { + 4, 5, 6, 7, 8, 10, 12, 15, 16, 32, + }; + static const unsigned int len_to_test[] = { + SZ_4K, SZ_16K, + }; + unsigned int i, j, l; + u64 t; + + if (!IS_ENABLED(CONFIG_RAID6_PQ_KUNIT_BENCHMARK)) + kunit_skip(test, "not enabled"); + + /* warm-up */ + for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) { + for (j = 0; j < ARRAY_SIZE(len_to_test); j++) { + for (l = 0; l < 10; l++) { + raid6_gen_syndrome(nr_to_test[i], + len_to_test[j], test_buffers); + } + } + } + + /* + * Preferably this would be a loop over len_to_test, but the kunit + * logging always adds a newline to each logged format string. + */ + static_assert(ARRAY_SIZE(len_to_test) == 2); + kunit_info(test, " \t%5u bytes\t%5u bytes\n", + len_to_test[0], len_to_test[1]); + + for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) { + unsigned int nr = nr_to_test[i]; + u64 speed[ARRAY_SIZE(len_to_test)]; + + KUNIT_ASSERT_LE(test, nr, RAID6_KUNIT_MAX_BUFFERS); + + for (j = 0; j < ARRAY_SIZE(len_to_test); j++) { + unsigned int len = len_to_test[j]; + const unsigned long num_iters = 1000; + + KUNIT_ASSERT_GT(test, len, 0); + KUNIT_ASSERT_LE(test, len, RAID6_KUNIT_ALLOC_BYTES); + + preempt_disable(); + t = ktime_get_ns(); + for (l = 0; l < num_iters; l++) + raid6_gen_syndrome(nr_to_test[i], + len_to_test[j], test_buffers); + t = max(ktime_get_ns() - t, 1); + preempt_enable(); + + speed[j] = div64_u64((u64)len * num_iters * nr, t); + } + + static_assert(ARRAY_SIZE(len_to_test) == 2); + kunit_info(test, "%3u disks:\t%5llu GB/s\t%5llu GB/s\n", + nr, speed[0], speed[1]); + } +} + static const void *raid6_gen_params(struct kunit *test, const void *prev, char *desc) { @@ -256,6 +319,7 @@ static const void *raid6_gen_params(struct kunit *test, const void *prev, static struct kunit_case raid6_test_cases[] = { KUNIT_CASE_PARAM(raid6_test, raid6_gen_params), + KUNIT_CASE(raid6_benchmark), {}, }; @@ -270,7 +334,7 @@ static int raid6_suite_init(struct kunit_suite *suite) * so that it is immediately followed by a guard page. This allows * buffer overreads to be detected, even in assembly code. */ - test_buflen = round_up(RAID6_KUNIT_MAX_BYTES, PAGE_SIZE); + test_buflen = round_up(RAID6_KUNIT_ALLOC_BYTES, PAGE_SIZE); for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++) { test_recov_buffers[i] = vmalloc(test_buflen); if (!test_recov_buffers[i]) From 83e98dbf19ab64e8528e101e20f8d50e1aaa68a8 Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Wed, 15 Jul 2026 10:09:57 +0800 Subject: [PATCH 48/95] fat: release buffer head after rebuilding parent fat_scan_logstart() leaves the matching directory entry's buffer head in sinfo.bh for the caller to release, just like fat_scan(). fat_rebuild_parent() uses the directory entry to rebuild the parent inode for the nostale_ro NFS export path, but does not release sinfo.bh after a successful scan. Release it once fat_build_inode() has consumed the directory entry data. Link: https://lore.kernel.org/20260715020957.1096309-1-chenyichong@uniontech.com Fixes: f1e6fb0ab451 ("fat (exportfs): rebuild directory-inode if fat_dget()") Signed-off-by: Yichong Chen Acked-by: OGAWA Hirofumi Cc: Christian Brauner Cc: Amit Sahrawat Cc: chenyichong Cc: Namjae Jeon Cc: Ravishankar N Signed-off-by: Andrew Morton --- fs/fat/nfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/fat/nfs.c b/fs/fat/nfs.c index 509eea96a457..6e1b371711ed 100644 --- a/fs/fat/nfs.c +++ b/fs/fat/nfs.c @@ -250,8 +250,10 @@ struct inode *fat_rebuild_parent(struct super_block *sb, int parent_logstart) MSDOS_I(dummy_grand_parent)->i_pos = -1; } - if (!fat_scan_logstart(dummy_grand_parent, clus_to_match, &sinfo)) + if (!fat_scan_logstart(dummy_grand_parent, clus_to_match, &sinfo)) { parent = fat_build_inode(sb, sinfo.de, sinfo.i_pos); + brelse(sinfo.bh); + } brelse(parent_bh); iput(dummy_grand_parent); From 31288373f62641422118341a6f0c7b97f3a7fd1d Mon Sep 17 00:00:00 2001 From: Shivank Sharma Date: Thu, 16 Jul 2026 19:45:45 +0530 Subject: [PATCH 49/95] tools/accounting: fix macro typos in getdelays Correct the spelling of PRINT_FILED_DELAY and PRINT_FILED_DELAY_WITH_TS to PRINT_FIELD_DELAY and PRINT_FIELD_DELAY_WITH_TS respectively. This resolves typo naming errors across the macro definitions and their matching inside print_delayacct(). Link: https://lore.kernel.org/20260716141545.1292951-1-shivanksharma2376543@gmail.com Signed-off-by: Shivank Sharma Cc: Fan Yu Cc: Wang Yaxin Signed-off-by: Andrew Morton --- tools/accounting/getdelays.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tools/accounting/getdelays.c b/tools/accounting/getdelays.c index caa5fe9dd573..52930629ec3f 100644 --- a/tools/accounting/getdelays.c +++ b/tools/accounting/getdelays.c @@ -302,7 +302,7 @@ static const char *format_timespec(struct __kernel_timespec *ts) average_ms((double)(t)->cpu_delay_total, (t)->cpu_count)); \ } \ } while (0) -#define PRINT_FILED_DELAY(name, version, t, count, total, max, min) \ +#define PRINT_FIELD_DELAY(name, version, t, count, total, max, min) \ do { \ if (version >= 16) { \ printf("%-10s%15s%15s%15s%15s%15s\n", \ @@ -324,7 +324,7 @@ static const char *format_timespec(struct __kernel_timespec *ts) } \ } while (0) -#define PRINT_FILED_DELAY_WITH_TS(name, version, t, count, total, max, min, max_ts) \ +#define PRINT_FIELD_DELAY_WITH_TS(name, version, t, count, total, max, min, max_ts) \ do { \ if (version >= 17) { \ printf("%-10s%15s%15s%15s%15s%15s%25s\n", \ @@ -365,71 +365,71 @@ static void print_delayacct(struct taskstats *t) /* Use new macro with timestamp support for version >= 17 */ if (t->version >= 17) { - PRINT_FILED_DELAY_WITH_TS("IO", t->version, t, + PRINT_FIELD_DELAY_WITH_TS("IO", t->version, t, blkio_count, blkio_delay_total, blkio_delay_max, blkio_delay_min, blkio_delay_max_ts); - PRINT_FILED_DELAY_WITH_TS("SWAP", t->version, t, + PRINT_FIELD_DELAY_WITH_TS("SWAP", t->version, t, swapin_count, swapin_delay_total, swapin_delay_max, swapin_delay_min, swapin_delay_max_ts); - PRINT_FILED_DELAY_WITH_TS("RECLAIM", t->version, t, + PRINT_FIELD_DELAY_WITH_TS("RECLAIM", t->version, t, freepages_count, freepages_delay_total, freepages_delay_max, freepages_delay_min, freepages_delay_max_ts); - PRINT_FILED_DELAY_WITH_TS("THRASHING", t->version, t, + PRINT_FIELD_DELAY_WITH_TS("THRASHING", t->version, t, thrashing_count, thrashing_delay_total, thrashing_delay_max, thrashing_delay_min, thrashing_delay_max_ts); if (t->version >= 11) { - PRINT_FILED_DELAY_WITH_TS("COMPACT", t->version, t, + PRINT_FIELD_DELAY_WITH_TS("COMPACT", t->version, t, compact_count, compact_delay_total, compact_delay_max, compact_delay_min, compact_delay_max_ts); } if (t->version >= 13) { - PRINT_FILED_DELAY_WITH_TS("WPCOPY", t->version, t, + PRINT_FIELD_DELAY_WITH_TS("WPCOPY", t->version, t, wpcopy_count, wpcopy_delay_total, wpcopy_delay_max, wpcopy_delay_min, wpcopy_delay_max_ts); } if (t->version >= 14) { - PRINT_FILED_DELAY_WITH_TS("IRQ", t->version, t, + PRINT_FIELD_DELAY_WITH_TS("IRQ", t->version, t, irq_count, irq_delay_total, irq_delay_max, irq_delay_min, irq_delay_max_ts); } } else { /* Use original macro for older versions */ - PRINT_FILED_DELAY("IO", t->version, t, + PRINT_FIELD_DELAY("IO", t->version, t, blkio_count, blkio_delay_total, blkio_delay_max, blkio_delay_min); - PRINT_FILED_DELAY("SWAP", t->version, t, + PRINT_FIELD_DELAY("SWAP", t->version, t, swapin_count, swapin_delay_total, swapin_delay_max, swapin_delay_min); - PRINT_FILED_DELAY("RECLAIM", t->version, t, + PRINT_FIELD_DELAY("RECLAIM", t->version, t, freepages_count, freepages_delay_total, freepages_delay_max, freepages_delay_min); - PRINT_FILED_DELAY("THRASHING", t->version, t, + PRINT_FIELD_DELAY("THRASHING", t->version, t, thrashing_count, thrashing_delay_total, thrashing_delay_max, thrashing_delay_min); if (t->version >= 11) { - PRINT_FILED_DELAY("COMPACT", t->version, t, + PRINT_FIELD_DELAY("COMPACT", t->version, t, compact_count, compact_delay_total, compact_delay_max, compact_delay_min); } if (t->version >= 13) { - PRINT_FILED_DELAY("WPCOPY", t->version, t, + PRINT_FIELD_DELAY("WPCOPY", t->version, t, wpcopy_count, wpcopy_delay_total, wpcopy_delay_max, wpcopy_delay_min); } if (t->version >= 14) { - PRINT_FILED_DELAY("IRQ", t->version, t, + PRINT_FIELD_DELAY("IRQ", t->version, t, irq_count, irq_delay_total, irq_delay_max, irq_delay_min); } From bc70726ddad53c7e9a9a85915bf2415b0d4f42f9 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Mon, 13 Jul 2026 22:56:25 +0200 Subject: [PATCH 50/95] ocfs2: validate directory-index entry counts when reading metadata ocfs2_validate_dx_leaf() and ocfs2_validate_dx_root() check the ECC and signature of an indexed-directory block before it reaches higher-level callers, but neither validator bounds the ocfs2_dx_entry_list counts against the capacity of the block that holds them. ocfs2_dx_dir_search() then walks for (i = 0; i < le16_to_cpu(entry_list->de_num_used); i++) dx_entry = &entry_list->de_entries[i]; over de_num_used entries with no bounds check. entry_list is either dx_leaf->dl_list (from ocfs2_read_dx_leaf) or, for an inline root, dx_root->dr_entries. A crafted on-disk image can set de_num_used (and de_count, which is the __counted_by_le() bound of de_entries) to 0xffff and make the walk read far past the end of the 4KB metadata block, giving a slab out-of-bounds read reachable from any path lookup, stat() or open() on an indexed directory once the image is mounted. Commit 775c17386a6f ("ocfs2: validate dx_root extent list fields during block read") already bounds dr_list for the non-inline dx_root, but left the inline dr_entries path and the dx_leaf dl_list unchecked. Add the same read-time validation for both entry lists: de_count must equal the capacity of the block (ocfs2_dx_entries_per_leaf()/per_root()) and de_num_used must not exceed de_count, rejecting corrupted metadata with -EFSCORRUPTED before ocfs2_dx_dir_search() can walk an out-of-range entry array. de_count is always written as exactly the block capacity when a leaf or inline root is formatted, so the equality check does not reject any valid image. Found by 0sec automated security-research tooling (https://0sec.ai). Link: https://lore.kernel.org/20260713205625.92391-1-doruk@0sec.ai Fixes: 9b7895efac90 ("ocfs2: Add a name indexed b-tree to directory inodes") Fixes: 4ed8a6bb083b ("ocfs2: Store dir index records inline") Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Reviewed-by: Joseph Qi Cc: Joel Becker Cc: Kees Cook Cc: Mark Fasheh Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/dir.c | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 047d1b486f4a..7e09a5a4d49e 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -625,6 +625,28 @@ static int ocfs2_validate_dx_root(struct super_block *sb, le16_to_cpu(el->l_count)); goto bail; } + } else { + struct ocfs2_dx_entry_list *dl_list = &dx_root->dr_entries; + + if (le16_to_cpu(dl_list->de_count) != + ocfs2_dx_entries_per_root(sb)) { + ret = ocfs2_error(sb, + "Dir Index Root # %llu has invalid de_count %u (expected %u)\n", + (unsigned long long)le64_to_cpu(dx_root->dr_blkno), + le16_to_cpu(dl_list->de_count), + ocfs2_dx_entries_per_root(sb)); + goto bail; + } + + if (le16_to_cpu(dl_list->de_num_used) > + le16_to_cpu(dl_list->de_count)) { + ret = ocfs2_error(sb, + "Dir Index Root # %llu has invalid de_num_used %u (de_count %u)\n", + (unsigned long long)le64_to_cpu(dx_root->dr_blkno), + le16_to_cpu(dl_list->de_num_used), + le16_to_cpu(dl_list->de_count)); + goto bail; + } } bail: @@ -664,10 +686,25 @@ static int ocfs2_validate_dx_leaf(struct super_block *sb, return ret; } - if (!OCFS2_IS_VALID_DX_LEAF(dx_leaf)) { - ret = ocfs2_error(sb, "Dir Index Leaf has bad signature %.*s\n", - 7, dx_leaf->dl_signature); - } + if (!OCFS2_IS_VALID_DX_LEAF(dx_leaf)) + return ocfs2_error(sb, "Dir Index Leaf has bad signature %.*s\n", + 7, dx_leaf->dl_signature); + + if (le16_to_cpu(dx_leaf->dl_list.de_count) != + ocfs2_dx_entries_per_leaf(sb)) + return ocfs2_error(sb, + "Dir Index Leaf # %llu has invalid de_count %u (expected %u)\n", + (unsigned long long)le64_to_cpu(dx_leaf->dl_blkno), + le16_to_cpu(dx_leaf->dl_list.de_count), + ocfs2_dx_entries_per_leaf(sb)); + + if (le16_to_cpu(dx_leaf->dl_list.de_num_used) > + le16_to_cpu(dx_leaf->dl_list.de_count)) + return ocfs2_error(sb, + "Dir Index Leaf # %llu has invalid de_num_used %u (de_count %u)\n", + (unsigned long long)le64_to_cpu(dx_leaf->dl_blkno), + le16_to_cpu(dx_leaf->dl_list.de_num_used), + le16_to_cpu(dx_leaf->dl_list.de_count)); return ret; } From fd3c1fd8baf434d6b52c158d05ba2b3ef31fab41 Mon Sep 17 00:00:00 2001 From: Paul White Date: Mon, 20 Jul 2026 16:40:01 -0400 Subject: [PATCH 51/95] selftests: ipc: change operation not supported error number The application doesn't know what ENOTSUPP means, as it is a kernelspace error code and the application doesn't have access to kernelspace error codes.I used EOPNOTSUPP in its place as that is an error number the application will recognize and know an operation is being attempted that it cannot support. Link: https://lore.kernel.org/20260720204001.1663473-1-paul.white.kernel@gmail.com Signed-off-by: Paul White Cc: Shuah Khan Cc: Wei Yang Signed-off-by: Andrew Morton --- tools/testing/selftests/ipc/msgque.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/ipc/msgque.c b/tools/testing/selftests/ipc/msgque.c index 82f73cdae120..4b4c1abfac0f 100644 --- a/tools/testing/selftests/ipc/msgque.c +++ b/tools/testing/selftests/ipc/msgque.c @@ -161,7 +161,7 @@ int dump_queue(struct msgque_data *msgque) ret = msgrcv(msgque->msq_id, &msgque->messages[i].mtype, MAX_MSG_SIZE, i, IPC_NOWAIT | MSG_COPY); if (ret < 0) { - if (errno == ENOSYS) + if (errno == EOPNOTSUPP) ksft_exit_skip("MSG_COPY not supported\n"); ksft_test_result_fail("Failed to copy IPC message: %m (%d)\n", errno); From 82ea9d4fc05fb7a387db547c6a7c0aa6a3719616 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Tue, 21 Jul 2026 13:28:40 +0300 Subject: [PATCH 52/95] ocfs2: always run deallocs on copy-on-write completion Local fuzzing of 6.12.94 has found the following memory leak caused by doing 'copy_file_range()' within the same filesystem: unreferenced object 0xffff88812192c980 (size 32): comm "syz.0.49", pid 12095, jiffies 4294964143 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 ................ c0 c5 92 21 81 88 ff ff 00 02 00 00 00 06 00 00 ...!............ backtrace (crc 7068d63f): kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline] slab_post_alloc_hook mm/slub.c:4152 [inline] slab_alloc_node mm/slub.c:4197 [inline] __kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358 kmalloc_noprof include/linux/slab.h:878 [inline] ocfs2_find_per_slot_free_list fs/ocfs2/alloc.c:6618 [inline] ocfs2_cache_block_dealloc+0x155/0x4b0 fs/ocfs2/alloc.c:6786 ocfs2_cache_extent_block_free fs/ocfs2/alloc.c:6819 [inline] ocfs2_unlink_path+0x286/0x450 fs/ocfs2/alloc.c:2613 ocfs2_rotate_subtree_left fs/ocfs2/alloc.c:2779 [inline] __ocfs2_rotate_tree_left+0x1f6f/0x2da0 fs/ocfs2/alloc.c:2985 ocfs2_rotate_tree_left+0x283/0xe00 fs/ocfs2/alloc.c:3237 ocfs2_try_to_merge_extent+0xf56/0x1a20 fs/ocfs2/alloc.c:3825 ocfs2_split_extent+0x15f4/0x2940 fs/ocfs2/alloc.c:5138 ocfs2_clear_ext_refcount+0x2f6/0x550 fs/ocfs2/refcounttree.c:3098 ocfs2_replace_clusters fs/ocfs2/refcounttree.c:3131 [inline] ocfs2_make_clusters_writable fs/ocfs2/refcounttree.c:3255 [inline] ocfs2_replace_cow+0x991/0x1660 fs/ocfs2/refcounttree.c:3349 ocfs2_refcount_cow_hunk fs/ocfs2/refcounttree.c:3427 [inline] ocfs2_refcount_cow+0x5e1/0x9f0 fs/ocfs2/refcounttree.c:3470 ocfs2_prepare_inode_for_write fs/ocfs2/file.c:2340 [inline] ocfs2_file_write_iter+0xbda/0x1880 fs/ocfs2/file.c:2451 iter_file_splice_write+0x890/0xf60 fs/splice.c:743 do_splice_from fs/splice.c:944 [inline] direct_splice_actor+0x232/0x480 fs/splice.c:1167 splice_direct_to_actor+0x4b4/0xb60 fs/splice.c:1111 do_splice_direct_actor fs/splice.c:1210 [inline] do_splice_direct+0x10f/0x1c0 fs/splice.c:1236 do_sendfile+0x430/0xbf0 fs/read_write.c:1388 unreferenced object 0xffff88812192c5c0 (size 32): comm "syz.0.49", pid 12095, jiffies 4294964143 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 29 70 00 00 00 00 00 00 19 00 00 00 00 00 00 00 )p.............. backtrace (crc afec850f): kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline] slab_post_alloc_hook mm/slub.c:4152 [inline] slab_alloc_node mm/slub.c:4197 [inline] __kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358 kmalloc_noprof include/linux/slab.h:878 [inline] kzalloc_noprof include/linux/slab.h:1014 [inline] ocfs2_cache_block_dealloc+0x25c/0x4b0 fs/ocfs2/alloc.c:6793 ocfs2_cache_extent_block_free fs/ocfs2/alloc.c:6819 [inline] ocfs2_unlink_path+0x286/0x450 fs/ocfs2/alloc.c:2613 ocfs2_rotate_subtree_left fs/ocfs2/alloc.c:2779 [inline] __ocfs2_rotate_tree_left+0x1f6f/0x2da0 fs/ocfs2/alloc.c:2985 ocfs2_rotate_tree_left+0x283/0xe00 fs/ocfs2/alloc.c:3237 ocfs2_try_to_merge_extent+0xf56/0x1a20 fs/ocfs2/alloc.c:3825 ocfs2_split_extent+0x15f4/0x2940 fs/ocfs2/alloc.c:5138 ocfs2_clear_ext_refcount+0x2f6/0x550 fs/ocfs2/refcounttree.c:3098 ocfs2_replace_clusters fs/ocfs2/refcounttree.c:3131 [inline] ocfs2_make_clusters_writable fs/ocfs2/refcounttree.c:3255 [inline] ocfs2_replace_cow+0x991/0x1660 fs/ocfs2/refcounttree.c:3349 ocfs2_refcount_cow_hunk fs/ocfs2/refcounttree.c:3427 [inline] ocfs2_refcount_cow+0x5e1/0x9f0 fs/ocfs2/refcounttree.c:3470 ocfs2_prepare_inode_for_write fs/ocfs2/file.c:2340 [inline] ocfs2_file_write_iter+0xbda/0x1880 fs/ocfs2/file.c:2451 iter_file_splice_write+0x890/0xf60 fs/splice.c:743 do_splice_from fs/splice.c:944 [inline] direct_splice_actor+0x232/0x480 fs/splice.c:1167 splice_direct_to_actor+0x4b4/0xb60 fs/splice.c:1111 do_splice_direct_actor fs/splice.c:1210 [inline] do_splice_direct+0x10f/0x1c0 fs/splice.c:1236 do_sendfile+0x430/0xbf0 fs/read_write.c:1388 This happens when 'ocfs2_cache_block_dealloc()' called from 'ocfs2_cache_extent_block_free()' uses the suballocator to schedule extent removal, so 'ocfs2_run_deallocs()' should be run unconditionally to complete the removal with 'ocfs2_free_cached_blocks()'. An extra semi-automated static analysis [1] suspects that the same scenario looks possible in 'ocfs2_attach_refcount_tree()' and 'ocfs2_reflink_remap_blocks()' as well, but, since 'ocfs2_run_deallocs()' is a safe no-op for an empty dealloc context, 'ocfs2_create_reflink_node()' and 'ocfs2_reflink_xattrs()' may be adjusted in the same way too, thus keeping the code pattern consistent. Link: https://lore.kernel.org/20260721102840.387663-1-dmantipov@yandex.ru Link: https://lore.kernel.org/ocfs2-devel/f1d7e266-4b44-41b9-98c0-5b3868a8d9c3@yandex.ru [1] Fixes: 6f70fa519976 ("ocfs2: Add CoW support.") Signed-off-by: Dmitry Antipov Suggested-by: Joseph Qi Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/refcounttree.c | 20 ++++++++------------ fs/ocfs2/xattr.c | 5 ++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c index 63d6cb326e30..d9f22b4a2654 100644 --- a/fs/ocfs2/refcounttree.c +++ b/fs/ocfs2/refcounttree.c @@ -3387,10 +3387,9 @@ static int ocfs2_replace_cow(struct ocfs2_cow_context *context) cow_start += num_clusters; } - if (ocfs2_dealloc_has_cluster(&context->dealloc)) { + if (ocfs2_dealloc_has_cluster(&context->dealloc)) ocfs2_schedule_truncate_log_flush(osb, 1); - ocfs2_run_deallocs(osb, &context->dealloc); - } + ocfs2_run_deallocs(osb, &context->dealloc); return ret; } @@ -3873,10 +3872,9 @@ static int ocfs2_attach_refcount_tree(struct inode *inode, ocfs2_unlock_refcount_tree(osb, ref_tree, 1); brelse(ref_root_bh); - if (!ret && ocfs2_dealloc_has_cluster(&dealloc)) { + if (!ret && ocfs2_dealloc_has_cluster(&dealloc)) ocfs2_schedule_truncate_log_flush(osb, 1); - ocfs2_run_deallocs(osb, &dealloc); - } + ocfs2_run_deallocs(osb, &dealloc); out: /* * Empty the extent map so that we may get the right extent @@ -4162,10 +4160,9 @@ static int ocfs2_create_reflink_node(struct inode *s_inode, ocfs2_unlock_refcount_tree(osb, ref_tree, 1); brelse(ref_root_bh); out: - if (ocfs2_dealloc_has_cluster(&dealloc)) { + if (ocfs2_dealloc_has_cluster(&dealloc)) ocfs2_schedule_truncate_log_flush(osb, 1); - ocfs2_run_deallocs(osb, &dealloc); - } + ocfs2_run_deallocs(osb, &dealloc); return ret; } @@ -4718,10 +4715,9 @@ loff_t ocfs2_reflink_remap_blocks(struct inode *s_inode, } out: - if (ocfs2_dealloc_has_cluster(&dealloc)) { + if (ocfs2_dealloc_has_cluster(&dealloc)) ocfs2_schedule_truncate_log_flush(osb, 1); - ocfs2_run_deallocs(osb, &dealloc); - } + ocfs2_run_deallocs(osb, &dealloc); return ret; } diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index d23364c30e1a..ed774bded974 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -7424,10 +7424,9 @@ int ocfs2_reflink_xattrs(struct inode *old_inode, ref_tree, 1); brelse(ref_root_bh); - if (ocfs2_dealloc_has_cluster(&dealloc)) { + if (ocfs2_dealloc_has_cluster(&dealloc)) ocfs2_schedule_truncate_log_flush(OCFS2_SB(old_inode->i_sb), 1); - ocfs2_run_deallocs(OCFS2_SB(old_inode->i_sb), &dealloc); - } + ocfs2_run_deallocs(OCFS2_SB(old_inode->i_sb), &dealloc); out: return ret; From af09df89db9a68a1d76df0f75667998135bc8d65 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Wed, 22 Jul 2026 20:49:31 +0800 Subject: [PATCH 53/95] ocfs2: cluster: don't sleep while holding o2hb_live_lock in o2hb_region_pin() Patch series "ocfs2: cluster: o2hb_region_pin() fixes", v2. This series fixes three related issues in o2hb_region_pin(), all are from the original implementation in commit: 58a3158a5d17 ("ocfs2/cluster: Pin/unpin o2hb regions"): 1) It is called with o2hb_live_lock (a spinlock) held, but the underlying configfs_depend_item() sleeps (takes inode rwsem and pins the filesystem). This triggers BUG under CONFIG_DEBUG_ATOMIC_SLEEP. 2) When called from the configfs drop_item callback, it creates a lock order inversion: parent inode_lock -> configfs root inode_lock, which can deadlock against subsystem unregistration paths taking root -> parent. 3) If pinning fails partway through o2hb_region_inc_user(), the o2hb_dependent_users counter is leaked and partially-pinned regions are never released, leaving heartbeat regions unprotected on subsequent mounts. Patch 1 reworks o2hb_region_pin() to drop o2hb_live_lock across each sleeping configfs_depend_item() call, using a config_item reference to keep the region alive while unlocked. Patch 2 adds a from_callback parameter to select configfs_depend_item_unlocked() when called from configfs context, avoiding the inode_lock nesting. Patch 3 fixes the error path in o2hb_region_inc_user() to unpin and decrement the counter on failure. This patch (of 3): o2hb_region_pin() is always called with the o2hb_live_lock spinlock held (from o2hb_region_inc_user() and o2hb_heartbeat_group_drop_item()), but it calls o2nm_depend_item() -> configfs_depend_item(), which sleeps: it pins the configfs filesystem and takes the configfs root inode rwsem. Under CONFIG_DEBUG_ATOMIC_SLEEP this triggers: BUG: sleeping function called from invalid context at kernel/locking/rwsem.c in_atomic(): 1, ... name: mount.ocfs2 down_write configfs_depend_item o2hb_region_pin o2hb_region_inc_user o2hb_register_callback dlm_register_domain_handlers ... ocfs2_dlm_init ocfs2_mount_volume ocfs2_fill_super Rework o2hb_region_pin() to pin one region at a time with the lock dropped across the sleeping call: under o2hb_live_lock find the next eligible region and take a config_item reference to keep it alive, drop the lock, call o2nm_depend_item(), then retake the lock and record the pin. The config_item_put() is done with the lock released as well, since o2hb_region_release() also acquires o2hb_live_lock and can sleep. The region list may change while unlocked, so the scan restarts from the top after each pin. Local heartbeat still pins only the matching region; global heartbeat pins all eligible regions. The unpin path is unaffected: configfs_undepend_item() only takes a spinlock and does not sleep. Link: https://lore.kernel.org/20260722124933.430554-1-joseph.qi@linux.alibaba.com Link: https://lore.kernel.org/20260722124933.430554-2-joseph.qi@linux.alibaba.com Fixes: 58a3158a5d17 ("ocfs2/cluster: Pin/unpin o2hb regions") Signed-off-by: Joseph Qi Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/cluster/heartbeat.c | 128 ++++++++++++++++++++++++++++------- 1 file changed, 102 insertions(+), 26 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index 29542edbc992..5ca1d9c0c657 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -43,6 +43,14 @@ static DECLARE_RWSEM(o2hb_callback_sem); * whenever any of the threads sees activity from the node in its region. */ static DEFINE_SPINLOCK(o2hb_live_lock); +/* + * Serializes region pin/unpin dependency management (o2hb_dependent_users + * and the o2nm_depend_item()/o2nm_undepend_item() calls). o2hb_region_pin() + * has to drop o2hb_live_lock across the sleeping o2nm_depend_item(), so the + * spinlock alone can no longer keep pin and unpin mutually exclusive; this + * mutex, taken outside o2hb_live_lock, does. + */ +static DEFINE_MUTEX(o2hb_dependency_mutex); static struct list_head o2hb_live_slots[O2NM_MAX_NODES]; static unsigned long o2hb_live_node_bitmap[BITS_TO_LONGS(O2NM_MAX_NODES)]; static LIST_HEAD(o2hb_node_events); @@ -2172,6 +2180,7 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group, * If global heartbeat active and there are dependent users, * pin all regions if quorum region count <= CUT_OFF */ + mutex_lock(&o2hb_dependency_mutex); spin_lock(&o2hb_live_lock); if (!o2hb_dependent_users) @@ -2183,6 +2192,7 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group, unlock: spin_unlock(&o2hb_live_lock); + mutex_unlock(&o2hb_dependency_mutex); } static ssize_t o2hb_heartbeat_group_dead_threshold_show(struct config_item *item, @@ -2322,46 +2332,108 @@ EXPORT_SYMBOL_GPL(o2hb_setup_callback); */ static int o2hb_region_pin(const char *region_uuid) { - int ret = 0, found = 0; - struct o2hb_region *reg; + int ret = 0, found; + struct o2hb_region *reg, *pinned; char *uuid; assert_spin_locked(&o2hb_live_lock); - list_for_each_entry(reg, &o2hb_all_regions, hr_all_item) { - if (reg->hr_item_dropped) - continue; + do { + found = 0; + pinned = NULL; - uuid = config_item_name(®->hr_item); - - /* local heartbeat */ - if (region_uuid) { - if (strcmp(region_uuid, uuid)) + list_for_each_entry(reg, &o2hb_all_regions, hr_all_item) { + if (reg->hr_item_dropped) continue; - found = 1; + + uuid = config_item_name(®->hr_item); + + /* local heartbeat */ + if (region_uuid) { + if (strcmp(region_uuid, uuid)) + continue; + found = 1; + } + + if (reg->hr_item_pinned || reg->hr_item_dropped) { + if (found) + break; + continue; + } + + /* + * Found a region that needs pinning. Take a reference + * so it stays alive while we drop the lock below. + */ + pinned = reg; + config_item_get(®->hr_item); + break; } - if (reg->hr_item_pinned || reg->hr_item_dropped) - goto skip_pin; + if (!pinned) + break; + + uuid = config_item_name(&pinned->hr_item); + + /* + * o2nm_depend_item() -> configfs_depend_item() can sleep (it + * takes the configfs root inode rwsem), so it must not run + * under o2hb_live_lock. Drop the lock across it; @pinned is + * kept alive by the reference taken above. The region list may + * change while unlocked, so we rescan from the top afterwards. + */ + spin_unlock(&o2hb_live_lock); /* Ignore ENOENT only for local hb (userdlm domain) */ - ret = o2nm_depend_item(®->hr_item); + ret = o2nm_depend_item(&pinned->hr_item); + + spin_lock(&o2hb_live_lock); if (!ret) { - mlog(ML_CLUSTER, "Pin region %s\n", uuid); - reg->hr_item_pinned = 1; - } else { - if (ret == -ENOENT && found) - ret = 0; - else { - mlog(ML_ERROR, "Pin region %s fails with %d\n", - uuid, ret); + /* + * o2hb_live_lock was dropped across o2nm_depend_item(). + * o2hb_set_quorum_device() runs in the heartbeat thread + * without o2hb_dependency_mutex, so for global heartbeat + * it may have crossed O2HB_PIN_CUT_OFF and unpinned the + * regions while we slept. If that happened this pin is + * no longer wanted; undo it and stop rather than + * resurrecting it on the rescan below. + */ + if (!region_uuid && + bitmap_weight(o2hb_quorum_region_bitmap, + O2NM_MAX_REGIONS) > O2HB_PIN_CUT_OFF) { + o2nm_undepend_item(&pinned->hr_item); + spin_unlock(&o2hb_live_lock); + config_item_put(&pinned->hr_item); + spin_lock(&o2hb_live_lock); break; } + mlog(ML_CLUSTER, "Pin region %s\n", uuid); + pinned->hr_item_pinned = 1; + } else if (ret == -ENOENT && (found || !region_uuid)) { + /* + * For local hb (found): ignore ENOENT from userdlm + * domains as before. For global hb (!region_uuid): + * the region may have been detached from configfs + * while the lock was dropped — skip it and continue + * pinning the remaining regions. + */ + ret = 0; + } else { + mlog(ML_ERROR, "Pin region %s fails with %d\n", + uuid, ret); } -skip_pin: - if (found) - break; - } + + /* + * config_item_put() may drop the last reference and run + * o2hb_region_release(), which also grabs o2hb_live_lock and + * can sleep, so it must happen with the lock released. + */ + spin_unlock(&o2hb_live_lock); + config_item_put(&pinned->hr_item); + spin_lock(&o2hb_live_lock); + + /* local hb pins a single matching region */ + } while (!ret && !region_uuid); return ret; } @@ -2406,6 +2478,7 @@ static int o2hb_region_inc_user(const char *region_uuid) { int ret = 0; + mutex_lock(&o2hb_dependency_mutex); spin_lock(&o2hb_live_lock); /* local heartbeat */ @@ -2428,11 +2501,13 @@ static int o2hb_region_inc_user(const char *region_uuid) unlock: spin_unlock(&o2hb_live_lock); + mutex_unlock(&o2hb_dependency_mutex); return ret; } static void o2hb_region_dec_user(const char *region_uuid) { + mutex_lock(&o2hb_dependency_mutex); spin_lock(&o2hb_live_lock); /* local heartbeat */ @@ -2451,6 +2526,7 @@ static void o2hb_region_dec_user(const char *region_uuid) unlock: spin_unlock(&o2hb_live_lock); + mutex_unlock(&o2hb_dependency_mutex); } int o2hb_register_callback(const char *region_uuid, From cd789996db3c87427343f54f509d17810bd7ba7c Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Wed, 22 Jul 2026 20:49:32 +0800 Subject: [PATCH 54/95] ocfs2: cluster: avoid lock order inversion in o2hb_region_pin() from drop_item o2hb_heartbeat_group_drop_item() is called from configfs rmdir with the parent directory's inode_lock held. It calls o2hb_region_pin() -> o2nm_depend_item() -> configfs_depend_item(), which acquires the configfs root inode_lock. This creates a parent -> root inode_lock nesting that could deadlock against paths taking root -> parent (e.g. subsystem unregistration). Fix this by using configfs_depend_item_unlocked() when o2hb_region_pin() is called from a configfs callback context. This variant skips the root inode_lock when caller and target are in the same subsystem, which is safe because VFS already holds a lock preventing unregistration. Add o2nm_depend_item_unlocked() wrapper and a from_callback parameter to o2hb_region_pin() to select the appropriate variant. Link: https://lore.kernel.org/20260722124933.430554-3-joseph.qi@linux.alibaba.com Fixes: 58a3158a5d17 ("ocfs2/cluster: Pin/unpin o2hb regions") Signed-off-by: Joseph Qi Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/cluster/heartbeat.c | 17 ++++++++++------- fs/ocfs2/cluster/nodemanager.c | 6 ++++++ fs/ocfs2/cluster/nodemanager.h | 1 + 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index 5ca1d9c0c657..b9395835523e 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -146,7 +146,7 @@ static unsigned int o2hb_dependent_users; * In global heartbeat mode, we pin/unpin all o2hb regions. This solution * works for both file system and userdlm domains. */ -static int o2hb_region_pin(const char *region_uuid); +static int o2hb_region_pin(const char *region_uuid, bool from_callback); static void o2hb_region_unpin(const char *region_uuid); /* Only sets a new threshold if there are no active regions. @@ -2188,7 +2188,7 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group, if (bitmap_weight(o2hb_quorum_region_bitmap, O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF) - o2hb_region_pin(NULL); + o2hb_region_pin(NULL, true); unlock: spin_unlock(&o2hb_live_lock); @@ -2330,7 +2330,7 @@ EXPORT_SYMBOL_GPL(o2hb_setup_callback); * In local, we only pin the matching region. In global we pin all the active * regions. */ -static int o2hb_region_pin(const char *region_uuid) +static int o2hb_region_pin(const char *region_uuid, bool from_callback) { int ret = 0, found; struct o2hb_region *reg, *pinned; @@ -2385,7 +2385,10 @@ static int o2hb_region_pin(const char *region_uuid) spin_unlock(&o2hb_live_lock); /* Ignore ENOENT only for local hb (userdlm domain) */ - ret = o2nm_depend_item(&pinned->hr_item); + if (from_callback) + ret = o2nm_depend_item_unlocked(&pinned->hr_item); + else + ret = o2nm_depend_item(&pinned->hr_item); spin_lock(&o2hb_live_lock); if (!ret) { @@ -2483,8 +2486,8 @@ static int o2hb_region_inc_user(const char *region_uuid) /* local heartbeat */ if (!o2hb_global_heartbeat_active()) { - ret = o2hb_region_pin(region_uuid); - goto unlock; + ret = o2hb_region_pin(region_uuid, false); + goto unlock; } /* @@ -2497,7 +2500,7 @@ static int o2hb_region_inc_user(const char *region_uuid) if (bitmap_weight(o2hb_quorum_region_bitmap, O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF) - ret = o2hb_region_pin(NULL); + ret = o2hb_region_pin(NULL, false); unlock: spin_unlock(&o2hb_live_lock); diff --git a/fs/ocfs2/cluster/nodemanager.c b/fs/ocfs2/cluster/nodemanager.c index e1f8f577ce5d..ebdf0bdbb8c6 100644 --- a/fs/ocfs2/cluster/nodemanager.c +++ b/fs/ocfs2/cluster/nodemanager.c @@ -778,6 +778,12 @@ int o2nm_depend_item(struct config_item *item) return configfs_depend_item(&o2nm_cluster_group.cs_subsys, item); } +int o2nm_depend_item_unlocked(struct config_item *item) +{ + return configfs_depend_item_unlocked(&o2nm_cluster_group.cs_subsys, + item); +} + void o2nm_undepend_item(struct config_item *item) { configfs_undepend_item(item); diff --git a/fs/ocfs2/cluster/nodemanager.h b/fs/ocfs2/cluster/nodemanager.h index 39006005427b..ca3483fb5450 100644 --- a/fs/ocfs2/cluster/nodemanager.h +++ b/fs/ocfs2/cluster/nodemanager.h @@ -64,6 +64,7 @@ void o2nm_node_get(struct o2nm_node *node); void o2nm_node_put(struct o2nm_node *node); int o2nm_depend_item(struct config_item *item); +int o2nm_depend_item_unlocked(struct config_item *item); void o2nm_undepend_item(struct config_item *item); int o2nm_depend_node(u8 node_num); void o2nm_undepend_node(u8 node_num); From 12c2ab42dbe227956c765e2674364bfca5de0533 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Wed, 22 Jul 2026 20:49:33 +0800 Subject: [PATCH 55/95] ocfs2: cluster: fix o2hb_dependent_users leak on pin failure In o2hb_region_inc_user(), o2hb_dependent_users is incremented unconditionally before calling o2hb_region_pin(). If the pin fails, the counter is never decremented and any partially-pinned regions are never unpinned, since the caller does not call o2hb_region_dec_user() on error. The leaked counter causes subsequent o2hb_region_inc_user() calls to skip pinning entirely (the > 1 check), leaving heartbeat regions unprotected. Fix by rolling back on failure: call o2hb_region_unpin(NULL) to release any partially-pinned regions and decrement o2hb_dependent_users to restore the pre-increment state. Link: https://lore.kernel.org/20260722124933.430554-4-joseph.qi@linux.alibaba.com Fixes: 58a3158a5d17 ("ocfs2/cluster: Pin/unpin o2hb regions") Signed-off-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/cluster/heartbeat.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index b9395835523e..c25aa2ff3ebc 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -2499,8 +2499,13 @@ static int o2hb_region_inc_user(const char *region_uuid) goto unlock; if (bitmap_weight(o2hb_quorum_region_bitmap, - O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF) + O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF) { ret = o2hb_region_pin(NULL, false); + if (ret) { + o2hb_region_unpin(NULL); + o2hb_dependent_users--; + } + } unlock: spin_unlock(&o2hb_live_lock); From 7f96e4a781950a5522e45ec332c630aaf99a9d88 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 21 Jul 2026 22:07:59 -0700 Subject: [PATCH 56/95] rbtree: fixup kernel-doc names Use the correct function parameter name in rb_next_match(). Use the struct keyword when describing struct latch_tree_ops. Prevents the following kernel-doc warnings: Warning: ./include/linux/rbtree.h:510 function parameter 'node' not described in 'rb_next_match' Warning: ./include/linux/rbtree.h:510 Excess function parameter 'tree' description in 'rb_next_match' Warning: ./include/linux/rbtree_latch.h:63 cannot understand function prototype: 'struct latch_tree_ops' Link: https://lore.kernel.org/20260722050759.3892887-1-rdunlap@infradead.org Signed-off-by: Randy Dunlap Cc: "Peter Zijlstra (Intel)" Cc: Thomas Gleixner Signed-off-by: Andrew Morton --- include/linux/rbtree.h | 2 +- include/linux/rbtree_latch.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/rbtree.h b/include/linux/rbtree.h index 48acdc3889dd..b7078b0e1eb2 100644 --- a/include/linux/rbtree.h +++ b/include/linux/rbtree.h @@ -500,7 +500,7 @@ rb_find_first(const void *key, const struct rb_root *tree, /** * rb_next_match() - find the next @key in @tree * @key: key to match - * @tree: tree to search + * @node: tree to search * @cmp: operator defining node order * * Returns the next node matching @key, or NULL. diff --git a/include/linux/rbtree_latch.h b/include/linux/rbtree_latch.h index 2f630eb8307e..b55a3a01bc8a 100644 --- a/include/linux/rbtree_latch.h +++ b/include/linux/rbtree_latch.h @@ -47,7 +47,7 @@ struct latch_tree_root { }; /** - * latch_tree_ops - operators to define the tree order + * struct latch_tree_ops - operators to define the tree order * @less: used for insertion; provides the (partial) order between two elements. * @comp: used for lookups; provides the order between the search key and an element. * From cec0d03fe785380540dc1b4d07c80f67ae2ffc78 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 23 Jul 2026 21:40:31 +0200 Subject: [PATCH 57/95] lib/ucs2_string.c: fix out-of-bounds read in ucs2_strnlen() Patch series "lib/ucs2_string.c: fix out-of-bounds read in ucs2_strnlen()", v2. This series fixes an off-by-one out-of-bounds read in ucs2_strnlen(). The first patch is the real fix, the second patch comes as a bonus and fixes the code indentation. This patch (of 2): ucs2_strnlen() checks the current character before checking whether the caller-provided maximum length has been reached. If the input is not NUL-terminated within that bound, the loop can read one ucs2_char_t past the limit. Test the length before dereferencing to prevent an off-by-one out-of-bounds read. Link: https://lore.kernel.org/20260723-fix-ucs2_strnlen-v2-0-9ea94e32a358@kernel.org Link: https://lore.kernel.org/20260723-fix-ucs2_strnlen-v2-1-9ea94e32a358@kernel.org Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Vincent Mailhol Cc: Kees Cook Cc: Signed-off-by: Andrew Morton --- lib/ucs2_string.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c index dfb4f2358cab..ca5c615d5bef 100644 --- a/lib/ucs2_string.c +++ b/lib/ucs2_string.c @@ -8,7 +8,7 @@ ucs2_strnlen(const ucs2_char_t *s, size_t maxlength) { unsigned long length = 0; - while (*s++ != 0 && length < maxlength) + while (length < maxlength && *s++ != 0) length++; return length; } From dbf0633e42ef30bb6db5ed2f1eccf0411d8a4c57 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 23 Jul 2026 21:40:32 +0200 Subject: [PATCH 58/95] lib/ucs2_string.c: fix indentation checkpatch.pl reports a total of 18 incorrect use of spaces instead of tabulations in lib/ucs2_string.c. Apply ./scripts/checkpatch.pl --fix-inplace lib/ucs2_string.c to fix them all. Link: https://lore.kernel.org/20260723-fix-ucs2_strnlen-v2-2-9ea94e32a358@kernel.org Signed-off-by: Vincent Mailhol Cc: Kees Cook Signed-off-by: Andrew Morton --- lib/ucs2_string.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/ucs2_string.c b/lib/ucs2_string.c index ca5c615d5bef..1f7dd4eb640a 100644 --- a/lib/ucs2_string.c +++ b/lib/ucs2_string.c @@ -6,18 +6,18 @@ unsigned long ucs2_strnlen(const ucs2_char_t *s, size_t maxlength) { - unsigned long length = 0; + unsigned long length = 0; while (length < maxlength && *s++ != 0) - length++; - return length; + length++; + return length; } EXPORT_SYMBOL(ucs2_strnlen); unsigned long ucs2_strlen(const ucs2_char_t *s) { - return ucs2_strnlen(s, ~0UL); + return ucs2_strnlen(s, ~0UL); } EXPORT_SYMBOL(ucs2_strlen); @@ -28,7 +28,7 @@ EXPORT_SYMBOL(ucs2_strlen); unsigned long ucs2_strsize(const ucs2_char_t *data, unsigned long maxlength) { - return ucs2_strnlen(data, maxlength/sizeof(ucs2_char_t)) * sizeof(ucs2_char_t); + return ucs2_strnlen(data, maxlength/sizeof(ucs2_char_t)) * sizeof(ucs2_char_t); } EXPORT_SYMBOL(ucs2_strsize); @@ -87,19 +87,19 @@ EXPORT_SYMBOL(ucs2_strscpy); int ucs2_strncmp(const ucs2_char_t *a, const ucs2_char_t *b, size_t len) { - while (1) { - if (len == 0) - return 0; - if (*a < *b) - return -1; - if (*a > *b) - return 1; - if (*a == 0) /* implies *b == 0 */ - return 0; - a++; - b++; - len--; - } + while (1) { + if (len == 0) + return 0; + if (*a < *b) + return -1; + if (*a > *b) + return 1; + if (*a == 0) /* implies *b == 0 */ + return 0; + a++; + b++; + len--; + } } EXPORT_SYMBOL(ucs2_strncmp); From 1f58a5335cdd14b3fb5f2a5d3763dee1f5cba1d3 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Thu, 23 Jul 2026 21:09:22 +0000 Subject: [PATCH 59/95] taskstats: fix cpumask parsing cutting off the last character parse() hands nla_strscpy() len as dstsize, and nla_strscpy() copies at most dstsize - 1 bytes. When the attr payload comes in without a trailing NUL, srclen == len >= dstsize and the last character of the cpumask string gets cut off. Register "0-15" and you are silently listening on "0-1", exit data for the rest never shows up. The bug only bites when the sender doesn't NUL terminate the payload; senders that include the NUL were always fine (srclen gets decremented for the trailing NUL, so srclen < dstsize). Thats probably why this survived 20 years. And the policy is NLA_STRING, not NLA_NUL_STRING, so a payload without the trailing NUL is legit input here. Skip the kmalloc/nla_strscpy dance entirely and use nla_strdup(), which already allocates srclen + 1 and terminates. The nla_len() bounds checks stay as they were. Link: https://lore.kernel.org/EC49FE41-7F5F-41E0-A07A-ABEB8ECA514D@grrlz.net Fixes: f9fd8914c1ac ("[PATCH] per-task delay accounting taskstats interface: control exit data through cpumasks") Signed-off-by: Bradley Morgan Reported-by: Oleg Deomi Closes: https://lore.kernel.org/CAByWkfZ6b1=3H9pwkz-dDQOs9cZaF-HYQ6b9Yb0=Hq2r1Vv_Pw@mail.gmail.com Reviewed-by: Andrew Morton Cc: Balbir Singh Cc: Signed-off-by: Andrew Morton --- kernel/taskstats.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/taskstats.c b/kernel/taskstats.c index 8d115670a3ca..74f89b632c0a 100644 --- a/kernel/taskstats.c +++ b/kernel/taskstats.c @@ -368,10 +368,9 @@ static int parse(struct nlattr *na, struct cpumask *mask) return -E2BIG; if (len < 1) return -EINVAL; - data = kmalloc(len, GFP_KERNEL); + data = nla_strdup(na, GFP_KERNEL); if (!data) return -ENOMEM; - nla_strscpy(data, na, len); ret = cpulist_parse(data, mask); kfree(data); return ret; From eb14721d5029f7ef9bf11e6100ac9892c2089d7f Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 23 Jul 2026 09:51:13 -0700 Subject: [PATCH 60/95] llist: use correct function parameter name Correct the function parameter name to avoid kernel-doc warnings: Warning: ./include/linux/llist.h:71 function parameter 'list' not described in 'init_llist_head' Warning: ./include/linux/llist.h:71 Excess function parameter 'head' description in 'init_llist_head' Link: https://lore.kernel.org/20260723165113.225098-1-rdunlap@infradead.org Signed-off-by: Randy Dunlap Signed-off-by: Andrew Morton --- include/linux/llist.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/llist.h b/include/linux/llist.h index 8846b7709669..413249764f7b 100644 --- a/include/linux/llist.h +++ b/include/linux/llist.h @@ -66,7 +66,7 @@ struct llist_node { /** * init_llist_head - initialize lock-less list head - * @head: the head for your lock-less list + * @list: the head for your lock-less list */ static inline void init_llist_head(struct llist_head *list) { From 2c5a2a6afe8fb59722c427d208085e088012999d Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 23 Jul 2026 09:18:31 -0700 Subject: [PATCH 61/95] stacktrace: header: repair kernel-doc comments - use the "typedef" keyword when describing a typedef - add a Returns: section to prevent these kernel-doc warnings: Warning: include/linux/stacktrace.h:20 function parameter 'stack_trace_consume_fn' not described in 'bool' Warning: include/linux/stacktrace.h:20 expecting prototype for stack_trace_consume_fn(). Prototype was for bool() instead Warning: include/linux/stacktrace.h:58 No description found for return value of 'arch_stack_walk_reliable' Link: https://lore.kernel.org/20260723161831.138237-1-rdunlap@infradead.org Signed-off-by: Randy Dunlap Cc: Marc Rutland Cc: Peter Zijlstra Signed-off-by: Andrew Morton --- include/linux/stacktrace.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/stacktrace.h b/include/linux/stacktrace.h index 97455880ac41..525cf60673fe 100644 --- a/include/linux/stacktrace.h +++ b/include/linux/stacktrace.h @@ -11,7 +11,7 @@ struct pt_regs; #ifdef CONFIG_ARCH_STACKWALK /** - * stack_trace_consume_fn - Callback for arch_stack_walk() + * typedef stack_trace_consume_fn - Callback for arch_stack_walk() * @cookie: Caller supplied pointer handed back by arch_stack_walk() * @addr: The stack entry address to consume * @@ -48,9 +48,9 @@ void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie, * @consume_entry * @task: Pointer to a task struct, can be NULL * - * This function returns an error if it detects any unreliable + * Returns: a negative error code if it detects any unreliable * features of the stack. Otherwise it guarantees that the stack - * trace is reliable. + * trace is reliable and returns %0. * * If the task is not 'current', the caller *must* ensure the task is * inactive and its stack is pinned. From 0cdc7dde00ec63ac714271fa8b2918d630b8da1a Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Thu, 23 Jul 2026 23:57:03 -0500 Subject: [PATCH 62/95] ocfs2: fix missing metadata reservation for large xattrs [BUG] lsetxattr() panics the kernel when setting a large xattr value on a fragmented filesystem where the file already has an external xattr block. [CAUSE] ocfs2_calc_xattr_set_need() never reserves metadata blocks for a new xattr value's extent tree when the file already has an external xattr block. The not_found path leaves meta_add at zero, so meta_ac is NULL when ocfs2_xattr_extend_allocation() runs. A new value root has room for a single extent record. On a fragmented filesystem, the allocator cannot satisfy the xattr value in one contiguous run, so each non-contiguous run requires its own extent record. When the value root's extent list is full and meta_ac is NULL, ocfs2_add_clusters_in_btree() returns RESTART_META, and ocfs2_xattr_extend_allocation() hits BUG_ON(why == RESTART_META). [FIX] The case where no xattr block exists yet already calls ocfs2_extend_meta_needed(&def_xv.xv.xr_list) to reserve value tree metadata. Add the same reservation to the case where an xattr block already exists, making the two cases consistent. Replace the BUG_ON with a -ENOSPC return so that if RESTART_META is returned despite the reservation, the error propagates to userspace instead of panicking the kernel. Link: https://lore.kernel.org/amLwn3i9tET8yhG7@dev Fixes: a78f9f466894 ("ocfs2: make xattr extension work with new local alloc reservation.") Signed-off-by: Ian Bridges Reported-by: syzbot+e538032956b1157914a3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e538032956b1157914a3 Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/xattr.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index ed774bded974..94ae1fb83cf1 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -764,12 +764,10 @@ static int ocfs2_xattr_extend_allocation(struct inode *inode, prev_clusters; if (why != RESTART_NONE && clusters_to_add) { - /* - * We can only fail in case the alloc file doesn't give - * up enough clusters. - */ - BUG_ON(why == RESTART_META); - + if (why == RESTART_META) { + status = -ENOSPC; + break; + } credits = ocfs2_calc_extend_credits(inode->i_sb, &vb->vb_xv->xr_list); status = ocfs2_extend_trans(handle, credits); @@ -3443,6 +3441,14 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode, } else credits += OCFS2_SUBALLOC_ALLOC + 1; + /* + * 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. + */ + if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE) + meta_add += ocfs2_extend_meta_needed(&def_xv.xv.xr_list); + /* * This cluster will be used either for new bucket or for * new xattr block. From 2eed77fdcb0cc48e8eccb2bcd4b7f2c6d650e84c Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Thu, 16 Jul 2026 08:01:29 +0900 Subject: [PATCH 63/95] kcov: fix data corruption and race conditions on PREEMPT_RT syzbot is reporting KCOV state corruption on PREEMPT_RT kernels, for the temporary storage used for saving/restoring remote KCOV state is currently allocated as the per-CPU area. On PREEMPT_RT kernels, softirq handlers run as preemptible task threads (e.g., ksoftirqd). If a softirq context preempts a task running a remote KCOV session, it safely saves the task's state into the per-CPU area. However, if that softirq thread is subsequently preempted by a higher- priority softirq thread on the same CPU, the second softirq will overwrite the same per-CPU area, permanently destroying the original task's KCOV state. Fix this data corruption by moving the temporary storage from the per-CPU area to the per-thread area. Since each softirq thread now owns its own task context, nested softirq preemption no longer causes data overwrites. Note that while the temporary storage is now on a per-thread basis, the per-CPU kcov_percpu_data.lock must be retained, for we need to ensure that kcov_remote_start() and kcov_remote_stop() operate atomically without racing against asynchronous interrupts that manipulate the current task's KCOV state. It is likely that GFP_KERNEL allocation by vmalloc_node() in kcov_init() has already called panic() before returning NULL, for there will be no OOM-killable userspace processes when __init function of built-in module runs. But this patch also fixes crashing the kernel when vmalloc_node() in kcov_init() returned NULL, for kcov_init() left per-CPU irq_area == NULL but kcov_remote_start() depends on per-CPU irq_area != NULL, resulting in (1) doing vmalloc() in kcov_remote_start() despite !in_task() context (2) out-of-array-bounds access if (1) succeeded but kcov->remote_size < CONFIG_KCOV_IRQ_AREA_SIZE (3) always leak memory allocated by (1), eventually killing all OOM-killable userspace processes problems. Link: https://lore.kernel.org/43552d09-2ce2-4b19-b0d3-a2d1ab952145@I-love.SAKURA.ne.jp Reported-by: syzbot+3f51ad7ac3ae57a6fdcc@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3f51ad7ac3ae57a6fdcc Reported-by: syzbot+47cf95ca1f9dcca872c8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=47cf95ca1f9dcca872c8 Reported-by: syzbot+8a173e13208949931dc7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=8a173e13208949931dc7 Reported-by: syzbot+90984d3713722683112e@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=90984d3713722683112e Analyzed-by: AI Mode in Google Search (no mail address) Fixes: 5ff3b30ab57d ("kcov: collect coverage from interrupts") Signed-off-by: Tetsuo Handa Reviewed-by: Alexander Potapenko Cc: Alan Stern Cc: Andrey Konovalov Cc: Christoph Hellwig Cc: Clark Williams Cc: Dmitry Vyukov Cc: Greg Kroah-Hartman Cc: Marco Elver Cc: Mark Brown Cc: Roman Gushchin Cc: Sebastian Andrzej Siewior Cc: Signed-off-by: Andrew Morton --- include/linux/sched.h | 8 ++++ kernel/kcov.c | 90 ++++++++++++++++++++++--------------------- lib/Kconfig.debug | 5 ++- 3 files changed, 58 insertions(+), 45 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h index 373bcc0598d1..7a53c15cecb5 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1543,6 +1543,14 @@ struct task_struct { /* Collect coverage from softirq context: */ unsigned int kcov_softirq; + + /* Temporary storage for preempting remote coverage collection: */ + unsigned int kcov_saved_mode; + unsigned int kcov_saved_size; + void *kcov_saved_area; + struct kcov *kcov_saved_kcov; + int kcov_saved_sequence; + #endif #ifdef CONFIG_MEMCG_V1 diff --git a/kernel/kcov.c b/kernel/kcov.c index 1df373fb562b..a7514303eff3 100644 --- a/kernel/kcov.c +++ b/kernel/kcov.c @@ -86,17 +86,12 @@ struct kcov_remote { static DEFINE_SPINLOCK(kcov_remote_lock); static DEFINE_HASHTABLE(kcov_remote_map, 4); -static struct list_head kcov_remote_areas = LIST_HEAD_INIT(kcov_remote_areas); +static struct list_head kcov_remote_areas[2] = { + LIST_HEAD_INIT(kcov_remote_areas[0]), LIST_HEAD_INIT(kcov_remote_areas[1]) +}; struct kcov_percpu_data { - void *irq_area; local_lock_t lock; - - unsigned int saved_mode; - unsigned int saved_size; - void *saved_area; - struct kcov *saved_kcov; - int saved_sequence; }; static DEFINE_PER_CPU(struct kcov_percpu_data, kcov_percpu_data) = { @@ -132,12 +127,13 @@ static struct kcov_remote *kcov_remote_add(struct kcov *kcov, u64 handle) } /* Must be called with kcov_remote_lock locked. */ -static struct kcov_remote_area *kcov_remote_area_get(unsigned int size) +static struct kcov_remote_area *kcov_remote_area_get(unsigned int size, bool irq) { struct kcov_remote_area *area; struct list_head *pos; + struct list_head *list = &kcov_remote_areas[irq]; - list_for_each(pos, &kcov_remote_areas) { + list_for_each(pos, list) { area = list_entry(pos, struct kcov_remote_area, list); if (area->size == size) { list_del(&area->list); @@ -149,11 +145,11 @@ static struct kcov_remote_area *kcov_remote_area_get(unsigned int size) /* Must be called with kcov_remote_lock locked. */ static void kcov_remote_area_put(struct kcov_remote_area *area, - unsigned int size) + unsigned int size, bool irq) { INIT_LIST_HEAD(&area->list); area->size = size; - list_add(&area->list, &kcov_remote_areas); + list_add(&area->list, &kcov_remote_areas[irq]); /* * KMSAN doesn't instrument this file, so it may not know area->list * is initialized. Unpoison it explicitly to avoid reports in @@ -390,6 +386,12 @@ void kcov_task_init(struct task_struct *t) kcov_task_reset(t); t->kcov_remote = NULL; t->kcov_handle = current->kcov_handle; + t->kcov_softirq = 0; + t->kcov_saved_mode = 0; + t->kcov_saved_size = 0; + t->kcov_saved_area = NULL; + t->kcov_saved_kcov = NULL; + t->kcov_saved_sequence = 0; } static void kcov_reset(struct kcov *kcov) @@ -836,17 +838,16 @@ static inline bool kcov_mode_enabled(unsigned int mode) static void kcov_remote_softirq_start(struct task_struct *t) __must_hold(&kcov_percpu_data.lock) { - struct kcov_percpu_data *data = this_cpu_ptr(&kcov_percpu_data); unsigned int mode; mode = READ_ONCE(t->kcov_mode); barrier(); if (kcov_mode_enabled(mode)) { - data->saved_mode = mode; - data->saved_size = t->kcov_size; - data->saved_area = t->kcov_area; - data->saved_sequence = t->kcov_sequence; - data->saved_kcov = t->kcov; + t->kcov_saved_mode = mode; + t->kcov_saved_size = t->kcov_size; + t->kcov_saved_area = t->kcov_area; + t->kcov_saved_sequence = t->kcov_sequence; + t->kcov_saved_kcov = t->kcov; kcov_stop(t); } } @@ -854,17 +855,15 @@ static void kcov_remote_softirq_start(struct task_struct *t) static void kcov_remote_softirq_stop(struct task_struct *t) __must_hold(&kcov_percpu_data.lock) { - struct kcov_percpu_data *data = this_cpu_ptr(&kcov_percpu_data); - - if (data->saved_kcov) { - kcov_start(t, data->saved_kcov, data->saved_size, - data->saved_area, data->saved_mode, - data->saved_sequence); - data->saved_mode = 0; - data->saved_size = 0; - data->saved_area = NULL; - data->saved_sequence = 0; - data->saved_kcov = NULL; + if (t->kcov_saved_kcov) { + kcov_start(t, t->kcov_saved_kcov, t->kcov_saved_size, + t->kcov_saved_area, t->kcov_saved_mode, + t->kcov_saved_sequence); + t->kcov_saved_mode = 0; + t->kcov_saved_size = 0; + t->kcov_saved_area = NULL; + t->kcov_saved_sequence = 0; + t->kcov_saved_kcov = NULL; } } @@ -927,17 +926,17 @@ void kcov_remote_start(u64 handle) sequence = kcov->sequence; if (in_task()) { size = kcov->remote_size; - area = kcov_remote_area_get(size); + area = kcov_remote_area_get(size, false); } else { size = CONFIG_KCOV_IRQ_AREA_SIZE; - area = this_cpu_ptr(&kcov_percpu_data)->irq_area; + area = kcov_remote_area_get(size, true); } spin_unlock(&kcov_remote_lock); - /* Can only happen when in_task(). */ + /* Allocate new buffer if we can sleep. */ if (!area) { local_unlock_irqrestore(&kcov_percpu_data.lock, flags); - area = vmalloc(size * sizeof(unsigned long)); + area = in_task() ? vmalloc(size * sizeof(unsigned long)) : NULL; if (!area) { kcov_put(kcov); return; @@ -1079,11 +1078,9 @@ void kcov_remote_stop(void) kcov_move_area(kcov->mode, kcov->area, kcov->size, area); spin_unlock(&kcov->lock); - if (in_task()) { - spin_lock(&kcov_remote_lock); - kcov_remote_area_put(area, size); - spin_unlock(&kcov_remote_lock); - } + spin_lock(&kcov_remote_lock); + kcov_remote_area_put(area, size, !in_task()); + spin_unlock(&kcov_remote_lock); local_unlock_irqrestore(&kcov_percpu_data.lock, flags); @@ -1129,14 +1126,21 @@ static void __init selftest(void) static int __init kcov_init(void) { - int cpu; + int cpu = num_possible_cpus(); + +#ifdef CONFIG_PREEMPT_RT + /* Allocate some extra buffers in order to prepare for softirq preemption. */ + cpu = cpu >= 4 ? cpu * 2 : cpu + 4; +#endif + while (cpu--) { + void *area = vmalloc(CONFIG_KCOV_IRQ_AREA_SIZE * sizeof(unsigned long)); + unsigned long flags; - for_each_possible_cpu(cpu) { - void *area = vmalloc_node(CONFIG_KCOV_IRQ_AREA_SIZE * - sizeof(unsigned long), cpu_to_node(cpu)); if (!area) return -ENOMEM; - per_cpu_ptr(&kcov_percpu_data, cpu)->irq_area = area; + spin_lock_irqsave(&kcov_remote_lock, flags); + kcov_remote_area_put(area, CONFIG_KCOV_IRQ_AREA_SIZE, true); + spin_unlock_irqrestore(&kcov_remote_lock, flags); } /* diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 009968c44de1..2a6be1af94a3 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2247,10 +2247,11 @@ config KCOV_INSTRUMENT_ALL config KCOV_IRQ_AREA_SIZE hex "Size of interrupt coverage collection area in words" depends on KCOV + range 0x80 0x1000000 default 0x40000 help - KCOV uses preallocated per-cpu areas to collect coverage from - soft interrupts. This specifies the size of those areas in the + KCOV uses preallocated areas to collect coverage from soft + interrupts. This specifies the size of those areas in the number of unsigned long words. config KCOV_SELFTEST From ebaf75adbec33356d746ca7df36b72d31e7e5109 Mon Sep 17 00:00:00 2001 From: Gou Hao Date: Fri, 24 Jul 2026 10:28:49 +0800 Subject: [PATCH 64/95] drm: remove dead WARN_ON NULL check after GFP_NOFAIL allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kzalloc_obj with the __GFP_NOFAIL flag will never return NULL, so the subsequent WARN_ON(!ctx) is unreachable dead code. Remove it. Link: https://lore.kernel.org/20260724022851.466017-5-gouhao@uniontech.com Signed-off-by: Gou Hao Cc: Bharat Potnuri Cc: Cédric Le Goater Cc: Dave Airlie Cc: Jason Gunthorpe Cc: jiazhenyuan Cc: Leon Romanovsky Cc: Maarten Lankhorst Cc: Madhavan Srinivasan Cc: Maxime Ripard Cc: Michael Ellerman Cc: Nam Cao Cc: Nicholas Piggin Cc: Nilay Shroff Cc: Shrikanth Hegde Cc: Thomas Zimemrmann Cc: Wentao Guan Cc: Alistair Popple Signed-off-by: Andrew Morton --- drivers/gpu/drm/drm_modeset_lock.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/gpu/drm/drm_modeset_lock.c b/drivers/gpu/drm/drm_modeset_lock.c index 2c806b0146d6..e14814c30d8c 100644 --- a/drivers/gpu/drm/drm_modeset_lock.c +++ b/drivers/gpu/drm/drm_modeset_lock.c @@ -149,11 +149,7 @@ void drm_modeset_lock_all(struct drm_device *dev) int ret; ctx = kzalloc_obj(*ctx, GFP_KERNEL | __GFP_NOFAIL); - if (WARN_ON(!ctx)) - return; - mutex_lock(&config->mutex); - drm_modeset_acquire_init(ctx, 0); retry: From f14bd29bbb1ad121cbf22939dbff6e42ef6f4b90 Mon Sep 17 00:00:00 2001 From: Gou Hao Date: Fri, 24 Jul 2026 10:28:50 +0800 Subject: [PATCH 65/95] lib/test_hmm: remove dead NULL checks after GFP_NOFAIL allocations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kvcalloc with the __GFP_NOFAIL flag will never return NULL, so the subsequent NULL checks are unreachable dead code. Remove them. Link: https://lore.kernel.org/20260724022851.466017-6-gouhao@uniontech.com Signed-off-by: Gou Hao Reviewed-by: Alistair Popple Cc: Bharat Potnuri Cc: Cédric Le Goater Cc: Dave Airlie Cc: Jason Gunthorpe Cc: jiazhenyuan Cc: Leon Romanovsky Cc: Maarten Lankhorst Cc: Madhavan Srinivasan Cc: Maxime Ripard Cc: Michael Ellerman Cc: Nam Cao Cc: Nicholas Piggin Cc: Nilay Shroff Cc: Shrikanth Hegde Cc: Thomas Zimemrmann Cc: Wentao Guan Signed-off-by: Andrew Morton --- lib/test_hmm.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/test_hmm.c b/lib/test_hmm.c index c4adbf98fac7..be1fbc33ba3f 100644 --- a/lib/test_hmm.c +++ b/lib/test_hmm.c @@ -1209,16 +1209,10 @@ static int dmirror_migrate_to_device(struct dmirror *dmirror, if (!mmget_not_zero(mm)) return -EINVAL; - ret = -ENOMEM; src_pfns = kvcalloc(PTRS_PER_PTE, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL); - if (!src_pfns) - goto free_mem; - dst_pfns = kvcalloc(PTRS_PER_PTE, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL); - if (!dst_pfns) - goto free_mem; ret = 0; mmap_read_lock(mm); From 34b18b31cb759d591e1de5c6c59292b5fc996f26 Mon Sep 17 00:00:00 2001 From: Gou Hao Date: Fri, 24 Jul 2026 10:28:51 +0800 Subject: [PATCH 66/95] RDMA/cxgb4: remove dead NULL checks after GFP_NOFAIL allocations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit alloc_skb() with the __GFP_NOFAIL flag will never return NULL, so the subsequent NULL checks and error handling are unreachable dead code. Remove them. Link: https://lore.kernel.org/20260724022851.466017-7-gouhao@uniontech.com Signed-off-by: Gou Hao Cc: Bharat Potnuri Cc: Cédric Le Goater Cc: Dave Airlie Cc: Jason Gunthorpe Cc: jiazhenyuan Cc: Leon Romanovsky Cc: Maarten Lankhorst Cc: Madhavan Srinivasan Cc: Maxime Ripard Cc: Michael Ellerman Cc: Nam Cao Cc: Nicholas Piggin Cc: Nilay Shroff Cc: Shrikanth Hegde Cc: Thomas Zimemrmann Cc: Wentao Guan Cc: Alistair Popple Signed-off-by: Andrew Morton --- drivers/infiniband/hw/cxgb4/mem.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/infiniband/hw/cxgb4/mem.c b/drivers/infiniband/hw/cxgb4/mem.c index cd1b01014198..08631de17c0a 100644 --- a/drivers/infiniband/hw/cxgb4/mem.c +++ b/drivers/infiniband/hw/cxgb4/mem.c @@ -74,11 +74,8 @@ static int _c4iw_write_mem_dma_aligned(struct c4iw_rdev *rdev, u32 addr, c4iw_init_wr_wait(wr_waitp); wr_len = roundup(sizeof(*req) + sizeof(*sgl), 16); - if (!skb) { + if (!skb) skb = alloc_skb(wr_len, GFP_KERNEL | __GFP_NOFAIL); - if (!skb) - return -ENOMEM; - } set_wr_txq(skb, CPL_PRIORITY_CONTROL, 0); req = __skb_put_zero(skb, wr_len); @@ -134,11 +131,8 @@ static int _c4iw_write_mem_inline(struct c4iw_rdev *rdev, u32 addr, u32 len, roundup(copy_len, T4_ULPTX_MIN_IO), 16); - if (!skb) { + if (!skb) skb = alloc_skb(wr_len, GFP_KERNEL | __GFP_NOFAIL); - if (!skb) - return -ENOMEM; - } set_wr_txq(skb, CPL_PRIORITY_CONTROL, 0); req = __skb_put_zero(skb, wr_len); From 3e326f3bf16506873777444608e8b715aab74a7a Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 24 Jun 2026 17:53:09 +0800 Subject: [PATCH 67/95] ocfs2: synchronize heartbeat callbacks with o2net teardown Patch series "ocfs2: harden heartbeat teardown races". This series fixes two OCFS2 heartbeat/o2net teardown races found by KASAN. This patch (of 2): Heartbeat callbacks stay registered while configfs local-node teardown enters o2net_stop_listening(). A node-down event can still run through o2net_disconnect_node() and o2net_set_nn_state() while teardown is destroying o2net_wq, so the later queue/flush operations can hit a dead workqueue. KASAN has caught this as a slab-use-after-free in __queue_work() with the call chain: KASAN slab-use-after-free in __queue_work+0x56/0xa90 Read of size 4 Call trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 __queue_work+0x56/0xa90 srso_alias_return_thunk+0x5/0xfbef5 __virt_addr_valid+0x19f/0x330 kasan_report+0xe0/0x110 __queue_delayed_work+0x58/0x1e0 queue_delayed_work_on+0xb4/0xc0 o2net_set_nn_state+0x467/0x840 o2net_disconnect_node+0x7b/0xe0 o2net_hb_node_down_cb+0x54/0x60 o2hb_run_event_list+0x236/0x2d0 o2hb_check_slot+0xad4/0xbc0 lock_release+0xc8/0x290 o2hb_check_slot+0x9ea/0xbc0 trace_hardirqs_on+0x18/0x130 o2hb_do_disk_heartbeat+0x646/0xb30 (fs/ocfs2/cluster/heartbeat.c:1079) __lock_acquire+0x466/0x2260 lockdep_hardirqs_on_prepare+0xea/0x1a0 ktime_get_with_offset+0xe9/0x230 o2hb_thread+0x14e/0x770 kthread+0x1ad/0x1f0 ret_from_fork+0x3c9/0x540 __switch_to+0x2e9/0x730 ret_from_fork_asm+0x1a/0x30 Allocated by task stack: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_noprof+0x292/0x760 __alloc_workqueue+0x736/0xc60 alloc_workqueue_noprof+0xb1/0x110 o2net_start_listening+0xe5/0x430 o2nm_node_local_store+0x184/0x310 configfs_write_iter+0x18a/0x210 vfs_write+0x469/0x810 ksys_write+0xd2/0x170 do_syscall_64+0x115/0x6a0 (arch/x86/entry/syscall_64.c:87) entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task stack: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x313/0x590 rcu_core+0x4f4/0x1320 handle_softirqs+0x156/0x660 queue_delayed_work_on o2net_set_nn_state o2net_disconnect_node o2net_hb_node_down_cb o2hb_run_event_list Keep heartbeat callbacks registered so quorum state still tracks node state, but stop them from driving o2net reconnect/disconnect work once local teardown starts. Mark the transport offline before destroying o2net_wq, wait for any in-flight heartbeat callback to finish, and delay bring-up replay until the new local node is published through o2nm_this_node(). The replay also has to stay serialized with heartbeat callback delivery. Otherwise a live-node snapshot can be copied, a real hb_down callback can install -ENOTCONN for a peer, and the stale replay can call o2net_hb_node_up() for that same peer and queue reconnect work even though heartbeat is already down. The buggy scenario involves two paths, with each column showing the order within that path: local-node teardown: heartbeat node-down callback: 1. configfs local-off enters 1. o2hb_run_event_list() invokes o2net_stop_listening(). o2net_hb_node_down_cb(). 2. teardown heads for 2. the callback reaches destroy_workqueue(o2net_wq). o2net_disconnect_node() and o2net_set_nn_state(). 3. teardown destroys and NULLs 3. the callback flushes or queues o2net_wq. work through o2net_wq. Link: https://lore.kernel.org/20260624095310.763763-1-zzzccc427@gmail.com Link: https://lore.kernel.org/20260624095310.763763-2-zzzccc427@gmail.com Fixes: 98211489d414 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Cen Zhang Assisted-by: Codex:gpt-5.5 Reviewed-by: Joseph Qi Cc: Changwei Ge Cc: Heming Zhao Cc: Joel Becker Cc: Jun Piao Cc: Junxiao Bi Cc: Mark Fasheh Signed-off-by: Andrew Morton --- fs/ocfs2/cluster/heartbeat.c | 43 ++++++++++++++---- fs/ocfs2/cluster/heartbeat.h | 5 ++ fs/ocfs2/cluster/nodemanager.c | 4 ++ fs/ocfs2/cluster/tcp.c | 83 +++++++++++++++++++++++++++------- fs/ocfs2/cluster/tcp.h | 1 + 5 files changed, 109 insertions(+), 27 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index c25aa2ff3ebc..834210af4e4b 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -1492,13 +1492,38 @@ void o2hb_init(void) o2hb_debug_init(); } -/* if we're already in a callback then we're already serialized by the sem */ -static void o2hb_fill_node_map_from_callback(unsigned long *map, - unsigned int bits) +static void __o2hb_fill_node_map(unsigned long *map, unsigned int bits) { bitmap_copy(map, o2hb_live_node_bitmap, bits); } +void o2hb_callback_read_lock(void) +{ + down_read(&o2hb_callback_sem); +} + +void o2hb_callback_read_unlock(void) +{ + up_read(&o2hb_callback_sem); +} + +void o2hb_synchronize_callbacks(void) +{ + down_write(&o2hb_callback_sem); + up_write(&o2hb_callback_sem); +} + +/* + * Callers must already hold o2hb_callback_sem for read or write so the copy + * stays serialized with callback delivery. + */ +void o2hb_fill_node_map_locked(unsigned long *map, unsigned int bits) +{ + spin_lock(&o2hb_live_lock); + __o2hb_fill_node_map(map, bits); + spin_unlock(&o2hb_live_lock); +} + /* * get a map of all nodes that are heartbeating in any regions */ @@ -1506,11 +1531,9 @@ void o2hb_fill_node_map(unsigned long *map, unsigned int bits) { /* callers want to serialize this map and callbacks so that they * can trust that they don't miss nodes coming to the party */ - down_read(&o2hb_callback_sem); - spin_lock(&o2hb_live_lock); - o2hb_fill_node_map_from_callback(map, bits); - spin_unlock(&o2hb_live_lock); - up_read(&o2hb_callback_sem); + o2hb_callback_read_lock(); + o2hb_fill_node_map_locked(map, bits); + o2hb_callback_read_unlock(); } EXPORT_SYMBOL_GPL(o2hb_fill_node_map); @@ -2609,7 +2632,7 @@ int o2hb_check_node_heartbeating_no_sem(u8 node_num) unsigned long testing_map[BITS_TO_LONGS(O2NM_MAX_NODES)]; spin_lock(&o2hb_live_lock); - o2hb_fill_node_map_from_callback(testing_map, O2NM_MAX_NODES); + __o2hb_fill_node_map(testing_map, O2NM_MAX_NODES); spin_unlock(&o2hb_live_lock); if (!test_bit(node_num, testing_map)) { mlog(ML_HEARTBEAT, @@ -2626,7 +2649,7 @@ int o2hb_check_node_heartbeating_from_callback(u8 node_num) { unsigned long testing_map[BITS_TO_LONGS(O2NM_MAX_NODES)]; - o2hb_fill_node_map_from_callback(testing_map, O2NM_MAX_NODES); + o2hb_fill_node_map_locked(testing_map, O2NM_MAX_NODES); if (!test_bit(node_num, testing_map)) { mlog(ML_HEARTBEAT, "node (%u) does not have heartbeating enabled.\n", diff --git a/fs/ocfs2/cluster/heartbeat.h b/fs/ocfs2/cluster/heartbeat.h index 8ef8c1b9eeb7..2ca2b657583c 100644 --- a/fs/ocfs2/cluster/heartbeat.h +++ b/fs/ocfs2/cluster/heartbeat.h @@ -58,6 +58,11 @@ int o2hb_register_callback(const char *region_uuid, struct o2hb_callback_func *hc); void o2hb_unregister_callback(const char *region_uuid, struct o2hb_callback_func *hc); +void o2hb_callback_read_lock(void); +void o2hb_callback_read_unlock(void); +void o2hb_synchronize_callbacks(void); +void o2hb_fill_node_map_locked(unsigned long *map, + unsigned int bits); void o2hb_fill_node_map(unsigned long *map, unsigned int bits); void o2hb_exit(void); diff --git a/fs/ocfs2/cluster/nodemanager.c b/fs/ocfs2/cluster/nodemanager.c index ebdf0bdbb8c6..e08850a5d736 100644 --- a/fs/ocfs2/cluster/nodemanager.c +++ b/fs/ocfs2/cluster/nodemanager.c @@ -326,6 +326,7 @@ static ssize_t o2nm_node_local_store(struct config_item *item, const char *page, struct o2nm_node *node = to_o2nm_node(item); struct o2nm_cluster *cluster; unsigned long tmp; + bool starting = false; char *p = (char *)page; ssize_t ret; @@ -362,6 +363,7 @@ static ssize_t o2nm_node_local_store(struct config_item *item, const char *page, ret = o2net_start_listening(node); if (ret) goto out; + starting = true; } if (!tmp && cluster->cl_has_local && @@ -375,6 +377,8 @@ static ssize_t o2nm_node_local_store(struct config_item *item, const char *page, if (node->nd_local) { cluster->cl_has_local = tmp; cluster->cl_local_node = node->nd_num; + if (starting) + o2net_complete_start_listening(node); } ret = count; diff --git a/fs/ocfs2/cluster/tcp.c b/fs/ocfs2/cluster/tcp.c index 09a1f3b77bb8..e62c1ef8223b 100644 --- a/fs/ocfs2/cluster/tcp.c +++ b/fs/ocfs2/cluster/tcp.c @@ -105,6 +105,8 @@ static struct socket *o2net_listen_sock; * destroying the work queue. */ static struct workqueue_struct *o2net_wq; +/* Heartbeat callbacks stay registered across local-node off/on. */ +static bool o2net_listening; static struct work_struct o2net_listen_work; static struct o2hb_callback_func o2net_hb_up, o2net_hb_down; @@ -1692,6 +1694,19 @@ static void o2net_still_up(struct work_struct *work) /* ------------------------------------------------------------ */ +static void o2net_hb_node_up(struct o2net_node *nn) +{ + /* ensure an immediate connect attempt */ + nn->nn_last_connect_attempt = jiffies - + (msecs_to_jiffies(o2net_reconnect_delay()) + 1); + + spin_lock(&nn->nn_lock); + atomic_set(&nn->nn_timeout, 0); + if (nn->nn_persistent_error) + o2net_set_nn_state(nn, NULL, 0, 0); + spin_unlock(&nn->nn_lock); +} + void o2net_disconnect_node(struct o2nm_node *node) { struct o2net_node *nn = o2net_nn_from_num(node->nd_num); @@ -1713,41 +1728,39 @@ void o2net_disconnect_node(struct o2nm_node *node) static void o2net_hb_node_down_cb(struct o2nm_node *node, int node_num, void *data) { + u8 this_node; + o2quo_hb_down(node_num); if (!node) - return; + goto out; - if (node_num != o2nm_this_node()) + this_node = o2nm_this_node(); + if (!READ_ONCE(o2net_listening) || this_node == O2NM_MAX_NODES) + goto out; + + if (node_num != this_node) o2net_disconnect_node(node); +out: BUG_ON(atomic_read(&o2net_connected_peers) < 0); } static void o2net_hb_node_up_cb(struct o2nm_node *node, int node_num, void *data) { - struct o2net_node *nn = o2net_nn_from_num(node_num); + u8 this_node; o2quo_hb_up(node_num); BUG_ON(!node); - /* ensure an immediate connect attempt */ - nn->nn_last_connect_attempt = jiffies - - (msecs_to_jiffies(o2net_reconnect_delay()) + 1); + this_node = o2nm_this_node(); + if (!READ_ONCE(o2net_listening) || this_node == O2NM_MAX_NODES) + return; - if (node_num != o2nm_this_node()) { - /* believe it or not, accept and node heartbeating testing - * can succeed for this node before we got here.. so - * only use set_nn_state to clear the persistent error - * if that hasn't already happened */ - spin_lock(&nn->nn_lock); - atomic_set(&nn->nn_timeout, 0); - if (nn->nn_persistent_error) - o2net_set_nn_state(nn, NULL, 0, 0); - spin_unlock(&nn->nn_lock); - } + if (node_num != this_node) + o2net_hb_node_up(o2net_nn_from_num(node_num)); } void o2net_unregister_hb_callbacks(void) @@ -1756,6 +1769,37 @@ void o2net_unregister_hb_callbacks(void) o2hb_unregister_callback(NULL, &o2net_hb_down); } +/* + * Delay heartbeat-driven network work until the local node is fully published + * through o2nm_this_node(), then replay the nodes that are already live while + * callback delivery stays blocked. + */ +void o2net_complete_start_listening(struct o2nm_node *node) +{ + unsigned long live_nodes[BITS_TO_LONGS(O2NM_MAX_NODES)]; + unsigned long node_num; + u8 local_node; + + local_node = o2nm_this_node(); + if (WARN_ON_ONCE(local_node == O2NM_MAX_NODES)) + return; + if (WARN_ON_ONCE(local_node != node->nd_num)) + return; + if (WARN_ON_ONCE(!o2net_wq)) + return; + + o2hb_callback_read_lock(); + WRITE_ONCE(o2net_listening, true); + o2hb_fill_node_map_locked(live_nodes, O2NM_MAX_NODES); + for_each_set_bit(node_num, live_nodes, O2NM_MAX_NODES) { + if (node_num == local_node) + continue; + + o2net_hb_node_up(o2net_nn_from_num(node_num)); + } + o2hb_callback_read_unlock(); +} + int o2net_register_hb_callbacks(void) { int ret; @@ -2034,6 +2078,8 @@ int o2net_start_listening(struct o2nm_node *node) { int ret = 0; + if (WARN_ON_ONCE(READ_ONCE(o2net_listening))) + return -EBUSY; BUG_ON(o2net_wq != NULL); BUG_ON(o2net_listen_sock != NULL); @@ -2065,6 +2111,9 @@ void o2net_stop_listening(struct o2nm_node *node) BUG_ON(o2net_wq == NULL); BUG_ON(o2net_listen_sock == NULL); + WRITE_ONCE(o2net_listening, false); + o2hb_synchronize_callbacks(); + /* stop the listening socket from generating work */ write_lock_bh(&sock->sk->sk_callback_lock); sock->sk->sk_data_ready = sock->sk->sk_user_data; diff --git a/fs/ocfs2/cluster/tcp.h b/fs/ocfs2/cluster/tcp.h index a75b551d31c7..2e86d42b5faf 100644 --- a/fs/ocfs2/cluster/tcp.h +++ b/fs/ocfs2/cluster/tcp.h @@ -96,6 +96,7 @@ struct o2nm_node; int o2net_register_hb_callbacks(void); void o2net_unregister_hb_callbacks(void); int o2net_start_listening(struct o2nm_node *node); +void o2net_complete_start_listening(struct o2nm_node *node); void o2net_stop_listening(struct o2nm_node *node); void o2net_disconnect_node(struct o2nm_node *node); int o2net_num_connected_peers(void); From 011291b70ba4832e136b7b581825b2bc0f525bf6 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Wed, 24 Jun 2026 17:53:10 +0800 Subject: [PATCH 68/95] ocfs2: o2hb: quiesce negotiate handlers and timeout work Heartbeat regions publish struct o2hb_region as the private data for the NEGO_TIMEOUT and NEGO_APPROVE o2net handlers as soon as make_item() creates the configfs region. The approve handler can call o2hb_arm_timeout(), so a peer can touch the region timeout work before dev_store() has finished building the heartbeat runtime, or after teardown has started to shut that runtime back down. The final configfs put also has to keep reg alive until the last in-flight o2net callback drops its handler reference. o2net_unregister_handler_list() blocks future handler lookups, but it does not wait for sc_rx_work that already passed o2net_handler_get(). That drain needs to cover local listener teardown as well, where the o2net ordered workqueue may already be inside destroy_workqueue(). Fix the lifetime rule in both directions. Initialize the region delayed works before publishing reg through the o2net handler table, keep new or stopping regions non-armable with hr_stopping, and quiesce both delayed works on failed-start and teardown paths even when no heartbeat thread is left to call o2hb_disarm_timeout(). Then unregister handlers before tearing down handler-visible region state and make the drain wait for the active or destroying o2net ordered workqueue before release frees reg. The buggy scenario involves two paths, with each column showing the order within that path: region lifecycle: late negotiate callback: 1. make_item() registers the 1. o2net_process_message() gets a region handlers before heartbeat handler for reg. dev_store() has built a 2. The callback runs after the lookup runnable heartbeat context. lock is dropped and dereferences reg. 2. A failed start or rmdir 3. An approve or timeout path tries to stops the heartbeat thread, queue reg's delayed work, or release quiesces existing work, and races the callback body after handler drops the final configfs ref. unregister. 3. region_release() must drain 4. The callback or delayed work can handler-visible o2net rx work outlive reg unless lifecycle code before freeing reg. keeps the region non-armable and drains the active-or-destroying o2net workqueue. Validation reproduced this kernel report: KASAN slab-use-after-free in __run_timers+0x22c/0x5b0 Write of size 8 Call trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 __run_timers+0x22c/0x5b0 kasan_report+0xe0/0x110 _raw_spin_unlock_irqrestore+0x27/0x60 try_to_wake_up+0x191/0xf70 timer_expire_remote+0xae/0xf0 run_timer_softirq+0x19b/0x1a0 handle_softirqs+0x156/0x660 __irq_exit_rcu+0xc4/0x160 irq_exit_rcu+0xe/0x20 sysvec_apic_timer_interrupt+0x6c/0x80 asm_sysvec_apic_timer_interrupt+0x1a/0x20 Allocated by task stack: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 o2hb_heartbeat_group_make_item+0x3c/0x600 Link: https://lore.kernel.org/20260624095310.763763-3-zzzccc427@gmail.com Fixes: 34069b886f95 ("ocfs2: o2hb: add NEGO_TIMEOUT message") Fixes: e76f8237a2f7 ("ocfs2: o2hb: add NEGOTIATE_APPROVE message") Signed-off-by: Cen Zhang Assisted-by: Codex:gpt-5.5 Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/cluster/heartbeat.c | 56 ++++++++++++++++--- fs/ocfs2/cluster/tcp.c | 103 ++++++++++++++++++++++++++++++----- fs/ocfs2/cluster/tcp.h | 1 + 3 files changed, 138 insertions(+), 22 deletions(-) diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c index 834210af4e4b..1c3def99bb07 100644 --- a/fs/ocfs2/cluster/heartbeat.c +++ b/fs/ocfs2/cluster/heartbeat.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -266,6 +267,9 @@ struct o2hb_region { /* Message key for negotiate timeout message. */ unsigned int hr_key; struct list_head hr_handler_list; + /* Serializes timeout arming against failed-start and teardown. */ + struct mutex hr_arming_mutex; + bool hr_stopping; /* last hb status, 0 for success, other value for error. */ int hr_last_hb_status; @@ -333,9 +337,14 @@ static void o2hb_write_timeout(struct work_struct *work) static void o2hb_arm_timeout(struct o2hb_region *reg) { + mutex_lock(®->hr_arming_mutex); + + if (reg->hr_stopping) + goto out_unlock; + /* Arm writeout only after thread reaches steady state */ if (atomic_read(®->hr_steady_iterations) != 0) - return; + goto out_unlock; mlog(ML_HEARTBEAT, "Queue write timeout for %u ms\n", O2HB_MAX_WRITE_TIMEOUT_MS); @@ -354,6 +363,18 @@ static void o2hb_arm_timeout(struct o2hb_region *reg) schedule_delayed_work(®->hr_nego_timeout_work, msecs_to_jiffies(O2HB_NEGO_TIMEOUT_MS)); bitmap_zero(reg->hr_nego_node_bitmap, O2NM_MAX_NODES); + +out_unlock: + mutex_unlock(®->hr_arming_mutex); +} + +static void o2hb_queue_nego_timeout(struct o2hb_region *reg, + unsigned long delay) +{ + mutex_lock(®->hr_arming_mutex); + if (!reg->hr_stopping) + schedule_delayed_work(®->hr_nego_timeout_work, delay); + mutex_unlock(®->hr_arming_mutex); } static void o2hb_disarm_timeout(struct o2hb_region *reg) @@ -362,6 +383,19 @@ static void o2hb_disarm_timeout(struct o2hb_region *reg) cancel_delayed_work_sync(®->hr_nego_timeout_work); } +static void o2hb_set_region_stopping(struct o2hb_region *reg, bool stopping) +{ + mutex_lock(®->hr_arming_mutex); + reg->hr_stopping = stopping; + mutex_unlock(®->hr_arming_mutex); +} + +static void o2hb_quiesce_timeout(struct o2hb_region *reg) +{ + o2hb_set_region_stopping(reg, true); + o2hb_disarm_timeout(reg); +} + static int o2hb_send_nego_msg(int key, int type, u8 target, u8 node_num) { struct o2hb_nego_msg msg; @@ -411,8 +445,7 @@ static void o2hb_nego_timeout(struct work_struct *work) /* check negotiate bitmap every second to do timeout * approve decision. */ - schedule_delayed_work(®->hr_nego_timeout_work, - msecs_to_jiffies(1000)); + o2hb_queue_nego_timeout(reg, msecs_to_jiffies(1000)); return; } @@ -1581,6 +1614,8 @@ static void o2hb_region_release(struct config_item *item) mlog(ML_HEARTBEAT, "hb region release (%pg)\n", reg_bdev(reg)); + o2hb_quiesce_timeout(reg); + o2net_unregister_and_flush_handler_list(®->hr_handler_list); o2hb_unmap_slot_data(reg); if (reg->hr_bdev_file) @@ -1596,7 +1631,6 @@ static void o2hb_region_release(struct config_item *item) list_del(®->hr_all_item); spin_unlock(&o2hb_live_lock); - o2net_unregister_handler_list(®->hr_handler_list); kfree(reg); } @@ -1911,9 +1945,6 @@ static ssize_t o2hb_region_dev_store(struct config_item *item, goto out; } - INIT_DELAYED_WORK(®->hr_write_timeout_work, o2hb_write_timeout); - INIT_DELAYED_WORK(®->hr_nego_timeout_work, o2hb_nego_timeout); - /* * A node is considered live after it has beat LIVE_THRESHOLD * times. We're not steady until we've given them a chance @@ -1933,6 +1964,7 @@ static ssize_t o2hb_region_dev_store(struct config_item *item, atomic_set(®->hr_steady_iterations, live_threshold); /* unsteady_iterations is triple the steady_iterations */ 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); @@ -1982,6 +2014,8 @@ static ssize_t o2hb_region_dev_store(struct config_item *item, out: if (ret < 0) { + o2hb_quiesce_timeout(reg); + spin_lock(&o2hb_live_lock); hb_task = reg->hr_task; reg->hr_task = NULL; @@ -2121,6 +2155,10 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g */ 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), @@ -2141,7 +2179,7 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g return ®->hr_item; unregister_handler: - o2net_unregister_handler_list(®->hr_handler_list); + o2net_unregister_and_flush_handler_list(®->hr_handler_list); remove_item: spin_lock(&o2hb_live_lock); list_del(®->hr_all_item); @@ -2160,6 +2198,8 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group, struct o2hb_region *reg = to_o2hb_region(item); int quorum_region = 0; + o2hb_quiesce_timeout(reg); + /* stop the thread when the user removes the region dir */ spin_lock(&o2hb_live_lock); hb_task = reg->hr_task; diff --git a/fs/ocfs2/cluster/tcp.c b/fs/ocfs2/cluster/tcp.c index e62c1ef8223b..474fe1414cee 100644 --- a/fs/ocfs2/cluster/tcp.c +++ b/fs/ocfs2/cluster/tcp.c @@ -38,6 +38,8 @@ */ #include +#include +#include #include #include #include @@ -102,9 +104,14 @@ static struct socket *o2net_listen_sock; * quorum work is queued as sock containers are shutdown.. stop_listening * tears down all the node's sock containers, preventing future shutdowns * and queued quorum work, before canceling delayed quorum work and - * destroying the work queue. + * destroying the work queue. Handler teardown can also race local listener + * shutdown, so keep a waitable destroying pointer until the old ordered + * queue has finished draining. */ static struct workqueue_struct *o2net_wq; +static struct workqueue_struct *o2net_wq_destroying; +static DEFINE_MUTEX(o2net_wq_mutex); +static DECLARE_COMPLETION(o2net_wq_destroyed); /* Heartbeat callbacks stay registered across local-node off/on. */ static bool o2net_listening; static struct work_struct o2net_listen_work; @@ -886,6 +893,27 @@ void o2net_unregister_handler_list(struct list_head *list) } EXPORT_SYMBOL_GPL(o2net_unregister_handler_list); +static void o2net_flush_wq(void) +{ + mutex_lock(&o2net_wq_mutex); + if (o2net_wq_destroying) { + mutex_unlock(&o2net_wq_mutex); + wait_for_completion(&o2net_wq_destroyed); + return; + } + + if (o2net_wq) + flush_workqueue(o2net_wq); + mutex_unlock(&o2net_wq_mutex); +} + +void o2net_unregister_and_flush_handler_list(struct list_head *list) +{ + o2net_unregister_handler_list(list); + o2net_flush_wq(); +} +EXPORT_SYMBOL_GPL(o2net_unregister_and_flush_handler_list); + static struct o2net_msg_handler *o2net_handler_get(u32 msg_type, u32 key) { struct o2net_msg_handler *nmh; @@ -1717,12 +1745,10 @@ void o2net_disconnect_node(struct o2nm_node *node) o2net_set_nn_state(nn, NULL, 0, -ENOTCONN); spin_unlock(&nn->nn_lock); - if (o2net_wq) { - cancel_delayed_work(&nn->nn_connect_expired); - cancel_delayed_work(&nn->nn_connect_work); - cancel_delayed_work(&nn->nn_still_up); - flush_workqueue(o2net_wq); - } + cancel_delayed_work(&nn->nn_connect_expired); + cancel_delayed_work(&nn->nn_connect_work); + cancel_delayed_work(&nn->nn_still_up); + o2net_flush_wq(); } static void o2net_hb_node_down_cb(struct o2nm_node *node, int node_num, @@ -2067,6 +2093,36 @@ static int o2net_open_listening_sock(__be32 addr, __be16 port) return ret; } +static void o2net_destroy_wq(void) +{ + struct workqueue_struct *wq; + + mutex_lock(&o2net_wq_mutex); + if (o2net_wq_destroying) { + mutex_unlock(&o2net_wq_mutex); + wait_for_completion(&o2net_wq_destroyed); + return; + } + + wq = o2net_wq; + if (!wq) { + mutex_unlock(&o2net_wq_mutex); + return; + } + + reinit_completion(&o2net_wq_destroyed); + o2net_wq_destroying = wq; + mutex_unlock(&o2net_wq_mutex); + + destroy_workqueue(wq); + + mutex_lock(&o2net_wq_mutex); + o2net_wq = NULL; + o2net_wq_destroying = NULL; + complete_all(&o2net_wq_destroyed); + mutex_unlock(&o2net_wq_mutex); +} + /* * called from node manager when we should bring up our network listening * socket. node manager handles all the serialization to only call this @@ -2077,24 +2133,44 @@ static int o2net_open_listening_sock(__be32 addr, __be16 port) int o2net_start_listening(struct o2nm_node *node) { int ret = 0; + struct workqueue_struct *wq; if (WARN_ON_ONCE(READ_ONCE(o2net_listening))) return -EBUSY; - BUG_ON(o2net_wq != NULL); + + mutex_lock(&o2net_wq_mutex); + if (o2net_wq_destroying) { + mutex_unlock(&o2net_wq_mutex); + return -EBUSY; + } + if (WARN_ON_ONCE(o2net_wq)) { + mutex_unlock(&o2net_wq_mutex); + return -EBUSY; + } + mutex_unlock(&o2net_wq_mutex); + BUG_ON(o2net_listen_sock != NULL); mlog(ML_KTHREAD, "starting o2net thread...\n"); - o2net_wq = alloc_ordered_workqueue("o2net", WQ_MEM_RECLAIM); - if (o2net_wq == NULL) { + wq = alloc_ordered_workqueue("o2net", WQ_MEM_RECLAIM); + if (!wq) { mlog(ML_ERROR, "unable to launch o2net thread\n"); return -ENOMEM; /* ? */ } + mutex_lock(&o2net_wq_mutex); + if (unlikely(o2net_wq_destroying || o2net_wq)) { + mutex_unlock(&o2net_wq_mutex); + destroy_workqueue(wq); + return -EBUSY; + } + o2net_wq = wq; + mutex_unlock(&o2net_wq_mutex); + ret = o2net_open_listening_sock(node->nd_ipv4_address, node->nd_ipv4_port); if (ret) { - destroy_workqueue(o2net_wq); - o2net_wq = NULL; + o2net_destroy_wq(); } else o2quo_conn_up(node->nd_num); @@ -2130,8 +2206,7 @@ void o2net_stop_listening(struct o2nm_node *node) /* finish all work and tear down the work queue */ mlog(ML_KTHREAD, "waiting for o2net thread to exit....\n"); - destroy_workqueue(o2net_wq); - o2net_wq = NULL; + o2net_destroy_wq(); sock_release(o2net_listen_sock); o2net_listen_sock = NULL; diff --git a/fs/ocfs2/cluster/tcp.h b/fs/ocfs2/cluster/tcp.h index 2e86d42b5faf..a11bcee28947 100644 --- a/fs/ocfs2/cluster/tcp.h +++ b/fs/ocfs2/cluster/tcp.h @@ -89,6 +89,7 @@ int o2net_register_handler(u32 msg_type, u32 key, u32 max_len, o2net_post_msg_handler_func *post_func, struct list_head *unreg_list); void o2net_unregister_handler_list(struct list_head *list); +void o2net_unregister_and_flush_handler_list(struct list_head *list); void o2net_fill_node_map(unsigned long *map, unsigned bytes); From 5cbef379a94b161726c5f504598bf4791d45cedc Mon Sep 17 00:00:00 2001 From: James Kim Date: Fri, 24 Jul 2026 08:52:20 +0900 Subject: [PATCH 69/95] rapidio: mport_cdev: fix use-after-free in dma_req_free() dma_req_free() acquires buf_mutex through req->map, drops the mapping reference with kref_put(), and then dereferences req->map again to unlock the mutex. If kref_put() drops the last reference, mport_release_mapping() frees the mapping, and the subsequent mutex_unlock() dereferences a freed object. This is a use-after-free. Fix this by caching map and md before kref_put(), clearing req->map while holding buf_mutex, and using the cached md for mutex unlocking. The bug is reachable from userspace via the RapidIO mport character device interface. Link: https://lore.kernel.org/20260723235220.588424-1-james010kim@gmail.com Fixes: e8de370188d0 ("rapidio: add mport char device driver") Signed-off-by: James Kim Reviewed-by: Dan Carpenter Cc: Alexandre Bounine Cc: Greg Kroah-Hartman Cc: Matt Porter Cc: Signed-off-by: Andrew Morton --- drivers/rapidio/devices/rio_mport_cdev.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/rapidio/devices/rio_mport_cdev.c b/drivers/rapidio/devices/rio_mport_cdev.c index 009b3b595bbf..ad82c2108a56 100644 --- a/drivers/rapidio/devices/rio_mport_cdev.c +++ b/drivers/rapidio/devices/rio_mport_cdev.c @@ -564,9 +564,13 @@ static void dma_req_free(struct kref *ref) } if (req->map) { - mutex_lock(&req->map->md->buf_mutex); - kref_put(&req->map->ref, mport_release_mapping); - mutex_unlock(&req->map->md->buf_mutex); + struct rio_mport_mapping *map = req->map; + struct mport_dev *md = map->md; + + mutex_lock(&md->buf_mutex); + req->map = NULL; + kref_put(&map->ref, mport_release_mapping); + mutex_unlock(&md->buf_mutex); } kref_put(&priv->dma_ref, mport_release_dma); From 9f121705503a17007777adfa609df46bb7c35be0 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 9 Jun 2026 18:00:28 +0300 Subject: [PATCH 70/95] lib/xz: replace min_t with min Use the simpler min() macro since the values are unsigned and compatible. Link: https://lore.kernel.org/20260609150030.634570-1-lasse.collin@tukaani.org Signed-off-by: Thorsten Blum Signed-off-by: Lasse Collin Reviewed-by: Lasse Collin Signed-off-by: Andrew Morton --- lib/xz/xz_dec_bcj.c | 2 +- lib/xz/xz_dec_lzma2.c | 11 +++++------ lib/xz/xz_dec_stream.c | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/xz/xz_dec_bcj.c b/lib/xz/xz_dec_bcj.c index cc49a300a5b2..88922323f96e 100644 --- a/lib/xz/xz_dec_bcj.c +++ b/lib/xz/xz_dec_bcj.c @@ -466,7 +466,7 @@ static void bcj_flush(struct xz_dec_bcj *s, struct xz_buf *b) { size_t copy_size; - copy_size = min_t(size_t, s->temp.filtered, b->out_size - b->out_pos); + copy_size = min(s->temp.filtered, b->out_size - b->out_pos); memcpy(b->out + b->out_pos, s->temp.buf, copy_size); b->out_pos += copy_size; diff --git a/lib/xz/xz_dec_lzma2.c b/lib/xz/xz_dec_lzma2.c index 4b783ac94e71..9d80342b9c6b 100644 --- a/lib/xz/xz_dec_lzma2.c +++ b/lib/xz/xz_dec_lzma2.c @@ -354,7 +354,7 @@ static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) if (dist >= dict->full || dist >= dict->size) return false; - left = min_t(size_t, dict->limit - dict->pos, *len); + left = min(dict->limit - dict->pos, *len); *len -= left; back = dict->pos - dist - 1; @@ -1098,9 +1098,8 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) * the output buffer yet, we may run this loop * multiple times without changing s->lzma2.sequence. */ - dict_limit(&s->dict, min_t(size_t, - b->out_size - b->out_pos, - s->lzma2.uncompressed)); + dict_limit(&s->dict, min(b->out_size - b->out_pos, + s->lzma2.uncompressed)); if (!lzma2_lzma(s, b)) return XZ_DATA_ERROR; @@ -1260,8 +1259,8 @@ enum xz_ret xz_dec_microlzma_run(struct xz_dec_microlzma *s_ptr, s->dict.end = b->out_size - b->out_pos; while (true) { - dict_limit(&s->dict, min_t(size_t, b->out_size - b->out_pos, - s->lzma2.uncompressed)); + dict_limit(&s->dict, min(b->out_size - b->out_pos, + s->lzma2.uncompressed)); if (!lzma2_lzma(s, b)) return XZ_DATA_ERROR; diff --git a/lib/xz/xz_dec_stream.c b/lib/xz/xz_dec_stream.c index 59bfd54ffee7..0bed6daefac2 100644 --- a/lib/xz/xz_dec_stream.c +++ b/lib/xz/xz_dec_stream.c @@ -155,8 +155,8 @@ static const uint8_t check_sizes[16] = { */ static bool fill_temp(struct xz_dec *s, struct xz_buf *b) { - size_t copy_size = min_t(size_t, - b->in_size - b->in_pos, s->temp.size - s->temp.pos); + size_t copy_size = min(b->in_size - b->in_pos, + s->temp.size - s->temp.pos); memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size); b->in_pos += copy_size; From 36969d97fea854b524784e486322325f6d2c74cb Mon Sep 17 00:00:00 2001 From: Jianlin Shi Date: Mon, 15 Jun 2026 10:27:41 +0800 Subject: [PATCH 71/95] ipc: only destroy orphaned shm segments on sysctl write proc_ipc_dointvec_minmax_orphans() currently calls shm_destroy_orphaned() whenever shm_rmid_forced is set, including on sysctl reads. Reading /proc/sys/kernel/shm_rmid_forced should not take shm_ids rwsem for write and walk all segments. Only run the cleanup when the sysctl is written and the forced RMID policy is enabled. When shm_rmid_forced=1, monitoring tools that read /proc/sys/kernel/shm_rmid_forced trigger the cleanup on every read. Link: https://lore.kernel.org/all/?q=only+destroy+orphaned+shm+segments+on+sysctl+write Link: https://lore.kernel.org/tencent_738A8BC6E9EA205F555E4B0DAA154D4F8E0A@qq.com Signed-off-by: Jianlin Shi Acked-by: Davidlohr Bueso Cc: "Eric W. Biederman" Signed-off-by: Andrew Morton --- ipc/ipc_sysctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c index 9b087ebeb643..d038d944257f 100644 --- a/ipc/ipc_sysctl.c +++ b/ipc/ipc_sysctl.c @@ -28,7 +28,7 @@ static int proc_ipc_dointvec_minmax_orphans(const struct ctl_table *table, int w if (err < 0) return err; - if (ns->shm_rmid_forced) + if (write && ns->shm_rmid_forced) shm_destroy_orphaned(ns); return err; } From f7f824e54b842c1e33a8e2e7304cd72def3c622e Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Sun, 14 Jun 2026 19:05:17 +0300 Subject: [PATCH 72/95] lib/xz: use size_t instead of uint32_t in a few places Reduce the number of uint32_t <-> size_t conversions a little. Eliminating such conversions entirely would require changing almost all uint32_t to size_t, which would look confusing and increase the sizes of the structs even more. Going the other way, converting everything to uint32_t, isn't possible because the input and output buffers use size_t in struct xz_buf. Now both arguments to min() have the same type. This is required to for compatibility with PowerPC boot code[1] whose min() is strict like include/linux/minmax.h was before the commit d03eba99f5bf ("minmax: allow min()/max()/clamp() if the arguments have the same signedness."). Swap the order of the "state" and "len" in struct lzma_dec to avoid padding in the middle of the struct when size_t is 64 bits. The reordering doesn't change the size of the struct; the padding just appears at the end instead. dict_flush() used to truncate size_t to uint32_t when returning. This wasn't a bug; the value is always small enough. Link: https://lore.kernel.org/20260614160521.924710-1-lasse.collin@tukaani.org Signed-off-by: Lasse Collin Reported-by: Nathan Chancellor Closes: https://lore.kernel.org/lkml/20260610232323.GA1071374@ax162/ [1] Reviewed-by: Thorsten Blum Cc: David Laight Signed-off-by: Andrew Morton --- lib/xz/xz_dec_lzma2.c | 40 ++++++++++++++++++++-------------------- lib/xz/xz_lzma2.h | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/xz/xz_dec_lzma2.c b/lib/xz/xz_dec_lzma2.c index 9d80342b9c6b..68ae9c33b6a8 100644 --- a/lib/xz/xz_dec_lzma2.c +++ b/lib/xz/xz_dec_lzma2.c @@ -135,14 +135,16 @@ struct lzma_dec { uint32_t rep2; uint32_t rep3; - /* Types of the most recently seen LZMA symbols */ - enum lzma_state state; - /* * Length of a match. This is updated so that dict_repeat can - * be called again to finish repeating the whole match. + * be called again to finish repeating the whole match. This is + * size_t because a pointer to this is passed to dict_repeat, + * and there it's nicer to have size_t instead of uint32_t. */ - uint32_t len; + size_t len; + + /* Types of the most recently seen LZMA symbols */ + enum lzma_state state; /* * LZMA properties or related bit masks (number of literal @@ -228,13 +230,13 @@ struct lzma2_dec { enum lzma2_seq next_sequence; /* Uncompressed size of LZMA chunk (2 MiB at maximum) */ - uint32_t uncompressed; + size_t uncompressed; /* * Compressed size of LZMA chunk or compressed/uncompressed * size of uncompressed chunk (64 KiB at maximum) */ - uint32_t compressed; + size_t compressed; /* * True if dictionary reset is needed. This is false before @@ -273,7 +275,7 @@ struct xz_dec_lzma2 { * decoder calls. See lzma2_lzma() for details. */ struct { - uint32_t size; + size_t size; uint8_t buf[3 * LZMA_IN_REQUIRED]; } temp; }; @@ -320,7 +322,7 @@ static inline bool dict_has_space(const struct dictionary *dict) * still empty. This special case is needed for single-call decoding to * avoid writing a '\0' to the end of the destination buffer. */ -static inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist) +static inline uint32_t dict_get(const struct dictionary *dict, size_t dist) { size_t offset = dict->pos - dist - 1; @@ -346,10 +348,10 @@ static inline void dict_put(struct dictionary *dict, uint8_t byte) * invalid, false is returned. On success, true is returned and *len is * updated to indicate how many bytes were left to be repeated. */ -static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) +static bool dict_repeat(struct dictionary *dict, size_t *len, size_t dist) { size_t back; - uint32_t left; + size_t left; if (dist >= dict->full || dist >= dict->size) return false; @@ -375,7 +377,7 @@ static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) /* Copy uncompressed data as is from input to dictionary and output buffers. */ static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, - uint32_t *left) + size_t *left) { size_t copy_size; @@ -433,7 +435,7 @@ static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, * enough space in b->out. This is guaranteed because caller uses dict_limit() * before decoding data into the dictionary. */ -static uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b) +static size_t dict_flush(struct dictionary *dict, struct xz_buf *b) { size_t copy_size = dict->pos - dict->start; @@ -878,7 +880,7 @@ static bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props) static bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b) { size_t in_avail; - uint32_t tmp; + size_t tmp; in_avail = b->in_size - b->in_pos; if (s->temp.size > 0 || s->lzma2.compressed == 0) { @@ -1046,25 +1048,23 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) case SEQ_UNCOMPRESSED_1: s->lzma2.uncompressed - += (uint32_t)b->in[b->in_pos++] << 8; + += (size_t)b->in[b->in_pos++] << 8; s->lzma2.sequence = SEQ_UNCOMPRESSED_2; break; case SEQ_UNCOMPRESSED_2: s->lzma2.uncompressed - += (uint32_t)b->in[b->in_pos++] + 1; + += (size_t)b->in[b->in_pos++] + 1; s->lzma2.sequence = SEQ_COMPRESSED_0; break; case SEQ_COMPRESSED_0: - s->lzma2.compressed - = (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.compressed = (size_t)b->in[b->in_pos++] << 8; s->lzma2.sequence = SEQ_COMPRESSED_1; break; case SEQ_COMPRESSED_1: - s->lzma2.compressed - += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.compressed += (size_t)b->in[b->in_pos++] + 1; s->lzma2.sequence = s->lzma2.next_sequence; break; diff --git a/lib/xz/xz_lzma2.h b/lib/xz/xz_lzma2.h index d2632b7dfb9c..a612ce4fd450 100644 --- a/lib/xz/xz_lzma2.h +++ b/lib/xz/xz_lzma2.h @@ -143,7 +143,7 @@ static inline bool lzma_state_is_literal(enum lzma_state state) * Get the index of the appropriate probability array for decoding * the distance slot. */ -static inline uint32_t lzma_get_dist_state(uint32_t len) +static inline size_t lzma_get_dist_state(size_t len) { return len < DIST_STATES + MATCH_LEN_MIN ? len - MATCH_LEN_MIN : DIST_STATES - 1; From 1a1b9b027049683f56aa66f854b982d7a4b82226 Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Sun, 14 Jun 2026 19:05:18 +0300 Subject: [PATCH 73/95] lib/xz: fix comments Link: https://lore.kernel.org/20260614160521.924710-2-lasse.collin@tukaani.org Signed-off-by: Lasse Collin Cc: David Laight Cc: Nathan Chancellor Cc: Thorsten Blum Signed-off-by: Andrew Morton --- lib/xz/xz_dec_lzma2.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/xz/xz_dec_lzma2.c b/lib/xz/xz_dec_lzma2.c index 68ae9c33b6a8..46c2df6ad6f5 100644 --- a/lib/xz/xz_dec_lzma2.c +++ b/lib/xz/xz_dec_lzma2.c @@ -271,7 +271,7 @@ struct xz_dec_lzma2 { struct lzma_dec lzma; /* - * Temporary buffer which holds small number of input bytes between + * Temporary buffer which holds a small number of input bytes between * decoder calls. See lzma2_lzma() for details. */ struct { @@ -757,8 +757,8 @@ static bool lzma_main(struct xz_dec_lzma2 *s) uint32_t pos_state; /* - * If the dictionary was reached during the previous call, try to - * finish the possibly pending repeat in the dictionary. + * If the dictionary write limit was reached during the previous call, + * try to finish the possibly pending repeat in the dictionary. */ if (dict_has_space(&s->dict) && s->lzma.len > 0) dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0); @@ -978,7 +978,7 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) * an uncompressed chunk * 0x02 Uncompressed chunk (no dictionary reset) * - * Highest three bits (s->control & 0xE0): + * Highest three bits (tmp & 0xE0): * 0xE0 Dictionary reset, new properties and state * reset, followed by LZMA compressed chunk * 0xC0 New properties and state reset, followed @@ -990,7 +990,7 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) * 0x80 LZMA chunk (no dictionary or state reset) * * For LZMA compressed chunks, the lowest five bits - * (s->control & 1F) are the highest bits of the + * (tmp & 1F) are the highest bits of the * uncompressed size (bits 16-20). * * A new LZMA2 stream must begin with a dictionary @@ -1091,7 +1091,7 @@ enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) case SEQ_LZMA_RUN: /* * Set dictionary limit to indicate how much we want - * to be encoded at maximum. Decode new data into the + * to be decoded at maximum. Decode new data into the * dictionary. Flush the new data from dictionary to * b->out. Check if we finished decoding this chunk. * In case the dictionary got full but we didn't fill From 36ffbe1d5e99ebe17bea60e65e053dbcd89bc04c Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Tue, 28 Jul 2026 20:21:03 +0000 Subject: [PATCH 74/95] taskstats: drop the dead NULL attribute check in parse() Patch series "taskstats: tidy up the cpumask command path". Two small cleanups from reading kernel/taskstats.c. No functional change in either one. This patch (of 2): taskstats_user_cmd() only calls the cpumask handlers after checking the same info->attrs[] entry, so parse() never sees a NULL attribute. Drop the check and its odd "return 1", which no caller tested for anyway. No functional change. Link: https://lore.kernel.org/20260728202104.17839-1-include@grrlz.net Link: https://lore.kernel.org/20260728202104.17839-2-include@grrlz.net Signed-off-by: Bradley Morgan Cc: Balbir Singh Signed-off-by: Andrew Morton --- kernel/taskstats.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/kernel/taskstats.c b/kernel/taskstats.c index 74f89b632c0a..7af47511152d 100644 --- a/kernel/taskstats.c +++ b/kernel/taskstats.c @@ -361,8 +361,6 @@ static int parse(struct nlattr *na, struct cpumask *mask) int len; int ret; - if (na == NULL) - return 1; len = nla_len(na); if (len > TASKSTATS_CPUMASK_MAXLEN) return -E2BIG; From c4904c4ae33c5f76c10b03a2283e47d8a6736941 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Tue, 28 Jul 2026 20:21:04 +0000 Subject: [PATCH 75/95] taskstats: fold the two cpumask handlers into one cmd_attr_register_cpumask() and cmd_attr_deregister_cpumask() differed only in which attribute they parsed and which action they passed on, so take both as arguments. __free(free_cpumask_var) then removes the goto. No functional change. Link: https://lore.kernel.org/20260728202104.17839-3-include@grrlz.net Signed-off-by: Bradley Morgan Cc: Balbir Singh Signed-off-by: Andrew Morton --- kernel/taskstats.c | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/kernel/taskstats.c b/kernel/taskstats.c index 7af47511152d..f31df72f0e9d 100644 --- a/kernel/taskstats.c +++ b/kernel/taskstats.c @@ -448,36 +448,18 @@ static int cgroupstats_user_cmd(struct sk_buff *skb, struct genl_info *info) return send_reply(rep_skb, info); } -static int cmd_attr_register_cpumask(struct genl_info *info) +static int cmd_attr_cpumask(struct genl_info *info, int attr, + enum actions action) { - cpumask_var_t mask; + cpumask_var_t mask __free(free_cpumask_var) = CPUMASK_VAR_NULL; int rc; if (!alloc_cpumask_var(&mask, GFP_KERNEL)) return -ENOMEM; - rc = parse(info->attrs[TASKSTATS_CMD_ATTR_REGISTER_CPUMASK], mask); + rc = parse(info->attrs[attr], mask); if (rc < 0) - goto out; - rc = add_del_listener(info->snd_portid, mask, REGISTER); -out: - free_cpumask_var(mask); - return rc; -} - -static int cmd_attr_deregister_cpumask(struct genl_info *info) -{ - cpumask_var_t mask; - int rc; - - if (!alloc_cpumask_var(&mask, GFP_KERNEL)) - return -ENOMEM; - rc = parse(info->attrs[TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK], mask); - if (rc < 0) - goto out; - rc = add_del_listener(info->snd_portid, mask, DEREGISTER); -out: - free_cpumask_var(mask); - return rc; + return rc; + return add_del_listener(info->snd_portid, mask, action); } static size_t taskstats_packet_size(void) @@ -552,9 +534,13 @@ static int cmd_attr_tgid(struct genl_info *info) static int taskstats_user_cmd(struct sk_buff *skb, struct genl_info *info) { if (info->attrs[TASKSTATS_CMD_ATTR_REGISTER_CPUMASK]) - return cmd_attr_register_cpumask(info); + return cmd_attr_cpumask(info, + TASKSTATS_CMD_ATTR_REGISTER_CPUMASK, + REGISTER); else if (info->attrs[TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK]) - return cmd_attr_deregister_cpumask(info); + return cmd_attr_cpumask(info, + TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK, + DEREGISTER); else if (info->attrs[TASKSTATS_CMD_ATTR_PID]) return cmd_attr_pid(info); else if (info->attrs[TASKSTATS_CMD_ATTR_TGID]) From aa27e55f24402ab7de5dc2593b54145f9b6dc3fe Mon Sep 17 00:00:00 2001 From: Xibo Wang Date: Thu, 30 Jul 2026 14:18:54 +0800 Subject: [PATCH 76/95] hung_task: update DETECT_HUNG_TASK_BLOCKER Kconfig help The help text still says the feature only covers mutexes, but blocker tracking has since been extended to semaphores and rwsems. Update the description to match the supported lock types. Link: https://lore.kernel.org/20260730061854.176547-1-wangxb12@chinatelecom.cn Cc: Petr Mladek Signed-off-by: Xibo Wang Suggested-by: Lance Yang Reviewed-by: Lance Yang Cc: "Masami Hiramatsu (Google)" Cc: Petr Mladek Signed-off-by: Andrew Morton --- lib/Kconfig.debug | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 2a6be1af94a3..31a61c83d023 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1320,10 +1320,10 @@ config DETECT_HUNG_TASK_BLOCKER depends on !PREEMPT_RT default y help - Say Y here to show the blocker task's stacktrace who acquires - the mutex lock which "hung tasks" are waiting. - This will add overhead a bit but shows suspicious tasks and - call trace if it comes from waiting a mutex. + Say Y here to show a possible blocker task's stacktrace when + a hung task is waiting on a mutex, semaphore, or rwsem. + This will add a bit of overhead but shows suspicious tasks and + call traces for waits on one of these locks. config WQ_WATCHDOG bool "Detect Workqueue Stalls" From 825cef942cd58ddf6d26d1f45a9d70d28ea63d55 Mon Sep 17 00:00:00 2001 From: Warren Xiong Date: Thu, 30 Jul 2026 09:58:09 +0800 Subject: [PATCH 77/95] tools/mm: prevent page_owner_sort from truncating input page_owner_sort opens the output file with "w" before reading the input. If both paths refer to the same file, this truncates the input and the tool silently processes zero records before returning success. Delay opening the output file until all input records have been loaded into memory. This allows the tool to sort a file in place without truncating data before it has been consumed. Link: https://lore.kernel.org/20260730015809.3819606-1-warren.xiong@ugreen.com Signed-off-by: Warren Xiong Reviewed-by: Andrew Morton Cc: Vishal Moola Cc: Ye Liu Cc: Zhen Ni Cc: Zi Yan Signed-off-by: Andrew Morton --- tools/mm/page_owner_sort.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/mm/page_owner_sort.c b/tools/mm/page_owner_sort.c index e6954909401c..904d48931af8 100644 --- a/tools/mm/page_owner_sort.c +++ b/tools/mm/page_owner_sort.c @@ -788,8 +788,7 @@ int main(int argc, char **argv) } fin = fopen(argv[optind], "r"); - fout = fopen(argv[optind + 1], "w"); - if (!fin || !fout) { + if (!fin) { usage(); perror("open: "); exit(1); @@ -826,6 +825,13 @@ int main(int argc, char **argv) goto out_free; } + fout = fopen(argv[optind + 1], "w"); + if (!fout) { + usage(); + perror("open: "); + exit(1); + } + printf("loaded %d\n", list_size); printf("sorting ....\n"); From 2780860eddecba9ffe210bb9436eee3cf22bfcdd Mon Sep 17 00:00:00 2001 From: Jordan R Abrahams-Whitehead Date: Fri, 31 Jul 2026 20:15:19 +0000 Subject: [PATCH 78/95] include/linux/list.h: mark list_add and __list_add as __always_inline This commit resolves an issue where modpost section verification fails due to section mismatches between list_add and its callers. At present, list_add (and its internal __list_add) are called from both .text and .init code sections. Since inlining can vary per call site, list_add can be 4 different states: list_add in text with arguments to non-.init.data values list_add in init with arguments to static .init.data values list_add in init with arguments to non-.init.data values list_add in text with arguments to static .init.data values It is last instance that ends up causing the section mismatch caused by constant propagation of the address of static libs inside the `dir_add` as seen below (with the dir_list being defined statically in initramfs.c, resting in .init.data). WARNING: modpost: vmlinux.o: section mismatch in reference: __list_add (section: .text.unlikely.) -> dir_list (section: .init.data) Because of these section matching requirements, semantically, __list_add and list_add MUST be inlined. This will then ensure callers inside .init will receive a list_add that exists and refers to only .init data, and list_add code in .text sections will only refer to non-init data. This issue manifests predominently in AutoFDO with clang, which is very hesitant to inline cold functions such as list_add even when marked `inline`. Marking them as `__always_inline` therefore matches the existing semantic constraints imposed by modpost's section mismatch checks. Link: https://lore.kernel.org/20260731-always-inline-list-add-v1-1-d29f54ce5477@google.com Link: https://lore.kernel.org/all/CANn89iJVQe=wedLheJmjZjOTJsWHijT0jZs=iRxKssJZbjAxHw@mail.gmail.com/ Signed-off-by: Jordan R Abrahams-Whitehead Suggested-by: Nathan Chancellor Suggested-by: Eric Dumazet Reviewed-by: Nick Desaulniers Tested-by: Nick Desaulniers Reported-by: Giuliano Procida Reported-by: Yabin Cui Closes: https://github.com/ClangBuiltLinux/linux/issues/2173 Cc: Bill Wendling Cc: Justin Stitt Cc: Signed-off-by: Andrew Morton --- include/linux/list.h | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/include/linux/list.h b/include/linux/list.h index 09d979976b3b..59f8aa0905d3 100644 --- a/include/linux/list.h +++ b/include/linux/list.h @@ -150,10 +150,13 @@ static inline bool __list_del_entry_valid(struct list_head *entry) * * This is only for internal list manipulation where we know * the prev/next entries already! + * + * Must be inlined to ensure it can be safely called + * with initdata arguments. */ -static inline void __list_add(struct list_head *new, - struct list_head *prev, - struct list_head *next) +static __always_inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) { if (!__list_add_valid(new, prev, next)) return; @@ -171,8 +174,12 @@ static inline void __list_add(struct list_head *new, * * Insert a new entry after the specified head. * This is good for implementing stacks. + * + * Must be inlined to ensure it can be safely called + * with initdata arguments. */ -static inline void list_add(struct list_head *new, struct list_head *head) +static __always_inline void list_add(struct list_head *new, + struct list_head *head) { __list_add(new, head, head->next); } From 809f455a75fdfebf8cf3fee5ec6275989b2acedf Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Mon, 3 Aug 2026 13:58:21 +0200 Subject: [PATCH 79/95] MAINTAINERS: add IRC and patchwork for LTP LTP project recently switched to patchwork.kernel.org, document it. Add also IRC channel. Link: https://github.com/linux-test-project/ltp/commit/3590f66120d1c875bef5d573c66c4c0d340c1612 Link: https://lore.kernel.org/ltp/20260731054548.133241-1-pvorel@suse.cz/ Link: https://lore.kernel.org/20260803115821.238704-1-pvorel@suse.cz Signed-off-by: Petr Vorel Suggested-by: Cyril Hrubis Reviewed-by: Cyril Hrubis Reviewed-by: Li Wang Reviewed-by: Andrea Cervesato Cc: Anders Roxell Cc: Ben Copeland Cc: Jan Stancek Cc: Tim Bird Signed-off-by: Andrew Morton --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2f9472c1a090..5014c48783cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15426,7 +15426,9 @@ M: Yang Xu M: Xiao Yang L: ltp@lists.linux.it (subscribers-only) S: Maintained +C: irc://irc.libera.chat/ltp W: https://linux-test-project.readthedocs.io/ +Q: https://patchwork.kernel.org/project/ltp/list/ T: git https://github.com/linux-test-project/ltp.git LTR390 AMBIENT/UV LIGHT SENSOR DRIVER From 8f1d96a16c6313afb58b1ccc1ea0e8df4bd6efd7 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Mon, 3 Aug 2026 18:30:46 +0800 Subject: [PATCH 80/95] selftests/prctl: fix non-anonymous VMA mapping in set-anon-vma-name test The test creates a non-anonymous VMA (ptr_not_anon) via mmap() with MAP_PRIVATE but without MAP_ANONYMOUS, using fd=0 (stdin) as the file descriptor. This always fails because fd=0 is not a regular file, and the failure was hidden because ASSERT_NE() incorrectly checked for NULL instead of MAP_FAILED. Fix by using mkstemp() + ftruncate() to create a real temporary file, then mapping it with MAP_PRIVATE to obtain a genuine file-backed VMA. Also fix the mmap() error checks to use MAP_FAILED instead of NULL, and pass fd=-1 for the anonymous mapping for clarity. The temp file is unlinked immediately so it does not persist on disk. Link: https://lore.kernel.org/20260803103046.14324-1-hongfu.li@linux.dev Signed-off-by: Hongfu Li Cc: Shuah Khan Cc: Wei Yang Signed-off-by: Andrew Morton --- .../selftests/prctl/set-anon-vma-name-test.c | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/prctl/set-anon-vma-name-test.c b/tools/testing/selftests/prctl/set-anon-vma-name-test.c index ac6721b184a6..5f9589534da8 100644 --- a/tools/testing/selftests/prctl/set-anon-vma-name-test.c +++ b/tools/testing/selftests/prctl/set-anon-vma-name-test.c @@ -4,11 +4,13 @@ */ #include +#include #include #include #include #include #include +#include #include "kselftest_harness.h" @@ -73,15 +75,24 @@ int was_renaming_successful(char *target_name, unsigned long ptr) FIXTURE(vma) { void *ptr_anon, *ptr_not_anon; + int fd_not_anon; }; FIXTURE_SETUP(vma) { + char template[] = "./set-anon-vma-test-XXXXXX"; + self->ptr_anon = mmap(NULL, AREA_SIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); - ASSERT_NE(self->ptr_anon, NULL); + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(self->ptr_anon, MAP_FAILED); + + self->fd_not_anon = mkstemp(template); + ASSERT_NE(self->fd_not_anon, -1); + unlink(template); + ASSERT_EQ(ftruncate(self->fd_not_anon, AREA_SIZE), 0); self->ptr_not_anon = mmap(NULL, AREA_SIZE, PROT_READ | PROT_WRITE, - MAP_PRIVATE, 0, 0); - ASSERT_NE(self->ptr_not_anon, NULL); + MAP_PRIVATE, self->fd_not_anon, 0); + ASSERT_NE(self->ptr_not_anon, MAP_FAILED); + close(self->fd_not_anon); } FIXTURE_TEARDOWN(vma) { @@ -98,7 +109,7 @@ TEST_F(vma, renaming) { EXPECT_EQ(rename_vma((unsigned long)self->ptr_anon, AREA_SIZE, BAD_NAME), -EINVAL); TH_LOG("Try to rename non-anonymous VMA"); - EXPECT_EQ(rename_vma((unsigned long) self->ptr_not_anon, AREA_SIZE, GOOD_NAME), -EINVAL); + EXPECT_EQ(rename_vma((unsigned long) self->ptr_not_anon, AREA_SIZE, GOOD_NAME), -EBADF); } TEST_HARNESS_MAIN From b2d31acbd3b182755b019183fb46949ba5e39b9f Mon Sep 17 00:00:00 2001 From: ZhengYuan Huang Date: Mon, 3 Aug 2026 11:00:06 +0800 Subject: [PATCH 81/95] ocfs2: validate orphan slot during inode read Patch series "ocfs2: validate active orphan slots during inode read". OCFS2 trusts active ordinary and append-DIO orphan slots read from dinodes. A corrupted slot can therefore index osb_orphan_wipes or the slot-local system-inode cache outside their allocations before the corruption is reported. Patch 1 validates the ordinary orphan slot used by inode wipe processing. Patch 2 validates the append-DIO orphan slot used by DIO completion and orphan recovery. Both checks reject corrupt metadata at the existing inode validation boundary. This patch (of 2): [BUG] A corrupted dinode with OCFS2_ORPHANED_FL can carry an i_orphaned_slot outside the mounted filesystem slot range. ocfs2_wipe_inode() uses it to index osb_orphan_wipes before looking up the orphan directory, causing an out-of-bounds memory access. BUG: KASAN: slab-use-after-free in ocfs2_get_system_file_inode+0x780/0x820 fs/ocfs2/sysfile.c:102 Read of size 8 at addr ffff88800b767c00 by task kworker/u8:3/85 Call Trace: ... ocfs2_get_system_file_inode+0x780/0x820 fs/ocfs2/sysfile.c:102 ocfs2_wipe_inode+0x292/0xf70 fs/ocfs2/inode.c:840 ocfs2_delete_inode fs/ocfs2/inode.c:1155 [inline] ocfs2_evict_inode+0x6c9/0x1170 fs/ocfs2/inode.c:1295 evict+0x38e/0x8f0 fs/inode.c:810 iput_final fs/inode.c:1914 [inline] iput fs/inode.c:1966 [inline] iput+0x55b/0x8b0 fs/inode.c:1926 ocfs2_recover_orphans+0x610/0xe40 fs/ocfs2/journal.c:2374 ocfs2_complete_recovery+0x5af/0xd00 fs/ocfs2/journal.c:1373 ... [CAUSE] ocfs2_validate_inode_block() validates i_suballoc_slot but leaves the active ordinary orphan slot unchecked. Downstream consumers assume that the value is smaller than osb->max_slots. [FIX] Reject an active i_orphaned_slot outside the slot range during dinode validation, before the inode reaches orphan wipe processing. Link: https://lore.kernel.org/20260803030007.3993199-1-gality369@gmail.com Link: https://lore.kernel.org/20260803030007.3993199-2-gality369@gmail.com Fixes: b4df6ed8db0c ("[PATCH] ocfs2: fix orphan recovery deadlock") Signed-off-by: ZhengYuan Huang Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/inode.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index 41db7dd39ed9..358ab3535366 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -1528,6 +1528,14 @@ int ocfs2_validate_inode_block(struct super_block *sb, 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", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(di->i_orphaned_slot)); + goto bail; + } + /* * Reject dinodes whose i_mode does not name one of the seven * canonical POSIX file types. ocfs2_populate_inode() copies From bb88131c9831075b8dc08cdd375743e5d44c7ca2 Mon Sep 17 00:00:00 2001 From: ZhengYuan Huang Date: Mon, 3 Aug 2026 11:00:07 +0800 Subject: [PATCH 82/95] ocfs2: validate DIO orphan slot during inode read [BUG] A corrupted append-DIO dinode (high byte at offset 0xa1 corrupted from 0 to 1) can carry an i_dio_orphaned_slot outside the mounted filesystem slot range and trigger a use-after-free error: BUG: KASAN: slab-use-after-free in ocfs2_get_system_file_inode+0x780/0x820 fs/ocfs2/sysfile.c:102 Read of size 8 at addr ffff88800b767c00 by task kworker/u8:3/85 Call Trace: ... ocfs2_get_system_file_inode+0x780/0x820 fs/ocfs2/sysfile.c:102 ocfs2_wipe_inode+0x292/0xf70 fs/ocfs2/inode.c:840 ocfs2_delete_inode fs/ocfs2/inode.c:1155 [inline] ocfs2_evict_inode+0x6c9/0x1170 fs/ocfs2/inode.c:1295 evict+0x38e/0x8f0 fs/inode.c:810 iput_final fs/inode.c:1914 [inline] iput fs/inode.c:1966 [inline] iput+0x55b/0x8b0 fs/inode.c:1926 ocfs2_recover_orphans+0x610/0xe40 fs/ocfs2/journal.c:2374 ocfs2_complete_recovery+0x5af/0xd00 fs/ocfs2/journal.c:1373 ... [CAUSE] ocfs2_del_inode_from_orphan() uses i_dio_orphaned_slot to index the slot-local system inode cache. The dinode validator does not check this active slot, so an out-of-range value produces an invalid cache entry pointer that is dereferenced as an inode pointer. [FIX] Reject an active i_dio_orphaned_slot outside the slot range during dinode validation, before DIO orphan recovery can consume it. Link: https://lore.kernel.org/20260803030007.3993199-3-gality369@gmail.com Fixes: 06ee5c75b575 ("ocfs2: add functions to add and remove inode in orphan dir") Signed-off-by: ZhengYuan Huang Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/inode.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c index 358ab3535366..180107a11046 100644 --- a/fs/ocfs2/inode.c +++ b/fs/ocfs2/inode.c @@ -1536,6 +1536,14 @@ int ocfs2_validate_inode_block(struct super_block *sb, goto bail; } + if ((le32_to_cpu(di->i_flags) & OCFS2_DIO_ORPHANED_FL) && + le16_to_cpu(di->i_dio_orphaned_slot) >= OCFS2_SB(sb)->max_slots) { + rc = ocfs2_error(sb, "Invalid dinode %llu: DIO orphaned slot %u\n", + (unsigned long long)bh->b_blocknr, + le16_to_cpu(di->i_dio_orphaned_slot)); + goto bail; + } + /* * Reject dinodes whose i_mode does not name one of the seven * canonical POSIX file types. ocfs2_populate_inode() copies From bd7c05fb4a4776dff5a87b19008d28458647d15d Mon Sep 17 00:00:00 2001 From: Krystian Kaniewski Date: Thu, 30 Jul 2026 07:42:21 +0000 Subject: [PATCH 83/95] ocfs2: fix circular locking dependency in ocfs2_init_acl() A lockdep warning indicates a circular locking dependency between `&oi->ip_xattr_sem` and `&journal->j_trans_barrier`: WARNING: possible circular locking dependency detected is trying to acquire lock: (&oi->ip_xattr_sem){++++}-{4:4}, at: ocfs2_init_acl+0x2fd/0x7e0 fs/ocfs2/acl.c:367 but task is already holding lock: (&journal->j_trans_barrier){.+.+}-{4:4}, at: ocfs2_start_trans+0x3ab/0x700 fs/ocfs2/journal.c:369 The deadlock involves two code paths: Path 1 (setxattr) where `ocfs2_xattr_set()` acquires `ip_xattr_sem` (write) and then starts a transaction, which acquires `j_trans_barrier` (read); and Path 2 (mkdir/mknod) where `ocfs2_mknod()` starts a transaction (`j_trans_barrier` read) and then calls `ocfs2_init_acl()`, which attempts to acquire `ip_xattr_sem` (read) on the parent directory to retrieve the default ACL. Because rw_semaphores are subject to writer priority, a pending writer on `j_trans_barrier` (e.g., the journal commit thread) can cause Path 1 to block, while Path 2 is blocked waiting for Path 1 to release `ip_xattr_sem`. The patch fixes the lock ordering by precomputing the ACL state before starting the OCFS2 transaction, while preserving POSIX ACL storage semantics and the existing inode/security initialization order. By reading the parent directory's default ACL and preparing the new inode's ACLs outside the transaction, `ip_xattr_sem` is always acquired before `j_trans_barrier`. `struct ocfs2_acl_state` encapsulates the prepared ACL state, while `ocfs2_acl_init_prepare()` and `ocfs2_acl_init_release()` avoid code duplication between `ocfs2_mknod()` and `ocfs2_init_security_and_acl()`. `ocfs2_calc_xattr_init()` and `ocfs2_init_acl()` use this precomputed state, removing internal `ip_xattr_sem` acquisition and redundant disk reads. Additionally, remove the `ip_xattr_sem` acquisition from `ocfs2_xattr_set_handle()`. This function is only used while initializing a new inode that has not yet been inserted into the inode hash or attached to a dentry, meaning there is no risk of concurrent access and the lock is unnecessary. Link: https://lore.kernel.org/4094de06-9b69-4174-b2ee-08126dffc693@mail.kernel.org Fixes: 16c8d569f570 ("ocfs2/acl: use 'ip_xattr_sem' to protect getting extended attribute") Signed-off-by: Krystian Kaniewski Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+4007ab5229e732466d9f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4007ab5229e732466d9f Link: https://syzkaller.appspot.com/ai_job?id=cc75363d-c672-499e-8fc5-44bcdc1cee39 Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/acl.c | 141 ++++++++++++++++++++++++++++++----------------- fs/ocfs2/acl.h | 19 +++++-- fs/ocfs2/namei.c | 17 ++++-- fs/ocfs2/xattr.c | 86 +++++++++++++++++------------ fs/ocfs2/xattr.h | 9 ++- 5 files changed, 176 insertions(+), 96 deletions(-) diff --git a/fs/ocfs2/acl.c b/fs/ocfs2/acl.c index af1e2cedb217..090ec60fb576 100644 --- a/fs/ocfs2/acl.c +++ b/fs/ocfs2/acl.c @@ -110,8 +110,7 @@ static void *ocfs2_acl_to_xattr(const struct posix_acl *acl, size_t *size) return ocfs2_acl; } -static struct posix_acl *ocfs2_get_acl_nolock(struct inode *inode, - int type, +static struct posix_acl *ocfs2_get_acl_nolock(struct inode *inode, int type, struct buffer_head *di_bh) { int name_index; @@ -349,63 +348,105 @@ int ocfs2_acl_chmod(struct inode *inode, struct buffer_head *bh) * Initialize the ACLs of a new inode. If parent directory has default ACL, * then clone to new inode. Called from ocfs2_mknod. */ -int ocfs2_init_acl(handle_t *handle, - struct inode *inode, - struct inode *dir, - struct buffer_head *di_bh, - struct buffer_head *dir_bh, - struct ocfs2_alloc_context *meta_ac, - struct ocfs2_alloc_context *data_ac) +void ocfs2_acl_init_release(struct ocfs2_acl_state *state) +{ + posix_acl_release(state->default_acl); + posix_acl_release(state->acl); + state->default_acl = NULL; + state->acl = NULL; +} + +int ocfs2_acl_init_prepare(struct inode *inode, struct inode *dir, + struct buffer_head *dir_bh, + struct ocfs2_acl_state *state) { struct ocfs2_super *osb = OCFS2_SB(inode->i_sb); - struct posix_acl *acl = NULL; - int ret = 0, ret2; - umode_t mode; + int ret = 0; - if (!S_ISLNK(inode->i_mode)) { - if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) { - down_read(&OCFS2_I(dir)->ip_xattr_sem); - acl = ocfs2_get_acl_nolock(dir, ACL_TYPE_DEFAULT, - dir_bh); - up_read(&OCFS2_I(dir)->ip_xattr_sem); - if (IS_ERR(acl)) - return PTR_ERR(acl); + state->default_acl = NULL; + state->acl = NULL; + state->mode = inode->i_mode; + + if (S_ISLNK(inode->i_mode)) + return 0; + + if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) { + down_read(&OCFS2_I(dir)->ip_xattr_sem); + state->default_acl = + ocfs2_get_acl_nolock(dir, ACL_TYPE_DEFAULT, dir_bh); + up_read(&OCFS2_I(dir)->ip_xattr_sem); + if (IS_ERR(state->default_acl)) { + ret = PTR_ERR(state->default_acl); + state->default_acl = NULL; + return ret; } - if (!acl) { - mode = inode->i_mode & ~current_umask(); - ret = ocfs2_acl_set_mode(inode, di_bh, handle, mode); - if (ret) { - mlog_errno(ret); + if (state->default_acl) { + state->acl = posix_acl_dup(state->default_acl); + if (!state->acl) { + ret = -ENOMEM; goto cleanup; } - } - } - if ((osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) && acl) { - if (S_ISDIR(inode->i_mode)) { - ret = ocfs2_set_acl(handle, inode, di_bh, - ACL_TYPE_DEFAULT, acl, - meta_ac, data_ac); - if (ret) + ret = __posix_acl_create(&state->acl, GFP_NOFS, + &state->mode); + if (ret < 0) goto cleanup; + if (ret == 0) { + posix_acl_release(state->acl); + state->acl = NULL; + } + if (!S_ISDIR(inode->i_mode)) { + posix_acl_release(state->default_acl); + state->default_acl = NULL; + } + } else { + state->mode &= ~current_umask(); } - mode = inode->i_mode; - ret = __posix_acl_create(&acl, GFP_NOFS, &mode); - if (ret < 0) - return ret; - - ret2 = ocfs2_acl_set_mode(inode, di_bh, handle, mode); - if (ret2) { - mlog_errno(ret2); - ret = ret2; - goto cleanup; - } - if (ret > 0) { - ret = ocfs2_set_acl(handle, inode, - di_bh, ACL_TYPE_ACCESS, - acl, meta_ac, data_ac); - } + } else { + state->mode &= ~current_umask(); } + + return 0; cleanup: - posix_acl_release(acl); + ocfs2_acl_init_release(state); + return ret; +} + +int ocfs2_init_acl(handle_t *handle, struct inode *inode, + struct buffer_head *di_bh, + struct ocfs2_alloc_context *meta_ac, + struct ocfs2_alloc_context *data_ac, + struct ocfs2_acl_state *state) +{ + struct ocfs2_super *osb = OCFS2_SB(inode->i_sb); + int ret = 0; + + if (S_ISLNK(inode->i_mode)) + return 0; + + if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) { + if (S_ISDIR(inode->i_mode) && state->default_acl) { + ret = ocfs2_set_acl(handle, inode, di_bh, + ACL_TYPE_DEFAULT, + state->default_acl, meta_ac, + data_ac); + if (ret) + return ret; + } + } + + ret = ocfs2_acl_set_mode(inode, di_bh, handle, state->mode); + if (ret) { + mlog_errno(ret); + return ret; + } + + if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) { + if (state->acl) { + ret = ocfs2_set_acl(handle, inode, di_bh, + ACL_TYPE_ACCESS, state->acl, + meta_ac, data_ac); + } + } + return ret; } diff --git a/fs/ocfs2/acl.h b/fs/ocfs2/acl.h index 667c6f03fa60..a91f9ce278d6 100644 --- a/fs/ocfs2/acl.h +++ b/fs/ocfs2/acl.h @@ -20,9 +20,20 @@ struct posix_acl *ocfs2_iop_get_acl(struct inode *inode, int type, bool rcu); int ocfs2_iop_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); extern int ocfs2_acl_chmod(struct inode *, struct buffer_head *); -extern int ocfs2_init_acl(handle_t *, struct inode *, struct inode *, - struct buffer_head *, struct buffer_head *, - struct ocfs2_alloc_context *, - struct ocfs2_alloc_context *); +struct ocfs2_acl_state { + struct posix_acl *default_acl; + struct posix_acl *acl; + umode_t mode; +}; + +int ocfs2_acl_init_prepare(struct inode *inode, struct inode *dir, + struct buffer_head *dir_bh, + struct ocfs2_acl_state *state); +void ocfs2_acl_init_release(struct ocfs2_acl_state *state); +int ocfs2_init_acl(handle_t *handle, struct inode *inode, + struct buffer_head *di_bh, + struct ocfs2_alloc_context *meta_ac, + struct ocfs2_alloc_context *data_ac, + struct ocfs2_acl_state *state); #endif /* OCFS2_ACL_H */ diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 8368a7f3d4a2..4c9944ae70ab 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -256,6 +256,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, sigset_t oldset; int did_block_signals = 0; struct ocfs2_dentry_lock *dl = NULL; + struct ocfs2_acl_state acl_state = { 0 }; trace_ocfs2_mknod(dir, dentry, dentry->d_name.len, dentry->d_name.name, (unsigned long long)OCFS2_I(dir)->ip_blkno, @@ -330,10 +331,14 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, } } + status = ocfs2_acl_init_prepare(inode, dir, parent_fe_bh, &acl_state); + if (status < 0) + goto leave; + /* calculate meta data/clusters for setting security and acl xattr */ - status = ocfs2_calc_xattr_init(dir, parent_fe_bh, mode, - &si, &want_clusters, - &xattr_credits, &want_meta); + status = ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, + &xattr_credits, &want_meta, + &acl_state); if (status < 0) { mlog_errno(status); goto leave; @@ -411,8 +416,8 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, inc_nlink(dir); } - status = ocfs2_init_acl(handle, inode, dir, new_fe_bh, parent_fe_bh, - meta_ac, data_ac); + status = ocfs2_init_acl(handle, inode, new_fe_bh, meta_ac, data_ac, + &acl_state); if (status < 0) { mlog_errno(status); @@ -477,6 +482,8 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, brelse(parent_fe_bh); kfree(si.value); + ocfs2_acl_init_release(&acl_state); + ocfs2_free_dir_lookup_result(&lookup); if (inode_ac) diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 94ae1fb83cf1..35bcbb0ff607 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -635,13 +635,10 @@ int ocfs2_calc_security_init(struct inode *dir, return ret; } -int ocfs2_calc_xattr_init(struct inode *dir, - struct buffer_head *dir_bh, - umode_t mode, +int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, struct ocfs2_security_xattr_info *si, - int *want_clusters, - int *xattr_credits, - int *want_meta) + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state) { int ret = 0; struct ocfs2_super *osb = OCFS2_SB(dir->i_sb); @@ -652,19 +649,15 @@ int ocfs2_calc_xattr_init(struct inode *dir, si->value_len); if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL) { - down_read(&OCFS2_I(dir)->ip_xattr_sem); - acl_len = ocfs2_xattr_get_nolock(dir, dir_bh, - OCFS2_XATTR_INDEX_POSIX_ACL_DEFAULT, - "", NULL, 0); - up_read(&OCFS2_I(dir)->ip_xattr_sem); - if (acl_len > 0) { - a_size = ocfs2_xattr_entry_real_size(0, acl_len); - if (S_ISDIR(mode)) - a_size <<= 1; - } else if (acl_len != 0 && acl_len != -ENODATA) { - ret = acl_len; - mlog_errno(ret); - return ret; + if (acl_state->default_acl && S_ISDIR(mode)) { + acl_len = acl_state->default_acl->a_count * + sizeof(struct ocfs2_acl_entry); + a_size += ocfs2_xattr_entry_real_size(0, acl_len); + } + if (acl_state->acl) { + acl_len = acl_state->acl->a_count * + sizeof(struct ocfs2_acl_entry); + a_size += ocfs2_xattr_entry_real_size(0, acl_len); } } @@ -707,14 +700,33 @@ int ocfs2_calc_xattr_init(struct inode *dir, new_clusters); *want_clusters += new_clusters; } - if (osb->s_mount_opt & OCFS2_MOUNT_POSIX_ACL && - acl_len > OCFS2_XATTR_INLINE_SIZE) { - /* for directory, it has DEFAULT and ACCESS two types of acls */ - new_clusters = (S_ISDIR(mode) ? 2 : 1) * - ocfs2_clusters_for_bytes(dir->i_sb, acl_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)) { + acl_len = acl_state->default_acl->a_count * + sizeof(struct ocfs2_acl_entry); + if (acl_len > OCFS2_XATTR_INLINE_SIZE) { + new_clusters = + ocfs2_clusters_for_bytes(dir->i_sb, + acl_len); + *xattr_credits += + ocfs2_clusters_to_blocks(dir->i_sb, + new_clusters); + *want_clusters += new_clusters; + } + } + if (acl_state->acl) { + acl_len = acl_state->acl->a_count * + sizeof(struct ocfs2_acl_entry); + if (acl_len > OCFS2_XATTR_INLINE_SIZE) { + new_clusters = + ocfs2_clusters_for_bytes(dir->i_sb, + acl_len); + *xattr_credits += + ocfs2_clusters_to_blocks(dir->i_sb, + new_clusters); + *want_clusters += new_clusters; + } + } } return ret; @@ -3678,9 +3690,10 @@ static int __ocfs2_xattr_set_handle(struct inode *inode, } /* - * This function only called duing creating inode - * for init security/acl xattrs of the new inode. - * All transanction credits have been reserved in mknod. + * This helper is only for setting initial ACL or security xattrs on an inode + * that is still unpublished, unhashed, and unattached to a dentry. + * Ordinary xattr updates must use ocfs2_xattr_set(). + * All transaction credits have been reserved in mknod or symlink callers. */ int ocfs2_xattr_set_handle(handle_t *handle, struct inode *inode, @@ -3737,8 +3750,6 @@ int ocfs2_xattr_set_handle(handle_t *handle, xis.inode_bh = xbs.inode_bh = di_bh; di = (struct ocfs2_dinode *)di_bh->b_data; - down_write(&OCFS2_I(inode)->ip_xattr_sem); - ret = ocfs2_xattr_ibody_find(inode, name_index, name, &xis); if (ret) goto cleanup; @@ -3751,7 +3762,6 @@ 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_xattr_sem); brelse(xbs.xattr_bh); ocfs2_xattr_bucket_free(xbs.bucket); @@ -7451,6 +7461,7 @@ int ocfs2_init_security_and_acl(struct inode *dir, { int ret = 0; struct buffer_head *dir_bh = NULL; + struct ocfs2_acl_state acl_state = { 0 }; ret = ocfs2_init_security_get(inode, dir, qstr, NULL); if (ret) { @@ -7463,10 +7474,17 @@ int ocfs2_init_security_and_acl(struct inode *dir, mlog_errno(ret); goto leave; } - ret = ocfs2_init_acl(NULL, inode, dir, NULL, dir_bh, NULL, NULL); + + ret = ocfs2_acl_init_prepare(inode, dir, dir_bh, &acl_state); + if (ret) + goto unlock; + + ret = ocfs2_init_acl(NULL, inode, NULL, NULL, NULL, &acl_state); if (ret) mlog_errno(ret); +unlock: + ocfs2_acl_init_release(&acl_state); ocfs2_inode_unlock(dir, 0); brelse(dir_bh); leave: diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h index 6b7589941315..5e18513277f1 100644 --- a/fs/ocfs2/xattr.h +++ b/fs/ocfs2/xattr.h @@ -57,9 +57,12 @@ int ocfs2_init_security_set(handle_t *, struct inode *, int ocfs2_calc_security_init(struct inode *, struct ocfs2_security_xattr_info *, int *, int *, struct ocfs2_alloc_context **); -int ocfs2_calc_xattr_init(struct inode *, struct buffer_head *, - umode_t, struct ocfs2_security_xattr_info *, - int *, int *, int *); + +struct ocfs2_acl_state; +int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, + struct ocfs2_security_xattr_info *si, + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state); /* * xattrs can live inside an inode, as part of an external xattr block, From 621c2bcb87548ff6fe7ec1116f9b27ed87fbeb48 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 5 Aug 2026 19:39:20 +0800 Subject: [PATCH 84/95] ocfs2: fix cached cluster count after suballocator reclaim When reclaiming a suballocator block group, first reduce the on-disk cluster count by cl_cpg. The current code then subtracts that new count (fe->i_clusters) from the old cached count (OCFS2_I(alloc_inode)->ip_clusters). For an allocator with N block groups, that leaves the cache at N * cl_cpg - (N * cl_cpg - cl_cpg) = cl_cpg i.e. ip_clusters -= (fe->i_clusters - cl_cpg) leaves ip_clusters equal to cl_cpg regardless of N. This happens to be correct when reclaiming from two block groups, but undercounts the clusters from three block groups onwards. The incorrect cache value is also used immediately to update i_blocks. Assign the updated on-disk count to the cache, matching the allocation and inode refresh paths. In a QEMU test using a clean 256 MiB OCFS2 image and a 10,000-file create/delete workload, the first buggy reclaim left the on-disk (fe->i_clusters) and cached (ip_clusters) counts at 2048 and 512 clusters respectively; later reclaims underflowed the cache. With this change, the cache matched the on-disk count across all four reclaims: 2048, 1536, 1024, and 512 clusters. Link: https://lore.kernel.org/20260805113920.385959-1-matthias.goergens@gmail.com Fixes: 4a54331616b3 ("ocfs2: give ocfs2 the ability to reclaim suballocator free bg") Signed-off-by: Matthias Goergens Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/suballoc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ocfs2/suballoc.c b/fs/ocfs2/suballoc.c index a4a2b87a45fe..20c3aec6b987 100644 --- a/fs/ocfs2/suballoc.c +++ b/fs/ocfs2/suballoc.c @@ -2759,7 +2759,7 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle, fe->i_clusters = cpu_to_le32(tmp_used - le16_to_cpu(cl->cl_cpg)); spin_lock(&OCFS2_I(alloc_inode)->ip_lock); - OCFS2_I(alloc_inode)->ip_clusters -= le32_to_cpu(fe->i_clusters); + OCFS2_I(alloc_inode)->ip_clusters = le32_to_cpu(fe->i_clusters); fe->i_size = cpu_to_le64(ocfs2_clusters_to_bytes(alloc_inode->i_sb, le32_to_cpu(fe->i_clusters))); spin_unlock(&OCFS2_I(alloc_inode)->ip_lock); From a63308ab426f3a3c7e33b02c150ea59054620261 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Thu, 6 Aug 2026 10:20:44 +0800 Subject: [PATCH 85/95] ocfs2: fix readdir position truncation on 32-bit kernels In ocfs2_dir_foreach_blk_el(), the directory cookie position is rebuilt with ctx->pos = (ctx->pos & ~(sb->s_blocksize - 1)) | offset; `ctx->pos` is loff_t (signed 64-bit), while `sb->s_blocksize` is unsigned long. On 32-bit kernels unsigned long is 32-bit, so the mask ~(sb->s_blocksize - 1) is computed as a 32-bit unsigned value (e.g. 0xfffff000 for a 4 KiB block size). In the AND expression with the 64-bit `ctx->pos`, that unsigned operand is zero-extended to 64 bits per the usual arithmetic conversions, yielding 0x00000000fffff000. The high 32 bits of `ctx->pos` are silently cleared, even though directory size is allowed to exceed 4 GiB. When readdir() crosses the 4 GiB boundary on a 32-bit kernel the position is reset back into the first 4 GiB block, making the re-validation path re-enumerate already-returned dirents indefinitely. This is ocfs2_dir_foreach_blk_el(), the extent-list readdir path taken for all non-inline directories, so a directory large enough to cross 4 GiB reaches it. This is the same class of bug that commit 3dce5bb82c97 ("exfat: Fix bitwise operation having different size") fixed in exfat, and the fix mirrors the equivalent ext4 fix in this series. Cast the operand to loff_t so the mask is 64-bit before the AND: ctx->pos = (ctx->pos & ~((loff_t)sb->s_blocksize - 1)) | offset; 64-bit kernels are unaffected. Link: https://lore.kernel.org/20260806022044.167962-3-zhanxusheng@xiaomi.com Fixes: ccd979bdbce9 ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem") Signed-off-by: Zhan Xusheng Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Cc: Andreas Dilger Cc: Jan Kara Cc: Ojaswin Mujoo Cc: "Ritesh Harjani (IBM)" Cc: Ted Ts'o Cc: "zhangyi (F)" Cc: Signed-off-by: Andrew Morton --- fs/ocfs2/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 7e09a5a4d49e..5148a9d6e1c4 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -1959,7 +1959,7 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode, i += le16_to_cpu(de->rec_len); } offset = i; - ctx->pos = (ctx->pos & ~(sb->s_blocksize - 1)) + ctx->pos = (ctx->pos & ~((loff_t)sb->s_blocksize - 1)) | offset; *f_version = inode_query_iversion(inode); } From 9b836cd566815ab6302cfe56a7e432376e119652 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Thu, 6 Aug 2026 13:30:13 +0000 Subject: [PATCH 86/95] signal: factor out the kernel reserved si_code check The check that prevents userspace from sending siginfo with si_code values reserved to the kernel is duplicated across do_rt_sigqueueinfo(), do_rt_tgsigqueueinfo() and do_pidfd_send_signal(). Move the check into a helper so the rule lives in one place. Link: https://lore.kernel.org/20260806133013.4341-1-include@grrlz.net Signed-off-by: Bradley Morgan Reviewed-by: Andrew Morton Acked-by: Oleg Nesterov Cc: Christian Brauner Cc: Thomas Gleixner Signed-off-by: Andrew Morton --- kernel/signal.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/kernel/signal.c b/kernel/signal.c index fdee0b012a11..a5e15bf09d31 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -3944,6 +3944,15 @@ static void prepare_kill_siginfo(int sig, struct kernel_siginfo *info, info->si_uid = from_kuid_munged(current_user_ns(), current_uid()); } +/* + * Not even root can pretend to send SI_FROMKERNEL() signals. + * Nor can they impersonate kill()/tgkill(), which have si_pid/uid + */ +static bool si_code_reserved_to_kernel(int si_code) +{ + return si_code >= 0 || si_code == SI_TKILL; +} + /** * sys_kill - send a signal to a process * @pid: the PID of the process @@ -4035,7 +4044,7 @@ static int do_pidfd_send_signal(struct pid *pid, int sig, enum pid_type type, /* Only allow sending arbitrary signals to yourself. */ if ((task_pid(current) != pid || type > PIDTYPE_TGID) && - (kinfo.si_code >= 0 || kinfo.si_code == SI_TKILL)) + si_code_reserved_to_kernel(kinfo.si_code)) return -EPERM; } else { prepare_kill_siginfo(sig, &kinfo, type); @@ -4190,11 +4199,8 @@ SYSCALL_DEFINE2(tkill, pid_t, pid, int, sig) static int do_rt_sigqueueinfo(pid_t pid, int sig, kernel_siginfo_t *info) { - /* Not even root can pretend to send signals from the kernel. - * Nor can they impersonate a kill()/tgkill(), which adds source info. - */ - if ((info->si_code >= 0 || info->si_code == SI_TKILL) && - (task_pid_vnr(current) != pid)) + if (si_code_reserved_to_kernel(info->si_code) && + task_pid_vnr(current) != pid) return -EPERM; /* POSIX.1b doesn't mention process groups. */ @@ -4237,11 +4243,8 @@ static int do_rt_tgsigqueueinfo(pid_t tgid, pid_t pid, int sig, kernel_siginfo_t if (pid <= 0 || tgid <= 0) return -EINVAL; - /* Not even root can pretend to send signals from the kernel. - * Nor can they impersonate a kill()/tgkill(), which adds source info. - */ - if ((info->si_code >= 0 || info->si_code == SI_TKILL) && - (task_pid_vnr(current) != pid)) + if (si_code_reserved_to_kernel(info->si_code) && + task_pid_vnr(current) != pid) return -EPERM; return do_send_specific(tgid, pid, sig, info); From e300eb5002925b29be803d2661af07266cfa267e Mon Sep 17 00:00:00 2001 From: Phillip Lougher Date: Fri, 7 Aug 2026 17:29:51 +0100 Subject: [PATCH 87/95] Squashfs: check block offset is not negative If a negative offset is read off disk (for example the offset into the decompressed fragment block), this will cause squashfs_copy_data() to perform an out of bounds access. Fix by checking if offset is negative, and returning 0. This matches existing behaviour where an offset beyond the block returns 0 bytes copied. To trigger this out of bounds access requires a crafted Squashfs filesystem and CAP_SYS_ADMIN to mount it. Unprivileged users will not be able to mount such a filesystem, but once mounted, an unprivileged user can trigger the out of bounds access by reading the crafted file with the negative offset. Link: https://lore.kernel.org/20260807162951.672510-1-phillip@squashfs.org.uk Fixes: f400e12656ab ("Squashfs: cache operations") Signed-off-by: Phillip Lougher Reported-by: Yuejie Shi Closes: https://lore.kernel.org/all/20260803032735.81785-1-syjcnss@gmail.com/ Signed-off-by: Andrew Morton --- fs/squashfs/cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/squashfs/cache.c b/fs/squashfs/cache.c index 67abd4dff222..2807b80d46b7 100644 --- a/fs/squashfs/cache.c +++ b/fs/squashfs/cache.c @@ -299,7 +299,7 @@ int squashfs_copy_data(void *buffer, struct squashfs_cache_entry *entry, { int remaining = length; - if (length == 0) + if (length == 0 || offset < 0) return 0; else if (buffer == NULL) return min(length, entry->length - offset); From 5779e0f30fa4246748d4168a0f53298443f3744e Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 6 Aug 2026 19:00:47 -0600 Subject: [PATCH 88/95] checkpatch: add NOKPROBE_SYMBOL to the whitelist of lines that can occur immediately after functions It's customary for NOKPROBE_SYMBOL() macro usage to appear immediately after a function's final closing brace, but checkpatch doesn't know that yet. As a result, checkpatch --strict incorrectly flags this common kernel pattern, e.g., CHECK: Please use a blank line after function/struct/union/enum declarations 33: FILE: arch/riscv/kernel/traps.c:273: } +NOKPROBE_SYMBOL(probe_single_step_handler); Fix by adding NOKPROBE_SYMBOL to the whitelist of patterns that are cleared to appear immediately after functions. Link: https://lore.kernel.org/130be7db-6098-86a4-60fe-0c1a5d9e30ba@kernel.org Signed-off-by: Paul Walmsley Acked-by: Joe Perches Cc: Nam Cao Cc: Jisheng Zhang Signed-off-by: Andrew Morton --- scripts/checkpatch.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 7a846a3ea127..73b5e788e84e 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -4154,6 +4154,7 @@ sub process { $line =~ /^\+[a-z_]*init/ || $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || $line =~ /^\+\s*DECLARE/ || + $line =~ /^\+\s*NOKPROBE_SYMBOL/ || $line =~ /^\+\s*builtin_[\w_]*driver/ || $line =~ /^\+\s*__setup/)) { if (CHK("LINE_SPACING", From d9c0fd5533fb23bc445bd581feb140e966195936 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 8 Aug 2026 14:36:08 +0200 Subject: [PATCH 89/95] lib/interval_tree: fix allocation warning messages WARN_ON_ONCE() takes a condition, not a message. The string literals are always true, so the warnings still trigger but the messages are never printed. Use WARN_ONCE(1, ...) instead to print the messages and keep the once-only behavior. Found with a Coccinelle script. Clang's -Wstring-conversion also flags such calls but is not enabled in kernel builds. Link: https://lore.kernel.org/20260808123608.73613-1-kmehltretter@gmail.com Fixes: 82114e45131f ("lib/interval_tree: add test case for interval_tree_iter_xxx() helpers") Assisted-by: Claude:claude-fable-5 coccinelle Signed-off-by: Karl Mehltretter Reviewed-by: Andrew Morton Reviewed-by: Wei Yang Signed-off-by: Andrew Morton --- lib/interval_tree_test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/interval_tree_test.c b/lib/interval_tree_test.c index eba2d3e28980..b0b07270ce7c 100644 --- a/lib/interval_tree_test.c +++ b/lib/interval_tree_test.c @@ -140,13 +140,13 @@ static int intersection_range_check(void) intxn1 = bitmap_alloc(nnodes, GFP_KERNEL); if (!intxn1) { - WARN_ON_ONCE("Failed to allocate intxn1\n"); + WARN_ONCE(1, "Failed to allocate intxn1\n"); return -ENOMEM; } intxn2 = bitmap_alloc(nnodes, GFP_KERNEL); if (!intxn2) { - WARN_ON_ONCE("Failed to allocate intxn2\n"); + WARN_ONCE(1, "Failed to allocate intxn2\n"); bitmap_free(intxn1); return -ENOMEM; } From dfe88f832fe80f11077cb9d3de39e78f04a158a4 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Mon, 10 Aug 2026 15:32:35 +0800 Subject: [PATCH 90/95] mailmap: update email address for Linfeng Sun 1. Update my university email to my personal Gmail, as the former will expire after graduation. 2. Fix a typo in my Gmail domain. Link: https://lore.kernel.org/20260810073235.22980-1-linfeng.sun.dev@gmail.com Signed-off-by: Linfeng Sun Signed-off-by: Andrew Morton --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index bf0a246e0ab9..d35e65d609d3 100644 --- a/.mailmap +++ b/.mailmap @@ -516,6 +516,8 @@ Leo Yan Liam R. Howlett Liam Mark Linas Vepstas +Linfeng Sun +Linfeng Sun Linus Lüssing Linus Lüssing Linus Lüssing From 5146e0688d86f0654263e4b0e4ff1719b4072f16 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sun, 9 Aug 2026 00:29:01 +0000 Subject: [PATCH 91/95] prctl: fix PR_SET_MM_AUXV losing the forced AT_NULL terminator prctl_set_auxv() copies the user vector into a stack buffer, forces AT_NULL on the last two entries there, and then copies only len bytes into mm->saved_auxv. Which is fine until the vector is shorter than the buffer, because then the forced terminator sits past the end of the copy and never lands in saved_auxv at all. The code even says /* Make sure the last entry is always AT_NULL */ and it does, just not in the part that gets copied. So mm->saved_auxv keeps the stale tail from exec. Reproducing it is easy: from a process with CAP_SYS_RESOURCE (just run it as root), call prctl(PR_SET_MM, PR_SET_MM_AUXV, ...) with a vector that has a couple of entries and no AT_NULL inside len (32 bytes on arm64), and then hexdump /proc/self/auxv, or gcore the process and look at the AUXV note with readelf -n. This is arm64, the new vector was just { AT_UID, 0x1111, AT_GID, 0x2222 }: idx before (from exec) after the prctl [0] AT_SYSINFO_EHDR 0x7ed1d6e000 AT_UID 0x1111 <- new [1] AT_MINSIGSTKSZ 0x1270 AT_GID 0x2222 <- new [2] AT_HWCAP 0x119fff AT_HWCAP 0x119fff <- stale [3] AT_PAGESZ 0x1000 AT_PAGESZ 0x1000 <- stale ... 16 more entries ... <- stale [20] AT_NULL 0x0 AT_NULL 0x0 21 entries before the prctl, still 21 after: the two new ones plus all 19 left over from exec. Every consumer walks the vector until AT_NULL, so what they get now is a vector that never existed at exec, the head from the prctl glued onto the tail of the old binary. gdb and crash pull the AUXV note out of coredumps to find AT_PHDR, AT_ENTRY, AT_SYSINFO_EHDR and friends, and a mixed vector points them at the wrong layout. /proc//auxv and PR_GET_AUXV hand the same mess out to live processes too. Nothing crashes, everything just quietly reads a frankenstein auxv. And callers that terminate their own vector hide the whole thing, which is likely why nobody noticed since PR_SET_MM_AUXV landed in 2012. Nothing exciting security wise either, I mean it needs CAP_SYS_RESOURCE to begin with. prctl_set_mm_map() right above already copies the whole buffer for exactly this reason, so just do the same here. user_auxv is zero initialized and only partially filled from userspace, so the rest is zeros and nothing leaks. Link: https://lore.kernel.org/20260809002901.32591-1-include@grrlz.net Fixes: fe8c7f5cbf91 ("c/r: prctl: extend PR_SET_MM to set up more mm_struct entries") Signed-off-by: Bradley Morgan Cc: Alexey Dobriyan Cc: Cyrill Gorcuno Signed-off-by: Andrew Morton --- kernel/sys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sys.c b/kernel/sys.c index df69bd71de03..35b538ba843c 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -2189,7 +2189,7 @@ static int prctl_set_auxv(struct mm_struct *mm, unsigned long addr, BUILD_BUG_ON(sizeof(user_auxv) != sizeof(mm->saved_auxv)); task_lock(current); - memcpy(mm->saved_auxv, user_auxv, len); + memcpy(mm->saved_auxv, user_auxv, sizeof(user_auxv)); task_unlock(current); return 0; From f6f47a9ca82b3943c136494143f41366f83a2584 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Fri, 7 Aug 2026 10:24:21 -0700 Subject: [PATCH 92/95] squashfs: avoid thundering-herd cache wakeups squashfs_cache_get() puts a task to sleep when its block is not cached and every cache entry is busy. Those sleeps are non-exclusive, so the nr_exclusive == 1 budget squashfs_cache_put() has always passed to wake_up() is inert and one release makes every waiter runnable. A wakee only returns to squashfs_cache_get() if it observes cache->unused before the entry is reclaimed; later wakees see zero and re-queue inside wait_event() without rescanning. One freed entry satisfies exactly one capacity waiter, so waking the rest is waste. On a Meta production host serving a Python web application from a packaged squashfs image, a 30-second trace caught 1,045,132 cache-release wake calls and 19,511,556 wakeups: 18.7 per release, although each release added only one reusable cache entry. This was causing significant spikes in CPU usage. Make the waits exclusive, enqueueing while still holding cache->lock so that a concurrent lookup either sees the waiter queued or the waiter sees the block that lookup publishes. Two things follow. A wakee cannot be assumed to consume the entry it was woken for: it may find its own block published meanwhile, share that entry, and leave the freed one unclaimed. So a wakee which shares hands its wakeup on to the next waiter, as commit 0ddad21d3e99 ("pipe: use exclusive waits when reading or writing") does with wake_next_reader. And a waiter can now sleep through a publication of the very block it wants, which the old broadcast gave it repeated chances to notice. So waiters are keyed by block: publishing wakes every waiter for that block (nr_exclusive == 0), freeing an entry wakes one. That needs a custom wake callback, like wake_page_function() in mm/filemap.c, which also records which wakeup arrived so the handoff only fires for a capacity wakee. Broadcast is kept where more than one task can proceed - every waiter for a published block, and the wake_up_all() on entry->wait_queue - at the cost of walking the queue under wait_queue.lock to test the key. Waiters are now served FIFO with a scheduling round trip per handoff hop, so per-waiter latency changes; the filebench run below is 4x oversubscribed, where that should hurt most. Measured on a 32-CPU VM against a read-only squashfs (gzip, DECOMP_MULTI_PERCPU, FILE_DIRECT, default 8 metadata / 3 fragment cache entries) staged in tmpfs, page cache dropped each iteration to force cold decompression: elbencho, 64 threads metadata stat 700 -> 1320 files/s 1.9x small-file read 40 -> 60 MiB/s 1.5x filebench, 128 threads, open+read+stat+close (mean of 3x 30s) throughput 11,314 -> 25,186 ops/s 2.2x sched:sched_wakeup 27.0 -> 4.55 per op 5.9x fewer context switches 37.2 -> 7.64 per op 4.9x fewer Wakeups and context switches are per operation, since the two runs did 2.2x different amounts of work. Workloads which never queue for a cache entry gain no wakeups. Link: https://lore.kernel.org/20260807172421.3875982-1-usama.arif@linux.dev Signed-off-by: Usama Arif Reviewed-by: Phillip Lougher Cc: Boris Burkov Cc: Christian Brauner Cc: Jeff Layton Cc: Johannes Weiner Cc: Rik van Riel Cc: Shakeel Butt Signed-off-by: Andrew Morton --- fs/squashfs/cache.c | 114 +++++++++++++++++++++++++++++++++-- fs/squashfs/squashfs_fs_sb.h | 9 +++ 2 files changed, 117 insertions(+), 6 deletions(-) diff --git a/fs/squashfs/cache.c b/fs/squashfs/cache.c index 2807b80d46b7..c1a83bb0e519 100644 --- a/fs/squashfs/cache.c +++ b/fs/squashfs/cache.c @@ -45,19 +45,82 @@ #include "squashfs.h" #include "page_actor.h" +/* + * Waiters on cache->wait_queue are keyed by the block they want, so a wakeup + * can name who it is for. A NULL key is a capacity wakeup: one entry became + * free, so wake one waiter. A block key is a publication wakeup: that block + * now has an entry, so wake every waiter which can share it. + */ +struct squashfs_cache_wait { + wait_queue_entry_t wait; + u64 block; + bool capacity_wake; +}; + +static int squashfs_cache_wake_function(wait_queue_entry_t *wait, + unsigned int mode, int sync, void *key) +{ + struct squashfs_cache_wait *cache_wait = + container_of(wait, struct squashfs_cache_wait, wait); + u64 *block = key; + + if (block && cache_wait->block != *block) + return 0; + + WRITE_ONCE(cache_wait->capacity_wake, !block); + + /* + * Wake and unlink unconditionally instead of using + * autoremove_wake_function(), which unlinks only when it changed the + * task state. A waiter can be made runnable by something which does + * not go through this queue: wake_up_process() takes TASK_NORMAL, and + * a cgroup v2 thaw calls it on every task in the cgroup, as do + * free_pid() on a pid namespace init and a late rcuwait_wake_up(). + * try_to_wake_up() then fails. Leaving such a waiter queued with a + * reason already recorded would let it act on a freed entry it was not + * given, and the failure would not consume the exclusive budget, so a + * second waiter would be woken for the same entry. + * + * list_del_init_careful() must be the last access to @cache_wait: it + * releases the waiter, whose wait structure lives on its stack, and it + * pairs with list_empty_careful() in finish_wait() to publish the + * store above. __wake_up_common() samples ->flags and the next entry + * before calling here, so it does not touch @wait afterwards either. + */ + default_wake_function(wait, mode, sync, key); + list_del_init_careful(&wait->entry); + + return 1; +} + +static void squashfs_cache_wake_block(struct squashfs_cache *cache, u64 block) +{ + /* nr_exclusive == 0: wake every waiter which matches the key. */ + __wake_up(&cache->wait_queue, TASK_NORMAL, 0, &block); +} + /* * Look-up block in cache, and increment usage count. If not in cache, read * and decompress it from disk. + * + * A caller which finds no free entry sleeps on cache->wait_queue as an + * exclusive waiter, so squashfs_cache_put() releasing one entry wakes exactly + * one task. Because a wakee may find its block published in the meantime and + * share that entry rather than claim the free one, a wakee which shares hands + * its wakeup on to the next waiter. */ struct squashfs_cache_entry *squashfs_cache_get(struct super_block *sb, struct squashfs_cache *cache, u64 block, int length) { int i, n; struct squashfs_cache_entry *entry; + bool capacity_wake = false; spin_lock(&cache->lock); while (1) { + bool pending, wake_next, wake_block; + for (i = cache->curr_blk, n = 0; n < cache->entries; n++) { if (cache->entry[i].block == block) { cache->curr_blk = i; @@ -72,9 +135,25 @@ struct squashfs_cache_entry *squashfs_cache_get(struct super_block *sb, * go to sleep waiting for one to become available. */ if (cache->unused == 0) { + struct squashfs_cache_wait wait = { + .block = block, + .capacity_wake = false, + }; + + init_wait_func(&wait.wait, + squashfs_cache_wake_function); cache->num_waiters++; + /* + * Enqueue while still holding cache->lock, so + * that a concurrent lookup either sees us + * queued or we see the block it publishes. + */ + prepare_to_wait_exclusive(&cache->wait_queue, + &wait.wait, TASK_UNINTERRUPTIBLE); spin_unlock(&cache->lock); - wait_event(cache->wait_queue, cache->unused); + schedule(); + finish_wait(&cache->wait_queue, &wait.wait); + capacity_wake = READ_ONCE(wait.capacity_wake); spin_lock(&cache->lock); cache->num_waiters--; continue; @@ -105,8 +184,18 @@ struct squashfs_cache_entry *squashfs_cache_get(struct super_block *sb, entry->pending = 1; entry->num_waiters = 0; entry->error = 0; + wake_block = cache->num_waiters > 0; spin_unlock(&cache->lock); + /* + * The entry is now findable, so release everybody + * queued for this block to share it rather than each + * waiting for an entry of their own. They will block + * on entry->wait_queue below until the read completes. + */ + if (wake_block) + squashfs_cache_wake_block(cache, block); + entry->length = squashfs_read_data(sb, block, length, &entry->next_index, entry->actor); @@ -138,20 +227,33 @@ struct squashfs_cache_entry *squashfs_cache_get(struct super_block *sb, * for reuse. */ entry = &cache->entry[i]; - if (entry->refcount == 0) + if (entry->refcount == 0) { cache->unused--; + /* This claims the capacity we were woken for. */ + capacity_wake = false; + } entry->refcount++; /* * If the entry is currently being filled in by another process * go to sleep waiting for it to become available. */ - if (entry->pending) { + pending = entry->pending; + if (pending) entry->num_waiters++; - spin_unlock(&cache->lock); + + /* + * We were woken because an entry became free, but shared a + * block instead of claiming it. Hand the wakeup on, otherwise + * the free entry sits unclaimed while others sleep. + */ + wake_next = capacity_wake && cache->unused && cache->num_waiters; + spin_unlock(&cache->lock); + + if (wake_next) + wake_up(&cache->wait_queue); + if (pending) wait_event(entry->wait_queue, !entry->pending); - } else - spin_unlock(&cache->lock); goto out; } diff --git a/fs/squashfs/squashfs_fs_sb.h b/fs/squashfs/squashfs_fs_sb.h index c01998eec146..b87221ea9bdd 100644 --- a/fs/squashfs/squashfs_fs_sb.h +++ b/fs/squashfs/squashfs_fs_sb.h @@ -12,6 +12,15 @@ #include "squashfs_fs.h" +/* + * Waiters for a cache entry sleep on wait_queue as exclusive waiters, so + * freeing one entry wakes one task. See squashfs_cache_get(). + * + * num_waiters is only a hint used to skip pointless wakeups: it is + * incremented before a task queues itself and decremented after it is woken, + * so it can transiently exceed the number of queued tasks. It never + * undercounts them, which is what the wakeup paths rely on. + */ struct squashfs_cache { char *name; int entries; From bec0eed29b41a4e1b922d9ce40a748216496ed6e Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Tue, 11 Aug 2026 10:43:36 +0800 Subject: [PATCH 93/95] ocfs2: bound-check dir entries in the readdir re-validation scan Patch series "ocfs2: bound-check both readdir re-validation scans", v2. This patch (of 2): When the inode version changed since the last readdir(), ocfs2_dir_foreach_blk_el() re-scans the directory block from its start to relocate the current position: for (i = 0; i < sb->s_blocksize && i < offset; ) { de = (struct ocfs2_dir_entry *)(bh->b_data + i); if (le16_to_cpu(de->rec_len) < OCFS2_DIR_REC_LEN(1)) break; i += le16_to_cpu(de->rec_len); } i walks the block on rec_len values taken from the block itself and the only thing tested is that rec_len is not too small, so a single bogus rec_len leaves i anywhere in the block, including its last OCFS2_DIR_REC_LEN(1) - 1 bytes. @offset comes from ctx->pos, which userspace moves with lseek() on the directory fd, and decides how far the walk gets. Two bounds are missing, both of which ocfs2_check_dir_entry() applies for the emit loop below. de->rec_len sits at byte offset 8 within the entry, so dereferencing de in that tail reads past the s_blocksize buffer. ocfs2_check_dir_entry() declines to look at an entry that close to the end: size - buf_offset < OCFS2_DIR_REC_LEN(1) Nothing bounds i += rec_len either, so i can end up past the block. The emit loop that follows is guarded by offset < sb->s_blocksize and does not run, but offset = i; ctx->pos = (ctx->pos & ~((loff_t)sb->s_blocksize - 1)) | offset; runs first and ORs a value with bits above the block mask into ctx->pos, corrupting the block number readdir() resumes from. ocfs2_check_dir_entry() rejects that as "directory entry overrun": next_offset = buf_offset + rlen; ... next_offset > size Apply both bounds. For a consistent directory this changes nothing: entries are at least OCFS2_DIR_REC_LEN(1) bytes and do not cross the end of the block, so no valid entry is skipped. Found by the sashiko review tool; fix approach suggested by Joseph Qi. Link: https://lore.kernel.org/20260811024337.3972976-1-zhanxusheng@xiaomi.com Link: https://sashiko.dev/#/patchset/20260806022044.167962-1-zhanxusheng@xiaomi.com Link: https://lore.kernel.org/20260811024337.3972976-2-zhanxusheng@xiaomi.com Signed-off-by: Zhan Xusheng Suggested-by: Joseph Qi Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/dir.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 5148a9d6e1c4..1e4efaf64c4b 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -1945,7 +1945,10 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode, * dirent right now. Scan from the start of the block * to make sure. */ if (!inode_eq_iversion(inode, *f_version)) { - for (i = 0; i < sb->s_blocksize && i < offset; ) { + for (i = 0; i + OCFS2_DIR_REC_LEN(1) <= sb->s_blocksize && + i < offset;) { + unsigned int rec_len; + de = (struct ocfs2_dir_entry *) (bh->b_data + i); /* It's too expensive to do a full * dirent test each time round this @@ -1953,10 +1956,11 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode, * least that it is non-zero. A * failure will be detected in the * dirent test below. */ - if (le16_to_cpu(de->rec_len) < - OCFS2_DIR_REC_LEN(1)) + rec_len = le16_to_cpu(de->rec_len); + if (rec_len < OCFS2_DIR_REC_LEN(1) || + i + rec_len > sb->s_blocksize) break; - i += le16_to_cpu(de->rec_len); + i += rec_len; } offset = i; ctx->pos = (ctx->pos & ~((loff_t)sb->s_blocksize - 1)) From 763c097f71bc6106e1b1e28a96e9e4e5ced6228c Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Tue, 11 Aug 2026 10:43:37 +0800 Subject: [PATCH 94/95] ocfs2: bound-check dir entries in the inline-data re-validation scan ocfs2_dir_foreach_blk_id() re-scans the inline data area the same way ocfs2_dir_foreach_blk_el() re-scans a directory block, and is missing the same two bounds: for (i = 0; i < i_size_read(inode) && i < offset; ) { de = (struct ocfs2_dir_entry *)(data->id_data + i); if (le16_to_cpu(de->rec_len) < OCFS2_DIR_REC_LEN(1)) break; i += le16_to_cpu(de->rec_len); } ocfs2_validate_inode_block() keeps i_size inside the inline area: if (le16_to_cpu(data->id_count) > ocfs2_max_inline_data_with_xattr(sb, di)) if (le64_to_cpu(di->i_size) > le16_to_cpu(data->id_count)) and that area runs to the end of the inode block, so for a full inline directory data->id_data + i_size is the end of di_bh->b_data. A bogus rec_len leaves i in the last OCFS2_DIR_REC_LEN(1) - 1 bytes of it, and de->rec_len, at byte offset 8 within the entry, is then read past the block. The emit loop below hands i_size_read(inode) to ocfs2_check_dir_entry(), which refuses both an entry that close to the end and one whose rec_len runs past it. Apply the same two bounds to the re-validation scan, reading i_size once into a local as ocfs2_check_dir_entry() takes it as @size. Unlike the extent case there is no mask to corrupt here: an unbounded i only sets ctx->pos past i_size, which ends the readdir early rather than moving it to the wrong place. Link: https://lore.kernel.org/20260811024337.3972976-3-zhanxusheng@xiaomi.com Signed-off-by: Zhan Xusheng Reviewed-by: Joseph Qi Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao Signed-off-by: Andrew Morton --- fs/ocfs2/dir.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 1e4efaf64c4b..0075e1624310 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -1849,7 +1849,12 @@ static int ocfs2_dir_foreach_blk_id(struct inode *inode, * dirent right now. Scan from the start of the block * to make sure. */ if (!inode_eq_iversion(inode, *f_version)) { - for (i = 0; i < i_size_read(inode) && i < offset; ) { + loff_t size = i_size_read(inode); + + for (i = 0; i + OCFS2_DIR_REC_LEN(1) <= size && + i < offset;) { + unsigned int rec_len; + de = (struct ocfs2_dir_entry *) (data->id_data + i); /* It's too expensive to do a full @@ -1858,10 +1863,11 @@ static int ocfs2_dir_foreach_blk_id(struct inode *inode, * least that it is non-zero. A * failure will be detected in the * dirent test below. */ - if (le16_to_cpu(de->rec_len) < - OCFS2_DIR_REC_LEN(1)) + rec_len = le16_to_cpu(de->rec_len); + if (rec_len < OCFS2_DIR_REC_LEN(1) || + i + rec_len > size) break; - i += le16_to_cpu(de->rec_len); + i += rec_len; } ctx->pos = offset = i; *f_version = inode_query_iversion(inode); From eef628bce8e18ce1eb8531c396bf55e36f86c62c Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Mon, 17 Aug 2026 15:33:46 +0200 Subject: [PATCH 95/95] mailmap: fix bouncing address for Taniya Das The quic_username@quicinc.com emails have been deprecated inside Qualcomm for a while now in favor of firstname.lastname@oss.qualcomm.com Re-route the emails to Taniya's current OSS email address to avoid Outlook bounces ("recipient's inbox is full"). Link: https://lore.kernel.org/20260817-topic-taniya_email_bounce-v1-1-d7af1f113d64@oss.qualcomm.com Signed-off-by: Konrad Dybcio Cc: Taniya Das Signed-off-by: Andrew Morton --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index d35e65d609d3..6406aa065799 100644 --- a/.mailmap +++ b/.mailmap @@ -864,7 +864,8 @@ Szymon Wilczek Takashi YOSHII Tamir Duberstein Tamizh Chelvam Raja -Taniya Das +Taniya Das +Taniya Das Tanzir Hasan Tejun Heo Tomeu Vizoso