From: Greg Kroah-Hartman Date: Thu, 28 May 2026 09:49:03 +0000 (+0200) Subject: 6.18-stable patches X-Git-Tag: v5.10.258~39 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=53f199fa11e3b99dfc2dbbe4bc6e5a6f550669c4;p=thirdparty%2Fkernel%2Fstable-queue.git 6.18-stable patches added patches: cgroup-cpuset-reset-dl-migration-state-on-can_attach-failure.patch fs-ntfs3-handle-attr_set_size-errors-when-truncating-files.patch net-ethtool-fix-null-pointer-dereference-in-phy_reply_size.patch net-ethtool-phy-avoid-null-deref-when-phy-driver-is-unbound.patch sched_ext-avoid-uaf-in-scx_root_enable_workfn-init-failure-path.patch sched_ext-fix-missing-warning-in-scx_set_task_state-default-case.patch tracing-fprobe-avoid-kcalloc-in-rcu_read_lock-section.patch tracing-fprobe-check-the-same-type-fprobe-on-table-as-the-unregistered-one.patch tracing-fprobe-remove-unused-local-variable.patch tracing-fprobe-use-ftrace-if-config_dynamic_ftrace_with_args.patch --- diff --git a/queue-6.18/cgroup-cpuset-reset-dl-migration-state-on-can_attach-failure.patch b/queue-6.18/cgroup-cpuset-reset-dl-migration-state-on-can_attach-failure.patch new file mode 100644 index 0000000000..a3059ff3ac --- /dev/null +++ b/queue-6.18/cgroup-cpuset-reset-dl-migration-state-on-can_attach-failure.patch @@ -0,0 +1,70 @@ +From stable+bounces-253781-greg=kroah.com@vger.kernel.org Fri May 22 15:01:31 2026 +From: Sasha Levin +Date: Fri, 22 May 2026 08:54:20 -0400 +Subject: cgroup/cpuset: Reset DL migration state on can_attach() failure +To: stable@vger.kernel.org +Cc: Guopeng Zhang , Tejun Heo , Chen Ridong , Waiman Long , Sasha Levin +Message-ID: <20260522125420.3841043-1-sashal@kernel.org> + +From: Guopeng Zhang + +[ Upstream commit 4a39eda5fdd867fc39f3c039714dd432cee00268 ] + +cpuset_can_attach() accumulates temporary SCHED_DEADLINE migration +state in the destination cpuset while walking the taskset. + +If a later task_can_attach() or security_task_setscheduler() check +fails, cgroup_migrate_execute() treats cpuset as the failing subsystem +and does not call cpuset_cancel_attach() for it. The partially +accumulated state is then left behind and can be consumed by a later +attach, corrupting cpuset DL task accounting and pending DL bandwidth +accounting. + +Reset the pending DL migration state from the common error exit when +ret is non-zero. Successful can_attach() keeps the state for +cpuset_attach() or cpuset_cancel_attach(). + +Fixes: 2ef269ef1ac0 ("cgroup/cpuset: Free DL BW in case can_attach() fails") +Cc: stable@vger.kernel.org # v6.10+ +Signed-off-by: Guopeng Zhang +Signed-off-by: Tejun Heo +Reviewed-by: Chen Ridong +Reviewed-by: Waiman Long +[ omitted upstream context line `cs->dl_bw_cpu = cpu;` ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/cgroup/cpuset.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/kernel/cgroup/cpuset.c ++++ b/kernel/cgroup/cpuset.c +@@ -3163,16 +3163,13 @@ static int cpuset_can_attach(struct cgro + int cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus); + + if (unlikely(cpu >= nr_cpu_ids)) { +- reset_migrate_dl_data(cs); + ret = -EINVAL; + goto out_unlock; + } + + ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw); +- if (ret) { +- reset_migrate_dl_data(cs); ++ if (ret) + goto out_unlock; +- } + } + + out_success: +@@ -3181,7 +3178,10 @@ out_success: + * changes which zero cpus/mems_allowed. + */ + cs->attach_in_progress++; ++ + out_unlock: ++ if (ret) ++ reset_migrate_dl_data(cs); + mutex_unlock(&cpuset_mutex); + return ret; + } diff --git a/queue-6.18/fs-ntfs3-handle-attr_set_size-errors-when-truncating-files.patch b/queue-6.18/fs-ntfs3-handle-attr_set_size-errors-when-truncating-files.patch new file mode 100644 index 0000000000..89e9c21ef8 --- /dev/null +++ b/queue-6.18/fs-ntfs3-handle-attr_set_size-errors-when-truncating-files.patch @@ -0,0 +1,69 @@ +From stable+bounces-254710-greg=kroah.com@vger.kernel.org Thu May 28 05:21:19 2026 +From: Bin Lan +Date: Thu, 28 May 2026 11:20:28 +0800 +Subject: fs/ntfs3: handle attr_set_size() errors when truncating files +To: gregkh@linuxfoundation.org, sashal@kernel.org, stable@vger.kernel.org +Cc: linux-kernel@vger.kernel.org, Konstantin Komarov , Bin Lan +Message-ID: <20260528032028.58379-1-lanbincn@139.com> + +From: Konstantin Komarov + +[ Upstream commit 576248a34b927e93b2fd3fff7df735ba73ad7d01 ] + +If attr_set_size() fails while truncating down, the error is silently +ignored and the inode may be left in an inconsistent state. + +Signed-off-by: Konstantin Komarov +[ Minor context conflict resolved. ] +Signed-off-by: Bin Lan +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/file.c | 12 ++++-------- + 1 file changed, 4 insertions(+), 8 deletions(-) + +--- a/fs/ntfs3/file.c ++++ b/fs/ntfs3/file.c +@@ -458,8 +458,8 @@ static int ntfs_truncate(struct inode *i + { + struct super_block *sb = inode->i_sb; + struct ntfs_inode *ni = ntfs_i(inode); +- int err, dirty = 0; + u64 new_valid; ++ int err; + + if (!S_ISREG(inode->i_mode)) + return 0; +@@ -475,7 +475,6 @@ static int ntfs_truncate(struct inode *i + } + + new_valid = ntfs_up_block(sb, min_t(u64, ni->i_valid, new_size)); +- + truncate_setsize(inode, new_size); + + ni_lock(ni); +@@ -489,22 +488,19 @@ static int ntfs_truncate(struct inode *i + ni->i_valid = new_valid; + + ni_unlock(ni); ++ if (unlikely(err)) ++ return err; + + ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE; + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + if (!IS_DIRSYNC(inode)) { +- dirty = 1; ++ mark_inode_dirty(inode); + } else { + err = ntfs_sync_inode(inode); + if (err) + return err; + } + +- if (dirty) +- mark_inode_dirty(inode); +- +- /*ntfs_flush_inodes(inode->i_sb, inode, NULL);*/ +- + return 0; + } + diff --git a/queue-6.18/net-ethtool-fix-null-pointer-dereference-in-phy_reply_size.patch b/queue-6.18/net-ethtool-fix-null-pointer-dereference-in-phy_reply_size.patch new file mode 100644 index 0000000000..ed4a47f522 --- /dev/null +++ b/queue-6.18/net-ethtool-fix-null-pointer-dereference-in-phy_reply_size.patch @@ -0,0 +1,106 @@ +From stable+bounces-253784-greg=kroah.com@vger.kernel.org Fri May 22 15:31:39 2026 +From: Sasha Levin +Date: Fri, 22 May 2026 09:02:59 -0400 +Subject: net: ethtool: fix NULL pointer dereference in phy_reply_size +To: stable@vger.kernel.org +Cc: Quan Sun <2022090917019@std.uestc.edu.cn>, Maxime Chevallier , Jakub Kicinski , Sasha Levin +Message-ID: <20260522130300.3869084-1-sashal@kernel.org> + +From: Quan Sun <2022090917019@std.uestc.edu.cn> + +[ Upstream commit 4908f1395fb1b832ceec11584af649874a2732ea ] + +In phy_prepare_data(), several strings such as 'name', 'drvname', +'upstream_sfp_name', and 'downstream_sfp_name' are allocated using +kstrdup(). However, these allocations were not checked for failure. + +If kstrdup() fails for 'name', it returns NULL while the function +continues. This leads to a kernel NULL pointer dereference and panic +later in phy_reply_size() when it unconditionally calls strlen() on +the NULL pointer. + +While other strings like 'upstream_sfp_name' might be checked before +access in certain code paths, failing to handle these allocations +consistently can lead to incomplete data reporting or hidden bugs. + +Fix this by adding proper NULL checks for all kstrdup() calls in +phy_prepare_data() and implement a centralized error handling path +using goto labels to ensure all previously allocated resources are +freed on failure. + +Fixes: 9dd2ad5e92b9 ("net: ethtool: phy: Convert the PHY_GET command to generic phy dump") +Signed-off-by: Quan Sun <2022090917019@std.uestc.edu.cn> +Reviewed-by: Maxime Chevallier +Link: https://patch.msgid.link/20260507131738.1173835-1-2022090917019@std.uestc.edu.cn +Signed-off-by: Jakub Kicinski +Stable-dep-of: e3adf69f8eb1 ("net: ethtool: phy: avoid NULL deref when PHY driver is unbound") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/ethtool/phy.c | 32 ++++++++++++++++++++++++++++++-- + 1 file changed, 30 insertions(+), 2 deletions(-) + +--- a/net/ethtool/phy.c ++++ b/net/ethtool/phy.c +@@ -76,6 +76,7 @@ static int phy_prepare_data(const struct + struct nlattr **tb = info->attrs; + struct phy_device_node *pdn; + struct phy_device *phydev; ++ int ret; + + /* RTNL is held by the caller */ + phydev = ethnl_req_get_phydev(req_info, tb, ETHTOOL_A_PHY_HEADER, +@@ -88,8 +89,17 @@ static int phy_prepare_data(const struct + return -EOPNOTSUPP; + + rep_data->phyindex = phydev->phyindex; ++ + rep_data->name = kstrdup(dev_name(&phydev->mdio.dev), GFP_KERNEL); ++ if (!rep_data->name) ++ return -ENOMEM; ++ + rep_data->drvname = kstrdup(phydev->drv->name, GFP_KERNEL); ++ if (!rep_data->drvname) { ++ ret = -ENOMEM; ++ goto err_free_name; ++ } ++ + rep_data->upstream_type = pdn->upstream_type; + + if (pdn->upstream_type == PHY_UPSTREAM_PHY) { +@@ -97,15 +107,33 @@ static int phy_prepare_data(const struct + rep_data->upstream_index = upstream->phyindex; + } + +- if (pdn->parent_sfp_bus) ++ if (pdn->parent_sfp_bus) { + rep_data->upstream_sfp_name = kstrdup(sfp_get_name(pdn->parent_sfp_bus), + GFP_KERNEL); ++ if (!rep_data->upstream_sfp_name) { ++ ret = -ENOMEM; ++ goto err_free_drvname; ++ } ++ } + +- if (phydev->sfp_bus) ++ if (phydev->sfp_bus) { + rep_data->downstream_sfp_name = kstrdup(sfp_get_name(phydev->sfp_bus), + GFP_KERNEL); ++ if (!rep_data->downstream_sfp_name) { ++ ret = -ENOMEM; ++ goto err_free_upstream_sfp; ++ } ++ } + + return 0; ++ ++err_free_upstream_sfp: ++ kfree(rep_data->upstream_sfp_name); ++err_free_drvname: ++ kfree(rep_data->drvname); ++err_free_name: ++ kfree(rep_data->name); ++ return ret; + } + + static int phy_fill_reply(struct sk_buff *skb, diff --git a/queue-6.18/net-ethtool-phy-avoid-null-deref-when-phy-driver-is-unbound.patch b/queue-6.18/net-ethtool-phy-avoid-null-deref-when-phy-driver-is-unbound.patch new file mode 100644 index 0000000000..f06a02f450 --- /dev/null +++ b/queue-6.18/net-ethtool-phy-avoid-null-deref-when-phy-driver-is-unbound.patch @@ -0,0 +1,59 @@ +From stable+bounces-253785-greg=kroah.com@vger.kernel.org Fri May 22 15:16:54 2026 +From: Sasha Levin +Date: Fri, 22 May 2026 09:03:00 -0400 +Subject: net: ethtool: phy: avoid NULL deref when PHY driver is unbound +To: stable@vger.kernel.org +Cc: David Carlier , Maxime Chevallier , Jakub Kicinski , Sasha Levin +Message-ID: <20260522130300.3869084-2-sashal@kernel.org> + +From: David Carlier + +[ Upstream commit e3adf69f8eb121a9128c2b0029efd050d3649153 ] + +phydev->drv can become NULL while the phy_device is still attached to +its net_device, namely after the PHY driver is unbound via sysfs: + + echo > /sys/bus/mdio_bus/drivers//unbind + +phy_remove() clears phydev->drv but doesn't call phy_detach(), so the +phy_device stays in the link topology xarray and ethnl_req_get_phydev() +still hands it back. ETHTOOL_MSG_PHY_GET then oopses on: + + rep_data->drvname = kstrdup(phydev->drv->name, GFP_KERNEL); + +drvname is already treated as optional by phy_reply_size(), +phy_fill_reply() and phy_cleanup_data(), so just skip the allocation +when there is no driver bound. + +Fixes: 9dd2ad5e92b9 ("net: ethtool: phy: Convert the PHY_GET command to generic phy dump") +Cc: stable@vger.kernel.org # 6.13.x +Signed-off-by: David Carlier +Reviewed-by: Maxime Chevallier +Tested-by: Maxime Chevallier +Link: https://patch.msgid.link/20260509215046.107157-1-devnexen@gmail.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/ethtool/phy.c | 10 ++++++---- + 1 file changed, 6 insertions(+), 4 deletions(-) + +--- a/net/ethtool/phy.c ++++ b/net/ethtool/phy.c +@@ -94,10 +94,12 @@ static int phy_prepare_data(const struct + if (!rep_data->name) + return -ENOMEM; + +- rep_data->drvname = kstrdup(phydev->drv->name, GFP_KERNEL); +- if (!rep_data->drvname) { +- ret = -ENOMEM; +- goto err_free_name; ++ if (phydev->drv) { ++ rep_data->drvname = kstrdup(phydev->drv->name, GFP_KERNEL); ++ if (!rep_data->drvname) { ++ ret = -ENOMEM; ++ goto err_free_name; ++ } + } + + rep_data->upstream_type = pdn->upstream_type; diff --git a/queue-6.18/sched_ext-avoid-uaf-in-scx_root_enable_workfn-init-failure-path.patch b/queue-6.18/sched_ext-avoid-uaf-in-scx_root_enable_workfn-init-failure-path.patch new file mode 100644 index 0000000000..cb320469e6 --- /dev/null +++ b/queue-6.18/sched_ext-avoid-uaf-in-scx_root_enable_workfn-init-failure-path.patch @@ -0,0 +1,44 @@ +From stable+bounces-253570-greg=kroah.com@vger.kernel.org Thu May 21 16:31:34 2026 +From: Sasha Levin +Date: Thu, 21 May 2026 09:53:58 -0400 +Subject: sched_ext: Avoid UAF in scx_root_enable_workfn() init failure path +To: stable@vger.kernel.org +Cc: Tejun Heo , Sashiko , Sasha Levin +Message-ID: <20260521135358.1280483-2-sashal@kernel.org> + +From: Tejun Heo + +[ Upstream commit 9a415cc53711f2238e0f0ca8a6bcc796c003b127 ] + +In scx_root_enable_workfn(), put_task_struct(p) is called before scx_error() +dereferences p->comm and p->pid. If the iterator's reference is the last +drop, the task is freed synchronously and the deref becomes a UAF. + +Move put_task_struct() past scx_error(). + +Reported-by: Sashiko +Closes: https://lore.kernel.org/all/20260511214031.AF5E9C2BCB0@smtp.kernel.org/ +Fixes: f0e1a0643a59 ("sched_ext: Implement BPF extensible scheduler class") +Cc: stable@vger.kernel.org # v6.12+ +Signed-off-by: Tejun Heo +[ kept `scx_init_task()` call site instead of `__scx_init_task()`/`task_rq_lock` ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/sched/ext.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/kernel/sched/ext.c ++++ b/kernel/sched/ext.c +@@ -4812,10 +4812,10 @@ static void scx_enable_workfn(struct kth + + ret = scx_init_task(p, task_group(p), false); + if (ret) { +- put_task_struct(p); + scx_task_iter_stop(&sti); + scx_error(sch, "ops.init_task() failed (%d) for %s[%d]", + ret, p->comm, p->pid); ++ put_task_struct(p); + goto err_disable_unlock_all; + } + diff --git a/queue-6.18/sched_ext-fix-missing-warning-in-scx_set_task_state-default-case.patch b/queue-6.18/sched_ext-fix-missing-warning-in-scx_set_task_state-default-case.patch new file mode 100644 index 0000000000..ac0a24c210 --- /dev/null +++ b/queue-6.18/sched_ext-fix-missing-warning-in-scx_set_task_state-default-case.patch @@ -0,0 +1,57 @@ +From stable+bounces-253569-greg=kroah.com@vger.kernel.org Thu May 21 16:59:34 2026 +From: Sasha Levin +Date: Thu, 21 May 2026 09:53:57 -0400 +Subject: sched_ext: Fix missing warning in scx_set_task_state() default case +To: stable@vger.kernel.org +Cc: Samuele Mariotti , Paolo Valente , Andrea Righi , Tejun Heo , Sasha Levin +Message-ID: <20260521135358.1280483-1-sashal@kernel.org> + +From: Samuele Mariotti + +[ Upstream commit b905ee77d5f557a83a485b4146210f54f13365fc ] + +In scx_set_task_state(), the default case was setting the +warn flag, but then returning immediately. This is problematic +because the only purpose of the warn flag is to trigger +WARN_ONCE, but the early return prevented it from ever firing, +leaving invalid task states undetected and untraced. + +To fix this, a WARN_ONCE call is now added directly in the +default case. + +The fix addresses two aspects: + + - Guarantees the invalid task states are properly logged + and traced. + + - Provides a distinct warning message + ("sched_ext: Invalid task state") specifically for + states outside the defined scx_task_state enum values, + making it easier to distinguish from other transition + warnings. + +This ensures proper detection and reporting of invalid states. + +Signed-off-by: Samuele Mariotti +Signed-off-by: Paolo Valente +Reviewed-by: Andrea Righi +Signed-off-by: Tejun Heo +Stable-dep-of: 9a415cc53711 ("sched_ext: Avoid UAF in scx_root_enable_workfn() init failure path") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/sched/ext.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/kernel/sched/ext.c ++++ b/kernel/sched/ext.c +@@ -2800,7 +2800,8 @@ static void scx_set_task_state(struct ta + warn = prev_state != SCX_TASK_READY; + break; + default: +- warn = true; ++ WARN_ONCE(1, "sched_ext: Invalid task state %d -> %d for %s[%d]", ++ prev_state, state, p->comm, p->pid); + return; + } + diff --git a/queue-6.18/series b/queue-6.18/series index a1433e75d2..6b9747503a 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -66,3 +66,13 @@ netfilter-ip6t_hbh-reject-oversized-option-lists.patch netfilter-nf_queue-hold-bridge-skb-dev-while-queued.patch netfilter-ipset-stop-hash-range-iteration-at-end.patch netfilter-nft_inner-fix-ipv6-inner_thoff-desync.patch +sched_ext-fix-missing-warning-in-scx_set_task_state-default-case.patch +sched_ext-avoid-uaf-in-scx_root_enable_workfn-init-failure-path.patch +tracing-fprobe-remove-unused-local-variable.patch +tracing-fprobe-use-ftrace-if-config_dynamic_ftrace_with_args.patch +tracing-fprobe-avoid-kcalloc-in-rcu_read_lock-section.patch +tracing-fprobe-check-the-same-type-fprobe-on-table-as-the-unregistered-one.patch +cgroup-cpuset-reset-dl-migration-state-on-can_attach-failure.patch +net-ethtool-fix-null-pointer-dereference-in-phy_reply_size.patch +net-ethtool-phy-avoid-null-deref-when-phy-driver-is-unbound.patch +fs-ntfs3-handle-attr_set_size-errors-when-truncating-files.patch diff --git a/queue-6.18/tracing-fprobe-avoid-kcalloc-in-rcu_read_lock-section.patch b/queue-6.18/tracing-fprobe-avoid-kcalloc-in-rcu_read_lock-section.patch new file mode 100644 index 0000000000..ae5b5a37e5 --- /dev/null +++ b/queue-6.18/tracing-fprobe-avoid-kcalloc-in-rcu_read_lock-section.patch @@ -0,0 +1,188 @@ +From stable+bounces-253539-greg=kroah.com@vger.kernel.org Thu May 21 15:10:32 2026 +From: Sasha Levin +Date: Thu, 21 May 2026 08:58:12 -0400 +Subject: tracing/fprobe: Avoid kcalloc() in rcu_read_lock section +To: stable@vger.kernel.org +Cc: "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260521125813.1165339-3-sashal@kernel.org> + +From: "Masami Hiramatsu (Google)" + +[ Upstream commit aa72812b49104bb5a38272fc9541feb62ca6fd32 ] + +fprobe_remove_node_in_module() is called under RCU read locked, but +this invokes kcalloc() if there are more than 8 fprobes installed +on the module. Sashiko warns it because kcalloc() can sleep [1]. + + [1] https://sashiko.dev/#/patchset/177552432201.853249.5125045538812833325.stgit%40mhiramat.tok.corp.google.com + +To fix this issue, expand the batch size to 128 and do not expand +the fprobe_addr_list, but just cancel walking on fprobe_ip_table, +update fgraph/ftrace_ops and retry the loop again. + +Link: https://lore.kernel.org/all/177669367206.132053.1493637946869032744.stgit@mhiramat.tok.corp.google.com/ + +Fixes: 0de4c70d04a4 ("tracing: fprobe: use rhltable for fprobe_ip_table") +Cc: stable@vger.kernel.org +Signed-off-by: Masami Hiramatsu (Google) +Stable-dep-of: 0ac0058a74ac ("tracing/fprobe: Check the same type fprobe on table as the unregistered one") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/trace/fprobe.c | 92 ++++++++++++++++++++++++-------------------------- + 1 file changed, 45 insertions(+), 47 deletions(-) + +--- a/kernel/trace/fprobe.c ++++ b/kernel/trace/fprobe.c +@@ -344,11 +344,10 @@ static bool fprobe_is_ftrace(struct fpro + } + + #ifdef CONFIG_MODULES +-static void fprobe_set_ips(unsigned long *ips, unsigned int cnt, int remove, +- int reset) ++static void fprobe_remove_ips(unsigned long *ips, unsigned int cnt) + { +- ftrace_set_filter_ips(&fprobe_graph_ops.ops, ips, cnt, remove, reset); +- ftrace_set_filter_ips(&fprobe_ftrace_ops, ips, cnt, remove, reset); ++ ftrace_set_filter_ips(&fprobe_graph_ops.ops, ips, cnt, 1, 0); ++ ftrace_set_filter_ips(&fprobe_ftrace_ops, ips, cnt, 1, 0); + } + #endif + #else +@@ -367,10 +366,9 @@ static bool fprobe_is_ftrace(struct fpro + } + + #ifdef CONFIG_MODULES +-static void fprobe_set_ips(unsigned long *ips, unsigned int cnt, int remove, +- int reset) ++static void fprobe_remove_ips(unsigned long *ips, unsigned int cnt) + { +- ftrace_set_filter_ips(&fprobe_graph_ops.ops, ips, cnt, remove, reset); ++ ftrace_set_filter_ips(&fprobe_graph_ops.ops, ips, cnt, 1, 0); + } + #endif + #endif /* !CONFIG_DYNAMIC_FTRACE_WITH_ARGS && !CONFIG_DYNAMIC_FTRACE_WITH_REGS */ +@@ -543,7 +541,7 @@ static void fprobe_graph_remove_ips(unsi + + #ifdef CONFIG_MODULES + +-#define FPROBE_IPS_BATCH_INIT 8 ++#define FPROBE_IPS_BATCH_INIT 128 + /* instruction pointer address list */ + struct fprobe_addr_list { + int index; +@@ -551,45 +549,24 @@ struct fprobe_addr_list { + unsigned long *addrs; + }; + +-static int fprobe_addr_list_add(struct fprobe_addr_list *alist, unsigned long addr) ++static int fprobe_remove_node_in_module(struct module *mod, struct fprobe_hlist_node *node, ++ struct fprobe_addr_list *alist) + { +- unsigned long *addrs; +- +- /* Previously we failed to expand the list. */ +- if (alist->index == alist->size) +- return -ENOSPC; +- +- alist->addrs[alist->index++] = addr; +- if (alist->index < alist->size) ++ if (!within_module(node->addr, mod)) + return 0; + +- /* Expand the address list */ +- addrs = kcalloc(alist->size * 2, sizeof(*addrs), GFP_KERNEL); +- if (!addrs) +- return -ENOMEM; +- +- memcpy(addrs, alist->addrs, alist->size * sizeof(*addrs)); +- alist->size *= 2; +- kfree(alist->addrs); +- alist->addrs = addrs; ++ if (delete_fprobe_node(node)) ++ return 0; ++ /* If no address list is available, we can't track this address. */ ++ if (!alist->addrs) ++ return 0; + ++ alist->addrs[alist->index++] = node->addr; ++ if (alist->index == alist->size) ++ return -ENOSPC; + return 0; + } + +-static void fprobe_remove_node_in_module(struct module *mod, struct fprobe_hlist_node *node, +- struct fprobe_addr_list *alist) +-{ +- if (!within_module(node->addr, mod)) +- return; +- if (delete_fprobe_node(node)) +- return; +- /* +- * If failed to update alist, just continue to update hlist. +- * Therefore, at list user handler will not hit anymore. +- */ +- fprobe_addr_list_add(alist, node->addr); +-} +- + /* Handle module unloading to manage fprobe_ip_table. */ + static int fprobe_module_callback(struct notifier_block *nb, + unsigned long val, void *data) +@@ -598,29 +575,50 @@ static int fprobe_module_callback(struct + struct fprobe_hlist_node *node; + struct rhashtable_iter iter; + struct module *mod = data; ++ bool retry; + + if (val != MODULE_STATE_GOING) + return NOTIFY_DONE; + + alist.addrs = kcalloc(alist.size, sizeof(*alist.addrs), GFP_KERNEL); +- /* If failed to alloc memory, we can not remove ips from hash. */ +- if (!alist.addrs) +- return NOTIFY_DONE; ++ /* ++ * If failed to alloc memory, ftrace_ops will not be able to remove ips from ++ * hash, but we can still remove nodes from fprobe_ip_table, so we can avoid ++ * the potential wrong callback. So just print a warning here and try to ++ * continue without address list. ++ */ ++ WARN_ONCE(!alist.addrs, ++ "Failed to allocate memory for fprobe_addr_list, ftrace_ops will not be updated"); + + mutex_lock(&fprobe_mutex); ++again: ++ retry = false; ++ alist.index = 0; + rhltable_walk_enter(&fprobe_ip_table, &iter); + do { + rhashtable_walk_start(&iter); + + while ((node = rhashtable_walk_next(&iter)) && !IS_ERR(node)) +- fprobe_remove_node_in_module(mod, node, &alist); ++ if (fprobe_remove_node_in_module(mod, node, &alist) < 0) { ++ retry = true; ++ break; ++ } + + rhashtable_walk_stop(&iter); +- } while (node == ERR_PTR(-EAGAIN)); ++ } while (node == ERR_PTR(-EAGAIN) && !retry); + rhashtable_walk_exit(&iter); ++ /* Remove any ips from hash table(s) */ ++ if (alist.index > 0) { ++ fprobe_remove_ips(alist.addrs, alist.index); ++ /* ++ * If we break rhashtable walk loop except for -EAGAIN, we need ++ * to restart looping from start for safety. Anyway, this is ++ * not a hotpath. ++ */ ++ if (retry) ++ goto again; ++ } + +- if (alist.index > 0) +- fprobe_set_ips(alist.addrs, alist.index, 1, 0); + mutex_unlock(&fprobe_mutex); + + kfree(alist.addrs); diff --git a/queue-6.18/tracing-fprobe-check-the-same-type-fprobe-on-table-as-the-unregistered-one.patch b/queue-6.18/tracing-fprobe-check-the-same-type-fprobe-on-table-as-the-unregistered-one.patch new file mode 100644 index 0000000000..7a9e3ae652 --- /dev/null +++ b/queue-6.18/tracing-fprobe-check-the-same-type-fprobe-on-table-as-the-unregistered-one.patch @@ -0,0 +1,207 @@ +From stable+bounces-253540-greg=kroah.com@vger.kernel.org Thu May 21 15:10:33 2026 +From: Sasha Levin +Date: Thu, 21 May 2026 08:58:13 -0400 +Subject: tracing/fprobe: Check the same type fprobe on table as the unregistered one +To: stable@vger.kernel.org +Cc: "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260521125813.1165339-4-sashal@kernel.org> + +From: "Masami Hiramatsu (Google)" + +[ Upstream commit 0ac0058a74ac5765c7ce09ea630f4fdeaf4d80fa ] + +Commit 2c67dc457bc6 ("tracing: fprobe: optimization for entry only case") +introduced a different ftrace_ops for entry-only fprobes. + +However, when unregistering an fprobe, the kernel only checks if another +fprobe exists at the same address, without checking which type of fprobe +it is. +If different fprobes are registered at the same address, the same address +will be registered in both fgraph_ops and ftrace_ops, but only one of +them will be deleted when unregistering. (the one removed first will not +be deleted from the ops). + +This results in junk entries remaining in either fgraph_ops or ftrace_ops. +For example: + ======= + cd /sys/kernel/tracing + + # 'Add entry and exit events on the same place' + echo 'f:event1 vfs_read' >> dynamic_events + echo 'f:event2 vfs_read%return' >> dynamic_events + + # 'Enable both of them' + echo 1 > events/fprobes/enable + cat enabled_functions +vfs_read (2) ->arch_ftrace_ops_list_func+0x0/0x210 + + # 'Disable and remove exit event' + echo 0 > events/fprobes/event2/enable + echo -:event2 >> dynamic_events + + # 'Disable and remove all events' + echo 0 > events/fprobes/enable + echo > dynamic_events + + # 'Add another event' + echo 'f:event3 vfs_open%return' > dynamic_events + cat dynamic_events +f:fprobes/event3 vfs_open%return + + echo 1 > events/fprobes/enable + cat enabled_functions +vfs_open (1) tramp: 0xffffffffa0001000 (ftrace_graph_func+0x0/0x60) ->ftrace_graph_func+0x0/0x60 subops: {ent:fprobe_fgraph_entry+0x0/0x620 ret:fprobe_return+0x0/0x150} +vfs_read (1) tramp: 0xffffffffa0001000 (ftrace_graph_func+0x0/0x60) ->ftrace_graph_func+0x0/0x60 subops: {ent:fprobe_fgraph_entry+0x0/0x620 ret:fprobe_return+0x0/0x150} + ======= + +As you can see, an entry for the vfs_read remains. + +To fix this issue, when unregistering, the kernel should also check if +there is the same type of fprobes still exist at the same address, and +if not, delete its entry from either fgraph_ops or ftrace_ops. + +Link: https://lore.kernel.org/all/177669367993.132053.10553046138528674802.stgit@mhiramat.tok.corp.google.com/ + +Fixes: 2c67dc457bc6 ("tracing: fprobe: optimization for entry only case") +Cc: stable@vger.kernel.org +Signed-off-by: Masami Hiramatsu (Google) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/trace/fprobe.c | 82 +++++++++++++++++++++++++++++++++++++++----------- + 1 file changed, 65 insertions(+), 17 deletions(-) + +--- a/kernel/trace/fprobe.c ++++ b/kernel/trace/fprobe.c +@@ -92,11 +92,8 @@ static int insert_fprobe_node(struct fpr + return ret; + } + +-/* Return true if there are synonims */ +-static bool delete_fprobe_node(struct fprobe_hlist_node *node) ++static void delete_fprobe_node(struct fprobe_hlist_node *node) + { +- bool ret; +- + lockdep_assert_held(&fprobe_mutex); + + /* Avoid double deleting and non-inserted nodes */ +@@ -105,13 +102,6 @@ static bool delete_fprobe_node(struct fp + rhltable_remove(&fprobe_ip_table, &node->hlist, + fprobe_rht_params); + } +- +- rcu_read_lock(); +- ret = !!rhltable_lookup(&fprobe_ip_table, &node->addr, +- fprobe_rht_params); +- rcu_read_unlock(); +- +- return ret; + } + + /* Check existence of the fprobe */ +@@ -343,6 +333,32 @@ static bool fprobe_is_ftrace(struct fpro + return !fp->exit_handler; + } + ++static bool fprobe_exists_on_hash(unsigned long ip, bool ftrace) ++{ ++ struct rhlist_head *head, *pos; ++ struct fprobe_hlist_node *node; ++ struct fprobe *fp; ++ ++ guard(rcu)(); ++ head = rhltable_lookup(&fprobe_ip_table, &ip, ++ fprobe_rht_params); ++ if (!head) ++ return false; ++ /* We have to check the same type on the list. */ ++ rhl_for_each_entry_rcu(node, pos, head, hlist) { ++ if (node->addr != ip) ++ break; ++ fp = READ_ONCE(node->fp); ++ if (likely(fp)) { ++ if ((!ftrace && fp->exit_handler) || ++ (ftrace && !fp->exit_handler)) ++ return true; ++ } ++ } ++ ++ return false; ++} ++ + #ifdef CONFIG_MODULES + static void fprobe_remove_ips(unsigned long *ips, unsigned int cnt) + { +@@ -365,6 +381,29 @@ static bool fprobe_is_ftrace(struct fpro + return false; + } + ++static bool fprobe_exists_on_hash(unsigned long ip, bool ftrace __maybe_unused) ++{ ++ struct rhlist_head *head, *pos; ++ struct fprobe_hlist_node *node; ++ struct fprobe *fp; ++ ++ guard(rcu)(); ++ head = rhltable_lookup(&fprobe_ip_table, &ip, ++ fprobe_rht_params); ++ if (!head) ++ return false; ++ /* We only need to check fp is there. */ ++ rhl_for_each_entry_rcu(node, pos, head, hlist) { ++ if (node->addr != ip) ++ break; ++ fp = READ_ONCE(node->fp); ++ if (likely(fp)) ++ return true; ++ } ++ ++ return false; ++} ++ + #ifdef CONFIG_MODULES + static void fprobe_remove_ips(unsigned long *ips, unsigned int cnt) + { +@@ -552,18 +591,25 @@ struct fprobe_addr_list { + static int fprobe_remove_node_in_module(struct module *mod, struct fprobe_hlist_node *node, + struct fprobe_addr_list *alist) + { ++ lockdep_assert_in_rcu_read_lock(); ++ + if (!within_module(node->addr, mod)) + return 0; + +- if (delete_fprobe_node(node)) +- return 0; ++ delete_fprobe_node(node); + /* If no address list is available, we can't track this address. */ + if (!alist->addrs) + return 0; ++ /* ++ * Don't care the type here, because all fprobes on the same ++ * address must be removed eventually. ++ */ ++ if (!rhltable_lookup(&fprobe_ip_table, &node->addr, fprobe_rht_params)) { ++ alist->addrs[alist->index++] = node->addr; ++ if (alist->index == alist->size) ++ return -ENOSPC; ++ } + +- alist->addrs[alist->index++] = node->addr; +- if (alist->index == alist->size) +- return -ENOSPC; + return 0; + } + +@@ -934,7 +980,9 @@ static int unregister_fprobe_nolock(stru + /* Remove non-synonim ips from table and hash */ + count = 0; + for (i = 0; i < hlist_array->size; i++) { +- if (!delete_fprobe_node(&hlist_array->array[i]) && addrs) ++ delete_fprobe_node(&hlist_array->array[i]); ++ if (addrs && !fprobe_exists_on_hash(hlist_array->array[i].addr, ++ fprobe_is_ftrace(fp))) + addrs[count++] = hlist_array->array[i].addr; + } + del_fprobe_hash(fp); diff --git a/queue-6.18/tracing-fprobe-remove-unused-local-variable.patch b/queue-6.18/tracing-fprobe-remove-unused-local-variable.patch new file mode 100644 index 0000000000..cc320b8aaf --- /dev/null +++ b/queue-6.18/tracing-fprobe-remove-unused-local-variable.patch @@ -0,0 +1,50 @@ +From stable+bounces-253536-greg=kroah.com@vger.kernel.org Thu May 21 15:10:23 2026 +From: Sasha Levin +Date: Thu, 21 May 2026 08:58:10 -0400 +Subject: tracing: fprobe: Remove unused local variable +To: stable@vger.kernel.org +Cc: "Masami Hiramatsu (Google)" , Menglong Dong , Sasha Levin +Message-ID: <20260521125813.1165339-1-sashal@kernel.org> + +From: "Masami Hiramatsu (Google)" + +[ Upstream commit 90e69d291d195d35215b578d210fd3ce0e5a3f42 ] + +The 'ret' local variable in fprobe_remove_node_in_module() was used +for checking the error state in the loop, but commit dfe0d675df82 +("tracing: fprobe: use rhltable for fprobe_ip_table") removed the loop. +So we don't need it anymore. + +Link: https://lore.kernel.org/all/175867358989.600222.6175459620045800878.stgit@devnote2/ + +Fixes: e5a4cc28a052 ("tracing: fprobe: use rhltable for fprobe_ip_table") +Signed-off-by: Masami Hiramatsu (Google) +Acked-by: Menglong Dong +Stable-dep-of: 0ac0058a74ac ("tracing/fprobe: Check the same type fprobe on table as the unregistered one") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/trace/fprobe.c | 5 +---- + 1 file changed, 1 insertion(+), 4 deletions(-) + +--- a/kernel/trace/fprobe.c ++++ b/kernel/trace/fprobe.c +@@ -561,8 +561,6 @@ static int fprobe_addr_list_add(struct f + static void fprobe_remove_node_in_module(struct module *mod, struct fprobe_hlist_node *node, + struct fprobe_addr_list *alist) + { +- int ret = 0; +- + if (!within_module(node->addr, mod)) + return; + if (delete_fprobe_node(node)) +@@ -571,8 +569,7 @@ static void fprobe_remove_node_in_module + * If failed to update alist, just continue to update hlist. + * Therefore, at list user handler will not hit anymore. + */ +- if (!ret) +- ret = fprobe_addr_list_add(alist, node->addr); ++ fprobe_addr_list_add(alist, node->addr); + } + + /* Handle module unloading to manage fprobe_ip_table. */ diff --git a/queue-6.18/tracing-fprobe-use-ftrace-if-config_dynamic_ftrace_with_args.patch b/queue-6.18/tracing-fprobe-use-ftrace-if-config_dynamic_ftrace_with_args.patch new file mode 100644 index 0000000000..fe285914b3 --- /dev/null +++ b/queue-6.18/tracing-fprobe-use-ftrace-if-config_dynamic_ftrace_with_args.patch @@ -0,0 +1,110 @@ +From stable+bounces-253538-greg=kroah.com@vger.kernel.org Thu May 21 15:50:39 2026 +From: Sasha Levin +Date: Thu, 21 May 2026 08:58:11 -0400 +Subject: tracing: fprobe: use ftrace if CONFIG_DYNAMIC_FTRACE_WITH_ARGS +To: stable@vger.kernel.org +Cc: Menglong Dong , Menglong Dong , "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260521125813.1165339-2-sashal@kernel.org> + +From: Menglong Dong + +[ Upstream commit cd06078a38aaedfebbf8fa0c009da0f99f4473fb ] + +For now, we will use ftrace for the fprobe if fp->exit_handler not exists +and CONFIG_DYNAMIC_FTRACE_WITH_REGS is enabled. + +However, CONFIG_DYNAMIC_FTRACE_WITH_REGS is not supported by some arch, +such as arm. What we need in the fprobe is the function arguments, so we +can use ftrace for fprobe if CONFIG_DYNAMIC_FTRACE_WITH_ARGS is enabled. + +Therefore, use ftrace if CONFIG_DYNAMIC_FTRACE_WITH_REGS or +CONFIG_DYNAMIC_FTRACE_WITH_ARGS enabled. + +Link: https://lore.kernel.org/all/20251103063434.47388-1-dongml2@chinatelecom.cn/ + +Signed-off-by: Menglong Dong +Signed-off-by: Masami Hiramatsu (Google) +Stable-dep-of: 0ac0058a74ac ("tracing/fprobe: Check the same type fprobe on table as the unregistered one") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/trace/fprobe.c | 32 ++++++++++++++++++++++---------- + 1 file changed, 22 insertions(+), 10 deletions(-) + +--- a/kernel/trace/fprobe.c ++++ b/kernel/trace/fprobe.c +@@ -45,6 +45,7 @@ + static struct hlist_head fprobe_table[FPROBE_TABLE_SIZE]; + static struct rhltable fprobe_ip_table; + static DEFINE_MUTEX(fprobe_mutex); ++static struct fgraph_ops fprobe_graph_ops; + + static u32 fprobe_node_hashfn(const void *data, u32 len, u32 seed) + { +@@ -259,7 +260,7 @@ static inline int __fprobe_kprobe_handle + return ret; + } + +-#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS ++#if defined(CONFIG_DYNAMIC_FTRACE_WITH_ARGS) || defined(CONFIG_DYNAMIC_FTRACE_WITH_REGS) + /* ftrace_ops callback, this processes fprobes which have only entry_handler. */ + static void fprobe_ftrace_entry(unsigned long ip, unsigned long parent_ip, + struct ftrace_ops *ops, struct ftrace_regs *fregs) +@@ -300,7 +301,7 @@ NOKPROBE_SYMBOL(fprobe_ftrace_entry); + + static struct ftrace_ops fprobe_ftrace_ops = { + .func = fprobe_ftrace_entry, +- .flags = FTRACE_OPS_FL_SAVE_REGS, ++ .flags = FTRACE_OPS_FL_SAVE_ARGS, + }; + static int fprobe_ftrace_active; + +@@ -341,6 +342,15 @@ static bool fprobe_is_ftrace(struct fpro + { + return !fp->exit_handler; + } ++ ++#ifdef CONFIG_MODULES ++static void fprobe_set_ips(unsigned long *ips, unsigned int cnt, int remove, ++ int reset) ++{ ++ ftrace_set_filter_ips(&fprobe_graph_ops.ops, ips, cnt, remove, reset); ++ ftrace_set_filter_ips(&fprobe_ftrace_ops, ips, cnt, remove, reset); ++} ++#endif + #else + static int fprobe_ftrace_add_ips(unsigned long *addrs, int num) + { +@@ -355,7 +365,15 @@ static bool fprobe_is_ftrace(struct fpro + { + return false; + } ++ ++#ifdef CONFIG_MODULES ++static void fprobe_set_ips(unsigned long *ips, unsigned int cnt, int remove, ++ int reset) ++{ ++ ftrace_set_filter_ips(&fprobe_graph_ops.ops, ips, cnt, remove, reset); ++} + #endif ++#endif /* !CONFIG_DYNAMIC_FTRACE_WITH_ARGS && !CONFIG_DYNAMIC_FTRACE_WITH_REGS */ + + /* fgraph_ops callback, this processes fprobes which have exit_handler. */ + static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops *gops, +@@ -601,14 +619,8 @@ static int fprobe_module_callback(struct + } while (node == ERR_PTR(-EAGAIN)); + rhashtable_walk_exit(&iter); + +- if (alist.index > 0) { +- ftrace_set_filter_ips(&fprobe_graph_ops.ops, +- alist.addrs, alist.index, 1, 0); +-#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS +- ftrace_set_filter_ips(&fprobe_ftrace_ops, +- alist.addrs, alist.index, 1, 0); +-#endif +- } ++ if (alist.index > 0) ++ fprobe_set_ips(alist.addrs, alist.index, 1, 0); + mutex_unlock(&fprobe_mutex); + + kfree(alist.addrs);