From 608045a91d9176d66b2114d0006bc8b57dff2ca9 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Mon, 15 Jun 2026 16:32:25 +0200 Subject: [PATCH 001/456] rust: allow `suspicious_runtime_symbol_definitions` lint for Rust >= 1.98 Starting with Rust 1.98.0 (expected 2026-08-20), Rust is introducing a couple new lints, `invalid_runtime_symbol_definitions` (deny-by-default) and `suspicious_runtime_symbol_definitions` (warn-by-default), which check the signature of items whose symbol name is a runtime symbol expected by `core`. Our build hits the second one, i.e. the warning: error: suspicious definition of the runtime `strlen` symbol used by the standard library --> rust/bindings/bindings_generated.rs:20018:5 | 20018 | pub fn strlen(s: *const ffi::c_char) -> usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: expected `unsafe extern "C" fn(*const i8) -> usize` found `unsafe extern "C" fn(*const u8) -> usize` = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "strlen")]`, or `#[link_name = "strlen"]` = help: allow this lint if the signature is compatible = note: `-D suspicious-runtime-symbol-definitions` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(suspicious_runtime_symbol_definitions)]` error: suspicious definition of the runtime `strlen` symbol used by the standard library --> rust/uapi/uapi_generated.rs:14236:5 | 14236 | pub fn strlen(s: *const ffi::c_char) -> usize; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: expected `unsafe extern "C" fn(*const i8) -> usize` found `unsafe extern "C" fn(*const u8) -> usize` = help: either fix the signature or remove any attributes like `#[unsafe(no_mangle)]`, `#[unsafe(export_name = "strlen")]`, or `#[link_name = "strlen"]` = help: allow this lint if the signature is compatible = note: `-D suspicious-runtime-symbol-definitions` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(suspicious_runtime_symbol_definitions)]` Thus `allow` the lint in `bindings` and `uapi`. A more targeted alternative to avoid `allow`ing it would be to pass `--blocklist-function strlen` to `bindgen`, but we would perhaps need to adjust if other C headers end up adding more (or Rust checking more). Since it is just the less critical one that we hit, and since eventually this should be properly fixed by getting upstream Rust to provide a flag like GCC/Clang's `-funsigned-char` [2][3], just `allow` it for now. Cc: Urgau Cc: stable@vger.kernel.org # Needed in 6.12.y and later (Rust is pinned in older LTSs). Link: https://github.com/rust-lang/rust/pull/155521 [1] Link: https://github.com/rust-lang/rust/issues/138446 [2] Link: https://github.com/Rust-for-Linux/linux/issues/355 [3] Reviewed-by: Gary Guo Reviewed-by: Alice Ryhl Reviewed-by: Tamir Duberstein Link: https://patch.msgid.link/20260615143225.471756-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- init/Kconfig | 3 +++ rust/bindings/lib.rs | 4 ++++ rust/uapi/lib.rs | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/init/Kconfig b/init/Kconfig index 5230d4879b1c..10f2013b5321 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -190,6 +190,9 @@ config RUSTC_HAS_FILE_WITH_NUL config RUSTC_HAS_FILE_AS_C_STR def_bool RUSTC_VERSION >= 109100 +config RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS + def_bool RUSTC_VERSION >= 109800 + config PAHOLE_VERSION int default "$(PAHOLE_VERSION)" diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 854e7c471434..812f8e5a08d5 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -27,6 +27,10 @@ #[allow(clippy::ref_as_ptr)] #[allow(clippy::undocumented_unsafe_blocks)] #[cfg_attr(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES, allow(unnecessary_transmutes))] +#[cfg_attr( + CONFIG_RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS, + allow(suspicious_runtime_symbol_definitions) +)] mod bindings_raw { use pin_init::{MaybeZeroable, Zeroable}; diff --git a/rust/uapi/lib.rs b/rust/uapi/lib.rs index b8a515de31ca..797ead5b5626 100644 --- a/rust/uapi/lib.rs +++ b/rust/uapi/lib.rs @@ -24,6 +24,10 @@ unsafe_op_in_unsafe_fn )] #![cfg_attr(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES, allow(unnecessary_transmutes))] +#![cfg_attr( + CONFIG_RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS, + allow(suspicious_runtime_symbol_definitions) +)] #![feature(cfi_encoding)] // Manual definition of blocklisted types. From 859e02a369ab328a77dfcabf59562100e55f9c5c Mon Sep 17 00:00:00 2001 From: Sneh Mankad Date: Tue, 16 Jun 2026 17:24:53 +0530 Subject: [PATCH 002/456] pinctrl: qcom: Unconditionally mark gpio as wakeup enable GPIO interrupts that are wakeup capable need to be forwarded to wakeup capable parent irqchip. This is done via writing to it's wakeup_enable bit. Currently the bit is set only for PDC irqchip by checking skip_wake_irqs. skip_wake_irqs is set to differentiate between parent irqchips MPM and PDC. It is set when the parent irqchip is PDC to inform pinctrl about skipping the IRQ setting up at TLMM. However, the functionality to forward GPIO interrupts during SoC low power mode is needed regardless of which parent irqchip it is. Without the functionality it is impossible for MPM irqchip to detect the GPIO interrupt during SoC low power mode since for MPM irqchip the skip_wake_irqs is always false. Remove skip_wake_irqs condition when setting wakeup enable bit to allow forwarding GPIO interrupts for SoCs using MPM irqchip too. Fixes: 76b446f5b86e ("pinctrl: qcom: handle intr_target_reg wakeup_present/enable bits") Signed-off-by: Sneh Mankad Reviewed-by: Maulik Shah Reviewed-by: Linus Walleij Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260616-enable_wakeup_capable_gpios-v3-1-fb59647d89cb@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/pinctrl/qcom/pinctrl-msm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pinctrl/qcom/pinctrl-msm.c b/drivers/pinctrl/qcom/pinctrl-msm.c index 11db6564c44d..fd0add4e71da 100644 --- a/drivers/pinctrl/qcom/pinctrl-msm.c +++ b/drivers/pinctrl/qcom/pinctrl-msm.c @@ -1240,12 +1240,12 @@ static int msm_gpio_irq_reqres(struct irq_data *d) /* * If the wakeup_enable bit is present and marked as available for the * requested GPIO, it should be enabled when the GPIO is marked as - * wake irq in order to allow the interrupt event to be transfered to - * the PDC HW. + * wake irq in order to allow the interrupt event to be transferred to + * the PDC/MPM HW. * While the name implies only the wakeup event, it's also required for * the interrupt event. */ - if (test_bit(d->hwirq, pctrl->skip_wake_irqs) && g->intr_wakeup_present_bit) { + if (g->intr_wakeup_present_bit) { u32 intr_cfg; raw_spin_lock_irqsave(&pctrl->lock, flags); @@ -1273,7 +1273,7 @@ static void msm_gpio_irq_relres(struct irq_data *d) unsigned long flags; /* Disable the wakeup_enable bit if it has been set in msm_gpio_irq_reqres() */ - if (test_bit(d->hwirq, pctrl->skip_wake_irqs) && g->intr_wakeup_present_bit) { + if (g->intr_wakeup_present_bit) { u32 intr_cfg; raw_spin_lock_irqsave(&pctrl->lock, flags); From 437a8d2aa1aa442c4a176fdf4700a9b3bb0c8794 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Fri, 26 Jun 2026 15:08:05 +0200 Subject: [PATCH 003/456] pinctrl: qcom: sc8280xp: Add missing wakeup entries for GPIO143/151 Pins 143 and 151 were not included in the PDC wakeup map. They are normally used for PCIe2A and PCIe3a PERST# respectively, so they're unlikely to be excercised in practice, but still add them for the sake of completeness. Fixes: c0e4c71a9e7c ("pinctrl: qcom: Introduce sc8280xp TLMM driver") Signed-off-by: Konrad Dybcio Link: https://patch.msgid.link/20260626-topic-8280_pinctrl_wakeup-v1-1-2ccb267148f5@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/pinctrl/qcom/pinctrl-sc8280xp.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/pinctrl/qcom/pinctrl-sc8280xp.c b/drivers/pinctrl/qcom/pinctrl-sc8280xp.c index 4056b9fa32f8..e018bd11626c 100644 --- a/drivers/pinctrl/qcom/pinctrl-sc8280xp.c +++ b/drivers/pinctrl/qcom/pinctrl-sc8280xp.c @@ -1881,16 +1881,17 @@ static const struct msm_gpio_wakeirq_map sc8280xp_pdc_map[] = { { 126, 200 }, { 127, 225 }, { 128, 262 }, { 129, 201 }, { 130, 209 }, { 131, 173 }, { 132, 202 }, { 136, 210 }, { 138, 171 }, { 139, 226 }, { 140, 227 }, { 142, 228 }, - { 144, 229 }, { 145, 230 }, { 146, 231 }, { 148, 232 }, - { 149, 233 }, { 150, 234 }, { 152, 235 }, { 154, 212 }, - { 157, 213 }, { 161, 219 }, { 170, 236 }, { 171, 221 }, - { 174, 222 }, { 175, 237 }, { 176, 223 }, { 177, 170 }, - { 180, 238 }, { 181, 239 }, { 182, 240 }, { 183, 241 }, - { 184, 242 }, { 185, 243 }, { 190, 178 }, { 193, 184 }, - { 196, 185 }, { 198, 186 }, { 200, 174 }, { 201, 175 }, - { 205, 176 }, { 206, 177 }, { 208, 187 }, { 210, 198 }, - { 211, 199 }, { 212, 204 }, { 215, 205 }, { 220, 188 }, - { 221, 194 }, { 223, 195 }, { 225, 196 }, { 227, 197 }, + { 143, 261 }, { 144, 229 }, { 145, 230 }, { 146, 231 }, + { 148, 232 }, { 149, 233 }, { 150, 234 }, { 151, 264 }, + { 152, 235 }, { 154, 212 }, { 157, 213 }, { 161, 219 }, + { 170, 236 }, { 171, 221 }, { 174, 222 }, { 175, 237 }, + { 176, 223 }, { 177, 170 }, { 180, 238 }, { 181, 239 }, + { 182, 240 }, { 183, 241 }, { 184, 242 }, { 185, 243 }, + { 190, 178 }, { 193, 184 }, { 196, 185 }, { 198, 186 }, + { 200, 174 }, { 201, 175 }, { 205, 176 }, { 206, 177 }, + { 208, 187 }, { 210, 198 }, { 211, 199 }, { 212, 204 }, + { 215, 205 }, { 220, 188 }, { 221, 194 }, { 223, 195 }, + { 225, 196 }, { 227, 197 }, }; static struct msm_pinctrl_soc_data sc8280xp_pinctrl = { From 9119ceb76e987c2ec2b549ea100e3268ce3a1c7c Mon Sep 17 00:00:00 2001 From: Tze Yee Ng Date: Wed, 24 Jun 2026 03:06:35 -0700 Subject: [PATCH 004/456] firmware: stratix10-svc: fix memory leaks and list corruption bugs Fix a memory leak when gen_pool_alloc() fails by freeing pmem on the error path. Switch pmem allocation from devm_kzalloc() to kzalloc() with explicit kfree() in the free path to match its list-managed lifetime. Remove the erroneous list_del(&svc_data_mem) which corrupted the list head on failed lookups. Fixes: 7ca5ce896524 ("firmware: add Intel Stratix10 service layer driver") Cc: stable@vger.kernel.org # 5.0+ Signed-off-by: Tze Yee Ng Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-svc.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index c24ca5823078..6306b79a14a1 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -1857,14 +1857,16 @@ void *stratix10_svc_allocate_memory(struct stratix10_svc_chan *chan, struct gen_pool *genpool = chan->ctrl->genpool; size_t s = roundup(size, 1 << genpool->min_alloc_order); - pmem = devm_kzalloc(chan->ctrl->dev, sizeof(*pmem), GFP_KERNEL); + pmem = kzalloc_obj(*pmem); if (!pmem) return ERR_PTR(-ENOMEM); guard(mutex)(&svc_mem_lock); va = gen_pool_alloc(genpool, s); - if (!va) + if (!va) { + kfree(pmem); return ERR_PTR(-ENOMEM); + } memset((void *)va, 0, s); pa = gen_pool_virt_to_phys(genpool, va); @@ -1890,6 +1892,7 @@ EXPORT_SYMBOL_GPL(stratix10_svc_allocate_memory); void stratix10_svc_free_memory(struct stratix10_svc_chan *chan, void *kaddr) { struct stratix10_svc_data_mem *pmem; + guard(mutex)(&svc_mem_lock); list_for_each_entry(pmem, &svc_data_mem, node) @@ -1898,10 +1901,9 @@ void stratix10_svc_free_memory(struct stratix10_svc_chan *chan, void *kaddr) (unsigned long)kaddr, pmem->size); pmem->vaddr = NULL; list_del(&pmem->node); + kfree(pmem); return; } - - list_del(&svc_data_mem); } EXPORT_SYMBOL_GPL(stratix10_svc_free_memory); From 9b9a6e31bdd1ff20c3ffdab87431672d8bfc2a07 Mon Sep 17 00:00:00 2001 From: Genevieve Chan Date: Mon, 6 Jul 2026 13:55:41 +0800 Subject: [PATCH 005/456] firmware: stratix10-svc: fix FCS SMC call kernel-doc Correct kernel-doc errors for two FCS SMC calls: INTEL_SIP_SMC_FCS_SEND_CERTIFICATE: - Describe as async to match STD_CALL_VAL usage - Replace INTEL_SIP_SMC_FCS_REJECTED with INTEL_SIP_SMC_REJECTED INTEL_SIP_SMC_FCS_GET_PROVISION_DATA: - Replace FCS-specific status macros with standard status macros (INTEL_SIP_SMC_STATUS_ERROR and INTEL_SIP_SMC_STATUS_REJECTED) - Restore return register documentation for a1 (mailbox error), a2 (physical address), and a3 (structure size) Fixes: 4a4709d470e6 ("firmware: stratix10-svc: add new FCS commands") Fixes: 4b0a32016347 ("firmware: stratix10-svc: change get provision data to async SMC call") Cc: stable@vger.kernel.org # 6.0+ Signed-off-by: Genevieve Chan Signed-off-by: Dinh Nguyen --- include/linux/firmware/intel/stratix10-smc.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/include/linux/firmware/intel/stratix10-smc.h b/include/linux/firmware/intel/stratix10-smc.h index 9116512169dc..2c603a24967f 100644 --- a/include/linux/firmware/intel/stratix10-smc.h +++ b/include/linux/firmware/intel/stratix10-smc.h @@ -606,7 +606,7 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE) /** * Request INTEL_SIP_SMC_FUNCID_FCS_SEND_CERTIFICATE - * Sync call to send a signed certificate + * Async call to send a signed certificate * * Call register usage: * a0 INTEL_SIP_SMC_FCS_SEND_CERTIFICATE @@ -615,7 +615,7 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE) * a3-a7 not used * * Return status: - * a0 INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_FCS_REJECTED + * a0 INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_REJECTED * a1-a3 not used */ #define INTEL_SIP_SMC_FUNCID_FCS_SEND_CERTIFICATE 93 @@ -631,9 +631,11 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE) * a1-a7 not used * * Return status: - * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_FCS_ERROR or - * INTEL_SIP_SMC_FCS_REJECTED - * a1-a3 not used + * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_STATUS_ERROR or + * INTEL_SIP_SMC_STATUS_REJECTED + * a1 mailbox error if a0 is INTEL_SIP_SMC_STATUS_ERROR + * a2 physical address for the structure of fuse and key hashes + * a3 the size of structure * */ #define INTEL_SIP_SMC_FUNCID_FCS_GET_PROVISION_DATA 94 From af9ea231c0b4530edc389a3126a69e0699b7699d Mon Sep 17 00:00:00 2001 From: Shengming Hu Date: Mon, 6 Jul 2026 21:39:09 +0800 Subject: [PATCH 006/456] mm/slub: fix lost local objects when bulk remote free batch fills In free_to_pcs_bulk(), when remote_objects[] fills to PCS_BATCH_MAX, the code jumps to flush_remote to free the batch. If all remote entries have already been compacted out of p[] via tail swaps while local objects remain, the flush_remote path returns early since `i < size` no longer holds. The leftover local objects are then neither cached in the sheaf nor returned to the slab freelist, causing a memory leak. For illustration: size = 64, local objects at p[0..31], remote objects at p[32..63] After scanning all remotes: i = 32, size = 32 p[0..31] local objects are dropped. Harry pointed out that, although the logic contains a real leak, it does not appear to be triggerable with the current in-tree users. To hit this path, at least PCS_BATCH_MAX objects, currently hardcoded to 32, need to be collected in remote_objects[]. Looking at current kmem_cache_free_bulk() users: * maple_node has sheaf_capacity = 32 * skbuff_head_cache has sheaf_capacity = 28 * panthor and msm drivers have sheaf_capacity = 4 The sheaf capacity is, at least for now, derived purely from the object size, with the user-requested capacity used as a minimum. Therefore, among the current users, only maple_node has a sheaf_capacity large enough to reach PCS_BATCH_MAX. However, for the bug to trigger in maple_node, all objects in the sheaf would have to be from remote nodes. In that case, there would be no local objects left to leak. So this issue was found by code review rather than from a runtime report, and it does not seem to be triggerable by current users. Still, the bug could become reachable with future users, a different sheaf capacity, or a change to PCS_BATCH_MAX. Fix the logic by freeing a full remote batch in place during the scan and then continuing to process the compacted array. This keeps all local objects on the normal fast path, while the tail path only handles any leftover partial remote batch. The redundant next_remote_batch jump label is removed as well. Fixes: 989b09b73978 ("slab: skip percpu sheaves for remote object freeing") Signed-off-by: Shengming Hu Link: https://patch.msgid.link/202607062139095043SOsLi6TIf403tcjPf8fm@zte.com.cn Cc: stable@vger.kernel.org Reviewed-by: Harry Yoo (Oracle) Signed-off-by: Vlastimil Babka (SUSE) --- mm/slub.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 9ec774dc7009..65febe957886 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -6217,7 +6217,6 @@ static void free_to_pcs_bulk(struct kmem_cache *s, size_t size, void **p) void *remote_objects[PCS_BATCH_MAX]; unsigned int remote_nr = 0; -next_remote_batch: while (i < size) { struct slab *slab = virt_to_slab(p[i]); @@ -6232,8 +6231,11 @@ static void free_to_pcs_bulk(struct kmem_cache *s, size_t size, void **p) if (unlikely(!can_free_to_pcs(slab))) { remote_objects[remote_nr] = p[i]; p[i] = p[--size]; - if (++remote_nr >= PCS_BATCH_MAX) - goto flush_remote; + if (++remote_nr >= PCS_BATCH_MAX) { + __kmem_cache_free_bulk(s, remote_nr, &remote_objects[0]); + stat_add(s, FREE_SLOWPATH, remote_nr); + remote_nr = 0; + } continue; } @@ -6317,10 +6319,6 @@ static void free_to_pcs_bulk(struct kmem_cache *s, size_t size, void **p) if (remote_nr) { __kmem_cache_free_bulk(s, remote_nr, &remote_objects[0]); stat_add(s, FREE_SLOWPATH, remote_nr); - if (i < size) { - remote_nr = 0; - goto next_remote_batch; - } } } From 4688cf884b3abcd12498e03b625d1916bf49a1e4 Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Wed, 8 Jul 2026 19:49:26 +0900 Subject: [PATCH 007/456] rust: allow `clippy::unwrap_or_default` globally Starting with rustc 1.88, the `clippy::unwrap_or_default` lint triggers on `rust/kernel/soc.rs` if `CONFIG_CC_OPTIMIZE_FOR_SIZE=y`: warning: use of `unwrap_or` to construct default value --> ../rust/kernel/soc.rs:66:10 | 66 | .unwrap_or(core::ptr::null()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` This is a clippy bug [1]: the lint decides whether an expression is equivalent to `Default::default()` by inspecting the optimized MIR of `<*const T as Default>::default` exported by `core`, so its outcome depends on the optimization level `core` was built with. Moreover, its suggestion ignores our MSRV of 1.85 (`Default` for `*const T` is only stable since Rust 1.88), so we could not apply it anyway. Disable the lint globally rather than working around this single occurrence; it can be re-enabled conditionally using `rustc-min-version` once clippy is fixed. Link: https://github.com/rust-lang/rust-clippy/issues/17379 [1] Suggested-by: Miguel Ojeda Signed-off-by: Alexandre Courbot Cc: stable@vger.kernel.org # Needed in 6.12.y and later (Rust is pinned in older LTSs). Link: https://patch.msgid.link/20260708-soc_unwrap_or-v2-1-007ed724cc7b@nvidia.com [ Moved to non-versioned group. - Miguel ] Signed-off-by: Miguel Ojeda --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index b9c5792c79e0..e54cc46340fd 100644 --- a/Makefile +++ b/Makefile @@ -475,6 +475,10 @@ KBUILD_USERLDFLAGS := $(USERLDFLAGS) # These flags apply to all Rust code in the tree, including the kernel and # host programs. +# +# `-Aclippy::unwrap_or_default`: the lint is buggy [1] and ignores our +# MSRV. It can trigger depending on the optimization level. +# [1] https://github.com/rust-lang/rust-clippy/issues/17379 export rust_common_flags := --edition=2021 \ -Zbinary_dep_depinfo=y \ -Astable_features \ @@ -503,6 +507,7 @@ export rust_common_flags := --edition=2021 \ -Aclippy::uninlined_format_args \ -Wclippy::unnecessary_safety_comment \ -Wclippy::unnecessary_safety_doc \ + -Aclippy::unwrap_or_default \ -Wrustdoc::missing_crate_level_docs \ -Wrustdoc::unescaped_backticks From 2808a3963988f00081594f1c4a2758733839eaac Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Fri, 26 Jun 2026 01:19:19 +0200 Subject: [PATCH 008/456] rust: zerocopy: update to v0.8.52 Update our vendored copy of `zerocopy` (and `zerocopy-derive`) to v0.8.52. Most SPDX identifiers have been added upstream at our request [1] (without parentheses -- supporting them is an issue on the kernel side, but it does already reduce our differences). The CSS one for `rustdoc` was added too [2], but will be picked up in a later version. For `zerocopy`, enable `--cfg no_fp_fmt_parse`, which was added at our request to avoid our local workaround [3]. This means one less difference, thus indicate so in our `README.md`. For `zerocopy-derive`, enable `--cfg zerocopy_unstable_linux`. This allows us to use `#[derive(zerocopy_derive::most_traits)]`, a new feature upstream added for us [4]. We noticed a minor doc render bug [5], which will be fixed for a future version too. The following script may be used to check for the remaining differences: for path in $(cd rust/zerocopy-derive/ && find . -type f ! -name README.md); do curl --silent --show-error --location \ https://github.com/google/zerocopy/raw/v0.8.52/zerocopy/zerocopy-derive/src/$path | git diff --no-index - rust/zerocopy-derive/$path && echo $path: OK done for path in $(cd rust/zerocopy/ && find . -type f ! -name README.md); do curl --silent --show-error --location \ https://github.com/google/zerocopy/raw/v0.8.52/zerocopy/$path | git diff --no-index - rust/zerocopy/$path && echo $path: OK done Cc: Joshua Liebow-Feeser Cc: Jack Wrenn Link: https://github.com/google/zerocopy/issues/3428 [1] Link: https://github.com/google/zerocopy/issues/3457 [2] Link: https://github.com/google/zerocopy/issues/3426 [3] Link: https://github.com/google/zerocopy/pull/3416 [4] Link: https://github.com/google/zerocopy/issues/3466 [5] Acked-by: Nicolas Schier Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260625231919.692444-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- rust/Makefile | 16 ++++-- rust/zerocopy-derive/README.md | 14 +++--- rust/zerocopy-derive/derive/from_bytes.rs | 2 +- rust/zerocopy-derive/derive/into_bytes.rs | 2 +- rust/zerocopy-derive/derive/known_layout.rs | 3 +- rust/zerocopy-derive/derive/mod.rs | 20 +++++--- rust/zerocopy-derive/derive/try_from_bytes.rs | 6 +-- rust/zerocopy-derive/derive/unaligned.rs | 2 +- rust/zerocopy-derive/lib.rs | 36 +++++++++++++- rust/zerocopy-derive/repr.rs | 2 +- rust/zerocopy-derive/util.rs | 34 ++++++++++--- rust/zerocopy/README.md | 15 +++--- rust/zerocopy/src/byte_slice.rs | 2 +- rust/zerocopy/src/byteorder.rs | 49 +++++++++++++++---- rust/zerocopy/src/deprecated.rs | 2 +- rust/zerocopy/src/error.rs | 2 +- rust/zerocopy/src/impls.rs | 4 +- rust/zerocopy/src/layout.rs | 2 +- rust/zerocopy/src/lib.rs | 13 +++-- rust/zerocopy/src/macros.rs | 2 +- rust/zerocopy/src/pointer/inner.rs | 2 +- rust/zerocopy/src/pointer/invariant.rs | 2 +- rust/zerocopy/src/pointer/mod.rs | 2 +- rust/zerocopy/src/pointer/ptr.rs | 2 +- rust/zerocopy/src/pointer/transmute.rs | 2 +- rust/zerocopy/src/ref.rs | 2 +- rust/zerocopy/src/split_at.rs | 2 +- rust/zerocopy/src/util/macro_util.rs | 2 +- rust/zerocopy/src/util/macros.rs | 2 +- rust/zerocopy/src/util/mod.rs | 2 +- rust/zerocopy/src/wrappers.rs | 2 +- 31 files changed, 174 insertions(+), 76 deletions(-) diff --git a/rust/Makefile b/rust/Makefile index a870d1616c71..835cf10e1c0b 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -84,11 +84,15 @@ core-flags := \ --edition=$(core-edition) \ $(call cfgs-to-flags,$(core-cfgs)) +zerocopy-cfgs := \ + no_fp_fmt_parse + zerocopy-flags := \ - --cap-lints=allow + --cap-lints=allow \ + $(call cfgs-to-flags,$(zerocopy-cfgs)) zerocopy-envs := \ - CARGO_PKG_VERSION=0.8.50 + CARGO_PKG_VERSION=0.8.52 proc_macro2-cfgs := \ feature="proc-macro" \ @@ -128,11 +132,15 @@ syn-flags := \ --extern quote \ $(call cfgs-to-flags,$(syn-cfgs)) +zerocopy_derive-cfgs := \ + zerocopy_unstable_linux + zerocopy_derive-flags := \ --cap-lints=allow \ --extern proc_macro2 \ --extern quote \ - --extern syn + --extern syn \ + $(call cfgs-to-flags,$(zerocopy_derive-cfgs)) pin_init_internal-cfgs := \ kernel USE_RUSTC_FEATURES @@ -644,9 +652,11 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L rust-analyzer: $(Q)MAKEFLAGS= $(srctree)/scripts/generate_rust_analyzer.py \ --cfgs='core=$(core-cfgs)' $(core-edition) \ + --cfgs='zerocopy=$(zerocopy-cfgs)' \ --cfgs='proc_macro2=$(proc_macro2-cfgs)' \ --cfgs='quote=$(quote-cfgs)' \ --cfgs='syn=$(syn-cfgs)' \ + --cfgs='zerocopy_derive=$(zerocopy_derive-cfgs)' \ --cfgs='pin_init_internal=$(pin_init_internal-cfgs)' \ --cfgs='pin_init=$(pin_init-cfgs)' \ --envs='zerocopy=$(zerocopy-envs)' \ diff --git a/rust/zerocopy-derive/README.md b/rust/zerocopy-derive/README.md index 110f4a401778..2a3dcf1212af 100644 --- a/rust/zerocopy-derive/README.md +++ b/rust/zerocopy-derive/README.md @@ -1,14 +1,14 @@ # `zerocopy-derive` -These source files come from the Rust `zerocopy-derive` crate, version v0.8.50 -(released 2026-05-31), hosted in the +These source files come from the Rust `zerocopy-derive` crate, version v0.8.52 +(released 2026-06-09), hosted in the repository, licensed under "BSD-2-Clause OR Apache-2.0 OR MIT" and only -modified to add the SPDX license identifiers and to remove the generation of +modified to tweak the SPDX license identifiers and to remove the generation of non-ASCII identifiers. For copyright details, please see: - https://github.com/google/zerocopy/blob/v0.8.50/README.md?plain=1 - https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-BSD - https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-APACHE - https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-MIT + https://github.com/google/zerocopy/blob/v0.8.52/README.md?plain=1 + https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-BSD + https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-APACHE + https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-MIT diff --git a/rust/zerocopy-derive/derive/from_bytes.rs b/rust/zerocopy-derive/derive/from_bytes.rs index d693a63b7645..66d820f6ad4c 100644 --- a/rust/zerocopy-derive/derive/from_bytes.rs +++ b/rust/zerocopy-derive/derive/from_bytes.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// use proc_macro2::{Span, TokenStream}; use syn::{ parse_quote, Data, DataEnum, DataStruct, DataUnion, Error, Expr, ExprLit, ExprUnary, Lit, UnOp, diff --git a/rust/zerocopy-derive/derive/into_bytes.rs b/rust/zerocopy-derive/derive/into_bytes.rs index ad52a6b45d28..0103a78d087f 100644 --- a/rust/zerocopy-derive/derive/into_bytes.rs +++ b/rust/zerocopy-derive/derive/into_bytes.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Data, DataEnum, DataStruct, DataUnion, Error, Type}; diff --git a/rust/zerocopy-derive/derive/known_layout.rs b/rust/zerocopy-derive/derive/known_layout.rs index fddffd167c82..d0c4cecfff15 100644 --- a/rust/zerocopy-derive/derive/known_layout.rs +++ b/rust/zerocopy-derive/derive/known_layout.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// use proc_macro2::TokenStream; use quote::quote; use syn::{parse_quote, Data, Error, Type}; @@ -87,7 +87,6 @@ fn pointer_to_metadata(ptr: *mut Self) -> }; let inner_extras = { - let leading_fields_tys = leading_fields_tys.clone(); let methods = make_methods(*trailing_field_ty); let (_, ty_generics, _) = ctx.ast.generics.split_for_impl(); diff --git a/rust/zerocopy-derive/derive/mod.rs b/rust/zerocopy-derive/derive/mod.rs index 665ba7da55a8..b3839fcf73c9 100644 --- a/rust/zerocopy-derive/derive/mod.rs +++ b/rust/zerocopy-derive/derive/mod.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// pub mod from_bytes; pub mod into_bytes; pub mod known_layout; @@ -15,8 +15,8 @@ util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait}, }; -pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> TokenStream { - match &ctx.ast.data { +pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> Result { + Ok(match &ctx.ast.data { Data::Struct(strct) => { ImplBlockBuilder::new(ctx, strct, Trait::Immutable, FieldBounds::ALL_SELF).build() } @@ -26,7 +26,7 @@ pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> TokenStream { Data::Union(unn) => { ImplBlockBuilder::new(ctx, unn, Trait::Immutable, FieldBounds::ALL_SELF).build() } - } + }) } pub(crate) fn derive_hash(ctx: &Ctx, _top_level: Trait) -> Result { @@ -97,16 +97,20 @@ pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result {} Data::Enum(_) | Data::Union(_) => { - return Err(Error::new(Span::call_site(), "can only be applied to structs")); + return ctx + .error_or_skip(Error::new(Span::call_site(), "can only be applied to structs")); } }; if repr.get_packed().is_some() { - return Err(Error::new(Span::call_site(), "must not have #[repr(packed)] attribute")); + return ctx.error_or_skip(Error::new( + Span::call_site(), + "must not have #[repr(packed)] attribute", + )); } if !(repr.is_c() || repr.is_transparent()) { - return Err(Error::new( + return ctx.error_or_skip(Error::new( Span::call_site(), "must have #[repr(C)] or #[repr(transparent)] in order to guarantee this type's layout is splitable", )); @@ -116,7 +120,7 @@ pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result) -> *mut , Apache License, Version 2.0 @@ -129,6 +129,40 @@ fn into_ts(self) -> proc_macro2::TokenStream { derive!(ByteEq => derive_eq => crate::derive::derive_eq); derive!(SplitAt => derive_split_at => crate::derive::derive_split_at); +#[cfg_attr(zerocopy_unstable_linux, doc(hidden))] +#[proc_macro_derive(most_traits, attributes(zerocopy))] +pub fn most_traits(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(ts as DeriveInput); + let ctx = match Ctx::try_from_derive_input(ast) { + Ok(ctx) => ctx, + Err(e) => return e.into_compile_error().into(), + } + .skip_on_error(); + + // top-level traits for which to attempt a derive + let derives: [(fn(&Ctx, Trait) -> _, _); 6] = [ + (crate::derive::known_layout::derive, Trait::KnownLayout), + (crate::derive::derive_immutable, Trait::Immutable), + (crate::derive::from_bytes::derive_from_bytes, Trait::FromBytes), + (crate::derive::into_bytes::derive_into_bytes, Trait::IntoBytes), + (crate::derive::derive_split_at, Trait::SplitAt), + (crate::derive::unaligned::derive_unaligned, Trait::Unaligned), + ]; + + let mut tokens = proc_macro2::TokenStream::new(); + for (derive, t) in derives { + tokens.extend(derive(&ctx, t)) + } + + // We wrap in `const_block` as a backstop in case any derive fails + // to wrap its output in `const_block` (and thus fails to annotate) + // with the full set of `#[allow(...)]` attributes). + let ts = const_block([Some(tokens)]); + #[cfg(test)] + crate::util::testutil::check_hygiene(ts.clone()); + ts.into() +} + /// Deprecated: prefer [`FromZeros`] instead. #[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")] #[doc(hidden)] diff --git a/rust/zerocopy-derive/repr.rs b/rust/zerocopy-derive/repr.rs index 74fd376d9fda..1525e94302d1 100644 --- a/rust/zerocopy-derive/repr.rs +++ b/rust/zerocopy-derive/repr.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2019 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 diff --git a/rust/zerocopy-derive/util.rs b/rust/zerocopy-derive/util.rs index 5ba5228e2a44..5c5e9d3bdcb8 100644 --- a/rust/zerocopy-derive/util.rs +++ b/rust/zerocopy-derive/util.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2019 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 @@ -98,6 +98,11 @@ pub(crate) fn with_input(&self, input: &DeriveInput) -> Self { } } + pub(crate) fn skip_on_error(mut self) -> Self { + self.skip_on_error = true; + self + } + pub(crate) fn core_path(&self) -> TokenStream { let zerocopy_crate = &self.zerocopy_crate; quote!(#zerocopy_crate::util::macro_util::core_reexport) @@ -106,20 +111,21 @@ pub(crate) fn core_path(&self) -> TokenStream { pub(crate) fn cfg_compile_error(&self) -> TokenStream { // By checking both during the compilation of the proc macro *and* in // the generated code, we ensure that `--cfg - // zerocopy_unstable_derive_on_error` need only be passed *either* when + // zerocopy_unstable_linux` need only be passed *either* when // compiling this crate *or* when compiling the user's crate. The former // is preferable, but in some situations (such as when cross-compiling // using `cargo build --target`), it doesn't get propagated to this // crate's build by default. - if cfg!(zerocopy_unstable_derive_on_error) { + if cfg!(zerocopy_unstable_linux) { quote!() } else if let Some(span) = self.on_error_span { let core = self.core_path(); - let error_message = "`on_error` is experimental; pass '--cfg zerocopy_unstable_derive_on_error' to enable"; + let error_message = + "`on_error` is experimental; pass '--cfg zerocopy_unstable_linux' to enable"; quote::quote_spanned! {span=> #[allow(unused_attributes, unexpected_cfgs)] const _: () = { - #[cfg(not(zerocopy_unstable_derive_on_error))] + #[cfg(not(zerocopy_unstable_linux))] #core::compile_error!(#error_message); }; } @@ -612,6 +618,20 @@ fn bound_tt(ty: &Type, traits: impl Iterator, ctx: &Ctx) -> WhereP } }; + let zerocopy_bounds = + field_type_bounds + .into_iter() + .chain(padding_check_bound) + .chain(self_bounds) + .map(|bound| { + if self.ctx.skip_on_error { + parse_quote!(for<'zc> #bound) + } else { + bound.clone() + } + }) + .collect::>(); + let bounds = self .ctx .ast @@ -621,9 +641,7 @@ fn bound_tt(ty: &Type, traits: impl Iterator, ctx: &Ctx) -> WhereP .map(|where_clause| where_clause.predicates.iter()) .into_iter() .flatten() - .chain(field_type_bounds.iter()) - .chain(padding_check_bound.iter()) - .chain(self_bounds.iter()); + .chain(zerocopy_bounds.iter()); // The parameters with trait bounds, but without type defaults. let mut params: Vec<_> = self diff --git a/rust/zerocopy/README.md b/rust/zerocopy/README.md index 99e6cad0e26c..712b0317df25 100644 --- a/rust/zerocopy/README.md +++ b/rust/zerocopy/README.md @@ -1,14 +1,13 @@ # `zerocopy` -These source files come from the Rust `zerocopy` crate, version v0.8.50 -(released 2026-05-31), hosted in the +These source files come from the Rust `zerocopy` crate, version v0.8.52 +(released 2026-06-09), hosted in the repository, licensed under "BSD-2-Clause OR Apache-2.0 OR MIT" and only -modified to add the SPDX license identifiers and to remove `Display` -for `f32` and `f64`. +modified to tweak the SPDX license identifiers. For copyright details, please see: - https://github.com/google/zerocopy/blob/v0.8.50/README.md?plain=1 - https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-BSD - https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-APACHE - https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-MIT + https://github.com/google/zerocopy/blob/v0.8.52/README.md?plain=1 + https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-BSD + https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-APACHE + https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-MIT diff --git a/rust/zerocopy/src/byte_slice.rs b/rust/zerocopy/src/byte_slice.rs index a5ded4a18b39..b7f85098dbc4 100644 --- a/rust/zerocopy/src/byte_slice.rs +++ b/rust/zerocopy/src/byte_slice.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2024 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 diff --git a/rust/zerocopy/src/byteorder.rs b/rust/zerocopy/src/byteorder.rs index 8f70048f1eb0..ecf95e38eebd 100644 --- a/rust/zerocopy/src/byteorder.rs +++ b/rust/zerocopy/src/byteorder.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2019 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 @@ -164,6 +164,42 @@ fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { /// A type alias for [`LittleEndian`]. pub type LE = LittleEndian; +macro_rules! impl_dbg_trait { + ($name:ident, $native:ident) => { + impl Debug for $name { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // This results in a format like "U16(42)". + f.debug_tuple(stringify!($name)).field(&self.get()).finish() + } + } + }; +} + +macro_rules! impl_dbg_traits { + ($name:ident, $native:ident, "floating point number") => { + #[cfg(not(no_fp_fmt_parse))] + impl_dbg_trait!($name, $native); + + #[cfg(no_fp_fmt_parse)] + impl Debug for $name { + #[inline] + fn fmt(&self, _f: &mut Formatter<'_>) -> fmt::Result { + panic!("floating point support is turned off"); + } + } + }; + ($name:ident, $native:ident, "unsigned integer") => { + impl_dbg_traits!($name, $native, @all_types); + }; + ($name:ident, $native:ident, "signed integer") => { + impl_dbg_traits!($name, $native, @all_types); + }; + ($name:ident, $native:ident, @all_types) => { + impl_dbg_trait!($name, $native); + }; +} + macro_rules! impl_fmt_trait { ($name:ident, $native:ident, $trait:ident) => { impl $trait for $name { @@ -177,6 +213,8 @@ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { macro_rules! impl_fmt_traits { ($name:ident, $native:ident, "floating point number") => { + #[cfg(not(no_fp_fmt_parse))] + impl_fmt_trait!($name, $native, Display); }; ($name:ident, $native:ident, "unsigned integer") => { impl_fmt_traits!($name, $native, @all_types); @@ -687,16 +725,9 @@ fn eq(&self, other: &$native) -> bool { } } + impl_dbg_traits!($name, $native, $number_kind); impl_fmt_traits!($name, $native, $number_kind); impl_ops_traits!($name, $native, $number_kind); - - impl Debug for $name { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // This results in a format like "U16(42)". - f.debug_tuple(stringify!($name)).field(&self.get()).finish() - } - } }; } diff --git a/rust/zerocopy/src/deprecated.rs b/rust/zerocopy/src/deprecated.rs index 24bafbf9adeb..59ddd35c77c6 100644 --- a/rust/zerocopy/src/deprecated.rs +++ b/rust/zerocopy/src/deprecated.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2024 The Fuchsia Authors // // Licensed under the 2-Clause BSD License README.md +// (cd .. && cargo -q run --manifest-path tools/Cargo.toml -p generate-readme) > README.md //! ***Fast, safe, compile error. Pick two.*** @@ -174,7 +174,7 @@ //! //! [Miri]: https://github.com/rust-lang/miri //! [Kani]: https://github.com/model-checking/kani -//! [soundness policy]: https://github.com/google/zerocopy/blob/main/POLICIES.md#soundness +//! [soundness policy]: https://github.com/google/zerocopy/blob/main/zerocopy/POLICIES.md#soundness //! //! # Relationship to Project Safe Transmute //! @@ -203,7 +203,7 @@ //! //! See our [MSRV policy]. //! -//! [MSRV policy]: https://github.com/google/zerocopy/blob/main/POLICIES.md#msrv +//! [MSRV policy]: https://github.com/google/zerocopy/blob/main/zerocopy/POLICIES.md#msrv //! //! # Changelog //! @@ -435,6 +435,9 @@ WARNING }; +#[doc(hidden)] +#[cfg(all(any(feature = "derive", test), zerocopy_unstable_linux))] +pub use zerocopy_derive::most_traits; /// Implements [`KnownLayout`]. /// /// This derive analyzes various aspects of a type's layout that are needed for @@ -2832,7 +2835,7 @@ fn try_ref_from_suffix_with_elems( /// ``` /// /// [`try_mut_from_bytes`]: TryFromBytes::try_mut_from_bytes - /// + /// #[doc = codegen_header!("h5", "try_mut_from_bytes_with_elems")] /// /// See [`TryFromBytes::try_ref_from_bytes_with_elems`](#method.try_ref_from_bytes_with_elems.codegen). diff --git a/rust/zerocopy/src/macros.rs b/rust/zerocopy/src/macros.rs index b801d86a8fa6..ec67c03a44fc 100644 --- a/rust/zerocopy/src/macros.rs +++ b/rust/zerocopy/src/macros.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2024 The Fuchsia Authors // // Licensed under the 2-Clause BSD License , Apache License, Version 2.0 diff --git a/rust/zerocopy/src/pointer/invariant.rs b/rust/zerocopy/src/pointer/invariant.rs index 1802d23563db..7ff0d43dad5e 100644 --- a/rust/zerocopy/src/pointer/invariant.rs +++ b/rust/zerocopy/src/pointer/invariant.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2024 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 diff --git a/rust/zerocopy/src/pointer/mod.rs b/rust/zerocopy/src/pointer/mod.rs index 3461f7f5ca80..d6eacc52febe 100644 --- a/rust/zerocopy/src/pointer/mod.rs +++ b/rust/zerocopy/src/pointer/mod.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2023 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 diff --git a/rust/zerocopy/src/pointer/ptr.rs b/rust/zerocopy/src/pointer/ptr.rs index b7c4ea56d2b2..7213f6f4a04e 100644 --- a/rust/zerocopy/src/pointer/ptr.rs +++ b/rust/zerocopy/src/pointer/ptr.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2023 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 diff --git a/rust/zerocopy/src/pointer/transmute.rs b/rust/zerocopy/src/pointer/transmute.rs index a534984b70d3..ef9836698203 100644 --- a/rust/zerocopy/src/pointer/transmute.rs +++ b/rust/zerocopy/src/pointer/transmute.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2025 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 diff --git a/rust/zerocopy/src/ref.rs b/rust/zerocopy/src/ref.rs index 860066d75196..e49f2a887ffa 100644 --- a/rust/zerocopy/src/ref.rs +++ b/rust/zerocopy/src/ref.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2024 The Fuchsia Authors // // Licensed under the 2-Clause BSD License , Apache License, Version 2.0 diff --git a/rust/zerocopy/src/util/macros.rs b/rust/zerocopy/src/util/macros.rs index 43e4fd64ee15..7e63e3a54fc4 100644 --- a/rust/zerocopy/src/util/macros.rs +++ b/rust/zerocopy/src/util/macros.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2023 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 diff --git a/rust/zerocopy/src/util/mod.rs b/rust/zerocopy/src/util/mod.rs index d6d4c6c2fcd9..f8affbbd336c 100644 --- a/rust/zerocopy/src/util/mod.rs +++ b/rust/zerocopy/src/util/mod.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2023 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 diff --git a/rust/zerocopy/src/wrappers.rs b/rust/zerocopy/src/wrappers.rs index 266aec25fa58..1a8cf2b41d55 100644 --- a/rust/zerocopy/src/wrappers.rs +++ b/rust/zerocopy/src/wrappers.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT - +// // Copyright 2023 The Fuchsia Authors // // Licensed under a BSD-style license , Apache License, Version 2.0 From 425e10586cf12b86af076d1746bb87b0bb3f18a1 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Thu, 9 Jul 2026 23:13:11 +0200 Subject: [PATCH 009/456] rust: zerocopy: update to v0.8.54 Update our vendored copy of `zerocopy` (and `zerocopy-derive`) to v0.8.54. It is a very small delta from v0.8.52, and most importantly it resolves the unexpected lack of inlining [1] which triggered a modpost error under `CONFIG_CC_OPTIMIZE_FOR_SIZE=y` reported by Alexandre using Gary's suggestion [2]: ERROR: modpost: "_RNvMNtCs5wX7wwEUCR9_8zerocopy6layoutNtB2_8SizeInfo24try_to_nonzero_elem_size" [drivers/gpu/nova-core.ko] undefined! ERROR: modpost: "_RNvNtCs5wX7wwEUCR9_8zerocopy4util18padding_needed_for" [drivers/gpu/nova-core.ko] undefined! It also resolves `most_traits` being unexpectedly documented [3] that I reported and adds a missing SPDX license identifier [4] that I requested to match the kernel version. The following script may be used to check for the remaining differences: for path in $(cd rust/zerocopy-derive/ && find . -type f ! -name README.md); do curl --silent --show-error --location \ https://github.com/google/zerocopy/raw/v0.8.54/zerocopy/zerocopy-derive/src/$path | git diff --no-index - rust/zerocopy-derive/$path && echo $path: OK done for path in $(cd rust/zerocopy/ && find . -type f ! -name README.md); do curl --silent --show-error --location \ https://github.com/google/zerocopy/raw/v0.8.54/zerocopy/$path | git diff --no-index - rust/zerocopy/$path && echo $path: OK done Cc: Joshua Liebow-Feeser Cc: Jack Wrenn Reported-by: Alexandre Courbot Closes: https://lore.kernel.org/rust-for-linux/20260708-zerocopy-export-v1-1-2bfc355853c6@nvidia.com/ [1] Suggested-by: Gary Guo Link: https://lore.kernel.org/rust-for-linux/DJT6235B3DOV.222XR5O6VHG4M@garyguo.net/ [2] Link: https://github.com/google/zerocopy/issues/3466 [3] Link: https://github.com/google/zerocopy/issues/3457 [4] Reviewed-by: Alice Ryhl Tested-by: Alexandre Courbot Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260709211311.142544-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- rust/Makefile | 2 +- rust/zerocopy-derive/README.md | 12 ++++++------ rust/zerocopy-derive/lib.rs | 2 +- rust/zerocopy/README.md | 12 ++++++------ rust/zerocopy/rustdoc/style.css | 1 - rust/zerocopy/src/byteorder.rs | 1 + rust/zerocopy/src/layout.rs | 2 ++ rust/zerocopy/src/lib.rs | 1 - rust/zerocopy/src/util/mod.rs | 6 ++++++ 9 files changed, 23 insertions(+), 16 deletions(-) diff --git a/rust/Makefile b/rust/Makefile index 835cf10e1c0b..627ed79dc6f5 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -92,7 +92,7 @@ zerocopy-flags := \ $(call cfgs-to-flags,$(zerocopy-cfgs)) zerocopy-envs := \ - CARGO_PKG_VERSION=0.8.52 + CARGO_PKG_VERSION=0.8.54 proc_macro2-cfgs := \ feature="proc-macro" \ diff --git a/rust/zerocopy-derive/README.md b/rust/zerocopy-derive/README.md index 2a3dcf1212af..d62c79804342 100644 --- a/rust/zerocopy-derive/README.md +++ b/rust/zerocopy-derive/README.md @@ -1,14 +1,14 @@ # `zerocopy-derive` -These source files come from the Rust `zerocopy-derive` crate, version v0.8.52 -(released 2026-06-09), hosted in the +These source files come from the Rust `zerocopy-derive` crate, version v0.8.54 +(released 2026-07-08), hosted in the repository, licensed under "BSD-2-Clause OR Apache-2.0 OR MIT" and only modified to tweak the SPDX license identifiers and to remove the generation of non-ASCII identifiers. For copyright details, please see: - https://github.com/google/zerocopy/blob/v0.8.52/README.md?plain=1 - https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-BSD - https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-APACHE - https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-MIT + https://github.com/google/zerocopy/blob/v0.8.54/README.md?plain=1 + https://github.com/google/zerocopy/blob/v0.8.54/LICENSE-BSD + https://github.com/google/zerocopy/blob/v0.8.54/LICENSE-APACHE + https://github.com/google/zerocopy/blob/v0.8.54/LICENSE-MIT diff --git a/rust/zerocopy-derive/lib.rs b/rust/zerocopy-derive/lib.rs index 88599e750894..d387de368367 100644 --- a/rust/zerocopy-derive/lib.rs +++ b/rust/zerocopy-derive/lib.rs @@ -129,7 +129,7 @@ fn into_ts(self) -> proc_macro2::TokenStream { derive!(ByteEq => derive_eq => crate::derive::derive_eq); derive!(SplitAt => derive_split_at => crate::derive::derive_split_at); -#[cfg_attr(zerocopy_unstable_linux, doc(hidden))] +#[cfg_attr(not(zerocopy_unstable_linux), doc(hidden))] #[proc_macro_derive(most_traits, attributes(zerocopy))] pub fn most_traits(ts: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse_macro_input!(ts as DeriveInput); diff --git a/rust/zerocopy/README.md b/rust/zerocopy/README.md index 712b0317df25..3d11a6502cf0 100644 --- a/rust/zerocopy/README.md +++ b/rust/zerocopy/README.md @@ -1,13 +1,13 @@ # `zerocopy` -These source files come from the Rust `zerocopy` crate, version v0.8.52 -(released 2026-06-09), hosted in the +These source files come from the Rust `zerocopy` crate, version v0.8.54 +(released 2026-07-08), hosted in the repository, licensed under "BSD-2-Clause OR Apache-2.0 OR MIT" and only modified to tweak the SPDX license identifiers. For copyright details, please see: - https://github.com/google/zerocopy/blob/v0.8.52/README.md?plain=1 - https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-BSD - https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-APACHE - https://github.com/google/zerocopy/blob/v0.8.52/LICENSE-MIT + https://github.com/google/zerocopy/blob/v0.8.54/README.md?plain=1 + https://github.com/google/zerocopy/blob/v0.8.54/LICENSE-BSD + https://github.com/google/zerocopy/blob/v0.8.54/LICENSE-APACHE + https://github.com/google/zerocopy/blob/v0.8.54/LICENSE-MIT diff --git a/rust/zerocopy/rustdoc/style.css b/rust/zerocopy/rustdoc/style.css index 414348964af2..d2d55ad2e689 100644 --- a/rust/zerocopy/rustdoc/style.css +++ b/rust/zerocopy/rustdoc/style.css @@ -1,5 +1,4 @@ /* SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT */ - /* Copyright 2026 The Fuchsia Authors diff --git a/rust/zerocopy/src/byteorder.rs b/rust/zerocopy/src/byteorder.rs index ecf95e38eebd..c761d5728320 100644 --- a/rust/zerocopy/src/byteorder.rs +++ b/rust/zerocopy/src/byteorder.rs @@ -100,6 +100,7 @@ impl Sealed for super::LittleEndian {} #[allow(missing_copy_implementations, missing_debug_implementations)] #[doc(hidden)] +#[derive(PartialEq)] pub enum Order { BigEndian, LittleEndian, diff --git a/rust/zerocopy/src/layout.rs b/rust/zerocopy/src/layout.rs index 942d35db8957..b1fa0cd436db 100644 --- a/rust/zerocopy/src/layout.rs +++ b/rust/zerocopy/src/layout.rs @@ -71,6 +71,8 @@ impl SizeInfo { /// Attempts to create a `SizeInfo` from `Self` in which `elem_size` is a /// `NonZeroUsize`. If `elem_size` is 0, returns `None`. #[allow(unused)] + #[cfg_attr(not(zerocopy_inline_always), inline)] + #[cfg_attr(zerocopy_inline_always, inline(always))] const fn try_to_nonzero_elem_size(&self) -> Option> { Some(match *self { SizeInfo::Sized { size } => SizeInfo::Sized { size }, diff --git a/rust/zerocopy/src/lib.rs b/rust/zerocopy/src/lib.rs index 700fece1b728..572f0563fe2f 100644 --- a/rust/zerocopy/src/lib.rs +++ b/rust/zerocopy/src/lib.rs @@ -435,7 +435,6 @@ WARNING }; -#[doc(hidden)] #[cfg(all(any(feature = "derive", test), zerocopy_unstable_linux))] pub use zerocopy_derive::most_traits; /// Implements [`KnownLayout`]. diff --git a/rust/zerocopy/src/util/mod.rs b/rust/zerocopy/src/util/mod.rs index f8affbbd336c..02fd4ed62741 100644 --- a/rust/zerocopy/src/util/mod.rs +++ b/rust/zerocopy/src/util/mod.rs @@ -150,6 +150,8 @@ pub(crate) fn validate_aligned_to(t: T) -> Result<(), Alignment // Ensures that we add the minimum required padding. kani::ensures(|&p| p < align.get()), )] +#[cfg_attr(not(zerocopy_inline_always), inline)] +#[cfg_attr(zerocopy_inline_always, inline(always))] pub(crate) const fn padding_needed_for(len: usize, align: NonZeroUsize) -> usize { #[cfg(kani)] #[kani::proof_for_contract(padding_needed_for)] @@ -251,6 +253,8 @@ fn proof() { n & mask } +#[cfg_attr(not(zerocopy_inline_always), inline)] +#[cfg_attr(zerocopy_inline_always, inline(always))] pub(crate) const fn max(a: NonZeroUsize, b: NonZeroUsize) -> NonZeroUsize { if a.get() < b.get() { b @@ -259,6 +263,8 @@ pub(crate) const fn max(a: NonZeroUsize, b: NonZeroUsize) -> NonZeroUsize { } } +#[cfg_attr(not(zerocopy_inline_always), inline)] +#[cfg_attr(zerocopy_inline_always, inline(always))] pub(crate) const fn min(a: NonZeroUsize, b: NonZeroUsize) -> NonZeroUsize { if a.get() > b.get() { b From 5a81c35c3b18cd59ded56171a6a9f643b92a6759 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Fri, 10 Jul 2026 19:32:52 +0200 Subject: [PATCH 010/456] objtool/rust: add one more `noreturn` Rust function for Rust 1.99.0 Starting with Rust 1.99.0 (expected 2026-10-01), under `CONFIG_RUST_DEBUG_ASSERTIONS=y`, `objtool` may report: rust/kernel.o: warning: objtool: _R..._6kernel12module_param9set_paramaEB4_() falls through to next function _R..._6kernel12module_param9set_paramhEB4_() (and many others) due to calls to the `noreturn` symbol [1]: core::panicking::panic_null_reference_constructed Thus add the mangled one to the list so that `objtool` knows it is actually `noreturn`. See commit 56d680dd23c3 ("objtool/rust: list `noreturn` Rust functions") for more details. Cc: stable@vger.kernel.org # Needed in 6.12.y and later (Rust is pinned in older LTSs). Cc: Josh Poimboeuf Cc: Peter Zijlstra Cc: Petr Pavlu Link: https://github.com/rust-lang/rust/pull/158796 [1] Reported-by: Alice Ryhl Closes: https://lore.kernel.org/rust-for-linux/alEBInX9gD1M5NAr@google.com/ Reviewed-by: Alice Ryhl Tested-by: Alice Ryhl Link: https://patch.msgid.link/20260710173252.191781-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- tools/objtool/check.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 10b18cf9c360..f03dd59e7fca 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -206,6 +206,7 @@ static bool is_rust_noreturn(const struct symbol *func) str_ends_with(func->name, "_4core9panicking18panic_nounwind_fmt") || str_ends_with(func->name, "_4core9panicking19assert_failed_inner") || str_ends_with(func->name, "_4core9panicking30panic_null_pointer_dereference") || + str_ends_with(func->name, "_4core9panicking32panic_null_reference_constructed") || str_ends_with(func->name, "_4core9panicking36panic_misaligned_pointer_dereference") || str_ends_with(func->name, "_7___rustc17rust_begin_unwind") || strstr(func->name, "_4core9panicking13assert_failed") || From 50246d57233ad3b3c5dab99001a84d93e3e3d8f0 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 26 May 2026 12:47:52 +0200 Subject: [PATCH 011/456] usb: udc: pxa: fix error handling As Dan Carpenter points out, my recent change makes subtle changes to the error handling that were not intended. Move the warning print up so it does not get skipped in case of an error, but handle -EPROBE_DEFER properly now. Change the devm_gpiod_get() to the _optional variant, which is in line with the intended behavior and the DT binding, though this did not work previously. Reported-by: Dan Carpenter Link: https://lore.kernel.org/linux-usb/ag6-xhfFjb5NpXQz@stanley.mountain/ Fixes: 25bd55f46032 ("usb: udc: pxa: remove unused platform_data") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260526104810.3906090-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/pxa27x_udc.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index 640f81988c04..df5cca43afbe 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -2374,9 +2374,10 @@ static int pxa_udc_probe(struct platform_device *pdev) struct pxa_udc *udc = &memory; int retval = 0; - udc->gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_ASIS); + udc->gpiod = devm_gpiod_get_optional(&pdev->dev, NULL, GPIOD_ASIS); if (IS_ERR(udc->gpiod)) - return PTR_ERR(udc->gpiod); + return dev_err_probe(&pdev->dev, PTR_ERR(udc->gpiod), + "Couldn't find or request D+ gpio\n"); udc->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(udc->regs)) @@ -2395,11 +2396,6 @@ static int pxa_udc_probe(struct platform_device *pdev) udc->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); } - if (IS_ERR(udc->gpiod)) { - dev_err(&pdev->dev, "Couldn't find or request D+ gpio : %ld\n", - PTR_ERR(udc->gpiod)); - return PTR_ERR(udc->gpiod); - } if (udc->gpiod) gpiod_direction_output(udc->gpiod, 0); From 79e2d75725c85607f8a9d87ae9cace62a19f767d Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 27 Jun 2026 18:41:53 +0800 Subject: [PATCH 012/456] usb: gadget: f_tcm: synchronize delayed set_alt with teardown The f_tcm set_alt() path defers endpoint setup to a work item and completes the delayed status response from process context. The delayed work uses f_tcm private state and may complete the setup request after disconnect or function teardown has already moved on. Cancel and drain the delayed set_alt work when the function is unbound or freed. For disable paths, which are reached under the composite device lock, use a small state machine and a non-sleeping cancellation path instead of cancel_work_sync(). If the work is already running, mark it cancelled and let the worker own the cleanup; otherwise tcm_disable() can cancel the queued work and clean up immediately. Also serialize the final delayed-status completion with the cancellation check while holding the composite device lock. This prevents a disconnect from clearing delayed_status while the worker is about to complete the control request. Validation reproduced this kernel report: BUG: KASAN: slab-use-after-free in tcm_delayed_set_alt+0x6c/0xef0 Call Trace: dump_stack_lvl+0x66/0xa0 print_report+0xce/0x630 ? tcm_delayed_set_alt+0x6c/0xef0 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x188/0x320 ? tcm_delayed_set_alt+0x6c/0xef0 kasan_report+0xe0/0x110 ? tcm_delayed_set_alt+0x6c/0xef0 tcm_delayed_set_alt+0x6c/0xef0 ? __pfx_tcm_delayed_set_alt+0x10/0x10 ? process_one_work+0x4cb/0xb90 ? rcu_is_watching+0x20/0x50 ? tcm_delayed_set_alt+0x9/0xef0 process_one_work+0x4d7/0xb90 ? __pfx_process_one_work+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __list_add_valid_or_report+0x37/0xf0 ? __pfx_tcm_delayed_set_alt+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 worker_thread+0x2d8/0x570 ? __pfx_worker_thread+0x10/0x10 kthread+0x1ad/0x1f0 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x3c9/0x540 ? __pfx_ret_from_fork+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 ? __switch_to+0x2e9/0x730 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Allocated by task 544: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0x8f/0xa0 tcm_alloc+0x68/0x180 usb_get_function+0x36/0x60 config_usb_cfg_link+0x125/0x1b0 configfs_symlink+0x322/0x890 vfs_symlink+0xc2/0x270 filename_symlinkat+0x295/0x2f0 __x64_sys_symlinkat+0x62/0x90 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 661: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x43/0x70 kfree+0x2f9/0x530 config_usb_cfg_unlink+0x173/0x1e0 configfs_unlink+0x1fa/0x340 vfs_unlink+0x15c/0x510 filename_unlinkat+0x2ba/0x450 __x64_sys_unlinkat+0x63/0x90 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: c52661d60f63 ("usb-gadget: Initial merge of target module for UASP + BOT") Cc: stable Assisted-by: Codex:gpt-5.5 Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260627104153.3822495-1-zzzccc427@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_tcm.c | 192 ++++++++++++++++++++++++---- drivers/usb/gadget/function/tcm.h | 13 ++ 2 files changed, 177 insertions(+), 28 deletions(-) diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index 34d9f49e9987..b3fa5a17fd2d 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -2363,31 +2363,158 @@ static int tcm_bind(struct usb_configuration *c, struct usb_function *f) return -ENOTSUPP; } -struct guas_setup_wq { - struct work_struct work; - struct f_uas *fu; - unsigned int alt; -}; +static void tcm_cleanup_old_alt(struct f_uas *fu) +{ + if (fu->flags & USBG_IS_UAS) + uasp_cleanup_old_alt(fu); + else if (fu->flags & USBG_IS_BOT) + bot_cleanup_old_alt(fu); + fu->flags = 0; +} + +static void tcm_delayed_set_alt_done(struct f_uas *fu) +{ + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); +} + +static bool tcm_delayed_set_alt_cancelled(struct f_uas *fu) +{ + bool cancelled; + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + cancelled = fu->delayed_set_alt_cancel; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + return cancelled; +} + +static bool tcm_complete_delayed_status(struct f_uas *fu) +{ + struct usb_composite_dev *cdev = fu->function.config->cdev; + struct usb_request *req = cdev->req; + unsigned long cdev_flags; + bool cancelled; + int ret; + + spin_lock_irqsave(&cdev->lock, cdev_flags); + spin_lock(&fu->delayed_set_alt_lock); + cancelled = fu->delayed_set_alt_cancel; + if (!cancelled) { + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + } + spin_unlock(&fu->delayed_set_alt_lock); + + if (cancelled) { + spin_unlock_irqrestore(&cdev->lock, cdev_flags); + return false; + } + + if (cdev->delayed_status == 0) { + WARN(cdev, "%s: Unexpected call\n", __func__); + } else if (--cdev->delayed_status == 0) { + req->length = 0; + req->context = cdev; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret == 0) { + cdev->setup_pending = true; + } else { + req->status = 0; + req->complete(cdev->gadget->ep0, req); + } + } + + spin_unlock_irqrestore(&cdev->lock, cdev_flags); + + return true; +} + +static bool tcm_cancel_delayed_set_alt(struct f_uas *fu) +{ + bool cleanup = false; + bool cancel = false; + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + switch (fu->delayed_set_alt_state) { + case USBG_DELAYED_SET_ALT_IDLE: + cleanup = true; + break; + case USBG_DELAYED_SET_ALT_QUEUED: + case USBG_DELAYED_SET_ALT_RUNNING: + fu->delayed_set_alt_cancel = true; + cancel = true; + break; + } + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + if (cancel && cancel_work(&fu->delayed_set_alt)) { + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state == USBG_DELAYED_SET_ALT_QUEUED) { + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + cleanup = true; + } + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + } + + return cleanup; +} + +static void tcm_cancel_delayed_set_alt_sync(struct f_uas *fu) +{ + unsigned long flags; + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state != USBG_DELAYED_SET_ALT_IDLE) + fu->delayed_set_alt_cancel = true; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + cancel_work_sync(&fu->delayed_set_alt); + + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_IDLE; + fu->delayed_set_alt_cancel = false; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); +} static void tcm_delayed_set_alt(struct work_struct *wq) { - struct guas_setup_wq *work = container_of(wq, struct guas_setup_wq, - work); - struct f_uas *fu = work->fu; - int alt = work->alt; + struct f_uas *fu = container_of(wq, struct f_uas, delayed_set_alt); + unsigned long flags; + unsigned int alt; - kfree(work); + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state != USBG_DELAYED_SET_ALT_QUEUED) { + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + return; + } + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_RUNNING; + alt = fu->delayed_alt; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); - if (fu->flags & USBG_IS_BOT) - bot_cleanup_old_alt(fu); - if (fu->flags & USBG_IS_UAS) - uasp_cleanup_old_alt(fu); + tcm_cleanup_old_alt(fu); + + if (tcm_delayed_set_alt_cancelled(fu)) + goto out_done; if (alt == USB_G_ALT_INT_BBB) bot_set_alt(fu); else if (alt == USB_G_ALT_INT_UAS) uasp_set_alt(fu); - usb_composite_setup_continue(fu->function.config->cdev); + + if (tcm_complete_delayed_status(fu)) + return; + + tcm_cleanup_old_alt(fu); +out_done: + tcm_delayed_set_alt_done(fu); } static int tcm_get_alt(struct usb_function *f, unsigned intf) @@ -2413,15 +2540,20 @@ static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) return -EOPNOTSUPP; if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) { - struct guas_setup_wq *work; + unsigned long flags; - work = kmalloc_obj(*work, GFP_ATOMIC); - if (!work) - return -ENOMEM; - INIT_WORK(&work->work, tcm_delayed_set_alt); - work->fu = fu; - work->alt = alt; - schedule_work(&work->work); + spin_lock_irqsave(&fu->delayed_set_alt_lock, flags); + if (fu->delayed_set_alt_state != USBG_DELAYED_SET_ALT_IDLE) { + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, + flags); + return -EBUSY; + } + fu->delayed_alt = alt; + fu->delayed_set_alt_cancel = false; + fu->delayed_set_alt_state = USBG_DELAYED_SET_ALT_QUEUED; + spin_unlock_irqrestore(&fu->delayed_set_alt_lock, flags); + + schedule_work(&fu->delayed_set_alt); return USB_GADGET_DELAYED_STATUS; } return -EOPNOTSUPP; @@ -2431,11 +2563,8 @@ static void tcm_disable(struct usb_function *f) { struct f_uas *fu = to_f_uas(f); - if (fu->flags & USBG_IS_UAS) - uasp_cleanup_old_alt(fu); - else if (fu->flags & USBG_IS_BOT) - bot_cleanup_old_alt(fu); - fu->flags = 0; + if (tcm_cancel_delayed_set_alt(fu)) + tcm_cleanup_old_alt(fu); } static int tcm_setup(struct usb_function *f, @@ -2583,11 +2712,16 @@ static void tcm_free(struct usb_function *f) { struct f_uas *tcm = to_f_uas(f); + tcm_cancel_delayed_set_alt_sync(tcm); kfree(tcm); } static void tcm_unbind(struct usb_configuration *c, struct usb_function *f) { + struct f_uas *fu = to_f_uas(f); + + tcm_cancel_delayed_set_alt_sync(fu); + tcm_cleanup_old_alt(fu); usb_free_all_descriptors(f); } @@ -2620,6 +2754,8 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi) fu->function.disable = tcm_disable; fu->function.free_func = tcm_free; fu->tpg = tpg_instances[i].tpg; + INIT_WORK(&fu->delayed_set_alt, tcm_delayed_set_alt); + spin_lock_init(&fu->delayed_set_alt_lock); hash_init(fu->stream_hash); mutex_unlock(&tpg_instances_lock); diff --git a/drivers/usb/gadget/function/tcm.h b/drivers/usb/gadget/function/tcm.h index 009974d81d66..e1d5a9391612 100644 --- a/drivers/usb/gadget/function/tcm.h +++ b/drivers/usb/gadget/function/tcm.h @@ -3,6 +3,7 @@ #define __TARGET_USB_GADGET_H__ #include +#include /* #include */ #include #include @@ -29,6 +30,12 @@ enum { #define USB_G_DEFAULT_SESSION_TAGS USBG_NUM_CMDS +enum { + USBG_DELAYED_SET_ALT_IDLE = 0, + USBG_DELAYED_SET_ALT_QUEUED, + USBG_DELAYED_SET_ALT_RUNNING, +}; + struct tcm_usbg_nexus { struct se_session *tvn_se_sess; }; @@ -132,6 +139,12 @@ struct f_uas { #define USBG_BOT_CMD_PEND (1 << 4) #define USBG_BOT_WEDGED (1 << 5) + struct work_struct delayed_set_alt; + spinlock_t delayed_set_alt_lock; /* protects delayed_set_alt_* */ + unsigned int delayed_alt; + unsigned int delayed_set_alt_state; + bool delayed_set_alt_cancel; + struct usbg_cdb cmd[USBG_NUM_CMDS]; struct usb_ep *ep_in; struct usb_ep *ep_out; From b70dc75e85ba968b7b76eebfe5d63000080b875b Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 30 Jun 2026 00:50:04 +0500 Subject: [PATCH 013/456] usb: gadget: uvc: clamp SEND_RESPONSE length to the response buffer uvc_send_response() builds the UVC control response from a user-supplied struct uvc_request_data: req->length = min_t(unsigned int, uvc->event_length, data->length); ... memcpy(req->buf, data->data, req->length); req->length is clamped to uvc->event_length, which is taken from the host control request wLength (up to UVC_MAX_REQUEST_SIZE, 64), and to data->length, which comes from the UVCIOC_SEND_RESPONSE ioctl and is only checked for being negative. The source buffer data->data is only 60 bytes, so a response with uvc->event_length and data->length both greater than 60 makes memcpy() read past the end of data->data. Clamp req->length to sizeof(data->data) as well. Fixes: a5eaaa1f33e7 ("usb: gadget: uvc: use capped length value") Cc: stable Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260629195004.148405-1-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_v4l2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c index 514e5930b9ca..dfa0521a243a 100644 --- a/drivers/usb/gadget/function/uvc_v4l2.c +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -200,6 +200,8 @@ uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data) return usb_ep_set_halt(cdev->gadget->ep0); req->length = min_t(unsigned int, uvc->event_length, data->length); + if (req->length > sizeof(data->data)) + req->length = sizeof(data->data); req->zero = data->length < uvc->event_length; memcpy(req->buf, data->data, req->length); From 1fc50f1ecde39feb4fccdaf4bc71aa6c0eb25c49 Mon Sep 17 00:00:00 2001 From: Xincheng Zhang Date: Tue, 30 Jun 2026 09:26:48 +0800 Subject: [PATCH 014/456] usb: xhci-pci: Limit VIA VL805 DMA addressing to 36 bits The VIA VL805/806 xHCI controller advertises AC64, but fails to handle DMA addresses at or above 0x1000000000. On systems with large amounts of RAM, this can cause USB device failures when the controller is given DMA addresses beyond its usable address width. Do not use XHCI_NO_64BIT_SUPPORT for this controller. That quirk clears the cached AC64 capability and limits DMA to 32 bits, causing unnecessary bouncing for addresses between 4GiB and 64GiB and hiding the controller's real AC64 capability from code that may need to distinguish register access width from usable DMA address width. Track the usable DMA address width separately from the AC64 capability. Initialize the generic xhci->dma_mask_bits field to 64 and let PCI quirks reduce it for controllers with narrower DMA support. Set VIA VL805/806 to 36 bits so the DMA API only hands it addresses in the range it can handle while keeping HCCPARAMS1.AC64 visible. Cc: stable Signed-off-by: Xincheng Zhang Link: https://patch.msgid.link/20260630-xhci-via-dma-fix-v3-1-690dcb8cf75a@ultrarisc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 1 + drivers/usb/host/xhci.c | 15 ++++++++++----- drivers/usb/host/xhci.h | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 039c26b241d0..6b3fcba44b08 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -448,6 +448,7 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == PCI_DEVICE_ID_VIA_VL805) { xhci->quirks |= XHCI_LPM_SUPPORT; xhci->quirks |= XHCI_TRB_OVERFETCH; + xhci->dma_mask_bits = 36; } if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA && diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index f44ccee5fa07..091c82ca8ee2 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -5466,6 +5466,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) if (xhci->hci_version > 0x100) xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); + xhci->dma_mask_bits = 64; xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS); xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS); /* xhci-plat or xhci-pci might have set max_interrupters already */ @@ -5515,12 +5516,16 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) xhci->hcc_params &= ~BIT(0); - /* Set dma_mask and coherent_dma_mask to 64-bits, - * if xHC supports 64-bit addressing */ + /* + * Set dma_mask and coherent_dma_mask to 64-bits if xHC supports + * 64-bit addressing, unless a controller-specific quirk callback + * limits the usable address width. + */ if ((xhci->hcc_params & HCC_64BIT_ADDR) && - !dma_set_mask(dev, DMA_BIT_MASK(64))) { - xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n"); - dma_set_coherent_mask(dev, DMA_BIT_MASK(64)); + !dma_set_mask(dev, DMA_BIT_MASK(xhci->dma_mask_bits))) { + xhci_dbg(xhci, "Enabling %u-bit DMA addresses.\n", + xhci->dma_mask_bits); + dma_set_coherent_mask(dev, DMA_BIT_MASK(xhci->dma_mask_bits)); } else { /* * This is to avoid error in cases where a 32-bit USB diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index d02046a573e4..2d3941b5e1e3 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1526,6 +1526,7 @@ struct xhci_hcd { /* imod_interval in ns (I * 250ns) */ u32 imod_interval; u32 page_size; + unsigned int dma_mask_bits; /* MSI-X/MSI vectors */ int nvecs; /* optional clocks */ From 6b874d00c466e73c6448a89856407fe46b2f50e4 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:15:33 +0200 Subject: [PATCH 015/456] USB: gadget: fsl-udc: fix device name leak on probe failure The gadget device name is set by UDC core when registering the gadget and must not be set before to avoid leaking the name in intermediate error paths (e.g. on dma pool creation failure). Fixes: eab35c4e6d95 ("usb: gadget: fsl_udc_core: let udc-core manage gadget->dev") Cc: stable Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260702141536.90887-2-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/fsl_udc_core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index 600ce8cc0fef..8c5b2f42ff44 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2474,7 +2474,6 @@ static int fsl_udc_probe(struct platform_device *pdev) udc_controller->gadget.name = driver_name; /* Setup gadget.dev and register with kernel */ - dev_set_name(&udc_controller->gadget.dev, "gadget"); udc_controller->gadget.dev.of_node = pdev->dev.of_node; if (!IS_ERR_OR_NULL(udc_controller->transceiver)) From 29a142d3e8b35ebc9e0bcc78f4bc26c9b6a9ac0b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:15:34 +0200 Subject: [PATCH 016/456] USB: gadget: snps-udc: fix device name leak on probe failure The gadget device name is set by UDC core when registering the gadget and must not be set before to avoid leaking the name in intermediate error paths (e.g. when detecting an older chip revision). Fixes: 12ad0fcaf2fb ("usb: gadget: amd5536udc: let udc-core manage gadget->dev") Cc: stable Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260702141536.90887-3-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/snps_udc_core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c index 0e0db68e0b27..d506f9d92bca 100644 --- a/drivers/usb/gadget/udc/snps_udc_core.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -3133,7 +3133,6 @@ int udc_probe(struct udc *dev) /* device struct setup */ dev->gadget.ops = &udc_ops; - dev_set_name(&dev->gadget.dev, "gadget"); dev->gadget.name = name; dev->gadget.max_speed = USB_SPEED_HIGH; From c4dd150fceab281496acb3a643ae712aacb74864 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:15:35 +0200 Subject: [PATCH 017/456] USB: gadget: fsl-udc: fix dev_printk() device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A change replacing custom printk() macros with dev_printk() incorrectly used the gadget struct device instead of the controller struct device (including for messages printed before the gadget device name has been initialised). Switch to using the controller platform device with dev_printk() so that the controller device and driver names are included in log messages as expected. Fixes: 6025f20f16c2 ("usb: gadget: fsl-udc: Replace custom log wrappers by dev_{err,warn,dbg,vdbg}") Cc: stable Cc: Uwe Kleine-König Signed-off-by: Johan Hovold Acked-by: Uwe Kleine-König Link: https://patch.msgid.link/20260702141536.90887-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/fsl_udc_core.c | 92 ++++++++++++--------------- drivers/usb/gadget/udc/fsl_usb2_udc.h | 1 + 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index 8c5b2f42ff44..c4761933ec86 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -184,7 +184,7 @@ __acquires(ep->udc->lock) usb_gadget_unmap_request(&ep->udc->gadget, &req->req, ep_is_in(ep)); if (status && (status != -ESHUTDOWN)) - dev_vdbg(&udc->gadget.dev, "complete %s req %p stat %d len %u/%u\n", + dev_vdbg(udc->dev, "complete %s req %p stat %d len %u/%u\n", ep->ep.name, &req->req, status, req->req.actual, req->req.length); @@ -286,7 +286,7 @@ static int dr_controller_setup(struct fsl_udc *udc) timeout = jiffies + FSL_UDC_RESET_TIMEOUT; while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) { if (time_after(jiffies, timeout)) { - dev_err(&udc->gadget.dev, "udc reset timeout!\n"); + dev_err(udc->dev, "udc reset timeout!\n"); return -ETIMEDOUT; } cpu_relax(); @@ -309,7 +309,7 @@ static int dr_controller_setup(struct fsl_udc *udc) tmp &= USB_EP_LIST_ADDRESS_MASK; fsl_writel(tmp, &dr_regs->endpointlistaddr); - dev_vdbg(&udc->gadget.dev, + dev_vdbg(udc->dev, "vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x\n", udc->ep_qh, (int)tmp, fsl_readl(&dr_regs->endpointlistaddr)); @@ -500,7 +500,7 @@ static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num, tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; break; default: - dev_vdbg(&udc->gadget.dev, "error ep type is %d\n", ep_type); + dev_vdbg(udc->dev, "error ep type is %d\n", ep_type); return; } if (zlt) @@ -613,7 +613,7 @@ static int fsl_ep_enable(struct usb_ep *_ep, spin_unlock_irqrestore(&udc->lock, flags); retval = 0; - dev_vdbg(&udc->gadget.dev, "enabled %s (ep%d%s) maxpacket %d\n", + dev_vdbg(udc->dev, "enabled %s (ep%d%s) maxpacket %d\n", ep->ep.name, ep->ep.desc->bEndpointAddress & 0x0f, (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", max); @@ -634,13 +634,8 @@ static int fsl_ep_disable(struct usb_ep *_ep) int ep_num; ep = container_of(_ep, struct fsl_ep, ep); - if (!_ep || !ep->ep.desc) { - /* - * dev_vdbg(&udc->gadget.dev, "%s not enabled\n", - * _ep ? ep->ep.name : NULL); - */ + if (!_ep || !ep->ep.desc) return -EINVAL; - } /* disable ep on controller */ ep_num = ep_index(ep); @@ -664,7 +659,7 @@ static int fsl_ep_disable(struct usb_ep *_ep) ep->stopped = 1; spin_unlock_irqrestore(&udc->lock, flags); - dev_vdbg(&udc->gadget.dev, "disabled %s OK\n", _ep->name); + dev_vdbg(udc->dev, "disabled %s OK\n", _ep->name); return 0; } @@ -724,9 +719,6 @@ static void fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req) { u32 temp, bitmask, tmp_stat; - /* dev_vdbg(&udc->gadget.dev, "QH addr Register 0x%8x\n", dr_regs->endpointlistaddr); - dev_vdbg(&udc->gadget.dev, "ep_qh[%d] addr is 0x%8x\n", i, (u32)&(ep->udc->ep_qh[i])); */ - bitmask = ep_is_in(ep) ? (1 << (ep_index(ep) + 16)) : (1 << (ep_index(ep))); @@ -813,7 +805,7 @@ static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, *is_last = 0; if ((*is_last) == 0) - dev_vdbg(&udc_controller->gadget.dev, "multi-dtd request!\n"); + dev_vdbg(udc_controller->dev, "multi-dtd request!\n"); /* Fill in the transfer size; set active bit */ swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); @@ -825,7 +817,7 @@ static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, mb(); - dev_vdbg(&udc_controller->gadget.dev, "length = %d address= 0x%x\n", *length, (int)*dma); + dev_vdbg(udc_controller->dev, "length = %d address= 0x%x\n", *length, (int)*dma); return dtd; } @@ -876,11 +868,11 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) /* catch various bogus parameters */ if (!_req || !req->req.complete || !req->req.buf || !list_empty(&req->queue)) { - dev_vdbg(&udc->gadget.dev, "%s, bad params\n", __func__); + dev_vdbg(udc->dev, "%s, bad params\n", __func__); return -EINVAL; } if (unlikely(!ep->ep.desc)) { - dev_vdbg(&udc->gadget.dev, "%s, bad ep\n", __func__); + dev_vdbg(udc->dev, "%s, bad ep\n", __func__); return -EINVAL; } if (usb_endpoint_xfer_isoc(ep->ep.desc)) { @@ -1040,7 +1032,7 @@ static int fsl_ep_set_halt(struct usb_ep *_ep, int value) udc->ep0_dir = 0; } out: - dev_vdbg(&udc->gadget.dev, "%s %s halt stat %d\n", ep->ep.name, + dev_vdbg(udc->dev, "%s %s halt stat %d\n", ep->ep.name, value ? "set" : "clear", status); return status; @@ -1109,7 +1101,7 @@ static void fsl_ep_fifo_flush(struct usb_ep *_ep) /* Wait until flush complete */ while (fsl_readl(&dr_regs->endptflush)) { if (time_after(jiffies, timeout)) { - dev_err(&udc_controller->gadget.dev, + dev_err(udc_controller->dev, "ep flush timeout\n"); return; } @@ -1182,7 +1174,7 @@ static int fsl_vbus_session(struct usb_gadget *gadget, int is_active) udc = container_of(gadget, struct fsl_udc, gadget); spin_lock_irqsave(&udc->lock, flags); - dev_vdbg(&gadget->dev, "VBUS %s\n", str_on_off(is_active)); + dev_vdbg(udc->dev, "VBUS %s\n", str_on_off(is_active)); udc->vbus_active = (is_active != 0); if (can_pullup(udc)) fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), @@ -1548,7 +1540,7 @@ static void ep0_req_complete(struct fsl_udc *udc, struct fsl_ep *ep0, udc->ep0_state = WAIT_FOR_SETUP; break; case WAIT_FOR_SETUP: - dev_err(&udc->gadget.dev, "Unexpected ep0 packets\n"); + dev_err(udc->dev, "Unexpected ep0 packets\n"); break; default: ep0stall(udc); @@ -1617,7 +1609,7 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, errors = hc32_to_cpu(curr_td->size_ioc_sts); if (errors & DTD_ERROR_MASK) { if (errors & DTD_STATUS_HALTED) { - dev_err(&udc->gadget.dev, "dTD error %08x QH=%d\n", errors, pipe); + dev_err(udc->dev, "dTD error %08x QH=%d\n", errors, pipe); /* Clear the errors and Halt condition */ tmp = hc32_to_cpu(curr_qh->size_ioc_int_sts); tmp &= ~errors; @@ -1628,26 +1620,26 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, break; } if (errors & DTD_STATUS_DATA_BUFF_ERR) { - dev_vdbg(&udc->gadget.dev, "Transfer overflow\n"); + dev_vdbg(udc->dev, "Transfer overflow\n"); status = -EPROTO; break; } else if (errors & DTD_STATUS_TRANSACTION_ERR) { - dev_vdbg(&udc->gadget.dev, "ISO error\n"); + dev_vdbg(udc->dev, "ISO error\n"); status = -EILSEQ; break; } else - dev_err(&udc->gadget.dev, + dev_err(udc->dev, "Unknown error has occurred (0x%x)!\n", errors); } else if (hc32_to_cpu(curr_td->size_ioc_sts) & DTD_STATUS_ACTIVE) { - dev_vdbg(&udc->gadget.dev, "Request not complete\n"); + dev_vdbg(udc->dev, "Request not complete\n"); status = REQ_UNCOMPLETE; return status; } else if (remaining_length) { if (direction) { - dev_vdbg(&udc->gadget.dev, + dev_vdbg(udc->dev, "Transmit dTD remaining length not zero\n"); status = -EPROTO; break; @@ -1655,8 +1647,7 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, break; } } else { - dev_vdbg(&udc->gadget.dev, - "dTD transmitted successful\n"); + dev_vdbg(udc->dev, "dTD transmitted successful\n"); } if (j != curr_req->dtd_count - 1) @@ -1699,7 +1690,7 @@ static void dtd_complete_irq(struct fsl_udc *udc) /* If the ep is configured */ if (!curr_ep->ep.name) { - dev_warn(&udc->gadget.dev, "Invalid EP?\n"); + dev_warn(udc->dev, "Invalid EP?\n"); continue; } @@ -1708,7 +1699,7 @@ static void dtd_complete_irq(struct fsl_udc *udc) queue) { status = process_ep_req(udc, i, curr_req); - dev_vdbg(&udc->gadget.dev, + dev_vdbg(udc->dev, "status of process_ep_req= %d, ep = %d\n", status, ep_num); if (status == REQ_UNCOMPLETE) @@ -1829,7 +1820,7 @@ static void reset_irq(struct fsl_udc *udc) while (fsl_readl(&dr_regs->endpointprime)) { /* Wait until all endptprime bits cleared */ if (time_after(jiffies, timeout)) { - dev_err(&udc->gadget.dev, "Timeout for reset\n"); + dev_err(udc->dev, "Timeout for reset\n"); break; } cpu_relax(); @@ -1839,7 +1830,7 @@ static void reset_irq(struct fsl_udc *udc) fsl_writel(0xffffffff, &dr_regs->endptflush); if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) { - dev_vdbg(&udc->gadget.dev, "Bus reset\n"); + dev_vdbg(udc->dev, "Bus reset\n"); /* Bus is reseting */ udc->bus_reset = 1; /* Reset all the queues, include XD, dTD, EP queue @@ -1847,7 +1838,7 @@ static void reset_irq(struct fsl_udc *udc) reset_queues(udc, true); udc->usb_state = USB_STATE_DEFAULT; } else { - dev_vdbg(&udc->gadget.dev, "Controller reset\n"); + dev_vdbg(udc->dev, "Controller reset\n"); /* initialize usb hw reg except for regs for EP, not * touch usbintr reg */ dr_controller_setup(udc); @@ -1881,7 +1872,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* Clear notification bits */ fsl_writel(irq_src, &dr_regs->usbsts); - /* dev_vdbg(&udc->gadget.dev, "irq_src [0x%8x]", irq_src); */ + /* dev_vdbg(udc->dev, "irq_src [0x%8x]", irq_src); */ /* Need to resume? */ if (udc->usb_state == USB_STATE_SUSPENDED) @@ -1890,7 +1881,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* USB Interrupt */ if (irq_src & USB_STS_INT) { - dev_vdbg(&udc->gadget.dev, "Packet int\n"); + dev_vdbg(udc->dev, "Packet int\n"); /* Setup package, we only support ep0 as control ep */ if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) { tripwire_handler(udc, 0, @@ -1919,7 +1910,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* Reset Received */ if (irq_src & USB_STS_RESET) { - dev_vdbg(&udc->gadget.dev, "reset int\n"); + dev_vdbg(udc->dev, "reset int\n"); reset_irq(udc); status = IRQ_HANDLED; } @@ -1931,7 +1922,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) } if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { - dev_vdbg(&udc->gadget.dev, "Error IRQ %x\n", irq_src); + dev_vdbg(udc->dev, "Error IRQ %x\n", irq_src); } spin_unlock_irqrestore(&udc->lock, flags); @@ -1967,7 +1958,7 @@ static int fsl_udc_start(struct usb_gadget *g, udc_controller->transceiver->otg, &udc_controller->gadget); if (retval < 0) { - dev_err(&udc_controller->gadget.dev, "can't bind to transceiver\n"); + dev_err(udc_controller->dev, "can't bind to transceiver\n"); udc_controller->driver = NULL; return retval; } @@ -2252,7 +2243,7 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->eps = kzalloc_objs(struct fsl_ep, udc->max_ep); if (!udc->eps) { - dev_err(&udc->gadget.dev, "kmalloc udc endpoint status failed\n"); + dev_err(udc->dev, "kmalloc udc endpoint status failed\n"); goto eps_alloc_failed; } @@ -2267,7 +2258,7 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->ep_qh = dma_alloc_coherent(&pdev->dev, size, &udc->ep_qh_dma, GFP_KERNEL); if (!udc->ep_qh) { - dev_err(&udc->gadget.dev, "malloc QHs for udc failed\n"); + dev_err(udc->dev, "malloc QHs for udc failed\n"); goto ep_queue_alloc_failed; } @@ -2278,14 +2269,14 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), struct fsl_req, req); if (!udc->status_req) { - dev_err(&udc->gadget.dev, "kzalloc for udc status request failed\n"); + dev_err(udc->dev, "kzalloc for udc status request failed\n"); goto udc_status_alloc_failed; } /* allocate a small amount of memory to get valid address */ udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); if (!udc->status_req->req.buf) { - dev_err(&udc->gadget.dev, "kzalloc for udc request buffer failed\n"); + dev_err(udc->dev, "kzalloc for udc request buffer failed\n"); goto udc_req_buf_alloc_failed; } @@ -2373,6 +2364,7 @@ static int fsl_udc_probe(struct platform_device *pdev) if (udc_controller == NULL) return -ENOMEM; + udc_controller->dev = &pdev->dev; pdata = dev_get_platdata(&pdev->dev); udc_controller->pdata = pdata; spin_lock_init(&udc_controller->lock); @@ -2382,7 +2374,7 @@ static int fsl_udc_probe(struct platform_device *pdev) if (pdata->operating_mode == FSL_USB2_DR_OTG) { udc_controller->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); if (IS_ERR_OR_NULL(udc_controller->transceiver)) { - dev_err(&udc_controller->gadget.dev, "Can't find OTG driver!\n"); + dev_err(&pdev->dev, "Can't find OTG driver!\n"); ret = -ENODEV; goto err_kfree; } @@ -2398,7 +2390,7 @@ static int fsl_udc_probe(struct platform_device *pdev) if (pdata->operating_mode == FSL_USB2_DR_DEVICE) { if (!request_mem_region(res->start, resource_size(res), driver_name)) { - dev_err(&udc_controller->gadget.dev, "request mem region for %s failed\n", pdev->name); + dev_err(&pdev->dev, "failed to request mem region\n"); ret = -EBUSY; goto err_kfree; } @@ -2429,7 +2421,7 @@ static int fsl_udc_probe(struct platform_device *pdev) /* Read Device Controller Capability Parameters register */ dccparams = fsl_readl(&dr_regs->dccparams); if (!(dccparams & DCCPARAMS_DC)) { - dev_err(&udc_controller->gadget.dev, "This SOC doesn't support device role\n"); + dev_err(&pdev->dev, "This SOC doesn't support device role\n"); ret = -ENODEV; goto err_exit; } @@ -2447,14 +2439,14 @@ static int fsl_udc_probe(struct platform_device *pdev) ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, driver_name, udc_controller); if (ret != 0) { - dev_err(&udc_controller->gadget.dev, "cannot request irq %d err %d\n", + dev_err(&pdev->dev, "cannot request irq %d err %d\n", udc_controller->irq, ret); goto err_exit; } /* Initialize the udc structure including QH member and other member */ if (struct_udc_setup(udc_controller, pdev)) { - dev_err(&udc_controller->gadget.dev, "Can't initialize udc data structure\n"); + dev_err(&pdev->dev, "Can't initialize udc data structure\n"); ret = -ENOMEM; goto err_free_irq; } diff --git a/drivers/usb/gadget/udc/fsl_usb2_udc.h b/drivers/usb/gadget/udc/fsl_usb2_udc.h index cc1756f3e89d..53922bc58ca0 100644 --- a/drivers/usb/gadget/udc/fsl_usb2_udc.h +++ b/drivers/usb/gadget/udc/fsl_usb2_udc.h @@ -470,6 +470,7 @@ struct fsl_ep { #define EP_DIR_OUT 0 struct fsl_udc { + struct device *dev; struct usb_gadget gadget; struct usb_gadget_driver *driver; struct fsl_usb2_platform_data *pdata; From f05847a216a74e1f2387169a336edbc198960016 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 2 Jul 2026 16:15:36 +0200 Subject: [PATCH 018/456] USB: gadget: fsl-udc: drop misleading unbind sanity check The UDC pointer is set on successful probe and will never be NULL when the driver is later unbound so drop the misleading sanity check (and confused error message). Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260702141536.90887-5-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/fsl_udc_core.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index c4761933ec86..20392409afb1 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2539,12 +2539,6 @@ static void fsl_udc_remove(struct platform_device *pdev) DECLARE_COMPLETION_ONSTACK(done); - if (!udc_controller) { - dev_err(&pdev->dev, - "Driver still in use but removing anyhow\n"); - return; - } - udc_controller->done = &done; usb_del_gadget_udc(&udc_controller->gadget); From 1febec7e47cdcd01f43fb0211094e3010474666e Mon Sep 17 00:00:00 2001 From: Sonali Pradhan Date: Fri, 3 Jul 2026 08:37:24 +0000 Subject: [PATCH 019/456] usb: gadget: f_ncm: validate datagram bounds in ncm_unwrap_ntb() When unpacking host-supplied NTBs, ncm_unwrap_ntb() checks datagram length against frame_max but does not verify that the datagram fits within the declared block length. Additionally, when decoding multiple NTBs from a single socket buffer, subsequent block lengths are not checked against the actual remaining buffer data. With these checks missing, a malicious USB host can specify datagram offsets and lengths that point beyond the block, or supply secondary NTB headers declaring lengths larger than the buffer. skb_put_data() then copies adjacent kernel memory from skb_shared_info into the network skb. Fix this by verifying that sufficient buffer space remains for the NTB header before parsing, handling zero-length block declarations, ensuring that block lengths never exceed the remaining buffer space, and verifying that each datagram payload stays strictly within the block boundary. Fixes: 427694cfaafa ("usb: gadget: ncm: Handle decoding of multiple NTB's in unwrap call") Fixes: 2b74b0a04d3e ("USB: gadget: f_ncm: add bounds checks to ncm_unwrap_ntb()") Cc: stable Assisted-by: Jetski:Gemini-2.5-Pro Signed-off-by: Sonali Pradhan Link: https://patch.msgid.link/20260703083725.1903850-1-sonalipradhan@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index c5bf8a448d64..64eabda2f546 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -1189,6 +1189,10 @@ static int ncm_unwrap_ntb(struct gether *port, frame_max = ncm_opts->max_segment_size; parse_ntb: + if (to_process < (int)opts->nth_size) { + INFO(port->func.config->cdev, "Packet too small for headers\n"); + goto err; + } tmp = (__le16 *)ntb_ptr; /* dwSignature */ @@ -1209,8 +1213,12 @@ static int ncm_unwrap_ntb(struct gether *port, tmp++; /* skip wSequence */ block_len = get_ncm(&tmp, opts->block_length); + if (block_len == 0) + block_len = to_process; + /* (d)wBlockLength */ - if ((block_len < opts->nth_size + opts->ndp_size) || (block_len > ntb_max)) { + if ((block_len < opts->nth_size + opts->ndp_size) || (block_len > ntb_max) || + (block_len > to_process)) { INFO(port->func.config->cdev, "Bad block length: %#X\n", block_len); goto err; } @@ -1273,7 +1281,7 @@ static int ncm_unwrap_ntb(struct gether *port, index = index2; /* wDatagramIndex[0] */ if ((index < opts->nth_size) || - (index > block_len - opts->dpe_size)) { + (index > block_len)) { INFO(port->func.config->cdev, "Bad index: %#X\n", index); goto err; @@ -1285,7 +1293,8 @@ static int ncm_unwrap_ntb(struct gether *port, * ethernet hdr + crc or larger than max frame size */ if ((dg_len < 14 + crc_len) || - (dg_len > frame_max)) { + (dg_len > frame_max) || + (dg_len > block_len - index)) { INFO(port->func.config->cdev, "Bad dgram length: %#X\n", dg_len); goto err; @@ -1310,7 +1319,7 @@ static int ncm_unwrap_ntb(struct gether *port, dg_len2 = get_ncm(&tmp, opts->dgram_item_len); /* wDatagramIndex[1] */ - if (index2 > block_len - opts->dpe_size) { + if (index2 > block_len) { INFO(port->func.config->cdev, "Bad index: %#X\n", index2); goto err; From 7aa7d4bf9d3fa9a6a47b640ad103ab433b7ff261 Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Tue, 7 Jul 2026 14:17:36 +0000 Subject: [PATCH 020/456] usb: typec: ucsi: Fix race condition and ordering in port unregistration A synchronization issue exists during port unregistration where pending partner work items can race against workqueue destruction, leading to use-after-free conditions: cros_ec_ucsi cros_ec_ucsi.3.auto: error -ETIMEDOUT: PPM init failed BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: 0010:__queue_work+0x83/0x4a0 Call Trace: __cfi_delayed_work_timer_fn+0x10/0x10 run_timer_softirq+0x3b6/0xbd0 sched_clock_cpu+0xc/0x110 irq_exit_rcu+0x18d/0x330 fred_sysvec_apic_timer_interrupt+0x5e/0x80 Fix this by ensuring strict ordering and proper serialization during teardown: 1. Move ucsi_unregister_partner() to the beginning of the teardown sequence and protect it under the connector mutex lock. 2. Ensure all pending partner tasks are explicitly flushed and finished before the workqueue is destroyed. 3. Switch from mod_delayed_work() to a cancel_delayed_work() and queue_delayed_work() sequence. This guarantees that items currently marked as pending won't be scheduled an additional time, preventing a double release of resources which leads to the following crash: Oops: general protection fault, probably for non-canonical address 0xdead000000000122: 0000 [#1] SMP NOPTI Workqueue: cros_ec_ucsi.3.auto-con2 ucsi_poll_worker RIP: 0010:ucsi_poll_worker+0x65/0x1e0 Call Trace: process_scheduled_works+0x218/0x6d0 worker_thread+0x188/0x3f0 __cfi_worker_thread+0x10/0x10 kthread+0x226/0x2a0 To ensure these rules are applied identically across both the normal teardown and the ucsi_init() error paths, consolidate the cleanup logic into a new helper, ucsi_unregister_port(). Cc: stable Fixes: b9aa02ca39a4 ("usb: typec: ucsi: Add polling mechanism for partner tasks like alt mode checking") Fixes: b13abcb7ddd8 ("usb: typec: ucsi: Fix NULL pointer access") Fixes: fac4b8633fd6 ("usb: ucsi: Ensure connector delayed work items are flushed") Signed-off-by: Andrei Kuchynski Reviewed-by: Benson Leung Link: https://patch.msgid.link/20260707141736.1635698-1-akuchynski@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 82 +++++++++++++++++------------------ 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 6a6723e8fb12..f56897e23df8 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1845,6 +1845,42 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con) return ret; } +static void ucsi_unregister_port(struct ucsi_connector *con) +{ + struct ucsi_work *uwork; + + if (con->wq) { + mutex_lock(&con->lock); + ucsi_unregister_partner(con); + /* + * queue delayed items immediately so they can execute + * and free themselves before the wq is destroyed + */ + list_for_each_entry(uwork, &con->partner_tasks, node) { + if (cancel_delayed_work(&uwork->work)) + queue_delayed_work(con->wq, &uwork->work, 0); + } + mutex_unlock(&con->lock); + + destroy_workqueue(con->wq); + con->wq = NULL; + } else { + ucsi_unregister_partner(con); + } + + ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); + ucsi_unregister_port_psy(con); + + usb_power_delivery_unregister_capabilities(con->port_sink_caps); + con->port_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(con->port_source_caps); + con->port_source_caps = NULL; + usb_power_delivery_unregister(con->pd); + con->pd = NULL; + typec_unregister_port(con->port); + con->port = NULL; +} + static u64 ucsi_get_supported_notifications(struct ucsi *ucsi) { u16 features = ucsi->cap.features; @@ -1971,22 +2007,8 @@ static int ucsi_init(struct ucsi *ucsi) for (i = 0; i < ucsi->cap.num_connectors; i++) lockdep_unregister_key(&connector[i].lock_key); - for (con = connector; con->port; con++) { - if (con->wq) - destroy_workqueue(con->wq); - ucsi_unregister_partner(con); - ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); - ucsi_unregister_port_psy(con); - - usb_power_delivery_unregister_capabilities(con->port_sink_caps); - con->port_sink_caps = NULL; - usb_power_delivery_unregister_capabilities(con->port_source_caps); - con->port_source_caps = NULL; - usb_power_delivery_unregister(con->pd); - con->pd = NULL; - typec_unregister_port(con->port); - con->port = NULL; - } + for (con = connector; con->port; con++) + ucsi_unregister_port(con); kfree(connector); err_reset: memset(&ucsi->cap, 0, sizeof(ucsi->cap)); @@ -2214,33 +2236,7 @@ void ucsi_unregister(struct ucsi *ucsi) for (i = 0; i < ucsi->cap.num_connectors; i++) { cancel_work_sync(&ucsi->connector[i].work); - - if (ucsi->connector[i].wq) { - struct ucsi_work *uwork; - - mutex_lock(&ucsi->connector[i].lock); - /* - * queue delayed items immediately so they can execute - * and free themselves before the wq is destroyed - */ - list_for_each_entry(uwork, &ucsi->connector[i].partner_tasks, node) - mod_delayed_work(ucsi->connector[i].wq, &uwork->work, 0); - mutex_unlock(&ucsi->connector[i].lock); - destroy_workqueue(ucsi->connector[i].wq); - } - - ucsi_unregister_partner(&ucsi->connector[i]); - ucsi_unregister_altmodes(&ucsi->connector[i], - UCSI_RECIPIENT_CON); - ucsi_unregister_port_psy(&ucsi->connector[i]); - - usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_sink_caps); - ucsi->connector[i].port_sink_caps = NULL; - usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_source_caps); - ucsi->connector[i].port_source_caps = NULL; - usb_power_delivery_unregister(ucsi->connector[i].pd); - ucsi->connector[i].pd = NULL; - typec_unregister_port(ucsi->connector[i].port); + ucsi_unregister_port(&ucsi->connector[i]); lockdep_unregister_key(&ucsi->connector[i].lock_key); } From 0583f2fbf8f86ae3a0ce054f96783dd83e65d9bb Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 9 Jul 2026 02:09:04 +0000 Subject: [PATCH 021/456] usb: gadget: udc: bdc: free IRQ and drain func_wake_notify before teardown The Broadcom BDC UDC driver registers its IRQ handler with devm_request_irq() in bdc_udc_init(), so the IRQ is released by devm only after bdc_remove() returns. devm releases resources in reverse LIFO order, but bdc_remove() runs bdc_udc_exit() and bdc_hw_exit() -> bdc_mem_free() manually before returning: bdc_udc_exit() tears down individual endpoint objects via bdc_free_ep(), while bdc_hw_exit() -> bdc_mem_free() frees and NULLs the DMA-coherent status-report ring (bdc->srr.sr_bds) and kfree()s bdc->bdc_ep_array. Both happen while the IRQ handler (bdc_udc_interrupt, requested with IRQF_SHARED) remains deliverable in the window up to the post-remove devm free_irq(). On receipt of a shared interrupt in that window, bdc_udc_interrupt() dereferences bdc->srr.sr_bds[bdc->srr.dqp_index] (NULL or freed DMA) and dispatches sr_handler callbacks that index into bdc_ep_array, causing a NULL-deref or use-after-free. The same window affects the delayed_work bdc->func_wake_notify, which is armed from the IRQ handler via bdc_sr_uspc() -> handle_link_state_change() -> schedule_delayed_work() and may self-rearm from its own callback bdc_func_wake_timer(). No cancel exists anywhere in the driver, so a queued work item that fires after bdc_remove() returns and the bdc structure is devm-freed dereferences freed memory. Replace devm_request_irq() with request_irq() and add an explicit free_irq(bdc->irq, bdc) in bdc_remove(). Clear BDC_GIE before free_irq() to stop the device from asserting interrupts, then free_irq() drains any in-flight handler, then cancel_delayed_work_sync() drains the func_wake_notify delayed work. This ordering ensures the IRQ handler and delayed work cannot interfere with the subsequent endpoint and DMA teardown in bdc_udc_exit() and bdc_hw_exit(). Wire the matching free_irq() into the bdc_udc_init() error path so the IRQ is released on probe failure, and route the bdc_init_ep() failure through err0 instead of returning directly. This issue was found by an in-house static analysis tool. Fixes: efed421a94e6 ("usb: gadget: Add UDC driver for Broadcom USB3.0 device controller IP BDC") Cc: stable Assisted-by: Codex:gpt-5.5 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260709020904.502611-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_core.c | 20 ++++++++++++++++++++ drivers/usb/gadget/udc/bdc/bdc_udc.c | 7 ++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index 438201dc96ca..a8dbaef54aba 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -585,9 +585,29 @@ static int bdc_probe(struct platform_device *pdev) static void bdc_remove(struct platform_device *pdev) { struct bdc *bdc; + unsigned long flags; + u32 temp; bdc = platform_get_drvdata(pdev); dev_dbg(bdc->dev, "%s ()\n", __func__); + /* + * Disable the device interrupt source before freeing the IRQ: + * clear BDC_GIE so the controller stops asserting interrupts, + * then free_irq drains any in-flight handler. + */ + spin_lock_irqsave(&bdc->lock, flags); + temp = bdc_readl(bdc->regs, BDC_BDCSC); + temp &= ~BDC_GIE; + bdc_writel(bdc->regs, BDC_BDCSC, temp); + spin_unlock_irqrestore(&bdc->lock, flags); + free_irq(bdc->irq, bdc); + /* + * Drain func_wake_notify after free_irq: the IRQ handler arms this + * delayed_work via bdc_sr_uspc -> handle_link_state_change -> + * schedule_delayed_work (self-rearmed in bdc_func_wake_timer), so + * the IRQ must be released first to prevent re-arm after cancel. + */ + cancel_delayed_work_sync(&bdc->func_wake_notify); bdc_udc_exit(bdc); bdc_hw_exit(bdc); bdc_phy_exit(bdc); diff --git a/drivers/usb/gadget/udc/bdc/bdc_udc.c b/drivers/usb/gadget/udc/bdc/bdc_udc.c index 23826fd7a8e6..7a12219edac6 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_udc.c +++ b/drivers/usb/gadget/udc/bdc/bdc_udc.c @@ -530,8 +530,8 @@ int bdc_udc_init(struct bdc *bdc) bdc->gadget.name = BRCM_BDC_NAME; - ret = devm_request_irq(bdc->dev, bdc->irq, bdc_udc_interrupt, - IRQF_SHARED, BRCM_BDC_NAME, bdc); + ret = request_irq(bdc->irq, bdc_udc_interrupt, IRQF_SHARED, + BRCM_BDC_NAME, bdc); if (ret) { dev_err(bdc->dev, "failed to request irq #%d %d\n", @@ -542,7 +542,7 @@ int bdc_udc_init(struct bdc *bdc) ret = bdc_init_ep(bdc); if (ret) { dev_err(bdc->dev, "bdc init ep fail: %d\n", ret); - return ret; + goto err0; } ret = usb_add_gadget_udc(bdc->dev, &bdc->gadget); @@ -571,6 +571,7 @@ int bdc_udc_init(struct bdc *bdc) err1: usb_del_gadget_udc(&bdc->gadget); err0: + free_irq(bdc->irq, bdc); bdc_free_ep(bdc); return ret; From 5650c18d93a1db7e27cb5a40b394747eb4686d5b Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 9 Jul 2026 15:07:17 +0000 Subject: [PATCH 022/456] usb: gadget: f_midi: cancel pending IN work before freeing the midi object The f_midi driver embeds a work item (midi->work) whose handler, f_midi_in_work(), dereferences the enclosing struct f_midi through container_of(). This work is armed from two sites: f_midi_complete(), on a normal IN-endpoint completion, and f_midi_in_trigger(), on an ALSA rawmidi output-stream start. Neither f_midi_disable() nor f_midi_unbind() cancels midi->work. f_midi_disable() only disables the endpoints and drains the in_req_fifo; it does not synchronize the work item, and the sound card is released asynchronously to the final free of the midi object. The midi object is reference-counted (midi->free_ref) and is freed in f_midi_free() only once both the usb_function reference and the rawmidi private_data reference have been dropped. In f_midi_unbind(), f_midi_disable() runs before the sound card is released, so while the USB endpoints are already disabled the rawmidi device is still usable by an open substream. A concurrent userspace write on such a substream can reach f_midi_in_trigger() and queue midi->work again after f_midi_disable() has returned. A work item armed this way may still be pending when the last reference drops and f_midi_free() proceeds to kfree(midi), letting f_midi_in_work() dereference the struct after it has been freed, a use-after-free. For this reason cancelling midi->work in f_midi_disable() would not be sufficient: the ALSA trigger path can rearm the work after disable() returns. Cancelling at the refcount-zero free site is the boundary after which neither arming source can survive, because by then both references that keep the midi object alive have been dropped: the USB endpoints are already disabled and the rawmidi device has been released. Fix this by calling cancel_work_sync(&midi->work) in the refcount-zero block of f_midi_free(), before the embedded work_struct is freed along with the rest of the structure. opts->lock is a sleeping mutex, so calling cancel_work_sync() under it is permitted, and the handler takes midi->transmit_lock rather than opts->lock, so no self-deadlock can occur while it waits for a running instance of the work to finish. This issue was found by an in-house static analysis tool. Fixes: 8653d71ce3763 ("usb/gadget: f_midi: Replace tasklet with work") Cc: stable Assisted-by: Codex:gpt-5.5 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260709150717.399083-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 4d9e4bd700d8..fba8cf787d6c 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -1309,6 +1309,7 @@ static void f_midi_free(struct usb_function *f) opts = container_of(f->fi, struct f_midi_opts, func_inst); mutex_lock(&opts->lock); if (!--midi->free_ref) { + cancel_work_sync(&midi->work); kfree(midi->id); kfifo_free(&midi->in_req_fifo); kfree(midi); From c2e819be6a5c7f34344926b4bd7e3dfca58cf48a Mon Sep 17 00:00:00 2001 From: Melbin K Mathew Date: Thu, 9 Jul 2026 21:56:22 +0100 Subject: [PATCH 023/456] usb: gadget: printer: fix infinite loop in printer_read() printer_read() uses the same variable for the requested copy size and the number of bytes actually copied to user space. copy_to_user() returns the number of bytes not copied, so when it fails to copy anything, the computed copied length becomes zero. In that case len, buf, current_rx_bytes and current_rx_buf are left unchanged. If RX data is available and the user buffer remains unwritable, the read loop can repeat indefinitely. Track the copied length separately and return -EFAULT, or the number of bytes already copied, if an iteration makes no progress. Fixes: b185f01a9ab7 ("usb: gadget: printer: factor out f_printer") Cc: stable Reviewed-by: Peter Chen Signed-off-by: Melbin K Mathew Link: https://patch.msgid.link/20260709205622.55700-1-mlbnkm1@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_printer.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/function/f_printer.c b/drivers/usb/gadget/function/f_printer.c index 837f753d0cae..1857d786110b 100644 --- a/drivers/usb/gadget/function/f_printer.c +++ b/drivers/usb/gadget/function/f_printer.c @@ -431,7 +431,7 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) { struct printer_dev *dev = fd->private_data; unsigned long flags; - size_t size; + size_t size, not_copied, copied; size_t bytes_copied; struct usb_request *req; /* This is a pointer to the current USB rx request. */ @@ -524,10 +524,12 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) else size = len; - size -= copy_to_user(buf, current_rx_buf, size); - bytes_copied += size; - len -= size; - buf += size; + not_copied = copy_to_user(buf, current_rx_buf, size); + copied = size - not_copied; + + bytes_copied += copied; + len -= copied; + buf += copied; spin_lock_irqsave(&dev->lock, flags); @@ -542,6 +544,17 @@ printer_read(struct file *fd, char __user *buf, size_t len, loff_t *ptr) if (dev->interface < 0) goto out_disabled; + if (!copied) { + dev->current_rx_req = current_rx_req; + dev->current_rx_bytes = current_rx_bytes; + dev->current_rx_buf = current_rx_buf; + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->lock_printer_io); + return bytes_copied ? bytes_copied : -EFAULT; + } + + size = copied; + /* If we not returning all the data left in this RX request * buffer then adjust the amount of data left in the buffer. * Othewise if we are done with this RX request buffer then From 42a97c0480f96a2977e6d51ce512adc780f1ef5d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 8 Jul 2026 16:31:35 +0200 Subject: [PATCH 024/456] USB: serial: keyspan_pda: fix data loss on receive throttling Killing the interrupt-in urb when the line disciple requests throttling may lead to data loss if an ongoing transfer is cancelled. Instead set a flag to prevent the completion handler from resubmitting the urb until the port is unthrottled. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 44 +++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index f05bcce60600..dd4cfd17f7ad 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -33,6 +33,8 @@ struct keyspan_pda_private { struct work_struct unthrottle_work; struct usb_serial *serial; struct usb_serial_port *port; + bool throttled; + bool throttle_req; }; static int keyspan_pda_write_start(struct usb_serial_port *port); @@ -148,6 +150,7 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) int retval; int status = urb->status; struct keyspan_pda_private *priv; + bool throttled = false; unsigned long flags; priv = usb_get_serial_port_data(port); @@ -209,16 +212,24 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) } exit: - retval = usb_submit_urb(urb, GFP_ATOMIC); - if (retval) - dev_err(&port->dev, - "%s - usb_submit_urb failed with result %d\n", - __func__, retval); + spin_lock_irqsave(&port->lock, flags); + if (priv->throttle_req) { + priv->throttled = true; + throttled = true; + } + spin_unlock_irqrestore(&port->lock, flags); + + if (!throttled) { + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&port->dev, "failed to resubmit in urb: %d\n", retval); + } } static void keyspan_pda_rx_throttle(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); /* * Stop receiving characters. We just turn off the URB request, and @@ -228,16 +239,29 @@ static void keyspan_pda_rx_throttle(struct tty_struct *tty) * send an XOFF, although it might make sense to foist that off upon * the device too. */ - usb_kill_urb(port->interrupt_in_urb); + spin_lock_irq(&port->lock); + priv->throttle_req = true; + spin_unlock_irq(&port->lock); } static void keyspan_pda_rx_unthrottle(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + bool throttled; + int ret; - /* just restart the receive interrupt URB */ - if (usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL)) - dev_dbg(&port->dev, "usb_submit_urb(read urb) failed\n"); + spin_lock_irq(&port->lock); + throttled = priv->throttled; + priv->throttled = false; + priv->throttle_req = false; + spin_unlock_irq(&port->lock); + + if (throttled) { + ret = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (ret) + dev_err(&port->dev, "failed to submit in urb: %d\n", ret); + } } static speed_t keyspan_pda_setbaud(struct usb_serial *serial, speed_t baud) @@ -577,6 +601,8 @@ static int keyspan_pda_open(struct tty_struct *tty, spin_lock_irq(&port->lock); priv->tx_room = rc; + priv->throttled = false; + priv->throttle_req = false; spin_unlock_irq(&port->lock); rc = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); From fad0fd120e29041b3e6cdf41bb12e3184fb524a2 Mon Sep 17 00:00:00 2001 From: Tim Pambor Date: Sat, 11 Jul 2026 17:36:30 +0000 Subject: [PATCH 025/456] USB: serial: ftdi_sio: add support for E+H FXA291 The Commubox FXA291 by Endress+Hauser AG is a USB serial converter based on FT232B which is used to communicate with field devices. It enumerates using the FTDI vendor ID and a custom PID. usb 1-9: New USB device found, idVendor=0403, idProduct=e510, bcdDevice= 4.00 usb 1-9: New USB device strings: Mfr=1, Product=2, SerialNumber=0 usb 1-9: Product: FXA291 usb 1-9: Manufacturer: Endress+Hauser usb 1-9: SerialNumber: 00000000 ftdi_sio 1-9:1.0: FTDI USB Serial Device converter detected usb 1-9: Detected FT232B usb 1-9: FTDI USB Serial Device converter now attached to ttyUSB0 Signed-off-by: Tim Pambor Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/ftdi_sio.c | 2 ++ drivers/usb/serial/ftdi_sio_ids.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 88dd32da82c2..c6ffa23bcc8f 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1072,6 +1072,8 @@ static const struct usb_device_id id_table_combined[] = { { USB_DEVICE_INTERFACE_NUMBER(ALTERA_VID, ALTERA_UB3_602E_PID, 3) }, /* Abacus Electrics */ { USB_DEVICE(FTDI_VID, ABACUS_OPTICAL_PROBE_PID) }, + /* Endress+Hauser AG devices */ + { USB_DEVICE(FTDI_VID, FTDI_EH_FXA291_PID) }, { } /* Terminating entry */ }; diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h index 6c76cfebfd0e..9c83c17853c8 100644 --- a/drivers/usb/serial/ftdi_sio_ids.h +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -313,6 +313,11 @@ #define FTDI_ELV_UDF77_PID 0xFB5E /* USB DCF Funkuhr (UDF 77) */ #define FTDI_ELV_UIO88_PID 0xFB5F /* USB-I/O Interface (UIO 88) */ +/* + * Endress+Hauser AG product ids (FTDI_VID) + */ +#define FTDI_EH_FXA291_PID 0xE510 + /* * EVER Eco Pro UPS (http://www.ever.com.pl/) */ From d52a13adbb8ccbab99cd3bad36804e87d8b5c052 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Tue, 7 Jul 2026 23:04:54 +0800 Subject: [PATCH 026/456] firewire: net: Fix fragmented datagram reassembly fwnet_frag_new() keeps a sorted list of received fragments for a partial datagram. When a new fragment is adjacent to an existing fragment, the code checks whether the new fragment also closes the gap to the next or previous list entry. Those neighbor lookups currently assume that the current fragment always has a real next or previous fragment. At a list edge, the next or previous entry is the list head, not a struct fwnet_fragment_info. The gap checks also compare against the old edge of the current fragment instead of the edge after adding the new fragment. As a result, a fragment that bridges two existing ranges may leave two adjacent ranges unmerged, so fwnet_pd_is_complete() can miss a complete datagram. Check for the list head before looking up the neighboring fragment, and compare the neighbor against the new fragment's far edge when deciding whether to merge all three ranges. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: c76acec6d551 ("firewire: add IPv4 support") Signed-off-by: Ruoyu Wang Link: https://lore.kernel.org/r/20260707150454.2265951-1-ruoyuw560@gmail.com Signed-off-by: Takashi Sakamoto --- drivers/firewire/net.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/drivers/firewire/net.c b/drivers/firewire/net.c index e5361f4f8bbd..961f1a046025 100644 --- a/drivers/firewire/net.c +++ b/drivers/firewire/net.c @@ -297,31 +297,34 @@ static struct fwnet_fragment_info *fwnet_frag_new( if (fi->offset + fi->len == offset) { /* The new fragment can be tacked on to the end */ /* Did the new fragment plug a hole? */ - fi2 = list_entry(fi->fi_link.next, - struct fwnet_fragment_info, fi_link); - if (fi->offset + fi->len == fi2->offset) { - /* glue fragments together */ - fi->len += len + fi2->len; - list_del(&fi2->fi_link); - kfree(fi2); - } else { - fi->len += len; + if (!list_is_last(&fi->fi_link, &pd->fi_list)) { + fi2 = list_next_entry(fi, fi_link); + if (offset + len == fi2->offset) { + /* glue fragments together */ + fi->len += len + fi2->len; + list_del(&fi2->fi_link); + kfree(fi2); + + return fi; + } } + fi->len += len; return fi; } if (offset + len == fi->offset) { /* The new fragment can be tacked on to the beginning */ /* Did the new fragment plug a hole? */ - fi2 = list_entry(fi->fi_link.prev, - struct fwnet_fragment_info, fi_link); - if (fi2->offset + fi2->len == fi->offset) { - /* glue fragments together */ - fi2->len += fi->len + len; - list_del(&fi->fi_link); - kfree(fi); + if (!list_is_first(&fi->fi_link, &pd->fi_list)) { + fi2 = list_prev_entry(fi, fi_link); + if (fi2->offset + fi2->len == offset) { + /* glue fragments together */ + fi2->len += fi->len + len; + list_del(&fi->fi_link); + kfree(fi); - return fi2; + return fi2; + } } fi->offset = offset; fi->len += len; From 1dba91a0493b7ffe9cbbcf7a8df1b361ddcac515 Mon Sep 17 00:00:00 2001 From: "Harry Yoo (Oracle)" Date: Mon, 13 Jul 2026 23:28:49 +0900 Subject: [PATCH 027/456] mm/slab: fix a memory leak due to bootstrapping sheaves twice When kmalloc caches are aliased, multiple cache pointers reference the same kmem_cache. As a result, iterating over kmalloc indices and bootstrapping sheaves can bootstrap the same cache more than once and leak memory. Currently, this could happen when the architecture specifies minimum alignment for slab caches that is larger than ARCH_KMALLOC_MINALIGN. Bootstrap sheaves only when the cache does not have them already. Add a warning when bootstrap_cache_sheaves() is called for a cache that already has sheaves enabled. Fixes: 913ffd3a1bf5 ("slab: handle kmalloc sheaves bootstrap") Cc: stable@vger.kernel.org Signed-off-by: Harry Yoo (Oracle) Reviewed-by: Suren Baghdasaryan Link: https://patch.msgid.link/20260713-kmalloc-no-objext-v3-1-47c7bd138de7@kernel.org Signed-off-by: Vlastimil Babka (SUSE) --- mm/slub.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 65febe957886..f9461a0c47d3 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -8497,6 +8497,8 @@ static void __init bootstrap_cache_sheaves(struct kmem_cache *s) bool failed = false; int node, cpu; + VM_WARN_ON_ONCE(cache_has_sheaves(s)); + capacity = calculate_sheaf_capacity(s, &empty_args); /* capacity can be 0 due to debugging or SLUB_TINY */ @@ -8548,8 +8550,11 @@ static void __init bootstrap_kmalloc_sheaves(void) for (type = KMALLOC_NORMAL; type <= KMALLOC_PARTITION_END; type++) { for (int idx = 0; idx < KMALLOC_SHIFT_HIGH + 1; idx++) { - if (kmalloc_caches[type][idx]) - bootstrap_cache_sheaves(kmalloc_caches[type][idx]); + struct kmem_cache *s = kmalloc_caches[type][idx]; + + /* Do not bootstrap twice when caches are aliased */ + if (s && !cache_has_sheaves(s)) + bootstrap_cache_sheaves(s); } } } From 982e31382d9a1a3c8c4e6a13702a53711f4efe9f Mon Sep 17 00:00:00 2001 From: "Harry Yoo (Oracle)" Date: Mon, 13 Jul 2026 23:28:50 +0900 Subject: [PATCH 028/456] mm/slab: decouple SLAB_NO_SHEAVES from SLAB_NO_OBJ_EXT Bootstrap caches are created with SLAB_NO_OBJ_EXT to disallow sheaves and obj_exts. To allow disabling obj_exts while allowing sheaves, decouple SLAB_NO_SHEAVES from SLAB_NO_OBJ_EXT. Bootstrap caches now have both SLAB_NO_SHEAVES and SLAB_NO_OBJ_EXT. No functional change intended. Reviewed-by: Vlastimil Babka (SUSE) Signed-off-by: Harry Yoo (Oracle) Reviewed-by: Suren Baghdasaryan Link: https://patch.msgid.link/20260713-kmalloc-no-objext-v3-2-47c7bd138de7@kernel.org Signed-off-by: Vlastimil Babka (SUSE) --- include/linux/slab.h | 13 +++++++++++-- mm/slub.c | 10 ++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 51f03f18c9a7..08d7b6c9c4d6 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -58,10 +58,13 @@ enum _slab_flag_bits { #endif _SLAB_OBJECT_POISON, _SLAB_CMPXCHG_DOUBLE, +#ifdef CONFIG_SLAB_OBJ_EXT _SLAB_NO_OBJ_EXT, -#if defined(CONFIG_SLAB_OBJ_EXT) && defined(CONFIG_64BIT) +#ifdef CONFIG_64BIT _SLAB_OBJ_EXT_IN_OBJ, #endif +#endif + _SLAB_NO_SHEAVES, _SLAB_FLAGS_LAST_BIT }; @@ -239,8 +242,14 @@ enum _slab_flag_bits { #endif #define SLAB_TEMPORARY SLAB_RECLAIM_ACCOUNT /* Objects are short-lived */ -/* Slab created using create_boot_cache */ +/* Slab caches without obj_exts array */ +#ifdef CONFIG_SLAB_OBJ_EXT #define SLAB_NO_OBJ_EXT __SLAB_FLAG_BIT(_SLAB_NO_OBJ_EXT) +#else +#define SLAB_NO_OBJ_EXT __SLAB_FLAG_UNUSED +#endif + +#define SLAB_NO_SHEAVES __SLAB_FLAG_BIT(_SLAB_NO_SHEAVES) #if defined(CONFIG_SLAB_OBJ_EXT) && defined(CONFIG_64BIT) #define SLAB_OBJ_EXT_IN_OBJ __SLAB_FLAG_BIT(_SLAB_OBJ_EXT_IN_OBJ) diff --git a/mm/slub.c b/mm/slub.c index f9461a0c47d3..abe748b7dddb 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -7799,12 +7799,12 @@ static unsigned int calculate_sheaf_capacity(struct kmem_cache *s, return 0; /* - * Bootstrap caches can't have sheaves for now (SLAB_NO_OBJ_EXT). + * Bootstrap caches can't have sheaves for now (SLAB_NO_SHEAVES). * SLAB_NOLEAKTRACE caches (e.g., kmemleak's object_cache) must not * have sheaves to avoid recursion when sheaf allocation triggers * kmemleak tracking. */ - if (s->flags & (SLAB_NO_OBJ_EXT | SLAB_NOLEAKTRACE)) + if (s->flags & (SLAB_NO_SHEAVES | SLAB_NOLEAKTRACE)) return 0; /* @@ -8586,7 +8586,8 @@ void __init kmem_cache_init(void) create_boot_cache(kmem_cache_node, "kmem_cache_node", sizeof(struct kmem_cache_node), - SLAB_HWCACHE_ALIGN | SLAB_NO_OBJ_EXT, 0, 0); + SLAB_HWCACHE_ALIGN | SLAB_NO_SHEAVES | SLAB_NO_OBJ_EXT, + 0, 0); hotplug_node_notifier(slab_memory_callback, SLAB_CALLBACK_PRI); @@ -8596,7 +8597,8 @@ void __init kmem_cache_init(void) create_boot_cache(kmem_cache, "kmem_cache", offsetof(struct kmem_cache, per_node) + nr_node_ids * sizeof(struct kmem_cache_per_node_ptrs), - SLAB_HWCACHE_ALIGN | SLAB_NO_OBJ_EXT, 0, 0); + SLAB_HWCACHE_ALIGN | SLAB_NO_SHEAVES | SLAB_NO_OBJ_EXT, + 0, 0); kmem_cache = bootstrap(&boot_kmem_cache); kmem_cache_node = bootstrap(&boot_kmem_cache_node); From a37b0066a10aabf3c968b4566706fb866eaf9a85 Mon Sep 17 00:00:00 2001 From: "Harry Yoo (Oracle)" Date: Mon, 13 Jul 2026 23:28:51 +0900 Subject: [PATCH 029/456] lib/alloc_tag: introduce mem_alloc_profiling_permanently_disabled() mem_alloc_profiling_enabled() tells whether memalloc profiling is currently enabled. However, even when this function returns false, it can be enabled later. However, this is not enough. Some optimizations can be applied only when memalloc profiling is permanently disabled. For example, to skip the creation of KMALLOC_NO_OBJ_EXT caches at boot time, mem_profiling must be set to "never", "0" w/ debugging on, or have been shutdown so that it can no longer be enabled. Introduce mem_alloc_profiling_permanently_disabled() for this purpose. Signed-off-by: Harry Yoo (Oracle) Acked-by: Suren Baghdasaryan Link: https://patch.msgid.link/20260713-kmalloc-no-objext-v3-3-47c7bd138de7@kernel.org Signed-off-by: Vlastimil Babka (SUSE) --- include/linux/alloc_tag.h | 3 +++ lib/alloc_tag.c | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/include/linux/alloc_tag.h b/include/linux/alloc_tag.h index 068ba2e77c5d..6ed9f82e639f 100644 --- a/include/linux/alloc_tag.h +++ b/include/linux/alloc_tag.h @@ -134,6 +134,8 @@ static inline bool mem_alloc_profiling_enabled(void) &mem_alloc_profiling_key); } +bool mem_alloc_profiling_permanently_disabled(void); + static inline struct alloc_tag_counters alloc_tag_read(struct alloc_tag *tag) { struct alloc_tag_counters v = { 0, 0 }; @@ -239,6 +241,7 @@ static inline bool alloc_tag_is_inaccurate(struct alloc_tag *tag) #define DEFINE_ALLOC_TAG(_alloc_tag) static inline bool mem_alloc_profiling_enabled(void) { return false; } +static inline bool mem_alloc_profiling_permanently_disabled(void) { return true; } static inline void alloc_tag_add(union codetag_ref *ref, struct alloc_tag *tag, size_t bytes) {} static inline void alloc_tag_sub(union codetag_ref *ref, size_t bytes) {} diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c index d9be1cf5187d..e5b218176c5a 100644 --- a/lib/alloc_tag.c +++ b/lib/alloc_tag.c @@ -26,6 +26,15 @@ static bool mem_profiling_support = true; static bool mem_profiling_support; #endif +/* + * Memory allocation profiling is permanently disabled and cannot be enabled. + * Must be called after setup_early_mem_profiling(). + */ +bool mem_alloc_profiling_permanently_disabled(void) +{ + return !mem_profiling_support; +} + static struct codetag_type *alloc_tag_cttype; #ifdef CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPU From d9e6a7623938968e3752b67e37eaff097e559a54 Mon Sep 17 00:00:00 2001 From: "Harry Yoo (Oracle)" Date: Mon, 13 Jul 2026 23:28:52 +0900 Subject: [PATCH 030/456] mm/slab: prevent unbounded recursion in free path with new kmalloc type Commit 280ea9c3154b ("mm/slab: avoid allocating slabobj_ext array from its own slab") avoided recursive allocation of obj_exts from kmalloc caches of the same size, by bumping the obj_exts array's allocation size whenever the array size equals the size of the object being allocated. However, as reported by Danielle Costantino and Shakeel Butt, even slabs from kmalloc caches of different sizes can form a cycle by allocating obj_exts arrays from each other [1]: What happened: a KMALLOC_NORMAL slab's obj_exts array (used by allocation profiling / memcg accounting) is itself kmalloc()'d from a KMALLOC_NORMAL cache, so the "slab holds another slab's obj_exts array" relation can form cycles. With sizeof(struct slabobj_ext) == 16 and the host's geometry: - kmalloc-512 has 64 objects/slab -> array is 64*16 == 1024 bytes, served from kmalloc-1k; - kmalloc-1k has 32 objects/slab -> array is 32*16 == 512 bytes, served from kmalloc-512. A kmalloc-512 slab and a kmalloc-1k slab therefore hold each other's obj_exts array. Discarding one frees the other's array, which empties and discards that slab, which frees the first's array, and so on: __free_slab() -> free_slab_obj_exts() -> kfree() -> discard_slab() -> __free_slab() recurses along the cycle until the stack is exhausted. With memory allocation profiling, this allows unbounded recursion in the free path and led to a stack overflow on a production host in the Meta fleet [1]: BUG: TASK stack guard page was hit Oops: stack guard page RIP: 0010:kfree+0x8/0x5d0 Call Trace: __free_slab+0x66/0xc0 kfree+0x3f0/0x5d0 ... ( ~125x __free_slab <-> kfree ) ... do_syscall_64 It is proposed [1] to resolve this issue by always serving the obj_exts array allocation from kmalloc caches (or large kmalloc) of sizes larger than the object size. However, as pointed out by Vlastimil Babka [2], this can waste an excessive amount of memory as slabs from large kmalloc sizes (e.g. kmalloc-8k) generally need obj_exts arrays much smaller than the object size. Therefore, rather than bumping the size, let us take a different approach; disallow formation of cycles between kmalloc types when allocating obj_exts arrays. Currently, all obj_exts arrays are served from normal kmalloc caches. Cycles cannot be created if obj_exts arrays of normal kmalloc caches are served from a special kmalloc type that can never have obj_exts arrays. To achieve this, create a new kmalloc type called KMALLOC_NO_OBJ_EXT. KMALLOC_NO_OBJ_EXT caches are created with SLAB_NO_OBJ_EXT flag when either 1) memory allocation profiling is not permanently disabled, or 2) kmalloc types with a priority higher than KMALLOC_CGROUP are aliased with KMALLOC_NORMAL. Sheaf bootstrapping for KMALLOC_NO_OBJ_EXT caches now must be deferred because allocation of a barn can trigger obj_exts array allocation of normal kmalloc caches when the KMALLOC_NO_OBJ_EXT cache for that size is not ready yet. For simplicity, perform bootstrapping of sheaves for all kmalloc caches later. Introduce a new slab alloc flag, SLAB_ALLOC_NO_OBJ_EXT, to prevent allocation of obj_exts arrays, and let kmalloc_slab() override the type to KMALLOC_NO_OBJ_EXT when specified. Note that kmalloc_type() remains unchanged because kmalloc_flags() bypasses the kmalloc fastpath. Do not pass SLAB_ALLOC_NO_RECURSE to kmalloc_flags() in alloc_slab_obj_exts() and instead use SLAB_ALLOC_NO_OBJ_EXT only when the objects are allocated from normal kmalloc caches. While this prevents unbounded recursive allocation of obj_exts, it allows KMALLOC_NO_OBJ_EXT caches to have sheaves. Since sheaf allocations specify SLAB_ALLOC_NO_RECURSE that prevents allocation of both sheaves and obj_exts arrays, the recursion depth is bounded. obj_exts arrays for non-kmalloc-normal caches can now have a valid tag. Do not call mark_obj_codetag_empty() when freeing an obj_exts array to avoid false warnings. KMALLOC_NO_OBJ_EXT don't need this as they never allocate those arrays. Reported-by: Danielle Costantino Reported-by: Shakeel Butt Closes: https://lore.kernel.org/linux-mm/20260625230029.703750-1-shakeel.butt@linux.dev [1] Fixes: 4b8736964640 ("mm/slab: add allocation accounting into slab allocation and free paths") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/linux-mm/c5c4208d-a6f0-413e-bad9-49be12f12d55@kernel.org [2] Signed-off-by: Harry Yoo (Oracle) Reviewed-by: Suren Baghdasaryan Link: https://patch.msgid.link/20260713-kmalloc-no-objext-v3-4-47c7bd138de7@kernel.org Signed-off-by: Vlastimil Babka (SUSE) --- include/linux/slab.h | 6 +++ mm/slab.h | 32 ++++++++++++++-- mm/slab_common.c | 19 +++++++++- mm/slub.c | 90 ++++++++++++++++---------------------------- 4 files changed, 85 insertions(+), 62 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 08d7b6c9c4d6..3a14df50766a 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -708,6 +708,9 @@ enum kmalloc_cache_type { #endif #ifndef CONFIG_MEMCG KMALLOC_CGROUP = KMALLOC_NORMAL, +#endif +#ifndef CONFIG_SLAB_OBJ_EXT + KMALLOC_NO_OBJ_EXT = KMALLOC_NORMAL, #endif KMALLOC_PARTITION_START = KMALLOC_NORMAL, KMALLOC_PARTITION_END = KMALLOC_PARTITION_START + KMALLOC_PARTITION_CACHES_NR, @@ -721,6 +724,9 @@ enum kmalloc_cache_type { #endif #ifdef CONFIG_MEMCG KMALLOC_CGROUP, +#endif +#ifdef CONFIG_SLAB_OBJ_EXT + KMALLOC_NO_OBJ_EXT, #endif NR_KMALLOC_TYPES }; diff --git a/mm/slab.h b/mm/slab.h index 281a65233795..f5e336b6b6b0 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -22,6 +22,7 @@ #define SLAB_ALLOC_NOLOCK 0x01 /* a kmalloc_nolock() allocation */ #define SLAB_ALLOC_NEW_SLAB 0x02 /* a flag for alloc_slab_obj_exts() */ #define SLAB_ALLOC_NO_RECURSE 0x04 /* prevent kmalloc() recursion */ +#define SLAB_ALLOC_NO_OBJ_EXT 0x08 /* prevent obj_exts array allocation */ static inline bool alloc_flags_allow_spinning(const unsigned int alloc_flags) { @@ -386,12 +387,17 @@ static inline unsigned int size_index_elem(unsigned int bytes) * KMALLOC_MAX_CACHE_SIZE and the caller must check that. */ static inline struct kmem_cache * -kmalloc_slab(size_t size, kmem_buckets *b, gfp_t flags, kmalloc_token_t token) +kmalloc_slab(size_t size, kmem_buckets *b, gfp_t flags, kmalloc_token_t token, + unsigned int alloc_flags) { unsigned int index; + enum kmalloc_cache_type type = kmalloc_type(flags, token); + + if (alloc_flags & SLAB_ALLOC_NO_OBJ_EXT) + type = KMALLOC_NO_OBJ_EXT; if (!b) - b = &kmalloc_caches[kmalloc_type(flags, token)]; + b = &kmalloc_caches[type]; if (size <= 192) index = kmalloc_size_index[size_index_elem(size)]; else @@ -426,7 +432,8 @@ static inline bool is_kmalloc_normal(struct kmem_cache *s) { if (!is_kmalloc_cache(s)) return false; - return !(s->flags & (SLAB_CACHE_DMA|SLAB_ACCOUNT|SLAB_RECLAIM_ACCOUNT)); + + return !(s->flags & (SLAB_CACHE_DMA|SLAB_ACCOUNT|SLAB_RECLAIM_ACCOUNT|SLAB_NO_OBJ_EXT)); } bool __kfree_rcu_sheaf(struct kmem_cache *s, void *obj); @@ -529,6 +536,25 @@ static inline void metadata_access_disable(void) kasan_enable_current(); } +/* + * Return true if KMALLOC_NORMAL caches may need obj_exts arrays. + * + * Memory allocation profiling requires obj_exts for all caches. + * Memcg usually doesn't need them for normal kmalloc caches, but kmalloc types + * with a priority higher than KMALLOC_CGROUP can be aliased with KMALLOC_NORMAL. + */ +static inline bool need_kmalloc_no_objext(void) +{ + if (!mem_alloc_profiling_permanently_disabled()) + return true; + + if (!mem_cgroup_kmem_disabled() && + (KMALLOC_NORMAL == KMALLOC_RECLAIM)) + return true; + + return false; +} + #ifdef CONFIG_SLAB_OBJ_EXT /* diff --git a/mm/slab_common.c b/mm/slab_common.c index b6426d7ceec9..03ecac12cd86 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -783,11 +783,15 @@ u8 kmalloc_size_index[24] __ro_after_init = { size_t kmalloc_size_roundup(size_t size) { if (size && size <= KMALLOC_MAX_CACHE_SIZE) { + struct kmem_cache *s; + /* * The flags don't matter since size_index is common to all. * Neither does the caller for just getting ->object_size. */ - return kmalloc_slab(size, NULL, GFP_KERNEL, __kmalloc_token(0))->object_size; + s = kmalloc_slab(size, NULL, GFP_KERNEL, __kmalloc_token(0), + SLAB_ALLOC_DEFAULT); + return s->object_size; } /* Above the smaller buckets, size is a multiple of page size. */ @@ -843,6 +847,12 @@ EXPORT_SYMBOL(kmalloc_size_roundup); #define KMALLOC_PARTITION_NAME(N, sz) #endif +#ifdef CONFIG_SLAB_OBJ_EXT +#define KMALLOC_NO_OBJ_EXT_NAME(sz) .name[KMALLOC_NO_OBJ_EXT] = "kmalloc-no-objext-" #sz, +#else +#define KMALLOC_NO_OBJ_EXT_NAME(sz) +#endif + #define INIT_KMALLOC_INFO(__size, __short_size) \ { \ .name[KMALLOC_NORMAL] = "kmalloc-" #__short_size, \ @@ -850,6 +860,7 @@ EXPORT_SYMBOL(kmalloc_size_roundup); KMALLOC_CGROUP_NAME(__short_size) \ KMALLOC_DMA_NAME(__short_size) \ KMALLOC_PARTITION_NAME(KMALLOC_PARTITION_CACHES_NR, __short_size) \ + KMALLOC_NO_OBJ_EXT_NAME(__short_size) \ .size = __size, \ } @@ -957,6 +968,12 @@ new_kmalloc_cache(int idx, enum kmalloc_cache_type type) return; } flags |= SLAB_ACCOUNT; + } else if (IS_ENABLED(CONFIG_SLAB_OBJ_EXT) && type == KMALLOC_NO_OBJ_EXT) { + if (!need_kmalloc_no_objext()) { + kmalloc_caches[type][idx] = kmalloc_caches[KMALLOC_NORMAL][idx]; + return; + } + flags |= SLAB_NO_OBJ_EXT | SLAB_NO_MERGE; } else if (IS_ENABLED(CONFIG_ZONE_DMA) && (type == KMALLOC_DMA)) { flags |= SLAB_CACHE_DMA; } diff --git a/mm/slub.c b/mm/slub.c index abe748b7dddb..0337e60db5ac 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -2123,42 +2123,6 @@ static inline void init_slab_obj_exts(struct slab *slab) slab->obj_exts = 0; } -/* - * Calculate the allocation size for slabobj_ext array. - * - * When memory allocation profiling is enabled, the obj_exts array - * could be allocated from the same slab cache it's being allocated for. - * This would prevent the slab from ever being freed because it would - * always contain at least one allocated object (its own obj_exts array). - * - * To avoid this, increase the allocation size when we detect the array - * may come from the same cache, forcing it to use a different cache. - */ -static inline size_t obj_exts_alloc_size(struct kmem_cache *s, - struct slab *slab, gfp_t gfp) -{ - size_t sz = sizeof(struct slabobj_ext) * slab->objects; - struct kmem_cache *obj_exts_cache; - - if (sz > KMALLOC_MAX_CACHE_SIZE) - return sz; - - if (!is_kmalloc_normal(s)) - return sz; - - obj_exts_cache = kmalloc_slab(sz, NULL, gfp, __kmalloc_token(0)); - /* - * We can't simply compare s with obj_exts_cache, because partitioned kmalloc - * caches have multiple caches per size, selected by caller address or type. - * Since caller address or type may differ between kmalloc_slab() and actual - * allocation, bump size when sizes are equal. - */ - if (s->object_size == obj_exts_cache->object_size) - return obj_exts_cache->object_size + 1; - - return sz; -} - int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s, gfp_t gfp, unsigned int alloc_flags) { @@ -2168,14 +2132,18 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s, unsigned long new_exts; unsigned long old_exts; struct slabobj_ext *vec; - size_t sz; + size_t sz = sizeof(struct slabobj_ext) * slab->objects; gfp &= ~OBJCGS_CLEAR_MASK; - /* Prevent recursive extension vector allocation */ - alloc_flags |= SLAB_ALLOC_NO_RECURSE; - alloc_flags &= ~SLAB_ALLOC_NEW_SLAB; + /* + * In most cases, obj_exts arrays are allocated from normal kmalloc. + * However, normal kmalloc caches must allocate them from + * KMALLOC_NO_OBJ_EXT caches to prevent recursion. + */ + if (is_kmalloc_normal(s)) + alloc_flags |= SLAB_ALLOC_NO_OBJ_EXT; - sz = obj_exts_alloc_size(s, slab, gfp); + alloc_flags &= ~SLAB_ALLOC_NEW_SLAB; /* This will use kmalloc_nolock() if alloc_flags say so */ vec = kmalloc_flags(sz, gfp | __GFP_ZERO, alloc_flags, slab_nid(slab)); @@ -2193,8 +2161,21 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s, return -ENOMEM; } - VM_WARN_ON_ONCE(virt_to_slab(vec) != NULL && - virt_to_slab(vec)->slab_cache == s); + if (IS_ENABLED(CONFIG_DEBUG_VM)) { + struct kmem_cache *exts_cache; + struct slab *exts_slab; + + exts_slab = virt_to_slab(vec); + if (exts_slab) { + /* + * The vector must be allocated from either normal or + * KMALLOC_NO_OBJ_EXT kmalloc caches to avoid cycles. + */ + exts_cache = exts_slab->slab_cache; + WARN_ON_ONCE(!is_kmalloc_normal(exts_cache) && + !(exts_cache->flags & SLAB_NO_OBJ_EXT)); + } + } new_exts = (unsigned long)vec; #ifdef CONFIG_MEMCG @@ -2217,7 +2198,6 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s, * assign slabobj_exts in parallel. In this case the existing * objcg vector should be reused. */ - mark_obj_codetag_empty(vec); if (unlikely(!allow_spin)) kfree_nolock(vec); else @@ -2253,14 +2233,6 @@ static inline void free_slab_obj_exts(struct slab *slab, bool allow_spin) return; } - /* - * obj_exts was created with SLAB_ALLOC_NO_RECURSE flag, therefore its - * corresponding extension will be NULL. alloc_tag_sub() will throw a - * warning if slab has extensions but the extension of an object is - * NULL, therefore replace NULL with CODETAG_EMPTY to indicate that - * the extension for obj_exts is expected to be NULL. - */ - mark_obj_codetag_empty(obj_exts); if (allow_spin) kfree(obj_exts); else @@ -5356,7 +5328,7 @@ void *__do_kmalloc_node(kmem_buckets *b, gfp_t flags, int node, if (unlikely(!size)) return ZERO_SIZE_PTR; - s = kmalloc_slab(size, b, flags, token); + s = kmalloc_slab(size, b, flags, token, ac->alloc_flags); ret = slab_alloc_node(s, flags, node, ac); ret = kasan_kmalloc(s, ret, size, flags); @@ -5419,7 +5391,9 @@ static void *__kmalloc_nolock_noprof(DECL_TOKEN_PARAMS(size, token), gfp_t gfp_f retry: if (unlikely(size > KMALLOC_MAX_CACHE_SIZE)) return NULL; - s = kmalloc_slab(size, NULL, gfp_flags, PASS_TOKEN_PARAM(token)); + + s = kmalloc_slab(size, NULL, gfp_flags, PASS_TOKEN_PARAM(token), + ac->alloc_flags); if (!(s->flags & __CMPXCHG_DOUBLE) && !kmem_cache_debug(s)) /* @@ -7979,10 +7953,10 @@ static int calculate_sizes(struct kmem_cache_args *args, struct kmem_cache *s) s->allocflags |= __GFP_RECLAIMABLE; /* - * For KMALLOC_NORMAL caches we enable sheaves later by - * bootstrap_kmalloc_sheaves() to avoid recursion + * For kmalloc caches we enable sheaves later by + * bootstrap_kmalloc_sheaves() to avoid recursion. */ - if (!is_kmalloc_normal(s)) + if (!is_kmalloc_cache(s)) s->sheaf_capacity = calculate_sheaf_capacity(s, args); /* @@ -8548,7 +8522,7 @@ static void __init bootstrap_kmalloc_sheaves(void) { enum kmalloc_cache_type type; - for (type = KMALLOC_NORMAL; type <= KMALLOC_PARTITION_END; type++) { + for (type = KMALLOC_NORMAL; type < NR_KMALLOC_TYPES; type++) { for (int idx = 0; idx < KMALLOC_SHIFT_HIGH + 1; idx++) { struct kmem_cache *s = kmalloc_caches[type][idx]; From 9e1dc434ca708e1134332e45918935752f4410cd Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Tue, 14 Jul 2026 13:37:46 +0800 Subject: [PATCH 031/456] firmware: stratix10-svc: handle NO_RESPONSE in async poll Define INTEL_SIP_SMC_STATUS_NO_RESPONSE (0x3) and handle it in stratix10_svc_async_poll() the same way as INTEL_SIP_SMC_STATUS_BUSY, returning -EAGAIN so callers can retry instead of treating the poll as a hard failure. When the Secure Device Manager has not yet produced a response for an asynchronous transaction, ATF is expected to return INTEL_SIP_SMC_STATUS_NO_RESPONSE. Without this handling, the service layer maps the status to -EINVAL and async clients cannot distinguish "not ready yet" from a real error. Fixes: bcb9f4f07061 ("firmware: stratix10-svc: Add support for async communication") Cc: stable@vger.kernel.org Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-svc.c | 5 +++-- include/linux/firmware/intel/stratix10-smc.h | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index 6306b79a14a1..c346f5fd397f 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -1499,8 +1499,9 @@ int stratix10_svc_async_poll(struct stratix10_svc_chan *chan, WARN_ON_ONCE(1); } return 0; - } else if (handle->res.a0 == INTEL_SIP_SMC_STATUS_BUSY) { - dev_dbg(ctrl->dev, "async message is still in progress\n"); + } else if (handle->res.a0 == INTEL_SIP_SMC_STATUS_BUSY || + handle->res.a0 == INTEL_SIP_SMC_STATUS_NO_RESPONSE) { + dev_dbg(ctrl->dev, "async message is not ready yet\n"); return -EAGAIN; } diff --git a/include/linux/firmware/intel/stratix10-smc.h b/include/linux/firmware/intel/stratix10-smc.h index 2c603a24967f..9224974fffc4 100644 --- a/include/linux/firmware/intel/stratix10-smc.h +++ b/include/linux/firmware/intel/stratix10-smc.h @@ -67,6 +67,9 @@ * INTEL_SIP_SMC_STATUS_REJECTED: * Secure monitor software reject the service client's request. * + * INTEL_SIP_SMC_STATUS_NO_RESPONSE: + * Secure monitor software has no response for the request yet. + * * INTEL_SIP_SMC_STATUS_ERROR: * There is error during the process of service request. * @@ -77,6 +80,7 @@ #define INTEL_SIP_SMC_STATUS_OK 0x0 #define INTEL_SIP_SMC_STATUS_BUSY 0x1 #define INTEL_SIP_SMC_STATUS_REJECTED 0x2 +#define INTEL_SIP_SMC_STATUS_NO_RESPONSE 0x3 #define INTEL_SIP_SMC_STATUS_ERROR 0x4 #define INTEL_SIP_SMC_RSU_ERROR 0x7 From bba85375f8751d6c949036269b529cdcb2a2ed8f Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Tue, 14 Jul 2026 13:37:47 +0800 Subject: [PATCH 032/456] firmware: stratix10-svc: fix teardown order in remove to prevent race In stratix10_svc_drv_remove(), stratix10_svc_async_exit() was called before client devices were unregistered. This created a race window where child devices could still be issuing service requests through the async channels after the async infrastructure had already been torn down. Unregister client devices before tearing down the async threads and channels to ensure all in-flight service calls drain before the underlying infrastructure is destroyed. Fixes: bcb9f4f07061 ("firmware: stratix10-svc: Add support for async communication") Cc: stable@vger.kernel.org Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-svc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index c346f5fd397f..5e20057ee344 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -2049,12 +2049,12 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev) struct stratix10_svc_controller *ctrl = platform_get_drvdata(pdev); struct stratix10_svc *svc = ctrl->svc; + platform_device_unregister(svc->stratix10_svc_rsu); + stratix10_svc_async_exit(ctrl); of_platform_depopulate(ctrl->dev); - platform_device_unregister(svc->stratix10_svc_rsu); - for (i = 0; i < SVC_NUM_CHANNEL; i++) { if (ctrl->chans[i].task) { kthread_stop(ctrl->chans[i].task); From 0db3a430d9681fdb29890bef6934cd89cd1745d0 Mon Sep 17 00:00:00 2001 From: "Tycho Andersen (AMD)" Date: Thu, 9 Jul 2026 13:57:35 -0600 Subject: [PATCH 033/456] iommu/amd: Fix IRQ unsafe locking in gdom allocation Lockdep complains: [ 259.410489] ===================================================== [ 259.417287] WARNING: HARDIRQ-safe -> HARDIRQ-unsafe lock order detected [ 259.424667] 7.0.0-g51db1d8d2113 #54 Not tainted [ 259.429718] ----------------------------------------------------- [ 259.436516] qemu-system-x86/10143 [HC0[0]:SC0[0]:HE0:SE1] is trying to acquire: [ 259.444670] ff3b2b1c60305170 (&xa->xa_lock#25){+.+.}-{3:3}, at: __domain_flush_pages+0x17c/0x4b0 [ 259.454485] and this task is already holding: [ 259.460991] ff3b2b1c98504cc0 (&domain->lock){-.-.}-{3:3}, at: amd_iommu_iotlb_sync+0x25/0x60 [ 259.470408] which would create a new lock dependency: [ 259.476041] (&domain->lock){-.-.}-{3:3} -> (&xa->xa_lock#25){+.+.}-{3:3} [ 259.483615] but this new dependency connects a HARDIRQ-irq-safe lock: [ 259.492447] (&domain->lock){-.-.}-{3:3} [ 259.492449] ... which became HARDIRQ-irq-safe at: [ 259.503705] lock_acquire+0xb6/0x2e0 [ 259.507790] _raw_spin_lock_irqsave+0x3e/0x60 [ 259.512748] amd_iommu_flush_iotlb_all+0x20/0x50 [ 259.517996] iommu_dma_free_iova.isra.0+0x1b8/0x1e0 [ 259.523534] __iommu_dma_unmap+0xc2/0x140 [ 259.528100] iommu_dma_unmap_phys+0x55/0xc0 [ 259.532863] dma_unmap_phys+0x274/0x2e0 [ 259.537238] dma_unmap_page_attrs+0x17/0x30 [ 259.542000] nvme_unmap_data+0x13e/0x280 [ 259.546473] nvme_pci_complete_batch+0x45/0x70 [ 259.551524] nvme_irq+0x83/0x90 [ 259.555123] __handle_irq_event_percpu+0x92/0x360 [ 259.560466] handle_irq_event+0x39/0x80 [ 259.564841] handle_edge_irq+0xb2/0x1a0 [ 259.569214] __common_interrupt+0x4e/0x130 [ 259.573882] common_interrupt+0x88/0xa0 [ 259.578256] asm_common_interrupt+0x27/0x40 [ 259.583019] cpuidle_enter_state+0x119/0x5d0 [ 259.587877] cpuidle_enter+0x2e/0x50 [ 259.591962] do_idle+0x153/0x2c0 [ 259.595657] cpu_startup_entry+0x29/0x30 [ 259.600128] start_secondary+0x118/0x150 [ 259.604601] common_startup_64+0x13e/0x141 [ 259.609266] to a HARDIRQ-irq-unsafe lock: [ 259.615384] (&xa->xa_lock#25){+.+.}-{3:3} [ 259.615386] ... which became HARDIRQ-irq-unsafe at: [ 259.627039] ... [ 259.627039] lock_acquire+0xb6/0x2e0 [ 259.633071] _raw_spin_lock+0x2f/0x50 [ 259.637250] amd_iommu_alloc_domain_nested+0x140/0x3c0 [ 259.643078] iommufd_hwpt_alloc+0x272/0x800 [iommufd] [ 259.648813] iommufd_fops_ioctl+0x14e/0x200 [iommufd] [ 259.654547] __x64_sys_ioctl+0x9d/0xf0 ... Since amd_iommu_domain_flush_pages() necessarily holds domain->lock to do the flush, switch the allocation side in gdom_info_load_or_alloc_locked() to HARDIRQ-safe allocation. The IOMMU_DESTROY->free path has the same issue, so switch that path to HARDIRQ-safe locking as well. Fixes: 757d2b1fdf5b ("iommu/amd: Introduce gDomID-to-hDomID Mapping and handle parent domain invalidation") Signed-off-by: Tycho Andersen (AMD) Reviewed-by: Ankit Soni Signed-off-by: Will Deacon --- drivers/iommu/amd/nested.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/drivers/iommu/amd/nested.c b/drivers/iommu/amd/nested.c index 5b902598e68a..5c9405223f91 100644 --- a/drivers/iommu/amd/nested.c +++ b/drivers/iommu/amd/nested.c @@ -59,7 +59,9 @@ static int validate_gdte_nested(struct iommu_hwpt_amd_guest *gdte) return 0; } -static void *gdom_info_load_or_alloc_locked(struct xarray *xa, unsigned long index) +static void *gdom_info_load_or_alloc_locked(struct xarray *xa, + unsigned long index, + unsigned long *flags) { struct guest_domain_mapping_info *elm, *res; @@ -67,13 +69,13 @@ static void *gdom_info_load_or_alloc_locked(struct xarray *xa, unsigned long ind if (elm) return elm; - xa_unlock(xa); + xa_unlock_irqrestore(xa, *flags); elm = kzalloc_obj(struct guest_domain_mapping_info); - xa_lock(xa); + xa_lock_irqsave(xa, *flags); if (!elm) return ERR_PTR(-ENOMEM); - res = __xa_cmpxchg(xa, index, NULL, elm, GFP_KERNEL); + res = __xa_cmpxchg(xa, index, NULL, elm, GFP_ATOMIC); if (xa_is_err(res)) res = ERR_PTR(xa_err(res)); @@ -95,6 +97,7 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, const struct iommu_user_data *user_data) { int ret; + unsigned long irqflags; struct nested_domain *ndom; struct guest_domain_mapping_info *gdom_info; struct amd_iommu_viommu *aviommu = container_of(viommu, struct amd_iommu_viommu, core); @@ -136,11 +139,12 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, * keep track of the gDomID mapping. When the S2 is changed, the INVALIDATE_IOMMU_PAGES * command must be issued for each hDomID in the xarray. */ - xa_lock(&aviommu->gdomid_array); + xa_lock_irqsave(&aviommu->gdomid_array, irqflags); - gdom_info = gdom_info_load_or_alloc_locked(&aviommu->gdomid_array, ndom->gdom_id); + gdom_info = gdom_info_load_or_alloc_locked(&aviommu->gdomid_array, + ndom->gdom_id, &irqflags); if (IS_ERR(gdom_info)) { - xa_unlock(&aviommu->gdomid_array); + xa_unlock_irqrestore(&aviommu->gdomid_array, irqflags); ret = PTR_ERR(gdom_info); goto out_err; } @@ -148,7 +152,7 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, /* Check if gDomID exist */ if (refcount_inc_not_zero(&gdom_info->users)) { ndom->gdom_info = gdom_info; - xa_unlock(&aviommu->gdomid_array); + xa_unlock_irqrestore(&aviommu->gdomid_array, irqflags); pr_debug("%s: Found gdom_id=%#x, hdom_id=%#x\n", __func__, ndom->gdom_id, gdom_info->hdom_id); @@ -161,7 +165,7 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, if (gdom_info->hdom_id <= 0) { __xa_cmpxchg(&aviommu->gdomid_array, ndom->gdom_id, gdom_info, NULL, GFP_ATOMIC); - xa_unlock(&aviommu->gdomid_array); + xa_unlock_irqrestore(&aviommu->gdomid_array, irqflags); ret = -ENOSPC; goto out_err_gdom_info; } @@ -169,7 +173,7 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, ndom->gdom_info = gdom_info; refcount_set(&gdom_info->users, 1); - xa_unlock(&aviommu->gdomid_array); + xa_unlock_irqrestore(&aviommu->gdomid_array, irqflags); pr_debug("%s: Allocate gdom_id=%#x, hdom_id=%#x\n", __func__, ndom->gdom_id, gdom_info->hdom_id); @@ -257,14 +261,15 @@ static int nested_attach_device(struct iommu_domain *dom, struct device *dev, static void nested_domain_free(struct iommu_domain *dom) { + unsigned long irqflags; struct guest_domain_mapping_info *curr; struct nested_domain *ndom = to_ndomain(dom); struct amd_iommu_viommu *aviommu = ndom->viommu; - xa_lock(&aviommu->gdomid_array); + xa_lock_irqsave(&aviommu->gdomid_array, irqflags); if (!refcount_dec_and_test(&ndom->gdom_info->users)) { - xa_unlock(&aviommu->gdomid_array); + xa_unlock_irqrestore(&aviommu->gdomid_array, irqflags); return; } @@ -275,7 +280,7 @@ static void nested_domain_free(struct iommu_domain *dom) curr = __xa_cmpxchg(&aviommu->gdomid_array, ndom->gdom_id, ndom->gdom_info, NULL, GFP_ATOMIC); - xa_unlock(&aviommu->gdomid_array); + xa_unlock_irqrestore(&aviommu->gdomid_array, irqflags); if (WARN_ON(!curr || xa_err(curr))) return; From 283c5c4c34b4c8d1ebd038d8f360c5ba7fcc767b Mon Sep 17 00:00:00 2001 From: "Tycho Andersen (AMD)" Date: Thu, 9 Jul 2026 13:57:36 -0600 Subject: [PATCH 034/456] iommu/amd: Fix nested domain leak A couple of runs of different AI tools have generated something like the following bug report: In nested_domain_free(), when refcount_dec_and_test() returns false (other nested domains still reference the same gdom_info), the function returns without calling kfree(ndom), leaking the nested_domain structure. This problem wasn't introduced by this patch, but exists in the code from commit 757d2b1fdf5b that the patch modifies. Each nested_domain (ndom) is allocated individually in amd_iommu_alloc_domain_nested() via kzalloc_obj(*ndom). The .free callback is the sole point responsible for freeing this domain. When the refcount is > 0, only the xa_unlock_irqrestore is performed and the function returns, leaving ndom permanently allocated. This leak occurs every time a nested domain sharing a gDomID is destroyed while other domains still use that gDomID. There is a similar leak later in this function in the WARN_ON() test when the mapping is already NULL. Switch to a RAII-based cleanup for ndom, since it should always be freed in this function. Fixes: 757d2b1fdf5b ("iommu/amd: Introduce gDomID-to-hDomID Mapping and handle parent domain invalidation") Signed-off-by: Tycho Andersen (AMD) Reviewed-by: Ankit Soni Signed-off-by: Will Deacon --- drivers/iommu/amd/nested.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/iommu/amd/nested.c b/drivers/iommu/amd/nested.c index 5c9405223f91..63b53b29e029 100644 --- a/drivers/iommu/amd/nested.c +++ b/drivers/iommu/amd/nested.c @@ -263,7 +263,7 @@ static void nested_domain_free(struct iommu_domain *dom) { unsigned long irqflags; struct guest_domain_mapping_info *curr; - struct nested_domain *ndom = to_ndomain(dom); + struct nested_domain *ndom __free(kfree) = to_ndomain(dom); struct amd_iommu_viommu *aviommu = ndom->viommu; xa_lock_irqsave(&aviommu->gdomid_array, irqflags); @@ -290,7 +290,6 @@ static void nested_domain_free(struct iommu_domain *dom) amd_iommu_pdom_id_free(ndom->gdom_info->hdom_id); kfree(curr); - kfree(ndom); } static const struct iommu_domain_ops nested_domain_ops = { From 7a7baebd9f23ba4f24796775472b2fd00dcd95d9 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Wed, 15 Jul 2026 13:23:08 +0530 Subject: [PATCH 035/456] gpio: sloppy-logic-analyzer: Fix memory leak in gpio_la_poll_probe() The memory allocated for priv->blob.data is not freed in the error paths that follow the fops_buf_size_set() call in gpio_la_poll_probe(), as well as in the remove function. Fix that by using device managed action to free the memory on remove. Fixes: 7828b7bbbf20 ("gpio: add sloppy logic analyzer using polling") Signed-off-by: Abdun Nihaal Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/20260715075311.527753-1-nihaal@cse.iitm.ac.in Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-sloppy-logic-analyzer.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpio/gpio-sloppy-logic-analyzer.c b/drivers/gpio/gpio-sloppy-logic-analyzer.c index 2bbd308ca08e..aa23b5779802 100644 --- a/drivers/gpio/gpio-sloppy-logic-analyzer.c +++ b/drivers/gpio/gpio-sloppy-logic-analyzer.c @@ -160,6 +160,13 @@ static int fops_buf_size_get(void *data, u64 *val) return 0; } +static void fops_buf_release(void *data) +{ + struct gpio_la_poll_priv *priv = data; + + vfree(priv->blob.data); +} + static int fops_buf_size_set(void *data, u64 val) { struct gpio_la_poll_priv *priv = data; @@ -238,6 +245,9 @@ static int gpio_la_poll_probe(struct platform_device *pdev) return ret; fops_buf_size_set(priv, GPIO_LA_DEFAULT_BUF_SIZE); + ret = devm_add_action_or_reset(dev, fops_buf_release, priv); + if (ret) + return ret; priv->descs = devm_gpiod_get_array(dev, "probe", GPIOD_IN); if (IS_ERR(priv->descs)) From e1cc8fa0fb9e5112d15f2c310b68ac316981c06c Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 14 Jul 2026 00:30:53 +0100 Subject: [PATCH 036/456] gpiolib: tolerate gpio-hogs lacking a hogging state Commit d1d564ec4992 ("gpio: move hogs into GPIO core") made gpiochip_add_hog() return -EINVAL for hog nodes lacking any of the 'input', 'output-low' or 'output-high' properties. The error is propagated by gpiochip_hog_lines() and fails registration of the whole GPIO chip. The previous OF-specific implementation tolerated such nodes: of_parse_own_gpio() warned "no hogging state specified, bailing out" and of_gpiochip_add_hog() stopped processing the node without failing chip registration. Some boards deliberately ship hog nodes without a hogging state in their base devicetree and supply the state via overlay, e.g. the PCIe slot key selection hogs on the BananaPi R4 Pro added in commit e309fa232d12 ("arm64: dts: mediatek: mt7988a-bpi-r4pro: rework pcie gpio-hog handling"), as the polarity set in the base devicetree could not be overridden from an overlay. Booting such a board without an overlay applied now fails to register the gpiochip. On the BananaPi R4 Pro this means the MT7988A pinctrl device fails to probe, all peripherals including the console UART defer forever, and the board finally hangs when clk_disable_unused() gates the clocks of the UART still in use by earlycon: gpiochip_add_data_with_key: GPIOs 512..595 (pinctrl_moore) failed to register, -22 mt7988-pinctrl 1001f000.pinctrl: error -EINVAL: Failed to add gpio_chip ... clk: Disabling unused clocks (hangs) Restore the previous behaviour by warning about hog nodes lacking a hogging state and skipping them instead of failing the registration of the whole GPIO chip. Fixes: d1d564ec4992 ("gpio: move hogs into GPIO core") Cc: stable@vger.kernel.org Signed-off-by: Daniel Golle Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/4c67cf0839ccf57db35a826df6d8fc779531509a.1783974733.git.daniel@makrotopia.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index e5fb60111151..c433a095907f 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -999,14 +999,17 @@ int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode) if (ret < 0) return ret; - if (fwnode_property_present(fwnode, "input")) + if (fwnode_property_present(fwnode, "input")) { dflags |= GPIOD_IN; - else if (fwnode_property_present(fwnode, "output-low")) + } else if (fwnode_property_present(fwnode, "output-low")) { dflags |= GPIOD_OUT_LOW; - else if (fwnode_property_present(fwnode, "output-high")) + } else if (fwnode_property_present(fwnode, "output-high")) { dflags |= GPIOD_OUT_HIGH; - else - return -EINVAL; + } else { + gpiochip_warn(gc, "%pfwP: no hogging state specified, bailing out\n", + fwnode); + return 0; + } fwnode_property_read_string(fwnode, "line-name", &name); From c1f3e770eec26d6f96dd6d2ea30555ba7c09a244 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Tue, 23 Jun 2026 13:23:46 -0700 Subject: [PATCH 037/456] fortify: Disable -Wstringop-overread in tests clang recently added support for -Wstringop-overread [1], which is on by default like -Wfortify-source. This breaks the usage of -Werror in the fortify tests, resulting in the following false positive warnings in the kernel build: warning: unsafe memcmp() usage lacked '__read_overflow2' warning in lib/test_fortify/read_overflow2-memcmp.c warning: unsafe memcmp() usage lacked '__read_overflow' warning in lib/test_fortify/read_overflow-memcmp.c warning: unsafe memchr() usage lacked '__read_overflow' warning in lib/test_fortify/read_overflow-memchr.c Examining the fortify test logs shows a warning like the following in each of the failed logs: In file included from lib/test_fortify/read_overflow2-memcmp.c:5: lib/test_fortify/test_fortify.h:34:2: error: 'memcmp' reading 17 bytes from a region of size 16 [-Werror,-Wstringop-overread] 34 | TEST; | ^ lib/test_fortify/read_overflow2-memcmp.c:3:2: note: expanded from macro 'TEST' 3 | memcmp(large, small, sizeof(small) + 1) | ^ 1 error generated. Disable -Wstringop-overread for the fortify tests, as it defeats the purpose of testing the Linux specific implementation of fortify, like -Wfortify-source. Cc: stable@vger.kernel.org Closes: https://github.com/ClangBuiltLinux/linux/issues/2168 Link: https://github.com/llvm/llvm-project/commit/86f2e71cb8d165b59ad31a442b2391e23826133e [1] Signed-off-by: Nathan Chancellor Link: https://patch.msgid.link/20260623-fix-test_fortify-for-clang-stringop-overread-v1-1-15ee8342a953@kernel.org Signed-off-by: Kees Cook --- lib/test_fortify/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/test_fortify/Makefile b/lib/test_fortify/Makefile index 399cae880e1d..44cd5df41a81 100644 --- a/lib/test_fortify/Makefile +++ b/lib/test_fortify/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 ccflags-y := $(call cc-disable-warning,fortify-source) +ccflags-y += $(call cc-disable-warning,stringop-overread) quiet_cmd_test_fortify = TEST $@ cmd_test_fortify = $(CONFIG_SHELL) $(src)/test_fortify.sh \ From b3a7aa9c0020ae549a0d4964867ff66d2bd61709 Mon Sep 17 00:00:00 2001 From: Haofeng Li Date: Wed, 15 Jul 2026 16:02:10 +0800 Subject: [PATCH 038/456] selftests/lkdtm: rename STACKLEAK_ERASING to KSTACK_ERASE Commit 57fbad15c2ee ("stackleak: Rename STACKLEAK to KSTACK_ERASE") renamed the LKDTM crash type and selftest configuration but missed the entry in tests.txt. As a result, the selftest generates STACKLEAK_ERASING.sh, which run.sh skips because the LKDTM DIRECT trigger only exposes KSTACK_ERASE. Rename the test entry so the generated runner uses the registered crash type. Fixes: 57fbad15c2ee ("stackleak: Rename STACKLEAK to KSTACK_ERASE") Signed-off-by: Haofeng Li Link: https://patch.msgid.link/tencent_CD80B5F746B6AABD68AF3F1097AD02C96F05@qq.com Signed-off-by: Kees Cook --- tools/testing/selftests/lkdtm/tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/lkdtm/tests.txt b/tools/testing/selftests/lkdtm/tests.txt index d8180bbe31e8..bec57a02913a 100644 --- a/tools/testing/selftests/lkdtm/tests.txt +++ b/tools/testing/selftests/lkdtm/tests.txt @@ -78,7 +78,7 @@ USERCOPY_STACK_FRAME_TO USERCOPY_STACK_FRAME_FROM USERCOPY_STACK_BEYOND USERCOPY_KERNEL -STACKLEAK_ERASING OK: the rest of the thread stack is properly erased +KSTACK_ERASE OK: the rest of the thread stack is properly erased CFI_FORWARD_PROTO CFI_BACKWARD call trace:|ok: control flow unchanged FORTIFY_STRSCPY detected buffer overflow From 3421b9b056a6576d0ebac1030eafb48ad0544092 Mon Sep 17 00:00:00 2001 From: Kuan-Ying Lee Date: Wed, 15 Jul 2026 13:35:52 +0800 Subject: [PATCH 039/456] selftests/seccomp: Fix pointer type mismatch build error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We hit the following build error while running the seccomp selftests in our testing. CC seccomp_bpf seccomp_bpf.c: In function ‘UPROBE_setup’: seccomp_bpf.c:5175:74: error: pointer type mismatch in conditional expression [-Wincompatible-pointer-types] 5175 | offset = get_uprobe_offset(variant->uretprobe ? probed_uretprobe : probed_uprobe); | ^ seccomp_bpf.c:5175:57: note: first expression has type ‘int (*)(void)’ 5175 | offset = get_uprobe_offset(variant->uretprobe ? probed_uretprobe : probed_uprobe); | ^~~~~~~~~~~~~~~~ seccomp_bpf.c:5175:76: note: second expression has type ‘int (__attribute__((nocf_check)) *)(void)’ 5175 | offset = get_uprobe_offset(variant->uretprobe ? probed_uretprobe : probed_uprobe); | ^~~~~~~~~~~~~ get_uprobe_offset() takes a 'const void *' argument, so cast both operands to 'void *'. Fixes: 9ffc7a635c35 ("selftests/seccomp: validate uprobe syscall passes through seccomp") Signed-off-by: Kuan-Ying Lee Acked-by: Jiri Olsa Link: https://patch.msgid.link/20260715053559.28535-1-kuan-ying.lee@canonical.com Signed-off-by: Kees Cook --- tools/testing/selftests/seccomp/seccomp_bpf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c index 358b6c65e120..0622bc2acad4 100644 --- a/tools/testing/selftests/seccomp/seccomp_bpf.c +++ b/tools/testing/selftests/seccomp/seccomp_bpf.c @@ -5178,7 +5178,8 @@ FIXTURE_SETUP(UPROBE) ASSERT_GE(bit, 0); } - offset = get_uprobe_offset(variant->uretprobe ? probed_uretprobe : probed_uprobe); + offset = get_uprobe_offset(variant->uretprobe ? (void *)probed_uretprobe + : (void *)probed_uprobe); ASSERT_GE(offset, 0); if (variant->uretprobe) From e057b94772328221405b067c3a85fe479b915dc8 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Thu, 16 Jul 2026 13:06:39 +0100 Subject: [PATCH 040/456] arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates When seccomp support was originally added to arm64 in a1ae65b21941 ("arm64: add seccomp support"), seccomp was erroneously called _before_ the ptrace syscall-enter-stop and therefore the tracer could trivially manipulate the syscall register state after the seccomp check had passed. This was subsequently fixed in a5cd110cb836 ("arm64/ptrace: run seccomp after ptrace") by moving the seccomp check after the tracer has run. Unfortunately, a decade later, that fix has been reported to be incomplete. On arm64, both the first argument to a syscall and its eventual return value are allocated to register x0. In order to facilitate syscall restarting and querying of syscall arguments on the syscall exit path, the original value of x0 is stashed in 'struct pt_regs::orig_x0' early during the syscall entry path and is returned for the first argument by syscall_get_arguments(). Unlike 32-bit Arm, this stashed value is not directly exposed via ptrace() and so changes to register x0 made by the tracer on a syscall-enter-stop are not reflected in 'orig_x0'. This means that seccomp, syscall tracepoints and audit can observe a stale value for the register compared to the argument that will be observed by the actual syscall. Re-sync 'orig_x0' from x0 on the syscall entry path following a potential ptrace stop (i.e. PTRACE_EVENTMSG_SYSCALL_ENTRY or SECCOMP_RET_TRACE). This behaviour is limited to native tasks (because compat tasks expose 'orig_r0' to ptrace) where the syscall is not being skipped (because x0 is updated to hold the return value of -ENOSYS in that case). Cc: Kees Cook Cc: Jinjie Ruan Cc: Mark Rutland Cc: stable@vger.kernel.org Reported-by: Yiqi Sun Link: https://lore.kernel.org/all/20260529065444.1336608-1-sunyiqixm@gmail.com/ Suggested-by: Catalin Marinas Fixes: a5cd110cb836 ("arm64/ptrace: run seccomp after ptrace") Reviewed-by: Jinjie Ruan Tested-by: Jinjie Ruan Signed-off-by: Will Deacon --- arch/arm64/kernel/ptrace.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index 4d08598e2891..390c9b2bd966 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -2408,6 +2408,21 @@ static void report_syscall_exit(struct pt_regs *regs) } } +static void update_syscall_orig_x0_after_ptrace(struct pt_regs *regs) +{ + /* + * Keep orig_x0 authoritative so that seccomp (via + * syscall_get_arguments()), audit and the restart path all see the same + * first argument the syscall is dispatched with, even if it has been + * updated by a tracer. Skip this for NO_SYSCALL (set either by the user + * or the tracer), as regs[0] holds the return value (see the comment in + * el0_svc_common()) and can be unwound using syscall_rollback(). + * For compat tasks, orig_r0 is provided directly through GPR index 17. + */ + if (!is_compat_task() && regs->syscallno != NO_SYSCALL) + regs->orig_x0 = regs->regs[0]; +} + int syscall_trace_enter(struct pt_regs *regs) { unsigned long flags = read_thread_flags(); @@ -2417,12 +2432,26 @@ int syscall_trace_enter(struct pt_regs *regs) ret = report_syscall_entry(regs); if (ret || (flags & _TIF_SYSCALL_EMU)) return NO_SYSCALL; + + /* + * Ensure ptrace changes to x0 during a regular + * syscall-enter-stop (PTRACE_SYSCALL) are visible to + * subsequent seccomp checks, tracepoints and audit. + */ + update_syscall_orig_x0_after_ptrace(regs); } /* Do the secure computing after ptrace; failures should be fast. */ if (secure_computing() == -1) return NO_SYSCALL; + /* + * Ensure tracer changes to x0 during seccomp ptrace exit + * processing (SECCOMP_RET_TRACE) are visible to tracepoints and + * audit. + */ + update_syscall_orig_x0_after_ptrace(regs); + if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) trace_sys_enter(regs, regs->syscallno); From 9469764292e0d6825c9bf51d75682e3a623b9b6b Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Fri, 15 May 2026 09:58:25 +0100 Subject: [PATCH 041/456] arm_mpam: Fix software reset values of MPAMCFG_PRI Priority partitioning is not supported other than to set the per-PARTID defaults in MPAMCFG_PRI, INTPRI and DSPRI, to the highest priority. When 0 is the lowest priority, all ones is the highest priority. However, these values are calculated with an extra higher bit set. Luckily, there is still no chance of setting functional bits incorrectly. When the priority widths are maximal, this is ensured as the fields have width 16 and a u16 holds the value for each field. When the widths are smaller, the higher order bits beyond the advertised widths, MPAMF_PRI_IDR.DSPRI_WD and MPAMF_PRI_IDR.INTPRI_WD, in the priority fields INTPRI and DSPRI are not used to calculate the priority. It is not specified whether these higher order bits are RAZ/WI or Res0 and so it is desirable not to set them to avoid the chance of misleading reads. Correct the priority reset values. Fixes: 880df85d8673 ("arm_mpam: Probe and reset the rest of the features") Signed-off-by: Ben Horgan Signed-off-by: Will Deacon --- drivers/resctrl/mpam_devices.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index b69f99488111..37ce11d7db92 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1552,12 +1552,9 @@ static u16 mpam_wa_t241_calc_min_from_max(struct mpam_props *props, static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid, struct mpam_config *cfg) { - u32 pri_val = 0; u16 cmax = MPAMCFG_CMAX_CMAX; struct mpam_msc *msc = ris->vmsc->msc; struct mpam_props *rprops = &ris->props; - u16 dspri = GENMASK(rprops->dspri_wd, 0); - u16 intpri = GENMASK(rprops->intpri_wd, 0); mutex_lock(&msc->part_sel_lock); __mpam_part_sel(ris->ris_idx, partid, msc); @@ -1622,16 +1619,25 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid, if (mpam_has_feature(mpam_feat_intpri_part, rprops) || mpam_has_feature(mpam_feat_dspri_part, rprops)) { - /* aces high? */ - if (!mpam_has_feature(mpam_feat_intpri_part_0_low, rprops)) - intpri = 0; - if (!mpam_has_feature(mpam_feat_dspri_part_0_low, rprops)) - dspri = 0; + u32 pri_val = 0; + + if (mpam_has_feature(mpam_feat_intpri_part, rprops)) { + u16 intpri = GENMASK(rprops->intpri_wd - 1, 0); + + /* aces high? */ + if (!mpam_has_feature(mpam_feat_intpri_part_0_low, rprops)) + intpri = 0; - if (mpam_has_feature(mpam_feat_intpri_part, rprops)) pri_val |= FIELD_PREP(MPAMCFG_PRI_INTPRI, intpri); - if (mpam_has_feature(mpam_feat_dspri_part, rprops)) + } + if (mpam_has_feature(mpam_feat_dspri_part, rprops)) { + u16 dspri = GENMASK(rprops->dspri_wd - 1, 0); + + if (!mpam_has_feature(mpam_feat_dspri_part_0_low, rprops)) + dspri = 0; + pri_val |= FIELD_PREP(MPAMCFG_PRI_DSPRI, dspri); + } mpam_write_partsel_reg(msc, PRI, pri_val); } From 021118ce5ea954ec316d7e30bcf4506e12eb5222 Mon Sep 17 00:00:00 2001 From: Fenghua Yu Date: Sat, 6 Jun 2026 22:09:25 -0700 Subject: [PATCH 042/456] arm_mpam: Fix MPAMCFG_MBW_PBM register setting MPAMCFG_MBW_PBM is written from cfg if cfg has the MBW partition feature. It is reset when cfg does not have the MBW partition feature. But the register handling is reversed. This may cause an incorrect register setting. For example, during an MPAM reset, reset_cfg is empty (no MBW partition feature set), and cfg->mbw_pbm is 0. Instead of resetting MPAMCFG_MBW_PBM to all 1's, the current logic will set it to cfg->mbw_pbm, which is 0. Fix the issue by swapping the if/else branches. Fixes: a1cb6577f575 ("arm_mpam: Reset when feature configuration bit unset") Reported-by: Matt Ochs Signed-off-by: Fenghua Yu Reviewed-by: Gavin Shan Reviewed-by: Ben Horgan Signed-off-by: Will Deacon --- drivers/resctrl/mpam_devices.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 37ce11d7db92..8e48b918ae54 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1580,9 +1580,9 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid, if (mpam_has_feature(mpam_feat_mbw_part, rprops)) { if (mpam_has_feature(mpam_feat_mbw_part, cfg)) - mpam_reset_msc_bitmap(msc, MPAMCFG_MBW_PBM, rprops->mbw_pbm_bits); - else mpam_write_partsel_reg(msc, MBW_PBM, cfg->mbw_pbm); + else + mpam_reset_msc_bitmap(msc, MPAMCFG_MBW_PBM, rprops->mbw_pbm_bits); } if (mpam_has_feature(mpam_feat_mbw_min, rprops)) { From 977f52909c624210178a1247fab0b02b110c1106 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 16:23:23 +0800 Subject: [PATCH 043/456] arm_mpam: guard MBWU state before adding it to garbage __destroy_component_cfg() adds each RIS mbwu_state object to the MPAM garbage list when destroying component configuration. However, mbwu_state is allocated per RIS and only for RISes with MBWU monitors. A component can therefore have comp->cfg allocated while some RISes still have ris->mbwu_state set to NULL. Passing a NULL mbwu_state to add_to_garbage() dereferences the NULL pointer inside the macro. Skip RISes that do not have an mbwu_state object before adding them to the garbage list. Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management") Signed-off-by: Guangshuo Li Reviewed-by: Ben Horgan Signed-off-by: Will Deacon --- drivers/resctrl/mpam_devices.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 8e48b918ae54..2f09f4b78bd3 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -2612,8 +2612,10 @@ static void __destroy_component_cfg(struct mpam_component *comp) msc = vmsc->msc; if (mpam_mon_sel_lock(msc)) { - list_for_each_entry(ris, &vmsc->ris, vmsc_list) - add_to_garbage(ris->mbwu_state); + list_for_each_entry(ris, &vmsc->ris, vmsc_list) { + if (ris->mbwu_state) + add_to_garbage(ris->mbwu_state); + } mpam_mon_sel_unlock(msc); } } From 21fc7ec93f8b633b60d5bddef2f1529ff6b36185 Mon Sep 17 00:00:00 2001 From: Yu Peng Date: Wed, 8 Jul 2026 10:35:14 +0800 Subject: [PATCH 044/456] arm64: fixmap: Allow 256K early_ioremap() at any offset NR_FIX_BTMAPS is the per-slot page limit for early_ioremap(). Since __early_ioremap() maps the page-aligned physical range, a 256K request can require one extra page when the physical address is not page-aligned. Reserve one extra page per slot so the 256K mapping budget is usable regardless of the initial page offset. Link: https://lore.kernel.org/r/08fd96fa-ee3a-4904-bd11-bb08bd90436f@kylinos.cn Signed-off-by: Yu Peng Signed-off-by: Will Deacon --- arch/arm64/include/asm/fixmap.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/fixmap.h b/arch/arm64/include/asm/fixmap.h index 65555284446e..7075a3bd2c61 100644 --- a/arch/arm64/include/asm/fixmap.h +++ b/arch/arm64/include/asm/fixmap.h @@ -78,8 +78,12 @@ enum fixed_addresses { /* * Temporary boot-time mappings, used by early_ioremap(), * before ioremap() is functional. + * + * Reserve one extra page so a 256K mapping may start at any + * offset within a page. early_ioremap() maps the page-aligned + * physical range, so the initial offset can consume an extra page. */ -#define NR_FIX_BTMAPS (SZ_256K / PAGE_SIZE) +#define NR_FIX_BTMAPS ((SZ_256K / PAGE_SIZE) + 1) #define FIX_BTMAPS_SLOTS 7 #define TOTAL_FIX_BTMAPS (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS) From 9dc325327babe7f159e84cbe9380a45342da0585 Mon Sep 17 00:00:00 2001 From: Mark Tomlinson Date: Thu, 9 Jul 2026 16:51:16 +1200 Subject: [PATCH 045/456] gpio: pca953x: fix pca953x_irq_bus_sync_unlock regmap lock Locking is disabled in the regmap config as this driver uses its own lock. This means that all calls to regmap functions (read or write) must hold the i2c_lock. The function pca953x_irq_bus_sync_unlock() did not do this, and it was therefore possible that multiple threads could cause an incorrect register to be read/written. A previous patch partly fixed this, but only protected the write to the interrupt mask register, and not the read from the direction register. Fixes: bfc6444b57dc ("gpio: pca953x: fix pca953x_irq_bus_sync_unlock race") Cc: stable@vger.kernel.org Signed-off-by: Mark Tomlinson Link: https://patch.msgid.link/20260709045116.2304246-1-mark.tomlinson@alliedtelesis.co.nz Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pca953x.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index f6b870b7b352..e03bf1b12091 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -604,20 +604,28 @@ static int pca953x_read_regs(struct pca953x_chip *chip, int reg, unsigned long * return 0; } -static int pca953x_gpio_direction_input(struct gpio_chip *gc, unsigned off) +static int pca953x_gpio_direction_input_unlocked(struct gpio_chip *gc, + unsigned int off) { struct pca953x_chip *chip = gpiochip_get_data(gc); u8 dirreg = chip->recalc_addr(chip, chip->regs->direction, off); u8 bit = pca953x_get_bit_mask(chip, off); - guard(mutex)(&chip->i2c_lock); - if (PCA_CHIP_TYPE(chip->driver_data) == TCA6418_TYPE) return regmap_update_bits(chip->regmap, dirreg, bit, 0); return regmap_update_bits(chip->regmap, dirreg, bit, bit); } +static int pca953x_gpio_direction_input(struct gpio_chip *gc, unsigned int off) +{ + struct pca953x_chip *chip = gpiochip_get_data(gc); + + guard(mutex)(&chip->i2c_lock); + + return pca953x_gpio_direction_input_unlocked(gc, off); +} + static int pca953x_gpio_direction_output(struct gpio_chip *gc, unsigned off, int val) { @@ -855,9 +863,10 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d) DECLARE_BITMAP(reg_direction, MAX_LINE); int level; + guard(mutex)(&chip->i2c_lock); + if (chip->driver_data & PCA_PCAL) { DECLARE_BITMAP(latched_inputs, MAX_LINE); - guard(mutex)(&chip->i2c_lock); /* Enable latch on edge-triggered interrupt-enabled inputs */ bitmap_or(latched_inputs, chip->irq_trig_fall, chip->irq_trig_raise, gc->ngpio); @@ -879,7 +888,7 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d) /* Look for any newly setup interrupt */ for_each_andnot_bit(level, irq_mask, reg_direction, gc->ngpio) - pca953x_gpio_direction_input(&chip->gpio_chip, level); + pca953x_gpio_direction_input_unlocked(&chip->gpio_chip, level); mutex_unlock(&chip->irq_lock); } From 3b4ca2e01c1dd8c00b675b794732945f460a471b Mon Sep 17 00:00:00 2001 From: Huang Wei Date: Thu, 16 Jul 2026 11:33:41 +0800 Subject: [PATCH 046/456] USB: storage: add NO_ATA_1X quirk for Longmai USB Key The Longmai Technologies USB Key (0x04b4:0xb708) advertises itself as a SCSI/Bulk-only mass storage device but does not correctly handle ATA pass-through commands. When such a command (ATA_12 or ATA_16) is sent to the device it fails to respond and the transfer eventually times out, leaving the device unusable. Add an unusual_devs entry for this device that sets the US_FL_NO_ATA_1X flag, so usb-storage short-circuits ATA pass-through commands and returns INVALID COMMAND OPERATION CODE (0x20 0x05 0x24 0x00) instead of forwarding them to the device. Information about the device in /sys/kernel/debug/usb/devices: T: Bus=02 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 12 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=06 Prot=50 MxPS=64 #Cfgs= 1 P: Vendor=04b4 ProdID=b708 Rev= 1.00 S: Manufacturer=Longmai Technologies S: Product=USB Key C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=100mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=08(stor.) Sub=06 Prot=50 Driver=usb-storage E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Reported-by: Ai Chao Cc: stable Signed-off-by: Huang Wei Acked-by: Alan Stern Link: https://patch.msgid.link/20260716033341.2830872-1-huangwei@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 255968f9ca42..ac22fa318734 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -395,6 +395,13 @@ UNUSUAL_DEV( 0x04b3, 0x4001, 0x0110, 0x0110, USB_SC_DEVICE, USB_PR_CB, NULL, US_FL_MAX_SECTORS_MIN), +/* Reported by Ai Chao */ +UNUSUAL_DEV( 0x04b4, 0xb708, 0x0000, 0xffff, + "Longmai Technologies", + "USB Key", + USB_SC_SCSI, USB_PR_BULK, NULL, + US_FL_NO_ATA_1X), + /* * Reported by Simon Levitt * This entry needs Sub and Proto fields From f797f51a185ffdc1e3f915afed55f308b376842f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 2 Jul 2026 20:13:35 +0100 Subject: [PATCH 047/456] arm64: mm: When logging data aborts only decode Xs when ISV=1 When logging the decode of a data abort we currently unconditionally decode and display Xs. Currently the only defined non-RES0 values for this field are for cases where ISV=1, move the decode of Xs into our existing check for ISV=1. This avoids potential confusion if some other use is assigned to these bits for ISV=0 cases in future, or misleading someone into thinking there is a meaningful value there with currently defined architecture. Signed-off-by: Mark Brown Signed-off-by: Will Deacon --- arch/arm64/mm/fault.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index 85e23388f9bb..0b52557652be 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -76,6 +76,8 @@ static void data_abort_decode(unsigned long esr) pr_alert(" SF = %lu, AR = %lu\n", (esr & ESR_ELx_SF) >> ESR_ELx_SF_SHIFT, (esr & ESR_ELx_AR) >> ESR_ELx_AR_SHIFT); + pr_alert(" Xs = %llu\n", + (iss2 & ESR_ELx_Xs_MASK) >> ESR_ELx_Xs_SHIFT); } else { pr_alert(" ISV = 0, ISS = 0x%08lx, ISS2 = 0x%08lx\n", esr & ESR_ELx_ISS_MASK, iss2); @@ -87,11 +89,10 @@ static void data_abort_decode(unsigned long esr) (iss2 & ESR_ELx_TnD) >> ESR_ELx_TnD_SHIFT, (iss2 & ESR_ELx_TagAccess) >> ESR_ELx_TagAccess_SHIFT); - pr_alert(" GCS = %ld, Overlay = %lu, DirtyBit = %lu, Xs = %llu\n", + pr_alert(" GCS = %ld, Overlay = %lu, DirtyBit = %lu\n", (iss2 & ESR_ELx_GCS) >> ESR_ELx_GCS_SHIFT, (iss2 & ESR_ELx_Overlay) >> ESR_ELx_Overlay_SHIFT, - (iss2 & ESR_ELx_DirtyBit) >> ESR_ELx_DirtyBit_SHIFT, - (iss2 & ESR_ELx_Xs_MASK) >> ESR_ELx_Xs_SHIFT); + (iss2 & ESR_ELx_DirtyBit) >> ESR_ELx_DirtyBit_SHIFT); } static void mem_abort_decode(unsigned long esr) From e0b291fe117964037e0ba382eff4bb365d531c3a Mon Sep 17 00:00:00 2001 From: "Chia-Lin Kao (AceLan)" Date: Thu, 11 Jun 2026 15:12:01 +0800 Subject: [PATCH 048/456] usb: core: port: Deattach Type-C connector on component unbind connector_unbind() is the mirror of connector_bind(), but it is missing the symmetric call to typec_deattach() that connector_bind() makes via: if (port_dev->child) typec_attach(port_dev->connector, &port_dev->child->dev); When a Thunderbolt dock is unplugged, two teardown paths race: 1. The component framework calls connector_unbind() first, which sets port_dev->connector = NULL without calling typec_deattach(). This leaves port->usb2_dev/port->usb3_dev in struct typec_port pointing at the USB device that is about to be freed. 2. usb_disconnect() then calls typec_deattach(port_dev->connector, ...), but port_dev->connector is already NULL, so the call is a no-op and port->usb2_dev is never cleared. 3. Concurrently, UCSI detects a PD partner-disconnect event and calls typec_unregister_partner(), which reads port->usb2_dev (now a dangling pointer to freed memory) and passes it to typec_partner_unlink_device() -> sysfs_remove_link() -> dev_name() on the freed device, corrupting the typec/UCSI partner state. This corruption leaves the Thunderbolt tunnel in an inconsistent state on the next dock hot-plug. On affected hardware the dock's I225/igc NIC fails to enumerate: AER fires a slot reset while the igc driver is still initialising ("PCIe link lost"), and the subsequent igc_reset attempt hits igc_rd32 on an already-detached device: igc 0000:2e:00.0 eth0: PCIe link lost, device now detached igc: Failed to read reg 0x0! WARNING: CPU: 9 PID: 129 at drivers/net/ethernet/intel/igc/igc_main.c:7005 igc_rd32+0xa4/0xc0 [igc] Call Trace: igc_disable_pcie_master+0x16/0xa0 [igc] igc_reset_hw_base+0x14/0x170 [igc] igc_reset+0x63/0x110 [igc] igc_io_slot_reset+0x9e/0xd0 [igc] report_slot_reset+0x5d/0xc0 pcie_do_recovery+0x209/0x400 aer_isr_one_error_type+0x235/0x430 aer_isr+0x4e/0x80 irq_thread+0xf4/0x1f0 4. UCSI later handles the PD partner-disconnect and calls typec_unregister_partner(), which still sees the stale port->usb2_dev and tries to remove its sysfs link a second time: kernfs: can not remove 'typec', no directory WARNING: CPU: 6 PID: 55 at fs/kernfs/dir.c:1706 kernfs_remove_by_name_ns+0xe9/0xf0 Workqueue: events ucsi_handle_connector_change [typec_ucsi] Call Trace: sysfs_remove_link+0x19/0x50 typec_unregister_partner+0x6e/0x120 [typec] ucsi_unregister_partner+0x107/0x150 [typec_ucsi] ucsi_handle_connector_change+0x3ec/0x490 [typec_ucsi] process_one_work+0x18e/0x3e0 worker_thread+0x2e3/0x420 kthread+0x10a/0x230 ret_from_fork+0x121/0x140 ret_from_fork_asm+0x1a/0x30 With worse timing the same stale pointer is dereferenced after the backing memory is freed, turning the warning into a use-after-free. Fix the asymmetry: call typec_deattach() before clearing port_dev->connector, matching what connector_bind() does on the bind side. typec_partner_deattach() is already protected by port->partner_link_lock, so it serialises safely with the concurrent typec_unregister_partner() path. Fixes: 11110783f5ea ("usb: Inform the USB Type-C class about enumerated devices") Cc: stable Signed-off-by: Chia-Lin Kao (AceLan) Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260611071201.1235545-1-acelan.kao@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/port.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index b1364f0c384c..b4452b665f59 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -740,6 +740,8 @@ static void connector_unbind(struct device *dev, struct device *connector, void sysfs_remove_link(&connector->kobj, dev_name(dev)); sysfs_remove_link(&dev->kobj, "connector"); + if (port_dev->child) + typec_deattach(port_dev->connector, &port_dev->child->dev); port_dev->connector = NULL; } From c947360ae63eee1c9eacc030dd6f5a53f717addf Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 13 Jul 2026 19:47:11 +0800 Subject: [PATCH 049/456] usb: musb: omap2430: Do not put borrowed of_node in probe omap2430_probe() stores pdev->dev.of_node in a local np variable. This is a borrowed pointer and the probe function does not take a reference to it. The success and error paths nevertheless call of_node_put(np). This drops a reference that is owned by the platform device, and can leave pdev->dev.of_node with an unbalanced reference count. Do not put the borrowed platform device node from omap2430_probe(). References taken for the child MUSB device are handled by the device core, and the ctrl-module phandle reference is still released separately. Fixes: ffbe2feac59b ("usb: musb: omap2430: Fix probe regression for missing resources") Cc: stable Reviewed-by: Johan Hovold Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260713114711.955253-1-lgs201920130244@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/omap2430.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 333ab79f0ca9..6e749faac33c 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -454,7 +454,6 @@ static int omap2430_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to register musb device\n"); goto err_disable_rpm; } - of_node_put(np); return 0; @@ -464,7 +463,6 @@ static int omap2430_probe(struct platform_device *pdev) if (!IS_ERR(glue->control_otghs)) put_device(glue->control_otghs); err_put_musb: - of_node_put(np); platform_device_put(musb); return ret; From 4e0197fbb0eec588795d5431716a244d9ac8fa93 Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Wed, 15 Jul 2026 16:59:05 +0200 Subject: [PATCH 050/456] usb: core: sysfs: add lock to bos_descriptors_read() Add a lock to the function bos_descriptors_read(). This function accesses udev->bos, which could be simultaneously freed in usb_reset_and_verify_device(), a function that is commonly called in drivers all over the kernel. Assisted-by: gkh_clanker_t1000 Signed-off-by: Griffin Kroah-Hartman Acked-by: Alan Stern Cc: stable Link: https://patch.msgid.link/20260715-usb_core_patches_3-v1-1-53021f5576fd@kroah.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/sysfs.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index a07866f1060c..d22dc78457d7 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -899,10 +899,15 @@ bos_descriptors_read(struct file *filp, struct kobject *kobj, { struct device *dev = kobj_to_dev(kobj); struct usb_device *udev = to_usb_device(dev); - struct usb_host_bos *bos = udev->bos; + struct usb_host_bos *bos; struct usb_bos_descriptor *desc; size_t desclen, n = 0; + int rc; + rc = usb_lock_device_interruptible(udev); + if (rc < 0) + return -EINTR; + bos = udev->bos; if (bos) { desc = bos->desc; desclen = le16_to_cpu(desc->wTotalLength); @@ -911,6 +916,7 @@ bos_descriptors_read(struct file *filp, struct kobject *kobj, memcpy(buf, (void *) desc + off, n); } } + usb_unlock_device(udev); return n; } static const BIN_ATTR_RO(bos_descriptors, 65535); /* max-size BOS */ From fc3afb5728e297994863f8a2a01b88a920bbf53e Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 16 Jul 2026 18:41:26 +0800 Subject: [PATCH 051/456] usb: chipidea: fix usage_count leak when autosuspend_delay is negative The probe() calls pm_runtime_use_autosuspend(), but remove() does not call pm_runtime_dont_use_autosuspend(). This can lead to a usage_count leak if autosuspend_delay is set to a negative value. The pm_runtime_use_autosuspend() also notes that it's important to undo this with pm_runtime_dont_use_autosuspend() at driver exit time. Fixes: 1f874edcb731 ("usb: chipidea: add runtime power management support") Cc: stable Assisted-by: Claude:claude-sonnet-4.6 Signed-off-by: Xu Yang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260716104126.2763454-1-xu.yang_2@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 07563be0013f..b53672f3ea63 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -1250,6 +1250,7 @@ static void ci_hdrc_remove(struct platform_device *pdev) usb_role_switch_unregister(ci->role_switch); if (ci->supports_runtime_pm) { + pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_get_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); From d5e5cd3654d2b5359a12ea6586120f05b28634ee Mon Sep 17 00:00:00 2001 From: Jinchao Wang Date: Thu, 16 Jul 2026 06:42:17 -0400 Subject: [PATCH 052/456] usb: gadget: dummy_hcd: prevent fifo_req reuse during giveback dummy_hcd embeds a single shared usb_request (dum->fifo_req) that the "emulated single-request FIFO" fast-path in dummy_queue() reuses for small IN transfers: it copies the caller's request into it (req->req = *_req) and queues it, treating list_empty(&fifo_req.queue) as "the slot is free". The completion side (dummy_timer/transfer/nuke/dummy_dequeue) follows the standard pattern: list_del_init(&req->queue) unlinks the request, then the lock is dropped and usb_gadget_giveback_request() invokes req->complete(). But list_del_init() makes fifo_req.queue look empty *before* the completion callback returns, so a concurrent dummy_queue() on another CPU sees the slot as free, reuses fifo_req and runs req->req = *_req -- overwriting req->complete while dummy_timer is mid-calling it. The indirect call then jumps to a clobbered pointer, causing a general protection fault / page fault in dummy_timer (syzkaller extid faf3a6cf579fc65591ca). The clobbering write is an in-bounds memcpy on a live shared object, so KASAN cannot flag it. Add a fifo_req_busy bit covering the shared request's whole lifetime: set it in dummy_queue() when the FIFO fast-path takes fifo_req (making it the fast-path guard, replacing the list_empty(&fifo_req.queue) test), and clear it after the completion callback has returned, via a dummy_giveback() helper used at all four gadget-request giveback sites. The shared slot can no longer be reused until its completion callback has finished. Reported-by: syzbot+faf3a6cf579fc65591ca@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=faf3a6cf579fc65591ca Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable Signed-off-by: Jinchao Wang Reviewed-by: Alan Stern Link: https://patch.msgid.link/5db8bba5b3499a86cd2e776f9918126b68b2508b.1784198306.git.wangjinchao600@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/dummy_hcd.c | 40 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index f47903461ed5..c0e40fa6dde5 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -278,6 +278,7 @@ struct dummy { unsigned ints_enabled:1; unsigned udc_suspended:1; unsigned pullup:1; + unsigned fifo_req_busy:1; /* * HOST side support @@ -329,6 +330,26 @@ static inline struct dummy *gadget_dev_to_dummy(struct device *dev) /* DEVICE/GADGET SIDE UTILITY ROUTINES */ +/* + * Give back a gadget request with dum->lock dropped around the callback. + * If @req is the shared fifo_req, clear fifo_req_busy afterward: the flag + * was set in dummy_queue() when the shared request was taken and must stay + * set until its completion callback has returned; list_del_init() alone + * makes the request look idle while the callback is still running. + * Caller holds dum->lock and has already done list_del_init() + status. + */ +static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, + struct dummy_request *req) +{ + bool fifo = req == &dum->fifo_req; + + spin_unlock(&dum->lock); + usb_gadget_giveback_request(_ep, &req->req); + spin_lock(&dum->lock); + if (fifo) + dum->fifo_req_busy = 0; +} + /* called with spinlock held */ static void nuke(struct dummy *dum, struct dummy_ep *ep) { @@ -339,9 +360,7 @@ static void nuke(struct dummy *dum, struct dummy_ep *ep) list_del_init(&req->queue); req->req.status = -ESHUTDOWN; - spin_unlock(&dum->lock); - usb_gadget_giveback_request(&ep->ep, &req->req); - spin_lock(&dum->lock); + dummy_giveback(dum, &ep->ep, req); } } @@ -728,10 +747,11 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, /* implement an emulated single-request FIFO */ if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && - list_empty(&dum->fifo_req.queue) && + !dum->fifo_req_busy && list_empty(&ep->queue) && _req->length <= FIFO_SIZE) { req = &dum->fifo_req; + dum->fifo_req_busy = 1; req->req = *_req; req->req.buf = dum->fifo_buf; memcpy(dum->fifo_buf, _req->buf, _req->length); @@ -785,9 +805,7 @@ static int dummy_dequeue(struct usb_ep *_ep, struct usb_request *_req) dev_dbg(udc_dev(dum), "dequeued req %p from %s, len %d buf %p\n", req, _ep->name, _req->length, _req->buf); - spin_unlock(&dum->lock); - usb_gadget_giveback_request(_ep, _req); - spin_lock(&dum->lock); + dummy_giveback(dum, _ep, req); } spin_unlock_irqrestore(&dum->lock, flags); return retval; @@ -1523,9 +1541,7 @@ static int transfer(struct dummy_hcd *dum_hcd, struct urb *urb, if (req->req.status != -EINPROGRESS) { list_del_init(&req->queue); - spin_unlock(&dum->lock); - usb_gadget_giveback_request(&ep->ep, &req->req); - spin_lock(&dum->lock); + dummy_giveback(dum, &ep->ep, req); /* requests might have been unlinked... */ rescan = 1; @@ -1910,9 +1926,7 @@ static enum hrtimer_restart dummy_timer(struct hrtimer *t) dev_dbg(udc_dev(dum), "stale req = %p\n", req); - spin_unlock(&dum->lock); - usb_gadget_giveback_request(&ep->ep, &req->req); - spin_lock(&dum->lock); + dummy_giveback(dum, &ep->ep, req); ep->already_seen = 0; goto restart; } From 67c92c6419ea6dbc5b1f3e9691aecea956e3e81c Mon Sep 17 00:00:00 2001 From: "Chia-Lin Kao (AceLan)" Date: Mon, 13 Jul 2026 16:43:21 +0800 Subject: [PATCH 053/456] usb: typec: ucsi: Detect and skip duplicate altmodes from buggy firmware Some firmware implementations incorrectly return the same altmode multiple times at different offsets when queried via UCSI_GET_ALTERNATE_MODES. This causes sysfs duplicate filename errors and kernel call traces when the driver attempts to register the same altmode twice: sysfs: cannot create duplicate filename '/devices/.../typec/port0/port0.0/partner' typec-thunderbolt port0-partner.1: failed to create symlinks typec-thunderbolt port0-partner.1: probe with driver typec-thunderbolt failed with error -17 The matching rules differ by recipient: - UCSI_RECIPIENT_CON (port) and UCSI_RECIPIENT_SOP_P (plug): Two altmodes with identical SVID and VDO are byte-for-byte duplicates and the second has no observable function, so drop it. - UCSI_RECIPIENT_SOP (partner): The typec class binds each partner altmode to a port altmode of the same SVID via altmode_match()/device_find_child(), which returns the first port altmode with a matching SVID. If the partner advertises more altmodes for SVID X than the port advertises, the surplus partner altmode(s) collapse onto an already-paired port altmode and trigger the "duplicate filename .../partner" sysfs error during typec_altmode_create_links(). Use the port-side altmode count for SVID X as the authoritative cap and reject any partner altmode that would exceed it. This preserves legitimate multi-Mode partner altmodes (vendor SVIDs that the port really does advertise more than once) while filtering the firmware-generated duplicates that have no port counterpart, and is therefore stricter than a plain SVID+VDO comparison (which still admits the Thunderbolt case where firmware reports the same SVID twice with different VDOs) without being over-broad like a plain SVID match (which would falsely drop legitimate vendor multi-Mode entries). If a duplicate is detected, skip it and emit a clean warning instead of generating a kernel call trace: ucsi_acpi USBC000:00: con2: Firmware bug: duplicate partner altmode SVID 0x8087 at offset 1, ignoring. ucsi_acpi USBC000:00: con2: VDO mismatch: 0x8087a043 vs 0x00000001 The duplicate detection logic lives in a reusable helper ucsi_altmode_is_duplicate() and is invoked from ucsi_register_altmodes(). It applies to all three recipient types: partner (SOP), port (CON), and plug (SOP_P) altmodes. Fixes: a79f16efcd00 ("usb: typec: ucsi: Add support for the partner USB Modes") Cc: stable Signed-off-by: Chia-Lin Kao (AceLan) unchanged: still SVID+VDO exact-dup match. Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260713084323.287516-1-acelan.kao@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 132 ++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index f56897e23df8..0db43521af3f 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -529,6 +529,129 @@ static int ucsi_register_altmode(struct ucsi_connector *con, return ret; } +static void ucsi_dump_duplicate_altmode(struct ucsi_connector *con, + u8 recipient, u16 svid, + u32 existing_vdo, u32 new_vdo, + int offset) +{ + static const char * const recipient_names[] = { + [UCSI_RECIPIENT_CON] = "port", + [UCSI_RECIPIENT_SOP] = "partner", + [UCSI_RECIPIENT_SOP_P] = "plug", + [UCSI_RECIPIENT_SOP_PP] = "cable plug prime", + }; + + dev_warn(con->ucsi->dev, + "con%d: Firmware bug: duplicate %s altmode SVID 0x%04x at offset %d, ignoring but please contact the BIOS vendor to fix this issue.\n", + con->num, recipient_names[recipient], svid, offset); + + if (existing_vdo != new_vdo) + dev_warn(con->ucsi->dev, + "con%d: VDO mismatch: 0x%08x vs 0x%08x\n", + con->num, existing_vdo, new_vdo); +} + +/* Count altmodes in @altmodes that advertise @svid. */ +static int ucsi_altmode_count_svid(struct typec_altmode **altmodes, u16 svid) +{ + int count = 0; + int k; + + for (k = 0; k < UCSI_MAX_ALTMODES; k++) { + if (!altmodes[k]) + break; + if (altmodes[k]->svid == svid) + count++; + } + + return count; +} + +/* + * Check if an altmode is a duplicate. Some firmware implementations + * incorrectly return the same altmode multiple times, causing sysfs errors. + * Returns true if the altmode should be skipped. + * + * The matching rules differ by recipient: + * + * - UCSI_RECIPIENT_CON (port) and UCSI_RECIPIENT_SOP_P (plug): + * Two altmodes with identical SVID and VDO are byte-for-byte duplicates + * and the second has no observable function. Drop them. + * + * - UCSI_RECIPIENT_SOP (partner): + * The typec class binds each partner altmode to a port altmode of the + * same SVID via altmode_match()/device_find_child(), which returns the + * first port altmode with a matching SVID. If the partner advertises + * more altmodes for SVID X than the port advertises, the surplus + * partner altmode(s) collapse onto an already-paired port altmode and + * trigger a "duplicate filename .../partner" sysfs error during + * typec_altmode_create_links(). Use the port-side altmode count for + * SVID X as the authoritative cap and reject any partner altmode that + * would exceed it. This preserves legitimate multi-Mode partner + * altmodes (e.g. vendor SVIDs that the port really does advertise + * twice) while filtering the firmware-generated duplicates that have + * no port counterpart. + */ +static bool ucsi_altmode_is_duplicate(struct ucsi_connector *con, u8 recipient, + const struct ucsi_altmode *alt_batch, int batch_idx, + u16 svid, u32 vdo, int offset) +{ + struct typec_altmode **altmodes; + int port_count, partner_count; + int k; + + /* Check for duplicates within the current batch first */ + for (k = 0; k < batch_idx; k++) { + if (alt_batch[k].svid == svid && alt_batch[k].mid == vdo) { + ucsi_dump_duplicate_altmode(con, recipient, svid, + vdo, vdo, offset); + return true; + } + } + + switch (recipient) { + case UCSI_RECIPIENT_SOP: + /* + * Cap partner altmodes per SVID by the port-side count: + * any further partner altmode for that SVID would alias an + * already-paired port altmode and break typec sysfs. + */ + port_count = ucsi_altmode_count_svid(con->port_altmode, svid); + partner_count = ucsi_altmode_count_svid(con->partner_altmode, + svid); + if (port_count && partner_count >= port_count) { + ucsi_dump_duplicate_altmode(con, recipient, svid, + con->partner_altmode[partner_count - 1]->vdo, + vdo, offset); + return true; + } + return false; + case UCSI_RECIPIENT_CON: + altmodes = con->port_altmode; + break; + case UCSI_RECIPIENT_SOP_P: + altmodes = con->plug_altmode; + break; + default: + return false; + } + + /* CON and SOP_P: drop only exact SVID+VDO duplicates. */ + for (k = 0; k < UCSI_MAX_ALTMODES; k++) { + if (!altmodes[k]) + break; + + if (altmodes[k]->svid != svid || altmodes[k]->vdo != vdo) + continue; + + ucsi_dump_duplicate_altmode(con, recipient, svid, + altmodes[k]->vdo, vdo, offset); + return true; + } + + return false; +} + static int ucsi_register_altmodes_nvidia(struct ucsi_connector *con, u8 recipient) { @@ -653,6 +776,15 @@ static int ucsi_register_altmodes(struct ucsi_connector *con, u8 recipient) if (!alt[j].svid) return 0; + /* + * Check for duplicates in current batch and already + * registered altmodes. Skip if duplicate found. + */ + if (ucsi_altmode_is_duplicate(con, recipient, alt, j, + alt[j].svid, alt[j].mid, + i - num + j)) + continue; + memset(&desc, 0, sizeof(desc)); desc.vdo = alt[j].mid; desc.svid = alt[j].svid; From f1aa17f72f9b9589bd724dc826c5b17d164193d1 Mon Sep 17 00:00:00 2001 From: "Chia-Lin Kao (AceLan)" Date: Mon, 13 Jul 2026 16:43:22 +0800 Subject: [PATCH 054/456] usb: typec: ucsi: Add duplicate detection to nvidia registration path Extend the duplicate altmode detection to ucsi_register_altmodes_nvidia() which is used when a driver provides the update_altmodes() callback. This ensures all drivers benefit from duplicate detection, whether they use the standard registration path or the nvidia path with update_altmodes callback. Without this fix, drivers using the nvidia path (like yoga_c630) would still encounter duplicate altmode registration errors from buggy firmware. Fixes: a79f16efcd00 ("usb: typec: ucsi: Add support for the partner USB Modes") Cc: stable Signed-off-by: Chia-Lin Kao (AceLan) Link: https://patch.msgid.link/20260713084323.287516-2-acelan.kao@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 0db43521af3f..1ae4224d2dfc 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -706,19 +706,25 @@ ucsi_register_altmodes_nvidia(struct ucsi_connector *con, u8 recipient) /* now register altmodes */ for (i = 0; i < max_altmodes; i++) { - memset(&desc, 0, sizeof(desc)); - if (multi_dp) { - desc.svid = updated[i].svid; - desc.vdo = updated[i].mid; - } else { - desc.svid = orig[i].svid; - desc.vdo = orig[i].mid; - } - desc.roles = TYPEC_PORT_DRD; + struct ucsi_altmode *altmode_array = multi_dp ? updated : orig; - if (!desc.svid) + if (!altmode_array[i].svid) return 0; + /* + * Check for duplicates in current array and already + * registered altmodes. Skip if duplicate found. + */ + if (ucsi_altmode_is_duplicate(con, recipient, altmode_array, i, + altmode_array[i].svid, + altmode_array[i].mid, i)) + continue; + + memset(&desc, 0, sizeof(desc)); + desc.svid = altmode_array[i].svid; + desc.vdo = altmode_array[i].mid; + desc.roles = TYPEC_PORT_DRD; + ret = ucsi_register_altmode(con, &desc, recipient); if (ret) return ret; From 8706409bd45fb89350f213b8baac5f16a43bb4a4 Mon Sep 17 00:00:00 2001 From: "Chia-Lin Kao (AceLan)" Date: Mon, 13 Jul 2026 16:43:23 +0800 Subject: [PATCH 055/456] usb: typec: ucsi: yoga_c630: Remove redundant duplicate altmode handling This reverts commit e0c48e42d818 ("usb: typec: ucsi: yoga-c630: remove duplicate AltModes"). The yoga_c630 driver previously implemented its own duplicate altmode detection in yoga_c630_ucsi_update_altmodes() to work around buggy EC firmware that returns duplicate AltModes instead of empty ones. With the introduction of the common ucsi_altmode_is_duplicate() helper in both the standard and nvidia registration paths, duplicate detection is now handled automatically in the core UCSI code. This makes the yoga_c630-specific implementation added in commit e0c48e42d818 ("usb: typec: ucsi: yoga-c630: remove duplicate AltModes") redundant. Remove yoga_c630_ucsi_update_altmodes() and its callback to eliminate code duplication and simplify the driver. Note that this causes the driver to switch back from the nvidia registration path to the standard path, which is the original behavior before commit e0c48e42d818 ("usb: typec: ucsi: yoga-c630: remove duplicate AltModes"). Both paths now include duplicate detection, ensuring the firmware bug is still properly handled. Signed-off-by: Chia-Lin Kao (AceLan) Link: https://patch.msgid.link/20260713084323.287516-3-acelan.kao@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_yoga_c630.c | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c index 1be18d101842..bb7b09bc7c1c 100644 --- a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c +++ b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c @@ -139,28 +139,6 @@ static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi, return ret; } -static bool yoga_c630_ucsi_update_altmodes(struct ucsi *ucsi, - u8 recipient, - struct ucsi_altmode *orig, - struct ucsi_altmode *updated) -{ - int i; - - if (orig[0].svid == 0 || recipient != UCSI_RECIPIENT_SOP) - return false; - - /* EC is nice and repeats altmodes again and again. Ignore copies. */ - for (i = 1; i < UCSI_MAX_ALTMODES; i++) { - if (orig[i].svid == orig[0].svid) { - dev_dbg(ucsi->dev, "Found duplicate altmodes, starting from %d\n", i); - memset(&orig[i], 0, (UCSI_MAX_ALTMODES - i) * sizeof(*orig)); - break; - } - } - - return false; -} - static void yoga_c630_ucsi_update_connector(struct ucsi_connector *con) { if (con->num == 1) @@ -174,7 +152,6 @@ static const struct ucsi_operations yoga_c630_ucsi_ops = { .read_message_in = yoga_c630_ucsi_read_message_in, .sync_control = yoga_c630_ucsi_sync_control, .async_control = yoga_c630_ucsi_async_control, - .update_altmodes = yoga_c630_ucsi_update_altmodes, .update_connector = yoga_c630_ucsi_update_connector, }; From a19bda861b3a79e25417462539df8b0d77c6b322 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 16 Jul 2026 10:22:43 +0000 Subject: [PATCH 056/456] rust: device: avoid trailing ; in printing macros These macros are used like expressions, so they should not emit a semicolon. This is being turned into a hard error in a future release of Rust. error: trailing semicolon in macro used in expression position --> drivers/gpu/nova-core/firmware/fsp.rs:79:34 | 79 | .inspect_err(|_| dev_err!(dev, "FMC firmware missing '{}' section\n", name)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #79813 = note: this error originates in the macro `dev_err` (in Nightly builds, run with -Z macro-backtrace for more info) [ I was doubly surprised since upstream made it a deny-by-default lint a year ago for Rust 1.91.0, and yet we didn't see it; plus I hadn't seen this in my CI even yesterday. It turns out this just landed into today's nightly (nightly-2026-07-16, using upstream commit d0babd8b6): Link: https://github.com/rust-lang/rust/pull/159222 which says: "The `semicolon_in_expressions_from_macros` lint previously suppressed warnings about non-local macros. This masks a lint that will subsequently become a hard error." So that explains it. And this is the PR that will make it a hard error at some point in the future: Link: https://github.com/rust-lang/rust/pull/159218 Thus starting with Rust 1.99.0 (expected 2026-10-01), we will be seeing the deny-by-default lint above, so clean it up already. - Miguel ] Cc: stable@vger.kernel.org # Needed in 6.18.y and later. Link: https://github.com/rust-lang/rust/issues/79813 Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Acked-by: Danilo Krummrich Link: https://github.com/rust-lang/rust/pull/159218 Link: https://github.com/rust-lang/rust/pull/159222 Link: https://patch.msgid.link/20260716-device-trail-semicolon-v1-1-f48e9dcfae15@google.com [ Fixed typo. ] Signed-off-by: Miguel Ojeda --- rust/kernel/device.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 645afc49a27d..1a38b3bbdfb7 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -708,9 +708,7 @@ macro_rules! impl_device_context_into_aref { #[macro_export] macro_rules! dev_printk { ($method:ident, $dev:expr, $($f:tt)*) => { - { - $crate::device::Device::$method($dev.as_ref(), $crate::prelude::fmt!($($f)*)) - } + $crate::device::Device::$method($dev.as_ref(), $crate::prelude::fmt!($($f)*)) } } @@ -737,7 +735,7 @@ macro_rules! dev_printk { /// ``` #[macro_export] macro_rules! dev_emerg { - ($($f:tt)*) => { $crate::dev_printk!(pr_emerg, $($f)*); } + ($($f:tt)*) => { $crate::dev_printk!(pr_emerg, $($f)*) } } /// Prints an alert-level message (level 1) prefixed with device information. @@ -763,7 +761,7 @@ macro_rules! dev_emerg { /// ``` #[macro_export] macro_rules! dev_alert { - ($($f:tt)*) => { $crate::dev_printk!(pr_alert, $($f)*); } + ($($f:tt)*) => { $crate::dev_printk!(pr_alert, $($f)*) } } /// Prints a critical-level message (level 2) prefixed with device information. @@ -789,7 +787,7 @@ macro_rules! dev_alert { /// ``` #[macro_export] macro_rules! dev_crit { - ($($f:tt)*) => { $crate::dev_printk!(pr_crit, $($f)*); } + ($($f:tt)*) => { $crate::dev_printk!(pr_crit, $($f)*) } } /// Prints an error-level message (level 3) prefixed with device information. @@ -815,7 +813,7 @@ macro_rules! dev_crit { /// ``` #[macro_export] macro_rules! dev_err { - ($($f:tt)*) => { $crate::dev_printk!(pr_err, $($f)*); } + ($($f:tt)*) => { $crate::dev_printk!(pr_err, $($f)*) } } /// Prints a warning-level message (level 4) prefixed with device information. @@ -841,7 +839,7 @@ macro_rules! dev_err { /// ``` #[macro_export] macro_rules! dev_warn { - ($($f:tt)*) => { $crate::dev_printk!(pr_warn, $($f)*); } + ($($f:tt)*) => { $crate::dev_printk!(pr_warn, $($f)*) } } /// Prints a notice-level message (level 5) prefixed with device information. @@ -867,7 +865,7 @@ macro_rules! dev_warn { /// ``` #[macro_export] macro_rules! dev_notice { - ($($f:tt)*) => { $crate::dev_printk!(pr_notice, $($f)*); } + ($($f:tt)*) => { $crate::dev_printk!(pr_notice, $($f)*) } } /// Prints an info-level message (level 6) prefixed with device information. @@ -893,7 +891,7 @@ macro_rules! dev_notice { /// ``` #[macro_export] macro_rules! dev_info { - ($($f:tt)*) => { $crate::dev_printk!(pr_info, $($f)*); } + ($($f:tt)*) => { $crate::dev_printk!(pr_info, $($f)*) } } /// Prints a debug-level message (level 7) prefixed with device information. @@ -919,5 +917,5 @@ macro_rules! dev_info { /// ``` #[macro_export] macro_rules! dev_dbg { - ($($f:tt)*) => { $crate::dev_printk!(pr_dbg, $($f)*); } + ($($f:tt)*) => { $crate::dev_printk!(pr_dbg, $($f)*) } } From 71132cedd1ecbc4032d76e9928c18a10f7e39b80 Mon Sep 17 00:00:00 2001 From: Diego Fernando Mancera Gomez Date: Fri, 17 Jul 2026 02:07:04 -0600 Subject: [PATCH 057/456] usb: atm: ueagle-atm: reject descriptors that confuse probe and disconnect uea_probe() distinguishes a pre-firmware device from a post-firmware one using the USB id (UEA_IS_PREFIRM()), and stores a different object as the interface data in each case: a 'struct completion' for a pre-firmware device (to be waited on in .disconnect()), or a 'struct usbatm_data' for a post-firmware one. uea_disconnect() instead tells the two apart by the number of interfaces of the active configuration (a pre-firmware device exposes a single interface, ADI930 has 2 and eagle has 3), and casts the interface data accordingly. Because the two handlers use different criteria, a crafted device that advertises a pre-firmware id together with a multi-interface descriptor (or a post-firmware id with a single interface) makes them disagree: the small 'struct completion' stored by uea_probe() is then passed to usbatm_usb_disconnect(), which casts it to 'struct usbatm_data' and takes instance->serialize, reading past the end of the allocation: BUG: KASAN: slab-out-of-bounds in __mutex_lock+0x152a/0x1b80 Read of size 8 at addr ffff8880470e2c60 by task kworker/1:2/982 ... __mutex_lock+0x152a/0x1b80 usbatm_usb_disconnect+0x70/0x820 uea_disconnect+0x133/0x2c0 usb_unbind_interface+0x1dd/0x9e0 ... which belongs to the cache kmalloc-96 of size 96 The buggy address is located 0 bytes to the right of allocated 96-byte region [ffff8880470e2c00, ffff8880470e2c60) Reject such inconsistent descriptors in uea_probe() so that both handlers always make the same pre/post-firmware decision. Reported-by: syzbot+e62a973f8322b3bbe3ac@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e62a973f8322b3bbe3ac Fixes: e2674dfbed8a ("usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect()") Signed-off-by: Diego Fernando Mancera Gomez Acked-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260717080704.1264-1-diegomancera.dev@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/ueagle-atm.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c index 4e71ed679a76..4266a0cb7e3b 100644 --- a/drivers/usb/atm/ueagle-atm.c +++ b/drivers/usb/atm/ueagle-atm.c @@ -2549,6 +2549,7 @@ static struct usbatm_driver uea_usbatm_driver = { static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *usb = interface_to_usbdev(intf); + bool single_iface = usb->config->desc.bNumInterfaces == 1; int ret; uea_dbg(usb, "ADSL device found with vid (%#X) pid (%#X) Rev (%#X): %s\n", @@ -2557,6 +2558,22 @@ static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) le16_to_cpu(usb->descriptor.bcdDevice), chip_name[UEA_CHIP_VERSION(id)]); + /* + * uea_probe() decides between the pre-firmware and post-firmware case + * from the USB id and stores a different object as interface data in + * each case: a struct completion for a pre-firmware device, a struct + * usbatm_data for a post-firmware one. uea_disconnect() instead tells + * the two apart by the number of interfaces (a pre-firmware device + * exposes a single interface, ADI930 has 2 and eagle has 3). A crafted + * device advertising a pre-firmware id together with a multi-interface + * descriptor (or the other way around) makes the two disagree, so that + * usbatm_usb_disconnect() treats the small completion object as a + * struct usbatm_data and reads out of bounds. Reject such inconsistent + * descriptors so both paths make the same decision. + */ + if (UEA_IS_PREFIRM(id) != single_iface) + return -ENODEV; + usb_reset_device(usb); if (UEA_IS_PREFIRM(id)) { From 7fb13fd7e9a59a37cd911efff83abe19e3ee029d Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Wed, 15 Jul 2026 15:35:46 +0800 Subject: [PATCH 058/456] serial: 8250_mid: Fix NULL function pointer dereference on DNV/ICX-D/SNR platforms Commit b1b4efea05a5 ("serial: 8250_mid: Disable DMA for selected platforms") replaced the dnv_board setup and exit callbacks with PTR_IF(false, ...), which evaluates to NULL. However, the three call sites in mid8250_probe() and mid8250_remove() unconditionally dereference these function pointers without NULL checks, causing a NULL pointer dereference (kernel oops) on any Denverton (DNV), Ice Lake Xeon D (ICX-D/CDF), or Snowridge (SNR) platform. Fix this by adding the missing NULL checks before calling the setup and exit callbacks. Fixes: b1b4efea05a5 ("serial: 8250_mid: Disable DMA for selected platforms") Cc: stable Reviewed-by: Andy Shevchenko Signed-off-by: Jiangshan Yi Link: https://patch.msgid.link/20260715073546.1875083-1-yijiangshan@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mid.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/tty/serial/8250/8250_mid.c b/drivers/tty/serial/8250/8250_mid.c index f88809ff370b..82656645b8a6 100644 --- a/drivers/tty/serial/8250/8250_mid.c +++ b/drivers/tty/serial/8250/8250_mid.c @@ -318,9 +318,11 @@ static int mid8250_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (!uart.port.membase) return -ENOMEM; - ret = mid->board->setup(mid, &uart.port); - if (ret) - return ret; + if (mid->board->setup) { + ret = mid->board->setup(mid, &uart.port); + if (ret) + return ret; + } ret = mid8250_dma_setup(mid, &uart); if (ret) @@ -336,7 +338,8 @@ static int mid8250_probe(struct pci_dev *pdev, const struct pci_device_id *id) return 0; err: - mid->board->exit(mid); + if (mid->board->exit) + mid->board->exit(mid); return ret; } @@ -346,7 +349,8 @@ static void mid8250_remove(struct pci_dev *pdev) serial8250_unregister_port(mid->line); - mid->board->exit(mid); + if (mid->board->exit) + mid->board->exit(mid); } static const struct mid8250_board pnw_board = { From af071d9e07e57cfff239e8d09d2f3b05ebc9c667 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 16 Jul 2026 17:08:09 -0400 Subject: [PATCH 059/456] serial: sc16is7xx: implement gpio get_direction() callback It's strongly recommended for GPIO drivers to always implement the .get_direction() callback - even when the direction is tracked in software. The GPIO core emits a warning when the callback is missing and a user reads the direction of a line, e.g. via /sys/kernel/debug/gpio. Fixes: dfeae619d781 ("serial: sc16is7xx") Cc: stable Signed-off-by: Hugo Villeneuve Acked-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260716210813.2582826-1-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index daebd92f32c7..6c97953d593b 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -1273,6 +1273,17 @@ static int sc16is7xx_gpio_set(struct gpio_chip *chip, unsigned int offset, return 0; } +static int sc16is7xx_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct sc16is7xx_port *s = gpiochip_get_data(chip); + struct uart_port *port = &s->p[0].port; + unsigned int val; + + val = sc16is7xx_port_read(port, SC16IS7XX_IODIR_REG); + + return val & BIT(offset) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + static int sc16is7xx_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { @@ -1350,6 +1361,7 @@ static int sc16is7xx_setup_gpio_chip(struct sc16is7xx_port *s) s->gpio.parent = dev; s->gpio.label = dev_name(dev); s->gpio.init_valid_mask = sc16is7xx_gpio_init_valid_mask; + s->gpio.get_direction = sc16is7xx_gpio_get_direction; s->gpio.direction_input = sc16is7xx_gpio_direction_input; s->gpio.get = sc16is7xx_gpio_get; s->gpio.direction_output = sc16is7xx_gpio_direction_output; From 30d49cba27f8905bc288cef5846963f0004f644c Mon Sep 17 00:00:00 2001 From: MinJea Kim Date: Tue, 14 Jul 2026 22:14:21 +0900 Subject: [PATCH 060/456] staging: rtl8723bs: fix inverted HT40 secondary channel offset rtw_get_chan_type() maps the driver's channel offset to nl80211 channel types the wrong way around. In this driver HAL_PRIME_CHNL_OFFSET_LOWER means the primary channel is the lower 20 MHz half of the 40 MHz pair, i.e. the secondary channel is above the primary one: rtw_get_center_ch() computes the center channel as "channel + 2" for OFFSET_LOWER, and bwmode_update_check() sets OFFSET_LOWER when the AP's HT operation IE announces SCA (secondary channel above). In nl80211 terms that is NL80211_CHAN_HT40PLUS, not HT40MINUS. Because of the inversion, cfg80211_rtw_get_channel() reports an HT40+ association as HT40-. For an HT40+ AP on a low channel (e.g. channel 3) the resulting chandef spans below the 2.4 GHz band edge and is invalid, so the regulatory core tears the connection down 60 seconds (REG_ENFORCE_GRACE_MS) after the AP's country IE triggers a regdomain change: reg_check_chans_work() considers the reported chandef unusable and calls cfg80211_leave(). The supplicant then reconnects, the country IE changes the regdomain again, and the cycle repeats, causing a disconnect/reconnect loop every ~65 seconds for as long as the link is up. Observed on a TECLAST X80 Power tablet (RTL8723BS) associated to an HT40+ AP on channel 3 with a KR country IE; a kprobe trace showed cfg80211_disconnect() being invoked from reg_check_chans_work(). With the mapping fixed, "iw dev wlan0 info" reports the correct "width: 40 MHz, center1: 2432 MHz" and the periodic disconnects stop. Fixes: 5402cc178c5d ("staging: rtl8723bs: add get_channel cfg80211 implementation") Cc: stable@vger.kernel.org Assisted-by: Claude-Code:claude-fable-5 bpftrace Signed-off-by: MinJea Kim Link: https://patch.msgid.link/20260714131421.3980-1-qndkdrnl@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c index 6a97afd89dc7..967cd1b34aed 100644 --- a/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c +++ b/drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c @@ -1957,7 +1957,7 @@ static u8 rtw_get_chan_type(struct adapter *adapter) else return NL80211_CHAN_NO_HT; case CHANNEL_WIDTH_40: - if (mlme_ext->cur_ch_offset == HAL_PRIME_CHNL_OFFSET_UPPER) + if (mlme_ext->cur_ch_offset == HAL_PRIME_CHNL_OFFSET_LOWER) return NL80211_CHAN_HT40PLUS; else return NL80211_CHAN_HT40MINUS; From 0e95ff792ae0aa6fbad9455943e9e1e4062670e9 Mon Sep 17 00:00:00 2001 From: Moksh Panicker Date: Thu, 25 Jun 2026 20:29:11 +0000 Subject: [PATCH 061/456] staging: rtl8723bs: fix OOB reads in rtw_get_wps_ie() rtw_get_wps_ie() iterates over IE data from network frames without validating that the IE header and payload fit within the remaining buffer before reading them. Specifically: - in_ie[cnt + 1] is read without checking cnt + 1 < in_len - memcmp(&in_ie[cnt + 2], ...) accesses cnt + 2 without bounds check - in_ie[cnt + 1] is used as length without verifying payload fits Add bounds checks at the top of the loop body to break early if fewer than 2 bytes remain for the IE header, or if the declared payload extends past the end of the buffer. Also require at least 4 bytes of payload before comparing the WPS OUI. Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable Signed-off-by: Moksh Panicker Link: https://patch.msgid.link/20260625202911.26782-1-mokshpanicker.7@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c index 54f805a6b5ce..863ddf846218 100644 --- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c @@ -670,7 +670,14 @@ u8 *rtw_get_wps_ie(u8 *in_ie, uint in_len, u8 *wps_ie, uint *wps_ielen) while (cnt < in_len) { eid = in_ie[cnt]; - if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt + 2], wps_oui, 4))) { + if (cnt + 2 > in_len) + break; + + if (in_ie[cnt + 1] + 2 > in_len - cnt) + break; + + if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (in_ie[cnt + 1] >= 4) && + (!memcmp(&in_ie[cnt + 2], wps_oui, 4))) { wpsie_ptr = &in_ie[cnt]; if (wps_ie) From 6815504f61e79383c77fa8fa8b219b1d6788475a Mon Sep 17 00:00:00 2001 From: Dave Penkler Date: Tue, 30 Jun 2026 17:55:17 +0200 Subject: [PATCH 062/456] MAINTAINERS: Add Greg Kroah-Hartman to GPIB Greg was not receiving patches for the GPIB subsystem from folks using scripts/get_maintainer.pl -f drivers/gpib. Signed-off-by: Dave Penkler Link: https://patch.msgid.link/20260630155517.5685-1-dpenkler@gmail.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 806bd2d80d15..e8704c09e0ed 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11107,6 +11107,7 @@ F: drivers/platform/x86/gpd-pocket-fan.c GPIB DRIVERS M: Dave Penkler +M: Greg Kroah-Hartman S: Maintained F: drivers/gpib/ F: include/uapi/linux/gpib.h From 87d3621ccc63b3999d756bb59f0cedd738c28eb3 Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Mon, 25 May 2026 14:04:22 +0200 Subject: [PATCH 063/456] uio_hv_generic: Bind to FCopy device by default The Hyper-V kernel-mode fcopy driver was removed in 6.10 and the new fcopy daemon requires this uio driver to function. However, by default the driver does not bind to any devices, and must be configured through the sysfs "new_id" file. Since the FCopy device is now only usable through this driver, add its ID to the driver's ID table so that the daemon will work "out of the box". Signed-off-by: Ben Hutchings Fixes: ec314f61e4fc ("Drivers: hv: Remove fcopy driver") Cc: stable Link: https://patch.msgid.link/ahQ6xuhSReidmN-3@decadent.org.uk Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_hv_generic.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c index 29ec2d15ada8..7b4cc456c453 100644 --- a/drivers/uio/uio_hv_generic.c +++ b/drivers/uio/uio_hv_generic.c @@ -396,9 +396,15 @@ hv_uio_remove(struct hv_device *dev) vmbus_free_ring(dev->channel); } +static const struct hv_vmbus_device_id hv_uio_id_table[] = { + { HV_FCOPY_GUID }, + {} +}; +MODULE_DEVICE_TABLE(vmbus, hv_uio_id_table); + static struct hv_driver hv_uio_drv = { .name = "uio_hv_generic", - .id_table = NULL, /* only dynamic id's */ + .id_table = hv_uio_id_table, .probe = hv_uio_probe, .remove = hv_uio_remove, }; From 17221216ae8ce6a24e8a4e787382e3ebc81b88a8 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Wed, 27 May 2026 13:51:03 +0100 Subject: [PATCH 064/456] comedi: comedi_parport: deal with premature interrupt Syzbot reported a general protection fault in `comedi_get_is_subdevice_running()`, which was called from the interrupt handler `parport_interrupt()` in the "comedi_parport" driver, but it does not currently have a C reproducer for the problem. It's probably due to a premature interrupt for one of two reasons: 1. The driver sets up the interrupt handler before the comedi subdevices used by the interrupt handler have been allocated, but does not disable the interrupt in the parallel port's CTRL register first. 2. The driver uses a user-supplied I/O port base address which Syzbot would have supplied, but it might not be backed by real parallel port hardware. Change the initialization order in the driver's comedi "attach" handler (`parport_attach()`) so that the hardware registers are initialized before the interrupt handler is requested. This should prevent premature interrupts occurring for real hardware. Also add a test to the interrupt handler to ensure the comedi device is fully attached and return early if it isn't. Fixes: 241ab6ad7108e ("Staging: comedi: add comedi_parport driver") Reported-by: syzbot+f24c3d5d316011bacc70@syzkaller.appspotmail.com Cc: stable Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260527125104.96596-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/comedi_parport.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/comedi/drivers/comedi_parport.c b/drivers/comedi/drivers/comedi_parport.c index 2604680d86c4..57ee3f9dfba2 100644 --- a/drivers/comedi/drivers/comedi_parport.c +++ b/drivers/comedi/drivers/comedi_parport.c @@ -211,6 +211,13 @@ static irqreturn_t parport_interrupt(int irq, void *d) unsigned int ctrl; unsigned short val = 0; + /* + * Check device is fully attached. Device interrupts should have + * been disabled, but do this in case of bad hardware. + */ + if (!dev->attached) + return IRQ_NONE; + ctrl = inb(dev->iobase + PARPORT_CTRL_REG); if (!(ctrl & PARPORT_CTRL_IRQ_ENA)) return IRQ_NONE; @@ -233,6 +240,9 @@ static int parport_attach(struct comedi_device *dev, if (ret) return ret; + outb(0, dev->iobase + PARPORT_DATA_REG); + outb(0, dev->iobase + PARPORT_CTRL_REG); + if (it->options[1]) { ret = request_irq(it->options[1], parport_interrupt, 0, dev->board_name, dev); @@ -288,9 +298,6 @@ static int parport_attach(struct comedi_device *dev, s->cancel = parport_intr_cancel; } - outb(0, dev->iobase + PARPORT_DATA_REG); - outb(0, dev->iobase + PARPORT_CTRL_REG); - return 0; } From f97752cfe511c1ed9933057455c73aaac07d6517 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Tue, 9 Jun 2026 10:55:04 -0700 Subject: [PATCH 065/456] selftests: ntsync: correct CONFIG_NTSYNC name The config fragment for these tests defines CONFIG_WINESYNC, which refers to an earlier name for the ntsync driver before it was merged [1]. Correct it to define CONFIG_NTSYNC instead. [1] https://lore.kernel.org/all/f4cc1a38-1441-62f8-47e4-0c67f5ad1d43@codeweavers.com/ Fixes: 7f853a252cde ("selftests: ntsync: Add some tests for semaphore state.") Cc: stable Signed-off-by: Ethan Nelson-Moore Reviewed-by: Elizabeth Figura Link: https://patch.msgid.link/20260609175505.19632-1-enelsonmoore@gmail.com Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/drivers/ntsync/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/ntsync/config b/tools/testing/selftests/drivers/ntsync/config index 60539c826d06..0aa68de147af 100644 --- a/tools/testing/selftests/drivers/ntsync/config +++ b/tools/testing/selftests/drivers/ntsync/config @@ -1 +1 @@ -CONFIG_WINESYNC=y +CONFIG_NTSYNC=y From ce1fed11d18e163baf7f875152a33bf80f625c1a Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 17 Jun 2026 22:53:50 +0800 Subject: [PATCH 066/456] misc: nsm: only unlock nsm_dev on post-lock error paths nsm_dev_ioctl() jumps to the common out label even when the initial copy_from_user() fails before nsm->lock has been taken. The error path then blindly unlocks a mutex that was never acquired. This issue was found by our static analysis tool and then manually reviewed against the current tree. The grounded PoC kept the miscdevice ioctl entry and the pre-lock copy_from_user(&raw, argp, _IOC_SIZE(cmd)) failure path by issuing NSM_IOCTL_RAW with an invalid user pointer. That failure reaches the shared out label before mutex_lock(&nsm->lock). Lockdep reported: WARNING: bad unlock balance detected! exploit/193 is trying to release lock (&global_nsm.lock) at: nsm_dev_ioctl+0x5f/0xcf [vuln_msv] but there are no more locks to release! no locks held by exploit/193. Return immediately on the pre-lock copy_from_user() failure and keep the common unlock label for the post-lock paths only. Fixes: b9873755a6c8 ("misc: Add Nitro Secure Module driver") Cc: stable Signed-off-by: Runyu Xiao Reviewed-by: Alexander Graf Link: https://patch.msgid.link/20260617145350.513875-1-runyu.xiao@seu.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/misc/nsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/nsm.c b/drivers/misc/nsm.c index ef7b32742340..185900cdad4a 100644 --- a/drivers/misc/nsm.c +++ b/drivers/misc/nsm.c @@ -367,7 +367,7 @@ static long nsm_dev_ioctl(struct file *file, unsigned int cmd, /* Copy user argument struct to kernel argument struct */ r = -EFAULT; if (copy_from_user(&raw, argp, _IOC_SIZE(cmd))) - goto out; + return r; mutex_lock(&nsm->lock); From f112ea910e554d58b4b39a4492b7d302f0f4204f Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Sun, 5 Jul 2026 18:12:59 +0300 Subject: [PATCH 067/456] mei: bus: access mei_device under device_lock on cleanup Fix couple of problems in mei_cl_bus_dev_release(): mei_cl_flush_queues() is running without lock. bus->file_list access after mei_dev_bus_put(bus) can become a use-after-free if this was the last reference to bus. Protect queues cleanup and WARN traversal by device lock there to avoid the concurrent access problems. Move WARN traversal before mei_dev_bus_put(bus). This file uses bus variable name for mei_device, adjust code of mei_cl_bus_dev_release() to use bus variable too. Cc: stable Fixes: 35e8a426b16a ("mei: bus: Check for still connected devices in mei_cl_bus_dev_release()") Reviewed-by: Menachem Adin Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20260705151259.3054795-1-alexander.usyskin@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index fcde082eb5e3..cfb87ab8667f 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -4,6 +4,7 @@ * Intel Management Engine Interface (Intel MEI) Linux driver */ +#include #include #include #include @@ -1330,15 +1331,16 @@ static void mei_dev_bus_put(struct mei_device *bus) static void mei_cl_bus_dev_release(struct device *dev) { struct mei_cl_device *cldev = to_mei_cl_device(dev); - struct mei_device *mdev = cldev->cl->dev; + struct mei_device *bus = cldev->bus; struct mei_cl *cl; - mei_cl_flush_queues(cldev->cl, NULL); - mei_me_cl_put(cldev->me_cl); - mei_dev_bus_put(cldev->bus); - - list_for_each_entry(cl, &mdev->file_list, link) - WARN_ON(cl == cldev->cl); + scoped_guard(mutex, &bus->device_lock) { + mei_cl_flush_queues(cldev->cl, NULL); + mei_me_cl_put(cldev->me_cl); + list_for_each_entry(cl, &bus->file_list, link) + WARN_ON(cl == cldev->cl); + } + mei_dev_bus_put(bus); kfree(cldev->cl); kfree(cldev); From 3b231f1e9990f4c21220d0a69733ce2105891ff9 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 13 Jul 2026 13:55:23 +0800 Subject: [PATCH 068/456] misc: nsm: pin the module while the device is open misc_open() installs a misc driver's file operations with fops_get(), which pins file_operations::owner before replacing the file's f_op. The NSM misc device leaves nsm_dev_fops.owner unset, so opening /dev/nsm does not take a module reference on the nsm driver. If the driver is built as a module, an open file descriptor can therefore survive rmmod of the module that provides its ioctl callbacks. A later ioctl through that descriptor can call into unloaded module text. Set nsm_dev_fops.owner to THIS_MODULE so the misc core holds the module while any /dev/nsm file descriptor is open, matching the lifetime expectation for the installed file operations. Fixes: b9873755a6c8 ("misc: Add Nitro Secure Module driver") Cc: stable Signed-off-by: Xu Rao Link: https://patch.msgid.link/BE6951D13B5E5513+20260713055523.3193089-1-raoxu@uniontech.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/nsm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/misc/nsm.c b/drivers/misc/nsm.c index 185900cdad4a..3960506eb7ab 100644 --- a/drivers/misc/nsm.c +++ b/drivers/misc/nsm.c @@ -413,6 +413,7 @@ static int nsm_device_init_vq(struct virtio_device *vdev) } static const struct file_operations nsm_dev_fops = { + .owner = THIS_MODULE, .unlocked_ioctl = nsm_dev_ioctl, .compat_ioctl = compat_ptr_ioctl, }; From 761b785a0cfbce43761227bc42a7f984f31f8921 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 15 Jul 2026 15:08:51 +0800 Subject: [PATCH 069/456] intel_th: fix MSC output device reference leak intel_th_output_open() looks up the output device with bus_find_device_by_devt(), which returns the device with a reference that must be dropped after use. commit 95fc36a234da ("intel_th: fix device leak on output open()") attempted to drop the reference from intel_th_output_release(). However, a successful open replaces file->f_op with the output driver file operations before returning, so close runs the output driver release callback instead. For MSC outputs, close runs intel_th_msc_release(), which only removes the per-file iterator and does not drop the device reference taken by intel_th_output_open(). Consequently, every successful MSC output open leaks one device reference. Drop the device reference from intel_th_msc_release(), which is the release path actually used for MSC output files. Remove the now-unused intel_th_output_release() callback from intel_th_output_fops. Fixes: 95fc36a234da ("intel_th: fix device leak on output open()") Cc: stable Signed-off-by: Guangshuo Li Reviewed-by: Johan Hovold Link: https://patch.msgid.link/20260715070851.2077965-1-lgs201920130244@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/core.c | 10 ---------- drivers/hwtracing/intel_th/msu.c | 2 ++ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/hwtracing/intel_th/core.c b/drivers/hwtracing/intel_th/core.c index 3924e63e2eee..56acf31546da 100644 --- a/drivers/hwtracing/intel_th/core.c +++ b/drivers/hwtracing/intel_th/core.c @@ -843,18 +843,8 @@ static int intel_th_output_open(struct inode *inode, struct file *file) return err; } -static int intel_th_output_release(struct inode *inode, struct file *file) -{ - struct intel_th_device *thdev = file->private_data; - - put_device(&thdev->dev); - - return 0; -} - static const struct file_operations intel_th_output_fops = { .open = intel_th_output_open, - .release = intel_th_output_release, .llseek = noop_llseek, }; diff --git a/drivers/hwtracing/intel_th/msu.c b/drivers/hwtracing/intel_th/msu.c index a82cf74f39ad..84d99d7b1d20 100644 --- a/drivers/hwtracing/intel_th/msu.c +++ b/drivers/hwtracing/intel_th/msu.c @@ -1490,8 +1490,10 @@ static int intel_th_msc_release(struct inode *inode, struct file *file) { struct msc_iter *iter = file->private_data; struct msc *msc = iter->msc; + struct intel_th_device *thdev = msc->thdev; msc_iter_remove(iter, msc); + put_device(&thdev->dev); return 0; } From bb66b1a3452534adb8b72abf2f761375970fe472 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 8 Jul 2026 12:00:26 +0000 Subject: [PATCH 070/456] rust_binder: only print failure if error has source The commit that fixes BINDER_GET_EXTENDED_ERROR changed the condition for printing transaction failures so errors are printed even if the cause is a dead or frozen process. Undo this change so that the error is only printed if the failure has an errno associated with it. Cc: stable@kernel.org Fixes: 77bfebf11077 ("rust_binder: fix BINDER_GET_EXTENDED_ERROR") Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260708-get-extended-error-fix-printing-v1-1-6e293b213b70@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/thread.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 3b8520813941..bc0ef8927905 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -1273,14 +1273,14 @@ fn transaction(self: &Arc, cmd: u32, reader: &mut UserSliceReader) -> Resu inner.extended_error = ExtendedError::new(info.debug_id as u32, err.reply, source.to_errno()); } - } - pr_warn!( - "{}:{} transaction to {} failed: {err:?}", - info.from_pid, - info.from_tid, - info.to_pid - ); + pr_warn!( + "{}:{} transaction to {} failed: {err:?}", + info.from_pid, + info.from_tid, + info.to_pid + ); + } } } From 6e8c279f93dba93f42be7c9c6995bc8e318ba2e4 Mon Sep 17 00:00:00 2001 From: Elizabeth Figura Date: Wed, 1 Jul 2026 13:32:14 -0500 Subject: [PATCH 071/456] MAINTAINERS: Update wine-devel list address Wine mailing lists were moved from winehq.org to list.winehq.org in December 2025. The old address no longer works. Signed-off-by: Elizabeth Figura Link: https://patch.msgid.link/20260701183214.13973-1-zfigura@codeweavers.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index e8704c09e0ed..63276b82abb5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19298,7 +19298,7 @@ F: fs/ntfs3/ NTSYNC SYNCHRONIZATION PRIMITIVE DRIVER M: Elizabeth Figura -L: wine-devel@winehq.org +L: wine-devel@list.winehq.org S: Supported F: Documentation/userspace-api/ntsync.rst F: drivers/misc/ntsync.c From 40a22ad1b5cb86a27f54d7837be20113d0eb49d2 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 10 Jun 2026 17:18:21 +0200 Subject: [PATCH 072/456] drm/appletbdrm: Allocate request/response buffers in begin_fb_access In atomic_check, damage handling is not fully evaluated. Another atomic_check helper could trigger a full modeset and thus invalidate damage clips. Allocation of the request/response buffers in appletbdrm depends on correct damage information. Otherwise it might allocate incorrectly sized buffers. Allocate the buffers in the driver's begin_fb_access helper. It runs early during the commit when damage clipping has been fully evaluated. v5: - pass plane state as the old damage-iterator state v2: - allocate before drm_gem_begin_shadow_fb_access() to avoid leak on error Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Acked-by: Aditya Garg Acked-by: Zack Rusin Link: https://patch.msgid.link/20260610152505.260172-6-tzimmermann@suse.de --- drivers/gpu/drm/tiny/appletbdrm.c | 53 +++++++++++++++++-------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/tiny/appletbdrm.c b/drivers/gpu/drm/tiny/appletbdrm.c index cdd35af49892..ef71b9957961 100644 --- a/drivers/gpu/drm/tiny/appletbdrm.c +++ b/drivers/gpu/drm/tiny/appletbdrm.c @@ -315,33 +315,16 @@ static const u32 appletbdrm_primary_plane_formats[] = { DRM_FORMAT_XRGB8888, /* emulated */ }; -static int appletbdrm_primary_plane_helper_atomic_check(struct drm_plane *plane, - struct drm_atomic_commit *state) +static int appletbdrm_primary_plane_helper_begin_fb_access(struct drm_plane *plane, + struct drm_plane_state *new_plane_state) { - struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); - struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); - struct drm_crtc *new_crtc = new_plane_state->crtc; - struct drm_crtc_state *new_crtc_state = NULL; struct appletbdrm_plane_state *appletbdrm_state = to_appletbdrm_plane_state(new_plane_state); + size_t frames_size = 0; struct drm_atomic_helper_damage_iter iter; struct drm_rect damage; - size_t frames_size = 0; size_t request_size; - int ret; - if (new_crtc) - new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc); - - ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state, - DRM_PLANE_NO_SCALING, - DRM_PLANE_NO_SCALING, - false, false); - if (ret) - return ret; - else if (!new_plane_state->visible) - return 0; - - drm_atomic_helper_damage_iter_init(&iter, old_plane_state, new_plane_state); + drm_atomic_helper_damage_iter_init(&iter, plane->state, new_plane_state); drm_atomic_for_each_plane_damage(&iter, &damage) { frames_size += struct_size((struct appletbdrm_frame *)0, buf, rect_size(&damage)); } @@ -366,6 +349,29 @@ static int appletbdrm_primary_plane_helper_atomic_check(struct drm_plane *plane, appletbdrm_state->request_size = request_size; appletbdrm_state->frames_size = frames_size; + return drm_gem_begin_shadow_fb_access(plane, new_plane_state); +} + +static int appletbdrm_primary_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_crtc *new_crtc = new_plane_state->crtc; + struct drm_crtc_state *new_crtc_state = NULL; + int ret; + + if (new_crtc) + new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc); + + ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); + if (ret) + return ret; + else if (!new_plane_state->visible) + return 0; + return 0; } @@ -468,7 +474,7 @@ static int appletbdrm_flush_damage(struct appletbdrm_device *adev, } static void appletbdrm_primary_plane_helper_atomic_update(struct drm_plane *plane, - struct drm_atomic_commit *old_state) + struct drm_atomic_commit *old_state) { struct appletbdrm_device *adev = drm_to_adev(plane->dev); struct drm_device *drm = plane->dev; @@ -552,7 +558,8 @@ static void appletbdrm_primary_plane_destroy_state(struct drm_plane *plane, } static const struct drm_plane_helper_funcs appletbdrm_primary_plane_helper_funcs = { - DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, + .begin_fb_access = appletbdrm_primary_plane_helper_begin_fb_access, + .end_fb_access = drm_gem_end_shadow_fb_access, .atomic_check = appletbdrm_primary_plane_helper_atomic_check, .atomic_update = appletbdrm_primary_plane_helper_atomic_update, .atomic_disable = appletbdrm_primary_plane_helper_atomic_disable, From a3fdf74ffa5966e5b4a17f1e9c5687f73bb0d536 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 16 Jul 2026 13:13:58 -0700 Subject: [PATCH 073/456] drm/ttm/pool: back up at native page order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ttm_pool_split_for_swap() unconditionally splits high-order pool pages into order-0 pages before backup, so every compound the shrinker touches is shattered even when the rest of the system would prefer it stay intact. Under sustained kswapd pressure this fragments memory enough to drive other parts of MM into recovery loops. Back up each compound at its native order instead. In ttm_pool_backup(), hand the full compound to the new ttm_backup_backup_folio(), which backs up subpages to a contiguous range of shmem indices and returns the base handle plus the number of subpages actually backed up (@nr_backed). On full success, free the compound once at its native order -- no split_page(), no per-4K refcount juggling. A per-folio backup can't be made fully atomic under memory pressure: ttm_backup_backup_folio() must allocate shmem folios before source subpages can be released, so under true OOM any subpage may fail while the rest of the compound is still live. Two mechanisms handle this without regressing reclaim behaviour: - alloc_gfp gets __GFP_NOMEMALLOC whenever order > 0 (cleared again for order-0), so a high-order backup fails fast with -ENOMEM instead of draining kernel reserves, leaving them for other allocations under the same pressure. - If ttm_backup_backup_folio() still returns a short @nr_backed with a valid handle for the successfully-backed prefix, split the source compound with ttm_pool_split_for_swap(), free the prefix as order-0 pages (already safely in shmem), and retry the remaining subpages at order 0, where __GFP_NOMEMALLOC is cleared and reserves may be used as a last resort. This preserves the original split-on-OOM fallback while keeping the common case fragmentation-free, and preserves the "partial backup is allowed" contract (shrunken is incremented per subpage backed up). The restore-side leftover-page split in ttm_pool_restore_commit() is left as-is: it's unreachable in practice and not worth complicating the restore state machine to avoid. Testing: the existing backup_fault_inject point only truncated tt->num_pages, which never exercised the reactive split path above since it never left a compound partially backed up. Wire fault injection into ttm_backup_backup_folio() itself: past the first subpage of a compound, synthesize a -ENOMEM in place of shmem_read_folio_gfp() when should_fail() trips, producing the same short @nr_pages_backed a real failure would and forcing ttm_pool_backup() through the split-and-retry path. The fault_attr stays private to ttm_pool.c; ttm_backup.c reaches it through ttm_backup_fault_inject_folio(), declared in ttm_pool_internal.h. While converting the writeback branch to operate on the whole folio, the unlock condition after shmem_writeout() also changed from `if (ret)` to `if (ret == AOP_WRITEPAGE_ACTIVATE)`, matching the actual contract: shmem_writeout()/swap_writeout() only leave the folio locked when returning AOP_WRITEPAGE_ACTIVATE; any other return (including a hard error from arch_prepare_to_swap()) means the folio was already unlocked internally. The old `if (ret)` check would have double- unlocked in that hard-error case. Cc: Christian Koenig Cc: Huang Rui Cc: Matthew Auld Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: Thomas Zimmermann Cc: David Airlie Cc: Simona Vetter Cc: dri-devel@lists.freedesktop.org Cc: linux-kernel@vger.kernel.org Cc: stable@vger.kernel.org Fixes: b63d715b8090 ("drm/ttm/pool, drm/ttm/tt: Provide a helper to shrink pages") Suggested-by: Thomas Hellström Assisted-by: Claude:claude-opus-4.6 Signed-off-by: Matthew Brost Reviewed-by: Thomas Hellström Link: https://patch.msgid.link/20260716201358.4086085-1-matthew.brost@intel.com --- drivers/gpu/drm/ttm/ttm_backup.c | 123 ++++++++++++++++-------- drivers/gpu/drm/ttm/ttm_pool.c | 89 ++++++++++++++--- drivers/gpu/drm/ttm/ttm_pool_internal.h | 8 ++ include/drm/ttm/ttm_backup.h | 12 +-- 4 files changed, 170 insertions(+), 62 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_backup.c b/drivers/gpu/drm/ttm/ttm_backup.c index 81df4cb5606b..3c067aadc52d 100644 --- a/drivers/gpu/drm/ttm/ttm_backup.c +++ b/drivers/gpu/drm/ttm/ttm_backup.c @@ -6,9 +6,10 @@ #include #include -#include #include +#include "ttm_pool_internal.h" + /* * Need to map shmem indices to handle since a handle value * of 0 means error, following the swp_entry_t convention. @@ -68,17 +69,23 @@ int ttm_backup_copy_page(struct file *backup, struct page *dst, } /** - * ttm_backup_backup_page() - Backup a page + * ttm_backup_backup_folio() - Backup a folio * @backup: The struct backup pointer to use. - * @page: The page to back up. - * @writeback: Whether to perform immediate writeback of the page. + * @folio: The folio to back up. + * @order: The allocation order of @folio. Since TTM allocates higher-order + * pages without __GFP_COMP, folio_nr_pages(@folio) would always + * return 1; the caller must pass the true order explicitly. + * @writeback: Whether to perform immediate writeback of the folio's pages. * This may have performance implications. - * @idx: A unique integer for each page and each struct backup. + * @idx: A unique integer for the first page of the folio and each struct backup. * This allows the backup implementation to avoid managing * its address space separately. - * @page_gfp: The gfp value used when the page was allocated. - * This is used for accounting purposes. + * @folio_gfp: The gfp value used when the folio was allocated. + * Currently unused. * @alloc_gfp: The gfp to be used when allocating memory. + * @nr_pages_backed: Output. On a successful return, set to the number of + * pages actually backed up, which may be less than (1 << @order) + * if an -ENOMEM was encountered mid-folio. * * Context: If called from reclaim context, the caller needs to * assert that the shrinker gfp has __GFP_FS set, to avoid @@ -87,53 +94,87 @@ int ttm_backup_copy_page(struct file *backup, struct page *dst, * that the shrinker gfp has __GFP_IO set, since without it, * we're not allowed to start backup IO. * - * Return: A handle on success. Negative error code on failure. - * - * Note: This function could be extended to back up a folio and - * implementations would then split the folio internally if needed. - * Drawback is that the caller would then have to keep track of - * the folio size- and usage. + * Return: A handle for the first backed-up page on success (handles for + * subsequent pages follow sequentially). -ENOMEM if no pages could be backed + * up. Any other negative error code if a non-ENOMEM failure occurred; in that + * case any pages backed up so far are truncated before returning. */ s64 -ttm_backup_backup_page(struct file *backup, struct page *page, - bool writeback, pgoff_t idx, gfp_t page_gfp, - gfp_t alloc_gfp) +ttm_backup_backup_folio(struct file *backup, struct folio *folio, + unsigned int order, bool writeback, pgoff_t idx, + gfp_t folio_gfp, gfp_t alloc_gfp, + pgoff_t *nr_pages_backed) { struct address_space *mapping = backup->f_mapping; - unsigned long handle = 0; + int nr_pages = 1 << order; struct folio *to_folio; - int ret; + int ret, i; - to_folio = shmem_read_folio_gfp(mapping, idx, alloc_gfp); - if (IS_ERR(to_folio)) - return PTR_ERR(to_folio); + *nr_pages_backed = 0; - folio_mark_accessed(to_folio); - folio_lock(to_folio); - folio_mark_dirty(to_folio); - copy_highpage(folio_file_page(to_folio, idx), page); - handle = ttm_backup_shmem_idx_to_handle(idx); + for (i = 0; i < nr_pages; ) { + int to_nr, j; - if (writeback && !folio_mapped(to_folio) && - folio_clear_dirty_for_io(to_folio)) { - folio_set_reclaim(to_folio); - ret = shmem_writeout(to_folio, NULL, NULL); - if (!folio_test_writeback(to_folio)) - folio_clear_reclaim(to_folio); /* - * If writeout succeeds, it unlocks the folio. errors - * are otherwise dropped, since writeout is only best - * effort here. + * Only inject past the first subpage so *nr_pages_backed is + * always > 0 here, matching a genuine mid-compound -ENOMEM + * and driving the caller's reactive split fallback instead + * of an early, no-progress failure. */ - if (ret) + if (IS_ENABLED(CONFIG_FAULT_INJECTION) && i && + ttm_backup_fault_inject_folio()) + to_folio = ERR_PTR(-ENOMEM); + else + to_folio = shmem_read_folio_gfp(mapping, idx + i, alloc_gfp); + if (IS_ERR(to_folio)) { + int err = PTR_ERR(to_folio); + + if (err == -ENOMEM && *nr_pages_backed) + return ttm_backup_shmem_idx_to_handle(idx); + + if (*nr_pages_backed) { + shmem_truncate_range(file_inode(backup), + (loff_t)idx << PAGE_SHIFT, + ((loff_t)(idx + i) << PAGE_SHIFT) - 1); + /* + * The pages just truncated are no longer + * backed up; don't let the caller mistake + * them for valid handles. + */ + *nr_pages_backed = 0; + } + return err; + } + + to_nr = min_t(int, nr_pages - i, + folio_next_index(to_folio) - (idx + i)); + + folio_mark_accessed(to_folio); + folio_lock(to_folio); + folio_mark_dirty(to_folio); + + for (j = 0; j < to_nr; j++) + copy_highpage(folio_file_page(to_folio, idx + i + j), + folio_page(folio, i + j)); + + if (writeback && !folio_mapped(to_folio) && + folio_clear_dirty_for_io(to_folio)) { + folio_set_reclaim(to_folio); + ret = shmem_writeout(to_folio, NULL, NULL); + if (!folio_test_writeback(to_folio)) + folio_clear_reclaim(to_folio); + if (ret == AOP_WRITEPAGE_ACTIVATE) + folio_unlock(to_folio); + } else { folio_unlock(to_folio); - } else { - folio_unlock(to_folio); + } + + folio_put(to_folio); + i += to_nr; + *nr_pages_backed = i; } - folio_put(to_folio); - - return handle; + return ttm_backup_shmem_idx_to_handle(idx); } /** diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c index 46983e7de7a3..e7648ad3e615 100644 --- a/drivers/gpu/drm/ttm/ttm_pool.c +++ b/drivers/gpu/drm/ttm/ttm_pool.c @@ -53,8 +53,23 @@ #ifdef CONFIG_FAULT_INJECTION #include static DECLARE_FAULT_ATTR(backup_fault_inject); + +/* + * Exposed to ttm_backup.c so a mid-compound subpage can be made to fail + * with -ENOMEM, exercising the reactive split-and-retry fallback in + * ttm_pool_backup() for high-order backups. + */ +bool ttm_backup_fault_inject_folio(void) +{ + return should_fail(&backup_fault_inject, 1); +} #else #define should_fail(...) false + +bool ttm_backup_fault_inject_folio(void) +{ + return false; +} #endif /** @@ -487,7 +502,7 @@ static void ttm_pool_split_for_swap(struct ttm_pool *pool, struct page *p) /** * DOC: Partial backup and restoration of a struct ttm_tt. * - * Swapout using ttm_backup_backup_page() and swapin using + * Swapout using ttm_backup_backup_folio() and swapin using * ttm_backup_copy_page() may fail. * The former most likely due to lack of swap-space or memory, the latter due * to lack of memory or because of signal interruption during waits. @@ -1036,12 +1051,12 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt, { struct file *backup = tt->backup; struct page *page; - unsigned long handle; gfp_t alloc_gfp; gfp_t gfp; int ret = 0; pgoff_t shrunken = 0; - pgoff_t i, num_pages; + pgoff_t i, j, num_pages, npages; + pgoff_t nr_backed; if (WARN_ON(ttm_tt_is_backed_up(tt))) return -EINVAL; @@ -1119,9 +1134,11 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt, if (IS_ENABLED(CONFIG_FAULT_INJECTION) && should_fail(&backup_fault_inject, 1)) num_pages = DIV_ROUND_UP(num_pages, 2); - for (i = 0; i < num_pages; ++i) { - s64 shandle; + for (i = 0; i < num_pages; i += npages) { + unsigned int order; + s64 handle; + npages = 1; page = tt->pages[i]; if (unlikely(!page)) continue; @@ -1130,19 +1147,61 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt, if (unlikely(ttm_backup_page_ptr_is_handle(page))) continue; - ttm_pool_split_for_swap(pool, page); + order = ttm_pool_page_order(pool, page); + npages = 1UL << order; - shandle = ttm_backup_backup_page(backup, page, flags->writeback, i, - gfp, alloc_gfp); - if (shandle < 0) { - /* We allow partially shrunken tts */ - ret = shandle; + /* + * We don't allow dipping kernel reserves for high order backup + */ + if (order) + alloc_gfp |= __GFP_NOMEMALLOC; + else + alloc_gfp &= ~__GFP_NOMEMALLOC; + + /* + * Back up the compound atomically at its native order. If + * fault injection truncated num_pages mid-compound, skip + * the partial tail rather than splitting. + */ + if (unlikely(i + npages > num_pages)) + break; + + handle = ttm_backup_backup_folio(backup, page_folio(page), + order, flags->writeback, i, + gfp, alloc_gfp, + &nr_backed); + /* + * Zero progress on this compound (whether order 0 or a + * high-order compound that failed before backing up even + * its first subpage) is unrecoverable: bail out rather than + * looping forever with npages == nr_backed == 0 below. + */ + if (unlikely(handle < 0 && !nr_backed)) { + ret = handle; break; } - handle = shandle; - tt->pages[i] = ttm_backup_handle_to_page_ptr(handle); - __free_pages_gpu_account(page, 0, false); - shrunken++; + + for (j = 0; j < nr_backed; j++) + tt->pages[i + j] = ttm_backup_handle_to_page_ptr(handle + j); + + shrunken += nr_backed; + + if (unlikely(nr_backed < npages)) { + /* + * Partial OOM backup: split the compound and free the + * subpages whose content is now in shmem. Continue the + * loop from the first un-backed order-0 page. + */ + ttm_pool_split_for_swap(pool, page); + for (j = 0; j < nr_backed; j++) + __free_pages_gpu_account(page + j, 0, false); + npages = nr_backed; + continue; + } + + /* Fully backed up: free at native order. */ + page->private = 0; + __free_pages_gpu_account(page, order, false); } return shrunken ? shrunken : ret; diff --git a/drivers/gpu/drm/ttm/ttm_pool_internal.h b/drivers/gpu/drm/ttm/ttm_pool_internal.h index 24c179fd69d1..cbb17a2129fe 100644 --- a/drivers/gpu/drm/ttm/ttm_pool_internal.h +++ b/drivers/gpu/drm/ttm/ttm_pool_internal.h @@ -22,4 +22,12 @@ static inline unsigned int ttm_pool_beneficial_order(struct ttm_pool *pool) return pool->alloc_flags & 0xff; } +/* + * Implemented in ttm_pool.c, used by ttm_backup.c. Returns true if a fault + * should be injected mid-compound to test the reactive split-and-retry + * fallback in ttm_pool_backup(). Always returns false when + * CONFIG_FAULT_INJECTION is disabled. + */ +bool ttm_backup_fault_inject_folio(void); + #endif diff --git a/include/drm/ttm/ttm_backup.h b/include/drm/ttm/ttm_backup.h index 29b9c855af77..49efa713e87c 100644 --- a/include/drm/ttm/ttm_backup.h +++ b/include/drm/ttm/ttm_backup.h @@ -13,9 +13,8 @@ * ttm_backup_handle_to_page_ptr() - Convert handle to struct page pointer * @handle: The handle to convert. * - * Converts an opaque handle received from the - * ttm_backup_backup_page() function to an (invalid) - * struct page pointer suitable for a struct page array. + * Converts an opaque handle received from a ttm_backup_backup_*() + * function to an (invalid) struct page pointer suitable for a struct page array. * * Return: An (invalid) struct page pointer. */ @@ -59,9 +58,10 @@ int ttm_backup_copy_page(struct file *backup, struct page *dst, pgoff_t handle, bool intr, gfp_t additional_gfp); s64 -ttm_backup_backup_page(struct file *backup, struct page *page, - bool writeback, pgoff_t idx, gfp_t page_gfp, - gfp_t alloc_gfp); +ttm_backup_backup_folio(struct file *backup, struct folio *folio, + unsigned int order, bool writeback, pgoff_t idx, + gfp_t folio_gfp, gfp_t alloc_gfp, + pgoff_t *nr_pages_backed); void ttm_backup_fini(struct file *backup); From 26b483d52417253d88a3a01262ac85914a7aec8e Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 17 Jul 2026 17:25:58 +0100 Subject: [PATCH 074/456] Revert "arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates" This reverts commit e057b94772328221405b067c3a85fe479b915dc8. Sashiko points out that updating 'orig_x0' after secure_computing() has returned is too late to handle the case where a seccomp filter is re-evaluated after initially returning SECCOMP_RET_TRACE. This means that a tracer can manipulate the first argument of the syscall behind seccomp's back. For now, revert the initial fix and we'll have another crack at it soon. Since the incorrect fix was cc'd to stable, do the same here with an appropriate fixes tag. Cc: stable@vger.kernel.org Fixes: e057b9477232 ("arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates") Link: https://sashiko.dev/#/patchset/20260716120640.6590-1-will@kernel.org Signed-off-by: Will Deacon --- arch/arm64/kernel/ptrace.c | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index 390c9b2bd966..4d08598e2891 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -2408,21 +2408,6 @@ static void report_syscall_exit(struct pt_regs *regs) } } -static void update_syscall_orig_x0_after_ptrace(struct pt_regs *regs) -{ - /* - * Keep orig_x0 authoritative so that seccomp (via - * syscall_get_arguments()), audit and the restart path all see the same - * first argument the syscall is dispatched with, even if it has been - * updated by a tracer. Skip this for NO_SYSCALL (set either by the user - * or the tracer), as regs[0] holds the return value (see the comment in - * el0_svc_common()) and can be unwound using syscall_rollback(). - * For compat tasks, orig_r0 is provided directly through GPR index 17. - */ - if (!is_compat_task() && regs->syscallno != NO_SYSCALL) - regs->orig_x0 = regs->regs[0]; -} - int syscall_trace_enter(struct pt_regs *regs) { unsigned long flags = read_thread_flags(); @@ -2432,26 +2417,12 @@ int syscall_trace_enter(struct pt_regs *regs) ret = report_syscall_entry(regs); if (ret || (flags & _TIF_SYSCALL_EMU)) return NO_SYSCALL; - - /* - * Ensure ptrace changes to x0 during a regular - * syscall-enter-stop (PTRACE_SYSCALL) are visible to - * subsequent seccomp checks, tracepoints and audit. - */ - update_syscall_orig_x0_after_ptrace(regs); } /* Do the secure computing after ptrace; failures should be fast. */ if (secure_computing() == -1) return NO_SYSCALL; - /* - * Ensure tracer changes to x0 during seccomp ptrace exit - * processing (SECCOMP_RET_TRACE) are visible to tracepoints and - * audit. - */ - update_syscall_orig_x0_after_ptrace(regs); - if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) trace_sys_enter(regs, regs->syscallno); From 62c740f823a8e47ffe56e45a7472c27cf988e2f6 Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Wed, 3 Jun 2026 11:05:21 +0000 Subject: [PATCH 075/456] drivers/virt: pkvm: Fix end calculation in mmio_guard_ioremap_hook() Sashiko (locally) reports a logical issues in mmio_guard_ioremap_hook() mmio_guard_ioremap_hook() attempts to handle unaligned addresses and sizes. However, aligning the start address before adding the size, might shift the end to the page before. Fixes: 0f1269495800 ("drivers/virt: pkvm: Intercept ioremap using pKVM MMIO_GUARD hypercall") Signed-off-by: Mostafa Saleh Reviewed-by: Catalin Marinas Tested-by: Aneesh Kumar K.V (Arm) Signed-off-by: Will Deacon --- drivers/virt/coco/pkvm-guest/arm-pkvm-guest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virt/coco/pkvm-guest/arm-pkvm-guest.c b/drivers/virt/coco/pkvm-guest/arm-pkvm-guest.c index 4230b817a80b..d66291def0f4 100644 --- a/drivers/virt/coco/pkvm-guest/arm-pkvm-guest.c +++ b/drivers/virt/coco/pkvm-guest/arm-pkvm-guest.c @@ -82,8 +82,8 @@ static int mmio_guard_ioremap_hook(phys_addr_t phys, size_t size, if (protval != PROT_DEVICE_nGnRE && protval != PROT_DEVICE_nGnRnE) return 0; + end = PAGE_ALIGN(phys + size); phys = PAGE_ALIGN_DOWN(phys); - end = phys + PAGE_ALIGN(size); while (phys < end) { const int func_id = ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_FUNC_ID; From 879a6754d3d11e30af24b7dc486f561510d62641 Mon Sep 17 00:00:00 2001 From: Pu Hu Date: Fri, 10 Jul 2026 06:32:53 +0000 Subject: [PATCH 076/456] arm64: kprobes: Only handle faults originating from XOL slot kprobe_fault_handler() currently treats any page fault taken while in KPROBE_HIT_SS or KPROBE_REENTER state as a kprobe single-step fault. This assumption does not hold: perf or tracing code may run from the debug exception path during the single-step window and take its own page fault. When the fault is handled as a kprobe fault, the PC is rewritten to the probe address, corrupting the exception recovery context for the real fault. A typical reproducer is running perf with preemptirq tracepoints and dwarf callchains while a kprobe is installed on a frequently executed function. Fix this in two layers: 1. At function entry, bail out immediately for simulated kprobes (ainsn.xol_insn == NULL), since they have no XOL slot and any fault taken during their execution cannot be a single-step fault. 2. For kprobes with an XOL slot, only handle the fault when the faulting PC matches the XOL instruction address. Faults from any other PC are left to the normal page fault handler. This follows the same principle as the x86 fix in commit 6381c24cd6d5 ("kprobes/x86: Fix page-fault handling logic"). Signed-off-by: Pu Hu Signed-off-by: Hongyan Xia Reviewed-by: Masami Hiramatsu (Google) Signed-off-by: Will Deacon --- arch/arm64/kernel/probes/kprobes.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c index 43a0361a8bf0..798e4b091d1a 100644 --- a/arch/arm64/kernel/probes/kprobes.c +++ b/arch/arm64/kernel/probes/kprobes.c @@ -282,9 +282,31 @@ int __kprobes kprobe_fault_handler(struct pt_regs *regs, unsigned int fsr) struct kprobe *cur = kprobe_running(); struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); + /* + * Simulated kprobes execute in the debug trap context and have no + * XOL slot. Any page fault taken while a simulated kprobe is in + * progress cannot have been caused by kprobe single-stepping and + * must be left alone for the normal page fault handler, including + * fixup_exception. + */ + if (cur && !cur->ainsn.xol_insn) + return 0; + switch (kcb->kprobe_status) { case KPROBE_HIT_SS: case KPROBE_REENTER: + /* + * A page fault taken while in KPROBE_HIT_SS or + * KPROBE_REENTER state is only attributable to kprobe + * single-stepping if the faulting PC points to the + * current kprobe's XOL instruction. If the fault occurred + * elsewhere (e.g. in perf or tracing code invoked from the + * debug exception path), leave it for the normal page fault + * handler to process. + */ + if (instruction_pointer(regs) != (unsigned long)cur->ainsn.xol_insn) + break; + /* * We are here because the instruction being single * stepped caused a page fault. We reset the current From 23f851ac0078a908bf3422d6467ebc1db5828c46 Mon Sep 17 00:00:00 2001 From: Pu Hu Date: Fri, 10 Jul 2026 06:32:55 +0000 Subject: [PATCH 077/456] arm64: kprobes: Allow reentering kprobes while single-stepping A kprobe can be hit while another kprobe is in KPROBE_HIT_SS state. This can happen when tracing or perf code runs from the debug exception path while the first kprobe is preparing or executing its out-of-line single-step instruction. Currently arm64 treats a kprobe hit in KPROBE_HIT_SS as unrecoverable, the same as a hit in KPROBE_REENTER. This is too strict. A hit in KPROBE_HIT_SS is still a one-level reentry and can be handled by saving the current kprobe state and setting up single-step for the new probe, just like reentry from KPROBE_HIT_ACTIVE or KPROBE_HIT_SSDONE. The truly unrecoverable case is hitting another kprobe while already in KPROBE_REENTER, because the reentry save area has already been consumed. Move KPROBE_HIT_SS to the recoverable reentry cases and leave KPROBE_REENTER as the unrecoverable nested reentry case. This change also requires saving saved_irqflag in struct prev_kprobe. When a nested kprobe calls kprobes_save_local_irqflag(), it overwrites kcb->saved_irqflag with the currently masked DAIF value, losing the outer kprobe's original DAIF state. Without this fix, when the outer kprobe's single-step finishes, kprobes_restore_local_irqflag() applies the wrong DAIF mask and leaves interrupts permanently disabled. Extend struct prev_kprobe with a saved_irqflag field and save/restore it alongside kp and status. This ensures the outer kprobe's original interrupt state is preserved across reentry. This mirrors the x86 fix in commit 6a5022a56ac3 ("kprobes/x86: Allow to handle reentered kprobe on single-stepping"). Signed-off-by: Pu Hu Signed-off-by: Hongyan Xia Reviewed-by: Masami Hiramatsu (Google) Signed-off-by: Will Deacon --- arch/arm64/include/asm/kprobes.h | 6 ++++++ arch/arm64/kernel/probes/kprobes.c | 23 ++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/kprobes.h b/arch/arm64/include/asm/kprobes.h index f2782560647b..35ce2c94040e 100644 --- a/arch/arm64/include/asm/kprobes.h +++ b/arch/arm64/include/asm/kprobes.h @@ -26,6 +26,12 @@ struct prev_kprobe { struct kprobe *kp; unsigned int status; + + /* + * The original DAIF state of the outer kprobe, saved here before + * a nested kprobe overwrites kcb->saved_irqflag during reentry. + */ + unsigned long saved_irqflag; }; /* per-cpu kprobe control block */ diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c index 798e4b091d1a..4e0efad5caf2 100644 --- a/arch/arm64/kernel/probes/kprobes.c +++ b/arch/arm64/kernel/probes/kprobes.c @@ -174,12 +174,27 @@ static void __kprobes save_previous_kprobe(struct kprobe_ctlblk *kcb) { kcb->prev_kprobe.kp = kprobe_running(); kcb->prev_kprobe.status = kcb->kprobe_status; + + /* + * Save the outer kprobe's original DAIF flags before the nested + * kprobe calls kprobes_save_local_irqflag() and overwrites + * kcb->saved_irqflag. Without this, the outer kprobe will restore + * the wrong DAIF state and leave interrupts permanently masked. + */ + kcb->prev_kprobe.saved_irqflag = kcb->saved_irqflag; } static void __kprobes restore_previous_kprobe(struct kprobe_ctlblk *kcb) { __this_cpu_write(current_kprobe, kcb->prev_kprobe.kp); kcb->kprobe_status = kcb->prev_kprobe.status; + + /* + * Restore the outer kprobe's saved_irqflag so that when its + * single-step completes, kprobes_restore_local_irqflag() uses + * the correct original DAIF value. + */ + kcb->saved_irqflag = kcb->prev_kprobe.saved_irqflag; } static void __kprobes set_current_kprobe(struct kprobe *p) @@ -240,10 +255,16 @@ static int __kprobes reenter_kprobe(struct kprobe *p, switch (kcb->kprobe_status) { case KPROBE_HIT_SSDONE: case KPROBE_HIT_ACTIVE: + case KPROBE_HIT_SS: + /* + * A probe can be hit while another kprobe is preparing or + * executing its XOL single-step instruction. This is still a + * recoverable one-level reentry, so handle it in the same way as + * reentry from KPROBE_HIT_ACTIVE or KPROBE_HIT_SSDONE. + */ kprobes_inc_nmissed_count(p); setup_singlestep(p, regs, kcb, 1); break; - case KPROBE_HIT_SS: case KPROBE_REENTER: pr_warn("Failed to recover from reentered kprobes.\n"); dump_kprobe(p); From 880c43b185ca52239e75bc546cc4f4d9154d0fed Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Tue, 14 Jul 2026 07:52:35 +0900 Subject: [PATCH 078/456] rust: time: fix as_micros_ceil() to round correctly for negative Delta The ceiling-division idiom `(n + d - 1) / d` only produces the correct result when `n` is non-negative. For example, if n = -1000 (exactly -1us), the old code computed (-1000 + 999) / 1000 == 0 instead of -1. For negative n, truncating division already rounds towards positive infinity, so no bias is needed in that case. Fixes: fae0cdc12340 ("rust: time: Introduce Delta type") Signed-off-by: FUJITA Tomonori Acked-by: Andreas Hindborg Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260713225235.3243480-1-tomo@flapping.org Signed-off-by: Miguel Ojeda --- rust/kernel/time.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs index 363e93cbb139..b8463823aed9 100644 --- a/rust/kernel/time.rs +++ b/rust/kernel/time.rs @@ -441,15 +441,22 @@ pub const fn as_nanos(self) -> i64 { /// to the value in the [`Delta`]. #[inline] pub fn as_micros_ceil(self) -> i64 { + let n = self.as_nanos(); + let n = if n >= 0 { + n.saturating_add(NSEC_PER_USEC - 1) + } else { + n + }; + #[cfg(CONFIG_64BIT)] { - self.as_nanos().saturating_add(NSEC_PER_USEC - 1) / NSEC_PER_USEC + n / NSEC_PER_USEC } #[cfg(not(CONFIG_64BIT))] // SAFETY: It is always safe to call `ktime_to_us()` with any value. unsafe { - bindings::ktime_to_us(self.as_nanos().saturating_add(NSEC_PER_USEC - 1)) + bindings::ktime_to_us(n) } } From 432a9b2780c0a01caf547bd1fc2fcf28aeb8d173 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Sun, 19 Jul 2026 08:45:40 -0500 Subject: [PATCH 079/456] ublk: wait on ublk_dev_ready() instead of ub->completion ub->completion is only re-armed by a successful START_USER_RECOVERY. If the ublk server sends END_USER_RECOVERY without one - e.g. its START failed with -EBUSY and the error was ignored - the wait is satisfied by the stale completion of the previous recovery cycle, and the device is marked LIVE and the requeue list kicked while the FETCH stream is still running and ubq->canceling is still set. The kick redispatches a previously requeued request, __ublk_queue_rq_common() sees ->canceling and parks it again via __ublk_abort_rq(), and after the last FETCH clears ->canceling nothing ever kicks the requeue list again: the request is stranded there while holding its tag. If it is the flush machinery's flush_rq, every subsequent fsync piles up in uninterruptible sleep and teardown hangs on tag draining. This matches a report of a lost PREFLUSH with ext4 on top of ublk after daemon crash recovery. ub->completion is an edge-triggered latch used as a proxy for the level condition "every queue has fetched all I/O commands", which can regress (F_BATCH's UNPREP, daemon death) and whose re-arm can be skipped. Drop it and wait on the real condition instead: the new helper ublk_wait_dev_ready_and_lock() waits on ublk_dev_ready() via wait_var_event_interruptible(), woken from ublk_mark_io_ready(), then re-checks it under ub->mutex, waiting again on regression, and returns with the mutex held and readiness guaranteed. Readiness becomes true in the same ub->mutex critical section that clears the last queue's ->canceling, so END_USER_RECOVERY marks the device LIVE and kicks the requeue list strictly after ->canceling clears. The wait stays interruptible, so a server whose daemon died can still be signalled out. For ublk_ctrl_start_dev() this replaces the fail-fast -EINVAL on an F_BATCH ready->UNPREP regression with waiting until the device is ready again. Reported-by: George Salisbury Fixes: 728cbac5fe21 ("ublk: move device reset into ublk_ch_release()") Cc: stable@vger.kernel.org Signed-off-by: Ming Lei Link: https://patch.msgid.link/20260719134540.120269-1-tom.leiming@gmail.com Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 47 +++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index c2c11f2a01e7..4ca6ec738c93 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -327,7 +327,6 @@ struct ublk_device { struct ublk_params params; - struct completion completion; u32 nr_queue_ready; bool unprivileged_daemons; struct mutex cancel_mutex; @@ -3054,12 +3053,12 @@ static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id, if (ublk_dev_ready(ub)) { /* * All queues ready - clear device-level canceling flag - * and complete the recovery/initialization. + * and wake ublk_dev_ready() waiters. */ mutex_lock(&ub->cancel_mutex); ub->canceling = false; mutex_unlock(&ub->cancel_mutex); - complete_all(&ub->completion); + wake_up_var(&ub->nr_queue_ready); } } @@ -4273,7 +4272,6 @@ static int ublk_init_queues(struct ublk_device *ub) goto fail; } - init_completion(&ub->completion); return 0; fail: @@ -4417,6 +4415,26 @@ static bool ublk_validate_user_pid(struct ublk_device *ub, pid_t ublksrv_pid) return ub->ublksrv_tgid == ublksrv_pid; } +/* + * Wait until all queues have fetched their I/O commands, and return with + * ub->mutex held and readiness guaranteed: then every queue's ->canceling + * is cleared. Ready may regress between wakeup and mutex_lock() (F_BATCH + * UNPREP, daemon death), so re-check it under the mutex and wait again. + */ +static int ublk_wait_dev_ready_and_lock(struct ublk_device *ub) +{ + while (true) { + if (wait_var_event_interruptible(&ub->nr_queue_ready, + ublk_dev_ready(ub))) + return -EINTR; + + mutex_lock(&ub->mutex); + if (ublk_dev_ready(ub)) + return 0; + mutex_unlock(&ub->mutex); + } +} + static int ublk_ctrl_start_dev(struct ublk_device *ub, const struct ublksrv_ctrl_cmd *header) { @@ -4499,15 +4517,10 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, }; } - if (wait_for_completion_interruptible(&ub->completion) != 0) + if (ublk_wait_dev_ready_and_lock(ub)) return -EINTR; - if (!ublk_validate_user_pid(ub, ublksrv_pid)) - return -EINVAL; - - mutex_lock(&ub->mutex); - /* device may become not ready in case of F_BATCH */ - if (!ublk_dev_ready(ub)) { + if (!ublk_validate_user_pid(ub, ublksrv_pid)) { ret = -EINVAL; goto out_unlock; } @@ -5071,7 +5084,6 @@ static int ublk_ctrl_start_recovery(struct ublk_device *ub) goto out_unlock; } pr_devel("%s: start recovery for dev id %d\n", __func__, ub->ub_number); - init_completion(&ub->completion); ret = 0; out_unlock: mutex_unlock(&ub->mutex); @@ -5087,16 +5099,17 @@ static int ublk_ctrl_end_recovery(struct ublk_device *ub, pr_devel("%s: Waiting for all FETCH_REQs, dev id %d...\n", __func__, header->dev_id); - if (wait_for_completion_interruptible(&ub->completion)) + if (ublk_wait_dev_ready_and_lock(ub)) return -EINTR; pr_devel("%s: All FETCH_REQs received, dev id %d\n", __func__, header->dev_id); - if (!ublk_validate_user_pid(ub, ublksrv_pid)) - return -EINVAL; + if (!ublk_validate_user_pid(ub, ublksrv_pid)) { + ret = -EINVAL; + goto out_unlock; + } - mutex_lock(&ub->mutex); if (ublk_nosrv_should_stop_dev(ub)) goto out_unlock; From a88df1d92fcf23d848e5fa6d7d2a1c38fd0174ed Mon Sep 17 00:00:00 2001 From: Zhengyu He Date: Wed, 15 Jul 2026 20:52:15 +0800 Subject: [PATCH 080/456] spi: spacemit: Correct TX FIFO slot calculation In k1_spi_write, the count variable is intended to represent the number of slots available for writing into the TX FIFO. The current implementation uses FIELD_GET(SSP_STATUS_TFL, val) in an attempt to determine this count, but this register field returns the number of occupied slots, not the available space. The previous implementation attempted to handle this via a ternary operator (? : K1_SPI_FIFO_SIZE), which incorrectly assumed that the hardware returned 0 when the FIFO was empty (meaning all slots were available), leading to incorrect accounting of the buffer space. Fix this by calculating the free slots: count = K1_SPI_FIFO_SIZE - FIELD_GET(SSP_STATUS_TFL, val); The associated comment has been updated to reflect the logic change: The old comment reflected an incorrect assumption about the hardware behavior, which was the root cause of the previous buggy logic. This patch accurately and concisely describes the purpose of the new calculation. Signed-off-by: Peixin Xie Signed-off-by: Zhengyu He Link: https://patch.msgid.link/20260715-k1-spi-tx-fifo-fix-v1-for-next-v1-1-02024223b08a@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-spacemit-k1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/spi/spi-spacemit-k1.c b/drivers/spi/spi-spacemit-k1.c index 215fe66d27b4..0faf7ffed676 100644 --- a/drivers/spi/spi-spacemit-k1.c +++ b/drivers/spi/spi-spacemit-k1.c @@ -494,8 +494,8 @@ static bool k1_spi_write(struct k1_spi_driver_data *drv_data, u32 val) { unsigned int count; - /* Get the number of open slots in the FIFO; zero means all */ - count = FIELD_GET(SSP_STATUS_TFL, val) ? : K1_SPI_FIFO_SIZE; + /* Get the number of free slots in the FIFO */ + count = K1_SPI_FIFO_SIZE - FIELD_GET(SSP_STATUS_TFL, val); /* * Limit how much we try to send at a time, to reduce the From 41e116ad01d8a704883187743b52d57e11bc3ef0 Mon Sep 17 00:00:00 2001 From: Greg Ungerer Date: Sun, 19 Jul 2026 23:11:11 +1000 Subject: [PATCH 081/456] m68k: coldfire: fix breakage of missed IO access update Fix the last remaining breakage caused by missing a SoC IO access update. Commit e1f3a00670d1 ("m68k: coldfire: use ColdFire specifc IO access in SoC code") missed this read16() call which should be mcf_read16(). Fixes: e1f3a00670d1 ("m68k: coldfire: use ColdFire specifc IO access in SoC code") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607180731.U4tiwFcQ-lkp@intel.com/ Signed-off-by: Greg Ungerer --- arch/m68k/coldfire/m528x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/m68k/coldfire/m528x.c b/arch/m68k/coldfire/m528x.c index 3383b1ba106a..f9874bba9e45 100644 --- a/arch/m68k/coldfire/m528x.c +++ b/arch/m68k/coldfire/m528x.c @@ -110,7 +110,7 @@ void wildfiremod_halt(void) printk(KERN_INFO "WildFireMod hibernating...\n"); /* Set portE.5 to Digital IO */ - mcf_write16(read16(MCFGPIO_PEPAR) & ~(1 << (5 * 2)), MCFGPIO_PEPAR); + mcf_write16(mcf_read16(MCFGPIO_PEPAR) & ~(1 << (5 * 2)), MCFGPIO_PEPAR); /* Make portE.5 an output */ mcf_write8(mcf_read8(MCFGPIO_PDDR_E) | (1 << 5), MCFGPIO_PDDR_E); From 488f4902e1deba4d507b4b8c25547a366f5dac63 Mon Sep 17 00:00:00 2001 From: Wendy Liang Date: Sat, 18 Jul 2026 01:34:09 -0700 Subject: [PATCH 082/456] accel/amdxdna: Fix command timeout race When two commands enter aie2_sched_job_timedout() concurrently, both check the timeout detection state. The first scheduler thread observes tdr_status as SIGNALED and updates it to WAIT. The second thread then observes the updated state instead of the original SIGNALED state, which may cause the command timeout to be handled incorrectly. Replace tdr_status with last_signal_ts, which records the timestamp of the last driver signal. Timeout detection now only reads last_signal_ts and never modifies it, allowing multiple serialized detect() calls under dev_lock to evaluate the same signal timestamp independently. If there is not any new job scheduled or completed within tdr_timeout_ms, the command will timeout. Fixes: 9022f010977f ("accel/amdxdna: Check for device hang on job timeout") Signed-off-by: Wendy Liang Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260718083409.1825940-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/aie2_ctx.c | 26 ++++++++++++++++++-------- drivers/accel/amdxdna/aie2_pci.c | 1 + drivers/accel/amdxdna/aie2_pci.h | 7 +------ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index 101f324ee178..4b3a62aa8798 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -43,20 +43,22 @@ struct aie2_ctx_health { static inline void aie2_tdr_signal(struct amdxdna_dev *xdna) { - WRITE_ONCE(xdna->dev_handle->tdr_status, AIE2_TDR_SIGNALED); + WRITE_ONCE(xdna->dev_handle->last_signal_ts, jiffies); } static bool aie2_tdr_detect(struct amdxdna_dev *xdna) { struct amdxdna_dev_hdl *ndev = xdna->dev_handle; + unsigned long last = READ_ONCE(ndev->last_signal_ts); - if (READ_ONCE(ndev->tdr_status) == AIE2_TDR_WAIT) { - XDNA_ERR(xdna, "TDR timeout detected"); - return true; - } + if (!tdr_timeout_ms) + return false; - WRITE_ONCE(ndev->tdr_status, AIE2_TDR_WAIT); - return false; + if (!time_after(jiffies, last + msecs_to_jiffies(tdr_timeout_ms))) + return false; + + XDNA_ERR(xdna, "TDR timeout detected"); + return true; } static void aie2_cmd_release(struct kref *ref) @@ -434,6 +436,12 @@ aie2_sched_job_run(struct drm_sched_job *sched_job) mmput(job->mm); fence = ERR_PTR(ret); } else { + /* + * Command is successfully posted to hardware, update the + * tdr timestamp. The total pending commands are limited. + * So there will not be a case that driver keeps posting + * commands without getting any hardware respond. + */ aie2_tdr_signal(hwctx->client->xdna); } trace_xdna_job(sched_job, hwctx->name, "sent to device", @@ -658,7 +666,9 @@ int aie2_hwctx_init(struct amdxdna_hwctx *hwctx) const struct drm_sched_init_args args = { .ops = &sched_ops, .credit_limit = HWCTX_MAX_CMDS, - .timeout = msecs_to_jiffies(tdr_timeout_ms), + .timeout = tdr_timeout_ms ? + msecs_to_jiffies(tdr_timeout_ms) : + MAX_SCHEDULE_TIMEOUT, .name = "amdxdna_js", .dev = xdna->ddev.dev, }; diff --git a/drivers/accel/amdxdna/aie2_pci.c b/drivers/accel/amdxdna/aie2_pci.c index 22f66c7f534d..daec1f6b4907 100644 --- a/drivers/accel/amdxdna/aie2_pci.c +++ b/drivers/accel/amdxdna/aie2_pci.c @@ -420,6 +420,7 @@ static int aie2_hw_start(struct amdxdna_dev *xdna) goto stop_fw; } + WRITE_ONCE(ndev->last_signal_ts, jiffies); ndev->dev_status = AIE2_DEV_START; return 0; diff --git a/drivers/accel/amdxdna/aie2_pci.h b/drivers/accel/amdxdna/aie2_pci.h index 77648cc548b6..ea1dac106400 100644 --- a/drivers/accel/amdxdna/aie2_pci.h +++ b/drivers/accel/amdxdna/aie2_pci.h @@ -143,11 +143,6 @@ struct aie2_exec_msg_ops { u32 (*get_chain_msg_op)(u32 cmd_op); }; -enum aie2_tdr_status { - AIE2_TDR_WAIT, - AIE2_TDR_SIGNALED, -}; - struct amdxdna_dev_hdl { struct aie_device aie; const struct amdxdna_dev_priv *priv; @@ -179,7 +174,7 @@ struct amdxdna_dev_hdl { u32 hwctx_num; struct amdxdna_async_error last_async_err; - enum aie2_tdr_status tdr_status; + unsigned long last_signal_ts; }; struct aie2_hw_ops { From f6f5ee2aa33b350c671721b965251c42cebb962e Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Thu, 16 Jul 2026 13:25:23 +0800 Subject: [PATCH 083/456] smb: client: validate DFS referral PathConsumed parse_dfs_referrals() validates that the response contains the fixed referral entry array and, on for-next, the per-referral string offsets. However, the response also contains a PathConsumed value that is later used for DFS path parsing. If a malformed response provides a PathConsumed value larger than the search name, later DFS parsing can advance beyond the end of the path. Validate PathConsumed against the search name length before storing it in the parsed referral. Fixes: 4ecce920e13a ("CIFS: move DFS response parsing out of SMB1 code") Reviewed-by: Paulo Alcantara (Red Hat) Signed-off-by: Yichong Chen Signed-off-by: Steve French --- fs/smb/client/misc.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index e4bac2a0b85d..b9c59b2cf76a 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -682,6 +682,8 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, int i, rc = 0; char *data_end; struct dfs_referral_level_3 *ref; + unsigned int path_consumed; + size_t search_name_len; if (rsp_size < sizeof(*rsp)) { cifs_dbg(VFS | ONCE, @@ -728,6 +730,7 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, rc = -ENOMEM; goto parse_DFS_referrals_exit; } + search_name_len = strlen(searchName); /* collect necessary data from referrals */ for (i = 0; i < *num_of_nodes; i++) { @@ -736,21 +739,34 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, struct dfs_info3_param *node = (*target_nodes)+i; node->flags = le32_to_cpu(rsp->DFSFlags); + path_consumed = le16_to_cpu(rsp->PathConsumed); if (is_unicode) { - __le16 *tmp = kmalloc(strlen(searchName)*2 + 2, - GFP_KERNEL); - if (tmp == NULL) { + size_t search_name_utf16_len = search_name_len * 2 + 2; + __le16 *tmp; + + if (path_consumed > search_name_utf16_len) { + rc = -EINVAL; + goto parse_DFS_referrals_exit; + } + + tmp = kmalloc(search_name_utf16_len, GFP_KERNEL); + if (!tmp) { rc = -ENOMEM; goto parse_DFS_referrals_exit; } - cifsConvertToUTF16((__le16 *) tmp, searchName, + cifsConvertToUTF16((__le16 *)tmp, searchName, PATH_MAX, nls_codepage, remap); - node->path_consumed = cifs_utf16_bytes(tmp, - le16_to_cpu(rsp->PathConsumed), - nls_codepage); + node->path_consumed = cifs_utf16_bytes(tmp, path_consumed, + nls_codepage); kfree(tmp); - } else - node->path_consumed = le16_to_cpu(rsp->PathConsumed); + } else { + if (path_consumed > search_name_len) { + rc = -EINVAL; + goto parse_DFS_referrals_exit; + } + + node->path_consumed = path_consumed; + } node->server_type = le16_to_cpu(ref->ServerType); node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags); From f8cf09a53a0dc1da298e9dd0ba5f21710cf119d6 Mon Sep 17 00:00:00 2001 From: Jay Vadayath Date: Fri, 17 Jul 2026 17:21:22 -0700 Subject: [PATCH 084/456] smb: client: bound dirent name against end of SMB response in cifs_filldir cifs_filldir() copies the entry name out of an SMB1 TRANS2_FIND_FIRST / FIND_NEXT response using a length (de.namelen) supplied by the server. The kmalloc'd SMB response buffer is bounded, but nothing checks that de.name + de.namelen still lies inside that buffer before the eventual filldir64() -> verify_dirent_name() -> memchr() reads namelen bytes. A hostile SMB1 server that returns an oversized FileNameLength in a directory entry therefore causes memchr() to read past the end of the response slab buffer. Reachable from any user who can list a directory on a CIFS mount served by an attacker-controlled server (getdents64() on the mounted directory): BUG: KASAN: slab-out-of-bounds in memchr+0x71/0x80 Read of size 1 at addr ffff88800e0640cc by task poc/115 Call Trace: dump_stack_lvl+0x64/0x80 print_report+0xce/0x620 kasan_report+0xec/0x120 memchr+0x71/0x80 filldir64+0x4c/0x6a0 cifs_filldir.constprop.0+0x9bb/0x1e00 cifs_readdir+0x2101/0x3380 iterate_dir+0x19c/0x520 __x64_sys_getdents64+0x126/0x210 do_syscall_64+0x107/0x5a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Pass the end-of-response pointer down to cifs_filldir() and reject entries whose name would extend past that boundary. This bug was discovered by Artiphishell's vTriage pipeline, which generated a userspace reproducer (an emulated hostile SMB1 server plus a getdents64() client) that reliably triggers the KASAN report on an unpatched kernel. The fix below was drafted with the Claude coding assistant; a userspace reproducer is available on request. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Jay Vadayath Signed-off-by: Steve French --- fs/smb/client/readdir.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index ee5996e6d7d8..32a75afca8f5 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -952,7 +952,7 @@ static bool cifs_dir_emit(struct dir_context *ctx, static int cifs_filldir(char *find_entry, struct file *file, struct dir_context *ctx, char *scratch_buf, unsigned int max_len, - struct cached_fid *cfid) + char *end_of_smb, struct cached_fid *cfid) { struct cifsFileInfo *file_info = file->private_data; struct super_block *sb = file_inode(file)->i_sb; @@ -974,6 +974,11 @@ static int cifs_filldir(char *find_entry, struct file *file, return -EINVAL; } + if (de.name + de.namelen > end_of_smb) { + cifs_dbg(VFS, "search entry name extends past end of SMB\n"); + return -EINVAL; + } + /* skip . and .. since we added them first */ if (cifs_entry_is_dot(&de, file_info->srch_inf.unicode)) return 0; @@ -1194,7 +1199,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx) */ *tmp_buf = 0; rc = cifs_filldir(current_entry, file, ctx, - tmp_buf, max_len, cfid); + tmp_buf, max_len, end_of_smb, cfid); if (rc) { if (rc > 0) rc = 0; From a411ea4a87162898d2a0547fdfb721ddb7626be3 Mon Sep 17 00:00:00 2001 From: Suraj Kandpal Date: Thu, 16 Jul 2026 08:39:59 +0530 Subject: [PATCH 085/456] drm/i915/backlight: Remove DP_EDP_BACKLIGHT_AUX_ENABLE_CAP check for DPCD backlight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns out some panels allow only AUX based backlight by just setting the DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP and not setting the DP_EDP_BACKLIGHT_AUX_ENABLE_CAP. If we make DP_EDP_BACKLIGHT_AUX_ENABLE_CAP a necessity for AUX based DPCD backlight these panels loose the ability to manipulate backlight via AUX, especially ones with no PWM controller. Remove this check from function so that panels who do not advertise DP_EDP_BACKLIGHT_AUX_ENABLE_CAP but advertise DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP are able to manipulate backlight again. Fixes: ed8be780bdbc ("drm/i915/backlight: Fix VESA backlight possible check condition") Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16507 Signed-off-by: Suraj Kandpal Reviewed-by: Michał Grzelak Link: https://patch.msgid.link/20260716030959.436430-1-suraj.kandpal@intel.com (cherry picked from commit 7d594b24c915afb4b0c5fb8875403253daef5b24) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c index 7a6c07f6aaeb..266e042e0023 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c +++ b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c @@ -615,12 +615,7 @@ check_if_vesa_backlight_possible(struct intel_dp *intel_dp) int ret; u8 bit_min, bit_max; - /* - * Since we only support Fully AUX Based VESA Backlight interface make sure - * backlight enable is possible via AUX along with backlight adjustment - */ - if (!(intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP && - intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP)) + if (!(intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP)) return false; ret = drm_dp_dpcd_read_byte(&intel_dp->aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &bit_min); From 17e2030f37600994440f875dc410615d5c66ee6d Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Tue, 14 Jul 2026 15:36:41 +0800 Subject: [PATCH 086/456] drm/imagination: acquire vm_ctx->lock before mapping memory to GPU VM The drm gpuvm code doesn't protect find operation against map operation, and the driver needs to ensure a map operation shouldn't happen when a find operation is in progress. In some cases a find operation will be in progress when doing map/unmap operations, and the find operation will do a NULL pointer dereference. An example of the stack trace of such NULL dereference is shown below: ``` Unable to handle kernel access to user memory without uaccess routines at virtual address 0000000000000010 [] drm_gpuva_find+0x28/0x6c [drm_gpuvm] [] pvr_vm_unmap+0x34/0x68 [powervr] [] pvr_ioctl_vm_unmap+0x2e/0x50 [powervr] [] drm_ioctl_kernel+0x8e/0xdc [] drm_ioctl+0x1be/0x3e0 [] __riscv_sys_ioctl+0xba/0xc4 [] do_trap_ecall_u+0x23e/0x3f4 [] handle_exception+0x168/0x174 ``` As all occurences of drm_gpuva_find*() are already guarded by vm_ctx->lock, make pvr_vm_map() to acquire this lock to prevent disturbing any find operation. This fixes the NULL deference problem in drm_gpuva_find*(). Cc: stable@vger.kernel.org Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Fixes: 4bc736f890ce ("drm/imagination: vm: make use of GPUVM's drm_exec helper") Signed-off-by: Icenowy Zheng Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260714073641.1935075-1-zhengxingda@iscas.ac.cn Signed-off-by: Alessio Belle --- drivers/gpu/drm/imagination/pvr_vm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c index 396d349fb6ce..ceb78694cd98 100644 --- a/drivers/gpu/drm/imagination/pvr_vm.c +++ b/drivers/gpu/drm/imagination/pvr_vm.c @@ -747,6 +747,7 @@ pvr_vm_map(struct pvr_vm_context *vm_ctx, struct pvr_gem_object *pvr_obj, pvr_gem_object_get(pvr_obj); + mutex_lock(&vm_ctx->lock); err = drm_gpuvm_exec_lock(&vm_exec); if (err) goto err_cleanup; @@ -756,6 +757,7 @@ pvr_vm_map(struct pvr_vm_context *vm_ctx, struct pvr_gem_object *pvr_obj, drm_gpuvm_exec_unlock(&vm_exec); err_cleanup: + mutex_unlock(&vm_ctx->lock); pvr_vm_bind_op_fini(&bind_op); return err; From 52beeed5e5d257e1da3e2d2f2fb25bc1e3cdb6d2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 20 Jul 2026 19:52:20 +0800 Subject: [PATCH 087/456] USB: serial: mxuport: validate firmware header size mxuport_probe() reads version bytes at fixed offsets after request_firmware() succeeds. Firmware loading success does not prove that the blob reaches the highest version offset. Reject short firmware images before reading the version bytes. This is source-level parser hardening; no affected device or crash was observed. Reviewed-by: Andrew Lunn Signed-off-by: Pengpeng Hou Fixes: ee467a1f2066 ("USB: serial: add Moxa UPORT 12XX/14XX/16XX driver") Signed-off-by: Johan Hovold --- drivers/usb/serial/mxuport.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c index e3c5a1b97542..088d5dd8abb5 100644 --- a/drivers/usb/serial/mxuport.c +++ b/drivers/usb/serial/mxuport.c @@ -1080,6 +1080,13 @@ static int mxuport_probe(struct usb_serial *serial, /* Use the firmware already in the device */ err = 0; } else { + if (fw_p->size <= VER_ADDR_3) { + dev_err(&serial->interface->dev, + "Firmware %s is too short\n", buf); + err = -EINVAL; + goto out; + } + local_ver = ((fw_p->data[VER_ADDR_1] << 16) | (fw_p->data[VER_ADDR_2] << 8) | fw_p->data[VER_ADDR_3]); From 6fcd91ce2a0787cd4bdf6a0b3cd4884566a3cdba Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 20 Jul 2026 19:48:17 +0800 Subject: [PATCH 088/456] USB: serial: io_ti: reject oversized boot-mode firmware do_boot_mode() copies the firmware payload, excluding its four-byte prefix, into a fixed 15.5 KiB staging buffer. check_fw_sanity() already proves that the image contains its seven-byte header and validates the declared image length and checksum, but it does not impose this boot-mode destination limit. Reject images whose payload does not fit before allocating and filling the staging buffer. Fixes: d12b219a228e ("edgeport-ti: use request_firmware()") Signed-off-by: Pengpeng Hou Signed-off-by: Johan Hovold --- drivers/usb/serial/io_ti.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index 07c0eff3bef4..cc28f5869a3e 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -1464,6 +1464,12 @@ static int do_boot_mode(struct edgeport_serial *serial, /* Allocate a 15.5k buffer + 3 byte header */ buffer_size = (((1024 * 16) - 512) + sizeof(struct ti_i2c_image_header)); + if (fw->size - 4 > buffer_size) { + dev_err(dev, "%s - firmware image is too large\n", + __func__); + return -EINVAL; + } + buffer = kmalloc(buffer_size, GFP_KERNEL); if (!buffer) return -ENOMEM; From 6c7b7a07db47df7745d30f4bca795f3bb5976b33 Mon Sep 17 00:00:00 2001 From: Steve French Date: Mon, 20 Jul 2026 10:16:28 -0500 Subject: [PATCH 089/456] Add missing git branch info for cifs and ksmbd to MAINTAINERS file cifs client and ksmbd server were missing the git branch info in the MAINTAINERS file. They just were showing the git tree. Signed-off-by: Steve French --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index a674e36529f7..276abc0ad458 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6478,7 +6478,7 @@ L: linux-cifs@vger.kernel.org L: samba-technical@lists.samba.org (moderated for non-subscribers) S: Supported W: https://wiki.samba.org/index.php/LinuxCIFS -T: git https://git.samba.org/sfrench/cifs-2.6.git +T: git https://git.samba.org/sfrench/cifs-2.6.git for-next F: Documentation/admin-guide/cifs/ F: fs/smb/client/ F: fs/smb/common/ @@ -14142,7 +14142,7 @@ R: Sergey Senozhatsky R: Tom Talpey L: linux-cifs@vger.kernel.org S: Maintained -T: git https://git.samba.org/ksmbd.git +T: git https://git.samba.org/ksmbd.git ksmbd-for-next F: Documentation/filesystems/smb/ksmbd.rst F: fs/smb/common/ F: fs/smb/server/ From 57441577bac3637473da2c9644336eaa0ac5732f Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Tue, 26 May 2026 19:24:47 +0530 Subject: [PATCH 090/456] drm/xe/madvise: Skip invalidation for purgeable state updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Purgeable state updates only change VMA/BO metadata. They do not zap PTEs when switching between DONTNEED and WILLNEED. PTEs are zapped later if the BO is actually purged. xe_vm_invalidate_madvise_range() waits on the VM dma-resv before checking vma->skip_invalidation. Since purgeable madvise marks all affected VMAs to skip invalidation, this wait is unnecessary and can stall on unrelated in-flight work. Skip the invalidate path entirely for purgeable state updates. v2: - Replace inline 'args->type != DRM_XE_VMA_ATTR_PURGEABLE_STATE' check with a small helper madvise_range_needs_invalidation(). (Himal) Suggested-by: Matthew Brost Cc: Matthew Brost Cc: Thomas Hellström Cc: Himal Prasad Ghimiray Signed-off-by: Arvind Yadav Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260526135447.2973029-1-arvind.yadav@intel.com Signed-off-by: Tejas Upadhyay Fixes: ada7486c5668 ("drm/xe: Implement madvise ioctl for xe") Cc: # v6.18+ (cherry picked from commit 134377098b9c14abd31c3bcac00c9653f0f0c4c3) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_vm_madvise.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vm_madvise.c b/drivers/gpu/drm/xe/xe_vm_madvise.c index 246fe1843142..0474768a38aa 100644 --- a/drivers/gpu/drm/xe/xe_vm_madvise.c +++ b/drivers/gpu/drm/xe/xe_vm_madvise.c @@ -332,6 +332,20 @@ static int xe_vm_invalidate_madvise_range(struct xe_vm *vm, u64 start, u64 end) return err; } +/** + * madvise_range_needs_invalidation() - Check whether madvise needs invalidation + * @args: madvise ioctl arguments + * + * Purgeable state updates only touch VMA/BO metadata. PTEs stay valid and are + * zapped only if the BO is later purged. + * + * Return: true when the update needs PTE invalidation. + */ +static bool madvise_range_needs_invalidation(const struct drm_xe_madvise *args) +{ + return args->type != DRM_XE_VMA_ATTR_PURGEABLE_STATE; +} + static bool madvise_args_are_sane(struct xe_device *xe, const struct drm_xe_madvise *args) { if (XE_IOCTL_DBG(xe, !args)) @@ -708,8 +722,9 @@ int xe_vm_madvise_ioctl(struct drm_device *dev, void *data, struct drm_file *fil madvise_funcs[attr_type](xe, vm, madvise_range.vmas, madvise_range.num_vmas, args, &details); - err = xe_vm_invalidate_madvise_range(vm, madvise_range.addr, - madvise_range.addr + args->range); + if (madvise_range_needs_invalidation(args)) + err = xe_vm_invalidate_madvise_range(vm, madvise_range.addr, + madvise_range.addr + args->range); if (madvise_range.has_svm_userptr_vmas) xe_svm_notifier_unlock(vm); From 1d26f125501f3fbe6c259ab75bf6516299a0bf0e Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Sat, 18 Jul 2026 02:05:59 +0100 Subject: [PATCH 091/456] regulator: mt6358: use regmap helper to read fixed LDO calibration The "fixed" LDOs with output voltage calibration use mt6358_get_buck_voltage_sel as their get_voltage_sel op, but the MT6358_REG_FIXED and MT6366_REG_FIXED entries do not populate da_vsel_reg/da_vsel_mask. The op therefore reads register 0x0 with a zero mask and shifts the result by ffs(0) - 1 = -1, which is undefined behaviour and gets flagged by UBSAN on every boot on MT6366 boards: UBSAN: shift-out-of-bounds in drivers/regulator/mt6358-regulator.c:384:38 shift exponent -1 is negative Call trace: mt6358_get_buck_voltage_sel+0xc8/0x120 regulator_get_voltage_rdev+0x70/0x170 set_machine_constraints+0x504/0xc38 regulator_register+0x324/0xc68 Besides the undefined shift, the returned selector is always 0, so the actual calibration offset programmed in _ANA_CON0 is never reported. The descriptor already carries the correct vsel_reg/vsel_mask (the ANA_CON0 calibration field), matching the regulator_set_voltage_sel_regmap op already in use. Read the selector back through regulator_get_voltage_sel_regmap instead. Fixes: cf08fa74c716 ("regulator: mt6358: Add output voltage fine tuning to fixed regulators") Signed-off-by: Daniel Golle Reviewed-by: Chen-Yu Tsai Tested-by: Chen-Yu Tsai Link: https://patch.msgid.link/dcd98d81dede338c9bbb9700a9613c848b702e49.1784336005.git.daniel@makrotopia.org Signed-off-by: Mark Brown --- drivers/regulator/mt6358-regulator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/regulator/mt6358-regulator.c b/drivers/regulator/mt6358-regulator.c index f2bb3c1523ca..d6a0ec406b07 100644 --- a/drivers/regulator/mt6358-regulator.c +++ b/drivers/regulator/mt6358-regulator.c @@ -492,7 +492,7 @@ static const struct regulator_ops mt6358_volt_fixed_ops = { .list_voltage = regulator_list_voltage_linear, .map_voltage = regulator_map_voltage_linear, .set_voltage_sel = regulator_set_voltage_sel_regmap, - .get_voltage_sel = mt6358_get_buck_voltage_sel, + .get_voltage_sel = regulator_get_voltage_sel_regmap, .set_voltage_time_sel = regulator_set_voltage_time_sel, .enable = regulator_enable_regmap, .disable = regulator_disable_regmap, From b5fa40226e71c17847b9ff2816c6ca4133d0d994 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sat, 18 Jul 2026 20:31:20 -0700 Subject: [PATCH 092/456] fscrypt: Add missing superblock check in find_or_insert_direct_key() The legacy 'fscrypt_direct_keys' table caches master keys that are used by v1 encryption policies that have FSCRYPT_POLICY_FLAG_DIRECT_KEY. It's just a global table for all filesystems (since the keys can be provided by the legacy process-subscribed keyrings mechanism, which makes it difficult to reuse super_block::s_master_keys). The entries in it ('struct fscrypt_direct_key') do contain a super_block pointer, though, for passing to fscrypt_destroy_inline_crypt_key() when the last inode that references the key is evicted. However, when finding the fscrypt_direct_key for an inode, we weren't actually comparing the super_block pointer. As a result, inodes with different super_blocks could point to the same fscrypt_direct_key. That could extend the lifetime of a fscrypt_direct_key beyond the super_block it points to, causing a use-after-free later. Fix this by creating distinct fscrypt_direct_key structs for distinct super_block structs. Note that this problem doesn't exist in the v2 policy equivalent ("per-mode keys"), since the data structures there are per super_block. Fixes: 22e9947a4b2b ("fscrypt: stop holding extra request_queue references") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260717044303.425265-1-ebiggers%40kernel.org Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260719033120.122120-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/keysetup_v1.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/crypto/keysetup_v1.c b/fs/crypto/keysetup_v1.c index e6e527c73f16..7e3a58dc4b56 100644 --- a/fs/crypto/keysetup_v1.c +++ b/fs/crypto/keysetup_v1.c @@ -147,13 +147,19 @@ find_or_insert_direct_key(struct fscrypt_direct_key *to_insert, if (memcmp(ci->ci_policy.v1.master_key_descriptor, dk->dk_descriptor, FSCRYPT_KEY_DESCRIPTOR_SIZE) != 0) continue; + /* The sb is used at eviction time, so it must be the same. */ + if (ci->ci_inode->i_sb != dk->dk_sb) + continue; if (ci->ci_mode != dk->dk_mode) continue; if (!fscrypt_is_key_prepared(&dk->dk_key, ci)) continue; if (crypto_memneq(raw_key, dk->dk_raw, ci->ci_mode->keysize)) continue; - /* using existing tfm with same (descriptor, mode, raw_key) */ + /* + * Use an existing prepared key with the same (descriptor, sb, + * mode, inlinecrypt, raw_key) combination. + */ refcount_inc(&dk->dk_refcount); spin_unlock(&fscrypt_direct_keys_lock); free_direct_key(to_insert); From 6fe4e4b8259e1330945b5f3c9476e08473b8e0e8 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sat, 18 Jul 2026 22:56:02 -0700 Subject: [PATCH 093/456] fscrypt: Avoid dynamic allocation in fscrypt_get_devices() When a blk_crypto_key starts being used or is evicted, fs/crypto/ calls fscrypt_get_devices() to get the filesystem's list of block devices, then iterates over them and calls blk_crypto_config_supported(), blk_crypto_start_using_key(), or blk_crypto_evict_key() on each one. Currently, the block device pointers are placed in a dynamically allocated array. This dynamic allocation is problematic because: - It can fail, especially at the fscrypt_destroy_inline_crypt_key() call site when it's invoked for inode eviction under direct reclaim. - fscrypt_destroy_inline_crypt_key() doesn't handle the failure. It just zeroizes and frees the blk_crypto_key without calling blk_crypto_evict_key(). That causes a use-after-free. For now, let's fix this in the straightforward and easily-backportable way by switching to an on-stack array. Currently the fscrypt multi-device functionality is used only by f2fs, which has a hardcoded limit of 8 block devices. An on-stack array works fine for that. (Of course, this solution won't scale up to large number of block devices. For that we'd need a different solution, like moving the block device iteration into the filesystem. Or in the case of btrfs, which will only support blk-crypto-fallback, we should make it just call blk-crypto-fallback directly, so the block devices won't be needed.) Fixes: 22e9947a4b2b ("fscrypt: stop holding extra request_queue references") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260713023708.9245-1-ebiggers%40kernel.org Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260719055602.78828-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/inline_crypt.c | 57 ++++++++++++++-------------------------- fs/f2fs/super.c | 25 ++++++++++-------- include/linux/fscrypt.h | 18 +++++++------ 3 files changed, 44 insertions(+), 56 deletions(-) diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c index 47324062fee5..66b9c9150fed 100644 --- a/fs/crypto/inline_crypt.c +++ b/fs/crypto/inline_crypt.c @@ -22,22 +22,14 @@ #include "fscrypt_private.h" -static struct block_device **fscrypt_get_devices(struct super_block *sb, - unsigned int *num_devs) +static unsigned int +fscrypt_get_devices(struct super_block *sb, + struct block_device *devs[FSCRYPT_MAX_DEVICES]) { - struct block_device **devs; - - if (sb->s_cop->get_devices) { - devs = sb->s_cop->get_devices(sb, num_devs); - if (devs) - return devs; - } - devs = kmalloc_obj(*devs); - if (!devs) - return ERR_PTR(-ENOMEM); + if (sb->s_cop->get_devices) + return sb->s_cop->get_devices(sb, devs); devs[0] = sb->s_bdev; - *num_devs = 1; - return devs; + return 1; } static unsigned int fscrypt_get_dun_bytes(const struct fscrypt_inode_info *ci) @@ -96,7 +88,7 @@ int fscrypt_select_encryption_impl(struct fscrypt_inode_info *ci, const struct inode *inode = ci->ci_inode; struct super_block *sb = inode->i_sb; struct blk_crypto_config crypto_cfg; - struct block_device **devs; + struct block_device *devs[FSCRYPT_MAX_DEVICES]; unsigned int num_devs; unsigned int i; @@ -135,20 +127,15 @@ int fscrypt_select_encryption_impl(struct fscrypt_inode_info *ci, crypto_cfg.key_type = is_hw_wrapped_key ? BLK_CRYPTO_KEY_TYPE_HW_WRAPPED : BLK_CRYPTO_KEY_TYPE_RAW; - devs = fscrypt_get_devices(sb, &num_devs); - if (IS_ERR(devs)) - return PTR_ERR(devs); - + num_devs = fscrypt_get_devices(sb, devs); for (i = 0; i < num_devs; i++) { if (!blk_crypto_config_supported(devs[i], &crypto_cfg)) - goto out_free_devs; + return 0; } fscrypt_log_blk_crypto_impl(ci->ci_mode, devs, num_devs, &crypto_cfg); ci->ci_inlinecrypt = true; -out_free_devs: - kfree(devs); return 0; } @@ -164,7 +151,7 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, enum blk_crypto_key_type key_type = is_hw_wrapped ? BLK_CRYPTO_KEY_TYPE_HW_WRAPPED : BLK_CRYPTO_KEY_TYPE_RAW; struct blk_crypto_key *blk_key; - struct block_device **devs; + struct block_device *devs[FSCRYPT_MAX_DEVICES]; unsigned int num_devs; unsigned int i; int err; @@ -182,17 +169,12 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, } /* Start using blk-crypto on all the filesystem's block devices. */ - devs = fscrypt_get_devices(sb, &num_devs); - if (IS_ERR(devs)) { - err = PTR_ERR(devs); - goto fail; - } + num_devs = fscrypt_get_devices(sb, devs); for (i = 0; i < num_devs; i++) { err = blk_crypto_start_using_key(devs[i], blk_key); if (err) break; } - kfree(devs); if (err) { fscrypt_err(inode, "error %d starting to use blk-crypto", err); goto fail; @@ -210,20 +192,21 @@ void fscrypt_destroy_inline_crypt_key(struct super_block *sb, struct fscrypt_prepared_key *prep_key) { struct blk_crypto_key *blk_key = prep_key->blk_key; - struct block_device **devs; + struct block_device *devs[FSCRYPT_MAX_DEVICES]; unsigned int num_devs; unsigned int i; if (!blk_key) return; - /* Evict the key from all the filesystem's block devices. */ - devs = fscrypt_get_devices(sb, &num_devs); - if (!IS_ERR(devs)) { - for (i = 0; i < num_devs; i++) - blk_crypto_evict_key(devs[i], blk_key); - kfree(devs); - } + /* + * Evict the key from all the filesystem's block devices. + * This *must* be done before the key is freed. + */ + num_devs = fscrypt_get_devices(sb, devs); + for (i = 0; i < num_devs; i++) + blk_crypto_evict_key(devs[i], blk_key); + kfree_sensitive(blk_key); } diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 2b8d96411156..9760e4efeffe 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -3749,24 +3749,27 @@ static bool f2fs_has_stable_inodes(struct super_block *sb) return true; } -static struct block_device **f2fs_get_devices(struct super_block *sb, - unsigned int *num_devs) +static unsigned int +f2fs_get_devices(struct super_block *sb, + struct block_device *devs[FSCRYPT_MAX_DEVICES]) { struct f2fs_sb_info *sbi = F2FS_SB(sb); - struct block_device **devs; + int ndevs; int i; - if (!f2fs_is_multi_device(sbi)) - return NULL; + static_assert(MAX_DEVICES <= FSCRYPT_MAX_DEVICES); - devs = kmalloc_objs(*devs, sbi->s_ndevs); - if (!devs) - return ERR_PTR(-ENOMEM); + if (!f2fs_is_multi_device(sbi)) { + devs[0] = sb->s_bdev; + return 1; + } + ndevs = sbi->s_ndevs; + if (WARN_ON_ONCE(ndevs > FSCRYPT_MAX_DEVICES)) + ndevs = FSCRYPT_MAX_DEVICES; - for (i = 0; i < sbi->s_ndevs; i++) + for (i = 0; i < ndevs; i++) devs[i] = FDEV(i).bdev; - *num_devs = sbi->s_ndevs; - return devs; + return ndevs; } static const struct fscrypt_operations f2fs_cryptops = { diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 54712ec61ffb..f6b235cd72b4 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -57,6 +57,9 @@ struct fscrypt_name { /* Maximum value for the third parameter of fscrypt_operations.set_context(). */ #define FSCRYPT_SET_CONTEXT_MAX_SIZE 40 +/* Maximum supported number of block devices per filesystem */ +#define FSCRYPT_MAX_DEVICES 8 + #ifdef CONFIG_FS_ENCRYPTION /* Crypto operations for filesystems */ @@ -181,21 +184,20 @@ struct fscrypt_operations { bool (*has_stable_inodes)(struct super_block *sb); /* - * Return an array of pointers to the block devices to which the - * filesystem may write encrypted file contents, NULL if the filesystem - * only has a single such block device, or an ERR_PTR() on error. + * Retrieve the list of block devices to which the filesystem may write + * encrypted file contents. * - * On successful non-NULL return, *num_devs is set to the number of - * devices in the returned array. The caller must free the returned - * array using kfree(). + * This writes the block_device pointers to @devs and returns the count + * (between 1 and FSCRYPT_MAX_DEVICES inclusively). * * If the filesystem can use multiple block devices (other than block * devices that aren't used for encrypted file contents, such as * external journal devices), and wants to support inline encryption, * then it must implement this function. Otherwise it's not needed. */ - struct block_device **(*get_devices)(struct super_block *sb, - unsigned int *num_devs); + unsigned int (*get_devices)( + struct super_block *sb, + struct block_device *devs[FSCRYPT_MAX_DEVICES]); }; int fscrypt_d_revalidate(struct inode *dir, const struct qstr *name, From bbf5f639918dc011aaf60aab8480218758ee68c5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 20 Jul 2026 14:36:49 +0200 Subject: [PATCH 094/456] binfmt_misc: set have_execfd only once the interpreter is opened load_misc_binary() raises bprm->have_execfd as soon as it sees the 'O' (or 'C') flag. This happens well before it opens the interpreter. If that open fails the flag stays set on the bprm. binfmt_misc is at the head of the format list so an interpreter open failure that returns -ENOEXEC lets the search fall through to a later format. This means it runs the matched binary directly having never staged an interpreter. So bprm->executable is NULL while have_execfd falsely claims a descriptor is present. Consequently, begin_new_exec() dereferences the missing executable: would_dump(bprm, bprm->executable); and NULL derefs. Had it not, the hand-off later in the same function would have failed anyway. FD_ADD(0, bprm->executable) rejects a NULL file with -ENOMEM. Both sites are past the point of no return so the exec cannot be unwound either way. This can be reached by unprivileged users as binfmt_misc can be mounted in user namespaces. So a user can register an 'O' entry whose interpreter lives on a FUSE mount, have the FUSE server fail the open with -ENOEXEC and execute a native ELF file that matches the entry. have_execfd only means anything alongside the executable it describes which is not set until the interpreter has been opened and staged. So lets raise it there, next to execfd_creds, which is already set at that point. An open failure now leaves it clear, so the fallback format derives credentials from the binary and emits no AT_EXECFD, as it would for any native exec. The argv rewrite load_misc_binary() performs before the open is still not undone. This means the binary sees the interpreter path in argv[0] and its own path in argv[1] but that predates this change and only became observable once the exec stopped faulting. Link: https://patch.msgid.link/20260720-beglichen-kognitiv-organismus-5e1e55326c56@brauner Fixes: bc2bf338d54b ("exec: Remove recursion from search_binary_handler") Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index 84349fcb93f1..5de615ca7a75 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -228,9 +228,6 @@ static int load_misc_binary(struct linux_binprm *bprm) goto ret; } - if (fmt->flags & MISC_FMT_OPEN_BINARY) - bprm->have_execfd = 1; - /* make argv[1] be the path to the binary */ retval = copy_string_kernel(bprm->interp, bprm); if (retval < 0) @@ -260,6 +257,8 @@ static int load_misc_binary(struct linux_binprm *bprm) goto ret; bprm->interpreter = interp_file; + if (fmt->flags & MISC_FMT_OPEN_BINARY) + bprm->have_execfd = 1; if (fmt->flags & MISC_FMT_CREDENTIALS) bprm->execfd_creds = 1; From ea3034b2b00fa50c8d2518d0804c9d427bbafa86 Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Sat, 27 Jun 2026 16:22:43 -0400 Subject: [PATCH 095/456] mm/vmstat: fold stranded per-cpu node stats when a node comes online A per-node vmstat counter is pgdat->vm_stat[] plus per-cpu deltas. A balanced counter can sit split as global=+N / per-cpu=-N. The folds reconciling the split only walk online nodes, so when try_offline_node() marks a node offline the per-cpu deltas are stranded. A subsequent online resets the per-cpu area but not pgdat->vm_stat[], orphaning the +N permanently. All NR_VM_NODE_STAT_ITEMS are affected. The existing code zeroes the per-cpu counters and causes a permanent skew. Fold the stranded deltas instead, before the node rejoins the online set. The node is not online yet and the hotplug lock is held, so the remote access to per-cpu values is safe. Discovered when node compaction hung for a nearly empty node, as the math to determine throttling broke. Reproduced by repeated memory hotplug/unplug cycles on a node under pressure: NR_ISOLATED_ANON ratchets up and never returns to zero. Link: https://lore.kernel.org/20260627202243.758289-1-gourry@gourry.net Fixes: 75ef71840539 ("mm, vmstat: add infrastructure for per-node vmstats") Signed-off-by: Gregory Price Cc: Johannes Weiner Cc: Mel Gorman Cc: Mike Rapoport Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/mm_init.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mm/mm_init.c b/mm/mm_init.c index 0f64909e8d20..498d62c4ece3 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -1540,7 +1540,7 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat) { int nid = pgdat->node_id; enum zone_type z; - int cpu; + int cpu, i; pgdat_init_internals(pgdat); @@ -1558,10 +1558,17 @@ void __ref free_area_init_core_hotplug(struct pglist_data *pgdat) pgdat->node_start_pfn = 0; pgdat->node_present_pages = 0; - for_each_online_cpu(cpu) { - struct per_cpu_nodestat *p; + /* + * Hot-unplug can leave per-cpu vmstat deltas unfolded (folders skip + * offline nodes) - reconcile this at online. Foreign access to counters + * is safe: the node is not online yet and we hold the hotplug lock. + */ + for_each_possible_cpu(cpu) { + struct per_cpu_nodestat *p = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu); - p = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu); + for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++) + if (p->vm_node_stat_diff[i]) + node_page_state_add(p->vm_node_stat_diff[i], pgdat, i); memset(p, 0, sizeof(*p)); } From 4fc089235378d1f9ddb6bcbe67c192ffdcaae0ed Mon Sep 17 00:00:00 2001 From: Stanislav Kinsburskii Date: Mon, 29 Jun 2026 16:30:14 -0700 Subject: [PATCH 096/456] lib: test_hmm: use device devt for coherent device range selection Commit af69016dab96 ("lib: test_hmm: implement a device release method") moved the initial dmirror_allocate_chunk() call before cdev_device_add(). That means the struct cdev has not been added yet, so cdev_add() has not initialized mdevice->cdevice.dev. The coherent-device range selection uses the device minor to choose between spm_addr_dev0 and spm_addr_dev1. Reading MINOR(mdevice->cdevice.dev) before cdev_add() therefore always sees an uninitialized dev_t. As a result, both coherent devices select the same physical range, and adding the second device fails due to the overlapping dev_pagemap range. Use mdevice->device.devt instead. It is initialized in dmirror_device_init() before dmirror_allocate_chunk() is called and is the same dev_t later passed to cdev_device_add(). Link: https://lore.kernel.org/178277581197.172200.16265155329935822153.stgit@skinsburskii Fixes: af69016dab96 ("lib: test_hmm: implement a device release method") Signed-off-by: Stanislav Kinsburskii Reviewed-by: Alistair Popple Cc: Balbir Singh Cc: Zenghui Yu (Huawei) Cc: Jason Gunthorpe Cc: Leon Romanovsky Cc: Signed-off-by: Andrew Morton --- lib/test_hmm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test_hmm.c b/lib/test_hmm.c index 9c59d1ceb5b5..c4adbf98fac7 100644 --- a/lib/test_hmm.c +++ b/lib/test_hmm.c @@ -581,7 +581,7 @@ static int dmirror_allocate_chunk(struct dmirror_device *mdevice, devmem->pagemap.type = MEMORY_DEVICE_PRIVATE; break; case HMM_DMIRROR_MEMORY_DEVICE_COHERENT: - devmem->pagemap.range.start = (MINOR(mdevice->cdevice.dev) - 2) ? + devmem->pagemap.range.start = (MINOR(mdevice->device.devt) - 2) ? spm_addr_dev0 : spm_addr_dev1; devmem->pagemap.range.end = devmem->pagemap.range.start + From 4165b7d1c45c2da0dfefe528f8d1fb7d79f0d344 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Sun, 5 Jul 2026 06:12:31 -0700 Subject: [PATCH 097/456] userfaultfd: wait on source PMD during UFFDIO_MOVE move_pages_huge_pmd() snapshots src_pmdval under src_ptl, drops the lock, and, for migration entries, waits with pmd_migration_entry_wait(). Passing &src_pmdval is wrong. pmd_migration_entry_wait() must lock and re-read the real page-table PMD; on split-PMD-lock kernels, a stack address also resolves to the wrong lock. softleaf_entry_wait_on_locked() then waits without a folio reference, which is safe only while serialized against migration-entry removal by the real PT lock. Pass src_pmd, matching __handle_mm_fault() and hmm_vma_walk_pmd(). Link: https://lore.kernel.org/20260705131231.1499198-1-usama.arif@linux.dev Fixes: adef440691ba ("userfaultfd: UFFDIO_MOVE uABI") Reported-by: sashiko-bot Link: https://sashiko.dev/#/patchset/20260703173903.3789516-1-usama.arif%40linux.dev?part=8 Signed-off-by: Usama Arif Reviewed-by: Rik van Riel Reviewed-by: Baolin Wang Reviewed-by: Lance Yang Acked-by: David Hildenbrand (Arm) Reviewed-by: Lance Yang Reviewed-by: Lorenzo Stoakes Cc: Andrea Arcangeli Cc: Barry Song Cc: Dev Jain Cc: Johannes Weiner Cc: Liam R. Howlett Cc: Nico Pache Cc: Ryan Roberts Cc: Shakeel Butt Cc: Zi Yan Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index b5d1e9d4463d..032702a4637b 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -2774,7 +2774,7 @@ int move_pages_huge_pmd(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd, pm if (!pmd_trans_huge(src_pmdval)) { spin_unlock(src_ptl); if (pmd_is_migration_entry(src_pmdval)) { - pmd_migration_entry_wait(mm, &src_pmdval); + pmd_migration_entry_wait(mm, src_pmd); return -EAGAIN; } return -ENOENT; From 07b4377bdbe74a3ec0c8da5849d014f70e003384 Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Tue, 7 Jul 2026 16:13:49 +0100 Subject: [PATCH 098/456] fs/proc/task_mmu: fix PAGEMAP_SCAN written state for unpopulated ptes PAGEMAP_SCAN reports an unpopulated pte differently depending on which path serves the request. The PAGE_IS_WRITTEN fast path in pagemap_scan_pmd_entry() reports a pte_none as written (and, under PM_SCAN_WP_MATCHING, arms a marker); pagemap_page_category() returns 0 for the same pte_none. A request that cannot take the fast path (an extra category bit, category_anyof_mask or category_inverted) therefore reports the pte as clean and skips arming it. A range that was populated and then MADV_DONTNEED'd reads as written via one mask and clean via another, and in the latter case is not re-armed for the next round -- an incremental-dump consumer (e.g. CRIU) using a richer mask drops the zapped range and stops tracking writes to it. Report pte_none as written in pagemap_page_category() too. A pte_none carries no uffd-wp marker, i.e. it is not write-protected -- the same condition under which the present and swap cases already report PAGE_IS_WRITTEN. The fast path applies no VMA test, so neither does this. The hugetlb and fully-unpopulated-PMD (no page table) scans have no PAGE_IS_WRITTEN fast path, so they do not exhibit the per-entry divergence and are left unchanged. Add a pagemap_ioctl selftest that populates a range, drops it with MADV_DONTNEED, and checks that the fast path and the generic (category_anyof_mask) path both report every page written. Link: https://lore.kernel.org/20260707151349.92143-1-kirill@shutemov.name Fixes: 12f6b01a0bcb ("fs/proc/task_mmu: add fast paths to get/clear PAGE_IS_WRITTEN flag") Signed-off-by: Kiryl Shutsemau Cc: Muhammad Usama Anjum Cc: David Hildenbrand Cc: Jann Horn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Pedro Falcato Cc: Peter Xu Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Assisted-by: Claude:claude-fable-5 Cc: Signed-off-by: Andrew Morton --- fs/proc/task_mmu.c | 14 +++++- tools/testing/selftests/mm/pagemap_ioctl.c | 56 +++++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index d32408f7cd5e..d45c729ab6bb 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -2432,8 +2432,18 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p, { unsigned long categories; - if (pte_none(pte)) - return 0; + if (pte_none(pte)) { + /* + * An unpopulated pte carries no uffd-wp marker, i.e. it is not + * write-protected, the same condition under which the present + * and swap cases below report PAGE_IS_WRITTEN. Report it here + * too so this generic path agrees with the PAGE_IS_WRITTEN fast + * path in pagemap_scan_pmd_entry(), which reports pte_none as + * written and, under PM_SCAN_WP_MATCHING, arms a marker. The + * fast path applies no VMA test, so neither does this. + */ + return PAGE_IS_WRITTEN; + } if (pte_present(pte)) { struct page *page; diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c index 6f8971d5b3ce..f9bcff8e78fa 100644 --- a/tools/testing/selftests/mm/pagemap_ioctl.c +++ b/tools/testing/selftests/mm/pagemap_ioctl.c @@ -1051,6 +1051,57 @@ static void test_simple(void) ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__); } +/* + * A range that was populated and then MADV_DONTNEED'd is genuine pte_none + * with no uffd-wp marker. Such a pte must read the same regardless of which + * PAGEMAP_SCAN path serves the request: both the PAGE_IS_WRITTEN fast path and + * the generic path (reached e.g. via category_anyof_mask) must report every + * page written. + */ +static void unpopulated_scan_test(void) +{ + int npages = 16, i; + long mem_size = npages * page_size; + struct page_region regions[16]; + long fast = 0, slow = 0, ret; + char *mem; + + mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mem == MAP_FAILED) + ksft_exit_fail_msg("%s mmap failed\n", __func__); + + wp_init(mem, mem_size); + + /* Populate, then drop: the ptes become pte_none without a marker. */ + memset(mem, 1, mem_size); + if (madvise(mem, mem_size, MADV_DONTNEED)) + ksft_exit_fail_msg("%s MADV_DONTNEED failed\n", __func__); + + /* Fast path: category_mask == return_mask == PAGE_IS_WRITTEN. */ + ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0, + PAGE_IS_WRITTEN, 0, 0, PAGE_IS_WRITTEN); + if (ret < 0) + ksft_exit_fail_msg("%s fast scan failed\n", __func__); + for (i = 0; i < ret; i++) + fast += LEN(regions[i]); + + /* Generic path: same query expressed via category_anyof_mask. */ + ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0, + 0, PAGE_IS_WRITTEN, 0, PAGE_IS_WRITTEN); + if (ret < 0) + ksft_exit_fail_msg("%s generic scan failed\n", __func__); + for (i = 0; i < ret; i++) + slow += LEN(regions[i]); + + ksft_test_result(fast == npages && slow == npages, + "%s unpopulated ptes reported written by both paths (%ld, %ld of %d)\n", + __func__, fast, slow, npages); + + wp_free(mem, mem_size); + munmap(mem, mem_size); +} + int sanity_tests(void) { unsigned long long mem_size, vec_size; @@ -1559,7 +1610,7 @@ int main(int __attribute__((unused)) argc, char *argv[]) if (!hugetlb_setup_default(4)) ksft_print_msg("HugeTLB test will be skipped\n"); - ksft_set_plan(117); + ksft_set_plan(118); page_size = getpagesize(); hpage_size = read_pmd_pagesize(); @@ -1737,6 +1788,9 @@ int main(int __attribute__((unused)) argc, char *argv[]) /* 17. ZEROPFN tests */ zeropfn_tests(); + /* 18. Unpopulated pte scan-path consistency */ + unpopulated_scan_test(); + close(pagemap_fd); ksft_finished(); } From 63867c82d0c0c2d182016a32b1cc0103116b0ea5 Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Mon, 6 Jul 2026 19:19:58 +0800 Subject: [PATCH 099/456] mm: migrate_device: fix pte_pfn/pte_dirty called on non-present PTE pte_pfn() and pte_dirty() have undefined behaviour when called on a non-present PTE. In migrate_vma_collect_pmd(), these functions may be invoked on non-present entries (e.g., device-private entries), leading to potential crashes from pte_pfn() or incorrect dirty folio accounting from pte_dirty(). Fix both by guarding with pte_present() checks. Link: https://lore.kernel.org/20260708003955.4024340-1-wangkefeng.wang@huawei.com Link: https://lore.kernel.org/20260706111958.3649651-1-wangkefeng.wang@huawei.com Fixes: fd35ca3d12cc ("mm/migrate_device.c: copy pte dirty bit to page") Fixes: 6c287605fd56 ("mm: remember exclusively mapped anonymous pages with PG_anon_exclusive") Signed-off-by: Kefeng Wang Reviewed-by: Balbir Singh Acked-by: Zi Yan Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: Gregory Price Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Cc: Ying Huang Cc: Signed-off-by: Andrew Morton --- mm/migrate_device.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/migrate_device.c b/mm/migrate_device.c index 554754eb26ff..908d2d4ec43a 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -401,7 +401,8 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp, bool anon_exclusive; pte_t swp_pte; - flush_cache_page(vma, addr, pte_pfn(pte)); + if (pte_present(pte)) + flush_cache_page(vma, addr, pte_pfn(pte)); anon_exclusive = folio_test_anon(folio) && PageAnonExclusive(page); if (anon_exclusive) { @@ -422,7 +423,7 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp, migrate->cpages++; /* Set the dirty flag on the folio now the pte is gone. */ - if (pte_dirty(pte)) + if (pte_present(pte) && pte_dirty(pte)) folio_mark_dirty(folio); /* Setup special migration page table entry */ From 83abe2fd5b3aeb3123b5408a5a91709c5538fb23 Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Wed, 8 Jul 2026 10:01:10 +0100 Subject: [PATCH 100/456] mm/hugetlb: fix swap entry corruption when clearing uffd-wp at fork() copy_hugetlb_page_range() clears the uffd-wp bit of migration and hwpoison entries with huge_pte_clear_uffd_wp(), which operates on the present-PTE bit position. Swap entries keep the uffd-wp state elsewhere -- the migration branch reads and sets it with pte_swp_uffd_wp() and pte_swp_mkuffd_wp() -- and the present-PTE position falls into the swap payload. On x86-64 it lands in the inverted swap offset, where a naturally-aligned hugetlb PFN always has the affected bit set, so the clear advances the encoded PFN by two pages. No userfaultfd needs to be involved: the clear is guarded only by the child VMA not being uffd-wp registered, so a plain fork() with an in-flight hugetlb migration entry (or a poisoned hugetlb page) corrupts the entry copied into the child. Instrumenting the clear and forking after MADV_HWPOISON on a 2MB anon hugetlb page shows: offset before=120e00 offset after =120e02 The fallout is mostly latent: rmap walks match migration entries by folio range and remove_migration_pte() rebuilds the PTE from the folio, so a within-folio PFN skew heals once migration completes. But any path that re-encodes the corrupted offset -- e.g. hugetlb_change_protection() rewriting a writable migration entry via make_readable_migration_entry(swp_offset(entry)) -- propagates it. Migration entries legitimately carry uffd-wp, so clear it with pte_swp_clear_uffd_wp(), matching copy_nonpresent_pte() and move_huge_pte(). A hwpoison entry, on the other hand, never carries the uffd-wp bit: it is installed fresh by make_hwpoison_entry() (try_to_unmap_one() does not preserve uffd-wp on the hwpoison path) and hugetlb_change_protection() leaves hwpoison entries untouched. There was nothing to clear there, only the corruption, so drop the clear entirely. Link: https://lore.kernel.org/20260708090110.136162-1-kirill@shutemov.name Fixes: bc70fbf269fd ("mm/hugetlb: handle uffd-wp during fork()") Signed-off-by: Kiryl Shutsemau Reported-by: Sashiko AI review Closes: https://lore.kernel.org/all/20260703140011.99E601F000E9@smtp.kernel.org/ Suggested-by: David Hildenbrand Acked-by: David Hildenbrand (Arm) Assisted-by: Claude:claude-fable-5 Cc: Muchun Song Cc: Oscar Salvador Cc: Peter Xu Cc: Signed-off-by: Andrew Morton --- mm/hugetlb.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 571212b80835..bca2707d02e3 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -4917,8 +4917,12 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, softleaf = softleaf_from_pte(entry); if (unlikely(softleaf_is_hwpoison(softleaf))) { - if (!userfaultfd_wp(dst_vma)) - entry = huge_pte_clear_uffd_wp(entry); + /* + * A hwpoison entry never carries the uffd-wp bit: it is + * installed fresh by make_hwpoison_entry() and + * hugetlb_change_protection() leaves it untouched, so + * there is nothing to clear for the child. + */ set_huge_pte_at(dst, addr, dst_pte, entry, sz); } else if (unlikely(softleaf_is_migration(softleaf))) { bool uffd_wp = pte_swp_uffd_wp(entry); @@ -4936,7 +4940,7 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, set_huge_pte_at(src, addr, src_pte, entry, sz); } if (!userfaultfd_wp(dst_vma)) - entry = huge_pte_clear_uffd_wp(entry); + entry = pte_swp_clear_uffd_wp(entry); set_huge_pte_at(dst, addr, dst_pte, entry, sz); } else if (unlikely(pte_is_marker(entry))) { const pte_marker marker = copy_pte_marker(softleaf, dst_vma); From 7441d6348c70738e9ed307510db171c7a9b3f4bf Mon Sep 17 00:00:00 2001 From: Aboorva Devarajan Date: Thu, 9 Jul 2026 01:49:54 +0530 Subject: [PATCH 101/456] mm/util: don't read __page_2 for order-1 folios in snapshot_page() snapshot_page() currently reads __page_2 after checking nr_pages > 1, but it should only do so when nr_pages > 2. If an order-1 folio is allocated at the end of a vmemmap section, __page_2 will not exist and reading it will cause a fault. During DLPAR memory remove on a 22 TB ppc64le LPAR, snapshot_page() oopsed on the page isolation path while reading an order-1 folio's __page_2 from an adjacent absent section (unmapped vmemmap). Fix this to avoid reading memmap that doesn't exist (e.g., a vmemmap hole). Link: https://lore.kernel.org/20260708201954.686111-1-aboorvad@linux.ibm.com Fixes: 31a31da8a618 ("mm: move _pincount in folio to page[2] on 32bit") Signed-off-by: Aboorva Devarajan Reported-by: Sourabh Jain Acked-by: David Hildenbrand (Arm) Reviewed-by: Lorenzo Stoakes Reviewed-by: Matthew Wilcox (Oracle) Reviewed-by: Luiz Capitulino Cc: Liam R. Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: "Ritesh Harjani (IBM)" Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: # v6.15+ Signed-off-by: Andrew Morton --- mm/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/util.c b/mm/util.c index af2c2103f0d9..34cb43b3eaa4 100644 --- a/mm/util.c +++ b/mm/util.c @@ -1353,7 +1353,7 @@ void snapshot_page(struct page_snapshot *ps, const struct page *page) if (ps->idx < MAX_FOLIO_NR_PAGES) { memcpy(&ps->folio_snapshot, foliop, 2 * sizeof(struct page)); nr_pages = folio_nr_pages(&ps->folio_snapshot); - if (nr_pages > 1) + if (nr_pages > 2) memcpy(&ps->folio_snapshot.__page_2, &foliop->__page_2, sizeof(struct page)); set_ps_flags(ps, foliop, page); From 89b1b79c308818a715e75f28744b70d8940a07c9 Mon Sep 17 00:00:00 2001 From: Zi Yan Date: Thu, 9 Jul 2026 15:12:01 -0400 Subject: [PATCH 102/456] mm/percpu-km: fix bitmap overflow and accounting in pcpu_create_chunk() In pcpu_create_chunk(), nr_pages is the total contiguous backing allocation, i.e., nr_units * pcpu_unit_pages, but pcpu_chunk_populated() uses it to set chunk->populated, whose size is pcpu_unit_pages, bitmap. Since bit N in chunk->populated means page offset N inside every unit is backed. When nr_units > 1, the function writes beyond chunk->populated. Fix it by using chunk->nr_pages. It also fixes the global pcpu_nr_empty_pop_pages accounting, since pcpu_balance_free() only iterates up to chunk->nr_pages. Commit a63d4ac4ab609 ("percpu: make percpu-km set chunk->populated bitmap properly") introduced the bitmap overflow issue. Later, commit b539b87fed37f ("percpu: implmeent pcpu_nr_empty_pop_pages and chunk->nr_populated") added pcpu_nr_empty_pop_pages and caused the accounting issue. Link: https://lore.kernel.org/20260709-fix-pcpu_create_chunk-in-percpu-km-v1-1-1f64745a84cc@nvidia.com Fixes: a63d4ac4ab609 ("percpu: make percpu-km set chunk->populated bitmap properly") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260703-keep-subpage-private-zero-at-free-v2-0-2970fe777dd6%40nvidia.com?part=1 Assisted-by: Codex:GPT-5 Signed-off-by: Zi Yan Acked-by: Dennis Zhou Cc: Christoph Lameter Cc: Tejun Heo Cc: Zi Yan Cc: Signed-off-by: Andrew Morton --- mm/percpu-km.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/percpu-km.c b/mm/percpu-km.c index 4efa74a495cb..dc096b5a6ce4 100644 --- a/mm/percpu-km.c +++ b/mm/percpu-km.c @@ -75,7 +75,7 @@ static struct pcpu_chunk *pcpu_create_chunk(gfp_t gfp) chunk->base_addr = page_address(pages); spin_lock_irqsave(&pcpu_lock, flags); - pcpu_chunk_populated(chunk, 0, nr_pages); + pcpu_chunk_populated(chunk, 0, chunk->nr_pages); spin_unlock_irqrestore(&pcpu_lock, flags); pcpu_stats_chunk_alloc(); From df8ce7ab48d01ac4f247599b35f0506d95ff57e1 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 10 Jul 2026 12:05:12 +0800 Subject: [PATCH 103/456] ocfs2: fix boundary check in ocfs2_check_dir_entry() to use buffer offset Commit 390ac56cf0f6 ("ocfs2: add boundary check to ocfs2_check_dir_entry()") added an out-of-bounds guard using the caller-supplied 'offset' argument: if (offset > size - OCFS2_DIR_REC_LEN(1)) return 0; However, 'offset' and 'size' are not measured against the same base for all callers. In the block-based lookup path, ocfs2_find_entry_el() passes 'offset' as an absolute offset into the whole directory: i = ocfs2_search_dirblock(bh, dir, name, namelen, block << sb->s_blocksize_bits, bh->b_data, sb->s_blocksize, res_dir); while 'size' is a single block size (sb->s_blocksize). For any directory entry located in the second or later block, 'offset' is >= sb->s_blocksize, so the guard rejects every such entry even though it is perfectly valid and lies entirely within its block buffer. This makes mounting fail for filesystems whose system directory spans more than one block, e.g. a volume formatted with a small block size: mkfs.ocfs2 -b 512 -C 4096 -N 2 -T datafiles --fs-features=usrquota,grpquota ocfs2_check_dir_entry:314 ERROR: directory entry (#18: offset=512) too close to end or out-of-bounds ocfs2_init_local_system_inodes:496 ERROR: status=-22, sysfile=12, slot=0 ocfs2_mount_volume:1757 ERROR: status = -22 The dirent's position within the buffer being validated is ((char *)de - buf), which is what the rest of the function already uses (via next_offset) and what must be bounds-checked against 'size'. Compute that buffer-relative offset and use it for the guard. The subtraction is reordered to size - buf_offset < OCFS2_DIR_REC_LEN(1) to avoid an unsigned underflow when size is smaller than the minimal record length. Link: https://lore.kernel.org/20260710040512.3310736-1-joseph.qi@linux.alibaba.com Fixes: 390ac56cf0f6 ("ocfs2: add boundary check to ocfs2_check_dir_entry()") Signed-off-by: Joseph Qi Reviewed-by: Dmitry Antipov Tested-by: Dmitry Antipov 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/dir.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c index 8e6b03238327..d7fc3cccf2f4 100644 --- a/fs/ocfs2/dir.c +++ b/fs/ocfs2/dir.c @@ -302,10 +302,11 @@ static int ocfs2_check_dir_entry(struct inode *dir, unsigned long offset) { const char *error_msg = NULL; + unsigned long buf_offset = (char *)de - buf; unsigned long next_offset; int rlen; - if (offset > size - OCFS2_DIR_REC_LEN(1)) { + if (buf_offset > size || size - buf_offset < OCFS2_DIR_REC_LEN(1)) { /* Dirent is (maybe partially) beyond the buffer * boundaries so touching 'de' members is unsafe. */ @@ -316,7 +317,7 @@ static int ocfs2_check_dir_entry(struct inode *dir, } rlen = le16_to_cpu(de->rec_len); - next_offset = ((char *) de - buf) + rlen; + next_offset = buf_offset + rlen; if (unlikely(rlen < OCFS2_DIR_REC_LEN(1))) error_msg = "rec_len is smaller than minimal"; From e3a0127eee04db8769e53c8102c4e76aa49be8c3 Mon Sep 17 00:00:00 2001 From: Jori Koolstra Date: Fri, 10 Jul 2026 19:17:35 +0200 Subject: [PATCH 104/456] selftest: fix headers in fclog.c fclog.c does not compile because it is missing fcntl.h, needed for O_RDONLY etc. There are also some redundant includes that are also in kselftest_harness.h. Link: https://lore.kernel.org/20260710171741.837308-1-jkoolstra@xs4all.nl Signed-off-by: Jori Koolstra Cc: Aleksa Sarai Cc: Shuah Khan Cc: Wei Yang Cc: Christian Brauner Cc: Signed-off-by: Andrew Morton --- tools/testing/selftests/filesystems/fclog.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/testing/selftests/filesystems/fclog.c b/tools/testing/selftests/filesystems/fclog.c index 551c4a0f395a..593a5136e991 100644 --- a/tools/testing/selftests/filesystems/fclog.c +++ b/tools/testing/selftests/filesystems/fclog.c @@ -6,10 +6,8 @@ #include #include +#include #include -#include -#include -#include #include #include From de4660898b7aa7e03d3b120a6bfa6b26211e4e77 Mon Sep 17 00:00:00 2001 From: Qi Zheng Date: Fri, 10 Jul 2026 23:43:18 +0800 Subject: [PATCH 105/456] mm: mglru: fix stale batch updates after memcg reparenting The mglru page table walker batches per-generation size deltas in walk->nr_pages while walking page tables without holding the lruvec lock. The reset_batch_size() later folds those deltas into walk->lruvec under the lruvec lock. The page table walker can run concurrently with the memcg reparenting path as follows: CPU0 CPU1 ==== ==== walk_mm --> walk_page_range --> update_batch_size --> walk->nr_pages += delta mem_cgroup_css_offline --> memcg_reparent_objcgs --> lock lruvec lru_gen_reparent_memcg --> reparent child folios to parent unlock lruvec lock lruvec reset_batch_size --> child lrugen->nr_pages += delta This will trigger the following warning in lru_gen_exit_memcg(): VM_WARN_ON_ONCE(memchr_inv(lruvec->lrugen.nr_pages, 0, sizeof(lruvec->lrugen.nr_pages))); And the user-visible impact of underestimated nr_pages in MGLRU was premature OOMs because MGLRU does not try to reclaim memory when nr_pages reaches zero, but there are still more pages. To fix it, make reset_batch_size() check CSS_DYING under RCU before flushing the pending batch. A non-dying memcg keeps the original lruvec stable against RCU-delayed offlining; a dying memcg redirects the deltas to the first non-dying ancestor. Link: https://lore.kernel.org/20260710154318.75388-1-qi.zheng@linux.dev Fixes: f304652609ea ("mm: vmscan: prepare for reparenting MGLRU folios") Signed-off-by: Qi Zheng Reported-by: Peiyang He Closes: https://lore.kernel.org/all/5A9E929D82717101+12fcf643-efb8-4b9a-a53a-1e28cc894f0b@smail.nju.edu.cn Reviewed-by: Harry Yoo (Oracle) Acked-by: Johannes Weiner Acked-by: Shakeel Butt Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Wei Xu Cc: Yuanchu Xie Cc: Signed-off-by: Andrew Morton --- include/linux/memcontrol.h | 25 +++++++++++++++++++++++++ mm/vmscan.c | 11 ++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index e1f46a0016fc..957260677678 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -1472,6 +1472,31 @@ static inline void lruvec_lock_irq(struct lruvec *lruvec) spin_lock_irq(&lruvec->lru_lock); } +static inline struct lruvec *lruvec_live_lock_irq(struct lruvec *lruvec) +{ +#ifdef CONFIG_MEMCG + struct pglist_data *pgdat = lruvec_pgdat(lruvec); + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + + rcu_read_lock(); + + /* + * The memcg can be NULL when the memory controller is disabled. + * Otherwise, the caller keeps the memcg owning @lruvec alive. + */ + while (unlikely(memcg && css_is_dying(&memcg->css))) { + memcg = parent_mem_cgroup(memcg); + lruvec = mem_cgroup_lruvec(memcg, pgdat); + } + + spin_lock_irq(&lruvec->lru_lock); +#else + lruvec_lock_irq(lruvec); +#endif + + return lruvec; +} + static inline void lruvec_unlock(struct lruvec *lruvec) { spin_unlock(&lruvec->lru_lock); diff --git a/mm/vmscan.c b/mm/vmscan.c index 35c3bb15ae96..1a142c58700d 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -3265,7 +3265,7 @@ static void update_batch_size(struct lru_gen_mm_walk *walk, struct folio *folio, static void reset_batch_size(struct lru_gen_mm_walk *walk) { int gen, type, zone; - struct lruvec *lruvec = walk->lruvec; + struct lruvec *lruvec = lruvec_live_lock_irq(walk->lruvec); struct lru_gen_folio *lrugen = &lruvec->lrugen; walk->batched = 0; @@ -3285,6 +3285,8 @@ static void reset_batch_size(struct lru_gen_mm_walk *walk) lru += LRU_ACTIVE; __update_lru_size(lruvec, lru, zone, delta); } + + lruvec_unlock_irq(lruvec); } static int should_skip_vma(unsigned long start, unsigned long end, struct mm_walk *args) @@ -3779,11 +3781,8 @@ static void walk_mm(struct mm_struct *mm, struct lru_gen_mm_walk *walk) mmap_read_unlock(mm); } - if (walk->batched) { - lruvec_lock_irq(lruvec); + if (walk->batched) reset_batch_size(walk); - lruvec_unlock_irq(lruvec); - } cond_resched(); } while (err == -EAGAIN); @@ -4867,9 +4866,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, walk = current->reclaim_state->mm_walk; if (walk && walk->batched) { walk->lruvec = lruvec; - lruvec_lock_irq(lruvec); reset_batch_size(walk); - lruvec_unlock_irq(lruvec); } mod_lruvec_state(lruvec, PGDEMOTE_KSWAPD + reclaimer_offset(sc), From dd9623f58ec702a07b2d67179d6fcea79c52231a Mon Sep 17 00:00:00 2001 From: Xiangfeng Cai Date: Tue, 14 Jul 2026 01:14:55 +0800 Subject: [PATCH 106/456] mm/hugetlb: fix list corruption in allocate_file_region_entries() allocate_file_region_entries() tops up resv->region_cache with freshly allocated file_region descriptors. The allocation uses GFP_KERNEL, so resv->lock is dropped around it: the new entries are gathered on a stack-local list head, allocated_regions, and spliced into resv->region_cache once the lock is re-acquired. The splice used list_splice(), which moves the entries but does not re-initialize the source head, so allocated_regions is left pointing at an entry that now lives on resv->region_cache. The top-up runs in a while loop that re-checks the cache deficit after re-acquiring the lock. For a shared mapping the resv_map is shared by every mapper of the hugetlbfs inode, so a concurrent region_chg()/region_add()/region_del() on the same resv_map can consume cache entries during the unlocked window and force a second iteration. That iteration calls list_add() on the stale head and corrupts the list; with CONFIG_DEBUG_LIST the __list_add_valid() check trips: list_add corruption. next->prev should be prev (ffffc900011ff7f8), but was ffff88814c281460. (next=ffff88814c545640). kernel BUG at lib/list_debug.c:31! allocate_file_region_entries+0x191/0x420 region_chg+0x267/0x300 hugetlb_reserve_pages+0x387/0xc80 hugetlbfs_file_mmap+0x2ce/0x3f0 mmap_region+0x1348/0x1a80 do_mmap+0x85e/0xb90 vm_mmap_pgoff+0x18c/0x330 ksys_mmap_pgoff+0x2a1/0x3e0 do_syscall_64+0xd7/0x420 Without CONFIG_DEBUG_LIST the bad list_add() silently links a kernel-stack address into resv->region_cache, leading to later use-after-free. This was observed as a real host panic on a dense KVM host where a QEMU guest-RAM hugetlbfs file was mapped MAP_SHARED by both QEMU and a separate SPDK/DPDK vhost-user target, generating concurrent region_* traffic on one shared resv_map. Use list_splice_init() so the source head is re-initialized empty after each splice, making the retry loop safe. Link: https://lore.kernel.org/20260713171456.300518-2-caixiangfeng@bytedance.com Fixes: d3ec7b6e09e5 ("mm/hugetlb: use list_splice to merge two list at once") Signed-off-by: Xiangfeng Cai Reviewed-by: Muchun Song Cc: Baoquan He Cc: David Hildenbrand Cc: Oscar Salvador Cc: Shuah Khan Cc: Wei Yang Cc: Signed-off-by: Andrew Morton --- mm/hugetlb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index bca2707d02e3..e93c4d2456aa 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -693,7 +693,7 @@ static int allocate_file_region_entries(struct resv_map *resv, spin_lock(&resv->lock); - list_splice(&allocated_regions, &resv->region_cache); + list_splice_init(&allocated_regions, &resv->region_cache); resv->region_cache_count += to_allocate; } From 40de8160ca7f67d14619ee0351ce5d68fc4a237a Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Wed, 15 Jul 2026 15:42:33 +0100 Subject: [PATCH 107/456] fs/proc/task_mmu: fix PAGEMAP_SCAN written state for PMD holes PAGEMAP_SCAN reports an unpopulated PTE in a uffd-wp VMA as written, but a range with no page table at all -- a PMD hole -- is skipped: pagemap_scan_pte_hole() tests p->cur_vma_category, which never carries PAGE_IS_WRITTEN, so the hole is neither reported nor (under PM_SCAN_WP_MATCHING) armed. In a uffd-wp VMA, WP_UNPOPULATED installs uffd-wp markers when protecting a range, allocating page tables as needed, so an unpopulated slot is treated as written -- see the pte_none() handling in pagemap_page_category(). A missing marker therefore means the range was zapped, e.g. via MADV_DONTNEED. This applies to anon and shmem VMAs. An anonymous THP is write-protected in place as a huge PMD, so a full-PMD MADV_DONTNEED clears it to pmd_none -- a hole with no page table -- and pagemap_scan_pte_hole() misses it. For a MAP_PRIVATE|MAP_ANON mapping MADV_DONTNEED has fill-with-zeros semantics, so a write-tracking checkpoint/migration tool (e.g. CRIU) treats the range as unchanged and keeps its previous contents; after restore or live migration the process reads stale data instead of zeroes -- data corruption. Report a hole in a non-hugetlb uffd-wp VMA as written, matching the pte_none handling in pagemap_page_category(); the existing PM_SCAN_WP_MATCHING path then arms it via uffd_wp_range(). hugetlb is excluded: pagemap_hugetlb_category() reports an empty hugetlb entry (huge_pte_none) as not-written, unlike pagemap_page_category(), which reports pte_none as written. pagemap_scan_pte_hole() fires for a hugetlb slot only when it has no page table; keeping that not-written matches how an allocated-but-empty hugetlb entry reads, so the hole and the empty-entry cases agree within the VMA. Link: https://lore.kernel.org/20260715144234.442721-2-kirill@shutemov.name Fixes: 2bad466cc9d9 ("mm/uffd: UFFD_FEATURE_WP_UNPOPULATED") Signed-off-by: Kiryl Shutsemau Reported-by: Sashiko AI review Closes: https://sashiko.dev/#/patchset/20260707151349.92143-1-kirill@shutemov.name Tested-by: Muhammad Usama Anjum Acked-by: David Hildenbrand (Arm) Cc: Peter Xu Cc: Jann Horn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Michal Hocko Cc: Mike Rapoport Cc: Pedro Falcato Cc: Shuah Khan Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zenghui Yu Assisted-by: Claude:claude-fable-5 Cc: Signed-off-by: Andrew Morton --- fs/proc/task_mmu.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index d45c729ab6bb..229d1fc3d7f1 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -3049,12 +3049,28 @@ static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end, { struct pagemap_scan_private *p = walk->private; struct vm_area_struct *vma = walk->vma; + unsigned long categories; int ret, err; - if (!vma || !pagemap_scan_is_interesting_page(p->cur_vma_category, p)) + if (!vma) return 0; - ret = pagemap_scan_output(p->cur_vma_category, p, addr, &end); + /* + * In a uffd-wp VMA an unpopulated range is treated as written: + * uffd-wp registration populates page tables and installs markers + * with WP_UNPOPULATED, so a missing marker means the range was + * zapped. See the pte_none() handling in pagemap_page_category(). + * + * hugetlb differs, see pagemap_hugetlb_category(). + */ + categories = p->cur_vma_category; + if (userfaultfd_wp(vma) && !is_vm_hugetlb_page(vma)) + categories |= PAGE_IS_WRITTEN; + + if (!pagemap_scan_is_interesting_page(categories, p)) + return 0; + + ret = pagemap_scan_output(categories, p, addr, &end); if (addr == end) return ret; From b27e195d4db8dea263050bdbeb11881b2999c9c6 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 20 Jul 2026 20:44:21 +0100 Subject: [PATCH 108/456] cdrom: fix stack out-of-bounds read in CDROMVOLCTRL mmc_ioctl_cdrom_volume() first reads the audio control mode page into a 32-byte stack buffer with cgc->buflen set to 24. If the device reports a block descriptor, the function increases cgc->buflen to include that descriptor and reads the page again. For CDROMVOLCTRL, the function then builds a MODE SELECT parameter list by moving cgc->buffer forward by offset - 8 bytes. This drops the block descriptor from the outgoing payload and leaves a new 8-byte mode parameter header in front of the audio control page. However, cgc->buflen is left unchanged. With a standard 8-byte block descriptor, cgc->buffer points at buffer + 8 but cgc->buflen remains 32. cdrom_mode_select() therefore asks the low level packet path to write 32 bytes from that adjusted pointer, reading 8 bytes past the end of the 32-byte stack buffer. This is not hit by CDROMVOLREAD, and CDROMVOLCTRL only triggers it on drives that return a non-zero block descriptor length, which helps explain why it has gone unnoticed. The overread is also sent to the device as extra MODE SELECT payload, so it may not produce an obvious local failure. Reduce cgc->buflen by the same amount as the buffer pointer adjustment so the MODE SELECT transfer covers only the intended parameter list. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Signed-off-by: Phillip Potter Link: https://patch.msgid.link/20260720194421.1497-2-phil@philpotter.co.uk Signed-off-by: Jens Axboe --- drivers/cdrom/cdrom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c index 62934cf4b10d..4f1fd389260f 100644 --- a/drivers/cdrom/cdrom.c +++ b/drivers/cdrom/cdrom.c @@ -3187,6 +3187,7 @@ static noinline int mmc_ioctl_cdrom_volume(struct cdrom_device_info *cdi, /* set volume */ cgc->buffer = buffer + offset - 8; + cgc->buflen -= offset - 8; memset(cgc->buffer, 0, 8); return cdrom_mode_select(cdi, cgc); } From 6881f45d0eb541f2cee8c37c84b3860a23823bb3 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sat, 4 Jul 2026 17:58:56 +0930 Subject: [PATCH 109/456] btrfs: fix leaking BTRFS_FS_STATE_REMOUNTING flag [BUG] The following script can lead to unexpected qgroup rescan failure: # mkfs.btrfs -f -O quota $dev # mount $dev $mnt # mount -o remount,rescue=ibadroots $mnt ^^^^^ This above command is expected to fail # btrfs quota rescan -w $mnt ^^^^^ The above qgroup rescan is not expected to fail # btrfs qgroup show $mnt WARNING: qgroup data inconsistent, rescan recommended Qgroupid Referenced Exclusive Path -------- ---------- --------- ---- 0/5 16.00KiB 16.00KiB The above short script will be converted to a proper fstests case. [CAUSE] Inside btrfs_reconfigure(), if either btrfs_check_options() or btrfs_check_features() failed, we will always have BTRFS_FS_STATE_REMOUNTING set for the fs until the next successful remount. That BTRFS_FS_STATE_REMOUNTING flag will interrupt several operations, including: - Qgroup rescan - Auto defrag - Space reclaim [FIX] Change the error handling of btrfs_check_options() and btrfs_check_features() to goto restore label. Fixes: eddb1a433f26 ("btrfs: add reconfigure callback for fs_context") Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/super.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index c946bccf0748..63bf1f1e16d4 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -1519,12 +1519,14 @@ static int btrfs_reconfigure(struct fs_context *fc) sync_filesystem(sb); set_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state); - if (!btrfs_check_options(fs_info, &ctx->mount_opt, fc->sb_flags)) - return -EINVAL; + if (!btrfs_check_options(fs_info, &ctx->mount_opt, fc->sb_flags)) { + ret = -EINVAL; + goto restore; + } ret = btrfs_check_features(fs_info, !(fc->sb_flags & SB_RDONLY)); if (ret < 0) - return ret; + goto restore; btrfs_ctx_to_info(fs_info, ctx); btrfs_remount_begin(fs_info, old_ctx.mount_opt, fc->sb_flags); From 1ebe51c29fa9755d5b2fea28727c051117907cf8 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Fri, 3 Jul 2026 07:54:40 +0200 Subject: [PATCH 110/456] btrfs: zoned: fix deadlock between metadata writeback and transaction commit When writing out metadata extent buffers in a zoned filesystem, btree_writepages() holds fs_info->zoned_meta_io_lock across the whole writeback loop, including the call to btrfs_check_meta_write_pointer() -> check_bg_is_active(). For the tree-log block group, check_bg_is_active() may fail to activate the zone and fall back to btrfs_zone_finish_one_bg() to free an active zone. That path waits for the running transaction to commit while still holding zoned_meta_io_lock, but the committer needs that same lock to write out the tree extents, so the two tasks deadlock: Task A (kworker, metadata writeback) Task B (fsstress, transaction commit) ------------------------------------ ------------------------------------- wb_workfn() btrfs_commit_transaction(T) btree_writepages() btrfs_write_and_wait_transaction() btrfs_zoned_meta_io_lock() btrfs_write_marked_extents() btrfs_check_meta_write_pointer() btree_writepages() check_bg_is_active() [treelog_bg] btrfs_zoned_meta_io_lock() btrfs_zone_finish_one_bg() do_zone_finish() btrfs_inc_block_group_ro() btrfs_wait_for_commit() The sibling branch in check_bg_is_active() already drops zoned_meta_io_lock around do_zone_finish() for this exact reason. Do the same in the tree-log branch: release the lock around btrfs_zone_finish_one_bg() and re-acquire it afterwards. The lock only protects fs_info->active_{meta,system}_bg, which this branch does not touch, and ctx->zoned_bg keeps a reference to the block group across the unlock, so nothing is lost while the lock is dropped. This hang occasionally reproduces with fstests generic/475 on a zoned btrfs filesystem. Fixes: 13bb483d32ab ("btrfs: zoned: activate metadata block group on write time") Reviewed-by: Naohiro Aota Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/zoned.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index 97f06dd01693..44a13ed6b8b2 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -2190,7 +2190,11 @@ static bool check_bg_is_active(struct btrfs_eb_write_context *ctx, if (fs_info->treelog_bg == block_group->start) { if (!btrfs_zone_activate(block_group)) { - int ret_fin = btrfs_zone_finish_one_bg(fs_info); + int ret_fin; + + btrfs_zoned_meta_io_unlock(fs_info); + ret_fin = btrfs_zone_finish_one_bg(fs_info); + btrfs_zoned_meta_io_lock(fs_info); if (ret_fin != 1 || !btrfs_zone_activate(block_group)) return false; From 5fabb1cf25d723274009d7b759545fd59f230c9d Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Fri, 3 Jul 2026 07:54:45 +0200 Subject: [PATCH 111/456] btrfs: zoned: reset meta_write_pointer on zone reset btrfs_reset_unused_block_groups() resets a block group's zone and sets alloc_offset back to 0 so the space can be reused, but it leaves meta_write_pointer pointing at the previous end of the zone. Once the block group is reactivated and reused for metadata, newly allocated tree blocks live before that stale write pointer. btrfs_check_meta_write_pointer() then sees them behind the write pointer, so they can never be written out in sequential order: the dirty extent buffers are stranded and pin their btree_inode folios until unmount. Reset meta_write_pointer back to the start of the block group for metadata and system block groups. Fixes: 453a73c3069a ("btrfs: zoned: reclaim unused zone by zone resetting") Reviewed-by: Naohiro Aota Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/zoned.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index 44a13ed6b8b2..0706c0788cb2 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -3189,6 +3189,17 @@ int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num reclaimed = bg->alloc_offset; bg->zone_unusable = bg->length - bg->zone_capacity; bg->alloc_offset = 0; + /* + * The zone was just reset to empty, so alloc_offset went back to + * the start of the zone. For metadata/system block groups the + * write pointer must follow it back to the start of the zone; + * otherwise it stays stale at the previous (finished) zone end, + * and metadata written into the reused zone would sit behind the + * write pointer, could never be written out in sequential order, + * and would be stranded (pinning its folio) until unmount. + */ + if (bg->flags & (BTRFS_BLOCK_GROUP_METADATA | BTRFS_BLOCK_GROUP_SYSTEM)) + bg->meta_write_pointer = bg->start; /* * This holds because we currently reset fully used then freed * block group. From 51a0e8399858621442807a26057bcd1cd3ced046 Mon Sep 17 00:00:00 2001 From: Dongjiang Zhu Date: Mon, 13 Jul 2026 16:50:08 +0800 Subject: [PATCH 112/456] btrfs: skip global block reserve accounting for rescue mounts [BUG] Mounting with rescue=ibadroots after corrupting the block group tree root triggers a NULL pointer dereference: BUG: kernel NULL pointer dereference, address: 0000000000000100 RIP: 0010:btrfs_update_global_block_rsv+0x9d/0x1c0 [btrfs] Call Trace: fill_dummy_bgs+0xd4/0x120 [btrfs] open_ctree+0xc6e/0x1ca0 [btrfs] btrfs_get_tree+0x50d/0xa40 [btrfs] The same crash occurs with a corrupted raid stripe tree root, via btrfs_read_block_groups() instead of fill_dummy_bgs(). [CAUSE] With rescue=ibadroots, btrfs_read_roots() allows the mount to continue when either root cannot be read, leaving the corresponding root pointer NULL while its on-disk feature bit remains set. btrfs_update_global_block_rsv() then dereferences the missing root based on the feature bit alone. [FIX] Rescue mounts are fully read-only and cannot start transactions, so the global reserve is never consumed. Under btrfs_is_full_ro(), mark the reserve as full and return before performing the accounting. And since we need to check if the fs is mount fully RO, export fs_is_full_ro() as btrfs_is_full_ro(), and move it to fs.h. Fixes: 8dbfc14fc736 ("btrfs: account block group tree when calculating global reserve size") Fixes: 515020900d44 ("btrfs: read raid stripe tree from disk") Suggested-by: Qu Wenruo Signed-off-by: Dongjiang Zhu [ Squash the fs_is_full_ro() export commit into this one. ] Reviewed-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/block-rsv.c | 19 +++++++++++++++++-- fs/btrfs/disk-io.c | 11 +---------- fs/btrfs/fs.h | 9 +++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/fs/btrfs/block-rsv.c b/fs/btrfs/block-rsv.c index 9efb3016ef11..c68a8f4b7d19 100644 --- a/fs/btrfs/block-rsv.c +++ b/fs/btrfs/block-rsv.c @@ -322,10 +322,25 @@ void btrfs_block_rsv_add_bytes(struct btrfs_block_rsv *block_rsv, void btrfs_update_global_block_rsv(struct btrfs_fs_info *fs_info) { struct btrfs_block_rsv *block_rsv = &fs_info->global_block_rsv; - struct btrfs_space_info *sinfo = block_rsv->space_info; + struct btrfs_space_info *sinfo; struct btrfs_root *root, *tmp; - u64 num_bytes = btrfs_root_used(&fs_info->tree_root->root_item); unsigned int min_items = 1; + u64 num_bytes; + + /* + * A full read-only mount (rescue options) cannot start transactions, + * so the global reserve is never consumed. Mark it as full and skip + * the accounting. + */ + if (btrfs_is_full_ro(fs_info)) { + spin_lock(&block_rsv->lock); + block_rsv->full = true; + spin_unlock(&block_rsv->lock); + return; + } + + sinfo = block_rsv->space_info; + num_bytes = btrfs_root_used(&fs_info->tree_root->root_item); /* * The global block rsv is based on the size of the extent tree, the diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 0a7d80da9c94..36332df9a0f1 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -3288,15 +3288,6 @@ int btrfs_check_features(struct btrfs_fs_info *fs_info, bool is_rw_mount) return 0; } -static bool fs_is_full_ro(const struct btrfs_fs_info *fs_info) -{ - if (!sb_rdonly(fs_info->sb)) - return false; - if (unlikely(fs_info->mount_opt & BTRFS_MOUNT_FULL_RO_MASK)) - return true; - return false; -} - /* * Try to wait for any metadata readahead, and invalidate all btree folios. * @@ -3462,7 +3453,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device WRITE_ONCE(fs_info->fs_error, -EUCLEAN); /* If the fs has any rescue options, no transaction is allowed. */ - if (fs_is_full_ro(fs_info)) + if (btrfs_is_full_ro(fs_info)) WRITE_ONCE(fs_info->fs_error, -EROFS); /* Set up fs_info before parsing mount options */ diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 5f0cfb0b5466..7ee9ec2b0efb 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -1159,6 +1159,15 @@ void __btrfs_clear_fs_compat_ro(struct btrfs_fs_info *fs_info, u64 flag, #define btrfs_test_opt(fs_info, opt) ((fs_info)->mount_opt & \ BTRFS_MOUNT_##opt) +static inline bool btrfs_is_full_ro(const struct btrfs_fs_info *fs_info) +{ + if (!sb_rdonly(fs_info->sb)) + return false; + if (unlikely(fs_info->mount_opt & BTRFS_MOUNT_FULL_RO_MASK)) + return true; + return false; +} + static inline bool btrfs_fs_closing(const struct btrfs_fs_info *fs_info) { return unlikely(test_bit(BTRFS_FS_CLOSING_START, &fs_info->flags)); From c438d34ec1eed4d23e2081d61c5e96f5176898a9 Mon Sep 17 00:00:00 2001 From: Dongjiang Zhu Date: Mon, 13 Jul 2026 16:50:10 +0800 Subject: [PATCH 113/456] btrfs: report missing raid stripe tree root during lookup When rescue=ibadroots ignores a failure to load the raid stripe tree root, fs_info->stripe_root remains NULL. After the rescue mount proceeds, reading file data that requires the raid stripe tree reaches btrfs_get_raid_extent_offset(). Currently btrfs_search_slot() handles the NULL root and returns -EINVAL. This avoids a NULL pointer dereference, but provides no diagnostic and incorrectly describes missing filesystem metadata as an invalid argument. Check stripe_root before allocating a path, emit a rate-limited error with the logical address, and return -EUCLEAN. Lookups with a valid stripe root are unchanged. Reviewed-by: Qu Wenruo Signed-off-by: Dongjiang Zhu Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/raid-stripe-tree.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/btrfs/raid-stripe-tree.c b/fs/btrfs/raid-stripe-tree.c index 454a95bf542a..b210371ce91e 100644 --- a/fs/btrfs/raid-stripe-tree.c +++ b/fs/btrfs/raid-stripe-tree.c @@ -414,6 +414,12 @@ int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, int slot; int ret; + if (unlikely(!stripe_root)) { + btrfs_err_rl(fs_info, "missing raid stripe tree root for logical %llu", + logical); + return -EUCLEAN; + } + stripe_key.objectid = logical; stripe_key.type = BTRFS_RAID_STRIPE_KEY; stripe_key.offset = 0; From 330dcc553f282e8dc0b88c9495b4c296465364e1 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sun, 12 Jul 2026 13:12:51 +0930 Subject: [PATCH 114/456] btrfs: raid56: fix an incorrect csum skip during scrub Commit 7425a2894019 ("btrfs: introduce btrfs_bio_for_each_block_all() helper") uses the new helper to replace the nested loop inside verify_bio_data_sectors(), which simplifies the code. However that also changed the behavior of "continue" when a block has no data checksum. Previously the "continue" would skip the old for() loop, which would also increase @total_sector_nr. Now the "continue" will skip the new btrfs_bio_for_each_block_all() loop, which doesn't update @total_sector_nr. This means if we hit a block that has no data checksum, we will skip all the remaining blocks no matter if they have data checksum. As @total_sector_nr will never be updated, and that test_bit() will always return false. Fix it by increasing @total_sector_nr before calling "continue". Fixes: 7425a2894019 ("btrfs: introduce btrfs_bio_for_each_block_all() helper") Reviewed-by: Daniel Vacek Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/raid56.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c index f7f7db40994c..3f2896e793e3 100644 --- a/fs/btrfs/raid56.c +++ b/fs/btrfs/raid56.c @@ -1679,8 +1679,10 @@ static void verify_bio_data_sectors(struct btrfs_raid_bio *rbio, continue; /* No csum for this sector, skip to the next sector. */ - if (!test_bit(total_sector_nr, rbio->csum_bitmap)) + if (!test_bit(total_sector_nr, rbio->csum_bitmap)) { + total_sector_nr++; continue; + } expected_csum = rbio->csum_buf + total_sector_nr * fs_info->csum_size; btrfs_calculate_block_csum_pages(fs_info, paddrs, csum_buf); From 8bc4d7209611e8aa9d5409b6a4a86a9eb91b69a3 Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Tue, 14 Jul 2026 17:55:43 +0800 Subject: [PATCH 115/456] btrfs: zoned: fix missing chunk metadata reservation reserve_chunk_space() stores the return value of btrfs_zoned_activate_one_bg() in ret. The helper can return 1 after successfully activating a block group, but ret is later used to decide whether to reserve metadata for chunk tree updates. As a result, successful activation skips btrfs_block_rsv_add() and leaves trans->chunk_bytes_reserved unchanged. Use a separate variable for the activation result so positive success does not affect the later reservation. Keep activation failures in ret instead of returning early so the function uses the common tail path. Fixes: b6a98021e401 ("btrfs: zoned: activate necessary block group") CC: stable@vger.kernel.org Reviewed-by: Johannes Thumshirn Signed-off-by: Guanghui Yang <3497809730@qq.com> Signed-off-by: David Sterba --- fs/btrfs/block-group.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index ab76a5173272..8def7abb728f 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -4532,25 +4532,29 @@ static void reserve_chunk_space(struct btrfs_trans_handle *trans, if (IS_ERR(bg)) { ret = PTR_ERR(bg); } else { + int activate_ret; + /* * We have a new chunk. We also need to activate it for * zoned filesystem. */ - ret = btrfs_zoned_activate_one_bg(info, true); - if (ret < 0) - return; - - /* - * If we fail to add the chunk item here, we end up - * trying again at phase 2 of chunk allocation, at - * btrfs_create_pending_block_groups(). So ignore - * any error here. An ENOSPC here could happen, due to - * the cases described at do_chunk_alloc() - the system - * block group we just created was just turned into RO - * mode by a scrub for example, or a running discard - * temporarily removed its free space entries, etc. - */ - btrfs_chunk_alloc_add_chunk_item(trans, bg); + activate_ret = btrfs_zoned_activate_one_bg(info, true); + if (activate_ret < 0) { + ret = activate_ret; + } else { + /* + * If we fail to add the chunk item here, we end + * up trying again at phase 2 of chunk allocation, + * at btrfs_create_pending_block_groups(). So + * ignore any error here. An ENOSPC here could + * happen, due to the cases described at + * do_chunk_alloc() - the system block group we + * just created was just turned into RO mode by a + * scrub for example, or a running discard + * temporarily removed its free space entries, etc. + */ + btrfs_chunk_alloc_add_chunk_item(trans, bg); + } } } From 0d214d14be503f16238999723acfcbf63f04cc8b Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Tue, 14 Jul 2026 13:32:24 -0700 Subject: [PATCH 116/456] btrfs: initialize 'args' to avoid compiler warning in btrfs_ioctl_get_csums() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [COMPILER WARNING] With GCC 11.5.0 and KASAN enabled on ARM, the following warning is triggered during compiling: In file included from ./include/asm-generic/rwonce.h:26, from ./arch/arm64/include/asm/rwonce.h:81, from ./include/linux/compiler.h:369, from ./include/linux/array_size.h:5, from ./include/linux/kernel.h:16, from fs/btrfs/ioctl.c:6: In function ‘instrument_copy_from_user_before’, inlined from ‘_inline_copy_from_user’ at ./include/linux/uaccess.h:184:2, inlined from ‘copy_from_user’ at ./include/linux/uaccess.h:222:9, inlined from ‘btrfs_ioctl_get_csums.isra’ at fs/btrfs/ioctl.c:5220:6: ./include/linux/kasan-checks.h:38:27: warning: ‘args’ may be used uninitialized [-Wmaybe-uninitialized] 38 | #define kasan_check_write __kasan_check_write ./include/linux/instrumented.h:146:9: note: in expansion of macro ‘kasan_check_write’ 146 | kasan_check_write(to, n); | ^~~~~~~~~~~~~~~~~ fs/btrfs/ioctl.c: In function ‘btrfs_ioctl_get_csums.isra’: ./include/linux/kasan-checks.h:20:6: note: by argument 1 of type ‘const volatile void *’ to ‘__kasan_check_write’ declared here 20 | bool __kasan_check_write(const volatile void *p, unsigned int size); | ^~~~~~~~~~~~~~~~~~~ fs/btrfs/ioctl.c:5201:43: note: ‘args’ declared here 5201 | struct btrfs_ioctl_get_csums_args args; | ^~~~ [POSSIBLE FALSE ALERTS] This seems to be a false alert from certain GCC versions. The @args is immediately over-written by copy_from_user(), and there is no code touching that @args until copy_from_user() finished correctly. [WORKAROUND] Initialize 'args' to zero, which suppresses the warning. Reviewed-by: Qu Wenruo Signed-off-by: Paul E. McKenney Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 68b33f365fda..baa645e98812 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -5205,7 +5205,7 @@ static int btrfs_ioctl_get_csums(struct file *file, void __user *argp) struct btrfs_inode *inode = BTRFS_I(vfs_inode); struct btrfs_fs_info *fs_info = inode->root->fs_info; struct btrfs_root *root = inode->root; - struct btrfs_ioctl_get_csums_args args; + struct btrfs_ioctl_get_csums_args args = { 0 }; BTRFS_PATH_AUTO_FREE(path); const u64 ino = btrfs_ino(inode); const u32 csum_size = fs_info->csum_size; From ab602da96a915d42dcb1b0b322e8daea0f71b51f Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Thu, 16 Jul 2026 10:23:12 +0200 Subject: [PATCH 117/456] btrfs: zoned: skip fully truncated ordered extents at zone finish A fully truncated ordered extent (truncated_len == 0) wrote no data, so its ->csum_list is empty and btrfs_finish_ordered_zoned() trips: assertion failed: !list_empty(&ordered->csum_list), in fs/btrfs/zoned.c:2141 Since commit 66ff4d366e7e a short or cancelled direct IO write finishes the unsubmitted ordered extent as truncated with uptodate = true instead of setting BTRFS_ORDERED_IOERR, so it now reaches btrfs_finish_ordered_zoned() rather than being skipped by the IOERR check in btrfs_finish_ordered_io(). generic/208 hits this on a zoned filesystem. Return early for these, like the BTRFS_ORDERED_PREALLOC case; there is no zone append result to record and btrfs_finish_one_ordered() skips them too. Fixes: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write") Reviewed-by: Qu Wenruo Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/zoned.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index 0706c0788cb2..a016cb471beb 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -2138,6 +2138,16 @@ void btrfs_finish_ordered_zoned(struct btrfs_ordered_extent *ordered) if (test_bit(BTRFS_ORDERED_PREALLOC, &ordered->flags)) return; + /* + * A fully truncated ordered extent wrote no data and so has + * no zone append result to record. + */ + if (test_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags) && + ordered->truncated_len == 0) { + ASSERT(list_empty(&ordered->csum_list)); + return; + } + ASSERT(!list_empty(&ordered->csum_list)); sum = list_first_entry(&ordered->csum_list, struct btrfs_ordered_sum, list); logical = sum->logical; From c4c0673e4cb15b0c127e6d00732a2427bdd12c11 Mon Sep 17 00:00:00 2001 From: Mykola Lysenko Date: Sat, 18 Jul 2026 16:37:10 -0700 Subject: [PATCH 118/456] btrfs: raid56: fix scrub read assembly submitting no reads Commit 5387bd958180 ("btrfs: raid56: remove sector_ptr structure") converted the bio-list membership checks from sector pointers to physical addresses. The two conversions in rmw_assemble_write_bios() kept their polarity (skip the sector when it is NOT in the bio list, i.e. when there is nothing to write), but scrub_assemble_read_bios() has the opposite polarity -- skip the sector when it IS in the bio list, because then there is nothing to read -- and the conversion flipped it: - sector = sector_in_rbio(rbio, stripe, sectornr, 1); - if (sector) + paddr = sector_paddr_in_rbio(rbio, stripe, sectornr, 1); + if (paddr == INVALID_PADDR) continue; Since a parity-scrub rbio's bio list only holds the empty completion bio, the result is that scrub_assemble_read_bios() submits no reads at all. finish_parity_scrub() then compares the parity it computes from the (cached, correct) data stripes against whatever happens to be in the freshly allocated, uninitialized stripe pages: - if the garbage differs from the computed parity, the sector is "repaired" and written back -- accidentally producing the correct on-disk result; - if a recycled page happens to still hold the old (correct) parity content, the sector is deemed clean, dropped from dbitmap, and the actually-corrupt on-disk parity is left in place. (Scrub reports no errors either way: there is no counter for P/Q corruption by design, so the bug here is purely the failure to read and repair.) The second case is intermittent because it depends on page-allocator recycling. Observed with fstests btrfs/297 (raid5, 2 devices): the corrupted P stripe intermittently stays corrupt after a scrub -- roughly 1/10 runs on x86-64 KVM and up to 7/8 on a UML build whose timing favors page reuse. Since the bio-list check can never be true for a parity-scrub rbio -- raid56_parity_alloc_scrub_rbio() adds a single empty completion bio (asserting bi_size == 0), bio_paddrs[] is only populated by index_rbio_pages() which is never called for BTRFS_RBIO_PARITY_SCRUB, and rbio_can_merge() refuses to merge rbios of different operations -- remove the dead check entirely and assert the invariant instead, as suggested by Qu Wenruo. After this fix the injected corruption is read, detected and repaired in every run (8/8 UML, 10/10 KVM), and the new assertion never fires across the full fstests raid group. Fixes: 5387bd958180 ("btrfs: raid56: remove sector_ptr structure") CC: stable@vger.kernel.org # 7.1+ Suggested-by: Qu Wenruo Assisted-by: Claude:claude-fable-5 Reviewed-by: Qu Wenruo Signed-off-by: Mykola Lysenko Signed-off-by: David Sterba --- fs/btrfs/raid56.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c index 3f2896e793e3..ca94c9e3d563 100644 --- a/fs/btrfs/raid56.c +++ b/fs/btrfs/raid56.c @@ -2911,13 +2911,12 @@ static int scrub_assemble_read_bios(struct btrfs_raid_bio *rbio) continue; /* - * We want to find all the sectors missing from the rbio and - * read them from the disk. If sector_paddr_in_rbio() finds a sector - * in the bio list we don't need to read it off the stripe. + * A parity-scrub rbio carries no data in its bio list: the + * only bio there is the empty completion bio added by + * raid56_parity_alloc_scrub_rbio(). Every sector is read + * from the stripe, so only assert that invariant here. */ - paddrs = sector_paddrs_in_rbio(rbio, stripe, sectornr, 1); - if (paddrs == NULL) - continue; + ASSERT(!sector_paddrs_in_rbio(rbio, stripe, sectornr, 1)); paddrs = rbio_stripe_paddrs(rbio, stripe, sectornr); /* From 847b371debf3c8c72384ab7b9a0c4123a74cc925 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 14 Jul 2026 10:00:25 -0700 Subject: [PATCH 119/456] drm/gpusvm: Fix MM reference leak in drm_gpusvm_range_evict If kvmalloc_array() fails in drm_gpusvm_range_evict(), the MM reference acquired earlier is not released, resulting in a reference leak. Fix this by dropping the MM reference on the kvmalloc_array() failure path. Fixes: 99624bdff867 ("drm/gpusvm: Add support for GPU Shared Virtual Memory") Cc: stable@vger.kernel.org Signed-off-by: Matthew Brost Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260714170025.3487974-1-matthew.brost@intel.com --- drivers/gpu/drm/drm_gpusvm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 9a06ff7d2608..9aeaca02e928 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -1753,8 +1753,10 @@ int drm_gpusvm_range_evict(struct drm_gpusvm *gpusvm, return -EFAULT; pfns = kvmalloc_array(npages, sizeof(*pfns), GFP_KERNEL); - if (!pfns) + if (!pfns) { + mmput(mm); return -ENOMEM; + } hmm_range.hmm_pfns = pfns; while (!time_after(jiffies, timeout)) { From 67b8bfd4ec7dac6e79a7ad9ad19a7a9d6fc35a26 Mon Sep 17 00:00:00 2001 From: Stanislav Kinsburskii Date: Tue, 14 Jul 2026 15:54:32 -0700 Subject: [PATCH 120/456] drm/gpusvm: Zero HMM PFNs before scanning ranges drm_gpusvm_scan_mm() asks HMM to report the current CPU page-table state without faulting missing entries by leaving default_flags set to zero. The HMM PFN array is still caller-owned input/output state, and the framework may preserve input bits while filling entries. It is not safe for the caller to hand HMM an uninitialized array and then treat entries without HMM_PFN_VALID as an authoritative unpopulated result. Use kvcalloc() for the temporary PFN array so entries that are not reported as valid start from the documented zero state. This prevents random stack or heap contents from being interpreted as HMM PFN flags or PFN values during the scan. Fixes: f1d08a586482 ("drm/gpusvm: Introduce a function to scan the current migration state") Cc: stable@vger.kernel.org Signed-off-by: Stanislav Kinsburskii Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/178406967042.1113483.2116704310277917086.stgit@skinsburskii --- drivers/gpu/drm/drm_gpusvm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_gpusvm.c b/drivers/gpu/drm/drm_gpusvm.c index 9aeaca02e928..18ddef94da47 100644 --- a/drivers/gpu/drm/drm_gpusvm.c +++ b/drivers/gpu/drm/drm_gpusvm.c @@ -781,7 +781,7 @@ enum drm_gpusvm_scan_result drm_gpusvm_scan_mm(struct drm_gpusvm_range *range, const struct dev_pagemap *other = NULL; int err, i; - pfns = kvmalloc_array(npages, sizeof(*pfns), GFP_KERNEL); + pfns = kvcalloc(npages, sizeof(*pfns), GFP_KERNEL); if (!pfns) return DRM_GPUSVM_SCAN_UNPOPULATED; From 5ff172f6c94d282d83cb88bdfec5f647ad9c6105 Mon Sep 17 00:00:00 2001 From: Marco Elver Date: Tue, 21 Jul 2026 11:19:21 +0200 Subject: [PATCH 121/456] slab: silence sparse warning with type-based partitioning Sparse does not know __builtin_infer_alloc_token() and complains: sparse: sparse: undefined identifier '__builtin_infer_alloc_token' Fix it by using a dummy variant of __kmalloc_token() if __CHECKER__ is defined. Fixes: feb662d9168b ("slab: support for compiler-assisted type-based slab cache partitioning") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607110912.nZTqfCrH-lkp@intel.com/ Signed-off-by: Marco Elver Link: https://patch.msgid.link/20260721092005.1986693-1-elver@google.com Acked-by: Harry Yoo (Oracle) Signed-off-by: Vlastimil Babka (SUSE) --- include/linux/slab.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 3a14df50766a..32c9f8ed7ae2 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -514,8 +514,12 @@ typedef struct { unsigned long v; } kmalloc_token_t; extern unsigned long random_kmalloc_seed; #define __kmalloc_token(...) ((kmalloc_token_t){ .v = _CODE_LOCATION_ }) #elif defined(CONFIG_KMALLOC_PARTITION_TYPED) +#ifdef __CHECKER__ +#define __kmalloc_token(...) ((kmalloc_token_t){ .v = 0 }) +#else /* !__CHECKER__ */ #define __kmalloc_token(...) ((kmalloc_token_t){ .v = __builtin_infer_alloc_token(__VA_ARGS__) }) -#endif +#endif /* __CHECKER__ */ +#endif /* CONFIG_KMALLOC_PARTITION_TYPED */ #define DECL_TOKEN_PARAM(_token) , kmalloc_token_t (_token) #define _PASS_TOKEN_PARAM(_token) , (_token) #define PASS_TOKEN_PARAM(_token) (_token) From 8a570b19b4b16a8a3b5ffa2b332bd5613110b2d8 Mon Sep 17 00:00:00 2001 From: D Scott Phillips Date: Tue, 14 Jul 2026 16:11:58 -0700 Subject: [PATCH 122/456] KVM: arm64: vgic: Avoid double-deactivate of IRQs in the nested context In the nested state, the physical interrupt has already been deactivated through the HW bit in the LR. The extra deactivation would be harmless but can hit an errata case on AmpereOne, so avoid it here. On AmpereOne, deactivating a physical interrupt through ICC_DIR_EL1 or ICC_EOIR1_EL1 (depending on EOImode) which is not active, but is the highest priority pending interrupt causes the cpu to lose the interrupt pending state and also prevents the delivery of future interrupts. Fixes: 6dd333c8942b2 ("KVM: arm64: GICv3: nv: Plug L1 LR sync into deactivation primitive") Signed-off-by: D Scott Phillips Cc: stable@vger.kernel.org Link: https://lore.kernel.org/linux-arm-kernel/20260710222128.416581-1-scott@os.amperecomputing.com/ Link: https://patch.msgid.link/20260714231158.496808-1-scott@os.amperecomputing.com Signed-off-by: Marc Zyngier --- Documentation/arch/arm64/silicon-errata.rst | 4 ++++ arch/arm64/kvm/vgic/vgic-v3.c | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst index 014aa1c215a1..88b4aa45a206 100644 --- a/Documentation/arch/arm64/silicon-errata.rst +++ b/Documentation/arch/arm64/silicon-errata.rst @@ -55,10 +55,14 @@ stable kernels. +----------------+-----------------+-----------------+-----------------------------+ | Ampere | AmpereOne | AC03_CPU_38 | AMPERE_ERRATUM_AC03_CPU_38 | +----------------+-----------------+-----------------+-----------------------------+ +| Ampere | AmpereOne | AC03_CPU_57 | N/A | ++----------------+-----------------+-----------------+-----------------------------+ | Ampere | AmpereOne AC04 | AC04_CPU_10 | AMPERE_ERRATUM_AC03_CPU_38 | +----------------+-----------------+-----------------+-----------------------------+ | Ampere | AmpereOne AC04 | AC04_CPU_23 | AMPERE_ERRATUM_AC04_CPU_23 | +----------------+-----------------+-----------------+-----------------------------+ +| Ampere | AmpereOne AC04 | AC04_CPU_29 | N/A | ++----------------+-----------------+-----------------+-----------------------------+ +----------------+-----------------+-----------------+-----------------------------+ | ARM | Cortex-A510 | #2457168 | ARM64_ERRATUM_2457168 | +----------------+-----------------+-----------------+-----------------------------+ diff --git a/arch/arm64/kvm/vgic/vgic-v3.c b/arch/arm64/kvm/vgic/vgic-v3.c index 9e841e7afd4a..7aa417440f6a 100644 --- a/arch/arm64/kvm/vgic/vgic-v3.c +++ b/arch/arm64/kvm/vgic/vgic-v3.c @@ -275,7 +275,13 @@ void vgic_v3_deactivate(struct kvm_vcpu *vcpu, u64 val) lr = vgic_v3_compute_lr(vcpu, irq) & ~ICH_LR_ACTIVE_BIT; } - if (lr & ICH_LR_HW) + /* + * In the nested state, the irq has already been deactivated via the HW + * bit in the LR. Deactivating again would be harmless except AmpereOne + * errata AC03_CPU_57, AC04_CPU_29 could cause irq delivery to break if + * the deactivation hits the highest priority pending irq. + */ + if ((lr & ICH_LR_HW) && !vgic_state_is_nested(vcpu)) vgic_v3_deactivate_phys(FIELD_GET(ICH_LR_PHYS_ID_MASK, lr)); vgic_v3_fold_lr(vcpu, lr); From 5aab4d46278df6e9e94bd0a92df9a135a5fe16ad Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Sun, 19 Jul 2026 17:32:57 +0100 Subject: [PATCH 123/456] KVM: arm64: Update Fuad Tabba's email address Switch my KVM/arm64 reviewer entry to fuad.tabba@linux.dev, my canonical address for kernel work, and add a .mailmap entry so the existing tabba@google.com commits map onto it. Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260719163257.3156529-1-fuad.tabba@linux.dev Signed-off-by: Marc Zyngier --- .mailmap | 1 + MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 735470e21075..91003fb86136 100644 --- a/.mailmap +++ b/.mailmap @@ -291,6 +291,7 @@ Frank Rowand Frank Rowand Frank Rowand Frank Zago +Fuad Tabba Gao Xiang Gao Xiang Gao Xiang diff --git a/MAINTAINERS b/MAINTAINERS index a674e36529f7..5ab6fc150426 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14192,7 +14192,7 @@ F: virt/kvm/* KERNEL VIRTUAL MACHINE FOR ARM64 (KVM/arm64) M: Marc Zyngier M: Oliver Upton -R: Fuad Tabba +R: Fuad Tabba R: Joey Gouly R: Steffen Eiden R: Suzuki K Poulose From 1e75a8255f11c81fb07e81e5029cfd75804350a0 Mon Sep 17 00:00:00 2001 From: Guanghui Feng Date: Thu, 16 Jul 2026 22:16:22 +0800 Subject: [PATCH 124/456] iommu/amd: Wait for completion instead of returning early in iommu_completion_wait() need_sync is a per-IOMMU flag shared by all domains and devices behind that IOMMU. It is set whenever a command is queued with sync == true and cleared when a completion-wait (CWAIT) command is queued. However, a cleared need_sync only means that a covering CWAIT has been queued, not that all previously queued commands have actually completed in hardware. iommu_completion_wait() read need_sync locklessly and returned early when it was false. This breaks the "block until all previously queued commands have completed" contract in a multi-CPU scenario: CPU2: queue inv-B => need_sync = true CPU1: queue CWAIT(N); need_sync = false; then wait_on_sem(N) CPU2: read need_sync == false => return 0 (no wait!) CPU2 returns without waiting for any sequence number even though its inv-B may not have completed yet (CWAIT(N), queued after inv-B, has not been signaled). CPU2 then proceeds to, for example, free page-table pages while the IOMMU can still walk stale translations, opening a use-after-free window. This is a logical race in the meaning of the flag, not a memory-visibility issue, so barriers alone do not help. Fix it without losing the optimization of avoiding redundant CWAIT commands: take iommu->lock before testing need_sync, and when it is false do not return early but wait for the last allocated sequence number (cmd_sem_val). Since need_sync == false implies no sync command was queued after the last CWAIT, that CWAIT is FIFO-ordered after every not-yet-completed command, so waiting for its sequence number guarantees all prior commands (possibly queued by another CPU) have completed. The common path with pending work is unchanged and no extra hardware command is issued. Signed-off-by: Guanghui Feng Fixes: 815b33fdc279 ("x86/amd-iommu: Cleanup completion-wait handling") Reviewed-by: Vasant Hegde Signed-off-by: Will Deacon --- drivers/iommu/amd/iommu.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 563f9c2672d5..29dc18d3d22e 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -1450,11 +1450,23 @@ static int iommu_completion_wait(struct amd_iommu *iommu) int ret; u64 data; - if (!iommu->need_sync) - return 0; - raw_spin_lock_irqsave(&iommu->lock, flags); + if (!iommu->need_sync) { + /* + * No command has been queued since the last completion-wait. + * A concurrent CPU may have already queued that CWAIT and + * cleared need_sync; need_sync == false only means a covering + * CWAIT is queued, not that all prior commands have completed. + * Wait for the last allocated sequence number so that any + * command queued before this call (possibly on another CPU) + * is guaranteed to have completed before returning. + */ + data = iommu->cmd_sem_val; + raw_spin_unlock_irqrestore(&iommu->lock, flags); + return wait_on_sem(iommu, data); + } + data = get_cmdsem_val(iommu); build_completion_wait(&cmd, iommu, data); @@ -1464,9 +1476,7 @@ static int iommu_completion_wait(struct amd_iommu *iommu) if (ret) return ret; - ret = wait_on_sem(iommu, data); - - return ret; + return wait_on_sem(iommu, data); } static void domain_flush_complete(struct protection_domain *domain) From 16cc4f5c1c4b9e45eca7f7deefa5410a292db599 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 21 Jul 2026 12:08:49 +0200 Subject: [PATCH 125/456] exec: fix unsigned loop counter wrap in transfer_args_to_stack() The stop value is derived from bprm->p >> PAGE_SHIFT. The index variable is an unsigned long. If bprm->p drops below PAGE_SIZE and stop becomes zero the loop condition index >= stop is always true. After the index == 0 iteration the decrement wraps to ULONG_MAX and bprm->page[ULONG_MAX] reads sizeof(void *) bytes in front of the array. The pointer has wrapped to -1. That garbage pointer is then passed to kmap_local_page() and PAGE_SIZE bytes are copied from wherever that lands into the stack of the process being created. And the loop doesn't terminate either... Getting there only requires bprm->p < PAGE_SIZE. On !MMU bprm_set_stack_limit() and bprm_hit_stack_limit() are empty. So the only constraint on how far bprm->p is pushed down is valid_arg_len(), i.e. that each individual string still fits in what is left. bprm->p starts at PAGE_SIZE * MAX_ARG_PAGES - sizeof(void *) so a single argument or environment string of a little over 31 pages leaves it in the first page: Oops - load access fault [#1] CPU: 0 UID: 0 PID: 1 Comm: victim Not tainted 7.2.0-rc4 #1 epc : __memcpy+0xd4/0xf8 ra : transfer_args_to_stack+0xaa/0xae s4 : ffffffffffffffff s2 : 0000000000000000 a1 : ffffffdc98000000 a2 : 0000000000001000 status: 0000000a00001880 badaddr: ffffffdc98000000 cause: 0000000000000005 [<801a5324>] __memcpy+0xd4/0xf8 [<800d5f6a>] load_flat_binary+0x43a/0x65e [<800a2de4>] bprm_execve+0x1d4/0x316 [<800a351a>] do_execveat_common+0x12e/0x138 [<800a3d44>] __riscv_sys_execve+0x38/0x4e Kernel panic - not syncing: Fatal exception in interrupt This is an arcane bug but we should still fix it. Count down from MAX_ARG_PAGES so the loop ends when index reaches stop, stop == 0 included. The iterations performed are unchanged for every other value of stop. Only CONFIG_MMU=n builds are affected, transfer_args_to_stack() is used by binfmt_flat and binfmt_elf_fdpic on nommu only. The loop predates git history. commit 7e7ec6a93434 ("elf_fdpic_transfer_args_to_stack(): make it generic") only moved it from binfmt_elf_fdpic.c into fs/exec.c and narrowed the copy to the used part of the first page. The condition and the decrement are unchanged from 2.6.12-rc2. Link: https://patch.msgid.link/20260721-hochachtung-staumauer-pigmente-15d71f7d7d04@brauner Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: David Hildenbrand (Arm) Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exec.c b/fs/exec.c index d5993cedc829..c7b8f2d6366c 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -740,7 +740,7 @@ int transfer_args_to_stack(struct linux_binprm *bprm, stop = bprm->p >> PAGE_SHIFT; sp = *sp_location; - for (index = MAX_ARG_PAGES - 1; index >= stop; index--) { + for (index = MAX_ARG_PAGES; index-- > stop; ) { unsigned int offset = index == stop ? bprm->p & ~PAGE_MASK : 0; char *src = kmap_local_page(bprm->page[index]) + offset; sp -= PAGE_SIZE - offset; From 3349ef6a366a61d631f6a263d12cea240957719d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 21 Jul 2026 13:20:45 +0200 Subject: [PATCH 126/456] binfmt_elf_fdpic: only honour the first PT_INTERP The program header scan handles PT_INTERP from a switch nested in the scan loop, so its break leaves the switch and not the loop. A binary carrying more than one PT_INTERP runs the case again and overwrites both interpreter_name and interpreter. The previous name allocation leaks and so does the previous interpreter reference, along with the write denial open_exec() took on it. The denial is never released, so the file stays unwritable for as long as the system runs. An unprivileged caller reaches this with a crafted binary and repeats it at will. binfmt_elf stops at the first PT_INTERP. Do the same here. The flaw dates back to the driver's introduction in the pre-git history tree introduced in v2.6.11 by 91808d6ebe39 ("[PATCH] FRV: Add FDPIC ELF binary format driver"). Link: https://patch.msgid.link/20260721-gezittert-medium-kreide-b41fc1f0277e@brauner Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Jori Koolstra Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_elf_fdpic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c index 7e3108489c83..fe0b5c5ed2bc 100644 --- a/fs/binfmt_elf_fdpic.c +++ b/fs/binfmt_elf_fdpic.c @@ -231,6 +231,10 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm) for (i = 0; i < exec_params.hdr.e_phnum; i++, phdr++) { switch (phdr->p_type) { case PT_INTERP: + /* elf ABI allows only one interpreter */ + if (interpreter_name) + continue; + retval = -ENOMEM; if (phdr->p_filesz > PATH_MAX) goto error; From 780dfed688622ea01be3c9c2c55eec2207f05e04 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Thu, 16 Jul 2026 13:35:53 +0800 Subject: [PATCH 127/456] iommu/vt-d: Disallow SVA if page walk is not coherent Hardware implementations report Scalable-Mode Page-walk Coherency Support via the SMPWCS field in the extended capability register. If the hardware does not support page-walk coherency, a clflush is required every time the page table entries (which are walked by the IOMMU hardware) are updated. In the SVA case, page tables are managed by the CPU mm core, not by the IOMMU driver. Because the IOMMU driver has no way of knowing whether the CPU page table management code has ensured coherency via clflush, the driver must deny SVA if the hardware does not support coherent paging. Fixes: ff3dc6521f78 ("iommu/vt-d: Fix CPU and IOMMU SVM feature matching checks") Cc: stable@vger.kernel.org Signed-off-by: Lu Baolu Reviewed-by: Kevin Tian Reviewed-by: Samiullah Khawaja Reviewed-by: Jason Gunthorpe Signed-off-by: Will Deacon --- drivers/iommu/intel/svm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/intel/svm.c b/drivers/iommu/intel/svm.c index fea10acd4f02..726f7b6d0bff 100644 --- a/drivers/iommu/intel/svm.c +++ b/drivers/iommu/intel/svm.c @@ -27,7 +27,7 @@ void intel_svm_check(struct intel_iommu *iommu) { - if (!pasid_supported(iommu)) + if (!pasid_supported(iommu) || !ecap_smpwc(iommu->ecap)) return; if (cpu_feature_enabled(X86_FEATURE_GBPAGES) && From fb80117fddb5b477218dc99bb53911b72c3847f8 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 20 Jul 2026 19:46:13 +0800 Subject: [PATCH 128/456] iommu/amd: Bound the early ACPI HID map The ivrs_acpihid command-line parser appends entries to a fixed four-element early_acpihid_map array. Unlike the sibling IOAPIC and HPET parsers, it does not reject a fifth entry before incrementing the map size. Check the capacity at the common found label before parsing the HID and UID or writing the entry. Fixes: ca3bf5d47cec ("iommu/amd: Introduces ivrs_acpihid kernel parameter") Signed-off-by: Pengpeng Hou Reviewed-by: Ankit Soni Signed-off-by: Will Deacon --- drivers/iommu/amd/init.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index e93bcb5eef70..e7d7b4cb9337 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -3864,6 +3864,12 @@ static int __init parse_ivrs_acpihid(char *str) return 1; found: + if (early_acpihid_map_size == EARLY_MAP_SIZE) { + pr_err("Early ACPI HID map overflow - ignoring ivrs_acpihid%s\n", + str); + return 1; + } + p = acpiid; hid = strsep(&p, ":"); uid = p; From 754f8efe45f87e3a9c6871b645b2f9d46d1b407b Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Tue, 21 Jul 2026 17:34:10 +0800 Subject: [PATCH 129/456] iommu/intel: Fix out-of-bounds memset in dmar_latency_disable() dmar_latency_disable() intends to zero out only the single latency_statistic entry for the given type, but the memset size was computed as sizeof(*lstat) * DMAR_LATENCY_NUM, which clears the entire array starting from &lstat[type]. When type > 0, this writes beyond the end of the allocated array, corrupting adjacent memory. Fix by using sizeof(*lstat) to clear only the target entry. Fixes: 55ee5e67a59a ("iommu/vt-d: Add common code for dmar latency performance monitors") Signed-off-by: Li RongQing Signed-off-by: Will Deacon --- drivers/iommu/intel/perf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/intel/perf.c b/drivers/iommu/intel/perf.c index 02168f2f20a4..bec98dcbb9ec 100644 --- a/drivers/iommu/intel/perf.c +++ b/drivers/iommu/intel/perf.c @@ -63,7 +63,7 @@ void dmar_latency_disable(struct intel_iommu *iommu, enum latency_type type) return; spin_lock_irqsave(&latency_lock, flags); - memset(&lstat[type], 0, sizeof(*lstat) * DMAR_LATENCY_NUM); + memset(&lstat[type], 0, sizeof(*lstat)); spin_unlock_irqrestore(&latency_lock, flags); } From 093fbffe03f5c1bb9c10a9e5aa65b23250844403 Mon Sep 17 00:00:00 2001 From: Roger Pau Monne Date: Tue, 21 Jul 2026 10:23:21 +0200 Subject: [PATCH 130/456] MAINTAINERS: update my email address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Roger Pau Monné Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20260721082321.81212-1-roger@xenproject.org Signed-off-by: Jens Axboe --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3d6db8cb608f..b88f69881035 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29134,7 +29134,7 @@ F: net/xdp/ F: tools/testing/selftests/bpf/*xsk* XEN BLOCK SUBSYSTEM -M: Roger Pau Monné +M: Roger Pau Monné L: xen-devel@lists.xenproject.org (moderated for non-subscribers) S: Supported F: drivers/block/xen* From 0c9e6367639548307d3f578f6943ce72c9d39087 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 21 Jul 2026 09:15:58 +0800 Subject: [PATCH 131/456] drm/vc4: Prevent shader BO mappings from becoming writable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_gem_object_mmap() rejects a writable mapping of a validated shader BO, but leaves VM_MAYWRITE set. Userspace can map the BO read-only and then turn it writable with mprotect(). Validated shader BOs must stay read-only: the validator checks the instructions once and the GPU trusts them afterwards. A writable mapping lets userspace rewrite the code after validation, bypassing the validator. Clear VM_MAYWRITE on the read-only path so the mapping cannot be upgraded, as i915 already does for its read-only objects. Fixes: 463873d57014 ("drm/vc4: Add an API for creating GPU shaders in GEM BOs.") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/dri-devel/20260720085554.B0AF01F000E9@smtp.kernel.org/ Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260721011558.1672477-1-lilinmao@kylinos.cn Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_bo.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 2161761b1f22..5e7c46dd7823 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -732,9 +732,13 @@ static int vc4_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct { struct vc4_bo *bo = to_vc4_bo(obj); - if (bo->validated_shader && (vma->vm_flags & VM_WRITE)) { - DRM_DEBUG("mmapping of shader BOs for writing not allowed.\n"); - return -EINVAL; + if (bo->validated_shader) { + if (vma->vm_flags & VM_WRITE) { + DRM_DEBUG("mmapping of shader BOs for writing not allowed.\n"); + return -EINVAL; + } + + vm_flags_clear(vma, VM_MAYWRITE); } mutex_lock(&bo->madv_lock); From 55645e4f3c6022ffb160ad3617d2b624eaa38501 Mon Sep 17 00:00:00 2001 From: Chukun Pan Date: Wed, 8 Jul 2026 18:00:01 +0800 Subject: [PATCH 132/456] USB: serial: option: add TDTECH MT5710-CN Add support for the TDTECH MT5710-CN (5G redcap) module based on the Huawei HiSilicon Balong chip. T: Bus=01 Lev=02 Prnt=02 Port=00 Cnt=01 Dev#= 3 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=3466 ProdID=3301 Rev=ff.ff S: Manufacturer=TD Tech Ltd. S: Product=TDTECH MT571X S: SerialNumber=0123456789ABCDEF C:* #Ifs= 6 Cfg#= 1 Atr=c0 MxPwr= 0mA A: FirstIf#= 0 IfCount= 2 Cls=02(comm.) Sub=0d Prot=00 I:* If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=0d Prot=00 Driver=cdc_ncm E: Ad=82(I) Atr=03(Int.) MxPS= 16 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 0 Cls=0a(data ) Sub=00 Prot=01 Driver=cdc_ncm I:* If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=01 Driver=cdc_ncm E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=13 Driver=option E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=12 Driver=option E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=1c Driver=option E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=14 Driver=option E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Interface: ECM / NCM + DIAG + AT + SERIAL + GPS Signed-off-by: Chukun Pan Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 7275f4e7f569..580f06f5ce5e 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2496,6 +2496,7 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x1003, 0xff), /* Rolling RW135R-GL (laptop MBIM) */ .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x3466, 0x3301, 0xff) }, /* TDTECH MT5710-CN */ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for Global */ { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0x00, 0x40) }, { USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x40) }, From b877075d0baa22c225842c2f19e3ea0a9cbcbe39 Mon Sep 17 00:00:00 2001 From: Steven Price Date: Fri, 3 Jul 2026 14:48:35 +0100 Subject: [PATCH 133/456] arm64: Correct value returned by ESR_ELx_FSC_ADDRSZ_nL() Address size fault, level -1 is encoded as 0b101001 or 0x29 according to the Arm ARM. Correct the value to match the spec. This also matches the offset of "level -1 address size fault" in the fault_info array in fault.c. Fixes: fb8a3eba9c81 ("KVM: arm64: Only read HPFAR_EL2 when value is architecturally valid") Signed-off-by: Steven Price Reviewed-by: Marc Zyngier Signed-off-by: Will Deacon --- arch/arm64/include/asm/esr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h index 81c17320a588..f816f5d77f1a 100644 --- a/arch/arm64/include/asm/esr.h +++ b/arch/arm64/include/asm/esr.h @@ -131,7 +131,7 @@ * Annoyingly, the negative levels for Address size faults aren't laid out * contiguously (or in the desired order) */ -#define ESR_ELx_FSC_ADDRSZ_nL(n) ((n) == -1 ? 0x25 : 0x2C) +#define ESR_ELx_FSC_ADDRSZ_nL(n) ((n) == -1 ? 0x29 : 0x2C) #define ESR_ELx_FSC_ADDRSZ_L(n) ((n) < 0 ? ESR_ELx_FSC_ADDRSZ_nL(n) : \ (ESR_ELx_FSC_ADDRSZ + (n))) From 285f90a4d1141c7594f2368e19cbb307388eff30 Mon Sep 17 00:00:00 2001 From: Richard Cheng Date: Tue, 21 Jul 2026 18:00:26 +0800 Subject: [PATCH 134/456] arm64/mm: Check the requested PFN range during memory removal prevent_memory_remove_notifier() advances pfn while scanning the requested range for early memory. When the loop completes, pfn is at or beyond end_pfn. Passing it to can_unmap_without_split() therefore checks a range after the one being offlined. Consequently, a valid request can be rejected based on the following range, while a request that would split a leaf mapping can be accepted if the shifted range can be unmapped without a split. This was observed with CXL DAX memory, where the final memory block was incorrectly allowed to be offlined. Pass arg->start_pfn into can_unmap_without_split() so it checks the requested range. Fixes: 95a58852b0e5 ("arm64/mm: Reject memory removal that splits a kernel leaf mapping") Signed-off-by: Richard Cheng Reviewed-by: Anshuman Khandual Signed-off-by: Will Deacon --- arch/arm64/mm/mmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c index a25d8beacc83..18a8b0d3714e 100644 --- a/arch/arm64/mm/mmu.c +++ b/arch/arm64/mm/mmu.c @@ -2194,7 +2194,7 @@ static int prevent_memory_remove_notifier(struct notifier_block *nb, } } - if (!can_unmap_without_split(pfn, arg->nr_pages)) + if (!can_unmap_without_split(arg->start_pfn, arg->nr_pages)) return NOTIFY_BAD; return NOTIFY_OK; From a4f55260f7f7d4dc4d0ee55063dfb0c457b77991 Mon Sep 17 00:00:00 2001 From: Ian Forbes Date: Tue, 23 Jun 2026 14:33:14 -0500 Subject: [PATCH 135/456] drm/vmwgfx: Validate vmw_surface_metadata::array_size This field comes from userspace and should be validated against specific limits depending on which Shader Model (SM) is available. Fixes: 504901dbb0b5 ("drm/vmwgfx: Refactor surface_define to use vmw_surface_metadata") Reported-by: Zero Day Initiative Cc: stable@vger.kernel.org Signed-off-by: Ian Forbes Reviewed-by: Maaz Mombasawala Signed-off-by: Zack Rusin Link: https://patch.msgid.link/20260623193314.506257-1-ian.forbes@broadcom.com --- drivers/gpu/drm/vmwgfx/vmwgfx_surface.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c index b2d3927b5567..bd0563741e89 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_surface.c @@ -78,7 +78,7 @@ static int vmw_gb_surface_unbind(struct vmw_resource *res, static int vmw_gb_surface_destroy(struct vmw_resource *res); static int vmw_gb_surface_define_internal(struct drm_device *dev, - struct drm_vmw_gb_surface_create_ext_req *req, + const struct drm_vmw_gb_surface_create_ext_req *req, struct drm_vmw_gb_surface_create_rep *rep, struct drm_file *file_priv); static int @@ -1503,7 +1503,7 @@ int vmw_gb_surface_reference_ext_ioctl(struct drm_device *dev, void *data, */ static int vmw_gb_surface_define_internal(struct drm_device *dev, - struct drm_vmw_gb_surface_create_ext_req *req, + const struct drm_vmw_gb_surface_create_ext_req *req, struct drm_vmw_gb_surface_create_rep *rep, struct drm_file *file_priv) { @@ -1521,9 +1521,21 @@ vmw_gb_surface_define_internal(struct drm_device *dev, req->base.svga3d_flags); /* array_size must be null for non-GL3 host. */ - if (req->base.array_size > 0 && !has_sm4_context(dev_priv)) { - VMW_DEBUG_USER("SM4 surface not supported.\n"); - return -EINVAL; + if (req->base.array_size > 0) { + if (has_sm5_context(dev_priv)) { + if (req->base.array_size > SVGA3D_SM5_MAX_SURFACE_ARRAYSIZE) { + VMW_DEBUG_USER("Invalid Surface Array Size.\n"); + return -EINVAL; + } + } else if (has_sm4_context(dev_priv)) { + if (req->base.array_size > SVGA3D_SM4_MAX_SURFACE_ARRAYSIZE) { + VMW_DEBUG_USER("Invalid Surface Array Size.\n"); + return -EINVAL; + } + } else { + VMW_DEBUG_USER("SM4+ surface not supported.\n"); + return -EINVAL; + } } if (!has_sm4_1_context(dev_priv)) { From 4b9a5458d02e214ef2b384124ca626e3e381d778 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Tue, 14 Jul 2026 00:09:31 +0200 Subject: [PATCH 136/456] fs: preserve ACL_DONT_CACHE state in forget_cached_acl() The ACL_DONT_CACHE state is meant to be a constant state for the inode for filesystems that want to opt out of posix acl caching. Commit facd61053cff1 ("fuse: fixes after adapting to new posix acl api") used this facility to opt out of posix acl caching for fuse inodes with fuse server that does not negotiate FUSE_POSIX_ACL (fc->posix_acl). The commit also takes care to gate the forget_all_cached_acls() call in fuse_set_acl() on fc->posix_acl because there is no need for it, but there are other placed in fuse code which call forget_all_cached_acls() unconditional to fc->posix_acl and those cause the loss of the ACL_DONT_CACHE state. This is not only a functional bug. Properly timed, a get_acl() from this fuse filesystem can return a stale cached value, as was observed in tests, because set_acl() does not invalidate the unintentional acl cache. We could fix this in fuse, but it actually makes no sense for the vfs helper forget_cached_acl() to invalidate the ACL_DONT_CACHE state, so let it not do that to fix fuse and future users of ACL_DONT_CACHE. Fixes: facd61053cff1 ("fuse: fixes after adapting to new posix acl api") Cc: stable@vger.kernel.org Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260713220932.413004-2-amir73il@gmail.com Reviewed-by: Luis Henriques Signed-off-by: Christian Brauner (Amutable) --- fs/posix_acl.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/posix_acl.c b/fs/posix_acl.c index b4bfe4ddf64e..3dc62c1c2708 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -93,6 +93,13 @@ static void __forget_cached_acl(struct posix_acl **p) { struct posix_acl *old; + /* + * ACL_DONT_CACHE is expected to be a "const" value and xchg it with + * ACL_NOT_CACHED would enable acl caching for the inode - + * clearly not what the caller has intended. + */ + if (READ_ONCE(*p) == ACL_DONT_CACHE) + return; old = xchg(p, ACL_NOT_CACHED); if (!is_uncached_acl(old)) posix_acl_release(old); From 9acb102522b92f24fba6b238b3668a4d9dfcb592 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Tue, 14 Jul 2026 00:09:32 +0200 Subject: [PATCH 137/456] selftests/fuse: add ACL_DONT_CACHE regression test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a test that reproduces the stale ACL bug fixed by: "fs: preserve ACL_DONT_CACHE state in forget_cached_acl()" A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises inodes with i_acl = ACL_DONT_CACHE. Before the fix, calling forget_all_cached_acls() (e.g. from fuse_update_get_attr() on a statx(AT_STATX_FORCE_SYNC)) would silently replace ACL_DONT_CACHE with ACL_NOT_CACHED, enabling the kernel ACL cache. A subsequent getxattr would populate the cache, and because fuse_set_acl() skips forget_all_cached_acls() for !fc->posix_acl, later ACL changes were not visible to callers — getxattr returned stale data. The test mounts a minimal libfuse3 lowlevel filesystem (no FUSE_POSIX_ACL negotiated) and: 1. Issues two getxattrs — both must reach the daemon, proving ACL_DONT_CACHE suppresses caching before any trigger. 2. Calls statx(AT_STATX_FORCE_SYNC) to trigger forget_all_cached_acls(). 3. Issues another getxattr (populates the cache on a buggy kernel). 4. Switches the daemon to a different-sized ACL (ACL_B). 5. Issues a final getxattr — expects ACL_B (44 bytes) and daemon call count 4; a buggy kernel returns stale ACL_A (28 bytes). fuse_acl_cache_test is only built when libfuse3 is detected via pkg-config. Christian Brauner says: Changed do_force_statx() to call the statx() libc wrapper instead of syscall(SYS_statx, ...) as requested by Amir after review feedback from Luis Henriques, and dropped the now unused include. Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260713220932.413004-3-amir73il@gmail.com Signed-off-by: Christian Brauner (Amutable) --- .../selftests/filesystems/fuse/Makefile | 10 + .../filesystems/fuse/fuse_acl_cache_test.c | 347 ++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile index 612aad69a93a..f47141484275 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -5,6 +5,13 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) TEST_GEN_PROGS := fusectl_test TEST_GEN_FILES := fuse_mnt +# fuse_acl_cache_test requires libfuse3; add it only when the library is present. +ACL_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null) +ACL_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null) +ifneq ($(ACL_CFLAGS),) +TEST_GEN_PROGS += fuse_acl_cache_test +endif + include ../../lib.mk VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null) @@ -19,3 +26,6 @@ endif $(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS) $(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS) + +$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(ACL_CFLAGS) +$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(ACL_LDLIBS) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c new file mode 100644 index 000000000000..2411a6e285f1 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test: FUSE ACL caching bug triggered by AT_STATX_FORCE_SYNC + * + * A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode + * with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed + * (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls + * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel, + * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED, + * inadvertently enabling the kernel ACL cache for that inode. The next + * getxattr populates the cache. Because fuse_set_acl() skips + * forget_all_cached_acls() for !fc->posix_acl mounts, any subsequent change to + * the ACL leaves the stale kernel entry in place, and the next getxattr returns + * wrong data without ever reaching the FUSE daemon. + * + * Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is + * ACL_DONT_CACHE, preserving the "never cache" invariant for the inode's + * lifetime. + * + * Test outline: + * 1. Mount a minimal FUSE fs (no FUSE_POSIX_ACL negotiated). + * 2. lgetxattr -> daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE). + * 3. statx(AT_STATX_FORCE_SYNC) -> forget_all_cached_acls() called. + * Buggy: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled). + * Fixed: ACL_DONT_CACHE preserved. + * 4. lgetxattr -> daemon called, ACL_A returned. + * Buggy: result now cached (ACL_NOT_CACHED -> cached). + * Fixed: result still not cached. + * 5. Daemon switches to ACL_B internally (different size). + * 6. lgetxattr -> should return ACL_B (44 bytes). + * Buggy: cache hit, returns stale ACL_A (28 bytes). FAIL. + * Fixed: no cache, daemon called, returns ACL_B (44 bytes). PASS. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_USE_VERSION 31 +#include + +#include "kselftest_harness.h" + +/* ---- ACL binary encoding ------------------------------------------------ */ +/* + * POSIX ACL v2 xattr format (little-endian): + * header: u32 version (= 0x00000002) + * entry: u16 tag | u16 perm | u32 id + * + * Entries must appear in tag-ascending order; named USER/GROUP entries + * require a MASK entry. Both ACLs pass posix_acl_from_xattr() validation. + */ + +/* ACL_A: 3 entries (USER_OBJ:rwx, GROUP_OBJ:r-x, OTHER:r-x) = 28 bytes */ +static const uint8_t acl_a[] = { + 0x02, 0x00, 0x00, 0x00, /* v2 header */ + 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */ + 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */ + 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */ +}; + +/* + * ACL_B: 5 entries — adds USER uid=1 and MASK = 44 bytes. + * A named USER entry requires a MASK; all tags in ascending order. + */ +static const uint8_t acl_b[] = { + 0x02, 0x00, 0x00, 0x00, /* v2 header */ + 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */ + 0x02, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, /* USER uid=1 rwx */ + 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */ + 0x10, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* MASK rwx */ + 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */ +}; + +/* ---- Shared state (daemon thread <-> test thread) ----------------------- */ + +#define FILE_INO 2 +#define FILE_NAME "testfile" + +struct daemon_state { + pthread_mutex_t lock; + const uint8_t *acl; + size_t acl_size; + int getxattr_count; +}; + +/* + * Global: callbacks are stateless fns so we use a single global. + * Safe because only one test instance runs at a time. + */ +static struct daemon_state g_ds = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +/* ---- FUSE lowlevel callbacks -------------------------------------------- */ + +static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + if (parent != FUSE_ROOT_ID || strcmp(name, FILE_NAME)) { + fuse_reply_err(req, ENOENT); + return; + } + struct fuse_entry_param e = {}; + + /* + * Long attr/entry timeouts so that normal stat() calls do not + * expire and trigger forget_all_cached_acls() on their own; + * only the explicit AT_STATX_FORCE_SYNC should trigger it. + */ + e.ino = FILE_INO; + e.generation = 1; + e.attr_timeout = 10.0; + e.entry_timeout = 10.0; + e.attr.st_ino = FILE_INO; + e.attr.st_mode = S_IFREG | 0644; + e.attr.st_nlink = 1; + fuse_reply_entry(req, &e); +} + +static void fs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat st = {}; + + (void)fi; + if (ino == FUSE_ROOT_ID) { + st.st_ino = FUSE_ROOT_ID; + st.st_mode = S_IFDIR | 0755; + st.st_nlink = 2; + } else if (ino == FILE_INO) { + st.st_ino = FILE_INO; + st.st_mode = S_IFREG | 0644; + st.st_nlink = 1; + } else { + fuse_reply_err(req, ENOENT); + return; + } + fuse_reply_attr(req, &st, 10); +} + +static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + if (ino != FILE_INO || + strcmp(name, "system.posix_acl_access") != 0) { + fuse_reply_err(req, ENODATA); + return; + } + + pthread_mutex_lock(&g_ds.lock); + const uint8_t *acl = g_ds.acl; + size_t acl_size = g_ds.acl_size; + g_ds.getxattr_count++; + pthread_mutex_unlock(&g_ds.lock); + + if (size == 0) + fuse_reply_xattr(req, acl_size); + else if (size < acl_size) + fuse_reply_err(req, ERANGE); + else + fuse_reply_buf(req, (const char *)acl, acl_size); +} + +static const struct fuse_lowlevel_ops fs_ops = { + .lookup = fs_lookup, + .getattr = fs_getattr, + .getxattr = fs_getxattr, +}; + +/* ---- Daemon thread ------------------------------------------------------- */ + +static void *run_daemon(void *arg) +{ + fuse_session_loop((struct fuse_session *)arg); + return NULL; +} + +/* ---- kselftest harness --------------------------------------------------- */ + +FIXTURE(acl_cache) { + struct fuse_session *se; + char mountpoint[PATH_MAX]; + char file_path[PATH_MAX]; + pthread_t thread; +}; + +FIXTURE_SETUP(acl_cache) +{ + char *fuse_argv[] = { "fuse_acl_cache_test", NULL }; + struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv); + + g_ds.acl = acl_a; + g_ds.acl_size = sizeof(acl_a); + g_ds.getxattr_count = 0; + + strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX"); + if (!mkdtemp(self->mountpoint)) + SKIP(return, "mkdtemp: %s", strerror(errno)); + + snprintf(self->file_path, sizeof(self->file_path), + "%s/" FILE_NAME, self->mountpoint); + + self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL); + if (!self->se) { + rmdir(self->mountpoint); + SKIP(return, "fuse_session_new failed"); + } + + if (fuse_session_mount(self->se, self->mountpoint)) { + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "fuse_session_mount failed " + "(missing fusermount3 or insufficient privileges)"); + } + + if (pthread_create(&self->thread, NULL, run_daemon, self->se)) { + fuse_session_unmount(self->se); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "pthread_create: %s", strerror(errno)); + } + + fuse_opt_free_args(&args); +} + +FIXTURE_TEARDOWN(acl_cache) +{ + fuse_session_exit(self->se); + fuse_session_unmount(self->se); + pthread_join(self->thread, NULL); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); +} + +static int do_force_statx(const char *path) +{ + struct statx stx; + + return statx(AT_FDCWD, path, AT_STATX_FORCE_SYNC, STATX_BASIC_STATS, + &stx); +} + +TEST_F(acl_cache, stale_after_force_sync) +{ + char buf[512]; + ssize_t sz; + int count; + + /* + * Step 1: two getxattr calls before any statx(FORCE_SYNC). + * i_acl == ACL_DONT_CACHE. __get_acl's cmpxchg(p, ACL_NOT_CACHED, + * sentinel) finds *p != ACL_NOT_CACHED on every call, so the sentinel + * is never placed and the result is never cached. Both calls must + * reach the daemon, proving ACL_DONT_CACHE suppresses caching. + */ + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); + + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); + + pthread_mutex_lock(&g_ds.lock); + count = g_ds.getxattr_count; + pthread_mutex_unlock(&g_ds.lock); + + ASSERT_EQ(count, 2); + TH_LOG("step 1 OK: both pre-trigger getxattrs reached daemon (count=%d), " + "ACL_DONT_CACHE is working", count); + + /* + * Step 2: statx(AT_STATX_FORCE_SYNC). + * fuse_update_get_attr() calls forget_all_cached_acls() before sending + * FUSE_GETATTR. + * Buggy kernel: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled) + * Fixed kernel: ACL_DONT_CACHE preserved (no effect) + */ + ASSERT_EQ(do_force_statx(self->file_path), 0); + TH_LOG("step 2 OK: statx(AT_STATX_FORCE_SYNC) succeeded"); + + /* + * Step 3: getxattr — cache population attempt after the trigger. + * Buggy: *p == ACL_NOT_CACHED -> sentinel placed -> fuse_get_inode_acl + * called -> ACL_A parsed and stored in the kernel cache. + * Fixed: *p == ACL_DONT_CACHE -> sentinel placement skipped -> + * fuse_get_inode_acl called but result not cached. + * Either way the correct ACL_A is returned here. + */ + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); + + pthread_mutex_lock(&g_ds.lock); + count = g_ds.getxattr_count; + pthread_mutex_unlock(&g_ds.lock); + + ASSERT_EQ(count, 3); + TH_LOG("step 3 OK: post-trigger getxattr reached daemon (count=%d), " + "returned correct ACL_A (%zd bytes)", count, sz); + + /* + * Step 4: switch daemon to ACL_B (different size: 44 vs 28 bytes). + * Simulates an ACL change that fuse_set_acl() would NOT invalidate for + * !fc->posix_acl mounts (it skips forget_all_cached_acls in that case). + * On a fixed kernel the ACL was never cached, so this is moot. + */ + pthread_mutex_lock(&g_ds.lock); + g_ds.acl = acl_b; + g_ds.acl_size = sizeof(acl_b); + pthread_mutex_unlock(&g_ds.lock); + TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b)); + + /* + * Step 5: getxattr — the decisive check. + * Buggy kernel: cache hit -> stale ACL_A (28 bytes), count stays 3. + * Fixed kernel: no cache -> daemon called -> ACL_B (44 bytes), count 4. + */ + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + + pthread_mutex_lock(&g_ds.lock); + count = g_ds.getxattr_count; + pthread_mutex_unlock(&g_ds.lock); + + if (sz == (ssize_t)sizeof(acl_a)) + TH_LOG("step 5 BUG: stale ACL_A (%zd bytes) from kernel cache " + "(count=%d); ACL_DONT_CACHE corrupted by " + "forget_all_cached_acls()", sz, count); + else + TH_LOG("step 5 OK: daemon reached (count=%d), " + "fresh ACL_B (%zd bytes)", count, sz); + + EXPECT_EQ(sz, (ssize_t)sizeof(acl_b)); + EXPECT_EQ(count, 4); +} + +TEST_HARNESS_MAIN From 2eb74eef4b7eda8df593d22fb48e94ef959ec8a5 Mon Sep 17 00:00:00 2001 From: Carl Johnson Date: Tue, 21 Jul 2026 13:27:55 -0400 Subject: [PATCH 138/456] smb: client: handle STATUS_STOPPED_ON_SYMLINK responses without a symlink target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macOS built-in SMB server returns STATUS_STOPPED_ON_SYMLINK for a CREATE on a path whose final component is a symlink, but it does not include a Symbolic Link Error Response in the error data: both ErrorContextCount and ByteCount are zero, so the symlink target is not present in the response at all. Per [MS-SMB2] section 2.2.2 such a response should carry a valid Symbolic Link Error Response, so this is a server bug, but the target can still be retrieved with FSCTL_GET_REPARSE_POINT. Frame from a capture against macOS 26.5.2 (build 25F84): SMB2 hdr : Status=0x8000002d STATUS_STOPPED_ON_SYMLINK, Cmd=Create Error Rsp: StructureSize=0x0009 Error Context Count: 0 Byte Count: 0 Error Data: 00 symlink_data() cannot find a struct smb2_symlink_err_rsp in such a response and returns -EINVAL, which parse_create_response() propagates, so smb2_query_path_info() bails out at if (rc || !data->reparse_point) goto out; before it can retry with SMB2_OP_GET_REPARSE. stat(), readlink() and ls of any server-side symlink then fail with -EINVAL: $ ls -la Config l????????? ? ? ? ? ? Config.json $ stat Config/Config.json stat: cannot statx 'Config/Config.json': Invalid argument A 5.10 client resolves these symlinks correctly against the same server and share, so this is a regression for Apple SMB servers. Handle it in several places: - symlink_data() detects the empty response (ErrorContextCount and ByteCount both zero) and returns a distinct -ENODATA, so that "server did not send the target" can be told apart from a genuinely malformed response and only this case is worked around. - parse_create_response() treats -ENODATA like STATUS_IO_REPARSE_TAG_NOT_HANDLED, which does not carry the target either: leave the reparse tag unset and clear rc, so the existing SMB2_OP_GET_REPARSE path retrieves the target. - smb2_query_path_info() only fixes up the symlink target type when the target is already known. SMB2_OP_GET_REPARSE sets data->reparse.tag but does not parse the target out of the reparse buffer; that happens later, in reparse_info_to_fattr(). Without this check smb2_fix_symlink_target_type() is called with a NULL target and returns -EIO. This could not happen with servers that send the target inline and therefore skip SMB2_OP_GET_REPARSE. - smb2_open_file() maps -ENODATA to -EIO, matching STATUS_IO_REPARSE_TAG_NOT_HANDLED, so its callers retrieve the target with SMB2_OP_GET_REPARSE as well. Tested on Debian 13, kernel 6.18.38 (armv7), against macOS 26.5.2: symlinks now resolve, including relative, parent-traversing and directory symlinks, and reads through symlinks succeed. Cc: stable@vger.kernel.org Co-developed-by: Pali Rohár Signed-off-by: Pali Rohár Signed-off-by: Carl Johnson Signed-off-by: Steve French --- fs/smb/client/smb2file.c | 21 +++++++++++++++++++++ fs/smb/client/smb2inode.c | 23 ++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index 5ef919bce52d..f35b6488d810 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -30,6 +30,19 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov) u8 *end = (u8 *)err + iov->iov_len; u32 len; + /* + * Per [MS-SMB2] section 2.2.2, a STATUS_STOPPED_ON_SYMLINK response has to + * carry a Symbolic Link Error Response, so ByteCount cannot be zero. Some + * servers (e.g. the macOS built-in SMB server) violate this and return an + * empty error response, with both ErrorContextCount and ByteCount set to + * zero, i.e. without the symlink target. Detect this and return -ENODATA + * so that callers can tell "server did not send the target" apart from a + * malformed response, and retrieve the target with FSCTL_GET_REPARSE_POINT + * instead. + */ + if (!err->ErrorContextCount && !le32_to_cpu(err->ByteCount)) + return ERR_PTR(-ENODATA); + if (err->ErrorContextCount) { struct smb2_error_context_rsp *p; @@ -199,6 +212,14 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, rc = smb2_parse_symlink_response(oparms->cifs_sb, &err_iov, oparms->path, &data->symlink_target); + /* + * If smb2_parse_symlink_response returned -ENODATA then the + * symlink_target was not sent. Treat this as if the SMB2_open() + * failed with STATUS_IO_REPARSE_TAG_NOT_HANDLED status, which is + * indicated by the -EIO errno. + */ + if (rc == -ENODATA) + rc = -EIO; if (!rc) { memset(&data->fi, 0, sizeof(data->fi)); oparms->create_options |= OPEN_REPARSE_POINT; diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 6c9c229b91f6..213bc298cdf2 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -792,9 +792,19 @@ static int parse_create_response(struct cifs_open_info_data *data, rc = smb2_parse_symlink_response(cifs_sb, iov, full_path, &data->symlink_target); - if (rc) + if (rc != 0 && rc != -ENODATA) return rc; - tag = IO_REPARSE_TAG_SYMLINK; + /* + * -ENODATA means that the response was parsed but did not contain + * the symlink target at all (see symlink_data()). Treat it like + * STATUS_IO_REPARSE_TAG_NOT_HANDLED, which does not contain it + * either: leave the tag unset and clear rc, so that the caller + * retrieves the target with SMB2_OP_GET_REPARSE. + */ + if (rc == -ENODATA) + rc = 0; + else + tag = IO_REPARSE_TAG_SYMLINK; reparse_point = true; break; case STATUS_SUCCESS: @@ -987,7 +997,14 @@ int smb2_query_path_info(const unsigned int xid, rc = -EOPNOTSUPP; } - if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc) { + /* + * If the symlink was already parsed in create response then it is needed to fix + * its type now (after the second call with OPEN_REPARSE_POINT which filled the + * data->fi.Attributes). If the symlink was not parsed in create response then + * the data->symlink_target was not filled yet and then the type will be fixed + * later after data->symlink_target is filled. + */ + if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc && data->symlink_target) { bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY; rc = smb2_fix_symlink_target_type(&data->symlink_target, directory, cifs_sb); } From 18a551482a4a326790698b273e76d7575a51a57d Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Mon, 20 Jul 2026 18:14:47 -0500 Subject: [PATCH 139/456] accel: ethosu: Fix element size accounting for cmd stream validation There are 2 issues with the element size handling in the command stream validation which result in too small of a size calculated when the element size is 16/32/64 bits. For NHWC format, the element size is simply missing from the calculation. The bitfield for the element size is different between IFM/IFM2 and OFM. IFM and IFM2 encode the precision in parameter bits 2:3, while OFM uses bits 1:2. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Acked-by: Tomeu Vizoso Link: https://patch.msgid.link/20260720231450.485221-1-robh@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_gem.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c index 3401883e207f..1daff32610c7 100644 --- a/drivers/accel/ethosu/ethosu_gem.c +++ b/drivers/accel/ethosu/ethosu_gem.c @@ -194,7 +194,7 @@ static u64 dma_length(struct ethosu_validated_cmdstream_info *info, static u64 feat_matrix_length(struct ethosu_validated_cmdstream_info *info, struct feat_matrix *fm, - u32 x, u32 y, u32 c) + u32 x, u32 y, u32 c, bool ofm) { u32 element_size, storage = fm->precision >> 14; int tile = 0; @@ -231,10 +231,11 @@ static u64 feat_matrix_length(struct ethosu_validated_cmdstream_info *info, switch ((fm->precision >> 6) & 0x3) { // format case 0: //nhwc: - addr += x * fm->stride_x + c; + element_size = BIT((fm->precision >> (ofm ? 1 : 2)) & 0x3); + addr += x * fm->stride_x + c * element_size; break; case 1: //nhcwb16: - element_size = BIT((fm->precision >> 1) & 0x3); + element_size = BIT((fm->precision >> (ofm ? 1 : 2)) & 0x3); addr += (c / 16) * fm->stride_c + (16 * x + (c & 0xf)) * element_size; break; @@ -268,7 +269,7 @@ static int calc_sizes(struct drm_device *ddev, return -EINVAL; len = feat_matrix_length(info, &st->ifm, ifm_width, - ifm_height, st->ifm.depth); + ifm_height, st->ifm.depth, false); dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n", op, st->ifm.region, st->ifm.base[0], len); if (len == U64_MAX) @@ -277,7 +278,7 @@ static int calc_sizes(struct drm_device *ddev, if (ifm2) { len = feat_matrix_length(info, &st->ifm2, st->ifm.depth, - 0, st->ofm.depth); + 0, st->ofm.depth, false); dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n", op, st->ifm2.region, st->ifm2.base[0], len); if (len == U64_MAX) @@ -309,7 +310,7 @@ static int calc_sizes(struct drm_device *ddev, } len = feat_matrix_length(info, &st->ofm, st->ofm.width, - st->ofm.height[2], st->ofm.depth); + st->ofm.height[2], st->ofm.depth, true); dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n", op, st->ofm.region, st->ofm.base[0], len); if (len == U64_MAX) @@ -333,7 +334,7 @@ static int calc_sizes_elemwise(struct drm_device *ddev, depth = st->ifm.broadcast & 0x4 ? 0 : st->ofm.depth; len = feat_matrix_length(info, &st->ifm, width, - height, depth); + height, depth, false); dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n", op, st->ifm.region, st->ifm.base[0], len); if (len == U64_MAX) @@ -346,7 +347,7 @@ static int calc_sizes_elemwise(struct drm_device *ddev, depth = st->ifm2.broadcast & 0x4 ? 0 : st->ofm.depth; len = feat_matrix_length(info, &st->ifm2, width, - height, depth); + height, depth, false); dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n", op, st->ifm2.region, st->ifm2.base[0], len); if (len == U64_MAX) @@ -354,7 +355,7 @@ static int calc_sizes_elemwise(struct drm_device *ddev, } len = feat_matrix_length(info, &st->ofm, st->ofm.width, - st->ofm.height[2], st->ofm.depth); + st->ofm.height[2], st->ofm.depth, true); dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n", op, st->ofm.region, st->ofm.base[0], len); if (len == U64_MAX) From 6b7e0066294d23ad1fd37f4326c32e8090fb8b65 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Mon, 20 Jul 2026 18:14:48 -0500 Subject: [PATCH 140/456] accel: ethosu: Handle U85 internal chaining buffer The Ethos-U85 supports an internal chaining buffer as temporary storage between some operations. When chaining is activated, the IFM/OFM region setting selects a chaining buffer rather than a region, and the IFM/OFM base addresses don't matter. In this case, the feature matrix size calculations should be skipped. Otherwise, the command stream will be intermittently rejected depending on prior feature matrix base addresses. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Acked-by: Tomeu Vizoso Link: https://patch.msgid.link/20260720231450.485221-2-robh@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_gem.c | 35 +++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c index 1daff32610c7..d50fed64d4d9 100644 --- a/drivers/accel/ethosu/ethosu_gem.c +++ b/drivers/accel/ethosu/ethosu_gem.c @@ -192,7 +192,15 @@ static u64 dma_length(struct ethosu_validated_cmdstream_info *info, return len; } -static u64 feat_matrix_length(struct ethosu_validated_cmdstream_info *info, +static bool feat_matrix_chained(struct ethosu_device *edev, struct feat_matrix *fm) +{ + u32 storage = fm->precision >> 14; + + return !ethosu_is_u65(edev) && storage == 2; +} + +static u64 feat_matrix_length(struct ethosu_device *edev, + struct ethosu_validated_cmdstream_info *info, struct feat_matrix *fm, u32 x, u32 y, u32 c, bool ofm) { @@ -203,6 +211,9 @@ static u64 feat_matrix_length(struct ethosu_validated_cmdstream_info *info, if (fm->region < 0) return U64_MAX; + if (feat_matrix_chained(edev, fm)) + return 0; + switch (storage) { case 0: if (x >= fm->width0 + 1) { @@ -223,6 +234,8 @@ static u64 feat_matrix_length(struct ethosu_validated_cmdstream_info *info, tile = 1; } break; + default: + return U64_MAX; } if (fm->base[tile] == U64_MAX) return U64_MAX; @@ -251,6 +264,7 @@ static int calc_sizes(struct drm_device *ddev, u16 op, struct cmd_state *st, bool ifm, bool ifm2, bool weight, bool scale) { + struct ethosu_device *edev = to_ethosu_device(ddev); u64 len; if (ifm) { @@ -268,7 +282,7 @@ static int calc_sizes(struct drm_device *ddev, if (ifm_height < 0 || ifm_width < 0) return -EINVAL; - len = feat_matrix_length(info, &st->ifm, ifm_width, + len = feat_matrix_length(edev, info, &st->ifm, ifm_width, ifm_height, st->ifm.depth, false); dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n", op, st->ifm.region, st->ifm.base[0], len); @@ -277,7 +291,7 @@ static int calc_sizes(struct drm_device *ddev, } if (ifm2) { - len = feat_matrix_length(info, &st->ifm2, st->ifm.depth, + len = feat_matrix_length(edev, info, &st->ifm2, st->ifm.depth, 0, st->ofm.depth, false); dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n", op, st->ifm2.region, st->ifm2.base[0], len); @@ -309,13 +323,14 @@ static int calc_sizes(struct drm_device *ddev, st->scale[0].base + st->scale[0].length); } - len = feat_matrix_length(info, &st->ofm, st->ofm.width, + len = feat_matrix_length(edev, info, &st->ofm, st->ofm.width, st->ofm.height[2], st->ofm.depth, true); dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n", op, st->ofm.region, st->ofm.base[0], len); if (len == U64_MAX) return -EINVAL; - info->output_region[st->ofm.region] = true; + if (!feat_matrix_chained(edev, &st->ofm)) + info->output_region[st->ofm.region] = true; return 0; } @@ -325,6 +340,7 @@ static int calc_sizes_elemwise(struct drm_device *ddev, u16 op, struct cmd_state *st, bool ifm, bool ifm2) { + struct ethosu_device *edev = to_ethosu_device(ddev); u32 height, width, depth; u64 len; @@ -333,7 +349,7 @@ static int calc_sizes_elemwise(struct drm_device *ddev, width = st->ifm.broadcast & 0x2 ? 0 : st->ofm.width; depth = st->ifm.broadcast & 0x4 ? 0 : st->ofm.depth; - len = feat_matrix_length(info, &st->ifm, width, + len = feat_matrix_length(edev, info, &st->ifm, width, height, depth, false); dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n", op, st->ifm.region, st->ifm.base[0], len); @@ -346,7 +362,7 @@ static int calc_sizes_elemwise(struct drm_device *ddev, width = st->ifm2.broadcast & 0x2 ? 0 : st->ofm.width; depth = st->ifm2.broadcast & 0x4 ? 0 : st->ofm.depth; - len = feat_matrix_length(info, &st->ifm2, width, + len = feat_matrix_length(edev, info, &st->ifm2, width, height, depth, false); dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n", op, st->ifm2.region, st->ifm2.base[0], len); @@ -354,13 +370,14 @@ static int calc_sizes_elemwise(struct drm_device *ddev, return -EINVAL; } - len = feat_matrix_length(info, &st->ofm, st->ofm.width, + len = feat_matrix_length(edev, info, &st->ofm, st->ofm.width, st->ofm.height[2], st->ofm.depth, true); dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n", op, st->ofm.region, st->ofm.base[0], len); if (len == U64_MAX) return -EINVAL; - info->output_region[st->ofm.region] = true; + if (!feat_matrix_chained(edev, &st->ofm)) + info->output_region[st->ofm.region] = true; return 0; } From a45cc646a3aa83eb4ab4c7ed2685785ea51dc5e6 Mon Sep 17 00:00:00 2001 From: Kamal Wadhwa Date: Mon, 20 Jul 2026 21:25:17 +0530 Subject: [PATCH 141/456] regulator: core: clamp voltage constraints before applying apply_uV machine_constraints_voltage() currently applies apply_uV against the machine-supplied [min_uV, max_uV] range, and only afterwards clamps that range down to what the regulator can actually supply (via ops->list_voltage()). If the machine-supplied range is wider than the regulator's actual range, apply_uV's rounding can pick a selector outside the (correct) clamped range, so the regulator ends up programmed outside its clamped min/max. At bring-up this shows up as a voltage read-back outside the clamped range. Fix this by moving the clamping block ahead of the apply_uV block, so apply_uV always targets an already-clamped range. Whether apply_uV should run is decided from the unclamped constraints beforehand and stored in a local bool, since clamping must not itself change whether apply_uV fires. No functional change to the clamping logic itself, only its position relative to apply_uV. Its early return 0 exits become fallthroughs since the apply_uV logic now follows it. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Kamal Wadhwa Link: https://patch.msgid.link/20260720-b4-regulator-core-clamp-voltage-v1-1-8e5eec076a8e@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/regulator/core.c | 163 +++++++++++++++++++++------------------ 1 file changed, 90 insertions(+), 73 deletions(-) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 1797929dfe56..2e61606fc1d0 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1220,10 +1220,98 @@ static int machine_constraints_voltage(struct regulator_dev *rdev, { const struct regulator_ops *ops = rdev->desc->ops; int ret; + bool apply_uV; + + /* + * Decide up front, from the constraints as handed to us, whether + * apply_uV needs to run below. The clamping pass right after this + * may rewrite constraints->min_uV/max_uV (e.g. the fixed-voltage + * autoconfigure case), and we don't want that to change whether + * apply_uV fires. + */ + apply_uV = rdev->constraints->apply_uV && + rdev->constraints->min_uV && rdev->constraints->max_uV; + + /* + * Constrain machine-level voltage specs to fit the actual range + * supported by this regulator before apply_uV (below) tries to + * force hardware to a value from that range: otherwise apply_uV + * can target a constraint value that doesn't correspond to any + * real voltage selector and fail registration outright, even + * though the clamping pass would have narrowed it to a value + * the regulator can actually hit. + */ + if (ops->list_voltage && rdev->desc->n_voltages) { + int count = rdev->desc->n_voltages; + int i; + int min_uV = INT_MAX; + int max_uV = INT_MIN; + int cmin = constraints->min_uV; + int cmax = constraints->max_uV; + + /* it's safe to autoconfigure fixed-voltage supplies + * and the constraints are used by list_voltage. + */ + if (count == 1 && !cmin) { + cmin = 1; + cmax = INT_MAX; + constraints->min_uV = cmin; + constraints->max_uV = cmax; + } + + /* voltage constraints are optional */ + if ((cmin == 0) && (cmax == 0)) { + /* nothing more to do */ + + /* else require explicit machine-level constraints */ + } else if (cmin <= 0 || cmax <= 0 || cmax < cmin) { + rdev_err(rdev, "invalid voltage constraints\n"); + return -EINVAL; + + /* no need to loop voltages if range is continuous */ + } else if (rdev->desc->continuous_voltage_range) { + /* nothing more to do */ + + } else { + /* initial: [cmin..cmax] valid, [min_uV..max_uV] not */ + for (i = 0; i < count; i++) { + int value; + + value = ops->list_voltage(rdev, i); + if (value <= 0) + continue; + + /* maybe adjust [min_uV..max_uV] */ + if (value >= cmin && value < min_uV) + min_uV = value; + if (value <= cmax && value > max_uV) + max_uV = value; + } + + /* final: [min_uV..max_uV] valid iff constraints valid */ + if (max_uV < min_uV) { + rdev_err(rdev, + "unsupportable voltage constraints %u-%uuV\n", + min_uV, max_uV); + return -EINVAL; + } + + /* use regulator's subset of machine constraints */ + if (constraints->min_uV < min_uV) { + rdev_dbg(rdev, "override min_uV, %d -> %d\n", + constraints->min_uV, min_uV); + constraints->min_uV = min_uV; + } + if (constraints->max_uV > max_uV) { + rdev_dbg(rdev, "override max_uV, %d -> %d\n", + constraints->max_uV, max_uV); + constraints->max_uV = max_uV; + } + } + } /* do we need to apply the constraint voltage */ - if (rdev->constraints->apply_uV && - rdev->constraints->min_uV && rdev->constraints->max_uV) { + if (apply_uV) { int target_min, target_max; int current_uV = regulator_get_voltage_rdev(rdev); @@ -1278,77 +1366,6 @@ static int machine_constraints_voltage(struct regulator_dev *rdev, } } - /* constrain machine-level voltage specs to fit - * the actual range supported by this regulator. - */ - if (ops->list_voltage && rdev->desc->n_voltages) { - int count = rdev->desc->n_voltages; - int i; - int min_uV = INT_MAX; - int max_uV = INT_MIN; - int cmin = constraints->min_uV; - int cmax = constraints->max_uV; - - /* it's safe to autoconfigure fixed-voltage supplies - * and the constraints are used by list_voltage. - */ - if (count == 1 && !cmin) { - cmin = 1; - cmax = INT_MAX; - constraints->min_uV = cmin; - constraints->max_uV = cmax; - } - - /* voltage constraints are optional */ - if ((cmin == 0) && (cmax == 0)) - return 0; - - /* else require explicit machine-level constraints */ - if (cmin <= 0 || cmax <= 0 || cmax < cmin) { - rdev_err(rdev, "invalid voltage constraints\n"); - return -EINVAL; - } - - /* no need to loop voltages if range is continuous */ - if (rdev->desc->continuous_voltage_range) - return 0; - - /* initial: [cmin..cmax] valid, [min_uV..max_uV] not */ - for (i = 0; i < count; i++) { - int value; - - value = ops->list_voltage(rdev, i); - if (value <= 0) - continue; - - /* maybe adjust [min_uV..max_uV] */ - if (value >= cmin && value < min_uV) - min_uV = value; - if (value <= cmax && value > max_uV) - max_uV = value; - } - - /* final: [min_uV..max_uV] valid iff constraints valid */ - if (max_uV < min_uV) { - rdev_err(rdev, - "unsupportable voltage constraints %u-%uuV\n", - min_uV, max_uV); - return -EINVAL; - } - - /* use regulator's subset of machine constraints */ - if (constraints->min_uV < min_uV) { - rdev_dbg(rdev, "override min_uV, %d -> %d\n", - constraints->min_uV, min_uV); - constraints->min_uV = min_uV; - } - if (constraints->max_uV > max_uV) { - rdev_dbg(rdev, "override max_uV, %d -> %d\n", - constraints->max_uV, max_uV); - constraints->max_uV = max_uV; - } - } - return 0; } From 6389eaf11d6cd3a66d10d0a5ea9cd91f242ccba2 Mon Sep 17 00:00:00 2001 From: Zhengyu He Date: Fri, 17 Jul 2026 07:45:26 -0700 Subject: [PATCH 142/456] spi: dt-bindings: spacemit: add K3 SPI compatible The K3 SPI controller is compatible with K1, so allow K3 device trees to use "spacemit,k1-spi" as fallback. Signed-off-by: Cody Kang Signed-off-by: Zhengyu He Link: https://patch.msgid.link/20260717-k3-com260-spi-v7-2-rc2-b4-preview-20260716-v1-2-969a1b0f783f@gmail.com Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml b/Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml index e82c7f8d0b98..5d86cc87e4fb 100644 --- a/Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml +++ b/Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml @@ -24,7 +24,11 @@ allOf: properties: compatible: - const: spacemit,k1-spi + oneOf: + - const: spacemit,k1-spi + - items: + - const: spacemit,k3-spi + - const: spacemit,k1-spi reg: maxItems: 1 From 88b8c6ae2ccb3ef9dbb04c8e13a4d1a98c42e922 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Tue, 23 Jun 2026 20:58:03 -0300 Subject: [PATCH 143/456] perf bench bpf: Add missing .gitignore file In 713eeb2279402758 ("perf build: Move BPF skeleton generation out of Makefile.perf") the bpf_skel used with 'perf bench uprobe' was moved from tools/perf/util/bpf_skel/ to tools/perf/bench/bpf_skel. Copy tools/perf/util/bpf_skel/.gitignore to that new directory so that files generated during build get ignored by git. Reported-by: Linus Torvalds Cc: Adrian Hunter Cc: Ian Rogers Cc: James Clark Cc: Jiri Olsa Cc: Namhyung Kim Fixes: 713eeb2279402758 ("perf build: Move BPF skeleton generation out of Makefile.perf") Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/bench/bpf_skel/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tools/perf/bench/bpf_skel/.gitignore diff --git a/tools/perf/bench/bpf_skel/.gitignore b/tools/perf/bench/bpf_skel/.gitignore new file mode 100644 index 000000000000..cd01455e1b53 --- /dev/null +++ b/tools/perf/bench/bpf_skel/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +.tmp +*.skel.h +vmlinux.h From 4a97144794920cb17e4a1c56c243edc757f42df9 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 25 Jun 2026 13:02:03 -0300 Subject: [PATCH 144/456] tools headers UAPI: Sync linux/const.h with the kernel sources To pick up the changes in: de9e2b3d88af3641 ("uapi: Provide DIV_ROUND_CLOSEST()") That just rebuilds perf, silencing this build warning. This addresses this perf build warning: Warning: Kernel ABI header differences: diff -u tools/include/uapi/linux/const.h include/uapi/linux/const.h Please see tools/include/uapi/README for further details. Cc: Cristian Ciocaltea Signed-off-by: Arnaldo Carvalho de Melo --- tools/include/uapi/linux/const.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tools/include/uapi/linux/const.h b/tools/include/uapi/linux/const.h index b8f629ef135f..565f309b9df8 100644 --- a/tools/include/uapi/linux/const.h +++ b/tools/include/uapi/linux/const.h @@ -50,4 +50,22 @@ #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +/* + * Divide positive or negative dividend by positive or negative divisor + * and round to closest integer. Result is undefined for negative + * divisors if the dividend variable type is unsigned and for negative + * dividends if the divisor variable type is unsigned. + */ +#define __KERNEL_DIV_ROUND_CLOSEST(x, divisor) \ +({ \ + __typeof__(x) __x = x; \ + __typeof__(divisor) __d = divisor; \ + \ + (((__typeof__(x))-1) > 0 || \ + ((__typeof__(divisor))-1) > 0 || \ + (((__x) > 0) == ((__d) > 0))) ? \ + (((__x) + ((__d) / 2)) / (__d)) : \ + (((__x) - ((__d) / 2)) / (__d)); \ +}) + #endif /* _UAPI_LINUX_CONST_H */ From edc148762eae074bc722ffb25c3a4dba5be33e50 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 25 Jun 2026 13:02:06 -0300 Subject: [PATCH 145/456] tools headers UAPI: Sync linux/rtnetlink.h with the kernel sources To pick up the changes in: b588019e85f49069 ("rtnetlink: add RTEXT_FILTER_NAME_ONLY support") That just rebuilds perf, silencing this build warning. This addresses this perf build warning: Warning: Kernel ABI header differences: diff -u tools/include/uapi/linux/rtnetlink.h include/uapi/linux/rtnetlink.h Please see tools/include/uapi/README for further details. Cc: Eric Dumazet Signed-off-by: Arnaldo Carvalho de Melo --- tools/include/uapi/linux/rtnetlink.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/include/uapi/linux/rtnetlink.h b/tools/include/uapi/linux/rtnetlink.h index dab9493c791b..27265fd31e5f 100644 --- a/tools/include/uapi/linux/rtnetlink.h +++ b/tools/include/uapi/linux/rtnetlink.h @@ -840,6 +840,7 @@ enum { #define RTEXT_FILTER_CFM_CONFIG (1 << 5) #define RTEXT_FILTER_CFM_STATUS (1 << 6) #define RTEXT_FILTER_MST (1 << 7) +#define RTEXT_FILTER_NAME_ONLY (1 << 8) /* End of information exported to user level */ From 5bbaca187c5e4f5afd7cb05cf40ce46046eac7f9 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 25 Jun 2026 13:02:08 -0300 Subject: [PATCH 146/456] tools headers: Sync linux/gfp_types.h with the kernel sources To pick up the changes in: b56ca146a2b27501 ("vmalloc: add __GFP_SKIP_KASAN support") 6a288a4ddb4a9944 ("mm/page_alloc: fix initialization of tags of the huge zero folio with init_on_free") That just rebuilds perf, silencing this build warning. This addresses this perf build warning: Warning: Kernel ABI header differences: diff -u tools/include/linux/gfp_types.h include/linux/gfp_types.h Please see tools/include/uapi/README for further details. Cc: David Hildenbrand (Arm) Cc: Muhammad Usama Anjum Signed-off-by: Arnaldo Carvalho de Melo --- tools/include/linux/gfp_types.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/include/linux/gfp_types.h b/tools/include/linux/gfp_types.h index 6c75df30a281..54ca0c88bab6 100644 --- a/tools/include/linux/gfp_types.h +++ b/tools/include/linux/gfp_types.h @@ -273,17 +273,17 @@ enum { * * %__GFP_ZERO returns a zeroed page on success. * - * %__GFP_ZEROTAGS zeroes memory tags at allocation time if the memory itself - * is being zeroed (either via __GFP_ZERO or via init_on_alloc, provided that - * __GFP_SKIP_ZERO is not set). This flag is intended for optimization: setting - * memory tags at the same time as zeroing memory has minimal additional - * performance impact. + * %__GFP_ZEROTAGS zeroes memory tags at allocation time. Setting memory tags at + * the same time as zeroing memory (e.g., with __GFP_ZERO) has minimal + * additional performance impact. However, __GFP_ZEROTAGS also zeroes the tags + * even if memory is not getting zeroed at allocation time (e.g., + * with init_on_free). * * %__GFP_SKIP_KASAN makes KASAN skip unpoisoning on page allocation. * Used for userspace and vmalloc pages; the latter are unpoisoned by - * kasan_unpoison_vmalloc instead. For userspace pages, results in - * poisoning being skipped as well, see should_skip_kasan_poison for - * details. Only effective in HW_TAGS mode. + * kasan_unpoison_vmalloc instead. If passed to vmalloc, kasan_unpoison_vmalloc + * is skipped too. For userspace pages, results in poisoning being skipped as + * well, see should_skip_kasan_poison for details. Only effective in HW_TAGS mode. */ #define __GFP_NOWARN ((__force gfp_t)___GFP_NOWARN) #define __GFP_COMP ((__force gfp_t)___GFP_COMP) From e619443bcc393f4e1c249393d19e6ec837f5c424 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 25 Jun 2026 13:02:10 -0300 Subject: [PATCH 147/456] tools headers x86 cpufeatures: Sync with the kernel sources To pick up the changes in: 5ce9cc5a232b9928 ("coco/tdx-host: Don't expose P-SEAMLDR information on CPUs with erratum") 7658b9343a8f65e5 ("KVM: SVM: add GMET bit definitions") de0bfdc7137d5132 ("KVM: x86: Advertise AVX512 Bit Matrix Multiply (BMM) to userspace") That just rebuilds perf, silencing this build warning. This addresses this perf build warning: Warning: Kernel ABI header differences: diff -u tools/arch/x86/include/asm/cpufeatures.h arch/x86/include/asm/cpufeatures.h Please see tools/include/uapi/README for further details. Cc: Chao Gao Cc: Nikunj A Dadhania Cc: Paolo Bonzini Signed-off-by: Arnaldo Carvalho de Melo --- tools/arch/x86/include/asm/cpufeatures.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/arch/x86/include/asm/cpufeatures.h b/tools/arch/x86/include/asm/cpufeatures.h index 86d17b195e79..1b4a48bff18f 100644 --- a/tools/arch/x86/include/asm/cpufeatures.h +++ b/tools/arch/x86/include/asm/cpufeatures.h @@ -379,6 +379,7 @@ #define X86_FEATURE_AVIC (15*32+13) /* "avic" Virtual Interrupt Controller */ #define X86_FEATURE_V_VMSAVE_VMLOAD (15*32+15) /* "v_vmsave_vmload" Virtual VMSAVE VMLOAD */ #define X86_FEATURE_VGIF (15*32+16) /* "vgif" Virtual GIF */ +#define X86_FEATURE_GMET (15*32+17) /* Guest Mode Execution Trap */ #define X86_FEATURE_X2AVIC (15*32+18) /* "x2avic" Virtual x2apic */ #define X86_FEATURE_V_SPEC_CTRL (15*32+20) /* "v_spec_ctrl" Virtual SPEC_CTRL */ #define X86_FEATURE_VNMI (15*32+25) /* "vnmi" Virtual NMI */ @@ -473,6 +474,7 @@ #define X86_FEATURE_GP_ON_USER_CPUID (20*32+17) /* User CPUID faulting */ #define X86_FEATURE_PREFETCHI (20*32+20) /* Prefetch Data/Instruction to Cache Level */ +#define X86_FEATURE_AVX512_BMM (20*32+23) /* AVX512 Bit Matrix Multiply instructions */ #define X86_FEATURE_ERAPS (20*32+24) /* Enhanced Return Address Predictor Security */ #define X86_FEATURE_SBPB (20*32+27) /* Selective Branch Prediction Barrier */ #define X86_FEATURE_IBPB_BRTYPE (20*32+28) /* MSR_PRED_CMD[IBPB] flushes all branch type predictions */ @@ -572,4 +574,5 @@ #define X86_BUG_ITS_NATIVE_ONLY X86_BUG( 1*32+ 8) /* "its_native_only" CPU is affected by ITS, VMX is not affected */ #define X86_BUG_TSA X86_BUG( 1*32+ 9) /* "tsa" CPU is affected by Transient Scheduler Attacks */ #define X86_BUG_VMSCAPE X86_BUG( 1*32+10) /* "vmscape" CPU is affected by VMSCAPE attacks from guests */ +#define X86_BUG_SEAMRET_INVD_VMCS X86_BUG( 1*32+11) /* "seamret_invd_vmcs" SEAMRET from P-SEAMLDR clears the current VMCS */ #endif /* _ASM_X86_CPUFEATURES_H */ From df3f28cd6861dcbdaab2d3025048eaaf950b1486 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 25 Jun 2026 13:02:13 -0300 Subject: [PATCH 148/456] tools arch x86: Sync the msr-index.h copy with the kernel sources To pick up the changes in: e93a93f11490992a ("KVM: x86: Virtualize AMD CPUID faulting") That introduce these new entries in the string tables: $ tools/perf/trace/beauty/tracepoints/x86_msr.sh > before.txt $ cp arch/x86/include/asm/msr-index.h tools/arch/x86/include/asm/msr-index.h $ tools/perf/trace/beauty/tracepoints/x86_msr.sh > after.txt $ diff -u before.txt after.txt @@ -349,6 +349,7 @@ [0xc00102b2 - x86_AMD_V_KVM_MSRs_offset] = "AMD_CPPC_CAP2", [0xc00102b3 - x86_AMD_V_KVM_MSRs_offset] = "AMD_CPPC_REQ", [0xc00102b4 - x86_AMD_V_KVM_MSRs_offset] = "AMD_CPPC_STATUS", + [0xc00102b5 - x86_AMD_V_KVM_MSRs_offset] = "AMD_CPPC_REQ2", [0xc00102f0 - x86_AMD_V_KVM_MSRs_offset] = "AMD_PPIN_CTL", [0xc00102f1 - x86_AMD_V_KVM_MSRs_offset] = "AMD_PPIN", [0xc0010300 - x86_AMD_V_KVM_MSRs_offset] = "AMD_SAMP_BR_FROM", This addresses this perf build warning: Warning: Kernel ABI header differences: diff -u tools/arch/x86/include/asm/msr-index.h arch/x86/include/asm/msr-index.h Please see tools/include/uapi/README for further details. Cc: Jim Mattson Signed-off-by: Arnaldo Carvalho de Melo --- tools/arch/x86/include/asm/msr-index.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/arch/x86/include/asm/msr-index.h b/tools/arch/x86/include/asm/msr-index.h index eff29645719b..18c4be75e927 100644 --- a/tools/arch/x86/include/asm/msr-index.h +++ b/tools/arch/x86/include/asm/msr-index.h @@ -674,6 +674,9 @@ #define MSR_AMD64_DC_CFG 0xc0011022 #define MSR_AMD64_TW_CFG 0xc0011023 +#define MSR_AMD64_FP_CFG 0xc0011028 +#define MSR_AMD64_FP_CFG_ZEN1_DENORM_FIX_BIT 9 + #define MSR_AMD64_DE_CFG 0xc0011029 #define MSR_AMD64_DE_CFG_LFENCE_SERIALIZE_BIT 1 #define MSR_AMD64_DE_CFG_LFENCE_SERIALIZE BIT_ULL(MSR_AMD64_DE_CFG_LFENCE_SERIALIZE_BIT) @@ -698,6 +701,8 @@ #define MSR_AMD64_IBSBRTARGET 0xc001103b #define MSR_AMD64_ICIBSEXTDCTL 0xc001103c #define MSR_AMD64_IBSOPDATA4 0xc001103d +#define MSR_AMD64_IBSOPCTL2 0xc001103e +#define MSR_AMD64_IBSFETCHCTL2 0xc001103f #define MSR_AMD64_IBS_REG_COUNT_MAX 8 /* includes MSR_AMD64_IBSBRTARGET */ #define MSR_AMD64_SVM_AVIC_DOORBELL 0xc001011b #define MSR_AMD64_VM_PAGE_FLUSH 0xc001011e @@ -765,12 +770,14 @@ #define MSR_AMD_CPPC_CAP2 0xc00102b2 #define MSR_AMD_CPPC_REQ 0xc00102b3 #define MSR_AMD_CPPC_STATUS 0xc00102b4 +#define MSR_AMD_CPPC_REQ2 0xc00102b5 /* Masks for use with MSR_AMD_CPPC_CAP1 */ #define AMD_CPPC_LOWEST_PERF_MASK GENMASK(7, 0) #define AMD_CPPC_LOWNONLIN_PERF_MASK GENMASK(15, 8) #define AMD_CPPC_NOMINAL_PERF_MASK GENMASK(23, 16) #define AMD_CPPC_HIGHEST_PERF_MASK GENMASK(31, 24) +#define AMD_CPPC_FLOOR_PERF_CNT_MASK GENMASK_ULL(39, 32) /* Masks for use with MSR_AMD_CPPC_REQ */ #define AMD_CPPC_MAX_PERF_MASK GENMASK(7, 0) @@ -778,6 +785,9 @@ #define AMD_CPPC_DES_PERF_MASK GENMASK(23, 16) #define AMD_CPPC_EPP_PERF_MASK GENMASK(31, 24) +/* Masks for use with MSR_AMD_CPPC_REQ2 */ +#define AMD_CPPC_FLOOR_PERF_MASK GENMASK(7, 0) + /* AMD Performance Counter Global Status and Control MSRs */ #define MSR_AMD64_PERF_CNTR_GLOBAL_STATUS 0xc0000300 #define MSR_AMD64_PERF_CNTR_GLOBAL_CTL 0xc0000301 @@ -889,6 +899,7 @@ #define MSR_K7_HWCR_IRPERF_EN_BIT 30 #define MSR_K7_HWCR_IRPERF_EN BIT_ULL(MSR_K7_HWCR_IRPERF_EN_BIT) #define MSR_K7_HWCR_CPUID_USER_DIS_BIT 35 +#define MSR_K7_HWCR_CPUID_USER_DIS BIT_ULL(MSR_K7_HWCR_CPUID_USER_DIS_BIT) #define MSR_K7_FID_VID_CTL 0xc0010041 #define MSR_K7_FID_VID_STATUS 0xc0010042 #define MSR_K7_HWCR_CPB_DIS_BIT 25 From 34ab6d10f82f56d18a0840b89d1c2e8e72216cb5 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 25 Jun 2026 13:02:13 -0300 Subject: [PATCH 149/456] tools headers: Sync UAPI drm/drm.h with kernel sources To pick up the changes in: a1b6cf8e5e7e9102 ("drm: uapi: Use SPDX in DRM core uAPI headers") dc2d30e7db8321a6 ("drm/doc: document DRM_IOCTL_SYNCOBJ_EVENTFD") That don't result in changes to the string tables generated from this header. This addresses this perf build warning: Warning: Kernel ABI header differences: diff -u tools/perf/trace/beauty/include/uapi/drm/drm.h include/uapi/drm/drm.h Please see tools/include/uapi/README for further details. Cc: Laurent Pinchart Cc: Simon Ser Signed-off-by: Arnaldo Carvalho de Melo --- .../perf/trace/beauty/include/uapi/drm/drm.h | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/tools/perf/trace/beauty/include/uapi/drm/drm.h b/tools/perf/trace/beauty/include/uapi/drm/drm.h index 27cc159c1d27..bc7ef7684099 100644 --- a/tools/perf/trace/beauty/include/uapi/drm/drm.h +++ b/tools/perf/trace/beauty/include/uapi/drm/drm.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: MIT */ /* * Header for the Direct Rendering Manager * @@ -11,25 +12,6 @@ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef _DRM_H_ @@ -1323,6 +1305,13 @@ extern "C" { */ #define DRM_IOCTL_MODE_GETFB2 DRM_IOWR(0xCE, struct drm_mode_fb_cmd2) +/** + * DRM_IOCTL_SYNCOBJ_EVENTFD - Register an eventfd to be signalled by a syncobj. + * + * This can be used to integrate a syncobj in an event loop. + * + * The IOCTL argument is a struct drm_syncobj_eventfd. + */ #define DRM_IOCTL_SYNCOBJ_EVENTFD DRM_IOWR(0xCF, struct drm_syncobj_eventfd) /** From dd1e6fb8ffdfe71b6f9812b1e5add25a48f231a5 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 25 Jun 2026 13:02:13 -0300 Subject: [PATCH 150/456] perf beauty: Update copy of linux/socket.h with the kernel sources To pick up the changes in: 4987a5763fd5ab72 ("net: block MSG_NO_SHARED_FRAGS in sendmsg()") That don't result in changes to the string tables generated from this header. This addresses this perf build warning: Warning: Kernel ABI header differences: diff -u tools/perf/trace/beauty/include/linux/socket.h include/linux/socket.h Please see tools/include/uapi/README for further details. Cc: Jann Horn Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/trace/beauty/include/linux/socket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/perf/trace/beauty/include/linux/socket.h b/tools/perf/trace/beauty/include/linux/socket.h index 3ffdfe184b23..2a8d7b14f1d1 100644 --- a/tools/perf/trace/beauty/include/linux/socket.h +++ b/tools/perf/trace/beauty/include/linux/socket.h @@ -356,7 +356,7 @@ struct ucred { /* Flags to be cleared on entry by sendmsg and sendmmsg syscalls */ #define MSG_INTERNAL_SENDMSG_FLAGS \ - (MSG_SPLICE_PAGES | MSG_SENDPAGE_NOPOLICY | MSG_SENDPAGE_DECRYPTED) + (MSG_SPLICE_PAGES | MSG_SENDPAGE_NOPOLICY | MSG_SENDPAGE_DECRYPTED | MSG_NO_SHARED_FRAGS) /* Setsockoptions(2) level. Thanks to BSD these must match IPPROTO_xxx */ #define SOL_IP 0 From 42bfbb3e8d1ce4611c407c0f9d0d2ffea51b67b9 Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 25 Jun 2026 13:02:13 -0300 Subject: [PATCH 151/456] tools headers: Sync UAPI linux/fs.h with the kernel sources To pick up the changes in: 45e57cfb7b10b64f ("fs: Clarify FS_CASEFOLD_FL semantics in UAPI header") That don't result in changes to the string tables generated from this header. This addresses this perf build warning: Warning: Kernel ABI header differences: diff -u tools/perf/trace/beauty/include/uapi/linux/fs.h include/uapi/linux/fs.h Please see tools/include/uapi/README for further details. Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/trace/beauty/include/uapi/linux/fs.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/perf/trace/beauty/include/uapi/linux/fs.h b/tools/perf/trace/beauty/include/uapi/linux/fs.h index 2ea4c81df08f..bd87262f2e34 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/fs.h +++ b/tools/perf/trace/beauty/include/uapi/linux/fs.h @@ -395,7 +395,16 @@ struct file_attr { #define FS_DAX_FL 0x02000000 /* Inode is DAX */ #define FS_INLINE_DATA_FL 0x10000000 /* Reserved for ext4 */ #define FS_PROJINHERIT_FL 0x20000000 /* Create with parents projid */ -#define FS_CASEFOLD_FL 0x40000000 /* Folder is case insensitive */ +/* + * FS_CASEFOLD_FL indicates case-insensitive name lookup. The + * bit is most often reported on directories, where it controls + * lookups of entries within. Filesystems that derive + * case-insensitivity from mount or volume state may also report + * it on non-directory inodes; userspace must not assume the bit + * is directory-only. FS_XFLAG_CASEFOLD reports the same + * information read-only via FS_IOC_FSGETXATTR. + */ +#define FS_CASEFOLD_FL 0x40000000 #define FS_RESERVED_FL 0x80000000 /* reserved for ext2 lib */ #define FS_FL_USER_VISIBLE 0x0003DFFF /* User visible flags */ From fac520e43a60230b24026f462a2b63e4d170566e Mon Sep 17 00:00:00 2001 From: Arnaldo Carvalho de Melo Date: Thu, 25 Jun 2026 13:02:16 -0300 Subject: [PATCH 152/456] tools headers: Sync KVM headers with the kernel sources To pick up the changes in: 103ff3a50e3a50a9 ("KVM: s390: Add capability to support 2G hugepages") 229132c309d667bb ("LoongArch: KVM: Add DMSINTC device support") 2619da73bb2f10d8 ("KVM: x86: Use __DECLARE_FLEX_ARRAY() for UAPI structures with VLAs") 4aebd7d5c72f805e ("KVM: s390: Add KVM capability for ESA mode guests") 4f256d5770febb9d ("KVM: x86: nSVM: Save/restore gPAT with KVM_{GET,SET}_NESTED_STATE") 822790ab01495d67 ("KVM: x86: Define KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT") 8800dbf6614aad10 ("KVM: arm64: Allow userspace to create protected VMs when pKVM is enabled") bf8f3cec939db68e ("KVM: s390: vsie: Refactor handle_stfle") c547c51ff4d44c78 ("KVM: arm64: gic-v5: Add ARM_VGIC_V5 device to KVM headers") d7507a94a0720223 ("KVM: SVM: Treat exit_code as an unsigned 64-bit value through all of KVM") This addresses these perf build warnings: Warning: Kernel ABI header differences: diff -u tools/include/uapi/linux/kvm.h include/uapi/linux/kvm.h diff -u tools/arch/x86/include/uapi/asm/kvm.h arch/x86/include/uapi/asm/kvm.h diff -u tools/arch/x86/include/uapi/asm/svm.h arch/x86/include/uapi/asm/svm.h diff -u tools/arch/s390/include/uapi/asm/kvm.h arch/s390/include/uapi/asm/kvm.h Please see tools/include/uapi/README for further details. Signed-off-by: Arnaldo Carvalho de Melo --- tools/arch/s390/include/uapi/asm/kvm.h | 1 + tools/arch/x86/include/uapi/asm/kvm.h | 14 ++++++----- tools/arch/x86/include/uapi/asm/svm.h | 32 +++++++++++++------------- tools/include/uapi/linux/kvm.h | 20 ++++++++++++---- 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/tools/arch/s390/include/uapi/asm/kvm.h b/tools/arch/s390/include/uapi/asm/kvm.h index 60345dd2cba2..4192769b5ce0 100644 --- a/tools/arch/s390/include/uapi/asm/kvm.h +++ b/tools/arch/s390/include/uapi/asm/kvm.h @@ -444,6 +444,7 @@ struct kvm_s390_vm_cpu_machine { #define KVM_S390_VM_CPU_FEAT_PFMFI 11 #define KVM_S390_VM_CPU_FEAT_SIGPIF 12 #define KVM_S390_VM_CPU_FEAT_KSS 13 +#define KVM_S390_VM_CPU_FEAT_ASTFLEIE2 14 struct kvm_s390_vm_cpu_feat { __u64 feat[16]; }; diff --git a/tools/arch/x86/include/uapi/asm/kvm.h b/tools/arch/x86/include/uapi/asm/kvm.h index 0d4538fa6c31..1585ec804066 100644 --- a/tools/arch/x86/include/uapi/asm/kvm.h +++ b/tools/arch/x86/include/uapi/asm/kvm.h @@ -197,13 +197,13 @@ struct kvm_msrs { __u32 nmsrs; /* number of msrs in entries */ __u32 pad; - struct kvm_msr_entry entries[]; + __DECLARE_FLEX_ARRAY(struct kvm_msr_entry, entries); }; /* for KVM_GET_MSR_INDEX_LIST */ struct kvm_msr_list { __u32 nmsrs; /* number of msrs in entries */ - __u32 indices[]; + __DECLARE_FLEX_ARRAY(__u32, indices); }; /* Maximum size of any access bitmap in bytes */ @@ -245,7 +245,7 @@ struct kvm_cpuid_entry { struct kvm_cpuid { __u32 nent; __u32 padding; - struct kvm_cpuid_entry entries[]; + __DECLARE_FLEX_ARRAY(struct kvm_cpuid_entry, entries); }; struct kvm_cpuid_entry2 { @@ -267,7 +267,7 @@ struct kvm_cpuid_entry2 { struct kvm_cpuid2 { __u32 nent; __u32 padding; - struct kvm_cpuid_entry2 entries[]; + __DECLARE_FLEX_ARRAY(struct kvm_cpuid_entry2, entries); }; /* for KVM_GET_PIT and KVM_SET_PIT */ @@ -398,7 +398,7 @@ struct kvm_xsave { * the contents of CPUID leaf 0xD on the host. */ __u32 region[1024]; - __u32 extra[]; + __DECLARE_FLEX_ARRAY(__u32, extra); }; #define KVM_MAX_XCRS 16 @@ -477,6 +477,7 @@ struct kvm_sync_regs { #define KVM_X86_QUIRK_STUFF_FEATURE_MSRS (1 << 8) #define KVM_X86_QUIRK_IGNORE_GUEST_PAT (1 << 9) #define KVM_X86_QUIRK_VMCS12_ALLOW_FREEZE_IN_SMM (1 << 10) +#define KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT (1 << 11) #define KVM_STATE_NESTED_FORMAT_VMX 0 #define KVM_STATE_NESTED_FORMAT_SVM 1 @@ -532,6 +533,7 @@ struct kvm_svm_nested_state_data { struct kvm_svm_nested_state_hdr { __u64 vmcb_pa; + __u64 gpat; }; /* for KVM_CAP_NESTED_STATE */ @@ -566,7 +568,7 @@ struct kvm_pmu_event_filter { __u32 fixed_counter_bitmap; __u32 flags; __u32 pad[4]; - __u64 events[]; + __DECLARE_FLEX_ARRAY(__u64, events); }; #define KVM_PMU_EVENT_ALLOW 0 diff --git a/tools/arch/x86/include/uapi/asm/svm.h b/tools/arch/x86/include/uapi/asm/svm.h index 650e3256ea7d..010a45c9f614 100644 --- a/tools/arch/x86/include/uapi/asm/svm.h +++ b/tools/arch/x86/include/uapi/asm/svm.h @@ -103,38 +103,38 @@ #define SVM_EXIT_VMGEXIT 0x403 /* SEV-ES software-defined VMGEXIT events */ -#define SVM_VMGEXIT_MMIO_READ 0x80000001 -#define SVM_VMGEXIT_MMIO_WRITE 0x80000002 -#define SVM_VMGEXIT_NMI_COMPLETE 0x80000003 -#define SVM_VMGEXIT_AP_HLT_LOOP 0x80000004 -#define SVM_VMGEXIT_AP_JUMP_TABLE 0x80000005 +#define SVM_VMGEXIT_MMIO_READ 0x80000001ull +#define SVM_VMGEXIT_MMIO_WRITE 0x80000002ull +#define SVM_VMGEXIT_NMI_COMPLETE 0x80000003ull +#define SVM_VMGEXIT_AP_HLT_LOOP 0x80000004ull +#define SVM_VMGEXIT_AP_JUMP_TABLE 0x80000005ull #define SVM_VMGEXIT_SET_AP_JUMP_TABLE 0 #define SVM_VMGEXIT_GET_AP_JUMP_TABLE 1 -#define SVM_VMGEXIT_PSC 0x80000010 -#define SVM_VMGEXIT_GUEST_REQUEST 0x80000011 -#define SVM_VMGEXIT_EXT_GUEST_REQUEST 0x80000012 -#define SVM_VMGEXIT_AP_CREATION 0x80000013 +#define SVM_VMGEXIT_PSC 0x80000010ull +#define SVM_VMGEXIT_GUEST_REQUEST 0x80000011ull +#define SVM_VMGEXIT_EXT_GUEST_REQUEST 0x80000012ull +#define SVM_VMGEXIT_AP_CREATION 0x80000013ull #define SVM_VMGEXIT_AP_CREATE_ON_INIT 0 #define SVM_VMGEXIT_AP_CREATE 1 #define SVM_VMGEXIT_AP_DESTROY 2 -#define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018 -#define SVM_VMGEXIT_SAVIC 0x8000001a +#define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018ull +#define SVM_VMGEXIT_SAVIC 0x8000001aull #define SVM_VMGEXIT_SAVIC_REGISTER_GPA 0 #define SVM_VMGEXIT_SAVIC_UNREGISTER_GPA 1 #define SVM_VMGEXIT_SAVIC_SELF_GPA ~0ULL -#define SVM_VMGEXIT_HV_FEATURES 0x8000fffd -#define SVM_VMGEXIT_TERM_REQUEST 0x8000fffe +#define SVM_VMGEXIT_HV_FEATURES 0x8000fffdull +#define SVM_VMGEXIT_TERM_REQUEST 0x8000fffeull #define SVM_VMGEXIT_TERM_REASON(reason_set, reason_code) \ /* SW_EXITINFO1[3:0] */ \ (((((u64)reason_set) & 0xf)) | \ /* SW_EXITINFO1[11:4] */ \ ((((u64)reason_code) & 0xff) << 4)) -#define SVM_VMGEXIT_UNSUPPORTED_EVENT 0x8000ffff +#define SVM_VMGEXIT_UNSUPPORTED_EVENT 0x8000ffffull /* Exit code reserved for hypervisor/software use */ -#define SVM_EXIT_SW 0xf0000000 +#define SVM_EXIT_SW 0xf0000000ull -#define SVM_EXIT_ERR -1 +#define SVM_EXIT_ERR -1ull #define SVM_EXIT_REASONS \ { SVM_EXIT_READ_CR0, "read_cr0" }, \ diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h index d0c0c8605976..419011097fa8 100644 --- a/tools/include/uapi/linux/kvm.h +++ b/tools/include/uapi/linux/kvm.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -542,7 +543,7 @@ struct kvm_coalesced_mmio { struct kvm_coalesced_mmio_ring { __u32 first, last; - struct kvm_coalesced_mmio coalesced_mmio[]; + __DECLARE_FLEX_ARRAY(struct kvm_coalesced_mmio, coalesced_mmio); }; #define KVM_COALESCED_MMIO_MAX \ @@ -592,7 +593,7 @@ struct kvm_clear_dirty_log { /* for KVM_SET_SIGNAL_MASK */ struct kvm_signal_mask { __u32 len; - __u8 sigset[]; + __DECLARE_FLEX_ARRAY(__u8, sigset); }; /* for KVM_TPR_ACCESS_REPORTING */ @@ -703,6 +704,11 @@ struct kvm_enable_cap { #define KVM_VM_TYPE_ARM_IPA_SIZE_MASK 0xffULL #define KVM_VM_TYPE_ARM_IPA_SIZE(x) \ ((x) & KVM_VM_TYPE_ARM_IPA_SIZE_MASK) + +#define KVM_VM_TYPE_ARM_PROTECTED (1UL << 31) +#define KVM_VM_TYPE_ARM_MASK (KVM_VM_TYPE_ARM_IPA_SIZE_MASK | \ + KVM_VM_TYPE_ARM_PROTECTED) + /* * ioctls for /dev/kvm fds: */ @@ -989,6 +995,8 @@ struct kvm_enable_cap { #define KVM_CAP_ARM_SEA_TO_USER 245 #define KVM_CAP_S390_USER_OPEREXEC 246 #define KVM_CAP_S390_KEYOP 247 +#define KVM_CAP_S390_VSIE_ESAMODE 248 +#define KVM_CAP_S390_HPAGE_2G 249 struct kvm_irq_routing_irqchip { __u32 irqchip; @@ -1051,7 +1059,7 @@ struct kvm_irq_routing_entry { struct kvm_irq_routing { __u32 nr; __u32 flags; - struct kvm_irq_routing_entry entries[]; + __DECLARE_FLEX_ARRAY(struct kvm_irq_routing_entry, entries); }; #define KVM_IRQFD_FLAG_DEASSIGN (1 << 0) @@ -1142,7 +1150,7 @@ struct kvm_dirty_tlb { struct kvm_reg_list { __u64 n; /* number of regs */ - __u64 reg[]; + __DECLARE_FLEX_ARRAY(__u64, reg); }; struct kvm_one_reg { @@ -1224,6 +1232,8 @@ enum kvm_device_type { #define KVM_DEV_TYPE_LOONGARCH_EIOINTC KVM_DEV_TYPE_LOONGARCH_EIOINTC KVM_DEV_TYPE_LOONGARCH_PCHPIC, #define KVM_DEV_TYPE_LOONGARCH_PCHPIC KVM_DEV_TYPE_LOONGARCH_PCHPIC + KVM_DEV_TYPE_LOONGARCH_DMSINTC, +#define KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_ARM_VGIC_V5, #define KVM_DEV_TYPE_ARM_VGIC_V5 KVM_DEV_TYPE_ARM_VGIC_V5 @@ -1610,7 +1620,7 @@ struct kvm_stats_desc { #ifdef __KERNEL__ char name[KVM_STATS_NAME_SIZE]; #else - char name[]; + __DECLARE_FLEX_ARRAY(char, name); #endif }; From e8a8d54c2d508891c142a928fc7d298c4c8bd0dd Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Tue, 21 Jul 2026 18:55:51 -0500 Subject: [PATCH 153/456] cifs: prevent readdir from changing file size due to stale directory metadata Windows Server's directory enumeration metadata lags behind the actual file size after a write+close or rename. A concurrent readdir() in the window between close() returning to userspace and stat() being called overwrites the correct cached i_size with the stale server value, causing stat() to return the wrong size. Once _cifsFileInfo_put() removes the last writable handle from openFileList, is_size_safe_to_change() permits readdir to overwrite i_size. smb2_close_getattr() then stamps cifs_i->time = jiffies, making the corrupt cached value appear fresh to the next stat(). The existing check (see Fixes:) only blocked stale size updates while an active RW lease was held, not after the last writable handle closes. Add cifsInodeInfo->time_last_write, written via smp_store_release() at writable close and on setattr/truncate. is_size_safe_to_change() checks is_inode_writable() first (acquiring open_file_lock), then rejects a readdir size update if time_last_write falls within acregmax jiffies. The spinlock release in _cifsFileInfo_put() forms a store-release barrier that pairs with the spin_lock() (load-acquire) in is_inode_writable(), ensuring the subsequent smp_load_acquire() on time_last_write observes any update from a concurrent close(). When a size update is rejected and the server value differs from the cached one, cifs_i->time is cleared to force a fresh QUERY_INFO on the next stat(). readdir is also blocked from changing i_size while writable handles are open or an RW lease is held, even on direct-IO mounts. For deferred close (closetimeo > 0), time_last_write is refreshed at the actual server close in smb2_deferred_work_close() and in the cifs_close_deferred_file*() drain paths invoked by lease/oplock breaks and tcon teardown, anchoring the protection window to the real close time rather than the earlier userspace close. time_last_write == 0 skips the time_before() check to avoid false positives near boot on 32-bit systems where jiffies starts close to INITIAL_JIFFIES. Does not reproduce against Samba or with actimeo=0. Fixes: e4b61f3b1c67 ("cifs: prevent updating file size from server if we have a read/write lease") Signed-off-by: Frank Sorenson Signed-off-by: Steve French --- fs/smb/client/cifsfs.c | 1 + fs/smb/client/cifsglob.h | 1 + fs/smb/client/file.c | 69 +++++++++++++++++++++++++++++++++++++--- fs/smb/client/inode.c | 6 ++++ fs/smb/client/misc.c | 27 ++++++++++++++-- 5 files changed, 96 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 66b9104e7ca2..1788d93a2522 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -440,6 +440,7 @@ cifs_alloc_inode(struct super_block *sb) return NULL; cifs_inode->cifsAttrs = ATTR_ARCHIVE; /* default */ cifs_inode->time = 0; + cifs_inode->time_last_write = 0; /* * Until the file is open and we have gotten oplock info back from the * server, can not assume caching of file data or metadata. diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 08e94633a9c1..79e4e84f8985 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -1566,6 +1566,7 @@ struct cifsInodeInfo { spinlock_t writers_lock; unsigned int writers; /* Number of writers on this inode */ unsigned long time; /* jiffies of last update of inode */ + unsigned long time_last_write; /* jiffies of last writable close or truncate */ u64 uniqueid; /* server inode number */ u64 createtime; /* creation time on server */ __u8 lease_key[SMB2_LEASE_KEY_SIZE]; /* lease key for this inode */ diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 968740e7c9c3..b279a44be729 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1423,11 +1423,21 @@ void smb2_deferred_work_close(struct work_struct *work) { struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo, deferred.work); + struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry)); - spin_lock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock); + spin_lock(&cinode->deferred_lock); cifs_del_deferred_close(cfile); cfile->deferred_close_scheduled = false; - spin_unlock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock); + spin_unlock(&cinode->deferred_lock); + /* + * Refresh time_last_write immediately before the actual server close + * so the protection window is anchored to the real close time, not + * the earlier userspace close time stored by cifs_close(). + */ + if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&cinode->time_last_write, jiffies); + } _cifsFileInfo_put(cfile, true, false); } @@ -1457,6 +1467,10 @@ int cifs_close(struct inode *inode, struct file *file) if (file->private_data != NULL) { cfile = file->private_data; file->private_data = NULL; + if (file->f_mode & FMODE_WRITE) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&cinode->time_last_write, jiffies); + } dclose = kmalloc_obj(struct cifs_deferred_close); if ((cfile->status_file_deleted == false) && (smb2_can_defer_close(inode, dclose))) { @@ -3225,13 +3239,26 @@ static int is_inode_writable(struct cifsInodeInfo *cifs_inode) bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file, bool from_readdir) { + struct cifs_sb_info *cifs_sb; + unsigned long tlw; + if (!cifsInode) return true; + cifs_sb = CIFS_SB(cifsInode); + if (is_inode_writable(cifsInode) || ((cifsInode->oplock & CIFS_CACHE_RW_FLG) != 0 && from_readdir)) { /* This inode is open for write at least once */ - struct cifs_sb_info *cifs_sb = CIFS_SB(cifsInode); + + /* + * Readdir data is unreliable when we have writable handles or + * an exclusive lease -- never allow it to change i_size, even + * on direct-IO mounts where the server's directory metadata + * can still lag behind the actual file state. + */ + if (from_readdir) + return false; if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_DIRECT_IO) { /* since no page cache to corrupt on directio @@ -3243,8 +3270,40 @@ bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file, return true; return false; - } else - return true; + } + + /* + * No writable handles open. Check whether we are within the attribute + * cache validity window of a recent local modification. + * + * For the close() path: cifs_close() calls smp_store_release() on + * time_last_write before _cifsFileInfo_put() removes the handle under + * open_file_lock. That spin_unlock() is a store-release that pairs + * with the spin_lock() (load-acquire) in is_inode_writable() above, + * so if is_inode_writable() returned false the smp_load_acquire() + * below is guaranteed to observe any time_last_write update from a + * concurrent close(). + * + * For the setattr/truncate paths: those callers use smp_store_release() + * directly; the smp_load_acquire() below pairs with that store. There + * is no shared lock between setattr and readdir, so this relies on + * acquire-release semantics alone. The store propagation latency on + * weakly-ordered architectures (nanoseconds) is negligible relative to + * the acregmax window (seconds) and the readdir RPC round-trip + * (milliseconds), making this a sound design choice in practice. + * + * time_last_write == 0 means the inode has never been written locally; + * skip the window check to avoid false positives near boot time when + * jiffies is still close to INITIAL_JIFFIES on 32-bit systems. + */ + if (from_readdir) { + /* Pairs with smp_store_release() at close and truncate sites. */ + tlw = smp_load_acquire(&cifsInode->time_last_write); + if (tlw && time_before(jiffies, tlw + cifs_sb->ctx->acregmax)) + return false; + } + + return true; } void cifs_oplock_break(struct work_struct *work) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index deed04dd9b91..b2806371bfde 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -237,6 +237,8 @@ cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr, if (is_size_safe_to_change(cifs_i, fattr->cf_eof, from_readdir)) { i_size_write(inode, fattr->cf_eof); inode->i_blocks = CIFS_INO_BLOCKS(fattr->cf_bytes); + } else if (from_readdir && i_size_read(inode) != fattr->cf_eof) { + cifs_i->time = 0; } if (S_ISLNK(fattr->cf_mode) && fattr->cf_symlink_target) { @@ -3277,6 +3279,8 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) if ((attrs->ia_valid & ATTR_SIZE) && attrs->ia_size != i_size_read(inode)) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&cifsInode->time_last_write, jiffies); truncate_setsize(inode, attrs->ia_size); netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true); fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size); @@ -3478,6 +3482,8 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) if ((attrs->ia_valid & ATTR_SIZE) && attrs->ia_size != i_size_read(inode)) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&cifsInode->time_last_write, jiffies); truncate_setsize(inode, attrs->ia_size); netfs_resize_file(&cifsInode->netfs, attrs->ia_size, true); fscache_resize_cookie(cifs_inode_cookie(inode), attrs->ia_size); diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index b9c59b2cf76a..fa8cfc4d97f3 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -524,7 +524,14 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode) spin_unlock(&cifs_inode->open_file_lock); list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) { - _cifsFileInfo_put(tmp_list->cfile, false, false); + struct cifsFileInfo *cfile = tmp_list->cfile; + + if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&CIFS_I(d_inode(cfile->dentry))->time_last_write, + jiffies); + } + _cifsFileInfo_put(cfile, false, false); list_del(&tmp_list->list); kfree(tmp_list); } @@ -557,7 +564,14 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon) spin_unlock(&tcon->open_file_lock); list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) { - _cifsFileInfo_put(tmp_list->cfile, true, false); + struct cifsFileInfo *cfile = tmp_list->cfile; + + if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&CIFS_I(d_inode(cfile->dentry))->time_last_write, + jiffies); + } + _cifsFileInfo_put(cfile, true, false); list_del(&tmp_list->list); kfree(tmp_list); } @@ -626,7 +640,14 @@ void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon, spin_unlock(&tcon->open_file_lock); list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) { - _cifsFileInfo_put(tmp_list->cfile, true, false); + struct cifsFileInfo *cfile = tmp_list->cfile; + + if (OPEN_FMODE(cfile->f_flags) & FMODE_WRITE) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&CIFS_I(d_inode(cfile->dentry))->time_last_write, + jiffies); + } + _cifsFileInfo_put(cfile, true, false); list_del(&tmp_list->list); kfree(tmp_list); } From c2f2e83e3bbc5483730fd4ee903182761f1ae50f Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Tue, 21 Jul 2026 18:55:52 -0500 Subject: [PATCH 154/456] cifs: fix cifsFileInfo leak on kmalloc failure in deferred close drain paths In cifs_close_deferred_file(), cifs_close_all_deferred_files(), and cifs_close_deferred_file_under_dentry(), when a pending deferred close is cancelled via cancel_delayed_work(), the subsequent kmalloc_obj() to add the file to the local processing list may fail under memory pressure. The loop breaks immediately, but the cancelled work is no longer pending (it would have called _cifsFileInfo_put()), and the cfile is never added to file_head for processing. The cifsFileInfo reference and the open server handle both leak. Fix by saving the cfile that failed allocation in a local variable, breaking as before, and calling _cifsFileInfo_put() on it after releasing the lock. Any files later in the iteration are unaffected since their deferred work is still pending and will fire normally. Fixes: e3fc065682eb ("cifs: Deferred close performance improvements") Signed-off-by: Frank Sorenson Signed-off-by: Steve French --- fs/smb/client/misc.c | 45 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index fa8cfc4d97f3..6edebc0807ea 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -497,7 +497,7 @@ cifs_del_deferred_close(struct cifsFileInfo *cfile) void cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode) { - struct cifsFileInfo *cfile = NULL; + struct cifsFileInfo *cfile = NULL, *failed_cfile = NULL; struct file_list *tmp_list, *tmp_next_list; LIST_HEAD(file_head); @@ -514,8 +514,10 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode) tmp_list = kmalloc_obj(struct file_list, GFP_ATOMIC); - if (tmp_list == NULL) + if (tmp_list == NULL) { + failed_cfile = cfile; break; + } tmp_list->cfile = cfile; list_add_tail(&tmp_list->list, &file_head); } @@ -523,6 +525,15 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode) } spin_unlock(&cifs_inode->open_file_lock); + if (failed_cfile) { + if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write, + jiffies); + } + _cifsFileInfo_put(failed_cfile, false, false); + } + list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) { struct cifsFileInfo *cfile = tmp_list->cfile; @@ -540,7 +551,7 @@ cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode) void cifs_close_all_deferred_files(struct cifs_tcon *tcon) { - struct cifsFileInfo *cfile; + struct cifsFileInfo *cfile, *failed_cfile = NULL; struct file_list *tmp_list, *tmp_next_list; LIST_HEAD(file_head); @@ -554,8 +565,10 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon) tmp_list = kmalloc_obj(struct file_list, GFP_ATOMIC); - if (tmp_list == NULL) + if (tmp_list == NULL) { + failed_cfile = cfile; break; + } tmp_list->cfile = cfile; list_add_tail(&tmp_list->list, &file_head); } @@ -563,6 +576,15 @@ cifs_close_all_deferred_files(struct cifs_tcon *tcon) } spin_unlock(&tcon->open_file_lock); + if (failed_cfile) { + if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write, + jiffies); + } + _cifsFileInfo_put(failed_cfile, true, false); + } + list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) { struct cifsFileInfo *cfile = tmp_list->cfile; @@ -618,7 +640,7 @@ void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon, struct dentry *dentry) { struct file_list *tmp_list, *tmp_next_list; - struct cifsFileInfo *cfile; + struct cifsFileInfo *cfile, *failed_cfile = NULL; LIST_HEAD(file_head); spin_lock(&tcon->open_file_lock); @@ -631,14 +653,25 @@ void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon, spin_unlock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock); tmp_list = kmalloc_obj(struct file_list, GFP_ATOMIC); - if (tmp_list == NULL) + if (tmp_list == NULL) { + failed_cfile = cfile; break; + } tmp_list->cfile = cfile; list_add_tail(&tmp_list->list, &file_head); } } spin_unlock(&tcon->open_file_lock); + if (failed_cfile) { + if (OPEN_FMODE(failed_cfile->f_flags) & FMODE_WRITE) { + /* Pairs with smp_load_acquire() in is_size_safe_to_change(). */ + smp_store_release(&CIFS_I(d_inode(failed_cfile->dentry))->time_last_write, + jiffies); + } + _cifsFileInfo_put(failed_cfile, true, false); + } + list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) { struct cifsFileInfo *cfile = tmp_list->cfile; From 97ab5aa5177cfa8759bd3861d71e047ea084a829 Mon Sep 17 00:00:00 2001 From: Hongbo Li Date: Tue, 21 Jul 2026 15:05:18 +0800 Subject: [PATCH 155/456] MAINTAINERS: update Hongbo Li's email address Since the original email account is no longer in use, I have updated it to my current email address. Signed-off-by: Hongbo Li Acked-by: Chao Yu Acked-by: Gao Xiang Signed-off-by: Gao Xiang --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index a674e36529f7..4ac000af898f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9617,7 +9617,7 @@ M: Chao Yu R: Yue Hu R: Jeffle Xu R: Sandeep Dhavale -R: Hongbo Li +R: Hongbo Li R: Chunhai Guo L: linux-erofs@lists.ozlabs.org S: Maintained From 4c92afb4c143526d340545ca581e88e6952ea511 Mon Sep 17 00:00:00 2001 From: Satyanarayana K V P Date: Tue, 21 Jul 2026 10:52:14 +0530 Subject: [PATCH 156/456] drm/xe/vf: Add drm_dev guards when detaching CCS read/write buffers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CCS read/write buffers are freed during BO destruction. In some cases, BOs may be destroyed after the device is unbound but while the DRM structure remains valid, leading to NULL pointer dereferences when accessing device resources. BUG: kernel NULL pointer dereference, address: 0000000000000000 PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP NOPTI CPU: 0 UID: 0 PID: 9376 Comm: xe_pat Not tainted 7.2.0-rc2+ #1 PREEMPT(lazy) RIP: 0010:xe_sriov_vf_ccs_rw_update_bb_addr+0x4d/0xa0 [xe] RSP: 0018:ffffcf304110b9c8 EFLAGS: 00010246 RAX: ffff8a85c38a0a00 RBX: 00000000810ef000 RCX: 0000000000000000 RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff8a85c39c1888 RBP: ffffcf304110b9e8 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: ffff8a85c39c1888 R13: 0000000000000000 R14: ffff8a85c39b4f28 R15: ffff8a85c3885000 FS: 0000000000000000(0000) GS:ffff8a878b809000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000000 CR3: 000000010314a002 CR4: 0000000000772ef0 PKRU: 55555554 Call Trace: xe_migrate_ccs_rw_copy_clear+0x98/0x120 [xe] xe_sriov_vf_ccs_detach_bo+0x2c/0x60 [xe] xe_ttm_bo_delete_mem_notify+0xc8/0xe0 [xe] ttm_bo_cleanup_memtype_use+0x26/0x80 [ttm] ttm_bo_release+0x29e/0x2d0 [ttm] ttm_bo_fini+0x39/0x70 [ttm] xe_gem_object_free+0x1f/0x30 [xe] drm_gem_object_free+0x1d/0x40 ttm_bo_vm_close+0x5f/0x90 [ttm] remove_vma+0x2c/0x70 tear_down_vmas+0x63/0xf0 exit_mmap+0x20d/0x3f0 __mmput+0x45/0x170 mmput+0x31/0x40 do_exit+0x2ba/0xac0 do_group_exit+0x2d/0xb0 __x64_sys_exit_group+0x18/0x20 x64_sys_call+0x14a0/0x2390 do_syscall_64+0xdd/0x640 ? count_memcg_events+0xea/0x240 ? handle_mm_fault+0x1ec/0x2f0 Fixes: 864690cf4dd6 ("drm/xe/vf: Attach and detach CCS copy commands with BO") Signed-off-by: Satyanarayana K V P Cc: Matthew Brost Cc: Michal Wajdeczko Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260721052215.2267228-2-satyanarayana.k.v.p@intel.com (cherry picked from commit 1ae415a6eefe5004954a1d352b1718faca8844ef) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_migrate.c | 16 ++++++++++------ drivers/gpu/drm/xe/xe_migrate.h | 3 ++- drivers/gpu/drm/xe/xe_sriov_vf_ccs.c | 14 ++++++++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c index 7d28290e7d1c..be787f331768 100644 --- a/drivers/gpu/drm/xe/xe_migrate.c +++ b/drivers/gpu/drm/xe/xe_migrate.c @@ -1313,6 +1313,7 @@ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, * content. * @src_bo: The buffer object @src is currently bound to. * @read_write : Creates BB commands for CCS read/write. + * @bound: Device is bound * * Directly clearing the BB lacks atomicity and can lead to undefined * behavior if the vCPU is halted mid-operation during the clearing @@ -1325,7 +1326,8 @@ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, * Returns: None. */ void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo, - enum xe_sriov_vf_ccs_rw_ctxs read_write) + enum xe_sriov_vf_ccs_rw_ctxs read_write, + bool bound) { struct xe_mem_pool_node *bb = src_bo->bb_ccs[read_write]; struct xe_device *xe = xe_bo_device(src_bo); @@ -1339,13 +1341,15 @@ void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo, bb_pool = ctx->mem.ccs_bb_pool; scoped_guard(mutex, xe_mem_pool_bo_swap_guard(bb_pool)) { - xe_mem_pool_swap_shadow_locked(bb_pool); + if (bound) { + xe_mem_pool_swap_shadow_locked(bb_pool); - cs = xe_mem_pool_node_cpu_addr(bb); - memset(cs, MI_NOOP, bb->sa_node.size); - xe_sriov_vf_ccs_rw_update_bb_addr(ctx); + cs = xe_mem_pool_node_cpu_addr(bb); + memset(cs, MI_NOOP, bb->sa_node.size); + xe_sriov_vf_ccs_rw_update_bb_addr(ctx); - xe_mem_pool_sync_shadow_locked(bb); + xe_mem_pool_sync_shadow_locked(bb); + } xe_mem_pool_free_node(bb); src_bo->bb_ccs[read_write] = NULL; } diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h index 78e5b63f3ebe..c3a268b01768 100644 --- a/drivers/gpu/drm/xe/xe_migrate.h +++ b/drivers/gpu/drm/xe/xe_migrate.h @@ -142,7 +142,8 @@ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, enum xe_sriov_vf_ccs_rw_ctxs read_write); void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo, - enum xe_sriov_vf_ccs_rw_ctxs read_write); + enum xe_sriov_vf_ccs_rw_ctxs read_write, + bool bound); struct xe_lrc *xe_migrate_lrc(struct xe_migrate *migrate); struct xe_exec_queue *xe_migrate_exec_queue(struct xe_migrate *migrate); diff --git a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c index 6787564629c6..a8c831fbee3b 100644 --- a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c +++ b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c @@ -3,6 +3,8 @@ * Copyright © 2025 Intel Corporation */ +#include + #include "instructions/xe_mi_commands.h" #include "instructions/xe_gpu_commands.h" #include "xe_bb.h" @@ -446,7 +448,7 @@ int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo, struct ttm_resource *new_mem) */ for_each_ccs_rw_ctx(ctx_id) { if (bo->bb_ccs[ctx_id]) - xe_migrate_ccs_rw_copy_clear(bo, ctx_id); + xe_migrate_ccs_rw_copy_clear(bo, ctx_id, true); } return err; } @@ -466,19 +468,27 @@ int xe_sriov_vf_ccs_detach_bo(struct xe_bo *bo) struct xe_device *xe = xe_bo_device(bo); enum xe_sriov_vf_ccs_rw_ctxs ctx_id; struct xe_mem_pool_node *bb; + bool bound; + int idx; xe_assert(xe, IS_VF_CCS_READY(xe)); if (!xe_bo_has_valid_ccs_bb(bo)) return 0; + bound = drm_dev_enter(&xe->drm, &idx); + for_each_ccs_rw_ctx(ctx_id) { bb = bo->bb_ccs[ctx_id]; if (!bb) continue; - xe_migrate_ccs_rw_copy_clear(bo, ctx_id); + xe_migrate_ccs_rw_copy_clear(bo, ctx_id, bound); } + + if (bound) + drm_dev_exit(idx); + return 0; } From 1f1be4ba643fb67410d753111c37ae42a2e2a814 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 21 Jul 2026 13:43:53 -0700 Subject: [PATCH 157/456] drm/pagemap: Clear driver-provided PFNs from migration PFN array DRM pagemap overloads the migration PFN array to store driver-provided PFNs before calling migrate_vma_*() to finalize the migration. If an error occurs during the incremental copy phase, the migration PFN entries are reverted to their original state. After reverting the device-folio mutations, clear any remaining driver-provided PFNs to avoid confusing the migrate_vma_*() helpers. Also clear any driver-provided PFNs if populate_devmem_pfn() fails, as a precaution against stale entries being interpreted as migration PFNs. Reported-by: Sashiko Fixes: 3902846af36b ("drm/pagemap Fix error paths in drm_pagemap_migrate_to_devmem") Fixes: ec265e1f1cfc ("drm/pagemap: Support source migration over interconnect") Cc: stable@vger.kernel.org Signed-off-by: Matthew Brost Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260721204353.1082632-1-matthew.brost@intel.com --- drivers/gpu/drm/drm_pagemap.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 15c78eca180b..892b325fa99b 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -727,8 +727,10 @@ int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, } err = ops->populate_devmem_pfn(devmem_allocation, npages, migrate.dst); - if (err) - goto err_aborted_migration; + if (err) { + npages = 0; + goto err_finalize; + } own_pages = 0; @@ -807,8 +809,11 @@ int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, msecs_to_jiffies(mdetails->timeslice_ms); err_finalize: - if (err) + if (err) { drm_pagemap_migration_unlock_put_pages(npages, migrate.dst); + for (i = npages; i < npages_in_range(start, end); ++i) + migrate.dst[i] = 0; + } err_aborted_migration: migrate_vma_pages(&migrate); From 04b177544a040cbafab760d6b766381c6b22e0a8 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 21 Jul 2026 15:32:44 -0700 Subject: [PATCH 158/456] drm/pagemap: Guard HPAGE_PMD_ORDER use with CONFIG_ARCH_ENABLE_THP_MIGRATION HPAGE_PMD_SHIFT expands to BUILD_BUG() when CONFIG_PGTABLE_HAS_HUGE_LEAVES is not set, causing a compile error when both CONFIG_TRANSPARENT_HUGEPAGE and CONFIG_HUGETLB_PAGE are disabled: drivers/gpu/drm/drm_pagemap.c:480:12: error: call to '__compiletime_assert_458' declared with 'error' attribute: BUILD_BUG failed 480 | order = HPAGE_PMD_ORDER; | ^ include/linux/huge_mm.h:117:26: note: expanded from macro 'HPAGE_PMD_ORDER' 117 | #define HPAGE_PMD_ORDER (HPAGE_PMD_SHIFT-PAGE_SHIFT) | ^ include/linux/huge_mm.h:113:28: note: expanded from macro 'HPAGE_PMD_SHIFT' 113 | #define HPAGE_PMD_SHIFT ({ BUILD_BUG(); 0; }) Define DRM_PAGEMAP_PMD_ORDER, which maps to HPAGE_PMD_ORDER when CONFIG_ARCH_ENABLE_THP_MIGRATION is enabled and to -1 otherwise. This is safe because all code paths that use DRM_PAGEMAP_PMD_ORDER are reachable only when CONFIG_ARCH_ENABLE_THP_MIGRATION is enabled. Fixes: 139ab31aea8a ("drm/pagemap: Correct cpages calculation for migrate_vma_setup") Reported-by: kernel test robot Closes: https://lore.kernel.org/r/202607201914.LpAGsbXs-lkp@intel.com/ Cc: Jan Stancek Cc: stable@vger.kernel.org Signed-off-by: Matthew Brost Tested-by: Jan Stancek Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260721223244.1102276-1-matthew.brost@intel.com --- drivers/gpu/drm/drm_pagemap.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 892b325fa99b..4a794544b7dc 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -12,6 +12,12 @@ #include #include +#if IS_ENABLED(CONFIG_ARCH_ENABLE_THP_MIGRATION) +#define DRM_PAGEMAP_PMD_ORDER HPAGE_PMD_ORDER +#else +#define DRM_PAGEMAP_PMD_ORDER (-1) +#endif + /** * DOC: Overview * @@ -579,7 +585,7 @@ static int drm_pagemap_cpages(unsigned long *migrate_pfn, unsigned long npages) order = folio_order(folio); cpages += NR_PAGES(order); } else if (migrate_pfn[i] & MIGRATE_PFN_COMPOUND) { - order = HPAGE_PMD_ORDER; + order = DRM_PAGEMAP_PMD_ORDER; cpages += NR_PAGES(order); } @@ -767,10 +773,11 @@ int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, if (migrate.src[i] & MIGRATE_PFN_COMPOUND) { drm_WARN_ONCE(dpagemap->drm, src_page && - folio_order(page_folio(src_page)) != HPAGE_PMD_ORDER, + folio_order(page_folio(src_page)) != + DRM_PAGEMAP_PMD_ORDER, "Unexpected folio order\n"); - order = HPAGE_PMD_ORDER; + order = DRM_PAGEMAP_PMD_ORDER; migrate.dst[i] |= MIGRATE_PFN_COMPOUND; for (j = 1; j < NR_PAGES(order) && i + j < npages; j++) From faaddd811c5099f11a5f52e68a6b31a5898cda4f Mon Sep 17 00:00:00 2001 From: Sunho Park Date: Tue, 14 Jul 2026 19:42:30 +0900 Subject: [PATCH 159/456] USB: serial: io_edgeport: cap received transmit credits The interrupt-status packet reports transmit credits returned by the device. edge_interrupt_callback() adds the 16-bit value to txCredits without checking maxTxCredits. edge_write() uses txCredits minus the software FIFO count as the amount of data that fits. Since the FIFO is allocated with maxTxCredits bytes, txCredits exceeding maxTxCredits can cause OOB write in ring buffer. Cap accumulated credits at maxTxCredits. Conforming devices should never hit the cap. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Sunho Park Signed-off-by: Johan Hovold --- drivers/usb/serial/io_edgeport.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index 34ccf7820537..eaa8f00716c8 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -646,7 +646,8 @@ static void edge_interrupt_callback(struct urb *urb) if (edge_port && edge_port->open) { spin_lock_irqsave(&edge_port->ep_lock, flags); - edge_port->txCredits += txCredits; + edge_port->txCredits = min(edge_port->txCredits + txCredits, + edge_port->maxTxCredits); spin_unlock_irqrestore(&edge_port->ep_lock, flags); dev_dbg(dev, "%s - txcredits for port%d = %d\n", From f73a8edc2ccc6ec72c37d5c578e7592d2e1f9922 Mon Sep 17 00:00:00 2001 From: Dev Jain Date: Fri, 3 Jul 2026 11:41:54 +0000 Subject: [PATCH 160/456] arm64: make huge_ptep_get handled unaligned addresses huge_ptep_get() can be handed a virtual address pointing to the middle of a contpmd/contpte mapped hugetlb folio (examples of callers are pagemap_hugetlb_range, page_mapped_in_vma). The arm64 helper rewalks the pgtables in find_num_contig to answer whether the huge pte we have maps a contpmd or a contpte hugetlb folio, and returns CONT_PMDS or CONT_PTES, so that it can collect a/d bits over the contiguous ptes. We can falsely return CONT_PTES instead of CONT_PMDS if the addr is not aligned. On systems where CONT_PTES != CONT_PMDS (meaning page size is 16K), we could collect excess A/D bit state, meaning extra work for the kernel. Even worse, we may iterate beyond the PTE table and dereference a garbage ptep pointer to access physical memory we don't own. Since the ptep pointer is a linear map address, we may run off the end of the linear map or into a hole, dereference a VA not mapped into the kernel pgtables and cause kernel panic. Fix this by aligning the pmdp pointer down to a contpmd base before checking equality with the passed huge pte pointer, to correctly answer whether the huge pte is the base of a contpmd block. Fixes: 29cb80519689 ("arm64: hugetlb: Cleanup huge_pte size discovery mechanisms") Cc: stable@vger.kernel.org Acked-by: David Hildenbrand (Arm) Signed-off-by: Dev Jain Acked-by: Muchun Song Signed-off-by: Will Deacon --- arch/arm64/mm/hugetlbpage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/mm/hugetlbpage.c b/arch/arm64/mm/hugetlbpage.c index 30772a909aea..8e799c1fe0aa 100644 --- a/arch/arm64/mm/hugetlbpage.c +++ b/arch/arm64/mm/hugetlbpage.c @@ -87,7 +87,7 @@ static int find_num_contig(struct mm_struct *mm, unsigned long addr, p4dp = p4d_offset(pgdp, addr); pudp = pud_offset(p4dp, addr); pmdp = pmd_offset(pudp, addr); - if ((pte_t *)pmdp == ptep) { + if ((pte_t *)PTR_ALIGN_DOWN(pmdp, sizeof(*pmdp) * CONT_PMDS) == ptep) { *pgsize = PMD_SIZE; return CONT_PMDS; } From a8e72879cd0d8422c0b47d6d3c1802274fe73b98 Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Wed, 8 Jul 2026 16:22:21 +0800 Subject: [PATCH 161/456] ovl: fix trusted xattr escape prefix matching In the trusted.* xattr namespace, ovl_is_escaped_xattr() compares one byte less than the escaped overlay xattr prefix length. This makes it match "trusted.overlay.overlay" without requiring the trailing dot. As a result, an xattr such as "trusted.overlay.overlayfoo" is incorrectly treated as an escaped overlay xattr. This can be reproduced by setting "trusted.overlay.overlayfoo" on a lower file and listing xattrs through an overlay mount. listxattr() then exposes it as "trusted.overlay.oo", and a following getxattr() on that listed name fails with ENODATA. Compare the full escaped prefix, including the trailing dot, so similarly-prefixed private xattrs are not misclassified. Fixes: dad02fad84cbc ("ovl: Support escaped overlay.* xattrs") Signed-off-by: Yichong Chen Link: https://patch.msgid.link/20260708082221.633602-1-chenyichong@uniontech.com Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/xattrs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/overlayfs/xattrs.c b/fs/overlayfs/xattrs.c index aa95855c7023..859e80ae6f40 100644 --- a/fs/overlayfs/xattrs.c +++ b/fs/overlayfs/xattrs.c @@ -13,7 +13,7 @@ static bool ovl_is_escaped_xattr(struct super_block *sb, const char *name) OVL_XATTR_ESCAPE_USER_PREFIX_LEN) == 0; else return strncmp(name, OVL_XATTR_ESCAPE_TRUSTED_PREFIX, - OVL_XATTR_ESCAPE_TRUSTED_PREFIX_LEN - 1) == 0; + OVL_XATTR_ESCAPE_TRUSTED_PREFIX_LEN) == 0; } static bool ovl_is_own_xattr(struct super_block *sb, const char *name) From bb6bc13c53e211d9148ed2eab3e689c5cd5c75da Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Jul 2026 13:50:53 +0200 Subject: [PATCH 162/456] pidfs: preserve thread pidfds reopened by file handle PIDFD_THREAD shares O_EXCL. do_dentry_open() clears O_EXCL after pidfs_export_open() validates the flags, so open_by_handle_at() silently turns a thread pidfd into a process pidfd. Restore PIDFD_THREAD on the opened file, matching pidfs_alloc_file(). Signed-off-by: Li Chen Reviewed-by: Jan Kara Link: https://patch.msgid.link/20260716052726.1032092-1-me@linux.beauty Signed-off-by: Christian Brauner (Amutable) --- fs/pidfs.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/pidfs.c b/fs/pidfs.c index aaa609ddab04..c20ffd747ff5 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -939,12 +939,18 @@ static int pidfs_export_permission(struct handle_to_path_ctx *ctx, static struct file *pidfs_export_open(const struct path *path, unsigned int oflags) { + struct file *file; + /* * Clear O_LARGEFILE as open_by_handle_at() forces it and raise * O_RDWR as pidfds always are. */ oflags &= ~O_LARGEFILE; - return dentry_open(path, oflags | O_RDWR, current_cred()); + file = dentry_open(path, oflags | O_RDWR, current_cred()); + /* do_dentry_open() strips O_EXCL, which encodes PIDFD_THREAD. */ + if (!IS_ERR(file)) + file->f_flags |= oflags & PIDFD_THREAD; + return file; } static const struct export_operations pidfs_export_operations = { From 58af123ec7aa7eab19e938b3777c41c02abda1b8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Jul 2026 13:50:53 +0200 Subject: [PATCH 163/456] selftests/pidfd: check PIDFD_THREAD survives open_by_handle_at() Verify that a thread pidfd reopened via open_by_handle_at() still reports PIDFD_THREAD in F_GETFL. Signed-off-by: Li Chen Reviewed-by: Jan Kara Link: https://patch.msgid.link/20260716052726.1032092-1-me@linux.beauty Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/pidfd/pidfd_file_handle_test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/pidfd/pidfd_file_handle_test.c b/tools/testing/selftests/pidfd/pidfd_file_handle_test.c index 68918734dcf3..1e03ae9575fe 100644 --- a/tools/testing/selftests/pidfd/pidfd_file_handle_test.c +++ b/tools/testing/selftests/pidfd/pidfd_file_handle_test.c @@ -373,6 +373,7 @@ TEST_F(file_handle, open_by_handle_at_valid_flags) O_CLOEXEC | O_EXCL); ASSERT_GE(pidfd, 0); + ASSERT_NE(fcntl(pidfd, F_GETFL) & PIDFD_THREAD, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); From 927b89700e9fdba61902f8828dbf9b5f29f40ea7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 22 Jul 2026 13:56:28 +0200 Subject: [PATCH 164/456] pidfs: add pidfs_dentry_open() helper Both pidfs_alloc_file() and pidfs_export_open() need to force O_RDWR and reapply the pidfd flags that do_dentry_open() strips. Move the common logic into a helper. PIDFD_AUTOKILL is now part of the restore mask in the file handle path as well, but pidfs_export_permission() rejects O_TRUNC, so this is a no-op there. But warn nonetheless. Link: https://patch.msgid.link/20260722-esszimmer-umsetzen-nennt-ed5fc604300a@brauner Signed-off-by: Christian Brauner (Amutable) --- fs/pidfs.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/fs/pidfs.c b/fs/pidfs.c index c20ffd747ff5..695215aa2a58 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -915,6 +915,20 @@ static struct dentry *pidfs_fh_to_dentry(struct super_block *sb, return path.dentry; } +static struct file *pidfs_dentry_open(const struct path *path, + unsigned int flags, + const struct cred *cred) +{ + struct file *file; + + /* pidfds are always O_RDWR. */ + file = dentry_open(path, flags | O_RDWR, cred); + /* do_dentry_open() strips O_EXCL and O_TRUNC. */ + if (!IS_ERR(file)) + file->f_flags |= flags & (PIDFD_THREAD | PIDFD_AUTOKILL); + return file; +} + /* * Make sure that we reject any nonsensical flags that users pass via * open_by_handle_at(). Note that PIDFD_THREAD is defined as O_EXCL, and @@ -939,18 +953,14 @@ static int pidfs_export_permission(struct handle_to_path_ctx *ctx, static struct file *pidfs_export_open(const struct path *path, unsigned int oflags) { - struct file *file; - /* - * Clear O_LARGEFILE as open_by_handle_at() forces it and raise - * O_RDWR as pidfds always are. + * Opening via file handle may never raise PIDFD_AUTOKILL. That can + * only be done at task creation! */ - oflags &= ~O_LARGEFILE; - file = dentry_open(path, oflags | O_RDWR, current_cred()); - /* do_dentry_open() strips O_EXCL, which encodes PIDFD_THREAD. */ - if (!IS_ERR(file)) - file->f_flags |= oflags & PIDFD_THREAD; - return file; + if (WARN_ON_ONCE(oflags & PIDFD_AUTOKILL)) + return ERR_PTR(-EINVAL); + /* Clear O_LARGEFILE as open_by_handle_at() forces it. */ + return pidfs_dentry_open(path, oflags & ~O_LARGEFILE, current_cred()); } static const struct export_operations pidfs_export_operations = { @@ -1114,7 +1124,6 @@ static struct file_system_type pidfs_type = { struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags) { - struct file *pidfd_file; struct path path __free(path_put) = {}; int ret; @@ -1132,16 +1141,7 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags) VFS_WARN_ON_ONCE(!pid->attr); flags &= ~PIDFD_STALE; - flags |= O_RDWR; - pidfd_file = dentry_open(&path, flags, current_cred()); - /* - * Raise PIDFD_THREAD and PIDFD_AUTOKILL explicitly as - * do_dentry_open() strips O_EXCL and O_TRUNC. - */ - if (!IS_ERR(pidfd_file)) - pidfd_file->f_flags |= (flags & (PIDFD_THREAD | PIDFD_AUTOKILL)); - - return pidfd_file; + return pidfs_dentry_open(&path, flags, current_cred()); } void __init pidfs_init(void) From 7a6004c230a7931569666a235e99d443b2319b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Fri, 17 Jul 2026 08:14:30 -0300 Subject: [PATCH 165/456] =?UTF-8?q?mailmap:=20Update=20Ma=C3=ADra=20Canal'?= =?UTF-8?q?s=20email=20address?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My university email will cease to exist in the next few days, so map it to a personal email address. Reviewed-by: André Almeida Acked-by: Maíra Canal Link: https://patch.msgid.link/20260717111559.2759217-1-maira.canal@usp.br Signed-off-by: Maíra Canal --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 12f3acdebd72..e8fa40478028 100644 --- a/.mailmap +++ b/.mailmap @@ -539,6 +539,7 @@ Maciej W. Rozycki Maciej W. Rozycki Maharaja Kennadyrajan Maheshwar Ajja +Maíra Canal Malathi Gottam Manikanta Pubbisetty Manivannan Sadhasivam From f7df2da0d1c375c1c4c70e1e0b569592de0d6b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Sat, 18 Jul 2026 10:44:36 -0300 Subject: [PATCH 166/456] drm/v3d: Reach the GMP through the hub registers on V3D 7.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3d_idle_axi() drains the GPU's memory interface for a safe powerdown by using the V3D_GMP_CFG register. It reached both registers with the macros V3D_CORE_READ and V3D_CORE_WRITE. On V3D 7.x the GMP is no longer a per-core block; it lives in the hub register region. Reaching it through the per-core register block addresses the wrong region. Select the hub accessors (V3D_{READ,WRITE}) for the GMP on V3D 7.x and keep the per-core path for earlier generations. Cc: stable@vger.kernel.org Fixes: 0ad5bc1ce463 ("drm/v3d: fix up register addresses for V3D 7.x") Link: https://patch.msgid.link/20260718-v3d-pm-axi-transactions-v1-1-4ecd7729ed70@igalia.com Reviewed-by: Iago Toral Quiroga Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_gem.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/v3d/v3d_gem.c b/drivers/gpu/drm/v3d/v3d_gem.c index c43d9af41374..bb0eec870794 100644 --- a/drivers/gpu/drm/v3d/v3d_gem.c +++ b/drivers/gpu/drm/v3d/v3d_gem.c @@ -39,6 +39,18 @@ v3d_init_core(struct v3d_dev *v3d, int core) static void v3d_idle_axi(struct v3d_dev *v3d, int core) { + if (v3d->ver >= V3D_GEN_71) { + V3D_WRITE(V3D_GMP_CFG(v3d->ver), V3D_GMP_CFG_STOP_REQ); + + if (wait_for((V3D_READ(V3D_GMP_STATUS(v3d->ver)) & + (V3D_GMP_STATUS_RD_COUNT_MASK | + V3D_GMP_STATUS_WR_COUNT_MASK | + V3D_GMP_STATUS_CFG_BUSY)) == 0, 100)) { + drm_err(&v3d->drm, "Failed to wait for safe GMP shutdown\n"); + } + return; + } + V3D_CORE_WRITE(core, V3D_GMP_CFG(v3d->ver), V3D_GMP_CFG_STOP_REQ); if (wait_for((V3D_CORE_READ(core, V3D_GMP_STATUS(v3d->ver)) & From ab05caca123c6d0b41850b7c05b246e4dca4a770 Mon Sep 17 00:00:00 2001 From: Yitang Yang Date: Wed, 22 Jul 2026 20:45:51 +0800 Subject: [PATCH 167/456] io_uring/rw: fix missing ERESTARTSYS conversion in read paths Both read and write may receive internal restart error codes from the filesystem layer and should be converted to -EINTR. However, when multishot read support was added, the error code normalization was lost for both io_read() and io_read_mshot(). Extract the conversion into io_fixup_restart_res() and apply it in all three locations: io_rw_done(), io_read(), and io_read_mshot(). Fixes: a08d195b586a ("io_uring/rw: split io_read() into a helper") Cc: stable@vger.kernel.org Signed-off-by: Yitang Yang Link: https://patch.msgid.link/20260722124551.130563-1-yi1tang.yang@gmail.com Signed-off-by: Jens Axboe --- io_uring/rw.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/io_uring/rw.c b/io_uring/rw.c index 63b6519e498c..95038cfda615 100644 --- a/io_uring/rw.c +++ b/io_uring/rw.c @@ -615,6 +615,24 @@ static void io_complete_rw_iopoll(struct kiocb *kiocb, long res) smp_store_release(&req->iopoll_completed, 1); } +static inline ssize_t io_fixup_restart_res(ssize_t ret) +{ + switch (ret) { + case -ERESTARTSYS: + case -ERESTARTNOINTR: + case -ERESTARTNOHAND: + case -ERESTART_RESTARTBLOCK: + /* + * We can't just restart the syscall, since previously + * submitted sqes may already be in progress. Just fail + * this IO with EINTR. + */ + return -EINTR; + default: + return ret; + } +} + static inline void io_rw_done(struct io_kiocb *req, ssize_t ret) { struct io_rw *rw = io_kiocb_to_cmd(req, struct io_rw); @@ -624,21 +642,8 @@ static inline void io_rw_done(struct io_kiocb *req, ssize_t ret) return; /* transform internal restart error codes */ - if (unlikely(ret < 0)) { - switch (ret) { - case -ERESTARTSYS: - case -ERESTARTNOINTR: - case -ERESTARTNOHAND: - case -ERESTART_RESTARTBLOCK: - /* - * We can't just restart the syscall, since previously - * submitted sqes may already be in progress. Just fail - * this IO with EINTR. - */ - ret = -EINTR; - break; - } - } + if (unlikely(ret < 0)) + ret = io_fixup_restart_res(ret); if (req->flags & REQ_F_IOPOLL) io_complete_rw_iopoll(&rw->kiocb, ret); @@ -1034,7 +1039,8 @@ int io_read(struct io_kiocb *req, unsigned int issue_flags) if (req->flags & REQ_F_BUFFERS_COMMIT) io_kbuf_recycle(req, sel.buf_list, issue_flags); - return ret; + + return io_fixup_restart_res(ret); } int io_read_mshot(struct io_kiocb *req, unsigned int issue_flags) @@ -1068,8 +1074,10 @@ int io_read_mshot(struct io_kiocb *req, unsigned int issue_flags) return IOU_RETRY; } else if (ret <= 0) { io_kbuf_recycle(req, sel.buf_list, issue_flags); - if (ret < 0) + if (ret < 0) { + ret = io_fixup_restart_res(ret); req_set_fail(req); + } } else if (!(req->flags & REQ_F_APOLL_MULTISHOT)) { cflags = io_put_kbuf(req, ret, sel.buf_list); } else { From 07e769ba3d82ac614725c81c512a8b54d33f2c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Sat, 18 Jul 2026 10:44:37 -0300 Subject: [PATCH 168/456] drm/v3d: Idle AXI transactions before disabling the clock on suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, v3d_power_suspend() removes the GPU clock without first quiescing the GPU's memory interface (AXI). If the clock is cut while the core still has outstanding AXI transactions in flight, the hardware is frozen mid-transaction. That corrupted state survives the power cycle, and the first job submitted after the next resume will cause a GPU hang accompanied by an L2T "pte invalid" MMU fault. The hardware already provides a safe-powerdown sequence for this: request the GMP to stop and wait for outstanding reads/writes to drain (v3d_idle_axi()), plus the GCA safe shutdown on pre-4.1 HW (v3d_idle_gca()). The driver implements both, but the runtime PM support added later never invoked them when powering the GPU down. Perform the safe-powerdown sequence in v3d_power_suspend() before disabling the clock, while the core is still powered. Link: https://github.com/raspberrypi/linux/issues/7443 Link: https://github.com/raspberrypi/linux/issues/7488 Fixes: 458f2a712ab4 ("drm/v3d: Introduce Runtime Power Management") Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260718-v3d-pm-axi-transactions-v1-2-4ecd7729ed70@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_drv.h | 2 ++ drivers/gpu/drm/v3d/v3d_gem.c | 4 ++-- drivers/gpu/drm/v3d/v3d_power.c | 7 +++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h index 4ebe175a8c6b..423bcfe331a0 100644 --- a/drivers/gpu/drm/v3d/v3d_drv.h +++ b/drivers/gpu/drm/v3d/v3d_drv.h @@ -571,6 +571,8 @@ extern bool super_pages; void v3d_init_hw_state(struct v3d_dev *v3d); int v3d_gem_init(struct drm_device *dev); void v3d_gem_destroy(struct drm_device *dev); +void v3d_idle_axi(struct v3d_dev *v3d, int core); +void v3d_idle_gca(struct v3d_dev *v3d); void v3d_reset_sms(struct v3d_dev *v3d); void v3d_reset(struct v3d_dev *v3d); void v3d_invalidate_caches(struct v3d_dev *v3d); diff --git a/drivers/gpu/drm/v3d/v3d_gem.c b/drivers/gpu/drm/v3d/v3d_gem.c index bb0eec870794..23a0db9575a5 100644 --- a/drivers/gpu/drm/v3d/v3d_gem.c +++ b/drivers/gpu/drm/v3d/v3d_gem.c @@ -36,7 +36,7 @@ v3d_init_core(struct v3d_dev *v3d, int core) V3D_CORE_WRITE(core, V3D_CTL_L2TFLEND, ~0); } -static void +void v3d_idle_axi(struct v3d_dev *v3d, int core) { if (v3d->ver >= V3D_GEN_71) { @@ -61,7 +61,7 @@ v3d_idle_axi(struct v3d_dev *v3d, int core) } } -static void +void v3d_idle_gca(struct v3d_dev *v3d) { if (v3d->ver >= V3D_GEN_41) diff --git a/drivers/gpu/drm/v3d/v3d_power.c b/drivers/gpu/drm/v3d/v3d_power.c index f7df6393d38f..c53146316079 100644 --- a/drivers/gpu/drm/v3d/v3d_power.c +++ b/drivers/gpu/drm/v3d/v3d_power.c @@ -54,8 +54,15 @@ int v3d_power_suspend(struct device *dev) v3d_clean_caches(v3d); + /* Wait until V3D has no active or pending AXI transactions. */ + v3d_idle_axi(v3d, 0); + v3d_idle_gca(v3d); + ret = v3d_suspend_sms(v3d); if (ret) { + /* Staying active: undo the GMP STOP_REQ from v3d_idle_axi(). */ + V3D_WRITE(V3D_GMP_CFG(v3d->ver), + V3D_READ(V3D_GMP_CFG(v3d->ver)) & ~V3D_GMP_CFG_STOP_REQ); v3d_irq_enable(v3d); return ret; } From 425224c2d700391729be7fe6929a88ef4e2d7a4e Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Mon, 6 Jul 2026 20:22:42 +0200 Subject: [PATCH 169/456] proc: Fix broken error paths for namespace links Don't return the return value of down_read_killable() (0) when a ptrace access check fails, return -EACCES as intended. Reported-by: Magnus Lindholm Closes: https://lore.kernel.org/r/20260706170735.2941493-1-linmag7@gmail.com Fixes: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)") Cc: stable@vger.kernel.org Signed-off-by: Jann Horn Link: https://patch.msgid.link/20260706-procfs-ns-eacces-fix-v1-1-a69ab14c02e6@google.com Tested-by: Magnus Lindholm Signed-off-by: Christian Brauner (Amutable) --- fs/proc/namespaces.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c index 2f46f1396744..ea6ec61a0430 100644 --- a/fs/proc/namespaces.c +++ b/fs/proc/namespaces.c @@ -46,7 +46,7 @@ static const char *proc_ns_get_link(struct dentry *dentry, const struct proc_ns_operations *ns_ops = PROC_I(inode)->ns_ops; struct task_struct *task; struct path ns_path; - int error = -EACCES; + int error; if (!dentry) return ERR_PTR(-ECHILD); @@ -59,6 +59,7 @@ static const char *proc_ns_get_link(struct dentry *dentry, if (error) goto out_put_task; + error = -EACCES; if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) goto out; @@ -90,6 +91,7 @@ static int proc_ns_readlink(struct dentry *dentry, char __user *buffer, int bufl if (res) goto out_put_task; + res = -EACCES; if (ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { res = ns_get_name(name, sizeof(name), task, ns_ops); if (res >= 0) From fa989f2cc351aec3643350489553c9bdfb3adf9f Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Mon, 13 Jul 2026 10:22:13 +0200 Subject: [PATCH 170/456] drm/panel: ilitek-ili9881c: do not fail probe if iovcc is absent Commit 4c95b2b7d49e ("drm/panel: ilitek-ili9881c: support Waveshare 7.0" DSI panel") adds an additional iovcc regulator that other Ilitek ili9881c based panels apparently do not have or need. The commit goes out of its way to make usage of this new regulator optional, dutifully testing if the field in `struct ili9881c` is NULL before touching the new regulator. However, in the probe function, it unconditionally fails if devm_regulator_get_optional returns an error. devm_regulator_get_optional() returns -ENODEV if the regulator is missing, causing probe to fail for other panels that do not have an iovcc-supply set in the device tree. Fixes: 4c95b2b7d49e ("drm/panel: ilitek-ili9881c: support Waveshare 7.0" DSI panel") Signed-off-by: David Oberhollenzer Reviewed-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260713082213.75759-1-david.oberhollenzer@sigma-star.at --- drivers/gpu/drm/panel/panel-ilitek-ili9881c.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c index 0652cdb57d11..6c8d02712566 100644 --- a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c +++ b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c @@ -2562,9 +2562,12 @@ static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi) "Couldn't get our power regulator\n"); ctx->iovcc = devm_regulator_get_optional(&dsi->dev, "iovcc"); - if (IS_ERR(ctx->iovcc)) - return dev_err_probe(&dsi->dev, PTR_ERR(ctx->iovcc), + if (IS_ERR(ctx->iovcc)) { + if (PTR_ERR(ctx->iovcc) != -ENODEV) + return dev_err_probe(&dsi->dev, PTR_ERR(ctx->iovcc), "Couldn't get our iovcc regulator\n"); + ctx->iovcc = NULL; + } ctx->reset = devm_gpiod_get_optional(&dsi->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(ctx->reset)) From fc03f930bd9fd7c09617e41b1743e50ba659707c Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 12 Jul 2026 01:26:32 +0100 Subject: [PATCH 171/456] drm/panel: ilitek-ili9882t: fix unmet dependency for DRM_PANEL_ILITEK_ILI9882T Currently, DRM_PANEL_ILITEK_ILI9882T selects DRM_DISPLAY_DSC_HELPER without ensuring DRM_DISPLAY_HELPER is also enabled, causing an unmet dependency and build failure. Other similar options select DRM_DISPLAY_HELPER, let's do the same here. This unmet dependency bug was found by kconfirm, a static analysis tool for Kconfig. Fixes: 68e28facbc8a ("drm/panel: ilitek-ili9882t: Select DRM_DISPLAY_DSC_HELPER") Signed-off-by: Julian Braha Reviewed-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260712002632.2323484-1-julianbraha@gmail.com --- drivers/gpu/drm/panel/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 7450b27622a2..613f09ccc7cb 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -332,6 +332,7 @@ config DRM_PANEL_ILITEK_ILI9882T depends on OF depends on DRM_MIPI_DSI depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_HELPER select DRM_DISPLAY_DSC_HELPER help Say Y if you want to enable support for panels based on the From 3667bc164849fee4f1b18b182bdfe643f758ca17 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 12 Jul 2026 01:15:14 +0100 Subject: [PATCH 172/456] drm/panel: s6e3ha8: fix unmet dependency on DRM_DISPLAY_HELPER Currently, DRM_PANEL_SAMSUNG_S6E3HA8 selects DRM_DISPLAY_DSC_HELPER without ensuring its dependency, DRM_DISPLAY_HELPER, is enabled, causing an unmet dependency. Let's select DRM_DISPLAY_HELPER as other similar options do. This unmet dependency bug was found by kconfirm, a static analysis tool for Kconfig. Fixes: fd3b2c5f40a1 ("drm/panel: s6e3ha8: select CONFIG_DRM_DISPLAY_DSC_HELPER") Signed-off-by: Julian Braha Reviewed-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260712001514.2318597-1-julianbraha@gmail.com --- drivers/gpu/drm/panel/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 613f09ccc7cb..fda7a3ee67e6 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -955,6 +955,7 @@ config DRM_PANEL_SAMSUNG_S6E3HA8 depends on OF depends on DRM_MIPI_DSI depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_HELPER select DRM_DISPLAY_DSC_HELPER help Say Y or M here if you want to enable support for the From aaa5be0258db1709e254b4150af5c2fa5518c548 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 12 Jul 2026 00:42:30 +0100 Subject: [PATCH 173/456] drm/panel: fix unmet dependency bug for DRM_PANEL_HIMAX_HX83121A Currently, DRM_PANEL_HIMAX_HX83121A selects DRM_DISPLAY_DSC_HELPER without also ensuring DRM_DISPLAY_HELPER is enabled, causing an unmet dependency: WARNING: unmet direct dependencies detected for DRM_DISPLAY_DSC_HELPER Depends on [n]: HAS_IOMEM [=y] && DRM [=m] && DRM_DISPLAY_HELPER [=n] Selected by [m]: - DRM_PANEL_HIMAX_HX83121A [=m] && HAS_IOMEM [=y] && DRM [=m] && DRM_PANEL [=y] && OF [=y] && DRM_MIPI_DSI [=y] && BACKLIGHT_CLASS_DEVICE [=m] - DRM_PANEL_ILITEK_ILI9882T [=m] && HAS_IOMEM [=y] && DRM [=m] && DRM_PANEL [=y] && OF [=y] && DRM_MIPI_DSI [=y] && BACKLIGHT_CLASS_DEVICE [=m] Many other DRM_PANEL_* options select DRM_DISPLAY_HELPER when selecting DRM_DISPLAY_DSC_HELPER, let's do the same here. This unmet dependency bug was found by kconfirm, a static analysis tool for Kconfig. Fixes: defab7b01e08 ("drm/panel: hx83121a: select DRM_DISPLAY_DSC_HELPER") Signed-off-by: Julian Braha Reviewed-by: Neil Armstrong Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260711234230.2236041-1-julianbraha@gmail.com --- drivers/gpu/drm/panel/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index fda7a3ee67e6..3eac4d0f88bc 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -232,6 +232,7 @@ config DRM_PANEL_HIMAX_HX83121A depends on OF depends on DRM_MIPI_DSI depends on BACKLIGHT_CLASS_DEVICE + select DRM_DISPLAY_HELPER select DRM_DISPLAY_DSC_HELPER select DRM_KMS_HELPER help From 7dc3680b7ffe01add3e9299fde8471d2dd53a8ae Mon Sep 17 00:00:00 2001 From: Gregor Herburger Date: Tue, 21 Jul 2026 17:38:19 +0200 Subject: [PATCH 174/456] drm/vc4: hvs/v3d: Fix null dereference in unbind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hvs and v3d drivers use dev_get_drvdata(master) in their unbind functions. Since the vc4-drm gets removed before its dependent drivers (vc4_hvs/vc4_v3d) the vc4_hvs_unbind/vc4_v3d_unbind functions try to get drvdata of its master and fails with a null dereference error. Use the data pointer passed to the unbind functions directly instead of dev_get_drvdata(master). This avoids using potentially freed memory. Fixes: d3f5168a0810 ("drm/vc4: Bind and initialize the V3D engine.") Fixes: c8b75bca92cb ("drm/vc4: Add KMS support for Raspberry Pi.") Signed-off-by: Gregor Herburger Link: https://patch.msgid.link/20260721-rpi-vc4-fix-v2-1-b813dcd01dc7@linutronix.de Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_hvs.c | 2 +- drivers/gpu/drm/vc4/vc4_v3d.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 184d51ea3fa5..e715147d091c 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -1752,7 +1752,7 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) static void vc4_hvs_unbind(struct device *dev, struct device *master, void *data) { - struct drm_device *drm = dev_get_drvdata(master); + struct drm_device *drm = data; struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_hvs *hvs = vc4->hvs; struct drm_mm_node *node, *next; diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index d31b906cb8e7..f32410420d3e 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -494,7 +494,7 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) static void vc4_v3d_unbind(struct device *dev, struct device *master, void *data) { - struct drm_device *drm = dev_get_drvdata(master); + struct drm_device *drm = data; struct vc4_dev *vc4 = to_vc4_dev(drm); vc4_irq_uninstall(drm); From a1e0eb8f55cfe09bb31a202a388babc411292656 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Sun, 12 Jul 2026 14:24:21 +0200 Subject: [PATCH 175/456] ovl: check access to copy_file_range source with src mounter creds Commit 5dae222a5ff0c ("vfs: allow copy_file_range to copy across devices") allowed filesystems that implement the copy_file_range() f_op to decide if they want to access cross-sb copy from/to the same fs type. The same commit added checks to verify same sb copy for filesystems that implement ->copy_file_range() and do not support cross-sb copy at the time, namely, to ceph, fuse and nfs. The two remaining fs which implement ->copy_file_range(), cifs and overlayfs started to support cross-sb copy from this time. While overlayfs does support cross-sb copy when the two underlying files are on the same base fs, the copy operation on the two real files from two different overalyfs filesystems is performed with the mounter creds of the destination overlayfs and the read permission access hook for the source file was called with the wrong creds. This could cause either deny of access to copy which would otherwise be allowed (e.g. with splice) or allow read access to file which would otherwise be denied. Fix the latter case by explicitly verifying read access to source file with the source overlayfs mounter creds. The former case remains a quirk of cross-sb overlayfs copy, but userspace could fall back to regular copy so no harm done. Fixes: 5dae222a5ff0c ("vfs: allow copy_file_range to copy across devices") Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260712122421.203113-1-amir73il@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/file.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index 27cc07738f33..f3d97eb146e8 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -528,6 +528,7 @@ static loff_t ovl_copyfile(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t len, unsigned int flags, enum ovl_copyop op) { + struct inode *inode_in = file_inode(file_in); struct inode *inode_out = file_inode(file_out); struct file *realfile_in, *realfile_out; loff_t ret; @@ -551,7 +552,20 @@ static loff_t ovl_copyfile(struct file *file_in, loff_t pos_in, if (IS_ERR(realfile_in)) goto out_unlock; - with_ovl_creds(file_inode(file_out)->i_sb) { + /* + * For cross-sb copy, vfs_copy_file_range() will verify read access with + * the mounter creds of the dest fs mounter, so we need to explicitly + * verify read access with the source mounter creds. + */ + if (unlikely(inode_in->i_sb != inode_out->i_sb)) { + with_ovl_creds(inode_in->i_sb) { + ret = rw_verify_area(READ, realfile_in, &pos_in, len); + if (unlikely(ret)) + goto out_unlock; + } + } + + with_ovl_creds(inode_out->i_sb) { switch (op) { case OVL_COPY: ret = vfs_copy_file_range(realfile_in, pos_in, From 1d0cff74d8c8d798a64c1e7fe442659aecb64130 Mon Sep 17 00:00:00 2001 From: Li Chen Date: Thu, 16 Jul 2026 13:28:20 +0800 Subject: [PATCH 176/456] pidfs: handle FS_IOC32_GETVERSION in compat ioctl FS_IOC32_GETVERSION has a distinct compat command encoding. Passing it through compat_ptr_ioctl() leaves pidfd_ioctl() unable to recognize the otherwise architecture-independent inode generation query. Translate the compat command to FS_IOC_GETVERSION before dispatching it through the native pidfd ioctl implementation. Signed-off-by: Li Chen Link: https://patch.msgid.link/20260716052822.1034228-1-me@linux.beauty Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/pidfs.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/fs/pidfs.c b/fs/pidfs.c index 695215aa2a58..d7fe9abdd6f1 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include @@ -659,6 +660,17 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return open_namespace(ns_common); } +#ifdef CONFIG_COMPAT +static long pidfd_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + if (cmd == FS_IOC32_GETVERSION) + cmd = FS_IOC_GETVERSION; + + return pidfd_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + static int pidfs_file_release(struct inode *inode, struct file *file) { struct pid *pid = inode->i_private; @@ -686,7 +698,9 @@ static const struct file_operations pidfs_file_operations = { .show_fdinfo = pidfd_show_fdinfo, #endif .unlocked_ioctl = pidfd_ioctl, - .compat_ioctl = compat_ptr_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = pidfd_compat_ioctl, +#endif }; struct pid *pidfd_pid(const struct file *file) From e148e567a9252643baa125cb65d7ae9c2c6cf68a Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 17 Jul 2026 11:06:45 +0900 Subject: [PATCH 177/456] ksmbd: preserve VFS inherited POSIX ACL mask The VFS initializes a child's POSIX ACL from the parent's default ACL and the requested creation mode. Do not mutate the parent ACL or overwrite the child's VFS-computed access and default ACLs afterwards. This preserves restrictive ACL_MASK entries and prevents SMB object creation from widening effective permissions. Reported-by: Charles Vosburgh Signed-off-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/vfs.c | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index d0a0ad15d803..d324585c0566 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -1886,10 +1886,6 @@ int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap, const struct path *path, struct inode *parent_inode) { struct posix_acl *acls; - struct posix_acl_entry *pace; - struct dentry *dentry = path->dentry; - struct inode *inode = d_inode(dentry); - int rc, i; if (!IS_ENABLED(CONFIG_FS_POSIX_ACL)) return -EOPNOTSUPP; @@ -1897,29 +1893,9 @@ int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap, acls = get_inode_acl(parent_inode, ACL_TYPE_DEFAULT); if (IS_ERR_OR_NULL(acls)) return -ENOENT; - pace = acls->a_entries; - - for (i = 0; i < acls->a_count; i++, pace++) { - if (pace->e_tag == ACL_MASK) { - pace->e_perm = 0x07; - break; - } - } - - rc = set_posix_acl(idmap, dentry, ACL_TYPE_ACCESS, acls); - if (rc < 0) - ksmbd_debug(SMB, "Set posix acl(ACL_TYPE_ACCESS) failed, rc : %d\n", - rc); - if (S_ISDIR(inode->i_mode)) { - rc = set_posix_acl(idmap, dentry, ACL_TYPE_DEFAULT, - acls); - if (rc < 0) - ksmbd_debug(SMB, "Set posix acl(ACL_TYPE_DEFAULT) failed, rc : %d\n", - rc); - } posix_acl_release(acls); - return rc; + return 0; } void ksmbd_vfs_update_compressed_fattr(struct dentry *dentry, __le32 *fattr) From 2bebf2470af1a72f87754a5c7b21e86af32b9c8f Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 17 Jul 2026 11:32:00 +0900 Subject: [PATCH 178/456] ksmbd: enforce signing required by the session SMB2_FLAGS_SIGNED is controlled by the incoming request and only indicates that a signature accompanies that request. Do not use it to decide whether a signing-required session must authenticate the request. Reject an unsigned plaintext request before dispatch when the session requires signing. Continue to validate signatures on signed requests, including when signing is optional. Encrypted requests have already been authenticated during decryption. An OPLOCK_BREAK acknowledgment is a session request and is subject to the same signing rule, so do not exclude it from signed-request detection. Reported-by: Charles Vosburgh Tested-by: ChenXiaoSong Reviewed-by: ChenXiaoSong Signed-off-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/server.c | 10 +++++++++- fs/smb/server/smb2pdu.c | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c index f5baba934840..960c4c897c11 100644 --- a/fs/smb/server/server.c +++ b/fs/smb/server/server.c @@ -112,6 +112,7 @@ static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn, { struct smb_version_cmds *cmds; u16 command; + bool signed_req; int ret; if (check_conn_state(work)) @@ -138,7 +139,14 @@ static int __process_request(struct ksmbd_work *work, struct ksmbd_conn *conn, return SERVER_HANDLER_ABORT; } - if (work->sess && conn->ops->is_sign_req(work, command)) { + signed_req = conn->ops->is_sign_req && conn->ops->is_sign_req(work, command); + if (work->sess && work->sess->sign && !work->encrypted && + !signed_req) { + conn->ops->set_rsp_status(work, STATUS_ACCESS_DENIED); + return SERVER_HANDLER_ABORT; + } + + if (work->sess && signed_req) { ret = conn->ops->check_sign_req(work); if (!ret) { conn->ops->set_rsp_status(work, STATUS_ACCESS_DENIED); diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index bec692bca1ca..d54b714cc36c 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -9596,8 +9596,7 @@ bool smb2_is_sign_req(struct ksmbd_work *work, unsigned int command) struct smb2_hdr *rcv_hdr2 = smb_get_msg(work->request_buf); if ((rcv_hdr2->Flags & SMB2_FLAGS_SIGNED) && - command != SMB2_NEGOTIATE_HE && - command != SMB2_OPLOCK_BREAK_HE) + command != SMB2_NEGOTIATE_HE) return true; return false; From 58d97fcd0bf1aee694e244cc28635b9df95b543b Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 3 Jul 2026 10:54:19 +0900 Subject: [PATCH 179/456] ksmbd: bound DACL dedup walk to copied ACEs set_ntacl_dacl() can stop copying ACEs before consuming the full input DACL when size accounting overflows. When that happens, num_aces reflects only the ACEs that were actually copied into the output DACL, but set_posix_acl_entries_dacl() still receives nt_num_aces and uses it to walk the existing ACE array during dedup. That makes the dedup walk scan past the copied ACE array and inspect buffer tail that does not contain valid ACEs. Split the two meanings currently carried by the NT ACE count. Pass the number of copied NT ACEs to bound the dedup walk, and preserve the original "input DACL had NT ACEs" state separately for the Everyone/default ACL fallback. This keeps the dedup walk aligned with the ACEs that are actually present in the rebuilt DACL. Signed-off-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/smbacl.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index 67b39b4d218c..d28289188e44 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -608,7 +608,8 @@ static void parse_dacl(struct mnt_idmap *idmap, static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, struct smb_ace *pndace, struct smb_fattr *fattr, u16 *num_aces, - u16 *size, u32 nt_aces_num) + u16 *size, u16 existing_nt_aces, + bool had_nt_aces) { struct posix_acl_entry *pace; struct smb_sid *sid; @@ -640,14 +641,14 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, gid = posix_acl_gid_translate(idmap, pace); id_to_sid(gid, SIDUNIX_GROUP, sid); - } else if (pace->e_tag == ACL_OTHER && !nt_aces_num) { + } else if (pace->e_tag == ACL_OTHER && !had_nt_aces) { smb_copy_sid(sid, &sid_everyone); } else { kfree(sid); continue; } ntace = pndace; - for (j = 0; j < nt_aces_num; j++) { + for (j = 0; j < existing_nt_aces; j++) { if (ntace->sid.sub_auth[ntace->sid.num_subauth - 1] == sid->sub_auth[sid->num_subauth - 1]) goto pass_same_sid; @@ -689,7 +690,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, kfree(sid); } - if (nt_aces_num) + if (had_nt_aces) return; posix_default_acl: @@ -742,6 +743,7 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, { struct smb_ace *ntace, *pndace; u16 nt_num_aces = le16_to_cpu(nt_dacl->num_aces), num_aces = 0; + u16 copied_nt_aces; unsigned short size = 0; int i; @@ -773,8 +775,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, } } + copied_nt_aces = num_aces; set_posix_acl_entries_dacl(idmap, pndace, fattr, - &num_aces, &size, nt_num_aces); + &num_aces, &size, copied_nt_aces, + nt_num_aces != 0); pndacl->num_aces = cpu_to_le16(num_aces); pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); } @@ -792,7 +796,7 @@ static void set_mode_dacl(struct mnt_idmap *idmap, if (fattr->cf_acls) { set_posix_acl_entries_dacl(idmap, pndace, fattr, - &num_aces, &size, num_aces); + &num_aces, &size, num_aces, false); goto out; } From bbf0a8e931204ecdab494a88d43b0a24a04285c5 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Fri, 3 Jul 2026 11:22:09 +0900 Subject: [PATCH 180/456] ksmbd: restore DACL size on check_add_overflow() to avoid malformed ACL check_add_overflow() unconditionally writes the truncated sum into *d even on overflow, per its contract in include/linux/overflow.h. The four check_add_overflow() guards in set_posix_acl_entries_dacl() and set_ntacl_dacl() break out of the ACE-building loops on overflow, but the truncated *size is then consumed downstream at the end of set_ntacl_dacl(): pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); This produces an on-wire NT ACL whose pndacl->size under-reports the bytes actually written by the preceding fill_ace_for_sid()/memcpy() calls, yielding a malformed ACL that can trigger out-of-bounds reads when re-parsed by clients or ksmbd itself. Restore *size to its pre-addition value on each overflow branch (via `*size -= ace_sz` / `size -= nt_ace_size`) so that after the break, *size once again holds the cumulative size of the successfully-written ACEs. The committed ACL is then truncated-but-self-consistent rather than malformed. The ksmbd DACL builders are the only check_add_overflow() sites found where an overflow path breaks out of a loop and the destination value is consumed afterward. The other nearby break-style cases either return -EINVAL on overflow (transport_ipc.c) or break without consuming the overflowed destination value afterward (buildid.c). Fixes: 299f962c0b02 ("ksmbd: use check_add_overflow() to prevent u16 DACL size overflow") Assisted-by: atomcode:glm-5.2 Assisted-by: Codex:gpt-5.5 Cc: stable@vger.kernel.org Signed-off-by: Wentao Guan Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/smbacl.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index d28289188e44..f285b4f24a5b 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -663,6 +663,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, flags, pace->e_perm, 0777); if (check_add_overflow(*size, ace_sz, size)) { + *size -= ace_sz; kfree(sid); break; } @@ -677,6 +678,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, 0x03, pace->e_perm, 0777); if (check_add_overflow(*size, ace_sz, size)) { + *size -= ace_sz; kfree(sid); break; } @@ -722,6 +724,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, ace_sz = fill_ace_for_sid(ntace, sid, ACCESS_ALLOWED, 0x0b, pace->e_perm, 0777); if (check_add_overflow(*size, ace_sz, size)) { + *size -= ace_sz; kfree(sid); break; } @@ -765,8 +768,10 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, goto next_ace; memcpy((char *)pndace + size, ntace, nt_ace_size); - if (check_add_overflow(size, nt_ace_size, &size)) + if (check_add_overflow(size, nt_ace_size, &size)) { + size -= nt_ace_size; break; + } num_aces++; next_ace: From 5152c6d49e3fd4e9f2e857c57527aead752f1f87 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 4 Jul 2026 11:07:51 +0900 Subject: [PATCH 181/456] ksmbd: validate ACE size against SID sub-authorities set_ntacl_dacl() validates sid.num_subauth before copying an ACE, but does not verify that the declared ACE size contains all sub-authorities described by that field. An undersized ACE can therefore be copied and later make the POSIX ACL deduplication walk inspect data beyond the copied ACE boundary. The existing initial bound check is also too small. It only ensures that the ACE size field is accessible before set_ntacl_dacl() reads sid.num_subauth farther into the input buffer. Require enough input for the fixed SID header before accessing num_subauth, reject ACEs smaller than that header, and skip ACEs whose declared size cannot contain the complete SID. This makes the validation consistent with the other ACE walk paths. Reported-by: LocalHost Signed-off-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/smbacl.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index f285b4f24a5b..c13f07a09ab8 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -756,15 +756,22 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, for (i = 0; i < nt_num_aces; i++) { unsigned short nt_ace_size; - if (offsetof(struct smb_ace, access_req) > aces_size) + if (aces_size < offsetof(struct smb_ace, sid) + + CIFS_SID_BASE_SIZE) break; nt_ace_size = le16_to_cpu(ntace->size); - if (nt_ace_size > aces_size) + if (nt_ace_size > aces_size || + nt_ace_size < offsetof(struct smb_ace, sid) + + CIFS_SID_BASE_SIZE) break; if (ntace->sid.num_subauth == 0 || - ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) + ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES || + nt_ace_size < offsetof(struct smb_ace, sid) + + CIFS_SID_BASE_SIZE + + sizeof(__le32) * + ntace->sid.num_subauth) goto next_ace; memcpy((char *)pndace + size, ntace, nt_ace_size); From c74801ee524f477c174a1899782b6c3b6918d407 Mon Sep 17 00:00:00 2001 From: James Montgomery Date: Fri, 3 Jul 2026 15:26:41 -0400 Subject: [PATCH 182/456] ksmbd: defer destroy_previous_session() until after NTLM authentication In ntlm_authenticate(), destroy_previous_session() is called using a user pointer resolved from the client-supplied NTLM blob username field before the NTLMv2 response is validated. An authenticated attacker can set the NTLM blob username to match a victim account and set PreviousSessionId to the victim's session ID; destroy_previous_session() destroys the victim's session while ksmbd_decode_ntlmssp_auth_blob() subsequently rejects the request with -EPERM. Move destroy_previous_session() and the prev_id assignment to after ksmbd_decode_ntlmssp_auth_blob() returns success and use sess->user rather than the pre-authentication lookup result. This matches the ordering already used by krb5_authenticate(), where destroy_previous_session() is called only after ksmbd_krb5_authenticate() returns success. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/linux-cifs/20260702155449.3639773-1-james_montgomery@disroot.org/ Signed-off-by: James Montgomery Acked-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/smb2pdu.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index d54b714cc36c..c1ba5e01aa7f 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -1717,11 +1717,6 @@ static int ntlm_authenticate(struct ksmbd_work *work, return -EPERM; } - /* Check for previous session */ - prev_id = le64_to_cpu(req->PreviousSessionId); - if (prev_id && prev_id != sess->id) - destroy_previous_session(conn, user, prev_id); - if (sess->state == SMB2_SESSION_VALID) { /* * Reuse session if anonymous try to connect @@ -1761,6 +1756,10 @@ static int ntlm_authenticate(struct ksmbd_work *work, } } + prev_id = le64_to_cpu(req->PreviousSessionId); + if (prev_id && prev_id != sess->id) + destroy_previous_session(conn, sess->user, prev_id); + /* * If session state is SMB2_SESSION_VALID, We can assume * that it is reauthentication. And the user/password From cfc0b8e5080aec87700774e8568765eaa4b7b92b Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 4 Jul 2026 11:30:27 +0900 Subject: [PATCH 183/456] ksmbd: validate minimum PDU size for transform requests The receive path applies the minimum SMB2 PDU size check only when ProtocolId is SMB2_PROTO_NUMBER. A packet carrying SMB2_TRANSFORM_PROTO_NUM bypasses the check even when the negotiated dialect does not provide transform handling. On an SMB 2.1 connection, a short transform packet therefore reaches init_smb2_rsp_hdr(), which interprets the request as a full SMB2 header and reads beyond the request allocation. The copied fields can then be returned to the unauthenticated client. Compression transforms are converted to ordinary SMB2 messages before protocol validation. After that conversion, validate ordinary SMB2 requests against SMB2_MIN_SUPPORTED_PDU_SIZE and require encryption transform requests to contain both a transform header and an SMB2 header. This rejects truncated requests before work allocation. Fixes: 368ba06881c3 ("ksmbd: check the validation of pdu_size in ksmbd_conn_handler_loop") Cc: stable@vger.kernel.org Reported-by: zdi-disclosures@trendmicro.com # ZDI-CAN-31063 Signed-off-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/connection.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c index 9e8fdb39e5a2..dee8e4aced99 100644 --- a/fs/smb/server/connection.c +++ b/fs/smb/server/connection.c @@ -441,6 +441,8 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn) /* "+2" for BCC field (ByteCount, 2 bytes) */ #define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2) #define SMB2_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb2_pdu)) +#define SMB2_TRANSFORM_MIN_SUPPORTED_PDU_SIZE \ + (sizeof(struct smb2_transform_hdr) + sizeof(struct smb2_hdr)) /** * ksmbd_conn_handler_loop() - session thread to listen on new smb requests @@ -455,6 +457,7 @@ int ksmbd_conn_handler_loop(void *p) struct ksmbd_conn *conn = (struct ksmbd_conn *)p; struct ksmbd_transport *t = conn->transport; unsigned int pdu_size, max_allowed_pdu_size, max_req; + __le32 proto; char hdr_buf[4] = {0,}; int size; @@ -546,11 +549,14 @@ int ksmbd_conn_handler_loop(void *p) if (!ksmbd_smb_request(conn)) break; - if (((struct smb2_hdr *)smb_get_msg(conn->request_buf))->ProtocolId == - SMB2_PROTO_NUMBER) { - if (pdu_size < SMB2_MIN_SUPPORTED_PDU_SIZE) - break; - } + proto = *(__le32 *)smb_get_msg(conn->request_buf); + if (proto == SMB2_PROTO_NUMBER && + pdu_size < SMB2_MIN_SUPPORTED_PDU_SIZE) + break; + + if (proto == SMB2_TRANSFORM_PROTO_NUM && + pdu_size < SMB2_TRANSFORM_MIN_SUPPORTED_PDU_SIZE) + break; if (!default_conn_ops.process_fn) { pr_err("No connection request callback\n"); From 5e1b924808568e89c5cb132ecebe1824bd91af0c Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 4 Jul 2026 12:23:14 +0900 Subject: [PATCH 184/456] ksmbd: reject undersized decompressed SMB2 requests ksmbd_decompress_request() bounds the decompressed size only against the maximum request size. A compression transform can therefore produce a buffer smaller than an SMB2 PDU and install it as conn->request_buf. The receive path subsequently calls ksmbd_smb_request(), which reads the protocol ID before the normal SMB2 minimum-size check. If the decompressed output is too short, that read can access beyond the request allocation. Require the decompressed output to contain at least a complete minimum SMB2 PDU before allocating and installing the replacement request buffer. Fixes: a08de24c2b85 ("ksmbd: negotiate and decode SMB2 compression") Cc: stable@vger.kernel.org Signed-off-by: Namjae Jeon Signed-off-by: Steve French --- fs/smb/server/compress.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c index f8cf515b9c30..95e48fa6b448 100644 --- a/fs/smb/server/compress.c +++ b/fs/smb/server/compress.c @@ -56,7 +56,8 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn) } max_allowed_pdu_size = SMB3_MAX_MSGSIZE + conn->vals->max_write_size; - if (out_size > max_allowed_pdu_size || + if (out_size < sizeof(struct smb2_pdu) || + out_size > max_allowed_pdu_size || out_size > MAX_STREAM_PROT_LEN) return -EINVAL; From af78c0020480aba3132fb9fa4db0fd1fb949feaa Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Wed, 22 Jul 2026 14:42:15 +0100 Subject: [PATCH 185/456] io_uring/zcrx: drop "notif" from stats struct names Keep zcrx statistics generic and don't stick "notif" to its uapi definitions. Stats dosn't need to be bound to notification details, it makes it cleaner and more readable. Signed-off-by: Pavel Begunkov Link: https://patch.msgid.link/6a39676b6f71b67d3f89c6ebab7a3739873834a3.1784726895.git.asml.silence@gmail.com Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/query.h | 6 +++--- include/uapi/linux/io_uring/zcrx.h | 4 ++-- io_uring/query.c | 4 ++-- io_uring/zcrx.c | 4 ++-- io_uring/zcrx.h | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/uapi/linux/io_uring/query.h b/include/uapi/linux/io_uring/query.h index 1a68eca7c6b4..708bcdd585dd 100644 --- a/include/uapi/linux/io_uring/query.h +++ b/include/uapi/linux/io_uring/query.h @@ -66,10 +66,10 @@ struct io_uring_query_zcrx { struct io_uring_query_zcrx_notif { /* Bitmask of supported ZCRX_NOTIF_* flags */ __u32 notif_flags; - /* Size of io_uring_zcrx_notif_stats */ - __u32 notif_stats_size; + /* Size of zcrx_stats */ + __u32 stats_size; /* Required alignment for the stats struct within the region (ie stats_offset) */ - __u32 notif_stats_off_alignment; + __u32 stats_off_alignment; __u32 __resv1; __u64 __resv2[4]; }; diff --git a/include/uapi/linux/io_uring/zcrx.h b/include/uapi/linux/io_uring/zcrx.h index 15c05c45ce36..99e56ec26ead 100644 --- a/include/uapi/linux/io_uring/zcrx.h +++ b/include/uapi/linux/io_uring/zcrx.h @@ -76,11 +76,11 @@ enum zcrx_notification_type { }; enum zcrx_notification_desc_flags { - /* If set, stats_offset holds a valid offset to a notif_stats struct */ + /* If set, stats_offset holds a valid offset to a zcrx_stats struct */ ZCRX_NOTIF_DESC_FLAG_STATS = 1 << 0, }; -struct zcrx_notif_stats { +struct zcrx_stats { __u64 copy_count; /* cumulative copy-fallback CQEs */ __u64 copy_bytes; /* cumulative bytes copied */ }; diff --git a/io_uring/query.c b/io_uring/query.c index d529d94aa8f4..2e48fddd8d3a 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -50,8 +50,8 @@ static ssize_t io_query_zcrx_notif(union io_query_data *data) struct io_uring_query_zcrx_notif *e = &data->zcrx_notif; e->notif_flags = ZCRX_NOTIF_TYPE_MASK; - e->notif_stats_size = sizeof(struct zcrx_notif_stats); - e->notif_stats_off_alignment = __alignof__(struct zcrx_notif_stats); + e->stats_size = sizeof(struct zcrx_stats); + e->stats_off_alignment = __alignof__(struct zcrx_stats); e->__resv1 = 0; memset(&e->__resv2, 0, sizeof(e->__resv2)); return sizeof(*e); diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 49163f9c39df..53dcb5110719 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -863,12 +863,12 @@ static int zcrx_validate_notif_stats(struct io_zcrx_ifq *ifq, used = reg->offsets.rqes + sizeof(struct io_uring_zcrx_rqe) * reg->rq_entries; - if (!IS_ALIGNED(stats_off, __alignof__(struct zcrx_notif_stats))) + if (!IS_ALIGNED(stats_off, __alignof__(struct zcrx_stats))) return -EINVAL; if (stats_off < used) return -ERANGE; if (check_add_overflow(stats_off, - sizeof(struct zcrx_notif_stats), + sizeof(struct zcrx_stats), &end)) return -ERANGE; if (end > io_region_size(&ifq->rq_region)) diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h index fa00900e479e..a8d301b6191f 100644 --- a/io_uring/zcrx.h +++ b/io_uring/zcrx.h @@ -80,7 +80,7 @@ struct io_zcrx_ifq { u32 allowed_notif_mask; u32 fired_notifs; u64 notif_data; - struct zcrx_notif_stats *notif_stats; + struct zcrx_stats *notif_stats; }; #if defined(CONFIG_IO_URING_ZCRX) From 201e30810223a40275f17c5e6ee410fa857b6b44 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Wed, 22 Jul 2026 14:42:16 +0100 Subject: [PATCH 186/456] io_uring/zcrx: rename ZCRX_NOTIF_NO_BUFFERS ZCRX_NOTIF_NO_BUFFERS tells when page pool fails to allocate memory from zcrx. "No buffers" could be more confusing, rename it to ZCRX_NOTIF_ALLOC_FAIL. Signed-off-by: Pavel Begunkov Link: https://patch.msgid.link/29bd4fc069bc89691868beba0627ffbe570c2722.1784726895.git.asml.silence@gmail.com Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/zcrx.h | 2 +- io_uring/zcrx.c | 2 +- io_uring/zcrx.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/uapi/linux/io_uring/zcrx.h b/include/uapi/linux/io_uring/zcrx.h index 99e56ec26ead..abb898d59a4b 100644 --- a/include/uapi/linux/io_uring/zcrx.h +++ b/include/uapi/linux/io_uring/zcrx.h @@ -69,7 +69,7 @@ enum zcrx_features { }; enum zcrx_notification_type { - ZCRX_NOTIF_NO_BUFFERS, + ZCRX_NOTIF_ALLOC_FAIL, ZCRX_NOTIF_COPY, __ZCRX_NOTIF_TYPE_LAST, diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 53dcb5110719..872d5794d3de 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -1244,7 +1244,7 @@ static netmem_ref io_pp_zc_alloc_netmems(struct page_pool *pp, gfp_t gfp) allocated = io_zcrx_refill_slow(pp, ifq, netmems, to_alloc); if (!allocated) { - zcrx_send_notif(ifq, ZCRX_NOTIF_NO_BUFFERS); + zcrx_send_notif(ifq, ZCRX_NOTIF_ALLOC_FAIL); return 0; } out_return: diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h index a8d301b6191f..d16206eb9e30 100644 --- a/io_uring/zcrx.h +++ b/io_uring/zcrx.h @@ -11,7 +11,7 @@ #define ZCRX_SUPPORTED_REG_FLAGS (ZCRX_REG_IMPORT | ZCRX_REG_NODEV) #define ZCRX_FEATURES (ZCRX_FEATURE_RX_PAGE_SIZE |\ ZCRX_FEATURE_NOTIFICATION) -#define ZCRX_NOTIF_TYPE_MASK ((1U << ZCRX_NOTIF_NO_BUFFERS) | (1U << ZCRX_NOTIF_COPY)) +#define ZCRX_NOTIF_TYPE_MASK ((1U << ZCRX_NOTIF_ALLOC_FAIL) | (1U << ZCRX_NOTIF_COPY)) struct io_zcrx_mem { unsigned long size; From e366c15e1610ef11d0717ecd875ae63050282676 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Wed, 22 Jul 2026 14:42:17 +0100 Subject: [PATCH 187/456] io_uring/zcrx: rename notif to event "Notification" is too long and the abbreviated version is used in several places, which is inconsistent and more ambiguous for users. Rename it to event, which is easier to keep consistent. To keep the change small, only change uapi/ + do necessary fix ups, and the rest of internals can be adjusted in the next release. Signed-off-by: Pavel Begunkov Link: https://patch.msgid.link/f95ca6717da3c8d3649a1a7f0d883a563f545052.1784726895.git.asml.silence@gmail.com Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/query.h | 8 ++++---- include/uapi/linux/io_uring/zcrx.h | 28 +++++++++++++-------------- io_uring/query.c | 8 ++++---- io_uring/zcrx.c | 30 ++++++++++++++--------------- io_uring/zcrx.h | 5 +++-- 5 files changed, 40 insertions(+), 39 deletions(-) diff --git a/include/uapi/linux/io_uring/query.h b/include/uapi/linux/io_uring/query.h index 708bcdd585dd..e6493e4a7a4d 100644 --- a/include/uapi/linux/io_uring/query.h +++ b/include/uapi/linux/io_uring/query.h @@ -23,7 +23,7 @@ enum { IO_URING_QUERY_OPCODES = 0, IO_URING_QUERY_ZCRX = 1, IO_URING_QUERY_SCQ = 2, - IO_URING_QUERY_ZCRX_NOTIF = 3, + IO_URING_QUERY_ZCRX_EVENT = 3, __IO_URING_QUERY_MAX, }; @@ -63,9 +63,9 @@ struct io_uring_query_zcrx { __u64 __resv2; }; -struct io_uring_query_zcrx_notif { - /* Bitmask of supported ZCRX_NOTIF_* flags */ - __u32 notif_flags; +struct io_uring_query_zcrx_event { + /* Bitmask of supported ZCRX_EVENT_* flags */ + __u32 event_flags; /* Size of zcrx_stats */ __u32 stats_size; /* Required alignment for the stats struct within the region (ie stats_offset) */ diff --git a/include/uapi/linux/io_uring/zcrx.h b/include/uapi/linux/io_uring/zcrx.h index abb898d59a4b..e01bc0e34b24 100644 --- a/include/uapi/linux/io_uring/zcrx.h +++ b/include/uapi/linux/io_uring/zcrx.h @@ -65,19 +65,19 @@ enum zcrx_features { * value in struct io_uring_zcrx_ifq_reg::rx_buf_len. */ ZCRX_FEATURE_RX_PAGE_SIZE = 1 << 0, - ZCRX_FEATURE_NOTIFICATION = 1 << 1, + ZCRX_FEATURE_EVENT = 1 << 1, }; -enum zcrx_notification_type { - ZCRX_NOTIF_ALLOC_FAIL, - ZCRX_NOTIF_COPY, +enum zcrx_event_type { + ZCRX_EVENT_ALLOC_FAIL, + ZCRX_EVENT_COPY, - __ZCRX_NOTIF_TYPE_LAST, + __ZCRX_EVENT_TYPE_LAST, }; -enum zcrx_notification_desc_flags { +enum zcrx_event_desc_flags { /* If set, stats_offset holds a valid offset to a zcrx_stats struct */ - ZCRX_NOTIF_DESC_FLAG_STATS = 1 << 0, + ZCRX_EVENT_DESC_FLAG_STATS = 1 << 0, }; struct zcrx_stats { @@ -85,10 +85,10 @@ struct zcrx_stats { __u64 copy_bytes; /* cumulative bytes copied */ }; -struct zcrx_notification_desc { +struct zcrx_event_desc { __u64 user_data; __u32 type_mask; - __u32 flags; /* see enum zcrx_notification_desc_flags */ + __u32 flags; /* see enum zcrx_event_desc_flags */ __u64 stats_offset; /* offset from the beginning of refill ring region for stats */ __u64 __resv2[9]; }; @@ -108,14 +108,14 @@ struct io_uring_zcrx_ifq_reg { struct io_uring_zcrx_offsets offsets; __u32 zcrx_id; __u32 rx_buf_len; - __u64 notif_desc; /* see struct zcrx_notification_desc */ + __u64 event_desc; /* see struct zcrx_event_desc */ __u64 __resv[2]; }; enum zcrx_ctrl_op { ZCRX_CTRL_FLUSH_RQ, ZCRX_CTRL_EXPORT, - ZCRX_CTRL_ARM_NOTIFICATION, + ZCRX_CTRL_ARM_EVENT, __ZCRX_CTRL_LAST, }; @@ -129,8 +129,8 @@ struct zcrx_ctrl_export { __u32 __resv1[11]; }; -struct zcrx_ctrl_arm_notif { - __u32 notif_type; +struct zcrx_ctrl_arm_event { + __u32 event_type; /* see enum zcrx_event_type */ __u32 __resv[11]; }; @@ -142,7 +142,7 @@ struct zcrx_ctrl { union { struct zcrx_ctrl_export zc_export; struct zcrx_ctrl_flush_rq zc_flush; - struct zcrx_ctrl_arm_notif zc_arm_notif; + struct zcrx_ctrl_arm_event zc_arm_event; }; }; diff --git a/io_uring/query.c b/io_uring/query.c index 2e48fddd8d3a..88a325736992 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -9,7 +9,7 @@ union io_query_data { struct io_uring_query_opcode opcodes; struct io_uring_query_zcrx zcrx; - struct io_uring_query_zcrx_notif zcrx_notif; + struct io_uring_query_zcrx_event zcrx_notif; struct io_uring_query_scq scq; }; @@ -47,9 +47,9 @@ static ssize_t io_query_zcrx(union io_query_data *data) static ssize_t io_query_zcrx_notif(union io_query_data *data) { - struct io_uring_query_zcrx_notif *e = &data->zcrx_notif; + struct io_uring_query_zcrx_event *e = &data->zcrx_notif; - e->notif_flags = ZCRX_NOTIF_TYPE_MASK; + e->event_flags = ZCRX_EVENT_TYPE_MASK; e->stats_size = sizeof(struct zcrx_stats); e->stats_off_alignment = __alignof__(struct zcrx_stats); e->__resv1 = 0; @@ -96,7 +96,7 @@ static int io_handle_query_entry(union io_query_data *data, void __user *uhdr, case IO_URING_QUERY_ZCRX: ret = io_query_zcrx(data); break; - case IO_URING_QUERY_ZCRX_NOTIF: + case IO_URING_QUERY_ZCRX_EVENT: ret = io_query_zcrx_notif(data); break; case IO_URING_QUERY_SCQ: diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 872d5794d3de..7d9fcf34539a 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -766,7 +766,7 @@ static int import_zcrx(struct io_ring_ctx *ctx, return -EINVAL; if (reg->if_rxq || reg->rq_entries || reg->area_ptr || reg->region_ptr) return -EINVAL; - if (reg->notif_desc) + if (reg->event_desc) return -EINVAL; if (reg->flags & ~ZCRX_REG_IMPORT) return -EINVAL; @@ -855,7 +855,7 @@ static int zcrx_register_netdev(struct io_zcrx_ifq *ifq, static int zcrx_validate_notif_stats(struct io_zcrx_ifq *ifq, const struct io_uring_zcrx_ifq_reg *reg, - const struct zcrx_notification_desc *notif) + const struct zcrx_event_desc *notif) { size_t stats_off = notif->stats_offset; size_t used, end; @@ -883,7 +883,7 @@ static int zcrx_validate_notif_stats(struct io_zcrx_ifq *ifq, int io_register_zcrx(struct io_ring_ctx *ctx, struct io_uring_zcrx_ifq_reg __user *arg) { - struct zcrx_notification_desc notif; + struct zcrx_event_desc notif; struct io_uring_zcrx_area_reg area; struct io_uring_zcrx_ifq_reg reg; struct io_uring_region_desc rd; @@ -928,14 +928,14 @@ int io_register_zcrx(struct io_ring_ctx *ctx, return -EFAULT; memset(¬if, 0, sizeof(notif)); - if (reg.notif_desc && copy_from_user(¬if, u64_to_user_ptr(reg.notif_desc), + if (reg.event_desc && copy_from_user(¬if, u64_to_user_ptr(reg.event_desc), sizeof(notif))) return -EFAULT; - if (notif.type_mask & ~ZCRX_NOTIF_TYPE_MASK) + if (notif.type_mask & ~ZCRX_EVENT_TYPE_MASK) return -EINVAL; - if (notif.flags & ~ZCRX_NOTIF_DESC_FLAG_STATS) + if (notif.flags & ~ZCRX_EVENT_DESC_FLAG_STATS) return -EINVAL; - if (!(notif.flags & ZCRX_NOTIF_DESC_FLAG_STATS)) { + if (!(notif.flags & ZCRX_EVENT_DESC_FLAG_STATS)) { if (notif.stats_offset) return -EINVAL; } @@ -970,7 +970,7 @@ int io_register_zcrx(struct io_ring_ctx *ctx, if (ret) goto err; - if (notif.flags & ZCRX_NOTIF_DESC_FLAG_STATS) { + if (notif.flags & ZCRX_EVENT_DESC_FLAG_STATS) { ret = zcrx_validate_notif_stats(ifq, ®, ¬if); if (ret) goto err; @@ -1244,7 +1244,7 @@ static netmem_ref io_pp_zc_alloc_netmems(struct page_pool *pp, gfp_t gfp) allocated = io_zcrx_refill_slow(pp, ifq, netmems, to_alloc); if (!allocated) { - zcrx_send_notif(ifq, ZCRX_NOTIF_ALLOC_FAIL); + zcrx_send_notif(ifq, ZCRX_EVENT_ALLOC_FAIL); return 0; } out_return: @@ -1398,16 +1398,16 @@ static int zcrx_flush_rq(struct io_ring_ctx *ctx, struct io_zcrx_ifq *zcrx, static int zcrx_arm_notif(struct io_ring_ctx *ctx, struct io_zcrx_ifq *zcrx, struct zcrx_ctrl *ctrl) { - const struct zcrx_ctrl_arm_notif *an = &ctrl->zc_arm_notif; + const struct zcrx_ctrl_arm_event *an = &ctrl->zc_arm_event; unsigned type_mask; - if (an->notif_type >= __ZCRX_NOTIF_TYPE_LAST) + if (an->event_type >= __ZCRX_EVENT_TYPE_LAST) return -EINVAL; if (!mem_is_zero(&an->__resv, sizeof(an->__resv))) return -EINVAL; guard(spinlock_bh)(&zcrx->ctx_lock); - type_mask = 1U << an->notif_type; + type_mask = 1U << an->event_type; if (type_mask & ~zcrx->fired_notifs) return -EINVAL; zcrx->fired_notifs &= ~type_mask; @@ -1420,7 +1420,7 @@ int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) struct io_zcrx_ifq *zcrx; BUILD_BUG_ON(sizeof(ctrl.zc_export) != sizeof(ctrl.zc_flush)); - BUILD_BUG_ON(sizeof(ctrl.zc_export) != sizeof(ctrl.zc_arm_notif)); + BUILD_BUG_ON(sizeof(ctrl.zc_export) != sizeof(ctrl.zc_arm_event)); if (nr_args) return -EINVAL; @@ -1438,7 +1438,7 @@ int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) return zcrx_flush_rq(ctx, zcrx, &ctrl); case ZCRX_CTRL_EXPORT: return zcrx_export(ctx, zcrx, &ctrl, arg); - case ZCRX_CTRL_ARM_NOTIFICATION: + case ZCRX_CTRL_ARM_EVENT: return zcrx_arm_notif(ctx, zcrx, &ctrl); } @@ -1584,7 +1584,7 @@ static int io_zcrx_copy_frag(struct io_kiocb *req, struct io_zcrx_ifq *ifq, zcrx_stat_add(&ifq->notif_stats->copy_count, 1); zcrx_stat_add(&ifq->notif_stats->copy_bytes, ret); } - zcrx_send_notif(ifq, ZCRX_NOTIF_COPY); + zcrx_send_notif(ifq, ZCRX_EVENT_COPY); } return ret; diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h index d16206eb9e30..c1005f23caff 100644 --- a/io_uring/zcrx.h +++ b/io_uring/zcrx.h @@ -10,8 +10,9 @@ #define ZCRX_SUPPORTED_REG_FLAGS (ZCRX_REG_IMPORT | ZCRX_REG_NODEV) #define ZCRX_FEATURES (ZCRX_FEATURE_RX_PAGE_SIZE |\ - ZCRX_FEATURE_NOTIFICATION) -#define ZCRX_NOTIF_TYPE_MASK ((1U << ZCRX_NOTIF_ALLOC_FAIL) | (1U << ZCRX_NOTIF_COPY)) + ZCRX_FEATURE_EVENT) +#define ZCRX_EVENT_TYPE_MASK ((1U << ZCRX_EVENT_ALLOC_FAIL) |\ + (1U << ZCRX_EVENT_COPY)) struct io_zcrx_mem { unsigned long size; From db3d0e0e5d4bc5ab4fe445b9f413d1b486508ca5 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sun, 12 Jul 2026 16:42:01 -0700 Subject: [PATCH 188/456] netfilter: nf_conntrack_sip: widen NAT rewrite delta to s32 in sip_help_tcp() sip_help_tcp() stores the size change of each NAT-rewritten SIP message in s16 diff and accumulates it in s16 tdiff, but a single message can grow by more than S16_MAX while the packet stays under the 65535 enlarge_skb() limit: nf_nat_sip() rewrites every matching URI, and a long Contact list expands the message by tens of kilobytes. diff then wraps, and "datalen = datalen + diff - msglen" yields a huge unsigned datalen, so the next iteration's ct_sip_get_header() reads past the linearized skb tail. Widen diff, tdiff and the seq_adjust hook to s32. Both are bounded by the 65535 byte packet limit, and the seqadj core is already s32 (nf_ct_seqadj_set() takes s32), so no previously accepted input is rejected. BUG: KASAN: use-after-free in ct_sip_get_header (net/netfilter/nf_conntrack_sip.c:464) Read of size 1 at addr ffff888010800000 by task ksoftirqd/1/25 ct_sip_get_header (net/netfilter/nf_conntrack_sip.c:464) sip_help_tcp (net/netfilter/nf_conntrack_sip.c:1694) nf_confirm (net/netfilter/nf_conntrack_proto.c:183) nf_hook_slow (net/netfilter/core.c:619) ip6_output (net/ipv6/ip6_output.c:246) ip6_forward (net/ipv6/ip6_output.c:690) ipv6_rcv (net/ipv6/ip6_input.c:351) __netif_receive_skb_one_core (net/core/dev.c:6212) process_backlog (net/core/dev.c:6676) __napi_poll (net/core/dev.c:7735) net_rx_action (net/core/dev.c:7955) handle_softirqs (kernel/softirq.c:622) run_ksoftirqd (kernel/softirq.c:1076) ... Fixes: f5b321bd37fb ("netfilter: nf_conntrack_sip: add TCP support") Reported-by: Weiming Shi Link: https://patch.msgid.link/netfilter-devel/20260712234201.3213635-1-xmei5@asu.edu Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter/nf_conntrack_sip.h | 2 +- net/netfilter/nf_conntrack_sip.c | 2 +- net/netfilter/nf_nat_sip.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/netfilter/nf_conntrack_sip.h b/include/linux/netfilter/nf_conntrack_sip.h index dbc614dfe0d5..aafa0c04f917 100644 --- a/include/linux/netfilter/nf_conntrack_sip.h +++ b/include/linux/netfilter/nf_conntrack_sip.h @@ -115,7 +115,7 @@ struct nf_nat_sip_hooks { unsigned int *datalen); void (*seq_adjust)(struct sk_buff *skb, - unsigned int protoff, s16 off); + unsigned int protoff, s32 off); unsigned int (*expect)(struct sk_buff *skb, unsigned int protoff, diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index f3f90a866338..e4a70d1d77b0 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -1663,7 +1663,7 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, unsigned int matchoff, matchlen; unsigned int msglen, origlen; const char *dptr, *end; - s16 diff, tdiff = 0; + s32 diff, tdiff = 0; int ret = NF_ACCEPT; unsigned long clen; bool term; diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c index aea02f6aff09..a93eaf0f7d30 100644 --- a/net/netfilter/nf_nat_sip.c +++ b/net/netfilter/nf_nat_sip.c @@ -321,7 +321,7 @@ static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff, } static void nf_nat_sip_seq_adjust(struct sk_buff *skb, unsigned int protoff, - s16 off) + s32 off) { enum ip_conntrack_info ctinfo; struct nf_conn *ct = nf_ct_get(skb, &ctinfo); From 1d6123f87eebb5148844cd43045c6e598799720b Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 13 Jul 2026 14:53:22 +0200 Subject: [PATCH 189/456] selftests: netfilter: nft_flowtable.sh: fix offload counter verification for tunnel tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IPIP and IP6IP6 tunnel tests call check_counters() to verify flowtable offloading occurred, but the flow-add rule only matches meta oif "veth1". When traffic is routed through a tunnel device, oif is the tunnel interface (tun0, tun6, etc.), not veth1, so the flow-add rule never fires, no flowtable entry is created, and counters stay at zero — producing a silent false pass. Fix by adding tunnel-specific flow-add rules for each tunnel interface. These match TCP dport 12345 traffic before the bare accept rule, set ct mark, add the flow to the flowtable, and increment routed_orig. The existing routed_repl rule on veth0 already handles the reply direction since decapsulated reply packets exit through the physical interface. Also add check_counters() for the IP6IP6 non-VLAN and IP6IP6-over-VLAN tests which previously used a bare PASS message. Fixes: fe8313316eaf ("selftests: netfilter: nft_flowtable.sh: Add IPIP flowtable selftest") Fixes: 5e5180352193 ("selftests: netfilter: nft_flowtable.sh: Add IP6IP6 flowtable selftest") Signed-off-by: Lorenzo Bianconi Signed-off-by: Pablo Neira Ayuso --- .../selftests/net/netfilter/nft_flowtable.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh index fb1c59d45567..449c518bd947 100755 --- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh +++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh @@ -617,7 +617,11 @@ ip -6 -net "$nsr2" route add default via fee1:3::1 ip -net "$ns2" route add default via 10.0.2.1 ip -6 -net "$ns2" route add default via dead:2::1 +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif tun0 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0 accept' +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif tun6 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6 accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward \ 'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept' @@ -629,7 +633,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel"; then fi if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then - echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel" + check_counters "flow offload for ns1/ns2 IP6IP6 tunnel" else echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel" 1>&2 ip netns exec "$nsr1" nft list ruleset @@ -642,6 +646,8 @@ ip -net "$nsr1" link set veth1.10 up ip -net "$nsr1" addr add 192.168.20.1/24 dev veth1.10 ip -net "$nsr1" addr add fee1:4::1/64 dev veth1.10 nodad ip netns exec "$nsr1" sysctl net.ipv4.conf.veth1/10.forwarding=1 > /dev/null +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif veth1.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif veth1.10 accept' ip -net "$nsr1" link add name tun0.10 type ipip local 192.168.20.1 remote 192.168.20.2 @@ -649,6 +655,8 @@ ip -net "$nsr1" link set tun0.10 up ip -net "$nsr1" addr add 192.168.200.1/24 dev tun0.10 ip -net "$nsr1" route change default via 192.168.200.2 ip netns exec "$nsr1" sysctl net.ipv4.conf.tun0/10.forwarding=1 > /dev/null +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif tun0.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0.10 accept' ip -net "$nsr1" link add name tun6.10 type ip6tnl local fee1:4::1 remote fee1:4::2 encaplimit none @@ -656,6 +664,8 @@ ip -net "$nsr1" link set tun6.10 up ip -net "$nsr1" addr add fee1:5::1/64 dev tun6.10 nodad ip -6 -net "$nsr1" route delete default ip -6 -net "$nsr1" route add default via fee1:5::2 +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif tun6.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6.10 accept' ip -net "$nsr2" link add link veth0 name veth0.10 type vlan id 10 @@ -683,7 +693,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel over vlan"; then fi if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then - echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel over vlan" + check_counters "flow offload for ns1/ns2 IP6IP6 tunnel over vlan" else echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel over vlan" 1>&2 ip netns exec "$nsr1" nft list ruleset From 4aa63842fc92de1bce59d4709a0d32e718890bb2 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 13 Jul 2026 00:26:04 +0200 Subject: [PATCH 190/456] netfilter: nf_conntrack_expect: add and use nf_ct_expect_related_pair() Add a new function to insert a pair of expectations, this is required by the SIP and H323 NAT helpers. The spinlock is held to check if there is a slot for both expectations, in such case, insert them. This removes the need for nf_ct_unexpect_related() inside the loop to find a pair of consecutive ports, otherwise inserting expectations whose dead flag is already set on can happen. Bump master_help->expecting for the expectation class after checking if the expectation fits in the master expectation list, which is needed for this new _pair() function variant to run the eviction routine including the preallocated slot for the first expectation in the pair. Fixes: b8b09dc2bf35 ("netfilter: nf_conntrack_expect: use conntrack GC to reap expectations") Reported-by: Jaeyeong Lee Link: https://patch.msgid.link/178377968720.33756.12204817361601593230@proton.me/ Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_expect.h | 3 ++ net/ipv4/netfilter/nf_nat_h323.c | 22 +++++-------- net/netfilter/nf_conntrack_expect.c | 35 ++++++++++++++++++++- net/netfilter/nf_nat_sip.c | 20 ++++-------- 4 files changed, 50 insertions(+), 30 deletions(-) diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h index c024345c9bd8..26d6babd92fc 100644 --- a/include/net/netfilter/nf_conntrack_expect.h +++ b/include/net/netfilter/nf_conntrack_expect.h @@ -161,6 +161,9 @@ static inline int nf_ct_expect_related(struct nf_conntrack_expect *expect, return nf_ct_expect_related_report(expect, 0, 0, flags); } +int nf_ct_expect_related_pair(struct nf_conntrack_expect *expect[], + unsigned int flag); + struct nf_conn_help; void nf_ct_expectation_gc(struct nf_conn_help *master_help); diff --git a/net/ipv4/netfilter/nf_nat_h323.c b/net/ipv4/netfilter/nf_nat_h323.c index 183e8a3ff2ba..6bcd6734769b 100644 --- a/net/ipv4/netfilter/nf_nat_h323.c +++ b/net/ipv4/netfilter/nf_nat_h323.c @@ -182,6 +182,7 @@ static int nat_rtp_rtcp(struct sk_buff *skb, struct nf_conn *ct, struct nf_conntrack_expect *rtp_exp, struct nf_conntrack_expect *rtcp_exp) { + struct nf_conntrack_expect *rtp_pair[2] = { rtp_exp, rtcp_exp }; struct nf_ct_h323_master *info = nfct_help_data(ct); int dir = CTINFO2DIR(ctinfo); int i; @@ -227,22 +228,13 @@ static int nat_rtp_rtcp(struct sk_buff *skb, struct nf_conn *ct, int ret; rtp_exp->tuple.dst.u.udp.port = htons(nated_port); - ret = nf_ct_expect_related(rtp_exp, 0); + rtcp_exp->tuple.dst.u.udp.port = htons(nated_port + 1); + ret = nf_ct_expect_related_pair(rtp_pair, 0); if (ret == 0) { - rtcp_exp->tuple.dst.u.udp.port = - htons(nated_port + 1); - ret = nf_ct_expect_related(rtcp_exp, 0); - if (ret == 0) - break; - else if (ret == -EBUSY) { - nf_ct_unexpect_related(rtp_exp); - continue; - } else if (ret < 0) { - nf_ct_unexpect_related(rtp_exp); - nated_port = 0; - break; - } - } else if (ret != -EBUSY) { + break; + } else if (ret == -EBUSY) { + continue; + } else if (ret < 0) { nated_port = 0; break; } diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c index 7ae68d60586a..8a3b9e33e94f 100644 --- a/net/netfilter/nf_conntrack_expect.c +++ b/net/netfilter/nf_conntrack_expect.c @@ -427,7 +427,6 @@ static void nf_ct_expect_insert(struct nf_conntrack_expect *exp, exp->timeout += helper->expect_policy[exp->class].timeout * HZ; hlist_add_head_rcu(&exp->lnode, &master_help->expectations); - master_help->expecting[exp->class]++; hlist_add_head_rcu(&exp->hnode, &nf_ct_expect_hash[h]); cnet = nf_ct_pernet(net); @@ -534,6 +533,7 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect, if (ret < 0) goto out; + master_help->expecting[expect->class]++; nf_ct_expect_insert(expect, master_help); nf_ct_expect_event_report(IPEXP_NEW, expect, portid, report); @@ -546,6 +546,39 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect, } EXPORT_SYMBOL_GPL(nf_ct_expect_related_report); +int nf_ct_expect_related_pair(struct nf_conntrack_expect *expect[], + unsigned int flags) +{ + struct nf_conn_help *master_help; + int i, ret; + + spin_lock_bh(&nf_conntrack_expect_lock); + master_help = nfct_help(expect[0]->master); + if (!master_help || master_help != nfct_help(expect[1]->master)) { + ret = -EINVAL; + goto out; + } + + for (i = 0; i < 2; i++) { + ret = __nf_ct_expect_check(expect[i], master_help, flags); + if (ret < 0) { + if (i == 1) + master_help->expecting[expect[0]->class]--; + goto out; + } + master_help->expecting[expect[i]->class]++; + } + + for (i = 0; i < 2; i++) { + nf_ct_expect_insert(expect[i], master_help); + nf_ct_expect_event_report(IPEXP_NEW, expect[i], 0, 0); + } +out: + spin_unlock_bh(&nf_conntrack_expect_lock); + return ret; +} +EXPORT_SYMBOL_GPL(nf_ct_expect_related_pair); + void nf_ct_expect_iterate_destroy(bool (*iter)(struct nf_conntrack_expect *e, void *data), void *data) { diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c index a93eaf0f7d30..133bd713fe0c 100644 --- a/net/netfilter/nf_nat_sip.c +++ b/net/netfilter/nf_nat_sip.c @@ -592,6 +592,7 @@ static unsigned int nf_nat_sdp_media(struct sk_buff *skb, unsigned int protoff, unsigned int medialen, union nf_inet_addr *rtp_addr) { + struct nf_conntrack_expect *rtp_pair[2] = { rtp_exp, rtcp_exp }; enum ip_conntrack_info ctinfo; struct nf_conn *ct = nf_ct_get(skb, &ctinfo); enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); @@ -622,24 +623,15 @@ static unsigned int nf_nat_sdp_media(struct sk_buff *skb, unsigned int protoff, int ret; rtp_exp->tuple.dst.u.udp.port = htons(port); - ret = nf_ct_expect_related(rtp_exp, - NF_CT_EXP_F_SKIP_MASTER); - if (ret == -EBUSY) - continue; - else if (ret < 0) { - port = 0; - break; - } rtcp_exp->tuple.dst.u.udp.port = htons(port + 1); - ret = nf_ct_expect_related(rtcp_exp, - NF_CT_EXP_F_SKIP_MASTER); + + ret = nf_ct_expect_related_pair(rtp_pair, + NF_CT_EXP_F_SKIP_MASTER); if (ret == 0) break; - else if (ret == -EBUSY) { - nf_ct_unexpect_related(rtp_exp); + else if (ret == -EBUSY) continue; - } else if (ret < 0) { - nf_ct_unexpect_related(rtp_exp); + else if (ret < 0) { port = 0; break; } From c58ea9adf7342508c6ac0b7ad79ef10d589f9c6e Mon Sep 17 00:00:00 2001 From: Chuyi Zhou Date: Wed, 15 Jul 2026 17:45:38 -0700 Subject: [PATCH 191/456] smp: Avoid invalid per-CPU CSD lookup with CSD lock debug Commit b0473dcd4b1d ("smp: Improve smp_call_function_single() CSD-lock diagnostics") made smp_call_function_single() use the destination CPU's csd_data when CSD lock debugging is enabled. That lets the debug code associate a stuck CSD lock with the target CPU, but it also means the CPU argument is used in per_cpu_ptr() before generic_exec_single() has a chance to validate it. This becomes unsafe when smp_call_function_any() cannot find an online CPU in the supplied mask. In that case the selected CPU can be nr_cpu_ids, and the !wait path calls get_single_csd_data(cpu) before generic_exec_single() returns -ENXIO. With csdlock_debug_enabled set, that indexes the per-CPU offset array with an invalid CPU number. Use the destination CPU's csd_data only when the CPU number is within nr_cpu_ids. For invalid CPU numbers, fall back to the local CPU's csd_data and let generic_exec_single() perform the existing validation and return -ENXIO. Fixes: b0473dcd4b1d ("smp: Improve smp_call_function_single() CSD-lock diagnostics") Signed-off-by: Chuyi Zhou Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: Paul E. McKenney Acked-by: Muchun Song Link: https://patch.msgid.link/20260716004539.13983-1-paulmck@kernel.org --- kernel/smp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/smp.c b/kernel/smp.c index a0bb56bd8dda..dc6582bb35d0 100644 --- a/kernel/smp.c +++ b/kernel/smp.c @@ -380,7 +380,8 @@ static DEFINE_PER_CPU_SHARED_ALIGNED(call_single_data_t, csd_data); #ifdef CONFIG_CSD_LOCK_WAIT_DEBUG static call_single_data_t *get_single_csd_data(int cpu) { - if (static_branch_unlikely(&csdlock_debug_enabled)) + if (static_branch_unlikely(&csdlock_debug_enabled) && + (unsigned int)cpu < nr_cpu_ids) return per_cpu_ptr(&csd_data, cpu); return this_cpu_ptr(&csd_data); } From 35551efb155e3b83445a6c3f66cb498d5efc182c Mon Sep 17 00:00:00 2001 From: Chuyi Zhou Date: Wed, 15 Jul 2026 17:45:39 -0700 Subject: [PATCH 192/456] smp: Make CSD lock acquisition atomic for debug mode Commit b0473dcd4b1d ("smp: Improve smp_call_function_single() CSD-lock diagnostics") changed smp_call_function_single() so that, when CSD lock debugging is enabled, async !wait calls use the destination CPU csd_data. That improves diagnostics, but it also removes the single-writer property that made the old csd_lock() safe: multiple CPUs can now prepare the same destination CPU CSD concurrently. csd_lock() currently waits for CSD_FLAG_LOCK to clear and then sets the bit with a non-atomic read-modify-write. Two senders can both see an unlocked CSD, set the bit, overwrite the callback fields, and enqueue the same llist node. Re-adding a node that is already the queue head can make node->next point to itself, leaving the target CPU stuck walking call_single_queue. Later synchronous work, such as a TLB shootdown, can then remain queued and trigger soft-lockup warnings or panics. Keep the single csd_lock() implementation, but when CSD lock debugging is enabled, acquire CSD_FLAG_LOCK with try_cmpxchg_acquire(). This makes the destination CPU CSD a real atomic lock in the only configuration where it can be shared by multiple remote senders, while preserving the existing non-debug fast path. Fixes: b0473dcd4b1d ("smp: Improve smp_call_function_single() CSD-lock diagnostics") Signed-off-by: Chuyi Zhou Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260716004539.13983-2-paulmck@kernel.org --- kernel/smp.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/kernel/smp.c b/kernel/smp.c index dc6582bb35d0..52dffc86555c 100644 --- a/kernel/smp.c +++ b/kernel/smp.c @@ -137,10 +137,10 @@ csd_do_func(smp_call_func_t func, void *info, call_single_data_t *csd) trace_csd_function_exit(func, csd); } -#ifdef CONFIG_CSD_LOCK_WAIT_DEBUG - static DEFINE_STATIC_KEY_MAYBE(CONFIG_CSD_LOCK_WAIT_DEBUG_DEFAULT, csdlock_debug_enabled); +#ifdef CONFIG_CSD_LOCK_WAIT_DEBUG + /* * Parse the csdlock_debug= kernel boot parameter. * @@ -342,6 +342,10 @@ static __always_inline void csd_lock_wait(call_single_data_t *csd) smp_cond_load_acquire(&csd->node.u_flags, !(VAL & CSD_FLAG_LOCK)); } #else +static __always_inline void __csd_lock_wait(call_single_data_t *csd) +{ +} + static void csd_lock_record(call_single_data_t *csd) { } @@ -354,8 +358,23 @@ static __always_inline void csd_lock_wait(call_single_data_t *csd) static __always_inline void csd_lock(call_single_data_t *csd) { - csd_lock_wait(csd); - csd->node.u_flags |= CSD_FLAG_LOCK; + if (IS_ENABLED(CONFIG_CSD_LOCK_WAIT_DEBUG) && + static_branch_unlikely(&csdlock_debug_enabled)) { + + for (;;) { + unsigned int flags; + + __csd_lock_wait(csd); + flags = READ_ONCE(csd->node.u_flags); + + if (!(flags & CSD_FLAG_LOCK) && + try_cmpxchg_acquire(&csd->node.u_flags, &flags, flags | CSD_FLAG_LOCK)) + break; + } + } else { + csd_lock_wait(csd); + csd->node.u_flags |= CSD_FLAG_LOCK; + } /* * prevent CPU from reordering the above assignment From 9b5ce5c496efd20c1c662cedba88465d39ec1f93 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 25 Jun 2026 08:32:40 +0800 Subject: [PATCH 193/456] drm/gma500: return errors from Oaktrail HDMI I2C reads xfer_read() waits for the HDMI I2C transaction to reach I2C_TRANSACTION_DONE, but it ignores both timeout and signal returns from wait_for_completion_interruptible_timeout(). If the interrupt never advances the transaction state, the loop can wait forever. Return -ETIMEDOUT when the completion wait expires, propagate interrupted waits, and make the I2C master_xfer callback return the first transfer error instead of reporting a successful message count. Signed-off-by: Pengpeng Hou Signed-off-by: Patrik Jakobsson Link: https://patch.msgid.link/20260625003240.6923-1-pengpeng@iscas.ac.cn --- drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c b/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c index 2a7916ca5907..a780643f25aa 100644 --- a/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c +++ b/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c @@ -98,6 +98,7 @@ static int xfer_read(struct i2c_adapter *adap, struct i2c_msg *pmsg) struct oaktrail_hdmi_dev *hdmi_dev = i2c_get_adapdata(adap); struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev; u32 temp; + int ret; i2c_dev->status = I2C_STAT_INIT; i2c_dev->msg = pmsg; @@ -109,9 +110,14 @@ static int xfer_read(struct i2c_adapter *adap, struct i2c_msg *pmsg) HDMI_WRITE(HDMI_HI2CHCR, temp); HDMI_READ(HDMI_HI2CHCR); - while (i2c_dev->status != I2C_TRANSACTION_DONE) - wait_for_completion_interruptible_timeout(&i2c_dev->complete, + while (i2c_dev->status != I2C_TRANSACTION_DONE) { + ret = wait_for_completion_interruptible_timeout(&i2c_dev->complete, 10 * HZ); + if (ret < 0) + return ret; + if (!ret) + return -ETIMEDOUT; + } return 0; } @@ -130,7 +136,7 @@ static int oaktrail_hdmi_i2c_access(struct i2c_adapter *adap, { struct oaktrail_hdmi_dev *hdmi_dev = i2c_get_adapdata(adap); struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev; - int i; + int i, ret = 0; mutex_lock(&i2c_dev->i2c_lock); @@ -142,9 +148,11 @@ static int oaktrail_hdmi_i2c_access(struct i2c_adapter *adap, for (i = 0; i < num; i++) { if (pmsg->len && pmsg->buf) { if (pmsg->flags & I2C_M_RD) - xfer_read(adap, pmsg); + ret = xfer_read(adap, pmsg); else - xfer_write(adap, pmsg); + ret = xfer_write(adap, pmsg); + if (ret) + break; } pmsg++; /* next message */ } @@ -154,6 +162,9 @@ static int oaktrail_hdmi_i2c_access(struct i2c_adapter *adap, mutex_unlock(&i2c_dev->i2c_lock); + if (ret) + return ret; + return i; } From 94b83ff0c0a69e42f403b59918529fbca2a89daf Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Mon, 13 Jul 2026 08:28:23 +0100 Subject: [PATCH 194/456] tracing/remotes: Fix page_va[] access before counter update in trace_remote_alloc_buffer() page_va[] is annotated __counted_by(nr_page_va), so nr_page_va must cover an index before that element is accessed. The allocation loop writes page_va[id] while nr_page_va is still id and increments it only afterwards, so every write is one element past the declared count. The store is out of bounds with respect to the annotation: a build with CONFIG_UBSAN_BOUNDS on a toolchain that honours __counted_by (clang >= 20.1, gcc >= 15.1) flags it as an array-index overflow. Increment nr_page_va before writing the element it now covers. A failed allocation then leaves the slot counted but NULL; the error path frees it with free_page(0), which is a no-op. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260713072823.2668323-1-fuad.tabba@linux.dev Fixes: 96e43537af546 ("tracing: Introduce trace remotes") Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace_remote.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index 0f6ef5c36d84..ef42d9c38b37 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -1004,11 +1004,10 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, desc->nr_cpus++; for (id = 0; id < nr_pages; id++) { + rb_desc->nr_page_va++; rb_desc->page_va[id] = (unsigned long)__get_free_page(GFP_KERNEL); if (!rb_desc->page_va[id]) goto err; - - rb_desc->nr_page_va++; } rb_desc = __next_ring_buffer_desc(rb_desc); } From 5d8ed6b64220ad629aade5f174e3f690c37435f9 Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Tue, 21 Jul 2026 17:04:38 +0530 Subject: [PATCH 195/456] drm/xe/i2c: Allow per domain unique id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PCI bus, device and function can be same for devices existing across different domains. Allow per domain unique identifier while registering platform device to prevent name conflict. Fixes: f0e53aadd702 ("drm/xe: Support for I2C attached MCUs") Signed-off-by: Raag Jadav Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260721113438.651100-1-raag.jadav@intel.com Signed-off-by: Matt Roper (cherry picked from commit a79f6abc8b516b5bd906e2eca8121e3549ee163f) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_i2c.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index 706783863d07..f05f23221c1b 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -95,18 +95,21 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c) struct platform_device *pdev; struct fwnode_handle *fwnode; int ret; + u32 id; fwnode = fwnode_create_software_node(xe_i2c_adapter_properties, NULL); if (IS_ERR(fwnode)) return PTR_ERR(fwnode); + id = (pci_domain_nr(pci->bus) << 16) | pci_dev_id(pci); + /* * Not using platform_device_register_full() here because we don't have * a handle to the platform_device before it returns. xe_i2c_notifier() * uses that handle, but it may be called before * platform_device_register_full() is done. */ - pdev = platform_device_alloc(adapter_name, pci_dev_id(pci)); + pdev = platform_device_alloc(adapter_name, id); if (!pdev) { ret = -ENOMEM; goto err_fwnode_remove; From d2c6800ad1802bed72a6de1416536737f114f1d6 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 21 Jul 2026 20:55:14 +0000 Subject: [PATCH 196/456] drm/xe/vm: Fix SVM leak on resv obj alloc failure in xe_vm_create() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 9e9787414882 ("drm/xe/userptr: replace xe_hmm with gpusvm") made xe_svm_init() unconditional in xe_vm_create() and extended it to also initialize a "simple" gpusvm state for non-fault-mode VMs. The matching xe_svm_fini() call in xe_vm_close_and_put() was updated to run unconditionally, but the error unwind path in xe_vm_create() was not. On the drm_gpuvm_resv_object_alloc() failure path, xe_svm_init() has already succeeded but xe_svm_fini() is only called when XE_VM_FLAG_FAULT_MODE is set. For non-fault-mode VMs this leaves vm->svm.gpusvm partially initialized and leaks the resources allocated by drm_gpusvm_init(). For fault-mode VMs, xe_svm_init() additionally acquires the pagemap owner via drm_pagemap_acquire_owner() and the pagemaps via xe_svm_get_pagemaps(). Those resources are released by xe_svm_close(), not xe_svm_fini(). On the same error path, xe_svm_close() is not called either, so fault-mode VMs leak the pagemap owner and pagemaps. Fix both leaks: - Call xe_svm_fini() unconditionally on the err_svm_fini path, matching the unconditional xe_svm_init() call. Move the vm->size = 0 assignment out of the conditional so the xe_vm_is_closed() assert in xe_svm_fini() (and xe_svm_close()) holds for both modes. - Call xe_svm_close() for fault-mode VMs before xe_svm_fini(), matching the ordering used in xe_vm_close_and_put(). Fixes: 9e9787414882 ("drm/xe/userptr: replace xe_hmm with gpusvm") Cc: Matthew Auld Assisted-by: Claude:claude-opus-4.7 Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260721205516.4058959-2-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin (cherry picked from commit ca2a3587d577ba764e0fe628fb676244fc33ddd4) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/xe/xe_vm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 32ded13491ca..67819deb45e3 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -1809,10 +1809,10 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef) return ERR_PTR(err); err_svm_fini: - if (flags & XE_VM_FLAG_FAULT_MODE) { - vm->size = 0; /* close the vm */ - xe_svm_fini(vm); - } + vm->size = 0; /* close the vm */ + if (flags & XE_VM_FLAG_FAULT_MODE) + xe_svm_close(vm); + xe_svm_fini(vm); err_no_resv: mutex_destroy(&vm->snap_mutex); for_each_tile(tile, xe, id) From 4a9ec5ec9555ad62dc5b81a37ac946025c2ea002 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 22 Jul 2026 17:09:43 -0700 Subject: [PATCH 197/456] x86/boot/compressed: Disable jump tables After a recent upstream LLVM change to start generating jump and lookup tables in switch statements in more instances [1], linking the compressed x86 boot image when CONFIG_KERNEL_ZSTD is enabled fails with: ld.lld: error: Unexpected run-time relocations (.rela) detected! Dumping the relocations in misc.o, which is the only file influenced by CONFIG_KERNEL_ZSTD in the decompressor, shows dynamic relocations to some string constants, which correspond to the string literals in the switch statement in handle_zstd_error(): Relocation section '.rela.data.rel.ro' at offset 0x277b0 contains 31 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000006600000001 R_X86_64_64 0000000000000000 .rodata.str1.1 + 73a 0000000000000008 0000006600000001 R_X86_64_64 0000000000000000 .rodata.str1.1 + 78e 0000000000000010 0000006600000001 R_X86_64_64 0000000000000000 .rodata.str1.1 + 78e 0000000000000018 0000006600000001 R_X86_64_64 0000000000000000 .rodata.str1.1 + 78e ... This optimization is problematic for the decompressor environment, as it is built as -fPIE without any explicit absolute references (as described at the top of misc.c) while not applying any dynamic relocations, hence the linker assertion. To opt out of this optimization, which is of little value in this special early boot code, and to mirror the other x86 startup code in arch/x86/boot/startup, disable jump tables in the decompressor. Signed-off-by: Nathan Chancellor Signed-off-by: Ingo Molnar Acked-by: Ard Biesheuvel Cc: Bill Wendling Cc: Justin Stitt Cc: Nick Desaulniers Cc: "H. Peter Anvin" Cc: Peter Zijlstra Cc: stable@vger.kernel.org Link: https://github.com/llvm/llvm-project/commit/fa02a6ed66b1700c996b49c96c6bc0eb014c9518 [1] Link: https://patch.msgid.link/20260722-x86-boot-compressed-disable-jt-clang-v2-1-7373d38482fb@kernel.org Closes: https://github.com/ClangBuiltLinux/linux/issues/2165 --- arch/x86/boot/compressed/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile index 07e0e64b9a98..06934f9691d6 100644 --- a/arch/x86/boot/compressed/Makefile +++ b/arch/x86/boot/compressed/Makefile @@ -27,6 +27,7 @@ targets := vmlinux vmlinux.bin vmlinux.bin.gz vmlinux.bin.bz2 vmlinux.bin.lzma \ KBUILD_CFLAGS := -m$(BITS) -O2 $(CLANG_FLAGS) KBUILD_CFLAGS += $(CC_FLAGS_DIALECT) KBUILD_CFLAGS += -fno-strict-aliasing -fPIE +KBUILD_CFLAGS += -fno-jump-tables KBUILD_CFLAGS += -Wundef KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING cflags-$(CONFIG_X86_32) := -march=i386 From 503d67fbaec6fdeaba391cb497675071db9d16ea Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Tue, 21 Jul 2026 14:41:40 +0800 Subject: [PATCH 198/456] fs/super: fix emergency thaw double-unlock of s_umount do_thaw_all() iterates over all superblocks via __iterate_supers() with SUPER_ITER_EXCL, which acquires s_umount exclusively before calling the callback and releases it afterwards. However, the callback do_thaw_all_callback() calls thaw_super_locked() which unconditionally releases s_umount on every code path. This results in a second unlock attempt in __iterate_supers() that corrupts the rwsem state, triggering a DEBUG_RWSEMS warning: [ 182.601148] sysrq: Emergency Thaw of all frozen filesystems [ 182.601865] ------------[ cut here ]------------ [ 182.602375] DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE)): count = 0x0, magic = 0xffff99b1011e5870, owner = 0x0, curr 0xffff99b101b06c80, list not empty [ 182.603817] WARNING: kernel/locking/rwsem.c:1412 at up_write+0xa3/0x170, CPU#2: kworker/2:1/53 [ 182.604578] Modules linked in: [ 182.604864] CPU: 2 UID: 0 PID: 53 Comm: kworker/2:1 Not tainted 7.2.0-rc4-00001-gbd3bd93ea98a-dirty #4 PREEMPT(lazy) [ 182.605711] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1kylin1 04/01/2014 [ 182.606417] Workqueue: events do_thaw_all [ 182.606750] RIP: 0010:up_write+0xaf/0x170 [ 182.607076] Code: 19 3a 92 48 0f 44 c2 48 8b 55 08 48 8b 55 00 4c 8b 45 08 48 8b 55 00 48 8d 3d ad 91 e0 01 48 8b 4d 20 50 48 c7 c6 f0 8c 26 92 <67> 48 0f b9 3a e8 d7 93 4e 00 58 eb 81 48 83 7f 18 00 48 c7 c2 8d [ 182.608563] RSP: 0018:ffffb670001d7e08 EFLAGS: 00010246 [ 182.609007] RAX: ffffffff92349e8d RBX: 0000000000000000 RCX: ffff99b1011e5870 [ 182.609595] RDX: 0000000000000000 RSI: ffffffff92268cf0 RDI: ffffffff92914d10 [ 182.610283] RBP: ffff99b1011e5870 R08: 0000000000000000 R09: ffff99b101b06c80 [ 182.610847] R10: ffff99b10139a808 R11: fefefefefefefeff R12: 0000000000000000 [ 182.611414] R13: ffffffff90cf74d0 R14: 0000000000000000 R15: ffff99b1011e5800 [ 182.612009] FS: 0000000000000000(0000) GS:ffff99b1eaaee000(0000) knlGS:0000000000000000 [ 182.612670] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 182.613146] CR2: 00000000005c631c CR3: 00000000013ee000 CR4: 00000000000006f0 [ 182.613722] Call Trace: [ 182.613946] [ 182.614130] __iterate_supers+0x128/0x150 [ 182.614463] do_thaw_all+0x1b/0x30 [ 182.614759] process_scheduled_works+0xbb/0x3f0 [ 182.615150] ? __pfx_worker_thread+0x10/0x10 [ 182.615499] worker_thread+0x129/0x270 [ 182.615816] ? __pfx_worker_thread+0x10/0x10 [ 182.616201] kthread+0xe2/0x120 [ 182.616469] ? __pfx_kthread+0x10/0x10 [ 182.616792] ret_from_fork+0x15b/0x240 [ 182.617115] ? __pfx_kthread+0x10/0x10 [ 182.617426] ret_from_fork_asm+0x1a/0x30 [ 182.617761] [ 182.617968] ---[ end trace 0000000000000000 ]--- [ 182.618412] Emergency Thaw complete Fix this by switching to SUPER_ITER_UNLOCKED and acquiring s_umount in the callback via super_lock_excl() before calling thaw_super_locked(). This matches the locking pattern expected by thaw_super_locked() and eliminates the double unlock. While at it, remove the dead 'return;' at the end of do_thaw_all_callback(). Fixes: 2992476528ae ("super: use a common iterator (Part 1)") Cc: stable@vger.kernel.org Signed-off-by: Chen Changcheng Link: https://patch.msgid.link/20260721064140.152305-1-chenchangcheng@kylinos.cn Signed-off-by: Christian Brauner (Amutable) --- fs/super.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/super.c b/fs/super.c index a8fd61136aaf..70dcb07e7fa5 100644 --- a/fs/super.c +++ b/fs/super.c @@ -1084,16 +1084,19 @@ void emergency_remount(void) static void do_thaw_all_callback(struct super_block *sb, void *unused) { + if (!super_lock_excl(sb)) + return; + if (IS_ENABLED(CONFIG_BLOCK)) while (sb->s_bdev && !bdev_thaw(sb->s_bdev)) pr_warn("Emergency Thaw on %pg\n", sb->s_bdev); + thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE, NULL); - return; } static void do_thaw_all(struct work_struct *work) { - __iterate_supers(do_thaw_all_callback, NULL, SUPER_ITER_EXCL); + __iterate_supers(do_thaw_all_callback, NULL, SUPER_ITER_UNLOCKED); kfree(work); printk(KERN_WARNING "Emergency Thaw complete\n"); } From fb0bf289f5d529336ef490c8273e88a8a8b29f69 Mon Sep 17 00:00:00 2001 From: Andrei Kuchynski Date: Fri, 17 Jul 2026 10:46:14 +0000 Subject: [PATCH 199/456] usb: typec: ucsi: Correct teardown ordering in ucsi_init() error path The commit 7aa7d4bf9d3f ("usb: typec: ucsi: Fix race condition and ordering in port unregistration") consolidated port teardown into the ucsi_unregister_port() helper. However, it introduced an ordering problem in the ucsi_init() error path. Fix this by ensuring ucsi_unregister_port() is called before we unregister their corresponding lockdep keys. Cc: stable@vger.kernel.org Fixes: 7aa7d4bf9d3f ("usb: typec: ucsi: Fix race condition and ordering in port unregistration") Reported-by: "Borah, Chaitanya Kumar" Closes: https://lore.kernel.org/all/22064276-6c56-411a-9f20-6917ceeb865f@intel.com/ Signed-off-by: Andrei Kuchynski Tested-by: Chaitanya Kumar Borah Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260717104614.325250-1-akuchynski@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 1ae4224d2dfc..49f1c53721bb 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -2142,11 +2142,11 @@ static int ucsi_init(struct ucsi *ucsi) return 0; err_unregister: + for (con = connector; con->port; con++) + ucsi_unregister_port(con); for (i = 0; i < ucsi->cap.num_connectors; i++) lockdep_unregister_key(&connector[i].lock_key); - for (con = connector; con->port; con++) - ucsi_unregister_port(con); kfree(connector); err_reset: memset(&ucsi->cap, 0, sizeof(ucsi->cap)); From cbfe2b24a1ea9de35032dbdd100fdc700f5be92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20L=C3=B3pez?= Date: Wed, 15 Jul 2026 12:51:37 +0200 Subject: [PATCH 200/456] KVM: arm64: vgic: Fix race between LPI release and re-registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a potential race between decrementing an LPI's reference count and evicting that structure from the LPI xarray. LPI structures are maintained in the VGIC LPI xarray (dist->lpi_xa). When the reference count of an LPI structure drops to zero, vgic_release_lpi_locked() removes the structure from the xarray and frees it under the xarray lock. However, the release of an LPI can race with a concurrent LPI re-registration with the same INTID via vgic_add_lpi() on another CPU, since the reference count drop and the xarray eviction are not performed in a single atomic step. This can happen e.g. if the guest issues a DISCARD while the LPI is still referenced from a vCPU's active-pending list (ap_list), and the same INTID is re-mapped via MAPTI. Particularly, vgic_release_lpi_locked() is called from two distinct paths: direct release via vgic_put_irq(), and deferred release via vgic_release_deleted_lpis(). During direct release, the issue can result in deleting a newly registered LPI from the xarray: CPU0 (Releasing LPI) CPU1 (Adding new LPI) ==================== ===================== vgic_put_irq() __vgic_put_irq() refcount_dec_and_test() vgic_add_lpi() xa_lock_irqsave() old_irq = xa_load(.., intid) vgic_try_get_irq_ref(old_irq) == false new IRQ inserted --> __xa_store(.., intid, ..) xa_unlock_irqrestore() xa_lock_irqsave(); vgic_release_lpi_locked() __xa_erase(.., irq->intid) <-- BUG: new IRQ is erased kfree_rcu(old_irq) During the deferred release path, the old IRQ can be leaked: CPU0 (Releasing LPI) CPU1 (Adding new LPI) ==================== ===================== vgic_put_irq_norelease() __vgic_put_irq() refcount_dec_and_test() irq->pending_release = true vgic_add_lpi() xa_lock_irqsave() old_irq = xa_load(.., intid) vgic_try_get_irq_ref(oldirq) == false BUG: old IRQ overwritten --> __xa_store(.., intid, ..) xa_unlock_irqrestore() vgic_release_deleted_lpis() xa_lock_irqsave() xa_for_each() { .. } <-- old IRQ with pending_release = true is gone, so it cannot be released To fix the direct release path, move the reference count drop inside the xarray lock, making sure that vgic_add_lpi() never encounters the to-be-released LPI. In the deferred release path, the refcount drop must happen under a raw spinlock, so the xarray lock cannot be grabbed, and the same solution does not work. Instead, update vgic_add_lpi(), so that if it evicts an LPI from the xarray, it takes on the responsibility of freeing it. Consequently, an LPI may now be freed concurrently after a deferred release drops the refcount, so accessing the pending_release field is no longer safe from use-after-free. Delete all uses of the flag, and update vgic_release_deleted_lpis() to identify orphaned LPIs purely based on their refcount. Reported-by: Claude:claude-opus-4-6 Fixes: 3a08a6ca7c37 ("KVM: arm64: vgic-v3: Use bare refcount for VGIC LPIs") Fixes: d54594accf73 ("KVM: arm64: vgic-v3: Erase LPIs from xarray outside of raw spinlocks") Signed-off-by: Carlos López Link: https://patch.msgid.link/20260715105137.3973823-4-clopez@suse.de Signed-off-by: Marc Zyngier --- arch/arm64/kvm/vgic/vgic-its.c | 24 ++++++++++++++++-------- arch/arm64/kvm/vgic/vgic.c | 18 ++++++++---------- include/kvm/arm_vgic.h | 3 --- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 740b39875728..5c5d1772147d 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -116,18 +116,26 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, kfree(irq); irq = oldirq; } else { - ret = xa_err(__xa_store(&dist->lpi_xa, intid, irq, 0)); + /* + * The entry is either empty or contains a dead LPI (refcount=0) + * from the deferred release path, pending cleanup by + * vgic_release_deleted_lpis(). Evict and free it if present. + */ + oldirq = __xa_store(&dist->lpi_xa, intid, irq, 0); + ret = xa_err(oldirq); + if (ret) { + xa_unlock_irqrestore(&dist->lpi_xa, flags); + kfree(irq); + + return ERR_PTR(ret); + } + + if (oldirq && !WARN_ON_ONCE(refcount_read(&oldirq->refcount))) + kfree_rcu(oldirq, rcu); } xa_unlock_irqrestore(&dist->lpi_xa, flags); - if (ret) { - xa_release(&dist->lpi_xa, intid); - kfree(irq); - - return ERR_PTR(ret); - } - /* * We "cache" the configuration table entries in our struct vgic_irq's. * However we only have those structs for mapped IRQs, so we read in diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index ccb7e3a90cd0..74bace10a22e 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -146,11 +146,7 @@ static __must_check bool __vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq) static __must_check bool vgic_put_irq_norelease(struct kvm *kvm, struct vgic_irq *irq) { - if (!__vgic_put_irq(kvm, irq)) - return false; - - irq->pending_release = true; - return true; + return __vgic_put_irq(kvm, irq); } void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq) @@ -167,12 +163,14 @@ void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq) guard(spinlock_irqsave)(&dist->lpi_xa.xa_lock); } - if (!__vgic_put_irq(kvm, irq)) + if (!irq_is_lpi(kvm, irq->intid)) return; - xa_lock_irqsave(&dist->lpi_xa, flags); - vgic_release_lpi_locked(dist, irq); - xa_unlock_irqrestore(&dist->lpi_xa, flags); + if (refcount_dec_and_lock_irqsave(&irq->refcount, + &dist->lpi_xa.xa_lock, &flags)) { + vgic_release_lpi_locked(dist, irq); + xa_unlock_irqrestore(&dist->lpi_xa, flags); + } } static void vgic_release_deleted_lpis(struct kvm *kvm) @@ -184,7 +182,7 @@ static void vgic_release_deleted_lpis(struct kvm *kvm) xa_lock_irqsave(&dist->lpi_xa, flags); xa_for_each(&dist->lpi_xa, intid, irq) { - if (irq->pending_release) + if (!refcount_read(&irq->refcount)) vgic_release_lpi_locked(dist, irq); } diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h index fe49fb56dc3c..cefddc9c621d 100644 --- a/include/kvm/arm_vgic.h +++ b/include/kvm/arm_vgic.h @@ -247,9 +247,6 @@ struct vgic_irq { * affinity reg (v3). */ - bool pending_release:1; /* Used for LPIs only, unreferenced IRQ - * pending a release */ - bool pending_latch:1; /* The pending latch state used to calculate * the pending state for both level * and edge triggered IRQs. */ From 21f12496fdd357ad4e1fcdd07dc80ab7378f7d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20L=C3=B3pez?= Date: Wed, 15 Jul 2026 12:51:38 +0200 Subject: [PATCH 201/456] KVM: arm64: vgic: Mitigate potential LPI registration failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mitigate a potential failure when inserting a new LPI into the VGIC LPI xarray. When vgic_add_lpi() is preparing to register a new LPI, it pre-allocates an xarray entry using xa_reserve_irq(), so that it can later perform the insertion under the xarray lock without allocating. However, since xa_reserve_irq() is called before acquiring such lock, there is a potential race where xa_reserve_irq() observes a populated entry, thus not performing the allocation, and another CPU removes that entry before the xarray lock is grabbed to perform the insertion. CPU0 (Adding new LPI) CPU1 (Releasing LPI) ===================== =================== vgic_add_lpi() /* Entry populated, does not allocate */ xa_reserve_irq(.., intid, ..) vgic_release_deleted_lpis() xa_lock_irqsave() vgic_release_lpi_locked() xarray node freed --> __xa_erase(.., intid) xa_unlock_irqrestore() xa_lock_irqsave() xa_load(.., intid) == NULL vgic_try_get_irq_ref(NULL) == false __xa_store(.., intid, irq, 0) <-- xarray node was freed, gfp=0 cannot allocate, returns -ENOMEM This can happen e.g. if the guest issues a DISCARD while the LPI is still referenced from a vCPU's active-pending list (ap_list), and the same INTID is re-mapped via MAPTI. Mitigate this by passing GFP_NOWAIT to __xa_store(), so that the allocation can happen under the lock in the rare case that this condition is hit. Add __GFP_ACCOUNT as well to match xa_reserve_irq()'s flags. Reported-by: Sashiko Fixes: 1d6f83f60f79 ("KVM: arm64: vgic: Store LPIs in an xarray") Signed-off-by: Carlos López Link: https://patch.msgid.link/20260715105137.3973823-5-clopez@suse.de Signed-off-by: Marc Zyngier --- arch/arm64/kvm/vgic/vgic-its.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 5c5d1772147d..36ab3e492915 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -121,7 +121,8 @@ static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid, * from the deferred release path, pending cleanup by * vgic_release_deleted_lpis(). Evict and free it if present. */ - oldirq = __xa_store(&dist->lpi_xa, intid, irq, 0); + oldirq = __xa_store(&dist->lpi_xa, intid, irq, + GFP_NOWAIT | __GFP_ACCOUNT); ret = xa_err(oldirq); if (ret) { xa_unlock_irqrestore(&dist->lpi_xa, flags); From bbece712cfc7f286b2908ac120dcf700279d87eb Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Wed, 15 Jul 2026 11:51:00 +0100 Subject: [PATCH 202/456] KVM: arm64: Fix hyp_trace clock disabling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the disable path in hyp_trace_clock_enable(), which fell through to re-initialize and reschedule the clock after cancelling the work. Return early instead. While at it, cleanup hyp_trace_clock::lock which is unused and hyp_trace_clock::running which is redundant: the trace_remote framework already serializes calls to the callback enable_tracing. Fixes: b22888917fa4 ("KVM: arm64: Sync boot clock with the nVHE/pKVM hyp") Signed-off-by: Vincent Donnefort Reviewed-by: Fuad Tabba (✓ DKIM/linux.dev) Link: https://patch.msgid.link/20260715105100.3178255-1-vdonnefort@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp_trace.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/arch/arm64/kvm/hyp_trace.c b/arch/arm64/kvm/hyp_trace.c index 2411b4c32932..9bfa368dd841 100644 --- a/arch/arm64/kvm/hyp_trace.c +++ b/arch/arm64/kvm/hyp_trace.c @@ -37,8 +37,6 @@ static struct hyp_trace_clock { u32 shift; struct delayed_work work; struct completion ready; - struct mutex lock; - bool running; } hyp_clock; static void __hyp_clock_work(struct work_struct *work) @@ -110,12 +108,9 @@ static void hyp_trace_clock_enable(struct hyp_trace_clock *hyp_clock, bool enabl { struct system_time_snapshot snap; - if (hyp_clock->running == enable) - return; - if (!enable) { cancel_delayed_work_sync(&hyp_clock->work); - hyp_clock->running = false; + return; } ktime_get_snapshot_id(CLOCK_BOOTTIME, &snap); @@ -128,7 +123,6 @@ static void hyp_trace_clock_enable(struct hyp_trace_clock *hyp_clock, bool enabl INIT_DELAYED_WORK(&hyp_clock->work, __hyp_clock_work); schedule_delayed_work(&hyp_clock->work, msecs_to_jiffies(CLOCK_INIT_MS)); wait_for_completion(&hyp_clock->ready); - hyp_clock->running = true; } /* Access to this struct within the trace_remote_callbacks are protected by the trace_remote lock */ @@ -304,9 +298,15 @@ static void hyp_trace_unload(struct trace_buffer_desc *desc, void *priv) static int hyp_trace_enable_tracing(bool enable, void *priv) { + int ret; + hyp_trace_clock_enable(&hyp_clock, enable); - return kvm_call_hyp_nvhe(__tracing_enable, enable); + ret = kvm_call_hyp_nvhe(__tracing_enable, enable); + if (ret) + hyp_trace_clock_enable(&hyp_clock, !enable); + + return ret; } static int hyp_trace_swap_reader_page(unsigned int cpu, void *priv) From df7a9d376f7a388ecfacf8dfe0b5819ddfba6972 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 10 Jul 2026 12:48:18 +0100 Subject: [PATCH 203/456] KVM: arm64: Fix potential leak in hyp_trace_buffer_alloc_bpages_backing In the very unlikely event of a failure in __map_hyp, the allocated backing pages are leaked in hyp_trace_buffer_alloc_bpages_backing(). Fix this by freeing the pages on error. Fixes: 3aed038aac8d ("KVM: arm64: Add trace remote for the nVHE/pKVM hyp") Reported-by: Sashiko Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Signed-off-by: Vincent Donnefort Link: https://patch.msgid.link/20260710114819.2689386-2-vdonnefort@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp_trace.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hyp_trace.c b/arch/arm64/kvm/hyp_trace.c index 9bfa368dd841..aabf2989d70d 100644 --- a/arch/arm64/kvm/hyp_trace.c +++ b/arch/arm64/kvm/hyp_trace.c @@ -154,6 +154,7 @@ static int hyp_trace_buffer_alloc_bpages_backing(struct hyp_trace_buffer *trace_ int nr_bpages = (PAGE_ALIGN(size) / PAGE_SIZE) + 1; size_t backing_size; void *start; + int ret; backing_size = PAGE_ALIGN(sizeof(struct simple_buffer_page) * nr_bpages * num_possible_cpus()); @@ -162,10 +163,16 @@ static int hyp_trace_buffer_alloc_bpages_backing(struct hyp_trace_buffer *trace_ if (!start) return -ENOMEM; + ret = __map_hyp(start, backing_size); + if (ret) { + free_pages_exact(start, backing_size); + return ret; + } + trace_buffer->desc->bpages_backing_start = (unsigned long)start; trace_buffer->desc->bpages_backing_size = backing_size; - return __map_hyp(start, backing_size); + return ret; } static void hyp_trace_buffer_free_bpages_backing(struct hyp_trace_buffer *trace_buffer) From ca28278d10ec234592989ad370d58294b9d43e0f Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 10 Jul 2026 12:48:19 +0100 Subject: [PATCH 204/456] KVM: arm64: Fix hyp_trace_desc allocation size in hyp_trace_load() The footprint calculated for struct hyp_trace_desc sizes only trace_buffer_desc and do not take into account the other fields. It worked so far thanks to the follow-up PAGE_ALIGN(). Fix the descriptor size and while at it, enforce an overflow check after PAGE_ALIGN(). Reported-by: Sashiko Fixes: 3aed038aac8d ("KVM: arm64: Add trace remote for the nVHE/pKVM hyp") Signed-off-by: Vincent Donnefort Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Link: https://patch.msgid.link/20260710114819.2689386-3-vdonnefort@google.com Signed-off-by: Marc Zyngier --- arch/arm64/kvm/hyp_trace.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/hyp_trace.c b/arch/arm64/kvm/hyp_trace.c index aabf2989d70d..1adfdc800187 100644 --- a/arch/arm64/kvm/hyp_trace.c +++ b/arch/arm64/kvm/hyp_trace.c @@ -229,18 +229,22 @@ static int hyp_trace_buffer_share_hyp(struct hyp_trace_buffer *trace_buffer) static struct trace_buffer_desc *hyp_trace_load(unsigned long size, void *priv) { struct hyp_trace_buffer *trace_buffer = priv; + size_t desc_size, tb_desc_size; struct hyp_trace_desc *desc; - size_t desc_size; int ret; if (WARN_ON(trace_buffer->desc)) return ERR_PTR(-EINVAL); - desc_size = trace_buffer_desc_size(size, num_possible_cpus()); + tb_desc_size = trace_buffer_desc_size(size, num_possible_cpus()); + desc_size = size_add(tb_desc_size, offsetof(struct hyp_trace_desc, trace_buffer_desc)); if (desc_size == SIZE_MAX) return ERR_PTR(-E2BIG); desc_size = PAGE_ALIGN(desc_size); + if (!desc_size) + return ERR_PTR(-E2BIG); + desc = (struct hyp_trace_desc *)alloc_pages_exact(desc_size, GFP_KERNEL); if (!desc) return ERR_PTR(-ENOMEM); @@ -256,7 +260,7 @@ static struct trace_buffer_desc *hyp_trace_load(unsigned long size, void *priv) if (ret) goto err_free_desc; - ret = trace_remote_alloc_buffer(&desc->trace_buffer_desc, desc_size, size, + ret = trace_remote_alloc_buffer(&desc->trace_buffer_desc, tb_desc_size, size, cpu_possible_mask); if (ret) goto err_free_backing; From e7821048e8d72a94b1fb7422f8b45aa374ff076f Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Wed, 17 Jun 2026 10:52:38 +0100 Subject: [PATCH 205/456] KVM: arm64: Add missing hyp_enter when trapping sysreg Add a missing hypervisor event call for hyp_enter on sysreg trapping, causing an unbalanced hyp_enter/hyp_exit. The enum hyp_enter_exit_reason is not ABI, so we can keep the ERET reasons at the end for clarity. Fixes: 696dfec22b8e ("KVM: arm64: Add hyp_enter/hyp_exit events to nVHE/pKVM hyp") Signed-off-by: Vincent Donnefort Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Link: https://patch.msgid.link/20260617095238.1530121-1-vdonnefort@google.com Signed-off-by: Marc Zyngier --- arch/arm64/include/asm/kvm_hypevents.h | 1 + arch/arm64/kvm/hyp/nvhe/hyp-main.c | 1 + arch/arm64/kvm/hyp_trace.c | 1 + 3 files changed, 3 insertions(+) diff --git a/arch/arm64/include/asm/kvm_hypevents.h b/arch/arm64/include/asm/kvm_hypevents.h index 743c49bd878f..5f6e6789d121 100644 --- a/arch/arm64/include/asm/kvm_hypevents.h +++ b/arch/arm64/include/asm/kvm_hypevents.h @@ -12,6 +12,7 @@ enum hyp_enter_exit_reason { HYP_REASON_SMC, HYP_REASON_HVC, + HYP_REASON_SYS, HYP_REASON_PSCI, HYP_REASON_HOST_ABORT, HYP_REASON_GUEST_EXIT, diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c index d3c69de698f4..d3df96ed8ba4 100644 --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c @@ -932,6 +932,7 @@ void handle_trap(struct kvm_cpu_context *host_ctxt) handle_host_mem_abort(host_ctxt); break; case ESR_ELx_EC_SYS64: + trace_hyp_enter(host_ctxt, HYP_REASON_SYS); if (handle_host_mte(esr)) break; fallthrough; diff --git a/arch/arm64/kvm/hyp_trace.c b/arch/arm64/kvm/hyp_trace.c index 1adfdc800187..9644c424819b 100644 --- a/arch/arm64/kvm/hyp_trace.c +++ b/arch/arm64/kvm/hyp_trace.c @@ -409,6 +409,7 @@ static const char *__hyp_enter_exit_reason_str(u8 reason) static const char strs[][12] = { "smc", "hvc", + "sys", "psci", "host_abort", "guest_exit", From 679d7201c1f09e37fa1c12ce28d84079c17fc87f Mon Sep 17 00:00:00 2001 From: Alexandru Elisei Date: Wed, 22 Jul 2026 10:03:54 +0100 Subject: [PATCH 206/456] KVM: arm64: Reject guest_memfd memslots when the VM has MTE The user cannot use MTE on VMAs created by mapping a guest_memfd file, as arch_calc_vm_flag_bits() does not set VM_MTE_ALLOWED. When creating a guest_memfd backed memslot, kvm_arch_prepare_memory_region() rejects the memslot if MTE is enabled for the VM and if guest_memfd has been mapped in a VMA that intersects the memslot. However, the documentation for KVM_SET_USER_MEMORY_REGION2 explicitly states that the only condition for userspace_addr is for it to be a legal userspace address, but the mapping is not required to be valid nor populated at memslot creation. If userspace sets userspace_addr to an address that hasn't been mapped, or if userspace_addr belongs to a VMA that isn't backed by the guest_memfd file, or if the VMA doesn't intersect the memslot, memslot creation is successful and KVM ends up with a VM with MTE and guest_memfd-backed memslots. The same happens if the order is reversed: when userspace enables MTE, KVM does not check if memslots backed by guest_memfd are already present. Fix both issues by rejecting guest_memfd-backed memslots when MTE is enabled, and by rejecting MTE when guest_memfd-backed memslots are already present. Fixes: 32e200bd6e44 ("KVM: arm64: Enable support for guest_memfd backed memory") Tested-by: Fuad Tabba Reviewed-by: Fuad Tabba Signed-off-by: Alexandru Elisei Link: https://patch.msgid.link/20260722090354.94245-1-alexandru.elisei@arm.com Signed-off-by: Marc Zyngier --- Documentation/virt/kvm/api.rst | 6 ++++++ arch/arm64/kvm/arm.c | 25 +++++++++++++++++++------ arch/arm64/kvm/mmu.c | 4 ++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index a5f9ee92f43e..e3003a241d5b 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -8414,6 +8414,12 @@ When this capability is enabled all memory in memslots must be mapped as attempts to create a memslot with an invalid mmap will result in an -EINVAL return. +``guest_memfd``, even though it is an anonymous file, is not supported with MTE. +Attempting to create a memslot backed by ``guest_memfd`` when the MTE capability +is enabled, or attempting to enable the MTE capability after +``guest_memfd``-backed memslots have been created, will result in an -EINVAL +return. + When enabled the VMM may make use of the ``KVM_ARM_MTE_COPY_TAGS`` ioctl to perform a bulk copy of tags to/from the guest. diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 50adfff75be8..9a6c72a18672 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -149,14 +149,27 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm, set_bit(KVM_ARCH_FLAG_RETURN_NISV_IO_ABORT_TO_USER, &kvm->arch.flags); break; - case KVM_CAP_ARM_MTE: - mutex_lock(&kvm->lock); - if (system_supports_mte() && !kvm->created_vcpus) { - r = 0; - set_bit(KVM_ARCH_FLAG_MTE_ENABLED, &kvm->arch.flags); + case KVM_CAP_ARM_MTE: { + struct kvm_memory_slot *memslot; + int bkt; + + guard(mutex)(&kvm->lock); + if (!system_supports_mte() || kvm->created_vcpus) + break; + + r = 0; + guard(mutex)(&kvm->slots_lock); + kvm_for_each_memslot(memslot, bkt, kvm_memslots(kvm)) { + if (kvm_slot_has_gmem(memslot)) { + r = -EINVAL; + break; + } } - mutex_unlock(&kvm->lock); + if (r == 0) + set_bit(KVM_ARCH_FLAG_MTE_ENABLED, &kvm->arch.flags); break; + + } case KVM_CAP_ARM_SYSTEM_SUSPEND: r = 0; set_bit(KVM_ARCH_FLAG_SYSTEM_SUSPEND_ENABLED, &kvm->arch.flags); diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 6c941aaa10c6..2d95203386ba 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -2652,6 +2652,10 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm, if (kvm_slot_has_gmem(new) && !kvm_memslot_is_gmem_only(new)) return -EINVAL; + /* guest_memfd is incompatible with MTE. */ + if (kvm_slot_has_gmem(new) && kvm_has_mte(kvm)) + return -EINVAL; + hva = new->userspace_addr; reg_end = hva + (new->npages << PAGE_SHIFT); From 88c26515313169806a412a362b32a1eca53d21bd Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 14 Jul 2026 16:23:21 +0800 Subject: [PATCH 207/456] iomap: correct the range of a partial dirty clear The block range calculation in ifs_clear_range_dirty() is incorrect when partially clearing a range in a folio. We cannot clear the dirty bit of the first block or the last block if the start or end offset is not blocksize-aligned. This has not yet caused any issues since we always clear a whole folio in iomap_writeback_folio(). Fix this by rounding up the first block to blocksize alignment, and calculate the last block by rounding down (using truncation). Correct the nr_blks calculation accordingly. Fixes: 4ce02c679722 ("iomap: Add per-block dirty state tracking to improve performance") Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260714082325.325163-2-yi.zhang@huaweicloud.com Reviewed-by: Joanne Koong Reviewed-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/buffered-io.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 276720bc18dc..238b8b1dea91 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -177,13 +177,17 @@ static void ifs_clear_range_dirty(struct folio *folio, { struct inode *inode = folio->mapping->host; unsigned int blks_per_folio = i_blocks_per_folio(inode, folio); - unsigned int first_blk = (off >> inode->i_blkbits); - unsigned int last_blk = (off + len - 1) >> inode->i_blkbits; - unsigned int nr_blks = last_blk - first_blk + 1; + unsigned int first_blk = round_up(off, i_blocksize(inode)) >> + inode->i_blkbits; + unsigned int last_blk = (off + len) >> inode->i_blkbits; unsigned long flags; + if (first_blk >= last_blk) + return; + spin_lock_irqsave(&ifs->state_lock, flags); - bitmap_clear(ifs->state, first_blk + blks_per_folio, nr_blks); + bitmap_clear(ifs->state, first_blk + blks_per_folio, + last_blk - first_blk); spin_unlock_irqrestore(&ifs->state_lock, flags); } From 562d192c43459d70d955775e8a17eebd995539d4 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 14 Jul 2026 16:23:22 +0800 Subject: [PATCH 208/456] iomap: support invalidating partial folios Current iomap_invalidate_folio() can only invalidate an entire folio. If we truncate a partial folio on a filesystem where the block size is smaller than the folio size, it will leave behind dirty bits for the truncated or punched blocks. During the write-back process, it will attempt to map the invalid hole range. Fortunately, this has not caused any real problems so far because the ->writeback_range() function corrects the length. However, the implementation of FALLOC_FL_ZERO_RANGE in ext4 depends on the support for invalidating partial folios. When ext4 partially zeroes out a dirty and unwritten folio, it does not perform a flush first like XFS. Therefore, if the dirty bits of the corresponding area cannot be cleared, the zeroed area after writeback remains in the written state rather than reverting to the unwritten state. Fix this by supporting invalidation of partial folios. Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260714082325.325163-3-yi.zhang@huaweicloud.com Reviewed-by: "Darrick J. Wong" Reviewed-by: Joanne Koong Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/buffered-io.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 238b8b1dea91..b482e112321f 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -815,6 +815,8 @@ void iomap_invalidate_folio(struct folio *folio, size_t offset, size_t len) WARN_ON_ONCE(folio_test_writeback(folio)); folio_cancel_dirty(folio); ifs_free(folio); + } else { + iomap_clear_range_dirty(folio, offset, len); } } EXPORT_SYMBOL_GPL(iomap_invalidate_folio); From 7a6fd6b21d7e1737b40de1a210acf9e6a1e4d59e Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 14 Jul 2026 16:23:23 +0800 Subject: [PATCH 209/456] iomap: fix incorrect did_zero setting in iomap_zero_iter() The did_zero output parameter was unconditionally set after the loop, which is incorrect. It should only be set when the zeroing operation actually completes, not when IOMAP_F_STALE is set or when IOMAP_F_FOLIO_BATCH is set but !folio causes the loop to break early, or when iomap_iter_advance() returns an error. This causes did_zero to be incorrectly set when zeroing a clean unwritten extent because the loop exits early without actually zeroing any data. Fix it by using a local variable to track whether any folio was actually zeroed, and only set did_zero after the loop if zeroing happened. Fixes: 98eb8d95025b ("iomap: set did_zero to true when zeroing successfully") Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260714082325.325163-4-yi.zhang@huaweicloud.com Reviewed-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/buffered-io.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index b482e112321f..0cf62e516827 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -1625,6 +1625,7 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero, const struct iomap_write_ops *write_ops) { u64 bytes = iomap_length(iter); + bool zeroed = false; int status; do { @@ -1645,6 +1646,8 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero, /* a NULL folio means we're done with a folio batch */ if (!folio) { status = iomap_iter_advance_full(iter); + if (status) + return status; break; } @@ -1655,6 +1658,7 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero, bytes); folio_zero_range(folio, offset, bytes); + zeroed = true; folio_mark_accessed(folio); ret = iomap_write_end(iter, bytes, bytes, folio); @@ -1664,10 +1668,10 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero, status = iomap_iter_advance(iter, bytes); if (status) - break; + return status; } while ((bytes = iomap_length(iter)) > 0); - if (did_zero) + if (did_zero && zeroed) *did_zero = true; return status; } From 9c7d8f7c8994c790fca501dc45ce66e7356cbe05 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 14 Jul 2026 16:23:24 +0800 Subject: [PATCH 210/456] iomap: fix out-of-bounds bitmap_set() with zero-length range ifs_set_range_dirty() and ifs_set_range_uptodate() compute last_blk as (off + len - 1) >> i_blkbits. When off is 0 and len is 0, the unsigned subtraction underflows to SIZE_MAX, producing a huge last_blk and nr_blks value that causes bitmap_set() to write far beyond the ifs->state allocation. Regarding ifs_set_range_uptodate(), it is temporarily safe because len cannot be passed in as 0. However, for ifs_set_range_dirty() this is reachable from __iomap_write_end(): when copy_folio_from_iter_atomic() returns 0 (e.g. user buffer fault) and the folio is already uptodate, the guard at the top of __iomap_write_end() does not trigger because !folio_test_uptodate() is false, and iomap_set_range_dirty() is called with copied == 0. Add a !len guard to both functions before the computation, so that a zero-length range is a no-op. Fixes: 4ce02c679722 ("iomap: Add per-block dirty state tracking to improve performance") Cc: stable@vger.kernel.org # v6.6 Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260714082325.325163-5-yi.zhang@huaweicloud.com Reviewed-by: Joanne Koong Reviewed-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/buffered-io.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 0cf62e516827..3a3ac3051fb0 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -68,11 +68,13 @@ static bool ifs_set_range_uptodate(struct folio *folio, struct iomap_folio_state *ifs, size_t off, size_t len) { struct inode *inode = folio->mapping->host; - unsigned int first_blk = off >> inode->i_blkbits; - unsigned int last_blk = (off + len - 1) >> inode->i_blkbits; - unsigned int nr_blks = last_blk - first_blk + 1; + unsigned int first_blk, last_blk; - bitmap_set(ifs->state, first_blk, nr_blks); + if (len) { + first_blk = off >> inode->i_blkbits; + last_blk = (off + len - 1) >> inode->i_blkbits; + bitmap_set(ifs->state, first_blk, last_blk - first_blk + 1); + } return ifs_is_fully_uptodate(folio, ifs); } @@ -204,13 +206,17 @@ static void ifs_set_range_dirty(struct folio *folio, { struct inode *inode = folio->mapping->host; unsigned int blks_per_folio = i_blocks_per_folio(inode, folio); - unsigned int first_blk = (off >> inode->i_blkbits); - unsigned int last_blk = (off + len - 1) >> inode->i_blkbits; - unsigned int nr_blks = last_blk - first_blk + 1; + unsigned int first_blk, last_blk; unsigned long flags; + if (!len) + return; + + first_blk = off >> inode->i_blkbits; + last_blk = (off + len - 1) >> inode->i_blkbits; spin_lock_irqsave(&ifs->state_lock, flags); - bitmap_set(ifs->state, first_blk + blks_per_folio, nr_blks); + bitmap_set(ifs->state, first_blk + blks_per_folio, + last_blk - first_blk + 1); spin_unlock_irqrestore(&ifs->state_lock, flags); } From 09b53b0787ee80b71b1dcceb99d004a33e55b823 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 14 Jul 2026 16:23:25 +0800 Subject: [PATCH 211/456] iomap: add comments for ifs_clear/set_range_dirty() The range alignment strategy differs between ifs_clear_range_dirty() and ifs_set_range_dirty(). The former rounds inwards to clear only fully-covered blocks, while the latter rounds outwards to mark any partially-touched block as dirty. Add comments to document this asymmetry in block range calculation. Suggested-by: "Darrick J. Wong" Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260714082325.325163-6-yi.zhang@huaweicloud.com Reviewed-by: Joanne Koong Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/buffered-io.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 3a3ac3051fb0..6d9a2efd4bee 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -174,6 +174,13 @@ static unsigned iomap_find_dirty_range(struct folio *folio, u64 *range_start, return range_end - *range_start; } +/* + * Clear the per-block dirty bits for the range [@off, @off + @len) within a + * folio. The range is rounded inwards so that only blocks fully covered by + * the range are cleared. This is required for operations like folio + * invalidation, where we must ensure a block is fully clean before discarding + * it. + */ static void ifs_clear_range_dirty(struct folio *folio, struct iomap_folio_state *ifs, size_t off, size_t len) { @@ -201,6 +208,13 @@ static void iomap_clear_range_dirty(struct folio *folio, size_t off, size_t len) ifs_clear_range_dirty(folio, ifs, off, len); } +/* + * Set the per-block dirty bits for the range [@off, @off + @len) within a + * folio. The range is rounded outwards so that any block partially touched + * by the range is marked dirty. This ensures blocks containing even a + * single dirty byte will be included in subsequent writeback, preventing + * data loss when partial blocks are written. + */ static void ifs_set_range_dirty(struct folio *folio, struct iomap_folio_state *ifs, size_t off, size_t len) { From c97cd6f447d8727af3d457bca3a9283a77dd70f8 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Mon, 13 Jul 2026 15:42:06 +0800 Subject: [PATCH 212/456] iomap: prevent ioend merge when io_private differs Different io_private values indicate distinct completion contexts that must not be merged together, as this could leak or corrupt the private data associated with each ioend. Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260713074206.1768006-1-yi.zhang@huaweicloud.com Reviewed-by: Christoph Hellwig Reviewed-by: Ojaswin Mujoo Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/ioend.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 0565328764c1..30468d51b5ad 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -385,6 +385,8 @@ static bool iomap_ioend_can_merge(struct iomap_ioend *ioend, if (ioend->io_bio.bi_status != next->io_bio.bi_status) return false; + if (ioend->io_private != next->io_private) + return false; if (next->io_flags & IOMAP_IOEND_BOUNDARY) return false; if ((ioend->io_flags & IOMAP_IOEND_NOMERGE_FLAGS) != From 62d9853aa4ce6e9797b6949804891be14b219752 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 13 Jul 2026 16:22:55 +0100 Subject: [PATCH 213/456] afs: Fix afs_edit_dir_remove() to get, not find, block 0 Fix afs_edit_dir_remove() to use afs_dir_get_block() to get block 0 rather than afs_dir_find_block() as the latter caches the found block in the afs_dir_iter and may[*] switch out the page it's on if another afs_dir_find_block() is done. This parallels what afs_edit_dir_add() does. [*] There's more than one block per page. Fixes: a5b5beebcf96 ("afs: Use the contained hashtable to search a directory") Closes: https://sashiko.dev/#/patchset/20260706153408.1231650-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/2380759.1783956175@warthog.procyon.org.uk cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: linux-fsdevel@vger.kernel.org cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/dir_edit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/afs/dir_edit.c b/fs/afs/dir_edit.c index fd3aa9f97ce6..3ead36a07048 100644 --- a/fs/afs/dir_edit.c +++ b/fs/afs/dir_edit.c @@ -415,7 +415,7 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, if (!afs_dir_init_iter(&iter, name)) return; - meta = afs_dir_find_block(&iter, 0); + meta = afs_dir_get_block(&iter, 0); if (!meta) return; From 0ef8faff490be6aa1a1e5dfcb0c8492689e91c0f Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Wed, 15 Jul 2026 03:35:16 -0700 Subject: [PATCH 214/456] fs: push nr_cached_objects memcg gating into individual filesystems Commit 0baad6f9b997 ("fs/super: skip non-memcg-aware nr_cached_objects in memcg slab shrink") added a check in fs/super.c that skipped every ->nr_cached_objects() hook whenever the shrinker was invoked for a non-root memcg, on the assumption that none of them honour sc->memcg. That assumption is wrong for XFS, whose inode-reclaim hook is intentionally driven from per-memcg contexts to free memcg-charged slab. Encoding a blanket "never memcg-aware" policy in fs/super.c short-circuits that path. Push the check down into the callbacks whose counters really are irrelevant to per-memcg reclaim - btrfs_nr_cached_objects() and shmem_unused_huge_count() - and drop the fs/super.c gate. Each filesystem can now lift the restriction independently if its counter later grows memcg awareness, without touching fs/super.c. Introduce mem_cgroup_shrink_is_root() in so the callbacks don't open-code "sc->memcg is NULL or root". Fixes: 0baad6f9b997 ("fs/super: skip non-memcg-aware nr_cached_objects in memcg slab shrink") Acked-by: Qi Zheng Reviewed-by: Jan Kara Reviewed-by: Shakeel Butt Signed-off-by: Usama Arif Link: https://patch.msgid.link/20260715103516.2410175-1-usama.arif@linux.dev Acked-by: David Sterba Reviewed-by: Baolin Wang Signed-off-by: Christian Brauner (Amutable) --- fs/btrfs/super.c | 10 ++++++++++ include/linux/memcontrol.h | 21 +++++++++++++++++++++ mm/shmem.c | 10 ++++++++++ 3 files changed, 41 insertions(+) diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index a7d804219bec..cc4537435399 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -2434,6 +2435,15 @@ static long btrfs_nr_cached_objects(struct super_block *sb, struct shrink_contro struct btrfs_fs_info *fs_info = btrfs_sb(sb); const s64 nr = percpu_counter_read_positive(&fs_info->evictable_extent_maps); + /* + * The evictable extent map counter is filesystem-global and does not + * honour sc->memcg, so it is only meaningful on the global (kswapd or + * root direct reclaim) shrink path. Skip the per-memcg iterations of + * shrink_slab_memcg() to avoid queueing duplicate global work. + */ + if (!mem_cgroup_shrink_is_root(sc)) + return 0; + trace_btrfs_extent_map_shrinker_count(fs_info, nr); return nr; diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index e1f46a0016fc..5407e4200460 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -520,6 +520,22 @@ static inline bool mem_cgroup_is_root(struct mem_cgroup *memcg) return (memcg == root_mem_cgroup); } +/** + * mem_cgroup_shrink_is_root - is this a global or root-memcg shrink invocation? + * @sc: shrink_control describing the current shrinker call + * + * Returns true when @sc represents a global reclaim shrink (sc->memcg == NULL) + * or a root-memcg shrink, i.e. not a per-memcg iteration of + * shrink_slab_memcg(). Filesystems whose ->nr_cached_objects()/ + * ->free_cached_objects() implementations operate on filesystem-global state + * and do not honour sc->memcg can use this to early-return 0 in per-memcg + * contexts. + */ +static inline bool mem_cgroup_shrink_is_root(struct shrink_control *sc) +{ + return !sc->memcg || mem_cgroup_is_root(sc->memcg); +} + static inline bool obj_cgroup_is_root(const struct obj_cgroup *objcg) { return objcg->is_root; @@ -1071,6 +1087,11 @@ static inline bool mem_cgroup_is_root(struct mem_cgroup *memcg) return true; } +static inline bool mem_cgroup_shrink_is_root(struct shrink_control *sc) +{ + return true; +} + static inline bool obj_cgroup_is_root(const struct obj_cgroup *objcg) { return true; diff --git a/mm/shmem.c b/mm/shmem.c index b51f83c970bb..9001aaf3b7b9 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -846,6 +846,16 @@ static long shmem_unused_huge_count(struct super_block *sb, struct shrink_control *sc) { struct shmem_sb_info *sbinfo = SHMEM_SB(sb); + + /* + * The per-superblock shrinklist is filesystem-global and does not + * honour sc->memcg, so it is only meaningful on the global (kswapd or + * root direct reclaim) shrink path. Skip the per-memcg iterations of + * shrink_slab_memcg() to avoid queueing duplicate global work. + */ + if (!mem_cgroup_shrink_is_root(sc)) + return 0; + return READ_ONCE(sbinfo->shrinklist_len); } #else /* !CONFIG_TRANSPARENT_HUGEPAGE */ From b04a248cfa6cfa1e7dc9ce91cb1eb88b1a70dd69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= Date: Fri, 3 Jul 2026 17:07:43 +0200 Subject: [PATCH 215/456] drm/tests: shmem: Set DMA mask to 64-bit in drm_gem_shmem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_gem_shmem_test_purge [1] and drm_gem_shmem_test_get_pages_sgt [2] intermittently fail on ppc64le and s390x CI systems with a DMA address overflow: DMA addr 0x0000000100307000+4096 overflow (mask ffffffff, bus limit 0) WARNING: kernel/dma/direct.h:114 dma_direct_map_sg+0x778/0x920 drm_gem_shmem_test_purge: ASSERTION FAILED at drivers/gpu/drm/tests/drm_gem_shmem_test.c:330 Expected sgt is not error, but is: -5 The call chain leading to the failure is: drm_gem_shmem_test_purge() / drm_gem_shmem_test_get_pages_sgt() drm_gem_shmem_get_pages_sgt() drm_gem_shmem_get_pages_sgt_locked() [drm_gem_shmem_helper.c] dma_map_sgtable() [mapping.c] __dma_map_sg_attrs() dma_direct_map_sg() [direct.c] dma_direct_map_phys() [kernel/dma/direct.h] dma_capable() Checks addr against DMA mask -> FAILS: addr > 0xFFFFFFFF The root cause is that KUnit devices are initialized with a 32-bit DMA mask (DMA_BIT_MASK(32)) in lib/kunit/device.c. On ppc64le and s390x systems with physical memory above 4GB, page allocations can land at addresses that exceed this mask. When drm_gem_shmem_get_pages_sgt() attempts to DMA-map these pages via dma_map_sgtable(), the DMA layer rejects the mapping because the physical address overflows the 32-bit mask. The failure is intermittent because pages may or may not be allocated above 4GB on any given run depend on memory pressure. Fix by setting a 64-bit DMA mask on the device before calling drm_gem_shmem_get_pages_sgt() for all tests, following the same pattern already used in drm_gem_shmem_test_obj_create_private(). [1] https://s3.amazonaws.com/arr-cki-prod-trusted-artifacts/trusted-artifacts/2643976103/test_s390x/15128551935/artifacts/jobwatch/logs/recipes/21561049/tasks/220716793/results/1014626315/logs/dmesg.log [2] https://s3.amazonaws.com/arr-cki-prod-trusted-artifacts/trusted-artifacts/2643976103/test_ppc64le/15128551933/artifacts/jobwatch/logs/recipes/21561041/tasks/220716705/results/1014628163/logs/dmesg.log Fixes: 93032ae634d4 ("drm/test: add a test suite for GEM objects backed by shmem") Closes: https://datawarehouse.cki-project.org/issue/5345 Closes: https://datawarehouse.cki-project.org/issue/3184 Assisted-by: Claude:claude-4.6-opus Reviewed-by: Thomas Zimmermann Signed-off-by: José Expósito Link: https://patch.msgid.link/20260703150808.3832-1-jose.exposito89@gmail.com --- drivers/gpu/drm/tests/drm_gem_shmem_test.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/tests/drm_gem_shmem_test.c b/drivers/gpu/drm/tests/drm_gem_shmem_test.c index 44a190109249..5e69ff1d1ad6 100644 --- a/drivers/gpu/drm/tests/drm_gem_shmem_test.c +++ b/drivers/gpu/drm/tests/drm_gem_shmem_test.c @@ -95,13 +95,9 @@ static void drm_gem_shmem_test_obj_create_private(struct kunit *test) sg_init_one(sgt->sgl, buf, TEST_SIZE); /* - * Set the DMA mask to 64-bits and map the sgtables - * otherwise drm_gem_shmem_free will cause a warning - * on debug kernels. + * Map the sgtables otherwise drm_gem_shmem_free will cause a warning on + * debug kernels. */ - ret = dma_set_mask(drm_dev->dev, DMA_BIT_MASK(64)); - KUNIT_ASSERT_EQ(test, ret, 0); - ret = dma_map_sgtable(drm_dev->dev, sgt, DMA_BIDIRECTIONAL, 0); KUNIT_ASSERT_EQ(test, ret, 0); @@ -352,11 +348,19 @@ static int drm_gem_shmem_test_init(struct kunit *test) { struct device *dev; struct drm_device *drm_dev; + int ret; /* Allocate a parent device */ dev = drm_kunit_helper_alloc_device(test); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + /* + * Set the DMA mask to 64-bits to avoid intermittent failures calling + * drm_gem_shmem_get_pages_sgt(). + */ + ret = dma_set_mask(dev, DMA_BIT_MASK(64)); + KUNIT_ASSERT_EQ(test, ret, 0); + /* * The DRM core will automatically initialize the GEM core and create * a DRM Memory Manager object which provides an address space pool From 8b7e8245e2293078f657521236ac92c045552e5a Mon Sep 17 00:00:00 2001 From: Guidong Han <2045gemini@gmail.com> Date: Sat, 18 Jul 2026 18:44:06 +0800 Subject: [PATCH 216/456] eventpoll: pin files while checking reverse paths Commit 319c15174757 ("epoll: take epitem list out of struct file") intentionally removed temporary file references from the reverse path check list. At the time, both epitems and their files were freed after an RCU grace period, so unlist_file() could obtain file->f_lock through an epitem while clear_tfile_check_list() held rcu_read_lock(). Commit 0ede61d8589c ("file: convert to SLAB_TYPESAFE_BY_RCU") made struct file SLAB_TYPESAFE_BY_RCU and removed its RCU-delayed freeing. RCU still protects the epitem, but no longer keeps the referenced file from being freed and reused. A concurrent close can therefore make unlist_file() lock or unlock f_lock in a recycled file object. This violates the documented SLAB_TYPESAFE_BY_RCU rule requiring a reference before acquiring an object's lock. The race was reproduced, causing a wild unlock of f_lock in a recycled file and breaking its mutual exclusion. Add ->file to epitems_head to remember the pinned file independently of ->epitems. A concurrent EPOLL_CTL_DEL can empty ->epitems before the head is unlisted, leaving no epi->ffd.file from which to drop the reference. In list_file(), acquire the reference before adding the head to the check list. The caller either owns a reference or holds the ep->mtx for the epitem leading to the file. In the latter case, file_ref_get() can fail after the last reference is dropped, but eventpoll_release_file() must acquire the same mutex before the file can be freed. The dying leaf can be skipped because removing links cannot increase the reverse path count. In unlist_file(), epnested_mutex excludes another list_file() or unlist_file(), while head->next prevents a concurrent EPOLL_CTL_DEL from freeing the head. Save head->file locally, clear it with head->next under f_lock, and drop the reference after the RCU-protected operation. Christian Brauner quotes: > SLAB_TYPESAFE_BY_RCU allows a slab slot to be reused while an RCU reader > still holds its old address. Once that address contains a new live > struct file, KASAN sees valid, unpoisoned memory and cannot distinguish > the stale object identity. CONFIG_DEBUG_SPINLOCK exposes the failure > instead. > > The failing interleaving is: > > CPU0: nested EPOLL_CTL_ADD CPU1: close/open churn > ------------------------------------ --------------------------------- > p = hlist_first_rcu(&head->epitems) > epi = container_of(p, ...) > close(victim) > __fput() > eventpoll_release_file() > file_free(victim) > // the slot is free; f_lock remains > spin_lock(&epi->ffd.file->f_lock) > open() reuses the slot as new_file > spin_lock_init(&new_file->f_lock) > spin_unlock(&epi->ffd.file->f_lock) // wild unlock of new_file's lock > > CONFIG_DEBUG_SPINLOCK reports: > > BUG: spinlock already unlocked on CPU#0, poc_unlist/150 > lock: 0xffff8880067fb200, .magic: dead4ead, .owner: /-1, .owner_cpu: -1 > CPU: 0 UID: 1000 PID: 150 Comm: poc_unlist Not tainted 7.2.0-rc3-dirty #22 PREEMPTLAZY > Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 > Call Trace: > > dump_stack_lvl+0x64/0x80 > do_raw_spin_unlock+0x75/0xb0 > _raw_spin_unlock+0xe/0x30 > clear_tfile_check_list+0x88/0xe0 > do_epoll_ctl_file+0x519/0xcf0 > ? __pfx_ep_ptable_queue_proc+0x10/0x10 > do_epoll_ctl+0x8f/0x100 > __x64_sys_epoll_ctl+0x6f/0xa0 > do_syscall_64+0xdc/0x520 > ? srso_alias_return_thunk+0x5/0xfbef5 > entry_SYSCALL_64_after_hwframe+0x76/0x7e > RIP: 0033:0x42034e > Code: 48 c7 c1 b8 ff ff ff f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 49 89 ca b8 e9 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b8 ff ff ff f7 d8 64 89 01 48 > RSP: 002b:00007a657ff3c198 EFLAGS: 00000202 ORIG_RAX: 00000000000000e9 > RAX: ffffffffffffffda RBX: 00007a657ff3ccdc RCX: 000000000042034e > RDX: 0000000000000003 RSI: 0000000000000001 RDI: 0000000000000004 > RBP: 00007a657ff3c2f0 R08: 0000000000000000 R09: 00007a657ff3c6c0 > R10: 00007a657ff3c1a4 R11: 0000000000000202 R12: 00007a657ff3c6c0 > R13: ffffffffffffffb8 R14: 000000000000000d R15: 00007fffb7de0210 > > ------------[ cut here ]------------ > > unlist_file() does not appear as a separate frame because it was inlined > into clear_tfile_check_list(). This report was obtained with mdelay() > instrumentation immediately before spin_lock() and spin_unlock() in > unlist_file() to widen the two race windows. > > More importantly, this is a wild unlock. The stale unlock can target > f_lock of a different live file and invalidate mutual exclusion for > state protected by that lock. Turning this into a reliable exploit > would require precise scheduling and same-slot reuse and is likely > difficult, but the primitive is potentially exploitable. Reported-by: Qi Tang Reported-by: Junxi Qian Fixes: 0ede61d8589c ("file: convert to SLAB_TYPESAFE_BY_RCU") Cc: stable@vger.kernel.org Signed-off-by: Guidong Han <2045gemini@gmail.com> Link: https://patch.msgid.link/20260718104406.27897-1-2045gemini@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/eventpoll.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/fs/eventpoll.c b/fs/eventpoll.c index 0e65c7431dfc..eed8cecd94e3 100644 --- a/fs/eventpoll.c +++ b/fs/eventpoll.c @@ -459,11 +459,14 @@ static struct kmem_cache *pwq_cache __ro_after_init; * Wrapper anchor for file->f_ep when the watched file is not itself an * eventpoll; for the epoll-watches-epoll case, file->f_ep points at * &watched_ep->refs directly. The ->next field threads - * ctx->tfile_check_list during one EPOLL_CTL_ADD path check. + * ctx->tfile_check_list during one EPOLL_CTL_ADD path check. The ->file + * field holds a reference to the associated file while the head is on + * the list. */ struct epitems_head { struct hlist_head epitems; struct epitems_head *next; + struct file *file; }; static struct kmem_cache *ephead_cache __ro_after_init; @@ -480,6 +483,16 @@ static void list_file(struct file *file, struct ep_ctl_ctx *ctx) head = container_of(file->f_ep, struct epitems_head, epitems); if (!head->next) { + /* + * The caller owns a reference to @file or holds the ep->mtx for the + * epitem that led here. The latter blocks eventpoll_release_file() + * before the file allocation can be freed and reused. A dying leaf + * can be skipped since removing links cannot increase the reverse + * path count. + */ + if (!file_ref_get(&file->f_ref)) + return; + head->file = file; head->next = ctx->tfile_check_list; ctx->tfile_check_list = head; } @@ -489,15 +502,18 @@ static void unlist_file(struct epitems_head *head) { struct epitems_head *to_free = head; struct hlist_node *p = rcu_dereference(hlist_first_rcu(&head->epitems)); + struct file *file = head->file; if (p) { struct epitem *epi= container_of(p, struct epitem, fllink); spin_lock(&epi->ffd.file->f_lock); if (!hlist_empty(&head->epitems)) to_free = NULL; head->next = NULL; + head->file = NULL; spin_unlock(&epi->ffd.file->f_lock); } free_ephead(to_free); + fput(file); } #ifdef CONFIG_SYSCTL From 7917d16d14fb512f8ffe3815b7940b6c93ff4fde Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Thu, 23 Jul 2026 22:27:15 +0800 Subject: [PATCH 217/456] LoongArch: Increase TASK_STRUCT_OFFSET up to 2040 for 32BIT THREAD_INFO_IN_TASK increase the size of task_struct, which casuses a build error for the 32BIT kernel if RANDSTRUCT is enabled. So increase TASK_STRUCT_OFFSET as big as possible (2040), but can still be aligned and be fit in the addi.w instruction. Cc: stable@vger.kernel.org Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/asmmacro.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/loongarch/include/asm/asmmacro.h b/arch/loongarch/include/asm/asmmacro.h index a648be5f723f..b7423d1ac568 100644 --- a/arch/loongarch/include/asm/asmmacro.h +++ b/arch/loongarch/include/asm/asmmacro.h @@ -14,7 +14,7 @@ #ifdef CONFIG_64BIT #define TASK_STRUCT_OFFSET 0 #else -#define TASK_STRUCT_OFFSET 2000 +#define TASK_STRUCT_OFFSET 2040 #endif .macro cpu_save_nonscratch thread From 7ea74820edcb22ffa3fb068076d73c6821d7e6d2 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Thu, 23 Jul 2026 22:27:16 +0800 Subject: [PATCH 218/456] LoongArch: Fix build errors due to wrong instructions for 32BIT In some assembly files there are some instructions that only valid for 64BIT, but those files can be compiled for 32BIT and cause build errors. So, replace those instructions with macros: li.d --> LONG_LI (li.w or li.d), addi.d --> PTR_ADDI (addi.w or addi.d). BTW, Re-tab the indention in the assembly files for alignment. Cc: stable@vger.kernel.org # 6.19+ Signed-off-by: Huacai Chen --- arch/loongarch/kernel/rethook_trampoline.S | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/arch/loongarch/kernel/rethook_trampoline.S b/arch/loongarch/kernel/rethook_trampoline.S index d4ceb2fa2a5c..2e009fbea53f 100644 --- a/arch/loongarch/kernel/rethook_trampoline.S +++ b/arch/loongarch/kernel/rethook_trampoline.S @@ -71,27 +71,27 @@ cfi_ld s7, PT_R30 cfi_ld s8, PT_R31 LONG_L t0, sp, PT_CRMD - li.d t1, 0x7 /* mask bit[1:0] PLV, bit[2] IE */ + LONG_LI t1, 0x7 /* mask bit[1:0] PLV, bit[2] IE */ csrxchg t0, t1, LOONGARCH_CSR_CRMD .endm SYM_CODE_START(arch_rethook_trampoline) UNWIND_HINT_UNDEFINED - addi.d sp, sp, -PT_SIZE + PTR_ADDI sp, sp, -PT_SIZE save_all_base_regs - addi.d t0, sp, PT_SIZE - LONG_S t0, sp, PT_R3 + PTR_ADDI t0, sp, PT_SIZE + LONG_S t0, sp, PT_R3 - move a0, sp /* pt_regs */ + move a0, sp /* pt_regs */ - bl arch_rethook_trampoline_callback + bl arch_rethook_trampoline_callback /* use the result as the return-address */ - move ra, a0 + move ra, a0 restore_all_base_regs - addi.d sp, sp, PT_SIZE + PTR_ADDI sp, sp, PT_SIZE - jr ra + jr ra SYM_CODE_END(arch_rethook_trampoline) From ea68d444a658783234a06f05414e41cf93a18fb2 Mon Sep 17 00:00:00 2001 From: Kanglong Wang Date: Thu, 23 Jul 2026 22:27:29 +0800 Subject: [PATCH 219/456] LoongArch: Move jump_label_init() before parse_early_param() When enabling both CONFIG_MEM_ALLOC_PROFILING=y and CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT=y, then diabling memory profiling by adding the boot parameter 'sysctl.vm.mem_profiling=0' will cause the kernel failed to boot. After analysis, this is because jump_label_init() must be called before parse_early_param(), the early param handlers may modify static keys by static_branch_enable/disable(). Fix this by moving jump_label_init() to before parse_early_param(). The solution is similar to other architectures. Cc: Signed-off-by: Kanglong Wang Signed-off-by: Huacai Chen --- arch/loongarch/kernel/setup.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c index eaebb52bd36e..6fa4a22a58fd 100644 --- a/arch/loongarch/kernel/setup.c +++ b/arch/loongarch/kernel/setup.c @@ -603,6 +603,7 @@ void __init setup_arch(char **cmdline_p) memblock_init(); pagetable_init(); bootcmdline_init(cmdline_p); + jump_label_init(); /* Initialise the static keys for early params */ parse_early_param(); reserve_initrd_mem(); @@ -610,8 +611,6 @@ void __init setup_arch(char **cmdline_p) arch_mem_init(cmdline_p); resource_init(); - jump_label_init(); /* Initialise the static keys for paravirtualization */ - #ifdef CONFIG_SMP plat_smp_setup(); prefill_possible_map(); From 4e8f58620f6717f72f3d88a2c8f25c0c656d0ba7 Mon Sep 17 00:00:00 2001 From: Rong Bao Date: Thu, 23 Jul 2026 22:27:29 +0800 Subject: [PATCH 220/456] LoongArch: Retrieve CPU package ID from PPTT when available Currently, the LoongArch CPU topology initialization code calculates each core's package ID by dividing its physical ID by loongson_sysconf. cores_per_package. This relies on the assumption that cores_per_package counts in the same domain as physical IDs. On Loongson-3B6000 (XB612B0V_1.2), cores_per_package matches the visible core count -- 24 in this case. However, the physical IDs range from 0 to 31 in a noncontinuous fashion: $ cat /proc/cpuinfo | grep -i -F 'global_id' global_id : 0 global_id : 1 global_id : 4 global_id : 5 global_id : 6 global_id : 7 global_id : 8 global_id : 9 global_id : 10 global_id : 11 global_id : 14 global_id : 15 global_id : 16 global_id : 17 global_id : 20 global_id : 21 global_id : 22 global_id : 23 global_id : 26 global_id : 27 global_id : 28 global_id : 29 global_id : 30 global_id : 31 Retrieve the exact package ID from ACPI PPTT when available, in the same style as retrieving the core ID and thread ID in parse_acpi_topology(). Use this information in loongson_init_secondary() when the PPTT readout is successful. The original division logic is kept as a fallback. Meanwhile, since some existing code paths like loongson3_cpufreq expect a continuous integer sequence of package IDs in [0, MAX_PACKAGES) when retrieving from cpu_data[], here we also canonicalize the package ID to be filled in parse_acpi_topology() to meet such an expectation. Cc: stable@vger.kernel.org Tested-by: Mingcong Bai Co-developed-by: Xi Ruoyao Signed-off-by: Xi Ruoyao Signed-off-by: Rong Bao Signed-off-by: Huacai Chen --- arch/loongarch/kernel/acpi.c | 27 ++++++++++++++++++++++++++- arch/loongarch/kernel/smp.c | 4 ++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/arch/loongarch/kernel/acpi.c b/arch/loongarch/kernel/acpi.c index 8f650c9ffecd..873e90990771 100644 --- a/arch/loongarch/kernel/acpi.c +++ b/arch/loongarch/kernel/acpi.c @@ -201,10 +201,12 @@ static void __init acpi_process_madt(void) } int pptt_enabled; +static int acpi_nr_packages; +static int acpi_package_ids[MAX_PACKAGES]; int __init parse_acpi_topology(void) { - int cpu, topology_id; + int i, cpu, topology_id; for_each_possible_cpu(cpu) { topology_id = find_acpi_cpu_topology(cpu, 0); @@ -222,6 +224,29 @@ int __init parse_acpi_topology(void) cpu_data[cpu].core = topology_id; } + + topology_id = find_acpi_cpu_topology_package(cpu); + if (topology_id < 0) { + pr_warn("Invalid BIOS PPTT\n"); + return -ENOENT; + } + + for (i = 0; i < acpi_nr_packages; i++) + if (acpi_package_ids[i] == topology_id) + break; + + if (i == acpi_nr_packages) + acpi_package_ids[acpi_nr_packages++] = topology_id; + + cpu_data[cpu].package = topology_id; + } + + for_each_possible_cpu(cpu) { + for (i = 0; i < acpi_nr_packages; i++) + if (cpu_data[cpu].package == acpi_package_ids[i]) { + cpu_data[cpu].package = i; /* Canonicalize */ + break; + } } pptt_enabled = 1; diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 5d792256bbb9..d4b5d1b6bb01 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -426,10 +426,10 @@ void loongson_init_secondary(void) numa_add_cpu(cpu); #endif per_cpu(cpu_state, cpu) = CPU_ONLINE; - cpu_data[cpu].package = - cpu_logical_map(cpu) / loongson_sysconf.cores_per_package; cpu_data[cpu].core = pptt_enabled ? cpu_data[cpu].core : cpu_logical_map(cpu) % loongson_sysconf.cores_per_package; + cpu_data[cpu].package = pptt_enabled ? cpu_data[cpu].package : + cpu_logical_map(cpu) / loongson_sysconf.cores_per_package; cpu_data[cpu].global_id = cpu_logical_map(cpu); } From 485ed44db5694d8d2e5027f63ad608e705286f30 Mon Sep 17 00:00:00 2001 From: George Guo Date: Thu, 23 Jul 2026 22:27:30 +0800 Subject: [PATCH 221/456] LoongArch: Fix address space mismatch in kexec command line lookup When searching the loaded segments for the "kexec" command line marker, the kexec_load(2) path (file_mode == 0) passes the user-space segment buffer straight to strncmp() through a bogus (char __user *) cast. This dereferences a user pointer in kernel context, which is wrong and is flagged by sparse: arch/loongarch/kernel/machine_kexec.c:84:51: sparse: incorrect type in argument 2 (different address spaces) @@ expected char const * @@ got char [noderef] __user * Here copy the marker-sized prefix of each segment into a small on-stack buffer with copy_from_user() before comparing, and skip segments that fault. The subsequent copy_from_user() that stages the full command line into the safe area is left unchanged. Cc: stable@vger.kernel.org Fixes: 4a03b2ac06a5 ("LoongArch: Add kexec support") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202605051639.aEPioXdD-lkp@intel.com/ Co-developed-by: Kexin Liu Signed-off-by: Kexin Liu Signed-off-by: George Guo Signed-off-by: Huacai Chen --- arch/loongarch/kernel/machine_kexec.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/kernel/machine_kexec.c b/arch/loongarch/kernel/machine_kexec.c index d7fafda1d541..1883cae93bc3 100644 --- a/arch/loongarch/kernel/machine_kexec.c +++ b/arch/loongarch/kernel/machine_kexec.c @@ -42,6 +42,7 @@ static unsigned long first_ind_entry; int machine_kexec_prepare(struct kimage *kimage) { int i; + char head[8]; char *bootloader = "kexec"; void *cmdline_ptr = (void *)KEXEC_CMDLINE_ADDR; @@ -59,7 +60,9 @@ int machine_kexec_prepare(struct kimage *kimage) } else { /* Find the command line */ for (i = 0; i < kimage->nr_segments; i++) { - if (!strncmp(bootloader, (char __user *)kimage->segment[i].buf, strlen(bootloader))) { + if (copy_from_user(head, kimage->segment[i].buf, strlen(bootloader))) + continue; + if (!strncmp(bootloader, head, strlen(bootloader))) { if (!copy_from_user(cmdline_ptr, kimage->segment[i].buf, COMMAND_LINE_SIZE)) kimage->arch.cmdline_ptr = (unsigned long)cmdline_ptr; break; From 73555fdab5e1e4f24ca000c41a616b34edf4b55d Mon Sep 17 00:00:00 2001 From: Haoran Jiang Date: Thu, 23 Jul 2026 22:27:30 +0800 Subject: [PATCH 222/456] LoongArch: Fix oops during single-step debugging When entering KDB via a breakpoint and then performing single-step debugging, an oops is triggered. Now during single-step debugging, kdb_local() expects the reason to be KDB_REASON_SSTEP, but it is actually KDB_REASON_OOPS. In kdb_stub(), when determining the reason, the ex_vector for single-step should be 0, as already implemented on other architectures such as arm64 and riscv. Before the patch: [112]kdb> ss Entering kdb (current=0x900020009f520000, pid 10661) on processor 112 Oops: (null) due to oops @ 0x90000000005b57a4 Cc: stable@vger.kernel.org Signed-off-by: Haoran Jiang Signed-off-by: Huacai Chen --- arch/loongarch/kernel/kgdb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/kernel/kgdb.c b/arch/loongarch/kernel/kgdb.c index 17664a6043b1..e7b59f8a4b05 100644 --- a/arch/loongarch/kernel/kgdb.c +++ b/arch/loongarch/kernel/kgdb.c @@ -252,7 +252,8 @@ static int kgdb_loongarch_notify(struct notifier_block *self, unsigned long cmd, if (atomic_read(&kgdb_active) != -1) kgdb_nmicallback(smp_processor_id(), regs); - if (kgdb_handle_exception(args->trapnr, args->signr, cmd, regs)) + if (kgdb_handle_exception(regs->csr_era == stepped_address ? 0 : args->trapnr, + args->signr, cmd, regs)) return NOTIFY_DONE; if (atomic_read(&kgdb_setting_breakpoint)) From dacd348b8a993373576fe2ee2d8b114740ba57a6 Mon Sep 17 00:00:00 2001 From: Nicholas Dudar Date: Thu, 23 Jul 2026 22:27:35 +0800 Subject: [PATCH 223/456] LoongArch: BPF: Zero-extend signed ALU32 div/mod results ALU32 operations write a 32-bit result and leave the upper 32 bits of the BPF register zero. The LoongArch JIT sign-extends the result of signed ALU32 BPF_DIV and BPF_MOD (off=1), so a negative 32-bit quotient or remainder leaves bits 63:32 set in JITted code while the verifier and interpreter model those bits as zero. Keep sign-extension on the operands, which signed divide needs, and zero-extend the ALU32 result after the divide or modulo instruction, matching the unsigned ALU32 div/mod paths and every other ALU32 operation in this JIT. Fixes: 2425c9e002d2 ("LoongArch: BPF: Support signed div instructions") Fixes: 7b6b13d32965 ("LoongArch: BPF: Support signed mod instructions") Assisted-by: Claude:claude-opus-4-8 Acked-by: Tiezhu Yang Tested-by: Tiezhu Yang Signed-off-by: Nicholas Dudar Signed-off-by: Huacai Chen --- arch/loongarch/net/bpf_jit.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 2738b4db1165..c91d474faba7 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -835,7 +835,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext move_reg(ctx, t1, src); emit_sext_32(ctx, t1, is32); emit_insn(ctx, divd, dst, dst, t1); - emit_sext_32(ctx, dst, is32); + emit_zext_32(ctx, dst, is32); } break; @@ -852,7 +852,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext emit_sext_32(ctx, t1, is32); emit_sext_32(ctx, dst, is32); emit_insn(ctx, divd, dst, dst, t1); - emit_sext_32(ctx, dst, is32); + emit_zext_32(ctx, dst, is32); } break; @@ -870,7 +870,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext move_reg(ctx, t1, src); emit_sext_32(ctx, t1, is32); emit_insn(ctx, modd, dst, dst, t1); - emit_sext_32(ctx, dst, is32); + emit_zext_32(ctx, dst, is32); } break; @@ -887,7 +887,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext emit_sext_32(ctx, t1, is32); emit_sext_32(ctx, dst, is32); emit_insn(ctx, modd, dst, dst, t1); - emit_sext_32(ctx, dst, is32); + emit_zext_32(ctx, dst, is32); } break; From 35d661c98fe4733490f20b4311616a3c2c30abc0 Mon Sep 17 00:00:00 2001 From: Fabrice Derepas Date: Wed, 22 Jul 2026 19:22:30 +0300 Subject: [PATCH 224/456] KEYS: trusted: dcp: fix key_len validation and calc_blob_len() return type Two correctness and type-hygiene issues exist in the DCP trusted keys implementation. First, trusted_dcp_unseal() reads p->key_len from a user-supplied blob without checking if it exceeds MAX_KEY_SIZE. If a crafted blob provides a payload_len larger than 128, the subsequent do_aead_crypto() call writes past the end of the p->key array into the adjacent p->blob buffer within the same struct trusted_key_payload -- the caller's own input, not unrelated kernel memory. While not exploitable, this violates strict array bounds and triggers static analyzers. Fix this by adding a validation check against MIN_KEY_SIZE and MAX_KEY_SIZE immediately after reading the length, matching the checks already done in trusted_core.c. Second, calc_blob_len() calculates a sum in size_t that truncates to unsigned int on 64-bit platforms. Because the DCP hardware is only present on 32-bit i.MX SoC platforms, size_t and unsigned int are functionally equivalent in production, making this truncation harmless in practice. Nevertheless, updating the return type to size_t (and subsequently updating 'blen' in the seal/unseal paths) resolves type-narrowing warnings and improves overall code hygiene. Fixes: 2e8a0f40a39c ("KEYS: trusted: Introduce NXP DCP-backed trusted keys") Signed-off-by: Fabrice Derepas Reviewed-by: David Gstir Reviewed-by: Richard Weinberger Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260719163939.3624767-1-fabrice.derepas@canonical.com Signed-off-by: Jarkko Sakkinen --- security/keys/trusted-keys/trusted_dcp.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/security/keys/trusted-keys/trusted_dcp.c b/security/keys/trusted-keys/trusted_dcp.c index 7b6eb655df0c..c078adebe190 100644 --- a/security/keys/trusted-keys/trusted_dcp.c +++ b/security/keys/trusted-keys/trusted_dcp.c @@ -69,7 +69,7 @@ static bool skip_zk_test; module_param_named(dcp_skip_zk_test, skip_zk_test, bool, 0); MODULE_PARM_DESC(dcp_skip_zk_test, "Don't test whether device keys are zero'ed"); -static unsigned int calc_blob_len(unsigned int payload_len) +static size_t calc_blob_len(unsigned int payload_len) { return sizeof(struct dcp_blob_fmt) + payload_len + DCP_BLOB_AUTHLEN; } @@ -200,7 +200,8 @@ static int encrypt_blob_key(u8 *plain_key, u8 *encrypted_key) static int trusted_dcp_seal(struct trusted_key_payload *p, char *datablob) { struct dcp_blob_fmt *b = (struct dcp_blob_fmt *)p->blob; - int blen, ret; + size_t blen; + int ret; u8 *plain_blob_key; blen = calc_blob_len(p->key_len); @@ -242,7 +243,8 @@ static int trusted_dcp_seal(struct trusted_key_payload *p, char *datablob) static int trusted_dcp_unseal(struct trusted_key_payload *p, char *datablob) { struct dcp_blob_fmt *b = (struct dcp_blob_fmt *)p->blob; - int blen, ret; + size_t blen; + int ret; u8 *plain_blob_key = NULL; if (b->fmt_version != DCP_BLOB_VERSION) { @@ -253,9 +255,14 @@ static int trusted_dcp_unseal(struct trusted_key_payload *p, char *datablob) } p->key_len = le32_to_cpu(b->payload_len); + if (p->key_len < MIN_KEY_SIZE || p->key_len > MAX_KEY_SIZE) { + ret = -EINVAL; + goto out; + } + blen = calc_blob_len(p->key_len); if (blen != p->blob_len) { - pr_err("DCP blob has bad length: %i != %i\n", blen, + pr_err("DCP blob has bad length: %zu != %u\n", blen, p->blob_len); ret = -EINVAL; goto out; From 63918731f9ae25b5deb022f118e941e6dddfcef4 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sun, 19 Jul 2026 12:15:03 -0400 Subject: [PATCH 225/456] keys: fix out-of-bounds read in keyring_get_key_chunk() For description-level chunks keyring_get_key_chunk() advances the read pointer by level * sizeof(long) past the inline prefix but only bounds-checks the prefix, so a long enough key description is read past its kmemdup(desc, desc_len + 1) allocation. Compute the full byte offset and bounds-check the description against it before reading. The walk only reaches a description-level chunk when two keys collide through the hash, x, type and domain_tag chunks, so this is reached from an unprivileged add_key(2) with a crafted pair of same-type keys whose index hashes collide; KASAN reports a slab-out-of-bounds read. Fixes: f771fde82051 ("keys: Simplify key description management") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260719161505.2423935-2-michael.bommarito@gmail.com Signed-off-by: Jarkko Sakkinen --- security/keys/keyring.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 7a2ee0ded7c9..085f7a743354 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -271,6 +271,7 @@ static unsigned long keyring_get_key_chunk(const void *data, int level) unsigned long chunk = 0; const u8 *d; int desc_len = index_key->desc_len, n = sizeof(chunk); + unsigned int offset; level /= ASSOC_ARRAY_KEY_CHUNK_SIZE; switch (level) { @@ -284,12 +285,12 @@ static unsigned long keyring_get_key_chunk(const void *data, int level) return (unsigned long)index_key->domain_tag; default: level -= 4; - if (desc_len <= sizeof(index_key->desc)) + offset = sizeof(index_key->desc) + level * sizeof(long); + if (desc_len <= offset) return 0; - d = index_key->description + sizeof(index_key->desc); - d += level * sizeof(long); - desc_len -= sizeof(index_key->desc); + d = index_key->description + offset; + desc_len -= offset; if (desc_len > n) desc_len = n; do { From 58565eef0f8d861aae92abfb7658458d661cee17 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sun, 19 Jul 2026 12:15:04 -0400 Subject: [PATCH 226/456] keys: make keyring key-chunk byte order agree with keyring_diff_objects() keyring_get_key_chunk() loads description bytes into the index chunk low address first, while keyring_diff_objects() numbers the first differing bit from the low end and folds the absolute byte index into the level without removing the inline-prefix offset the level already carries. The two disagree on byte order and bit position, so the array can be told two keys first differ at a bit that does not differ in the chunk the walker uses, letting crafted descriptions collide into one node. Load the chunk in the order keyring_diff_objects() assumes and drop the inline-prefix length when folding the byte index into the level. This only changes the in-memory ordering used to place keys within a keyring; add, search and read of non-colliding keys are unaffected. Fixes: f771fde82051 ("keys: Simplify key description management") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260719161505.2423935-3-michael.bommarito@gmail.com Signed-off-by: Jarkko Sakkinen --- security/keys/keyring.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 085f7a743354..15bf4af8f282 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -293,9 +293,10 @@ static unsigned long keyring_get_key_chunk(const void *data, int level) desc_len -= offset; if (desc_len > n) desc_len = n; + d += desc_len; do { chunk <<= 8; - chunk |= *d++; + chunk |= *--d; } while (--desc_len > 0); return chunk; } @@ -376,7 +377,7 @@ static int keyring_diff_objects(const void *object, const void *data) return -1; differ_plus_i: - level += i; + level += i - (int)sizeof(a->desc); differ: i = level * 8 + __ffs(seg_a ^ seg_b); return i; From a82c8a05e86f3f84e09698f65b4515b5d04633f6 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sun, 19 Jul 2026 12:15:05 -0400 Subject: [PATCH 227/456] assoc_array: trim the final shortcut word using the current chunk end assoc_array_walk() masks off the bits past shortcut->skip_to_level in the word that contains skip_to_level, gated on round_up(sc_level, ASSOC_ARRAY_KEY_CHUNK_SIZE) > skip_to_level. That guard is wrong in two opposite ways: - When sc_level is word-aligned (every word after the first) round_up() is a no-op, so the guard is sc_level > skip_to_level and never fires for the word that holds skip_to_level. A shortcut that spans more than one word and ends in the middle of its last word leaves that word untrimmed, and its stale high bits leak into the dissimilarity word and can steer the walk down the wrong descendant. - When sc_level is unaligned (the first word) and skip_to_level sits on the next chunk boundary, sc_level + CHUNK would exceed skip_to_level and fire the trim with shift = skip_to_level & CHUNK_MASK == 0, which clears the whole dissimilarity word and makes a differing shortcut compare equal. Use the end of the chunk that contains sc_level instead: skip_to_level < round_down(sc_level, CHUNK) + CHUNK For an aligned sc_level whose word holds skip_to_level this now fires (the first bug); for an unaligned sc_level with skip_to_level on the following boundary it does not, so shift is never 0 when the branch runs and the trim never clears the whole word. Fixes: 3cb989501c26 ("Add a generic associative array implementation.") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260719161505.2423935-4-michael.bommarito@gmail.com Signed-off-by: Jarkko Sakkinen --- lib/assoc_array.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/assoc_array.c b/lib/assoc_array.c index bcc6e0a013eb..b6c9723e12ce 100644 --- a/lib/assoc_array.c +++ b/lib/assoc_array.c @@ -255,7 +255,8 @@ assoc_array_walk(const struct assoc_array *array, sc_segments = shortcut->index_key[sc_level >> ASSOC_ARRAY_KEY_CHUNK_SHIFT]; dissimilarity = segments ^ sc_segments; - if (round_up(sc_level, ASSOC_ARRAY_KEY_CHUNK_SIZE) > shortcut->skip_to_level) { + if (shortcut->skip_to_level < round_down(sc_level, + ASSOC_ARRAY_KEY_CHUNK_SIZE) + ASSOC_ARRAY_KEY_CHUNK_SIZE) { /* Trim segments that are beyond the shortcut */ int shift = shortcut->skip_to_level & ASSOC_ARRAY_KEY_CHUNK_MASK; dissimilarity &= ~(ULONG_MAX << shift); From f30415929be8aeb002d557c8d3f7ab2d2188003a Mon Sep 17 00:00:00 2001 From: David Lee Date: Mon, 13 Jul 2026 09:59:15 +0000 Subject: [PATCH 228/456] netfilter: ipset: do not update comments from kernel-side hash adds mtype_resize() copies comment pointers with memcpy(), not the comment objects themselves. During the window after an entry has been copied but before the table swap and backlog replay, the old table is still published for packet-side updates while the replacement-table entry already holds the same ip_set_comment_rcu pointer. If xt_SET --add-set ... --exist hits that old entry in this window, mtype_add() calls ip_set_init_comment() even though packet-side adds carry no comment payload. That call frees the shared comment through the old entry, so the replacement-table entry now holds a stale pointer. When the queued add is replayed on the new table, mtype_add() calls ip_set_init_comment() again and strlen() dereferences the stale pointer. Fix this in mtype_add() by skipping ip_set_init_comment() when ext->target marks a packet-side add. Userspace adds still update comments, while packet-side adds can no longer free comment storage shared with a resize copy. Fixes: f66ee0410b1c ("netfilter: ipset: Fix "INFO: rcu detected stall in hash_xxx" reports") Cc: stable@vger.kernel.org Signed-off-by: David Lee Assisted-by: Codex:gpt-5.5 Acked-by: Jozsef Kadlecsik Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipset/ip_set_hash_gen.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h index 8231317b0f1f..b2d77973272d 100644 --- a/net/netfilter/ipset/ip_set_hash_gen.h +++ b/net/netfilter/ipset/ip_set_hash_gen.h @@ -1005,7 +1005,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, #endif if (SET_WITH_COUNTER(set)) ip_set_init_counter(ext_counter(data, set), ext); - if (SET_WITH_COMMENT(set)) + if (SET_WITH_COMMENT(set) && !ext->target) ip_set_init_comment(set, ext_comment(data, set), ext); if (SET_WITH_SKBINFO(set)) ip_set_init_skbinfo(ext_skbinfo(data, set), ext); From a63d2dbaeb50a85d4c976b15a36e6b0c7113db5b Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 13 Jul 2026 19:52:32 +0800 Subject: [PATCH 229/456] ipvs: do not propagate one-packet flag to synced conns Synced connections can be created before their destination exists. When the destination is later added, ip_vs_bind_dest() copies connection flags from the destination into cp->flags. IP_VS_CONN_F_ONE_PACKET connections are not synced. If a synced connection inherits IP_VS_CONN_F_ONE_PACKET while it is already hashed, expiry can treat it as a one-packet connection and skip unlinking the existing conn_tab node, leaving stale hash nodes pointing at a freed struct ip_vs_conn. Drop IP_VS_CONN_F_ONE_PACKET from destination flags when binding synced connections. Fixes: 26ec037f9841 ("IPVS: one-packet scheduling") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Suggested-by: Julian Anastasov Signed-off-by: Zhiling Zou Signed-off-by: Ren Wei Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_conn.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 6ed2622363f0..0682cec5f0a7 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1014,6 +1014,9 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest) flags = cp->flags; /* Bind with the destination and its corresponding transmitter */ if (flags & IP_VS_CONN_F_SYNC) { + /* Synced conns are hashed, so they can not get this flag */ + conn_flags &= ~IP_VS_CONN_F_ONE_PACKET; + /* if the connection is not template and is created * by sync, preserve the activity flag. */ From 712d2993bea555f1f09cd53cbdb25714f28e85db Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Mon, 13 Jul 2026 19:52:33 +0800 Subject: [PATCH 230/456] ipvs: adjust double hashing when fwd method changes Synced conns can be created with one forwarding method and later updated with different one after the dest server is configured. This needs adjusting the hashing for node hn1 because only MASQ supports double hashing. Modify conn_tab_lock() to support seeking for hash node hn0 together with adding for hn1. By this way we can safely modify the forwarding method and hn1.hash_key under bucket lock for the first node hn0. The forwarding method is also protected by cp->lock as it is part of cp->flags. Fix the usage of stale idx/idx2 values in conn_tab_lock after jumping to the retry label. Instead, use idx/idx2 values just to order the locking for the old/new tables. Reported-by: Zhiling Zou Link: https://patch.msgid.link/1b914f41d725bc064c9ba9830dc8169329737270.1782540466.git.roxy520tt@gmail.com/ Link: https://sashiko.dev/#/patchset/CALMqdkR704S2BG_QD_bgHTFp2%2B1QCi7n0T4zoZyTo8mDZevYSA%40mail.gmail.com Fixes: f20c73b0460d ("ipvs: use more keys for connection hashing") Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_conn.c | 189 +++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 42 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 0682cec5f0a7..36c5cba03f5b 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -70,25 +70,45 @@ static struct kmem_cache *ip_vs_conn_cachep __read_mostly; * bucket or hash table * - hash table resize works like rehash but always rehashes into new table * - bit lock on bucket serializes all operations that modify the chain + * - on resize, bucket from the old table is locked before bucket from the + * new table * - cp->lock protects conn fields like cp->flags, cp->dest */ -/* Lock conn_tab bucket for conn hash/unhash, not for rehash */ +/** + * conn_tab_lock - Lock conn_tab buckets for conn hash/unhash, not for rehash + * @t: hash table for hn0, new_tbl when new_hash=true + * @t2: hash table for hn1, new_tbl when new_hash2=true + * @cp: connection + * @hash_key: hash key for hn0 + * @hash_key2: hash key for hn1 + * @use2: using hn1 (double hashing) based on the forwarding method + * @new_hash: mode for hn0, hash node (true) or seek node (false) + * @new_hash2: mode for hn1, hash node (true) or seek node (false) + * @head_ret: returned head for hn0 + * @head2_ret: returned head for hn1 + * + * We support 3 modes: + * - seek mode for both nodes, used for unhashing + * - hash mode for both nodes, used for hashing + * - seek hn0 and hash hn1, used when forwarding method is changed + */ static __always_inline void -conn_tab_lock(struct ip_vs_rht *t, struct ip_vs_conn *cp, u32 hash_key, - u32 hash_key2, bool use2, bool new_hash, - struct hlist_bl_head **head_ret, struct hlist_bl_head **head2_ret) +conn_tab_lock(struct ip_vs_rht *t, struct ip_vs_rht *t2, struct ip_vs_conn *cp, + u32 hash_key, u32 hash_key2, bool use2, bool new_hash, + bool new_hash2, struct hlist_bl_head **head_ret, + struct hlist_bl_head **head2_ret) { struct hlist_bl_head *head, *head2; u32 hash_key_new, hash_key_new2; - struct ip_vs_rht *t2 = t; - u32 idx, idx2; + int idx = 0, idx2 = 0; + + /* Advance idx2 when new_hash is not set but hash_key2 + * is for new table + */ + if (new_hash2 && use2 && t != t2) + idx2++; - idx = hash_key & t->mask; - if (use2) - idx2 = hash_key2 & t->mask; - else - idx2 = idx; if (!new_hash) { /* We need to lock the bucket in the right table */ @@ -100,46 +120,45 @@ conn_tab_lock(struct ip_vs_rht *t, struct ip_vs_conn *cp, u32 hash_key, * both nodes in different tables, use idx/idx2 * for proper lock ordering for heads. */ - idx = hash_key & t->mask; - idx |= IP_VS_RHT_TABLE_ID_MASK; - } - if (use2) { - if (!ip_vs_rht_same_table(t2, hash_key2)) { - /* It is already moved to new table */ - t2 = rcu_dereference(t2->new_tbl); - idx2 = hash_key2 & t2->mask; - idx2 |= IP_VS_RHT_TABLE_ID_MASK; - } - } else { - idx2 = idx; + idx++; } } + if (use2 && !new_hash2 && !ip_vs_rht_same_table(t2, hash_key2)) { + /* It is already moved to new table */ + t2 = rcu_dereference(t2->new_tbl); + idx2++; + } + if (!use2) + idx2 = idx; head = t->buckets + (hash_key & t->mask); head2 = use2 ? t2->buckets + (hash_key2 & t2->mask) : head; - local_bh_disable(); - /* Do not touch seqcount, this is a safe operation */ - - if (idx <= idx2) { + if (idx > idx2 || (head > head2 && idx == idx2)) { + hlist_bl_lock(head2); + hlist_bl_lock(head); + } else { hlist_bl_lock(head); if (head != head2) hlist_bl_lock(head2); - } else { - hlist_bl_lock(head2); - hlist_bl_lock(head); } if (!new_hash) { + bool changed; + /* Ensure hash_key is read under lock */ hash_key_new = READ_ONCE(cp->hn0.hash_key); - hash_key_new2 = READ_ONCE(cp->hn1.hash_key); + changed = hash_key != hash_key_new; + if (use2 && !new_hash2) { + hash_key_new2 = READ_ONCE(cp->hn1.hash_key); + changed |= hash_key2 != hash_key_new2; + } else { + hash_key_new2 = hash_key2; + } /* Hash changed ? */ - if (hash_key != hash_key_new || - (hash_key2 != hash_key_new2 && use2)) { + if (changed) { if (head != head2) hlist_bl_unlock(head2); hlist_bl_unlock(head); - local_bh_enable(); hash_key = hash_key_new; hash_key2 = hash_key_new2; goto retry; @@ -155,7 +174,6 @@ static inline void conn_tab_unlock(struct hlist_bl_head *head, if (head != head2) hlist_bl_unlock(head2); hlist_bl_unlock(head); - local_bh_enable(); } static void ip_vs_conn_expire(struct timer_list *t); @@ -268,8 +286,9 @@ static inline int ip_vs_conn_hash(struct ip_vs_conn *cp) use2 = false; } - conn_tab_lock(t, cp, hash_key, hash_key2, use2, true /* new_hash */, - &head, &head2); + local_bh_disable(); + conn_tab_lock(t, t, cp, hash_key, hash_key2, use2, true /* new_hash */, + true /* new_hash2 */, &head, &head2); cp->flags |= IP_VS_CONN_F_HASHED; WRITE_ONCE(cp->hn0.hash_key, hash_key); @@ -280,6 +299,7 @@ static inline int ip_vs_conn_hash(struct ip_vs_conn *cp) hlist_bl_add_head_rcu(&cp->hn1.node, head2); conn_tab_unlock(head, head2); + local_bh_enable(); ret = 1; /* Schedule resizing if load increases */ @@ -306,18 +326,20 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn *cp) return refcount_dec_if_one(&cp->refcnt); rcu_read_lock(); + local_bh_disable(); t = rcu_dereference(ipvs->conn_tab); hash_key = READ_ONCE(cp->hn0.hash_key); hash_key2 = READ_ONCE(cp->hn1.hash_key); use2 = ip_vs_conn_use_hash2(cp); - conn_tab_lock(t, cp, hash_key, hash_key2, use2, false /* new_hash */, - &head, &head2); + conn_tab_lock(t, t, cp, hash_key, hash_key2, use2, false /* new_hash */, + false /* new_hash2 */, &head, &head2); if (cp->flags & IP_VS_CONN_F_HASHED) { /* Decrease refcnt and unlink conn only if we are last user */ - if (refcount_dec_if_one(&cp->refcnt)) { + if (use2 == ip_vs_conn_use_hash2(cp) && + refcount_dec_if_one(&cp->refcnt)) { hlist_bl_del_rcu(&cp->hn0.node); if (use2) hlist_bl_del_rcu(&cp->hn1.node); @@ -328,6 +350,7 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn *cp) conn_tab_unlock(head, head2); + local_bh_enable(); rcu_read_unlock(); return ret; @@ -632,6 +655,7 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport) int ntbl; int dir; +restart: /* No packets from inside, so we can do it in 2 steps. */ dir = use2 ? 1 : 0; @@ -686,6 +710,23 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport) /* Protect the cp->flags modification */ spin_lock_bh(&cp->lock); + /* Recheck the forwarding method under lock */ + if (use2 != ip_vs_conn_use_hash2(cp)) { + use2 = !use2; + if (use2) { + spin_unlock_bh(&cp->lock); + /* Restart with new use2 value */ + goto restart; + } + if (dir) { + /* Not started yet, so just skip dir 1 */ + spin_unlock_bh(&cp->lock); + dir--; + goto next_dir; + } + /* Just finish dir 0 */ + } + /* Lock seqcount only for the old bucket, even if we are on new table * because it affects the del operation, not the adding. */ @@ -752,6 +793,61 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport) goto next_dir; } +/* Change forwarding method for hashed conn */ +static void ip_vs_conn_change_fwd_mask(struct ip_vs_conn *cp, u32 new_flags) +{ + struct netns_ipvs *ipvs = cp->ipvs; + struct hlist_bl_head *head, *head2; + u32 hash2, hash_key, hash_key2; + struct ip_vs_rht *t, *t2; + + /* See ip_vs_conn_use_hash2() for reference */ + if ((cp->flags & IP_VS_CONN_F_TEMPLATE) || + /* No change in double hashing ? */ + (IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ) == + ((new_flags & IP_VS_CONN_F_FWD_MASK) == IP_VS_CONN_F_MASQ)) { + cp->flags = new_flags; + return; + } + t = rcu_dereference(ipvs->conn_tab); + if (ip_vs_conn_use_hash2(cp)) { + /* Stop double hashing */ + hash_key = READ_ONCE(cp->hn0.hash_key); + hash_key2 = READ_ONCE(cp->hn1.hash_key); + + conn_tab_lock(t, t, cp, hash_key, hash_key2, true /* use2 */, + false /* new_hash */, false /* new_hash2 */, + &head, &head2); + + /* Keep both hash keys in same table */ + hash_key = READ_ONCE(cp->hn0.hash_key); + WRITE_ONCE(cp->hn1.hash_key, hash_key); + hlist_bl_del_rcu(&cp->hn1.node); + cp->flags = new_flags; + + conn_tab_unlock(head, head2); + } else { + /* Start double hashing */ + + hash_key = READ_ONCE(cp->hn0.hash_key); + + t2 = rcu_dereference(t->new_tbl); + hash2 = ip_vs_conn_hashkey_conn(t2, cp, true); + hash_key2 = ip_vs_rht_build_hash_key(t2, hash2); + + /* Change the forwarding method under locked hn0 */ + conn_tab_lock(t, t2, cp, hash_key, hash_key2, true /* use2 */, + false /* new_hash */, true /* new_hash2 */, + &head, &head2); + + WRITE_ONCE(cp->hn1.hash_key, hash_key2); + cp->flags = new_flags; + hlist_bl_add_head_rcu(&cp->hn1.node, head2); + + conn_tab_unlock(head, head2); + } +} + /* Get default load factor to map conn_count/u_thresh to t->size */ static int ip_vs_conn_default_load_factor(struct netns_ipvs *ipvs) { @@ -1024,9 +1120,18 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest) conn_flags &= ~IP_VS_CONN_F_INACTIVE; /* connections inherit forwarding method from dest */ flags &= ~(IP_VS_CONN_F_FWD_MASK | IP_VS_CONN_F_NOOUTPUT); + flags |= conn_flags; + /* Changing forwarding method for hashed conn can + * happen only under locks + */ + if (cp->flags & IP_VS_CONN_F_HASHED) + ip_vs_conn_change_fwd_mask(cp, flags); + else + cp->flags = flags; + } else { + flags |= conn_flags; + cp->flags = flags; } - flags |= conn_flags; - cp->flags = flags; cp->dest = dest; IP_VS_DBG_BUF(7, "Bind-dest %s c:%s:%d v:%s:%d " From f4f699790590bd0896c48a71e9232a65198f92f0 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 16 Jul 2026 10:13:37 +0200 Subject: [PATCH 231/456] netfilter: nf_tables: make nft_object rhltable per table The nft_object rhltable is global, this allows for accessing objects that are being dismangled from lookup path by other existing netns. Given the nft_obj_destroy() releases the object inmediately, this might lead to use-after-free of these objects that are being released. Make the existing rhltable per table to address this issue to deal with with the nft_rcv_nl_event() path too. Update nft_obj_lookup() to take the table as non-const, otherwise, compiler complains when passing the objname_ht to rhltable_lookup(). Fixes: 4d44175aa5bb ("netfilter: nf_tables: handle nft_object lookups via rhltable") Suggested-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_tables.h | 4 +++- net/netfilter/nf_tables_api.c | 34 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 9d844354c4d9..3be612145c13 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -1294,6 +1294,7 @@ static inline void nft_use_inc_restore(u32 *use) * @sets: sets in the table * @objects: stateful objects in the table * @flowtables: flow tables in the table + * @objname_ht: hashtable for objects lookup by name * @hgenerator: handle generator state * @handle: table handle * @use: number of chain references to this table @@ -1313,6 +1314,7 @@ struct nft_table { struct list_head sets; struct list_head objects; struct list_head flowtables; + struct rhltable objname_ht; u64 hgenerator; u64 handle; u32 use; @@ -1400,7 +1402,7 @@ static inline void *nft_obj_data(const struct nft_object *obj) #define nft_expr_obj(expr) *((struct nft_object **)nft_expr_priv(expr)) struct nft_object *nft_obj_lookup(const struct net *net, - const struct nft_table *table, + struct nft_table *table, const struct nlattr *nla, u32 objtype, u8 genmask); diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index a9eaf9455c77..af357f6c5070 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -45,8 +45,6 @@ enum { NFT_VALIDATE_DO, }; -static struct rhltable nft_objname_ht; - static u32 nft_chain_hash(const void *data, u32 len, u32 seed); static u32 nft_chain_hash_obj(const void *data, u32 len, u32 seed); static int nft_chain_hash_cmp(struct rhashtable_compare_arg *, const void *); @@ -1635,6 +1633,10 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info, if (err) goto err_chain_ht; + err = rhltable_init(&table->objname_ht, &nft_objname_ht_params); + if (err < 0) + goto err_obj_ht; + INIT_LIST_HEAD(&table->chains); INIT_LIST_HEAD(&table->sets); INIT_LIST_HEAD(&table->objects); @@ -1653,6 +1655,8 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info, list_add_tail_rcu(&table->list, &nft_net->tables); return 0; err_trans: + rhltable_destroy(&table->objname_ht); +err_obj_ht: rhltable_destroy(&table->chains_ht); err_chain_ht: kfree(table->udata); @@ -1819,6 +1823,7 @@ static void nf_tables_table_destroy(struct nft_table *table) return; rhltable_destroy(&table->chains_ht); + rhltable_destroy(&table->objname_ht); kfree(table->name); kfree(table->udata); kfree(table); @@ -8086,7 +8091,7 @@ void nft_unregister_obj(struct nft_object_type *obj_type) EXPORT_SYMBOL_GPL(nft_unregister_obj); struct nft_object *nft_obj_lookup(const struct net *net, - const struct nft_table *table, + struct nft_table *table, const struct nlattr *nla, u32 objtype, u8 genmask) { @@ -8102,7 +8107,7 @@ struct nft_object *nft_obj_lookup(const struct net *net, !lockdep_commit_lock_is_held(net)); rcu_read_lock(); - list = rhltable_lookup(&nft_objname_ht, &k, nft_objname_ht_params); + list = rhltable_lookup(&table->objname_ht, &k, nft_objname_ht_params); if (!list) goto out; @@ -8382,7 +8387,7 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, if (err < 0) goto err_trans; - err = rhltable_insert(&nft_objname_ht, &obj->rhlhead, + err = rhltable_insert(&table->objname_ht, &obj->rhlhead, nft_objname_ht_params); if (err < 0) goto err_obj_ht; @@ -8567,8 +8572,8 @@ nf_tables_getobj_single(u32 portid, const struct nfnl_info *info, struct netlink_ext_ack *extack = info->extack; u8 genmask = nft_genmask_cur(info->net); u8 family = info->nfmsg->nfgen_family; - const struct nft_table *table; struct net *net = info->net; + struct nft_table *table; struct nft_object *obj; struct sk_buff *skb2; u32 objtype; @@ -10437,9 +10442,9 @@ static void nf_tables_commit_chain(struct net *net, struct nft_chain *chain) nf_tables_commit_chain_free_rules_old(g0); } -static void nft_obj_del(struct nft_object *obj) +static void nft_obj_del(struct nft_table *table, struct nft_object *obj) { - rhltable_remove(&nft_objname_ht, &obj->rhlhead, nft_objname_ht_params); + rhltable_remove(&table->objname_ht, &obj->rhlhead, nft_objname_ht_params); list_del_rcu(&obj->list); } @@ -11124,7 +11129,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) break; case NFT_MSG_DELOBJ: case NFT_MSG_DESTROYOBJ: - nft_obj_del(nft_trans_obj(trans)); + nft_obj_del(table, nft_trans_obj(trans)); nf_tables_obj_notify(&ctx, nft_trans_obj(trans), trans->msg_type); break; @@ -11416,7 +11421,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) nft_trans_destroy(trans); } else { nft_use_dec_restore(&table->use); - nft_obj_del(nft_trans_obj(trans)); + nft_obj_del(table, nft_trans_obj(trans)); } break; case NFT_MSG_DELOBJ: @@ -12043,7 +12048,7 @@ static void __nft_release_table(struct net *net, struct nft_table *table) nft_set_destroy(&ctx, set); } list_for_each_entry_safe(obj, ne, &table->objects, list) { - nft_obj_del(obj); + nft_obj_del(table, obj); nft_use_dec(&table->use); nft_obj_destroy(&ctx, obj); } @@ -12225,10 +12230,6 @@ static int __init nf_tables_module_init(void) if (err < 0) goto err_netdev_notifier; - err = rhltable_init(&nft_objname_ht, &nft_objname_ht_params); - if (err < 0) - goto err_rht_objname; - err = nft_offload_init(); if (err < 0) goto err_offload; @@ -12251,8 +12252,6 @@ static int __init nf_tables_module_init(void) err_netlink_notifier: nft_offload_exit(); err_offload: - rhltable_destroy(&nft_objname_ht); -err_rht_objname: unregister_netdevice_notifier(&nf_tables_flowtable_notifier); err_netdev_notifier: nf_tables_core_module_exit(); @@ -12274,7 +12273,6 @@ static void __exit nf_tables_module_exit(void) unregister_pernet_subsys(&nf_tables_net_ops); cancel_work_sync(&trans_gc_work); rcu_barrier(); - rhltable_destroy(&nft_objname_ht); nf_tables_core_module_exit(); } From 305b63e1402267459fdabb183af4527f6799eebf Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 21 Jul 2026 22:02:46 +0200 Subject: [PATCH 232/456] netfilter: xt_hashlimit: validate hashtable supports XT_HASHLIMIT_RATE_MATCH The XT_HASHLIMIT_RATE_MATCH flag mode changes the semantics of the dsthash_ent structure which represents an entry in the hashtable. There is a union area which uses a different layout to express the rate match mode. Update .checkentry path to validate the XT_HASHLIMIT_RATE_MATCH mode flag is requested by two or more different rules that refer to the same hashtable. Otherwise, uninitialized access to the burst field in the union is possible. Reject the use of the XT_HASHLIMIT_RATE_MATCH mode flag if set on by revision less than 3 too. Fixes: bea74641e378 ("netfilter: xt_hashlimit: add rate match mode") Reported-and-tested-by: Talha Berk Arslan Link: https://patch.msgid.link/20260721074629.668-1-talha.anything.info@gmail.com/ Signed-off-by: Pablo Neira Ayuso --- net/netfilter/xt_hashlimit.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/net/netfilter/xt_hashlimit.c b/net/netfilter/xt_hashlimit.c index 2704b4b60d1e..9af0fa895f73 100644 --- a/net/netfilter/xt_hashlimit.c +++ b/net/netfilter/xt_hashlimit.c @@ -117,6 +117,7 @@ struct xt_hashlimit_htable { refcount_t use; u_int8_t family; bool rnd_initialized; + bool ratematch; struct hashlimit_cfg3 cfg; /* config */ @@ -323,6 +324,7 @@ static int htable_create(struct net *net, struct hashlimit_cfg3 *cfg, kvfree(hinfo); return -ENOMEM; } + hinfo->ratematch = !!(cfg->mode & XT_HASHLIMIT_RATE_MATCH); spin_lock_init(&hinfo->lock); switch (revision) { @@ -872,7 +874,10 @@ static int hashlimit_mt_check_common(const struct xt_mtchk_param *par, } /* Check for overflow. */ - if (revision >= 3 && cfg->mode & XT_HASHLIMIT_RATE_MATCH) { + if (cfg->mode & XT_HASHLIMIT_RATE_MATCH) { + if (revision < 3) + return -EINVAL; + if (cfg->avg == 0 || cfg->avg > U32_MAX) { pr_info_ratelimited("invalid rate\n"); return -ERANGE; @@ -905,6 +910,15 @@ static int hashlimit_mt_check_common(const struct xt_mtchk_param *par, mutex_unlock(&hashlimit_mutex); return ret; } + } else { + if ((cfg->mode & XT_HASHLIMIT_RATE_MATCH && + !(*hinfo)->ratematch) || + (!(cfg->mode & XT_HASHLIMIT_RATE_MATCH) && + (*hinfo)->ratematch)) { + mutex_unlock(&hashlimit_mutex); + htable_put(*hinfo); + return -EINVAL; + } } mutex_unlock(&hashlimit_mutex); From e876b75b9020a97bbdc79721e7fc749024891c65 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Wed, 22 Jul 2026 13:15:15 +0300 Subject: [PATCH 233/456] ipvs: fix the checksum validations ip_vs_in_icmp_v6() is missing checksum validation for ICMPv6 packets from clients. In fact, as for TCP/UDP we should validate the checksum for ICMP packets only when we mangle the packets on MASQ or on reply for tunnel. Also, Sashiko points out that handle_response_icmp() being common for IPv4 and IPv6 is missing the pseudo-header calculation while validating ICMPv6 messages from real servers which is a problem if checksum is not validated by the hardware. Fix the problems by creating ip_vs_checksum_common_check() helper and use it for TCP/UDP/ICMP both for IPv4 and IPv6. Rely on the nf_checksum() for validating the ICMP messages but use it also for TCP and UDP. Use correct IP offset for IP_VS_DBG_RL_PKT for TCP/UDP/SCTP. IPVS packets (TCP/UDP/SCTP/ICMP) do not need checksum validation on LOCAL_OUT (local clients or local real servers) and on FORWARD (traffic from servers on LAN). Do it only on LOCAL_IN, in case nf_checksum() is not called on PRE_ROUTING. Also, ip_vs_checksum_complete() can be marked static. Fixes: 2a3b791e6e11 ("IPVS: Add/adjust Netfilter hook functions and helpers for v6") Link: https://sashiko.dev/#/patchset/20260708180315.77413-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 31 +++++++++++++++-- net/netfilter/ipvs/ip_vs_core.c | 20 +++++++++-- net/netfilter/ipvs/ip_vs_proto_sctp.c | 15 ++++---- net/netfilter/ipvs/ip_vs_proto_tcp.c | 44 +++++------------------ net/netfilter/ipvs/ip_vs_proto_udp.c | 50 ++++++--------------------- 5 files changed, 74 insertions(+), 86 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 417ff51f62fc..d8f9ddb0fb38 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -25,7 +25,9 @@ #include /* for union nf_inet_addr */ #include #include /* for struct ipv6hdr */ +#include #include +#include #if IS_ENABLED(CONFIG_NF_CONNTRACK) #include #endif @@ -2066,8 +2068,6 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int dir); #endif -__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset); - static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum) { __be32 diff[2] = { ~old, new }; @@ -2093,6 +2093,33 @@ static inline __wsum ip_vs_check_diff2(__be16 old, __be16 new, __wsum oldsum) return csum_partial(diff, sizeof(diff), oldsum); } +static inline bool ip_vs_checksum_needed(struct sk_buff *skb, int af) +{ + /* Checksum unnecessary or already validated? */ + if (skb_csum_unnecessary(skb)) + return false; + /* LOCAL_OUT ? */ + if (!skb->dev || skb->dev->flags & IFF_LOOPBACK) + return false; + /* !LOCAL_IN (FORWARD) ? */ + if (af == AF_INET6) { + if (!(dst_rt6_info(skb_dst(skb))->rt6i_flags & RTF_LOCAL)) + return false; + } else { + if (!(skb_rtable(skb)->rt_flags & RTCF_LOCAL)) + return false; + } + return true; +} + +static inline bool ip_vs_checksum_common_check(struct sk_buff *skb, + int offset, int proto, int af) +{ + if (!ip_vs_checksum_needed(skb, af)) + return true; + return !nf_checksum(skb, NF_INET_LOCAL_IN, offset, proto, af); +} + /* Forget current conntrack (unconfirmed) and attach notrack entry */ static inline void ip_vs_notrack(struct sk_buff *skb) { diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index bafab93451d0..c8b512725e6e 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -867,7 +867,7 @@ static int sysctl_nat_icmp_send(struct netns_ipvs *ipvs) { return 0; } #endif -__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset) +static __sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset) { return csum_fold(skb_checksum(skb, offset, skb->len - offset, 0)); } @@ -1038,13 +1038,14 @@ static int handle_response_icmp(int af, struct sk_buff *skb, unsigned int offset, unsigned int ihl, unsigned int hooknum) { + int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP; unsigned int verdict = NF_DROP; if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) goto after_nat; /* Ensure the checksum is correct */ - if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) { + if (!ip_vs_checksum_common_check(skb, ihl, iproto, af)) { /* Failed checksum! */ IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n", IP_VS_DBG_ADDR(af, snet)); @@ -1898,7 +1899,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, verdict = NF_DROP; /* Ensure the checksum is correct */ - if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) { + if ((IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ || tunnel) && + !ip_vs_checksum_common_check(skb, ihl, IPPROTO_ICMP, AF_INET)) { /* Failed checksum! */ IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n", &iph->saddr); @@ -2064,6 +2066,18 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, goto out; } + verdict = NF_DROP; + + /* Ensure the checksum is correct */ + if (IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ && + !ip_vs_checksum_common_check(skb, iph->len, IPPROTO_ICMPV6, + AF_INET6)) { + /* Failed checksum! */ + IP_VS_DBG(1, "Incoming ICMPv6: failed checksum from %pI6c!\n", + &iph->saddr); + goto out; + } + /* do the statistics and put it back */ ip_vs_in_stats(cp, skb); diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c index c67317be17df..f6f732b7dfa8 100644 --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c @@ -11,7 +11,7 @@ static int sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int sctphoff); + struct ip_vs_iphdr *iph); static int sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, @@ -109,7 +109,7 @@ sctp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!sctp_csum_check(cp->af, skb, pp, sctphoff)) + if (!sctp_csum_check(cp->af, skb, pp, iph)) return 0; /* Call application helper if needed */ @@ -157,7 +157,7 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!sctp_csum_check(cp->af, skb, pp, sctphoff)) + if (!sctp_csum_check(cp->af, skb, pp, iph)) return 0; /* Call application helper if needed */ @@ -187,19 +187,22 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, static int sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int sctphoff) + struct ip_vs_iphdr *iph) { + unsigned int sctphoff = iph->len; struct sctphdr *sh; __le32 cmp, val; + if (!ip_vs_checksum_needed(skb, af)) + return 1; sh = (struct sctphdr *)(skb->data + sctphoff); cmp = sh->checksum; val = sctp_compute_cksum(skb, sctphoff); if (val != cmp) { /* CRC failure, dump it. */ - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); + IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off, + "Failed checksum for"); return 0; } return 1; diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c index f86b763efcc4..533fce3e5e4e 100644 --- a/net/netfilter/ipvs/ip_vs_proto_tcp.c +++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c @@ -29,7 +29,7 @@ static int tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int tcphoff); + struct ip_vs_iphdr *iph); static int tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, @@ -166,7 +166,7 @@ tcp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!tcp_csum_check(cp->af, skb, pp, tcphoff)) + if (!tcp_csum_check(cp->af, skb, pp, iph)) return 0; /* Call application helper if needed */ @@ -244,7 +244,7 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!tcp_csum_check(cp->af, skb, pp, tcphoff)) + if (!tcp_csum_check(cp->af, skb, pp, iph)) return 0; /* @@ -302,41 +302,13 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, static int tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int tcphoff) + struct ip_vs_iphdr *iph) { - switch (skb->ip_summed) { - case CHECKSUM_NONE: - skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0); - fallthrough; - case CHECKSUM_COMPLETE: -#ifdef CONFIG_IP_VS_IPV6 - if (af == AF_INET6) { - if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr, - &ipv6_hdr(skb)->daddr, - skb->len - tcphoff, - IPPROTO_TCP, - skb->csum)) { - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); - return 0; - } - } else -#endif - if (csum_tcpudp_magic(ip_hdr(skb)->saddr, - ip_hdr(skb)->daddr, - skb->len - tcphoff, - ip_hdr(skb)->protocol, - skb->csum)) { - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); - return 0; - } - break; - default: - /* No need to checksum. */ - break; + if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_TCP, af)) { + IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off, + "Failed checksum for"); + return 0; } - return 1; } diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c index 58f9e255927e..de3597347542 100644 --- a/net/netfilter/ipvs/ip_vs_proto_udp.c +++ b/net/netfilter/ipvs/ip_vs_proto_udp.c @@ -25,7 +25,7 @@ static int udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int udphoff); + struct ip_vs_iphdr *iph); static int udp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, @@ -155,7 +155,7 @@ udp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!udp_csum_check(cp->af, skb, pp, udphoff)) + if (!udp_csum_check(cp->af, skb, pp, iph)) return 0; /* @@ -238,7 +238,7 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, int ret; /* Some checks before mangling */ - if (!udp_csum_check(cp->af, skb, pp, udphoff)) + if (!udp_csum_check(cp->af, skb, pp, iph)) return 0; /* @@ -298,48 +298,20 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, static int udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, - unsigned int udphoff) + struct ip_vs_iphdr *iph) { struct udphdr _udph, *uh; - uh = skb_header_pointer(skb, udphoff, sizeof(_udph), &_udph); + uh = skb_header_pointer(skb, iph->len, sizeof(_udph), &_udph); if (uh == NULL) return 0; - if (uh->check != 0) { - switch (skb->ip_summed) { - case CHECKSUM_NONE: - skb->csum = skb_checksum(skb, udphoff, - skb->len - udphoff, 0); - fallthrough; - case CHECKSUM_COMPLETE: -#ifdef CONFIG_IP_VS_IPV6 - if (af == AF_INET6) { - if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr, - &ipv6_hdr(skb)->daddr, - skb->len - udphoff, - IPPROTO_UDP, - skb->csum)) { - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); - return 0; - } - } else -#endif - if (csum_tcpudp_magic(ip_hdr(skb)->saddr, - ip_hdr(skb)->daddr, - skb->len - udphoff, - ip_hdr(skb)->protocol, - skb->csum)) { - IP_VS_DBG_RL_PKT(0, af, pp, skb, 0, - "Failed checksum for"); - return 0; - } - break; - default: - /* No need to checksum. */ - break; - } + if (!uh->check) + return 1; + if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_UDP, af)) { + IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off, + "Failed checksum for"); + return 0; } return 1; } From 15cab31a3730e05f0767b922a7450e5d784b2607 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Wed, 22 Jul 2026 13:15:16 +0300 Subject: [PATCH 234/456] ipvs: fix places with wrong packet offsets The offsets we use to packet headers and payloads should be based on skb->data. We even already respect non-zero network offset in ip_vs_fill_iph_skb() but some places do it wrongly and support only zero offset which is expected for the IP layer where IPVS has hooks. Change all places that instead of skb->data use offsets based on the network header (skb_network_header, ip_hdr, etc) because this doubles the network offset as noted by Sashiko. For ip_vs_nat_icmp_v6() we can even rely on the IPv6 header parsing done by the caller. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://sashiko.dev/#/patchset/20260710143733.29741-2-fw%40strlen.de Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 15 +-- net/netfilter/ipvs/ip_vs_app.c | 4 +- net/netfilter/ipvs/ip_vs_core.c | 133 +++++++++++++------------- net/netfilter/ipvs/ip_vs_proto_sctp.c | 4 +- net/netfilter/ipvs/ip_vs_proto_tcp.c | 4 +- net/netfilter/ipvs/ip_vs_proto_udp.c | 4 +- net/netfilter/ipvs/ip_vs_xmit.c | 26 ++--- 7 files changed, 97 insertions(+), 93 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index d8f9ddb0fb38..4a10a01d6e2f 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -1974,8 +1974,9 @@ int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, int offset, - unsigned int hooknum, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, unsigned int toff, + unsigned int wlen, unsigned int hooknum, + struct ip_vs_iphdr *ciph); void ip_vs_dest_dst_rcu_free(struct rcu_head *head); #ifdef CONFIG_IP_VS_IPV6 @@ -1988,8 +1989,9 @@ int ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, int ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, int offset, - unsigned int hooknum, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, unsigned int toff, + unsigned int wlen, unsigned int hooknum, + struct ip_vs_iphdr *ciph); #endif #ifdef CONFIG_SYSCTL @@ -2061,11 +2063,12 @@ static inline bool ip_vs_conn_use_hash2(struct ip_vs_conn *cp) } void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int dir); + struct ip_vs_conn *cp, int dir, unsigned int toff); #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int dir); + struct ip_vs_conn *cp, int dir, unsigned int toff, + struct ip_vs_iphdr *ciph); #endif static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum) diff --git a/net/netfilter/ipvs/ip_vs_app.c b/net/netfilter/ipvs/ip_vs_app.c index b0e00be85cb1..11cbdbaf561d 100644 --- a/net/netfilter/ipvs/ip_vs_app.c +++ b/net/netfilter/ipvs/ip_vs_app.c @@ -367,7 +367,7 @@ static inline int app_tcp_pkt_out(struct ip_vs_conn *cp, struct sk_buff *skb, if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th))) return 0; - th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len); + th = (struct tcphdr *)(skb->data + ipvsh->len); /* * Remember seq number in case this pkt gets resized @@ -443,7 +443,7 @@ static inline int app_tcp_pkt_in(struct ip_vs_conn *cp, struct sk_buff *skb, if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th))) return 0; - th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len); + th = (struct tcphdr *)(skb->data + ipvsh->len); /* * Remember seq number in case this pkt gets resized diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index c8b512725e6e..cd5eb71543ec 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -924,13 +924,12 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af, * - inout: 1=in->out, 0=out->in */ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int inout) + struct ip_vs_conn *cp, int inout, unsigned int toff) { struct iphdr *iph = ip_hdr(skb); - unsigned int icmp_offset = iph->ihl*4; - struct icmphdr *icmph = (struct icmphdr *)(skb_network_header(skb) + - icmp_offset); + struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff); struct iphdr *ciph = (struct iphdr *)(icmph + 1); + unsigned int coff __maybe_unused = toff + sizeof(struct icmphdr); if (inout) { iph->saddr = cp->vaddr.ip; @@ -957,48 +956,45 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, /* And finally the ICMP checksum */ icmph->checksum = 0; - icmph->checksum = ip_vs_checksum_complete(skb, icmp_offset); + icmph->checksum = ip_vs_checksum_complete(skb, toff); skb->ip_summed = CHECKSUM_UNNECESSARY; if (inout) - IP_VS_DBG_PKT(11, AF_INET, pp, skb, (void *)ciph - (void *)iph, - "Forwarding altered outgoing ICMP"); + IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff, + "Forwarding altered outgoing ICMP"); else - IP_VS_DBG_PKT(11, AF_INET, pp, skb, (void *)ciph - (void *)iph, - "Forwarding altered incoming ICMP"); + IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff, + "Forwarding altered incoming ICMP"); } #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int inout) + struct ip_vs_conn *cp, int inout, unsigned int toff, + struct ip_vs_iphdr *ciph) { struct ipv6hdr *iph = ipv6_hdr(skb); - unsigned int icmp_offset = 0; - unsigned int offs = 0; /* header offset*/ int protocol; struct icmp6hdr *icmph; - struct ipv6hdr *ciph; - unsigned short fragoffs; + struct ipv6hdr *cih; - ipv6_find_hdr(skb, &icmp_offset, IPPROTO_ICMPV6, &fragoffs, NULL); - icmph = (struct icmp6hdr *)(skb_network_header(skb) + icmp_offset); - offs = icmp_offset + sizeof(struct icmp6hdr); - ciph = (struct ipv6hdr *)(skb_network_header(skb) + offs); + icmph = (struct icmp6hdr *)(skb->data + toff); + cih = (struct ipv6hdr *)(skb->data + ciph->off); - protocol = ipv6_find_hdr(skb, &offs, -1, &fragoffs, NULL); + protocol = ciph->protocol; if (inout) { iph->saddr = cp->vaddr.in6; - ciph->daddr = cp->vaddr.in6; + cih->daddr = cp->vaddr.in6; } else { iph->daddr = cp->daddr.in6; - ciph->saddr = cp->daddr.in6; + cih->saddr = cp->daddr.in6; } /* the TCP/UDP/SCTP port */ - if (!fragoffs && (IPPROTO_TCP == protocol || IPPROTO_UDP == protocol || - IPPROTO_SCTP == protocol)) { - __be16 *ports = (void *)(skb_network_header(skb) + offs); + if (!ciph->fragoffs && + (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || + protocol == IPPROTO_SCTP)) { + __be16 *ports = (void *)(skb->data + ciph->len); IP_VS_DBG(11, "%s() changed port %d to %d\n", __func__, ntohs(inout ? ports[1] : ports[0]), @@ -1011,19 +1007,17 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, /* And finally the ICMP checksum */ icmph->icmp6_cksum = ~csum_ipv6_magic(&iph->saddr, &iph->daddr, - skb->len - icmp_offset, + skb->len - toff, IPPROTO_ICMPV6, 0); - skb->csum_start = skb_network_header(skb) - skb->head + icmp_offset; + skb->csum_start = skb_headroom(skb) + toff; skb->csum_offset = offsetof(struct icmp6hdr, icmp6_cksum); skb->ip_summed = CHECKSUM_PARTIAL; if (inout) - IP_VS_DBG_PKT(11, AF_INET6, pp, skb, - (void *)ciph - (void *)iph, + IP_VS_DBG_PKT(11, AF_INET6, pp, skb, ciph->off, "Forwarding altered outgoing ICMPv6"); else - IP_VS_DBG_PKT(11, AF_INET6, pp, skb, - (void *)ciph - (void *)iph, + IP_VS_DBG_PKT(11, AF_INET6, pp, skb, ciph->off, "Forwarding altered incoming ICMPv6"); } #endif @@ -1033,37 +1027,38 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, */ static int handle_response_icmp(int af, struct sk_buff *skb, union nf_inet_addr *snet, - __u8 protocol, struct ip_vs_conn *cp, + struct ip_vs_conn *cp, struct ip_vs_protocol *pp, - unsigned int offset, unsigned int ihl, - unsigned int hooknum) + struct ip_vs_iphdr *ciph, + unsigned int toff, unsigned int hooknum) { int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP; unsigned int verdict = NF_DROP; + unsigned int ctoff = ciph->len; if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) goto after_nat; /* Ensure the checksum is correct */ - if (!ip_vs_checksum_common_check(skb, ihl, iproto, af)) { + if (!ip_vs_checksum_common_check(skb, toff, iproto, af)) { /* Failed checksum! */ IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n", IP_VS_DBG_ADDR(af, snet)); goto out; } - if (IPPROTO_TCP == protocol || IPPROTO_UDP == protocol || - IPPROTO_SCTP == protocol) - offset += 2 * sizeof(__u16); - if (skb_ensure_writable(skb, offset)) + if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP || + ciph->protocol == IPPROTO_SCTP) + ctoff += 2 * sizeof(__u16); + if (skb_ensure_writable(skb, ctoff)) goto out; #ifdef CONFIG_IP_VS_IPV6 if (af == AF_INET6) - ip_vs_nat_icmp_v6(skb, pp, cp, 1); + ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, ciph); else #endif - ip_vs_nat_icmp(skb, pp, cp, 1); + ip_vs_nat_icmp(skb, pp, cp, 1, toff); if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum)) goto out; @@ -1091,9 +1086,9 @@ static int handle_response_icmp(int af, struct sk_buff *skb, * Currently handles error types - unreachable, quench, ttl exceeded. */ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, - int *related, unsigned int hooknum) + int *related, unsigned int hooknum, + struct ip_vs_iphdr *ipvsh) { - struct iphdr *iph; struct icmphdr _icmph, *ic; struct iphdr _ciph, *cih; /* The ip header contained within the ICMP */ struct ip_vs_iphdr ciph; @@ -1108,17 +1103,19 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, if (ip_is_fragment(ip_hdr(skb))) { if (ip_vs_gather_frags(ipvs, skb, ip_vs_defrag_user(hooknum))) return NF_STOLEN; + if (!ip_vs_fill_iph_skb(AF_INET, skb, false, ipvsh)) + return NF_ACCEPT; } - iph = ip_hdr(skb); - offset = ihl = iph->ihl * 4; + ihl = ipvsh->len; + offset = ipvsh->len; ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph); if (ic == NULL) return NF_DROP; IP_VS_DBG(12, "Outgoing ICMP (%d,%d) %pI4->%pI4\n", ic->type, ntohs(icmp_id(ic)), - &iph->saddr, &iph->daddr); + &ipvsh->saddr.ip, &ipvsh->daddr.ip); /* * Work through seeing if this is for us. @@ -1137,7 +1134,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, /* Now find the contained IP header */ offset += sizeof(_icmph); cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (cih == NULL) + if (!(cih && cih->version == 4 && cih->ihl >= 5)) return NF_ACCEPT; /* The packet looks wrong, ignore */ pp = ip_vs_proto_get(cih->protocol); @@ -1160,9 +1157,9 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, if (!cp) return NF_ACCEPT; - snet.ip = iph->saddr; - return handle_response_icmp(AF_INET, skb, &snet, cih->protocol, cp, - pp, ciph.len, ihl, hooknum); + snet.ip = ipvsh->saddr.ip; + return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph, ihl, + hooknum); } #ifdef CONFIG_IP_VS_IPV6 @@ -1175,7 +1172,6 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, struct ip_vs_conn *cp; struct ip_vs_protocol *pp; union nf_inet_addr snet; - unsigned int offset; *related = 1; ic = frag_safe_skb_hp(skb, ipvsh->len, sizeof(_icmph), &_icmph); @@ -1218,9 +1214,8 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; snet.in6 = ciph.saddr.in6; - offset = ciph.len; - return handle_response_icmp(AF_INET6, skb, &snet, ciph.protocol, cp, - pp, offset, ipvsh->len, hooknum); + return handle_response_icmp(AF_INET6, skb, &snet, cp, pp, &ciph, + ipvsh->len, hooknum); } #endif @@ -1546,7 +1541,8 @@ ip_vs_out_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *stat #endif if (unlikely(iph.protocol == IPPROTO_ICMP)) { int related; - int verdict = ip_vs_out_icmp(ipvs, skb, &related, hooknum); + int verdict = ip_vs_out_icmp(ipvs, skb, &related, + hooknum, &iph); if (related) return verdict; @@ -1754,9 +1750,8 @@ static int ipvs_gre_decap(struct netns_ipvs *ipvs, struct sk_buff *skb, */ static int ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, - unsigned int hooknum) + unsigned int hooknum, struct ip_vs_iphdr *iph) { - struct iphdr *iph; struct icmphdr _icmph, *ic; struct iphdr _ciph, *cih; /* The ip header contained within the ICMP */ struct ip_vs_iphdr ciph; @@ -1766,7 +1761,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, unsigned int offset, offset2, ihl, verdict; bool tunnel, new_cp = false; union nf_inet_addr *raddr; - char *outer_proto = "IPIP"; + char *outer_proto __maybe_unused = "IPIP"; unsigned int hlen_ipip; int ulen = 0; @@ -1776,17 +1771,19 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, if (ip_is_fragment(ip_hdr(skb))) { if (ip_vs_gather_frags(ipvs, skb, ip_vs_defrag_user(hooknum))) return NF_STOLEN; + if (!ip_vs_fill_iph_skb(AF_INET, skb, false, iph)) + return NF_ACCEPT; } - iph = ip_hdr(skb); - offset = ihl = iph->ihl * 4; + ihl = iph->len; + offset = iph->len; ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph); if (ic == NULL) return NF_DROP; IP_VS_DBG(12, "Incoming ICMP (%d,%d) %pI4->%pI4\n", ic->type, ntohs(icmp_id(ic)), - &iph->saddr, &iph->daddr); + &iph->saddr.ip, &iph->daddr.ip); /* * Work through seeing if this is for us. @@ -1903,7 +1900,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, !ip_vs_checksum_common_check(skb, ihl, IPPROTO_ICMP, AF_INET)) { /* Failed checksum! */ IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n", - &iph->saddr); + &iph->saddr.ip); goto out; } @@ -1974,7 +1971,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol || IPPROTO_SCTP == cih->protocol) offset += 2 * sizeof(__u16); - verdict = ip_vs_icmp_xmit(skb, cp, pp, offset, hooknum, &ciph); + verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, offset, hooknum, + &ciph); out: if (likely(!new_cp)) @@ -2087,7 +2085,8 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, IPPROTO_SCTP == ciph.protocol) offset += 2 * sizeof(__u16); /* Also mangle ports */ - verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, offset, hooknum, &ciph); + verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, offset, hooknum, + &ciph); out: if (likely(!new_cp)) @@ -2166,7 +2165,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state if (unlikely(iph.protocol == IPPROTO_ICMP)) { int related; int verdict = ip_vs_in_icmp(ipvs, skb, &related, - hooknum); + hooknum, &iph); if (related) return verdict; @@ -2302,6 +2301,7 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct netns_ipvs *ipvs = net_ipvs(state->net); + struct ip_vs_iphdr iphdr; int r; /* ipvs enabled in this netns ? */ @@ -2311,10 +2311,9 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb, if (state->pf == NFPROTO_IPV4) { if (ip_hdr(skb)->protocol != IPPROTO_ICMP) return NF_ACCEPT; + ip_vs_fill_iph_skb(AF_INET, skb, false, &iphdr); #ifdef CONFIG_IP_VS_IPV6 } else { - struct ip_vs_iphdr iphdr; - ip_vs_fill_iph_skb(AF_INET6, skb, false, &iphdr); if (iphdr.protocol != IPPROTO_ICMPV6) @@ -2324,7 +2323,7 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb, #endif } - return ip_vs_in_icmp(ipvs, skb, &r, state->hook); + return ip_vs_in_icmp(ipvs, skb, &r, state->hook, &iphdr); } static const struct nf_hook_ops ip_vs_ops4[] = { diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c index f6f732b7dfa8..3dbd3096e163 100644 --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c @@ -121,7 +121,7 @@ sctp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - sctph = (void *) skb_network_header(skb) + sctphoff; + sctph = (void *)skb->data + sctphoff; /* Only update csum if we really have to */ if (sctph->source != cp->vport || payload_csum || @@ -169,7 +169,7 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - sctph = (void *) skb_network_header(skb) + sctphoff; + sctph = (void *)skb->data + sctphoff; /* Only update csum if we really have to */ if (sctph->dest != cp->dport || payload_csum || diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c index 533fce3e5e4e..99a286fdc90c 100644 --- a/net/netfilter/ipvs/ip_vs_proto_tcp.c +++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c @@ -179,7 +179,7 @@ tcp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - tcph = (void *)skb_network_header(skb) + tcphoff; + tcph = (void *)skb->data + tcphoff; tcph->source = cp->vport; /* Adjust TCP checksums */ @@ -260,7 +260,7 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - tcph = (void *)skb_network_header(skb) + tcphoff; + tcph = (void *)skb->data + tcphoff; tcph->dest = cp->dport; /* diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c index de3597347542..f32785682402 100644 --- a/net/netfilter/ipvs/ip_vs_proto_udp.c +++ b/net/netfilter/ipvs/ip_vs_proto_udp.c @@ -170,7 +170,7 @@ udp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - udph = (void *)skb_network_header(skb) + udphoff; + udph = (void *)skb->data + udphoff; udph->source = cp->vport; /* @@ -254,7 +254,7 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, payload_csum = true; } - udph = (void *)skb_network_header(skb) + udphoff; + udph = (void *)skb->data + udphoff; udph->dest = cp->dport; /* diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index 9fef4335da13..c23401c789de 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -1502,8 +1502,9 @@ ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, */ int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, int offset, unsigned int hooknum, - struct ip_vs_iphdr *iph) + struct ip_vs_protocol *pp, unsigned int toff, + unsigned int wlen, unsigned int hooknum, + struct ip_vs_iphdr *ciph) { struct rtable *rt; /* Route to the other host */ int rc; @@ -1515,7 +1516,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, translate address/port back */ if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) { if (cp->packet_xmit) - rc = cp->packet_xmit(skb, cp, pp, iph); + rc = cp->packet_xmit(skb, cp, pp, ciph); else rc = NF_ACCEPT; /* do not touch skb anymore */ @@ -1533,7 +1534,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL; local = __ip_vs_get_out_rt(cp->ipvs, cp->af, skb, cp->dest, cp->daddr.ip, rt_mode, - NULL, iph); + NULL, ciph); if (local < 0) goto tx_error; rt = skb_rtable(skb); @@ -1565,13 +1566,13 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, } /* copy-on-write the packet before mangling it */ - if (skb_ensure_writable(skb, offset)) + if (skb_ensure_writable(skb, wlen)) goto tx_error; if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp(skb, pp, cp, 0); + ip_vs_nat_icmp(skb, pp, cp, 0, toff); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; @@ -1587,8 +1588,9 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, #ifdef CONFIG_IP_VS_IPV6 int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, int offset, unsigned int hooknum, - struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, unsigned int toff, + unsigned int wlen, unsigned int hooknum, + struct ip_vs_iphdr *ciph) { struct rt6_info *rt; /* Route to the other host */ int rc; @@ -1600,7 +1602,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, translate address/port back */ if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) { if (cp->packet_xmit) - rc = cp->packet_xmit(skb, cp, pp, ipvsh); + rc = cp->packet_xmit(skb, cp, pp, ciph); else rc = NF_ACCEPT; /* do not touch skb anymore */ @@ -1617,7 +1619,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL; local = __ip_vs_get_out_rt_v6(cp->ipvs, cp->af, skb, cp->dest, - &cp->daddr.in6, NULL, ipvsh, 0, rt_mode); + &cp->daddr.in6, NULL, ciph, 0, rt_mode); if (local < 0) goto tx_error; rt = dst_rt6_info(skb_dst(skb)); @@ -1649,13 +1651,13 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, } /* copy-on-write the packet before mangling it */ - if (skb_ensure_writable(skb, offset)) + if (skb_ensure_writable(skb, wlen)) goto tx_error; if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp_v6(skb, pp, cp, 0); + ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, ciph); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; From 342e24a339b90e8e339a0f8c151ca479b8565661 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Wed, 22 Jul 2026 13:15:17 +0300 Subject: [PATCH 235/456] ipvs: do not mangle ICMP replies for non-first fragments Sashiko warns that ip_vs_nat_icmp() unconditionally mangles the payload for embedded non-first IPv4 fragments. The problem is in the very old inverted pp->dont_defrag check which should not continue when embedded is a non-first TCP/UDP/SCTP fragment. Check for embedded non-first fragment is also missing from ip_vs_out_icmp_v6(), it is needed before any connection lookups that expect ports after the network headers. Drop the blocking code from ip_vs_in_icmp_v6() which prevents ICMPv6 from local clients to use non-MASQ forwarding. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://sashiko.dev/#/patchset/20260720201122.79882-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 11 +++--- net/netfilter/ipvs/ip_vs_core.c | 61 ++++++++++++--------------------- net/netfilter/ipvs/ip_vs_xmit.c | 28 +++++++++++---- 3 files changed, 48 insertions(+), 52 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 4a10a01d6e2f..e6ca930a3507 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -1975,8 +1975,7 @@ int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int wlen, unsigned int hooknum, - struct ip_vs_iphdr *ciph); + unsigned int hooknum, struct ip_vs_iphdr *ciph); void ip_vs_dest_dst_rcu_free(struct rcu_head *head); #ifdef CONFIG_IP_VS_IPV6 @@ -1990,8 +1989,7 @@ int ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int wlen, unsigned int hooknum, - struct ip_vs_iphdr *ciph); + unsigned int hooknum, struct ip_vs_iphdr *ciph); #endif #ifdef CONFIG_SYSCTL @@ -2063,12 +2061,13 @@ static inline bool ip_vs_conn_use_hash2(struct ip_vs_conn *cp) } void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int dir, unsigned int toff); + struct ip_vs_conn *cp, int dir, unsigned int toff, + bool has_ports); #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int dir, unsigned int toff, - struct ip_vs_iphdr *ciph); + bool has_ports, struct ip_vs_iphdr *ciph); #endif static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index cd5eb71543ec..7efa209a517b 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -924,7 +924,8 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af, * - inout: 1=in->out, 0=out->in */ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, - struct ip_vs_conn *cp, int inout, unsigned int toff) + struct ip_vs_conn *cp, int inout, unsigned int toff, + bool has_ports) { struct iphdr *iph = ip_hdr(skb); struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff); @@ -944,8 +945,7 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, } /* the TCP/UDP/SCTP port */ - if (IPPROTO_TCP == ciph->protocol || IPPROTO_UDP == ciph->protocol || - IPPROTO_SCTP == ciph->protocol) { + if (has_ports) { __be16 *ports = (void *)ciph + ciph->ihl*4; if (inout) @@ -970,18 +970,15 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, #ifdef CONFIG_IP_VS_IPV6 void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int inout, unsigned int toff, - struct ip_vs_iphdr *ciph) + bool has_ports, struct ip_vs_iphdr *ciph) { struct ipv6hdr *iph = ipv6_hdr(skb); - int protocol; struct icmp6hdr *icmph; struct ipv6hdr *cih; icmph = (struct icmp6hdr *)(skb->data + toff); cih = (struct ipv6hdr *)(skb->data + ciph->off); - protocol = ciph->protocol; - if (inout) { iph->saddr = cp->vaddr.in6; cih->daddr = cp->vaddr.in6; @@ -991,9 +988,7 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, } /* the TCP/UDP/SCTP port */ - if (!ciph->fragoffs && - (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || - protocol == IPPROTO_SCTP)) { + if (has_ports) { __be16 *ports = (void *)(skb->data + ciph->len); IP_VS_DBG(11, "%s() changed port %d to %d\n", __func__, @@ -1035,6 +1030,7 @@ static int handle_response_icmp(int af, struct sk_buff *skb, int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP; unsigned int verdict = NF_DROP; unsigned int ctoff = ciph->len; + bool has_ports = false; if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) goto after_nat; @@ -1048,17 +1044,19 @@ static int handle_response_icmp(int af, struct sk_buff *skb, } if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP || - ciph->protocol == IPPROTO_SCTP) + ciph->protocol == IPPROTO_SCTP) { ctoff += 2 * sizeof(__u16); + has_ports = true; + } if (skb_ensure_writable(skb, ctoff)) goto out; #ifdef CONFIG_IP_VS_IPV6 if (af == AF_INET6) - ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, ciph); + ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, has_ports, ciph); else #endif - ip_vs_nat_icmp(skb, pp, cp, 1, toff); + ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports); if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum)) goto out; @@ -1142,8 +1140,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; /* Is the embedded protocol header present? */ - if (unlikely(cih->frag_off & htons(IP_OFFSET) && - pp->dont_defrag)) + if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag)) return NF_ACCEPT; IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset, @@ -1207,6 +1204,10 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, if (!pp) return NF_ACCEPT; + /* Is the embedded protocol header present? */ + if (unlikely(ciph.fragoffs && !pp->dont_defrag)) + return NF_ACCEPT; + /* The embedded headers contain source and dest in reverse order */ cp = INDIRECT_CALL_1(pp->conn_out_get, ip_vs_conn_out_get_proto, ipvs, AF_INET6, skb, &ciph); @@ -1865,8 +1866,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, pp = pd->pp; /* Is the embedded protocol header present? */ - if (unlikely(cih->frag_off & htons(IP_OFFSET) && - pp->dont_defrag)) + if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag)) return NF_ACCEPT; IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset, @@ -1874,7 +1874,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, offset2 = offset; ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph); - offset = ciph.len; /* The embedded headers contain source and dest in reverse order. * For IPIP/UDP/GRE tunnel this is error for request, not for reply. @@ -1968,11 +1967,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* do the statistics and put it back */ ip_vs_in_stats(cp, skb); - if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol || - IPPROTO_SCTP == cih->protocol) - offset += 2 * sizeof(__u16); - verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, offset, hooknum, - &ciph); + verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, hooknum, &ciph); out: if (likely(!new_cp)) @@ -2032,8 +2027,8 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, return NF_ACCEPT; pp = pd->pp; - /* Cannot handle fragmented embedded protocol */ - if (ciph.fragoffs) + /* Is the embedded protocol header present? */ + if (ciph.fragoffs && !pp->dont_defrag) return NF_ACCEPT; IP_VS_DBG_PKT(11, AF_INET6, pp, skb, offset, @@ -2057,13 +2052,6 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, new_cp = true; } - /* VS/TUN, VS/DR and LOCALNODE just let it go */ - if ((hooknum == NF_INET_LOCAL_OUT) && - (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)) { - verdict = NF_ACCEPT; - goto out; - } - verdict = NF_DROP; /* Ensure the checksum is correct */ @@ -2079,14 +2067,7 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, /* do the statistics and put it back */ ip_vs_in_stats(cp, skb); - /* Need to mangle contained IPv6 header in ICMPv6 packet */ - offset = ciph.len; - if (IPPROTO_TCP == ciph.protocol || IPPROTO_UDP == ciph.protocol || - IPPROTO_SCTP == ciph.protocol) - offset += 2 * sizeof(__u16); /* Also mangle ports */ - - verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, offset, hooknum, - &ciph); + verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, hooknum, &ciph); out: if (likely(!new_cp)) diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index c23401c789de..0b0c5304993a 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -1503,13 +1503,14 @@ ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int wlen, unsigned int hooknum, - struct ip_vs_iphdr *ciph) + unsigned int hooknum, struct ip_vs_iphdr *ciph) { struct rtable *rt; /* Route to the other host */ int rc; int local; int rt_mode, was_input; + bool has_ports = false; + unsigned int wlen; /* The ICMP packet for VS/TUN, VS/DR and LOCALNODE will be forwarded directly here, because there is no need to @@ -1565,6 +1566,13 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, goto tx_error; } + wlen = ciph->len; + if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP || + ciph->protocol == IPPROTO_SCTP) { + wlen += 2 * sizeof(__u16); /* Also mangle ports */ + has_ports = true; + } + /* copy-on-write the packet before mangling it */ if (skb_ensure_writable(skb, wlen)) goto tx_error; @@ -1572,7 +1580,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp(skb, pp, cp, 0, toff); + ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; @@ -1589,10 +1597,11 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int wlen, unsigned int hooknum, - struct ip_vs_iphdr *ciph) + unsigned int hooknum, struct ip_vs_iphdr *ciph) { + bool has_ports = false; struct rt6_info *rt; /* Route to the other host */ + unsigned int wlen; int rc; int local; int rt_mode; @@ -1650,6 +1659,13 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, goto tx_error; } + wlen = ciph->len; + if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP || + ciph->protocol == IPPROTO_SCTP) { + wlen += 2 * sizeof(__u16); /* Also mangle ports */ + has_ports = true; + } + /* copy-on-write the packet before mangling it */ if (skb_ensure_writable(skb, wlen)) goto tx_error; @@ -1657,7 +1673,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, if (skb_cow(skb, rt->dst.dev->hard_header_len)) goto tx_error; - ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, ciph); + ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, has_ports, ciph); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; From da7d894c41d5910daae2b8ffa024c52ff0a4df6a Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Wed, 22 Jul 2026 13:25:39 +0300 Subject: [PATCH 236/456] ipvs: clear the nfct flag under lock Sashiko warns that cp->flags should be changed under cp->lock Fixes: 35dfb013149f ("ipvs: queue delayed work to expire no destination connections if expire_nodest_conn=1") Fixes: f0a5e4d7a594 ("ipvs: allow connection reuse for unconfirmed conntrack") Link: https://sashiko.dev/#/patchset/CALMqdkR704S2BG_QD_bgHTFp2%2B1QCi7n0T4zoZyTo8mDZevYSA%40mail.gmail.com Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index 7efa209a517b..6b79e0c4d9e2 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -2194,8 +2194,11 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state } if (resched) { - if (!old_ct) + if (!old_ct) { + spin_lock_bh(&cp->lock); cp->flags &= ~IP_VS_CONN_F_NFCT; + spin_unlock_bh(&cp->lock); + } if (!atomic_read(&cp->n_control)) ip_vs_conn_expire_now(cp); __ip_vs_conn_put(cp); @@ -2211,8 +2214,11 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state if (sysctl_expire_nodest_conn(ipvs)) { bool old_ct = ip_vs_conn_uses_old_conntrack(cp, skb); - if (!old_ct) + if (!old_ct) { + spin_lock_bh(&cp->lock); cp->flags &= ~IP_VS_CONN_F_NFCT; + spin_unlock_bh(&cp->lock); + } ip_vs_conn_expire_now(cp); __ip_vs_conn_put(cp); From 39e88f28fb32bf02bd4b525c24c842c9cff5663d Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Sun, 19 Jul 2026 22:15:23 +0000 Subject: [PATCH 237/456] netfilter: nft_payload: fix mask build for partial field offload nft_payload_offload_mask() builds the offload match mask for a payload expression that covers only part of a header field. For a partial IPv6 address match (field_len = 16, priv_len = 1) that shift is 1 << 120, which is undefined on the 32-bit int operand. It also trims only one word, so the remaining words stay 0xffffffff (and when priv_len is a multiple of 4 the trim is skipped entirely), leaving the mask covering more bytes than the rule matches. UBSAN: shift-out-of-bounds in net/netfilter/nft_payload.c:278:20 shift exponent 120 is too large for 32-bit type 'int' ... The match is byte-granular and struct nft_data is zero-initialised, so the correct mask is simply the first priv_len bytes set to 0xff. Set those bytes directly and drop the word/shift trimming; this removes the undefined shift and no longer over-masks the trailing bytes. Fixes: a5d45bc0dc50 ("netfilter: nftables_offload: build mask based from the matching bytes") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_payload.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c index 391539a1ceaa..8a4472fd77d9 100644 --- a/net/netfilter/nft_payload.c +++ b/net/netfilter/nft_payload.c @@ -259,9 +259,7 @@ static int nft_payload_dump(struct sk_buff *skb, static bool nft_payload_offload_mask(struct nft_offload_reg *reg, u32 priv_len, u32 field_len) { - unsigned int remainder, delta, k; struct nft_data mask = {}; - __be32 remainder_mask; if (priv_len == field_len) { memset(®->mask, 0xff, priv_len); @@ -270,15 +268,7 @@ static bool nft_payload_offload_mask(struct nft_offload_reg *reg, return false; } - memset(&mask, 0xff, field_len); - remainder = priv_len % sizeof(u32); - if (remainder) { - k = priv_len / sizeof(u32); - delta = field_len - priv_len; - remainder_mask = htonl(~((1 << (delta * BITS_PER_BYTE)) - 1)); - mask.data[k] = (__force u32)remainder_mask; - } - + memset(&mask, 0xff, priv_len); memcpy(®->mask, &mask, field_len); return true; From 655111f878a455f724e20122929cf2afa52b76e4 Mon Sep 17 00:00:00 2001 From: Jackie Liu Date: Wed, 15 Jul 2026 15:44:55 +0800 Subject: [PATCH 238/456] tracing: Propagate errors from remote event bulk updates remote_events_dir_enable_write() ignores the return value from trace_remote_enable_event(). If a remote rejects an event state change, the write therefore reports success even though the affected event remains in its previous state. Keep trying all events, but retain and return the first error. This matches __ftrace_set_clr_event_nolock(), which permits partial updates while notifying userspace when an operation fails. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260715074455.3897-1-liu.yun@linux.dev Fixes: 775cb093bc50 ("tracing: Add events/ root files to trace remotes") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Jackie Liu Reviewed-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace_remote.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index ef42d9c38b37..e6724f947170 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -1149,10 +1149,21 @@ static ssize_t remote_events_dir_enable_write(struct file *filp, const char __us for (i = 0; i < remote->nr_events; i++) { struct remote_event *evt = &remote->events[i]; + int eret; - trace_remote_enable_event(remote, evt, enable); + eret = trace_remote_enable_event(remote, evt, enable); + /* + * Save the first error and return that. Some events + * may still have been enabled, but let the user + * know that something went wrong. + */ + if (!ret && eret) + ret = eret; } + if (ret) + return ret; + return count; } From c1d87e724ae55e781b7cc7ccafb34d9e668582b2 Mon Sep 17 00:00:00 2001 From: deepakraog Date: Wed, 15 Jul 2026 20:06:04 +0530 Subject: [PATCH 239/456] tracing: Fix resource leak on mmiotrace trace_pipe close The mmiotrace tracer was added May 12th 2008. At that time, resources created in pipe_open() could not be freed because there was not pipe_close function pointer of the tracer. The pipe_close function pointer was added in December 7th, 2009, but the mmiotrace tracer was not updated. mmio_pipe_open() allocates a header_iter and takes a pci_dev reference when trace_pipe is opened. mmio_close() frees them, but it was only wired to the tracer's .close callback. tracing_release_pipe() invokes .pipe_close, not .close, when the trace_pipe file is released. As a result, closing trace_pipe with the mmiotrace tracer active leaked the header_iter allocation and left a stale pci_dev reference. Set .pipe_close to mmio_close, matching how function_graph wires both callbacks to the same handler. Note, if the trace_pipe is read to completion, it will clean up the resources, but if one were to run: # head -n 1 /sys/kernel/tracing/trace_pipe VERSION 20070824 Over and over again, it would trigger a massive leak. Cc: stable@vger.kernel.org Fixes: c521efd1700a8 ("tracing: Add pipe_close interface) Link: https://patch.msgid.link/20260715143604.14481-1-gaikwad.dcg@gmail.com Signed-off-by: deepakraog Signed-off-by: Steven Rostedt --- kernel/trace/trace_mmiotrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_mmiotrace.c b/kernel/trace/trace_mmiotrace.c index 226cf66e0d68..20812e7f911f 100644 --- a/kernel/trace/trace_mmiotrace.c +++ b/kernel/trace/trace_mmiotrace.c @@ -109,7 +109,6 @@ static void mmio_pipe_open(struct trace_iterator *iter) iter->private = hiter; } -/* XXX: This is not called when the pipe is closed! */ static void mmio_close(struct trace_iterator *iter) { struct header_iter *hiter = iter->private; @@ -279,6 +278,7 @@ static struct tracer mmio_tracer __read_mostly = .start = mmio_trace_start, .pipe_open = mmio_pipe_open, .close = mmio_close, + .pipe_close = mmio_close, .read = mmio_read, .print_line = mmio_print_line, .noboot = true, From 98917a499ec7064c14fc56d180a4fd636fc2784c Mon Sep 17 00:00:00 2001 From: Raphael Zimmer Date: Wed, 27 May 2026 16:06:17 +0200 Subject: [PATCH 240/456] libceph: Fix multiplication overflow in decode_new_up_state_weight() If a message of type CEPH_MSG_OSD_MAP contains a (maliciously) corrupted osdmap, out-of-bounds memory accesses may occur in decode_new_up_state_weight(). This happens because the bounds check for the new_state part is based on calculating its length depending on a len value read from the incoming message. This calculation may overflow leading to an incorrect bounds check. Subsequently, out-of-bounds reads may occur when decoding this part. This patch switches the multiplication to use check_mul_overflow() to abort processing the osdmap if an overflow occurred. Therefore, osdmaps/messages containing large values for len that result in a multiplication overflow are treated as invalid. [ idryomov: rename new_state_len -> new_state_item_size, formatting ] Cc: stable@vger.kernel.org Fixes: 930c53286977 ("libceph: apply new_state before new_up_client on incrementals") Signed-off-by: Raphael Zimmer Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- net/ceph/osdmap.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index 8b5b0587a0cf..8e77096718c4 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -1842,6 +1842,8 @@ static int decode_new_up_state_weight(void **p, void *end, u8 struct_v, void *new_up_client; void *new_state; void *new_weight_end; + const u32 new_state_item_size = + sizeof(u32) + (struct_v >= 5 ? sizeof(u32) : sizeof(u8)); u32 len; int ret; int i; @@ -1862,7 +1864,8 @@ static int decode_new_up_state_weight(void **p, void *end, u8 struct_v, new_state = *p; ceph_decode_32_safe(p, end, len, e_inval); - len *= sizeof(u32) + (struct_v >= 5 ? sizeof(u32) : sizeof(u8)); + if (check_mul_overflow(len, new_state_item_size, &len)) + goto e_inval; ceph_decode_need(p, end, len, e_inval); *p += len; From 05f90284223381005d6bcddab3fda4a97f9c3401 Mon Sep 17 00:00:00 2001 From: Douya Le Date: Fri, 29 May 2026 16:11:44 +0800 Subject: [PATCH 241/456] libceph: reject zero bucket types in crush_decode CRUSH bucket type 0 is reserved for devices. The mapper relies on that invariant and uses type 0 to identify leaf devices. If crush_decode() accepts a bucket with type 0, a malformed CRUSH map can make the mapper treat a negative bucket ID as a device and pass it to is_out(), which then indexes the OSD weight array with a negative value. Reject zero bucket types while decoding the CRUSH map so the invalid state never reaches the mapper. Cc: stable@vger.kernel.org Fixes: f24e9980eb86 ("ceph: OSD client") Reported-by: Yuan Tan Reported-by: Zhengchuan Liang Reported-by: Xin Liu Assisted-by: Codex:GPT-5.4 Signed-off-by: Douya Le Signed-off-by: Ren Wei Reviewed-by: Ilya Dryomov Signed-off-by: Ilya Dryomov --- net/ceph/osdmap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index 8e77096718c4..3c87f4b24e51 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -518,6 +518,8 @@ static struct crush_map *crush_decode(void *pbyval, void *end) ceph_decode_need(p, end, 4*sizeof(u32), bad); b->id = ceph_decode_32(p); b->type = ceph_decode_16(p); + if (b->type == 0) + goto bad; b->alg = ceph_decode_8(p); if (b->alg != alg) { b->alg = 0; From 40480eee361ed9676b3f844d532ac28b47251634 Mon Sep 17 00:00:00 2001 From: Raphael Zimmer Date: Fri, 29 May 2026 09:42:57 +0200 Subject: [PATCH 242/456] libceph: Reject monmaps advertising zero monitors A message of type CEPH_MSG_MON_MAP contains a monmap that is sent from a monitor to the client. This monmap contains information about the existing monitors in the cluster. Currently, a monmap indicating that there are zero monitors in the cluster is treated as valid. However, it is impossible to have zero monitors in the cluster and still receive a valid monmap from a monitor. Therefore, such a monmap must be corrupted and should be treated as invalid. Furthermore, a monmap with a monitor count of zero can subsequently crash the client when attempting to open a session with a monitor in __open_session(). This happens because the "BUG_ON(monc->monmap->num_mon < 1)" assertion in pick_new_mon() is triggered. This patch extends a check in ceph_monmap_decode() to also reject arriving mon_maps with num_mon == 0 rather than only with num_mon > CEPH_MAX_MON. [ idryomov: drop "log output for unusual values of num_mon" part ] Cc: stable@vger.kernel.org Signed-off-by: Raphael Zimmer Reviewed-by: Ilya Dryomov Signed-off-by: Ilya Dryomov --- net/ceph/mon_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ceph/mon_client.c b/net/ceph/mon_client.c index d2cdc8ee3155..24acdd580e79 100644 --- a/net/ceph/mon_client.c +++ b/net/ceph/mon_client.c @@ -114,7 +114,7 @@ static struct ceph_monmap *ceph_monmap_decode(void **p, void *end, bool msgr2) dout("%s fsid %pU epoch %u num_mon %u\n", __func__, &fsid, epoch, num_mon); - if (num_mon > CEPH_MAX_MON) + if (num_mon == 0 || num_mon > CEPH_MAX_MON) goto e_inval; monmap = kmalloc_flex(*monmap, mon_inst, num_mon, GFP_NOIO); From 4dbc71bcaf9a30abf3920a4e2cc4ed33bba78c02 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 29 May 2026 00:37:24 +0000 Subject: [PATCH 243/456] ceph: fix pre-auth out-of-bounds read on snaptrace in ceph_handle_caps() ceph_handle_caps() reads snap_trace_len from the wire-format ceph_mds_caps header and uses it unconditionally to build a fake end pointer (snaptrace + snaptrace_len) that is later handed to ceph_update_snap_trace() in the CEPH_CAP_OP_IMPORT case: snaptrace = h + 1; snaptrace_len = le32_to_cpu(h->snap_trace_len); p = snaptrace + snaptrace_len; ... case CEPH_CAP_OP_IMPORT: if (snaptrace_len) { ... if (ceph_update_snap_trace(mdsc, snaptrace, snaptrace + snaptrace_len, false, &realm)) { ... } ceph_update_snap_trace() then decodes a struct ceph_mds_snap_realm from snaptrace using ceph_decode_need(&p, e, sizeof(*ri), bad) with the attacker-supplied fake end e == snaptrace + snaptrace_len. With snaptrace_len == 0xFFFFFFFF the bound check is trivially satisfied, ri = p reads sizeof(struct ceph_mds_snap_realm) past the legitimate msg->front buffer, and ri->num_snaps / ri->num_prior_parent_snaps then drive further out-of-bounds reads of the encoded snap arrays. The eleven msg_version >= 2 .. msg_version >= 12 decoder blocks above the op switch each catch this OOB through their ceph_decode_*_safe() / ceph_decode_need() helpers, but they sit behind a hdr.version-gated if, so a malicious or compromised MDS that sets msg->hdr.version = 1 reaches the IMPORT path with no version-gated decoder having validated snap_trace_len. The shape has been present since ceph_handle_caps() was introduced. Validate snap_trace_len against the message front buffer before consuming it, using the canonical ceph_decode_need() / ceph_has_room() helper. The helper bounds the length with subtraction (n <= end - p, guarded by end >= p) rather than pointer addition, so it is wrap-safe for the attacker-controlled u32 length on 32-bit builds where p + snap_trace_len could overflow the address space. This matches the rest of the ceph decode path (e.g. the pool_ns_len check a few lines below), and the existing goto bad cleanup already covers this exit path. Cc: stable@vger.kernel.org Fixes: a8599bd821d0 ("ceph: capability management") Signed-off-by: Bryam Vargas Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 4b37d9ffdf7f..77b23fe51425 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -4375,6 +4375,7 @@ void ceph_handle_caps(struct ceph_mds_session *session, snaptrace = h + 1; snaptrace_len = le32_to_cpu(h->snap_trace_len); + ceph_decode_need(&snaptrace, end, snaptrace_len, bad); p = snaptrace + snaptrace_len; if (msg_version >= 2) { From a109a556115271ca7896dcda7b4b7e45e156c227 Mon Sep 17 00:00:00 2001 From: Pavitra Jha Date: Tue, 2 Jun 2026 00:17:35 -0400 Subject: [PATCH 244/456] libceph: fix two unsafe bare decodes in decode_lockers() decode_lockers() in cls_lock_client.c contains two bare decode operations that allow a malicious or compromised OSD to trigger slab-out-of-bounds reads: 1. ceph_decode_32(p) at the num_lockers field has no preceding bounds check. ceph_start_decoding() accepts struct_len=0 as valid -- the internal ceph_decode_need(p, end, 0, bad) always passes -- so when an OSD sends struct_len=0, ceph_start_decoding() returns success with p == end. The immediately following bare ceph_decode_32(p) then reads 4 bytes past the validated buffer boundary. The garbage value is passed directly to kzalloc_objs() as the locker count. The sibling function decode_watchers() in osd_client.c already uses ceph_decode_32_safe() after its own ceph_start_decoding() call. decode_lockers() was the only site using the bare variant. 2. ceph_decode_8(p) after the decode_locker() loop has no preceding bounds check. If an OSD crafts num_lockers such that the loop advances p exactly to end, the subsequent bare ceph_decode_8(p) reads one byte past the validated buffer boundary. The result is passed directly into *type, which is used as a lock type discriminator by callers, giving an OSD-controlled one-byte OOB read with direct influence over the lock type field. Fix both by replacing bare operations with their safe variants: ceph_decode_32(p) -> ceph_decode_32_safe(p, end, *num_lockers, err_inval) ceph_decode_8(p) -> ceph_decode_8_safe(p, end, *type, err_free_lockers) The goto targets differ intentionally: err_inval: is a new label returning -EINVAL directly. It is used for the pre-allocation failure path where *lockers is not yet allocated and must not be passed to ceph_free_lockers(). err_free_lockers: is the existing label. It is used for the post-allocation failure path where *lockers is allocated and must be freed. ret is set to -EINVAL before ceph_decode_8_safe() so that err_free_lockers returns the correct error code on bounds violation. Without this, err_free_lockers would return a stale ret value (0 from the successful decode_locker() loop), silently swallowing the error. -EINVAL is correct for both failure paths. The data received from the OSD is structurally malformed. -ENOMEM would misrepresent the failure class to callers and to stable@ backporters triaging error paths. Attacker model: a malicious or compromised OSD in a multi-tenant Ceph deployment can trigger this against any kernel client that issues the lock.get_info class method (e.g. during RBD exclusive lock acquisition). [ idryomov: trim changelog, formatting ] Cc: stable@vger.kernel.org Fixes: d4ed4a530562 ("libceph: support for lock.lock_info") Signed-off-by: Pavitra Jha Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- net/ceph/cls_lock_client.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/net/ceph/cls_lock_client.c b/net/ceph/cls_lock_client.c index c6956f1df333..377336982f7d 100644 --- a/net/ceph/cls_lock_client.c +++ b/net/ceph/cls_lock_client.c @@ -299,7 +299,7 @@ static int decode_lockers(void **p, void *end, u8 *type, char **tag, if (ret) return ret; - *num_lockers = ceph_decode_32(p); + ceph_decode_32_safe(p, end, *num_lockers, err_inval); *lockers = kzalloc_objs(**lockers, *num_lockers, GFP_NOIO); if (!*lockers) return -ENOMEM; @@ -310,7 +310,8 @@ static int decode_lockers(void **p, void *end, u8 *type, char **tag, goto err_free_lockers; } - *type = ceph_decode_8(p); + ret = -EINVAL; + ceph_decode_8_safe(p, end, *type, err_free_lockers); s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO); if (IS_ERR(s)) { ret = PTR_ERR(s); @@ -320,6 +321,9 @@ static int decode_lockers(void **p, void *end, u8 *type, char **tag, *tag = s; return 0; +err_inval: + return -EINVAL; + err_free_lockers: ceph_free_lockers(*lockers, *num_lockers); return ret; From cbf59617cd715219e84c50d106a3d0e1e8ba054e Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 4 Jun 2026 02:19:51 +0000 Subject: [PATCH 245/456] ceph: fix writeback_count leak in write_folio_nounlock() write_folio_nounlock() increments fsc->writeback_count to track in-flight writeback operations. On several error paths where the function returns early (folio lookup failure, snapshot context allocation failure, and writepages submission failure), the function returns without calling atomic_long_dec_return() to decrement the counter. Each leaked increment keeps the counter above zero, which can prevent the filesystem from cleanly unmounting or suspending writes. Add atomic_long_dec_return() calls on all error paths that currently return without decrementing the counter. Cc: stable@vger.kernel.org Fixes: d55207717ded ("ceph: add encryption support to writepage and writepages") Signed-off-by: Wentao Liang Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- fs/ceph/addr.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index 61bd6d92ff25..ecf33b66610c 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -790,6 +790,9 @@ static int write_folio_nounlock(struct folio *folio, ceph_wbc.truncate_size, true); if (IS_ERR(req)) { folio_redirty_for_writepage(wbc, folio); + if (atomic_long_dec_return(&fsc->writeback_count) < + CONGESTION_OFF_THRESH(fsc->mount_options->congestion_kb)) + fsc->write_congested = false; return PTR_ERR(req); } @@ -809,6 +812,9 @@ static int write_folio_nounlock(struct folio *folio, folio_redirty_for_writepage(wbc, folio); folio_end_writeback(folio); ceph_osdc_put_request(req); + if (atomic_long_dec_return(&fsc->writeback_count) < + CONGESTION_OFF_THRESH(fsc->mount_options->congestion_kb)) + fsc->write_congested = false; return PTR_ERR(bounce_page); } } @@ -847,6 +853,9 @@ static int write_folio_nounlock(struct folio *folio, ceph_vinop(inode), folio); folio_redirty_for_writepage(wbc, folio); folio_end_writeback(folio); + if (atomic_long_dec_return(&fsc->writeback_count) < + CONGESTION_OFF_THRESH(fsc->mount_options->congestion_kb)) + fsc->write_congested = false; return err; } if (err == -EBLOCKLISTED) From d3c32939fa0e3ee9b883b9a0fd1972c5c444e3d0 Mon Sep 17 00:00:00 2001 From: Douya Le Date: Sun, 7 Jun 2026 17:35:49 +0800 Subject: [PATCH 246/456] libceph: bound get_version reply decode to front len handle_get_version_reply() uses msg->front_alloc_len as the decode boundary for MON_GET_VERSION_REPLY. That is the size of the reused reply buffer, not the number of bytes actually received. A truncated reply can therefore pass ceph_decode_need() and decode the second u64 from stale tail bytes left in the buffer by an earlier message, causing an uninitialized memory read. Use msg->front.iov_len as the receive-side decode boundary, matching other libceph reply handlers and limiting decoding to the bytes that were actually read from the wire. Cc: stable@vger.kernel.org Fixes: 513a8243d67f ("libceph: mon_get_version request infrastructure") Reported-by: Yuan Tan Reported-by: Zhengchuan Liang Reported-by: Xin Liu Assisted-by: Codex:GPT-5.4 Signed-off-by: Douya Le Signed-off-by: Ren Wei Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- net/ceph/mon_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ceph/mon_client.c b/net/ceph/mon_client.c index 24acdd580e79..c56457378d00 100644 --- a/net/ceph/mon_client.c +++ b/net/ceph/mon_client.c @@ -821,7 +821,7 @@ static void handle_get_version_reply(struct ceph_mon_client *monc, struct ceph_mon_generic_request *req; u64 tid = le64_to_cpu(msg->hdr.tid); void *p = msg->front.iov_base; - void *end = p + msg->front_alloc_len; + void *const end = p + msg->front.iov_len; u64 handle; dout("%s msg %p tid %llu\n", __func__, msg, tid); From e4c804726c4afce3ba648b982d564f6af2cfa328 Mon Sep 17 00:00:00 2001 From: Douya Le Date: Mon, 15 Jun 2026 14:31:06 +0800 Subject: [PATCH 247/456] libceph: remove debugfs files before client teardown ceph_destroy_client() tears down the monitor client before removing the per-client debugfs files. A concurrent read of the monmap debugfs file can enter monmap_show() after ceph_monc_stop() has freed monc->monmap, triggering a use-after-free. Remove the debugfs files before stopping the OSD and monitor clients. debugfs_remove() drains active handlers and prevents new accesses, so the debugfs callbacks can no longer race the rest of client teardown. Cc: stable@vger.kernel.org Fixes: 76aa844d5b2f ("ceph: debugfs") Reported-by: Yuan Tan Reported-by: Zhengchuan Liang Reported-by: Xin Liu Assisted-by: Codex:GPT-5.4 Signed-off-by: Douya Le Signed-off-by: Ren Wei Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- net/ceph/ceph_common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ceph/ceph_common.c b/net/ceph/ceph_common.c index 952121849180..a797c7360e3c 100644 --- a/net/ceph/ceph_common.c +++ b/net/ceph/ceph_common.c @@ -762,13 +762,13 @@ void ceph_destroy_client(struct ceph_client *client) atomic_set(&client->msgr.stopping, 1); + ceph_debugfs_client_cleanup(client); + /* unmount */ ceph_osdc_stop(&client->osdc); ceph_monc_stop(&client->monc); ceph_messenger_fini(&client->msgr); - ceph_debugfs_client_cleanup(client); - ceph_destroy_options(client->options); kfree(client); From bbeae12fda3384a90fbebc8a19ba9d33f85b5361 Mon Sep 17 00:00:00 2001 From: Zhao Zhang Date: Fri, 19 Jun 2026 15:40:03 +0800 Subject: [PATCH 248/456] libceph: guard missing CRUSH type name lookup Localized read selection can walk a parent bucket whose name exists in the CRUSH map while its type has no matching entry in type_names. get_immediate_parent() then dereferences a NULL type_cn and passes an invalid pointer into strcmp(), causing a null-ptr-deref. Skip such malformed parent buckets unless both the bucket name and type name metadata are present. This keeps malformed hierarchy data from crashing locality lookup and safely falls back to "not local". [ idryomov: add WARN_ON_ONCE ] Cc: stable@vger.kernel.org Fixes: 117d96a04f00 ("libceph: support for balanced and localized reads") Reported-by: Yuan Tan Reported-by: Zhengchuan Liang Reported-by: Xin Liu Assisted-by: Codex:GPT-5.4 Signed-off-by: Zhao Zhang Signed-off-by: Ren Wei Reviewed-by: Viacheslav Dubeyko Signed-off-by: Ilya Dryomov --- net/ceph/osdmap.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index 3c87f4b24e51..04036c047b8c 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -3060,8 +3060,11 @@ static int get_immediate_parent(struct crush_map *c, int id, if (b->items[j] != id) continue; - *parent_type_id = b->type; type_cn = lookup_crush_name(&c->type_names, b->type); + if (WARN_ON_ONCE(!type_cn)) + continue; + + *parent_type_id = b->type; parent_loc->cl_type_name = type_cn->cn_name; parent_loc->cl_name = cn->cn_name; return b->id; From c3e64079d8b9663e3998d0caac9aba915b6b93ae Mon Sep 17 00:00:00 2001 From: WenTao Liang Date: Thu, 11 Jun 2026 22:40:07 +0800 Subject: [PATCH 249/456] ceph: fix refcount leak in ceph_readdir() The ceph_readdir() function allocates a ceph_mds_request via ceph_mdsc_create_request() and stores it in dfi->last_readdir. In the directory entry processing loop, if the entry's offset is less than ctx->pos or if the inode pointer is unexpectedly NULL, the function returns -EIO without releasing the reference held by dfi->last_readdir, causing a refcount leak. Fix this by adding ceph_mdsc_put_request(dfi->last_readdir) before returning on these error paths. Also set dfi->last_readdir to NULL for safety, matching the cleanup done at the normal exit. Cc: stable@vger.kernel.org Fixes: af9ffa6df7e3 ("ceph: add support to readdir for encrypted names") Signed-off-by: WenTao Liang Reviewed-by: Viacheslav Dubeyko Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/dir.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index 27ce9e55e947..ef9e92e362d3 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -546,11 +546,16 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx) pr_warn_client(cl, "%p %llx.%llx rde->offset 0x%llx ctx->pos 0x%llx\n", inode, ceph_vinop(inode), rde->offset, ctx->pos); + ceph_mdsc_put_request(dfi->last_readdir); + dfi->last_readdir = NULL; return -EIO; } - if (WARN_ON_ONCE(!rde->inode.in)) + if (WARN_ON_ONCE(!rde->inode.in)) { + ceph_mdsc_put_request(dfi->last_readdir); + dfi->last_readdir = NULL; return -EIO; + } ctx->pos = rde->offset; doutc(cl, "%p %llx.%llx (%d/%d) -> %llx '%.*s' %p\n", inode, From 937d61f86d377a3aa578adae7a3dfcecdddf9d89 Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Mon, 29 Jun 2026 13:14:22 -0400 Subject: [PATCH 250/456] libceph: refresh auth->authorizer_buf{,_len} after authorizer update ceph_x_create_authorizer() caches au->buf->vec.iov_base and au->buf->vec.iov_len in struct ceph_auth_handshake. These cached values are then used by the messenger connect code when sending the authorizer. ceph_x_update_authorizer() can rebuild the authorizer when a newer service ticket is available. If the rebuilt authorizer no longer fits in the existing buffer, ceph_x_build_authorizer() drops its reference to au->buf and allocates a new one. If this is the final reference, ceph_buffer_put() frees the old ceph_buffer and its vec.iov_base, but auth->authorizer_buf still points at that freed memory. A subsequent msgr1 reconnect can therefore queue the stale pointer and trigger a KASAN slab-use-after-free in _copy_from_iter() while tcp_sendmsg() copies the authorizer. Refresh auth->authorizer_buf and auth->authorizer_buf_len after a successful authorizer rebuild so the messenger sends the current buffer. Cc: stable@vger.kernel.org Fixes: 0bed9b5c523d ("libceph: add update_authorizer auth method") Closes: https://lore.kernel.org/all/E378850E-106C-427B-A241-970EB2D054D7@gmail.com/ Signed-off-by: Shuangpeng Bai Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- net/ceph/auth_x.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/ceph/auth_x.c b/net/ceph/auth_x.c index 9e64e82d0b63..50a79e8aa656 100644 --- a/net/ceph/auth_x.c +++ b/net/ceph/auth_x.c @@ -849,9 +849,16 @@ static int ceph_x_update_authorizer( au = (struct ceph_x_authorizer *)auth->authorizer; if (au->secret_id < th->secret_id) { + int ret; + dout("ceph_x_update_authorizer service %u secret %llu < %llu\n", au->service, au->secret_id, th->secret_id); - return ceph_x_build_authorizer(ac, th, au); + ret = ceph_x_build_authorizer(ac, th, au); + if (ret) + return ret; + + auth->authorizer_buf = au->buf->vec.iov_base; + auth->authorizer_buf_len = au->buf->vec.iov_len; } return 0; } From 9f00f9cf2be293efe899db67dc5272e3a9c62717 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Mon, 8 Jun 2026 21:40:09 -0700 Subject: [PATCH 251/456] libceph: bound pg_{temp,upmap,upmap_items} length to CEPH_PG_MAX_SIZE __decode_pg_temp() decodes an user-controlled length but only rejects values large enough to overflow the allocation; it does not bound it to CEPH_PG_MAX_SIZE. The helper backs both pg_temp and pg_upmap decoding, and apply_upmap()/get_temp_osds() later copy the decoded list into the fixed-size on-stack array struct ceph_osds.osds[CEPH_PG_MAX_SIZE]. A monitor that sends an OSDMap with a pg_temp/pg_upmap entry longer than 32 thus causes a stack out-of-bounds write. An OSD set for a single PG can never exceed CEPH_PG_MAX_SIZE, so reject longer entries at decode time. The bound is well below the old overflow threshold, so it also covers the allocation-size overflow the previous check guarded against. BUG: KASAN: stack-out-of-bounds in ceph_pg_to_up_acting_osds Write of size 4 ... by task exploit kasan_report (mm/kasan/report.c:595) ceph_pg_to_up_acting_osds (net/ceph/osdmap.c:2617 net/ceph/osdmap.c:2833) calc_target (net/ceph/osd_client.c:1638) __submit_request (net/ceph/osd_client.c:2394) ceph_osdc_start_request (net/ceph/osd_client.c:2490) ceph_osdc_call (net/ceph/osd_client.c:5164) rbd_dev_image_probe (drivers/block/rbd.c:6899) do_rbd_add (drivers/block/rbd.c:7138) ... kernel BUG at net/ceph/osdmap.c:2670! [ idryomov: do the same in __decode_pg_upmap_items() ] Cc: stable@vger.kernel.org Fixes: a303bb0e5834 ("libceph: introduce and switch to decode_pg_mapping()") Reported-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Xiang Mei Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- net/ceph/osdmap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index 04036c047b8c..a4b0dd8672ec 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -1438,7 +1438,7 @@ static struct ceph_pg_mapping *__decode_pg_temp(void **p, void *end, ceph_decode_32_safe(p, end, len, e_inval); if (len == 0 && incremental) return NULL; /* new_pg_temp: [] to remove */ - if ((size_t)len > (SIZE_MAX - sizeof(*pg)) / sizeof(u32)) + if (len > CEPH_PG_MAX_SIZE) return ERR_PTR(-EINVAL); ceph_decode_need(p, end, len * sizeof(u32), e_inval); @@ -1619,7 +1619,7 @@ static struct ceph_pg_mapping *__decode_pg_upmap_items(void **p, void *end, u32 len, i; ceph_decode_32_safe(p, end, len, e_inval); - if ((size_t)len > (SIZE_MAX - sizeof(*pg)) / (2 * sizeof(u32))) + if (len > CEPH_PG_MAX_SIZE) return ERR_PTR(-EINVAL); ceph_decode_need(p, end, 2 * len * sizeof(u32), e_inval); From a6c4250b81bd30beae94e1b7a4b26fa1193ad2e4 Mon Sep 17 00:00:00 2001 From: Raphael Zimmer Date: Thu, 9 Jul 2026 13:26:20 +0200 Subject: [PATCH 252/456] rbd: Reset positive result codes to zero in object map update path In a reply message to an RBD request, a positive result code indicates a data payload, which is not allowed for writes. While rbd_osd_req_callback() already resets a positive result code for writes to zero, rbd_object_map_callback() does not. This allows a corrupted reply to an object map update to trigger the rbd_assert(*result < 0) in __rbd_obj_handle_request(). This happens, because rbd_object_map_callback() calls rbd_obj_handle_request() -> __rbd_obj_handle_request() and passes this positive result code. From __rbd_obj_handle_request(), rbd_obj_advance_write() is called, which leaves the positive result code unchanged and returns true. Therefore, the if(done && *result) branch is executed in __rbd_obj_handle_request() and the assertion triggers. This patch fixes the issue by adjusting the logic in the rbd_object_map_callback() path. A positive result code for an object map update is now reset to zero (similar to rbd_osd_req_callback()), and the message is subsequently handled the same way as if the result code was zero from the beginning. Additionally, a WARN_ON_ONCE() is added for this case. Cc: stable@vger.kernel.org Fixes: 22e8bd51bb04 ("rbd: support for object-map and fast-diff") Signed-off-by: Raphael Zimmer Reviewed-by: Ilya Dryomov Signed-off-by: Ilya Dryomov --- drivers/block/rbd.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index ac90d81aa294..1f1c2810f6ee 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -1950,9 +1950,14 @@ static int rbd_object_map_update_finish(struct rbd_obj_request *obj_req, bool has_current_state; void *p; - if (osd_req->r_result) + if (osd_req->r_result < 0) return osd_req->r_result; + /* + * Writes aren't allowed to return a data payload. + */ + WARN_ON_ONCE(osd_req->r_result > 0); + /* * Nothing to do for a snapshot object map. */ From 50958bb928bad3bdba9e5d1b7ff4bbadcf6951e6 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 6 Jul 2026 17:06:59 +0200 Subject: [PATCH 253/456] ceph: fix hanging __ceph_get_caps() with stale mds_wanted A reader can hang forever in __ceph_get_caps() when the client no longer holds `FILE_RD`, but local cap state still says that the capability is already wanted (via `mds_wanted`). One way to trigger this is through MDS cap revocation. If another client performs a conflicting operation, the MDS can revoke `FILE_RD` from the reader; the next read then has to reacquire `FILE_RD`. If the cap update that should request `FILE_RD` never reaches the MDS after `cap->mds_wanted` was raised, the reader is left holding only non-file caps while local `mds_wanted` still includes the file read caps. In that state, try_get_cap_refs() sees `need <= mds_wanted` and returns 0, so __ceph_get_caps() just waits on `i_cap_wq`. If the cap update that was supposed to request `FILE_RD never reaches the MDS after `cap->mds_wanted was` raised, no further request is sent and the waiter can sleep indefinitely until unrelated cap traffic happens to wake it up. The ordering issue is that `cap->mds_wanted` is updated in __prep_cap() before the `CEPH_MSG_CLIENT_CAPS message` is actually queued for send. That makes one field serve two different meanings at once: what this client wants, and what the client believes the MDS already knows it wants. A proper fix would be to split those states and track whether a cap update is actually in flight or has been observed by the MDS. However, simply moving the `cap->mds_wanted assignment` later would not be sufficient: queueing the message in the messenger does not guarantee that the MDS processed that specific wanted set, and reconnect or message loss can still invalidate that assumption. Fixing that properly would require a larger rework of the cap state machine. To allow simpler backports to stable kernels, this patch implements a simpler workaround: - stop waiting forever in __ceph_get_caps(); after a bounded wait, fall back to the renew path - make ceph_renew_caps() issue a synchronous `OPEN` request whenever the inode still does not actually hold the wanted caps, instead of only calling ceph_check_caps() The extra issued-vs-wanted check in ceph_renew_caps() is necessary because the previous test only checked whether the inode still had any real caps at all. That is not enough after revocation: the client can still hold something like `pLs` and yet be missing `FILE_RD` completely. In that case, falling back to ceph_check_caps() is not sufficient, because it still trusts `cap->mds_wanted` and may resend nothing. By requiring `(issued & wanted) == wanted` before taking the asynchronous path, the code only uses ceph_check_caps() when the `wanted caps` are already actually issued. Otherwise, it sends the synchronous `OPEN` renew. This preserves the existing asynchronous fast path when the wanted caps are already issued, avoids changing cap-state semantics, and fixes the hang by guaranteeing that a stalled waiter eventually retries through a path that does not rely on the stale `mds_wanted` state. [ idryomov: move CEPH_GET_CAPS_WAIT_TIMEOUT from libceph.h to mds_client.h, formatting ] Cc: stable@vger.kernel.org Fixes: 0a454bdd501a ("ceph: reorganize __send_cap for less spinlock abuse") Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/caps.c | 17 +++++++++++++++-- fs/ceph/file.c | 9 +++++---- fs/ceph/mds_client.h | 1 + 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c index 77b23fe51425..d7283fb54cec 100644 --- a/fs/ceph/caps.c +++ b/fs/ceph/caps.c @@ -3094,7 +3094,19 @@ int __ceph_get_caps(struct inode *inode, struct ceph_file_info *fi, int need, ret = -ERESTARTSYS; break; } - wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); + + /* + * If a cap update is lost after + * mds_wanted was raised, waiting + * forever will never make progress. + * Retry the renew path periodically + * so we can resend synchronously. + */ + if (!wait_woken(&wait, TASK_INTERRUPTIBLE, + CEPH_GET_CAPS_WAIT_TIMEOUT)) { + ret = -EUCLEAN; + break; + } } remove_wait_queue(&ci->i_cap_wq, &wait); @@ -3128,7 +3140,8 @@ int __ceph_get_caps(struct inode *inode, struct ceph_file_info *fi, int need, continue; } if (ret == -EUCLEAN) { - /* session was killed, try renew caps */ + /* session was killed or a waited cap + * request needs a retry */ ret = ceph_renew_caps(inode, flags); if (ret == 0) continue; diff --git a/fs/ceph/file.c b/fs/ceph/file.c index 71161f2b2151..a4a2a4b6a027 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -314,7 +314,7 @@ static int ceph_init_file(struct inode *inode, struct file *file, int fmode) } /* - * try renew caps after session gets killed. + * Retry cap acquisition after a stale session or a lost cap update. */ int ceph_renew_caps(struct inode *inode, int fmode) { @@ -322,14 +322,15 @@ int ceph_renew_caps(struct inode *inode, int fmode) struct ceph_client *cl = mdsc->fsc->client; struct ceph_inode_info *ci = ceph_inode(inode); struct ceph_mds_request *req; - int err, flags, wanted; + int err, flags, wanted, issued; spin_lock(&ci->i_ceph_lock); __ceph_touch_fmode(ci, mdsc, fmode); wanted = __ceph_caps_file_wanted(ci); + issued = __ceph_caps_issued(ci, NULL); if (__ceph_is_any_real_caps(ci) && - (!(wanted & CEPH_CAP_ANY_WR) || ci->i_auth_cap)) { - int issued = __ceph_caps_issued(ci, NULL); + (!(wanted & CEPH_CAP_ANY_WR) || ci->i_auth_cap) && + (issued & wanted) == wanted) { spin_unlock(&ci->i_ceph_lock); doutc(cl, "%p %llx.%llx want %s issued %s updating mds_wanted\n", inode, ceph_vinop(inode), ceph_cap_string(wanted), diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h index 731d6ad04956..0ece4c9e3529 100644 --- a/fs/ceph/mds_client.h +++ b/fs/ceph/mds_client.h @@ -77,6 +77,7 @@ struct ceph_fs_client; struct ceph_cap; #define MDS_AUTH_UID_ANY -1 +#define CEPH_GET_CAPS_WAIT_TIMEOUT (5 * HZ) #define CEPH_CAP_FLUSH_WAIT_TIMEOUT_SEC 60 #define CEPH_CAP_FLUSH_MAX_DUMP_ENTRIES 5 #define CEPH_CAP_FLUSH_MAX_DUMP_ITERS 5 From cee38bbf5556a8e0a232ccae41649580827d7806 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 21 Jul 2026 08:20:46 +0200 Subject: [PATCH 254/456] ceph: add owner/capability checks for CEPH_IOC_SET_LAYOUT* These permission checks were already missing in the initial impementation of these ioctls. This Ceph allows any user who owns a file descriptor to manipulate the layout of any file, even if they don't have write permissions. It might be a good idea to guard other ioctls with permission checks as well or even disallow regular users (even if they own the file) to manipulate layout settings completely, as this may be abused to DoS the Ceph servers, but right now, I find it most urgent to have setter checks at all. Cc: stable@vger.kernel.org Fixes: 8f4e91dee2a2 ("ceph: ioctls") Signed-off-by: Max Kellermann Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- fs/ceph/ioctl.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/ceph/ioctl.c b/fs/ceph/ioctl.c index 15cde055f3da..de07f19b0caa 100644 --- a/fs/ceph/ioctl.c +++ b/fs/ceph/ioctl.c @@ -72,6 +72,9 @@ static long ceph_ioctl_set_layout(struct file *file, void __user *arg) struct ceph_ioctl_layout nl; int err; + if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) + return -EACCES; + if (copy_from_user(&l, arg, sizeof(l))) return -EFAULT; @@ -142,6 +145,9 @@ static long ceph_ioctl_set_layout_policy (struct file *file, void __user *arg) int err; struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc; + if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) + return -EACCES; + /* copy and validate */ if (copy_from_user(&l, arg, sizeof(l))) return -EFAULT; From 5b602344a49e039e792ce5a8923bcc61412ee134 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 22 Jul 2026 13:49:31 +0200 Subject: [PATCH 255/456] ceph: avoid fs reclaim while using current->journal_info handle_reply() stores a `ceph_mds_request` pointer in `current->journal_info` while filling the inode and dentry cache from an MDS reply. An allocation in this section can enter direct reclaim and prune dentries from another filesystem. If this dirties an ext4 inode, ext4 starts a JBD2 transaction. JBD2 interprets the Ceph request in `current->journal_info` as a journal handle and dereferences the request's `r_tid` as `h_transaction`, causing a kernel crash, e.g.: Unable to handle kernel paging request at virtual address 00000000077b4818 [...] Internal error: Oops: 0000000096000004 [#1] SMP Modules linked in: CPU: 6 UID: 0 PID: 2699135 Comm: kworker/6:3 Tainted: G W 6.18.38-i3 #1113 NONE [...] Workqueue: ceph-msgr ceph_con_workfn pstate: 80400009 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : jbd2__journal_start+0x2c/0x208 lr : __ext4_journal_start_sb+0x100/0x178 [...] Call trace: jbd2__journal_start+0x2c/0x208 (P) __ext4_journal_start_sb+0x100/0x178 ext4_dirty_inode+0x3c/0x90 __mark_inode_dirty+0x58/0x400 iput.part.0+0x2b0/0x370 iput+0x18/0x30 dentry_unlink_inode+0xc0/0x158 __dentry_kill+0x80/0x250 shrink_dentry_list+0x90/0x130 prune_dcache_sb+0x60/0x98 super_cache_scan+0xe8/0x190 do_shrink_slab+0x174/0x388 shrink_slab+0xd8/0x4c0 shrink_node+0x31c/0x908 do_try_to_free_pages+0xd0/0x508 try_to_free_pages+0x11c/0x238 __alloc_frozen_pages_noprof+0x4d0/0xdd0 __folio_alloc_noprof+0x18/0x70 __filemap_get_folio+0x248/0x440 ceph_readdir_prepopulate+0x570/0x9e8 mds_dispatch+0x1424/0x1ba0 ceph_con_process_message+0x74/0xa0 ceph_con_v1_try_read+0x3a0/0x1510 ceph_con_workfn+0x260/0x460 Enter a scoped NOFS allocation context and leave it after clearing `journal_info`. This prevents filesystem reclaim from recursing into another filesystem while the field contains Ceph-private data. Cc: stable@vger.kernel.org Fixes: 315f24088048 ("ceph: fix security xattr deadlock") Signed-off-by: Max Kellermann Reviewed-by: Viacheslav Dubeyko Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 853bf698b356..3c692ad02c85 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -4015,6 +4016,7 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg) struct ceph_mds_reply_head *head = msg->front.iov_base; struct ceph_mds_reply_info_parsed *rinfo; /* parsed reply info */ struct ceph_snap_realm *realm; + unsigned int nofs_flags; u64 tid; int err, result; int mds = session->s_mds; @@ -4158,6 +4160,14 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg) /* insert trace into our cache */ mutex_lock(&req->r_fill_mutex); + + /* disable fs reclaim while we are using current->journal_info + * for our own purposes, or else shrinkers of other + * filesystems might dereference this pointer as a different + * type + */ + nofs_flags = memalloc_nofs_save(); + current->journal_info = req; err = ceph_fill_trace(mdsc->fsc->sb, req); if (err == 0) { @@ -4166,6 +4176,7 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg) err = ceph_readdir_prepopulate(req, req->r_session); } current->journal_info = NULL; + memalloc_nofs_restore(nofs_flags); mutex_unlock(&req->r_fill_mutex); up_read(&mdsc->snap_rwsem); From 78f75d632f74b8de0f081a128588f7c37d0d1164 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Wed, 22 Jul 2026 14:02:03 -0700 Subject: [PATCH 256/456] rds: tcp: hold the RCU lock across ipv6_chk_addr() in rds_tcp_laddr_check() rds_tcp_laddr_check() looks up a scoped IPv6 interface with dev_get_by_index_rcu(), drops the RCU read-side lock, and only then passes the bare struct net_device * into ipv6_chk_addr(). dev_get_by_index_rcu() only keeps the device alive within the same RCU read-side section. After rcu_read_unlock(), a concurrent RTM_DELLINK can free the net_device; ipv6_chk_addr() then dereferences the stale pointer in __ipv6_chk_addr_and_flags() (e.g. l3mdev_master_dev_rcu(dev)), reading freed memory. Keep the RCU read-side lock held across the ipv6_chk_addr() call instead of dropping it right after the lookup, so the device cannot be freed while it is in use. BUG: KASAN: slab-use-after-free in __ipv6_chk_addr_and_flags (... net/ipv6/addrconf.c:1998) Read of size 8 at addr ffff8880106ec000 by task exploit/153 Call Trace: ... kasan_report (mm/kasan/report.c:595) __ipv6_chk_addr_and_flags (... net/ipv6/addrconf.c:1998) ipv6_chk_addr (net/ipv6/addrconf.c:2031 net/ipv6/addrconf.c:1972) rds_tcp_laddr_check (net/rds/tcp.c:370) rds_bind (net/rds/bind.c:248) __sys_bind (net/socket.c:1920) __x64_sys_bind (net/socket.c:1956) do_syscall_64 (arch/x86/entry/syscall_64.c:63) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Fixes: eee2fa6ab322 ("rds: Changing IP address internal representation to struct in6_addr") Reported-by: Weiming Shi Signed-off-by: Xiang Mei Reviewed-by: Allison Henderson Link: https://patch.msgid.link/20260722210203.565803-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- net/rds/tcp.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/rds/tcp.c b/net/rds/tcp.c index 5de35d556f29..b263634ac750 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -355,23 +355,25 @@ int rds_tcp_laddr_check(struct net *net, const struct in6_addr *addr, /* If the scope_id is specified, check only those addresses * hosted on the specified interface. */ + rcu_read_lock(); if (scope_id != 0) { - rcu_read_lock(); dev = dev_get_by_index_rcu(net, scope_id); /* scope_id is not valid... */ if (!dev) { rcu_read_unlock(); return -EADDRNOTAVAIL; } - rcu_read_unlock(); } #if IS_ENABLED(CONFIG_IPV6) if (ipv6_mod_enabled()) { ret = ipv6_chk_addr(net, addr, dev, 0); - if (ret) + if (ret) { + rcu_read_unlock(); return 0; + } } #endif + rcu_read_unlock(); return -EADDRNOTAVAIL; } From 6273dd3ffb54ec581855b82ae77331b66028249c Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 20 Jul 2026 16:44:26 +0800 Subject: [PATCH 257/456] drm/vc4: Shut down BO cache timer before teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The BO cache timer callback schedules time_work, and time_work can rearm the timer through vc4_bo_cache_free_old(). vc4_bo_cache_destroy() deletes the timer and then cancels the work, which does not break that cycle: the work being cancelled can rearm the timer, and the timer then queues work again after teardown. Use timer_shutdown_sync() instead, so the timer cannot be rearmed and the cycle ends with cancel_work_sync(). Fixes: c826a6e10644 ("drm/vc4: Add a BO cache.") Cc: stable@vger.kernel.org Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260720084426.1632508-1-lilinmao@kylinos.cn Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_bo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 5e7c46dd7823..49ea2ed0996b 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -1048,7 +1048,7 @@ static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused) struct vc4_dev *vc4 = to_vc4_dev(dev); int i; - timer_delete(&vc4->bo_cache.time_timer); + timer_shutdown_sync(&vc4->bo_cache.time_timer); cancel_work_sync(&vc4->bo_cache.time_work); vc4_bo_cache_purge(dev); From 289e680c89ae8a0bb629fa8308313f5c8c6c76a3 Mon Sep 17 00:00:00 2001 From: Amery Hung Date: Thu, 23 Jul 2026 15:18:14 -0700 Subject: [PATCH 258/456] bpf: Reject passing scalar NULL to nonnull arg of a global subprog A global subprogram argument tagged __arg_nonnull is set up as a non-nullable PTR_TO_MEM. However the verifier does not check against a scalar NULL, leading to real NULL pointer dereference. Reject it as well. Fixes: 94e1c70a3452 ("bpf: support 'arg:xxx' btf_decl_tag-based hints for global subprog args") Signed-off-by: Amery Hung Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260723221815.367797-1-ameryhung@gmail.com Signed-off-by: Eduard Zingerman --- kernel/bpf/verifier.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 99444eae917e..7aa47342dc65 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9189,7 +9189,8 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog, return ret; if (check_mem_reg(env, reg, argno, arg->mem_size)) return -EINVAL; - if (!(arg->arg_type & PTR_MAYBE_NULL) && (reg->type & PTR_MAYBE_NULL)) { + if (!(arg->arg_type & PTR_MAYBE_NULL) && + (type_may_be_null(reg->type) || bpf_register_is_null(reg))) { bpf_log(log, "%s is expected to be non-NULL\n", reg_arg_name(env, argno)); return -EINVAL; From 55c7bd2ddee50fd37b3b4c0b7a76bb0fd565f38b Mon Sep 17 00:00:00 2001 From: Amery Hung Date: Thu, 23 Jul 2026 15:18:15 -0700 Subject: [PATCH 259/456] selftests/bpf: Test passing scalar NULL to nonnull global subprog Make sure the verifier reject passing a hardcoded NULL to an __arg_nonnull argument. Signed-off-by: Amery Hung Link: https://patch.msgid.link/20260723221815.367797-2-ameryhung@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/progs/verifier_global_subprogs.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c index 75a2e3f48d0f..67dc352addfd 100644 --- a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c +++ b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c @@ -185,6 +185,16 @@ int arg_tag_nonnull_ptr_good(void *ctx) return subprog_nonnull_ptr_good(&x, &y); } +SEC("?raw_tp") +__failure __log_level(2) +__msg("R1 is expected to be non-NULL") +int arg_tag_nonnull_ptr_null_bad(void *ctx) +{ + int y = 74; + + return subprog_nonnull_ptr_good(NULL, &y); +} + /* this global subprog can be now called from many types of entry progs, each * with different context type */ From 1ff399c4cd132a24c73e5e237a11cb9d6b68dff1 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Fri, 24 Jul 2026 08:02:14 +0200 Subject: [PATCH 260/456] Revert "drm/pagemap: Guard HPAGE_PMD_ORDER use with CONFIG_ARCH_ENABLE_THP_MIGRATION" This reverts commit 04b177544a040cbafab760d6b766381c6b22e0a8. The original author requested it to be reverted, as it conflicts with changes in the -next branch for MM: "I'm not sure who is doing the drm-misc-fixes PR, but if you are can you omit this patch: https://patchwork.freedesktop.org/series/170865/ I guess this conflicts with MM changes in their next tree and it easy enough on our side to do this slightly differently to avoid a conflict so going to post revert + a different change. If this is already sent nbd." Signed-off-by: Maarten Lankhorst --- drivers/gpu/drm/drm_pagemap.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 4a794544b7dc..892b325fa99b 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -12,12 +12,6 @@ #include #include -#if IS_ENABLED(CONFIG_ARCH_ENABLE_THP_MIGRATION) -#define DRM_PAGEMAP_PMD_ORDER HPAGE_PMD_ORDER -#else -#define DRM_PAGEMAP_PMD_ORDER (-1) -#endif - /** * DOC: Overview * @@ -585,7 +579,7 @@ static int drm_pagemap_cpages(unsigned long *migrate_pfn, unsigned long npages) order = folio_order(folio); cpages += NR_PAGES(order); } else if (migrate_pfn[i] & MIGRATE_PFN_COMPOUND) { - order = DRM_PAGEMAP_PMD_ORDER; + order = HPAGE_PMD_ORDER; cpages += NR_PAGES(order); } @@ -773,11 +767,10 @@ int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, if (migrate.src[i] & MIGRATE_PFN_COMPOUND) { drm_WARN_ONCE(dpagemap->drm, src_page && - folio_order(page_folio(src_page)) != - DRM_PAGEMAP_PMD_ORDER, + folio_order(page_folio(src_page)) != HPAGE_PMD_ORDER, "Unexpected folio order\n"); - order = DRM_PAGEMAP_PMD_ORDER; + order = HPAGE_PMD_ORDER; migrate.dst[i] |= MIGRATE_PFN_COMPOUND; for (j = 1; j < NR_PAGES(order) && i + j < npages; j++) From c1d04c1bce98f9dd984a9c6657278a7761854c9c Mon Sep 17 00:00:00 2001 From: Mateusz Guzik Date: Thu, 23 Jul 2026 18:01:13 +0200 Subject: [PATCH 261/456] pidfs: make pidfs_ino_lock static Fixes: 87caaeef7995 ("pidfs: implement ino allocation without the pidmap lock") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607231547.ehCQxi0L-lkp@intel.com/ Signed-off-by: Mateusz Guzik Link: https://patch.msgid.link/20260723160114.291515-1-mjguzik@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/pidfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/pidfs.c b/fs/pidfs.c index d7fe9abdd6f1..b57ecc96e967 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -108,7 +108,7 @@ struct pidfs_attr { #if BITS_PER_LONG == 32 -DEFINE_SPINLOCK(pidfs_ino_lock); +static DEFINE_SPINLOCK(pidfs_ino_lock); static u64 pidfs_ino_nr = 1; static inline unsigned long pidfs_ino(u64 ino) From 47e20d4b3da97ef3881d1e55e43545c22424f3fc Mon Sep 17 00:00:00 2001 From: Pu Lehui Date: Fri, 24 Jul 2026 16:33:08 +0800 Subject: [PATCH 262/456] LoongArch: BPF: Fix memory leak in bpf_jit_free() When bpf_int_jit_compile() is called for subprograms, it returns early during the first pass (!prog->is_func || extra_pass is false), keeping ctx->offset alive for the subsequent extra pass. If JIT compilation fails for a later subprogram, the BPF core aborts and calls bpf_jit_free() to clean up the first subprogram. However, bpf_jit_free() fails to free jit_data->ctx.offset, which causes a memory leak of the JIT context offsets array. So fix this by adding the missing kvfree(jit_data->ctx.offset) in bpf_jit_free(). Reported-by: Sashiko Fixes: 4ab17e762b34 ("LoongArch: BPF: Use BPF prog pack allocator") Acked-by: Tiezhu Yang Signed-off-by: Pu Lehui Signed-off-by: Huacai Chen --- arch/loongarch/net/bpf_jit.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index c91d474faba7..29c281bef28e 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -2361,6 +2361,7 @@ void bpf_jit_free(struct bpf_prog *prog) */ if (jit_data) { bpf_jit_binary_pack_finalize(jit_data->ro_header, jit_data->header); + kvfree(jit_data->ctx.offset); kfree(jit_data); } hdr = bpf_jit_binary_pack_hdr(prog); From cf4dd800e49d35d48ebd63d511a6e200f39176f7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 24 Jul 2026 16:33:14 +0800 Subject: [PATCH 263/456] platform/loongarch: laptop: Stop setting acpi_device_class() The driver populates acpi_device_class() which is never read afterward, so make it stop doing that and drop the symbol defined specifically for this purpose. No intentional functional impact. This change will facilitate the removal of "device_class" from "struct acpi_device_pnp" in the future. Reviewed-by: Andy Shevchenko Signed-off-by: Rafael J. Wysocki Signed-off-by: Huacai Chen --- drivers/platform/loongarch/loongson-laptop.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/platform/loongarch/loongson-laptop.c b/drivers/platform/loongarch/loongson-laptop.c index 61b18ac206c9..f2dcc6d78051 100644 --- a/drivers/platform/loongarch/loongson-laptop.c +++ b/drivers/platform/loongarch/loongson-laptop.c @@ -30,7 +30,6 @@ #define LOONGSON_ACPI_HKEY_HID "LOON0000" #define ACPI_LAPTOP_NAME "loongson-laptop" -#define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson" #define MAX_ACPI_ARGS 3 #define GENERIC_HOTKEY_MAP_MAX 64 @@ -167,8 +166,6 @@ static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver) } sub_driver->device->driver_data = sub_driver; - sprintf(acpi_device_class(sub_driver->device), "%s/%s", - ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name); status = acpi_install_notify_handler(*sub_driver->handle, sub_driver->type, dispatch_acpi_notify, sub_driver); From 91a70492c03040d51b36f595530d6491d5d6c541 Mon Sep 17 00:00:00 2001 From: Zixing Liu Date: Fri, 24 Jul 2026 16:33:14 +0800 Subject: [PATCH 264/456] platform/loongarch: laptop: Explicitly reset bl_powered state when suspend On EAECIS NL60R with EC firmware version 1.11, resuming from S3 has a very high chance (>90%) of causing the EC to lose the previous backlight power state. When this happens, the laptop resumes normally from S3, but the backlight remains off (when shining on the screen with a flash light, we can see the screen contents are updating normally). Since there is no generic way to query the EC's backlight state on Loongson laptop platforms, assume the worst-case scenario and restart the backlight power inside the kernel each time the system resumes. Cc: stable@vger.kernel.org Fixes: 53c762b47f72 ("platform/loongarch: laptop: Add backlight power control support") Tested-by: Yao Zi Tested-by: Xi Ruoyao Signed-off-by: Zixing Liu Signed-off-by: Huacai Chen --- drivers/platform/loongarch/loongson-laptop.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/loongarch/loongson-laptop.c b/drivers/platform/loongarch/loongson-laptop.c index f2dcc6d78051..4ec56621db24 100644 --- a/drivers/platform/loongarch/loongson-laptop.c +++ b/drivers/platform/loongarch/loongson-laptop.c @@ -186,6 +186,7 @@ static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver) static int loongson_hotkey_suspend(struct device *dev) { + bl_powered = false; return 0; } From 8fa01be5a6149404adb82c0979a78f6347edd3ef Mon Sep 17 00:00:00 2001 From: Farhan Ali Date: Thu, 23 Jul 2026 15:14:04 -0700 Subject: [PATCH 265/456] KVM: s390: pci: Reject adapter interrupt forwarding if already enabled The MPCIFC instruction doesn't allow registering adapter interrupts without first unregistering. So reject any request to enable interrupt forwarding if its already enabled for the zPCI device. This also fixes overwriting and thus leaking resources when the ioctl is called multiple times for the same device. Fixes: 3c5a1b6f0a18 ("KVM: s390: pci: provide routines for enabling/disabling interrupt forwarding") Cc: stable@vger.kernel.org Reviewed-by: Christian Borntraeger Reviewed-by: Matthew Rosato Signed-off-by: Farhan Ali Tested-by: Matthew Rosato Signed-off-by: Christian Borntraeger --- arch/s390/kvm/pci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/s390/kvm/pci.c b/arch/s390/kvm/pci.c index 720bb58cabe2..d2a11cdf6941 100644 --- a/arch/s390/kvm/pci.c +++ b/arch/s390/kvm/pci.c @@ -237,6 +237,10 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, if (zdev->gisa == 0) return -EINVAL; + /* AIF already enabled for the device */ + if (zdev->kzdev->fib.fmt0.aibv != 0) + return -EINVAL; + kvm = zdev->kzdev->kvm; msi_vecs = min_t(unsigned int, fib->fmt0.noi, zdev->max_msi); From 36f6999ecde3976731a8bfc0b8e667da6f593069 Mon Sep 17 00:00:00 2001 From: Farhan Ali Date: Thu, 23 Jul 2026 15:14:05 -0700 Subject: [PATCH 266/456] KVM: s390: pci: Fix memory accounting for pinned/unpinned pages The account_mem() and unaccount_mem() functions call get_uid() which increments the reference count of struct user_struct on every invocation. But we don't decrement the count by calling free_uid(). It also accounted/unaccounted the pages against the current->mm. But its possible the unaccount_mem() can be called from a different process context than the one that originally pinned the pages. Let's fix this by storing the pinning process user_struct and mm_struct when accounting for pinned pages, and subsequently free these resources when the pages are unpinned. Fixes: 3c5a1b6f0a18 ("KVM: s390: pci: provide routines for enabling/disabling interrupt forwarding") Cc: stable@vger.kernel.org Reviewed-by: Christian Borntraeger Reviewed-by: Matthew Rosato Signed-off-by: Farhan Ali Tested-by: Matthew Rosato [borntraeger@linux.ibm.com: Fixed whitespace] Signed-off-by: Christian Borntraeger --- arch/s390/kvm/pci.c | 43 ++++++++++++++++++++++++++++++++----------- arch/s390/kvm/pci.h | 2 ++ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/arch/s390/kvm/pci.c b/arch/s390/kvm/pci.c index d2a11cdf6941..0741aed442bc 100644 --- a/arch/s390/kvm/pci.c +++ b/arch/s390/kvm/pci.c @@ -190,33 +190,54 @@ static int kvm_zpci_clear_airq(struct zpci_dev *zdev) return cc ? -EIO : 0; } -static inline void unaccount_mem(unsigned long nr_pages) +static inline void unaccount_mem(struct kvm_zdev *kzdev, unsigned long nr_pages) { - struct user_struct *user = get_uid(current_user()); + struct user_struct *user = kzdev->user_account; + struct mm_struct *mm_account = kzdev->mm_account; - if (user) + if (user) { atomic_long_sub(nr_pages, &user->locked_vm); - if (current->mm) - atomic64_sub(nr_pages, ¤t->mm->pinned_vm); + free_uid(user); + kzdev->user_account = NULL; + } + + if (mm_account) { + atomic64_sub(nr_pages, &mm_account->pinned_vm); + mmdrop(mm_account); + kzdev->mm_account = NULL; + } } -static inline int account_mem(unsigned long nr_pages) +static inline int account_mem(struct kvm_zdev *kzdev, unsigned long nr_pages) { struct user_struct *user = get_uid(current_user()); unsigned long page_limit, cur_pages, new_pages; + int rc = 0; page_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; cur_pages = atomic_long_read(&user->locked_vm); do { new_pages = cur_pages + nr_pages; - if (new_pages > page_limit) - return -ENOMEM; + if (new_pages > page_limit) { + rc = -ENOMEM; + goto out; + } } while (!atomic_long_try_cmpxchg(&user->locked_vm, &cur_pages, new_pages)); - atomic64_add(nr_pages, ¤t->mm->pinned_vm); + if (current->mm) { + mmgrab(current->mm); + atomic64_add(nr_pages, ¤t->mm->pinned_vm); + } + + kzdev->user_account = user; + kzdev->mm_account = current->mm; return 0; + +out: + free_uid(user); + return rc; } static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, @@ -279,7 +300,7 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, } /* Account for pinned pages, roll back on failure */ - if (account_mem(pcount)) + if (account_mem(zdev->kzdev, pcount)) goto unpin2; /* AISB must be allocated before we can fill in GAITE */ @@ -400,7 +421,7 @@ static int kvm_s390_pci_aif_disable(struct zpci_dev *zdev, bool force) pcount++; } if (pcount > 0) - unaccount_mem(pcount); + unaccount_mem(kzdev, pcount); out: mutex_unlock(&aift->aift_lock); diff --git a/arch/s390/kvm/pci.h b/arch/s390/kvm/pci.h index ff0972dd5e71..fdf8c7bf4ed0 100644 --- a/arch/s390/kvm/pci.h +++ b/arch/s390/kvm/pci.h @@ -22,6 +22,8 @@ struct kvm_zdev { struct kvm *kvm; struct zpci_fib fib; struct list_head entry; + struct user_struct *user_account; + struct mm_struct *mm_account; }; struct zpci_gaite { From f86842e4d6c482300f4567f492d512c9ccf5bc4f Mon Sep 17 00:00:00 2001 From: Farhan Ali Date: Thu, 23 Jul 2026 15:14:06 -0700 Subject: [PATCH 267/456] KVM: s390: pci: Fix missing error codes and memory unaccounting In kvm_s390_pci_aif_enable() two error paths failed to set an error code, causing the function to return 0 on failure. It also failed to rollback memory accounting on failure. Fix both by propagating an error code on failure and calling unaccount_mem() in the cleanup path. Fixes: 3c5a1b6f0a18 ("KVM: s390: pci: provide routines for enabling/disabling interrupt forwarding") Cc: stable@vger.kernel.org Reviewed-by: Christian Borntraeger Reviewed-by: Matthew Rosato Signed-off-by: Farhan Ali Tested-by: Matthew Rosato Signed-off-by: Christian Borntraeger --- arch/s390/kvm/pci.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/arch/s390/kvm/pci.c b/arch/s390/kvm/pci.c index 0741aed442bc..36eb30953bb5 100644 --- a/arch/s390/kvm/pci.c +++ b/arch/s390/kvm/pci.c @@ -300,14 +300,17 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, } /* Account for pinned pages, roll back on failure */ - if (account_mem(zdev->kzdev, pcount)) + rc = account_mem(zdev->kzdev, pcount); + if (rc) goto unpin2; /* AISB must be allocated before we can fill in GAITE */ mutex_lock(&aift->aift_lock); bit = airq_iv_alloc_bit(aift->sbv); - if (bit == -1UL) + if (bit == -1UL) { + rc = -ENOMEM; goto unlock; + } zdev->aisb = bit; /* store the summary bit number */ zdev->aibv = airq_iv_create(msi_vecs, AIRQ_IV_DATA | AIRQ_IV_BITLOCK | @@ -351,6 +354,8 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, return rc; unlock: + if (pcount > 0) + unaccount_mem(zdev->kzdev, pcount); mutex_unlock(&aift->aift_lock); unpin2: if (fib->fmt0.sum == 1) From 8bf09b9b7d3232806df95f409581f8a9fd99a3fa Mon Sep 17 00:00:00 2001 From: Farhan Ali Date: Thu, 23 Jul 2026 15:14:07 -0700 Subject: [PATCH 268/456] KVM: s390: pci: Fix NULL dereference on AIBV allocation failure The airq_iv_create() can return NULL on failure, but the return value was never checked. If it fails, zdev->aibv will be NULL and fail when dereferenced in kvm_zpci_set_airq(). Add a NULL check and free the previously allocated AISB bit and zdev->aisb on failure. Fixes: 3c5a1b6f0a18 ("KVM: s390: pci: provide routines for enabling/disabling interrupt forwarding") Cc: stable@vger.kernel.org Reviewed-by: Christian Borntraeger Reviewed-by: Matthew Rosato Signed-off-by: Farhan Ali Tested-by: Matthew Rosato Signed-off-by: Christian Borntraeger --- arch/s390/kvm/pci.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/s390/kvm/pci.c b/arch/s390/kvm/pci.c index 36eb30953bb5..1eb127fc9f89 100644 --- a/arch/s390/kvm/pci.c +++ b/arch/s390/kvm/pci.c @@ -317,6 +317,11 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, AIRQ_IV_GUESTVEC, phys_to_virt(fib->fmt0.aibv)); + if (!zdev->aibv) { + rc = -ENOMEM; + goto free_aisb; + } + spin_lock_irq(&aift->gait_lock); gaite = aift->gait + zdev->aisb; @@ -353,6 +358,9 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, rc = kvm_zpci_set_airq(zdev); return rc; +free_aisb: + airq_iv_free_bit(aift->sbv, zdev->aisb); + zdev->aisb = 0; unlock: if (pcount > 0) unaccount_mem(zdev->kzdev, pcount); From 5580c9858f1e00f60191eb09c3add359836d60b6 Mon Sep 17 00:00:00 2001 From: Farhan Ali Date: Thu, 23 Jul 2026 15:14:08 -0700 Subject: [PATCH 269/456] KVM: s390: pci: Fix resource leak on IRQ registration failure Currently if kvm_zpci_set_airq() fails, kvm_s390_pci_aif_enable() returns the error code but doesn't do any resource cleanup thus leaking resources. Fix this by cleaning up all the resources such as the GAITE, AIBV, AISB and unpinning any pinned pages. While at it, remove dead code that stored FIB values that were never referenced. As part of the cleanup, we are also holding the aift_lock a bit longer, as we hold the lock while executing the MPCIFC instruction. Though this is not strictly necessary, it means we don't have to drop and re-acquire in the error case. Fixes: 3c5a1b6f0a18 ("KVM: s390: pci: provide routines for enabling/disabling interrupt forwarding") Cc: stable@vger.kernel.org Reviewed-by: Matthew Rosato Reviewed-by: Christian Borntraeger Signed-off-by: Farhan Ali Tested-by: Matthew Rosato Signed-off-by: Christian Borntraeger --- arch/s390/kvm/pci.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/arch/s390/kvm/pci.c b/arch/s390/kvm/pci.c index 1eb127fc9f89..50f495bc8303 100644 --- a/arch/s390/kvm/pci.c +++ b/arch/s390/kvm/pci.c @@ -344,19 +344,32 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, aift->kzdev[zdev->aisb] = zdev->kzdev; spin_unlock_irq(&aift->gait_lock); - /* Update guest FIB for re-issue */ - fib->fmt0.aisbo = zdev->aisb & 63; - fib->fmt0.aisb = virt_to_phys(aift->sbv->vector) + (zdev->aisb / 64) * 8; - fib->fmt0.isc = gisc; - /* Save some guest fib values in the host for later use */ - zdev->kzdev->fib.fmt0.isc = fib->fmt0.isc; + zdev->kzdev->fib.fmt0.isc = gisc; zdev->kzdev->fib.fmt0.aibv = fib->fmt0.aibv; - mutex_unlock(&aift->aift_lock); /* Issue the clp to setup the irq now */ rc = kvm_zpci_set_airq(zdev); - return rc; + if (!rc) { + mutex_unlock(&aift->aift_lock); + return rc; + } + + /* Start cleanup */ + zdev->kzdev->fib.fmt0.isc = 0; + zdev->kzdev->fib.fmt0.aibv = 0; + + spin_lock_irq(&aift->gait_lock); + gaite->count--; + gaite->aisb = 0; + gaite->gisc = 0; + gaite->aisbo = 0; + gaite->gisa = 0; + aift->kzdev[zdev->aisb] = NULL; + spin_unlock_irq(&aift->gait_lock); + + airq_iv_release(zdev->aibv); + zdev->aibv = NULL; free_aisb: airq_iv_free_bit(aift->sbv, zdev->aisb); From 868d32ac72cba21c5c6d8a66a814b7c25a3a5c01 Mon Sep 17 00:00:00 2001 From: Farhan Ali Date: Thu, 23 Jul 2026 15:14:09 -0700 Subject: [PATCH 270/456] KVM: s390: pci: Validate AIBV and AISB before pinning guest pages The AIBV holds one bit per MSI-X vector for a given function. The size of the bit vector is derived from the NOI and the AIBVO. If the size of the AIBV exceeds a single page boundary, then reject the request as we cannot safely pin the guest AIBV. Similarly reject the request if the AISB address is not 8-byte aligned as the architecture requires doubleword alignment for the summary bit address. Since the AISBO can address up to 64 bits, the size of the AISB can only be 8 bytes for the function. This also ensures the AISB doesn't exceed a single page boundary. Fixes: 3c5a1b6f0a18 ("KVM: s390: pci: provide routines for enabling/disabling interrupt forwarding") Cc: stable@vger.kernel.org Reviewed-by: Christian Borntraeger Reviewed-by: Matthew Rosato Signed-off-by: Farhan Ali Tested-by: Matthew Rosato Signed-off-by: Christian Borntraeger --- arch/s390/kvm/pci.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/arch/s390/kvm/pci.c b/arch/s390/kvm/pci.c index 50f495bc8303..50f5ec79600e 100644 --- a/arch/s390/kvm/pci.c +++ b/arch/s390/kvm/pci.c @@ -244,7 +244,7 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, bool assist) { struct page *pages[1], *aibv_page, *aisb_page = NULL; - unsigned int msi_vecs, idx; + unsigned int msi_vecs, idx, size; struct zpci_gaite *gaite; unsigned long hva, bit; struct kvm *kvm; @@ -271,6 +271,14 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, return gisc; /* Replace AIBV address */ + size = BITS_TO_LONGS(msi_vecs + fib->fmt0.aibvo) * sizeof(unsigned long); + npages = DIV_ROUND_UP((fib->fmt0.aibv & ~PAGE_MASK) + size, PAGE_SIZE); + /* AIBV cannot span more than 1 page */ + if (npages > 1) { + rc = -EINVAL; + goto out; + } + idx = srcu_read_lock(&kvm->srcu); hva = gfn_to_hva(kvm, gpa_to_gfn((gpa_t)fib->fmt0.aibv)); npages = pin_user_pages_fast(hva, 1, FOLL_WRITE | FOLL_LONGTERM, pages); @@ -286,6 +294,12 @@ static int kvm_s390_pci_aif_enable(struct zpci_dev *zdev, struct zpci_fib *fib, /* Pin the guest AISB if one was specified */ if (fib->fmt0.sum == 1) { + /* AISB must be dword aligned */ + if (fib->fmt0.aisb & 0x7) { + rc = -EINVAL; + goto unpin1; + } + idx = srcu_read_lock(&kvm->srcu); hva = gfn_to_hva(kvm, gpa_to_gfn((gpa_t)fib->fmt0.aisb)); npages = pin_user_pages_fast(hva, 1, FOLL_WRITE | FOLL_LONGTERM, From 82730dba0cf9d9524af0ceeb7eb6b5c3ab1bdb87 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 23 Jul 2026 14:01:59 -0400 Subject: [PATCH 271/456] drm/amd/display: Fix flip-done timeouts on mode1 reset The vblank on/off callbacks mixed use of amdgpu_irq_get/put() and amdgpu_dm_crtc_set_vupdate_irq() to enable and disable IRQs. With get/put, base driver will callback into DC to disable IRQs when refcount == 0. With set_vupdate_irq(), DC is called directly to disable IRQs, bypassing base driver's refcount tracking. During gpu reset, base driver can restore IRQs via amdgpu_irq_gpu_reset_resume_helper() > amdgpu_irq_update(). So if get/put() is not used (i.e. refcount == 0), then vupdate_irq will be disabled. This is problematic if DRM requests vblank on before amdgpu_irq_update() is called: drm_vblank_on() > set_vupdate_irq() enables vupdate_irq, but the refcount is still 0. gpu_reset_resume_helper() > irq_update() then immediately disables it, thus leading to flip done timeouts. This is made worse on DCN since VUPDATE_NO_LOCK is the only IRQ enabled. Prior to 8382cd234981, a combination of GRPH_FLIP and VSTARTUP IRQs were used, and they used get/put(). This explains why 8382cd234981 exposed this issue. Fix by using get/put() instead of set_vupdate_irq(). DCE is unchanged, since it relies on unbalanced enable/disable calls based on VRR status, and hence requires direct set_vupdate_irq(). Plus, it also uses GRPH_FLIP and VLINE IRQs, which are properly tracked by get/put(). Cc: stable@vger.kernel.org Fixes: 8382cd234981 ("drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock") Signed-off-by: Leo Li Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260723180159.52121-1-sunpeng.li@amd.com Signed-off-by: Mario Limonciello --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index b43cd68cde67..95d3da3c4199 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -280,10 +280,19 @@ static inline int amdgpu_dm_crtc_set_vblank(struct drm_crtc *crtc, bool enable) * is enabled. On DCE, vupdate is only needed in VRR mode. */ if (amdgpu_ip_version(adev, DCE_HWIP, 0) != 0) { - rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, enable); + if (enable) { + rc = amdgpu_irq_get(adev, &adev->vupdate_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Get vupdate_irq ret=%d\n", rc); + } else { + rc = amdgpu_irq_put(adev, &adev->vupdate_irq, irq_type); + drm_dbg_vbl(crtc->dev, "Put vupdate_irq ret=%d\n", rc); + } } else if (dc_supports_vrr(dm->dc->ctx->dce_version)) { if (enable) { - /* vblank irq on -> Only need vupdate irq in vrr mode */ + /* vblank irq on -> Only need vupdate irq in vrr mode + * Not ref-counted since we need explicit enable/disable + * for DCE VRR handling + */ if (amdgpu_dm_crtc_vrr_active(acrtc_state)) rc = amdgpu_dm_crtc_set_vupdate_irq(crtc, true); } else { From fbbaca9e208733652828ea98d00f09f260a7770e Mon Sep 17 00:00:00 2001 From: Leo Li Date: Thu, 23 Jul 2026 09:44:50 -0400 Subject: [PATCH 272/456] drm/amd/display: Fix missing DCE check in dm_gpureset_toggle_interrupts() This line was lost when cping from amd-staging-drm-next to drm-fixes. So add it back. Cc: stable@vger.kernel.org Fixes: 8382cd234981 ("drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock") Reported-by: Lu Yao Signed-off-by: Leo Li Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260723134450.13838-1-sunpeng.li@amd.com Signed-off-by: Mario Limonciello --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 9c564cd5edee..eaf19ec843e8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3324,7 +3324,8 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, acrtc = get_crtc_by_otg_inst( adev, state->stream_status[i].primary_otg_inst); - if (acrtc && state->stream_status[i].plane_count != 0) { + if (acrtc && state->stream_status[i].plane_count != 0 && + amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; if (rc) From 39490ec6063d9dc3d995b7b48fc5106cd361a547 Mon Sep 17 00:00:00 2001 From: Surendra Singh Chouhan Date: Fri, 24 Jul 2026 18:25:33 +0530 Subject: [PATCH 273/456] platform/x86: dell-dw5826e: fix ACPI _DSM function index and bitmask usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PALC_DSM_FN_TRIGGER_PLDR was defined as BIT(1) (value 2). acpi_evaluate_dsm() expects a 0-based function index integer (0, 1, 2, ...), whereas acpi_check_dsm() expects a bitmask of supported function indices (BIT(1), BIT(2), ...). Because PALC_DSM_FN_TRIGGER_PLDR was defined as BIT(1), acpi_evaluate_dsm() was evaluating Function Index 2 instead of Function Index 1, while acpi_check_dsm() was checking for Function Index 1 support. Fix this by setting PALC_DSM_FN_TRIGGER_PLDR to 1 (the function index) and passing BIT(PALC_DSM_FN_TRIGGER_PLDR) to acpi_check_dsm(). Fixes: 1ab843135a77 ("platform/x86: dell-dw5826e: Add reset driver for DW5826e") Signed-off-by: Surendra Singh Chouhan Link: https://patch.msgid.link/20260724125533.74751-1-kr494167@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-dw5826e-reset.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell/dell-dw5826e-reset.c b/drivers/platform/x86/dell/dell-dw5826e-reset.c index 1ca7c3421bb5..64e6e9b1d7a0 100644 --- a/drivers/platform/x86/dell/dell-dw5826e-reset.c +++ b/drivers/platform/x86/dell/dell-dw5826e-reset.c @@ -13,7 +13,7 @@ #include #include -#define PALC_DSM_FN_TRIGGER_PLDR BIT(1) +#define PALC_DSM_FN_TRIGGER_PLDR 1 static guid_t palc_dsm_guid = GUID_INIT(0x5a1a4bba, 0x8006, 0x487e, 0xbe, 0x0a, 0xac, 0xf5, 0xd8, 0xfd, 0xfe, 0x59); @@ -66,7 +66,7 @@ static int palc_probe(struct platform_device *pdev) if (!handle) return -ENODEV; - if (!acpi_check_dsm(handle, &palc_dsm_guid, 1, PALC_DSM_FN_TRIGGER_PLDR)) + if (!acpi_check_dsm(handle, &palc_dsm_guid, 1, BIT(PALC_DSM_FN_TRIGGER_PLDR))) return -ENODEV; return 0; From 144f29e85702234b23d2a62abf723e6a17eb5427 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Tue, 21 Jul 2026 21:11:43 -0400 Subject: [PATCH 274/456] tracing: Fix mmiotrace possible NULL dereferencing of hiter->dev If the mmio_pipe_open() fails to find a PCI device, the hiter->dev will be assigned to NULL. The mmiotrace read() function dereferences the hiter->dev if hiter exists. Change the test of the read to not only check hiter being NULL, but also the hiter->dev before dereferencing it. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260721211143.36dbd559@gandalf.local.home Fixes: f984b51e0779 ("ftrace: add mmiotrace plugin") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260715143604.14481-1-gaikwad.dcg%40gmail.com Signed-off-by: Steven Rostedt --- kernel/trace/trace_mmiotrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_mmiotrace.c b/kernel/trace/trace_mmiotrace.c index 20812e7f911f..b88b8d9923ad 100644 --- a/kernel/trace/trace_mmiotrace.c +++ b/kernel/trace/trace_mmiotrace.c @@ -145,7 +145,7 @@ static ssize_t mmio_read(struct trace_iterator *iter, struct file *filp, goto print_out; } - if (!hiter) + if (!hiter || !hiter->dev) return 0; mmio_print_pcidev(s, hiter->dev); From b4eb07bde606c2096b24252be589e735eff6d413 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 17 Jul 2026 11:51:49 +0900 Subject: [PATCH 275/456] tracing: Fix union collision of module and refcnt for dynamic events In 'struct trace_event_call', the 'module' pointer and the 'refcnt' atomic variable share the same memory space in a union. For dynamic events, the union member is 'refcnt', which acts as an active reference counter. When a dynamic event (such as kprobe, uprobe, fprobe, eprobe, or wprobe) has a non-zero reference count (e.g. due to active event triggers or perf attachments), its 'call->module' evaluates to a small non-zero integer instead of NULL. When filtering or setting events for a specific module (e.g., writing ':mod:' to 'set_event'), the code in '__ftrace_set_clr_event_nolock()' and 'update_event_fields()' reads 'call->module' directly without checking whether the event is dynamic. This causes the kernel to treat the small integer (refcnt) as a 'struct module' pointer, leading to a NULL/invalid pointer dereference (Oops) when dereferencing the module name. Fix this by ensuring that the 'TRACE_EVENT_FL_DYNAMIC' flag is checked before treating 'call->module' as a valid pointer in these code paths. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/178425670947.84440.11344393611899824907.stgit@devnote2 Fixes: 4c86bc531e60 ("tracing: Add :mod: command to enabled module events") Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index c46e623e7e0d..956692856fa8 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -1350,7 +1350,9 @@ __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match, call = file->event_call; /* If a module is specified, skip events that are not that module */ - if (module && (!call->module || strcmp(module_name(call->module), module))) + if (module && + ((call->flags & TRACE_EVENT_FL_DYNAMIC) || + !call->module || strcmp(module_name(call->module), module))) continue; name = trace_event_name(call); From 1a087033a6bad73b4140020b40e819b0933aafc3 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 17 Jul 2026 11:51:59 +0900 Subject: [PATCH 276/456] selftests/ftrace: Reset triggers at top level before instance loop When running instance tests, 'ftracetest' creates a new ftrace instance and runs the tests inside it. Before starting each test, it executes 'initialize_system()' to reset the ftrace state to initial-state. However, since 'initialize_system()' is executed in the context of the instance directory, it only cleans up triggers and filters of that instance. Any triggers or dynamic events left behind in the top-level instance by previous failed top-level tests, are left completely untouched. These top-level leftovers can cause subsequent instance-based tests to fail or even crash the kernel. Fix this by executing 'initialize_system()' in the top-level tracing directory once before entering the instance loop. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/178425671889.84440.9477850701738666404.stgit@devnote2 Fixes: b5b77be812de ("selftests: ftrace: Allow some tests to be run in a tracing instance") Assisted-by: Antigravity:gemini-3.5-flash Signed-off-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- tools/testing/selftests/ftrace/ftracetest | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/ftrace/ftracetest b/tools/testing/selftests/ftrace/ftracetest index 0a56bf209f6c..8ad2c385407e 100755 --- a/tools/testing/selftests/ftrace/ftracetest +++ b/tools/testing/selftests/ftrace/ftracetest @@ -503,6 +503,7 @@ for t in $TEST_CASES; do done # Test on instance loop +(cd $TRACING_DIR; initialize_system) INSTANCE=" (instance) " for t in $TEST_CASES; do test_on_instance $t || continue From 8f76afb9b114bee1c1251e0e52553e9dc7c59f20 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Fri, 17 Jul 2026 10:32:52 -0700 Subject: [PATCH 277/456] tracing: Fix context switch counter truncation trace_user_fault_read() samples nr_context_switches_cpu() before enabling preemption and retries the user copy if the counter changes. The helper returns unsigned long long because rq->nr_switches is u64, but the saved value is unsigned int. Once a CPU has performed 2^32 context switches, assigning the counter to cnt discards its upper bits. The comparison after the copy promotes cnt back to unsigned long long, but the lost bits remain zero, so it reports a change even when the task was never scheduled out. Every retry then fails the same way until the 100-try guard warns and the user copy is abandoned. This affects long-running systems and workloads with high context-switch rates. A CPU switching 1,000 times per second takes about 50 days. Store the sampled count in unsigned long long so the full value is preserved. Cc: stable@vger.kernel.org Fixes: 64cf7d058a00 ("tracing: Have trace_marker use per-cpu data to read user space") Link: https://patch.msgid.link/20260717173252.3431565-1-usama.arif@linux.dev Reported-by: Breno Leitao Signed-off-by: Usama Arif Acked-by: Masami Hiramatsu (Google) Reviewed-by: Breno Leitao Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 18710c190c92..01a5e87af299 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -6187,7 +6187,7 @@ char *trace_user_fault_read(struct trace_user_buf_info *tinfo, { int cpu = smp_processor_id(); char *buffer = per_cpu_ptr(tinfo->tbuf, cpu)->buf; - unsigned int cnt; + unsigned long long cnt; int trys = 0; int ret; From 9736d2efc99670054359e153a9f312ef4646ec7b Mon Sep 17 00:00:00 2001 From: Wenjia Zhang Date: Fri, 24 Jul 2026 07:37:52 +0200 Subject: [PATCH 278/456] MAINTAINERS: Update SHARED MEMORY COMMUNICATIONS (SMC) maintainer entries Due to a change in responsibilities, I can no longer serve as an SMC maintainer and need to be removed from the MAINTAINERS list. To reflect these organizational changes, promote Mahanta Jambigi from reviewer to maintainer. Acked-by: Mahanta Jambigi Signed-off-by: Wenjia Zhang Reviewed-by: Sidraya Jayagond Reviewed-by: Dust Li Link: https://patch.msgid.link/20260724053752.3084-1-wenjia@linux.ibm.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index b72d2bd07f08..61126d170e4a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24632,8 +24632,7 @@ SHARED MEMORY COMMUNICATIONS (SMC) SOCKETS M: D. Wythe M: Dust Li M: Sidraya Jayagond -M: Wenjia Zhang -R: Mahanta Jambigi +M: Mahanta Jambigi R: Tony Lu R: Wen Gu L: linux-rdma@vger.kernel.org From 072cd1f21819dedd2252e704d255de3b0cfc61a7 Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Wed, 22 Jul 2026 00:29:50 +0000 Subject: [PATCH 279/456] nexthop: take nh->lock for f6i_list walks in replace check and notify fib6_check_nh_list() and __nexthop_replace_notify() walk nh->f6i_list during an RTNL-serialized nexthop replace without holding nh->lock. IPv6 RTM_NEWROUTE/RTM_DELROUTE run without RTNL and mutate that list under nh->lock (fib6_add_rt2node_nh(), fib6_purge_rt()), so both walks race a concurrent route delete that unlinks and frees a fib6_info: BUG: KASAN: slab-use-after-free in rt6_fill_node.isra.0 (net/ipv6/route.c:5799) Read of size 4 at addr ffff888014607e64 by task exploit/143 rt6_fill_node.isra.0 (net/ipv6/route.c:5799) fib6_rt_update (net/ipv6/route.c:6412) __nexthop_replace_notify (net/ipv4/nexthop.c:2542) rtm_new_nexthop (net/ipv4/nexthop.c:2554) rtnetlink_rcv_msg (net/core/rtnetlink.c:7076) BUG: KASAN: slab-use-after-free in fib6_check_nh_list (net/ipv4/nexthop.c:1605) Read of size 8 at addr ffff888014a7d068 by task exploit/142 fib6_check_nh_list (net/ipv4/nexthop.c:1605) rtm_new_nexthop (net/ipv4/nexthop.c:2575) rtnetlink_rcv_msg (net/core/rtnetlink.c:7076) Both walks only read the entries and take no tb6_lock, so protect them with nh->lock; fib6_rt_update() uses gfp_any(), which returns GFP_ATOMIC under the lock. Fixes: 081efd18326e ("ipv6: Protect nh->f6i_list with spinlock and flag.") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260722002951.2614721-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- net/ipv4/nexthop.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c index 44fe75004cac..eb5c76ac807b 100644 --- a/net/ipv4/nexthop.c +++ b/net/ipv4/nexthop.c @@ -1597,14 +1597,21 @@ static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new, struct netlink_ext_ack *extack) { struct fib6_info *f6i; + int err = 0; if (list_empty(&old->f6i_list)) return 0; + spin_lock_bh(&old->lock); list_for_each_entry(f6i, &old->f6i_list, nh_list) { - if (check_src_addr(&f6i->fib6_src.addr, extack) < 0) - return -EINVAL; + err = check_src_addr(&f6i->fib6_src.addr, extack); + if (err) + break; } + spin_unlock_bh(&old->lock); + + if (err) + return err; return fib6_check_nexthop(new, NULL, extack); } @@ -2538,8 +2545,10 @@ static void __nexthop_replace_notify(struct net *net, struct nexthop *nh, fi->nh_updated = false; } + spin_lock_bh(&nh->lock); list_for_each_entry(f6i, &nh->f6i_list, nh_list) fib6_rt_update(net, f6i, info); + spin_unlock_bh(&nh->lock); } /* send RTM_NEWROUTE with REPLACE flag set for all FIB entries From 4787a6d2629b4e8c0b6bacab1f75c1660eca44d9 Mon Sep 17 00:00:00 2001 From: "Xiang Mei (Microsoft)" Date: Wed, 22 Jul 2026 00:29:51 +0000 Subject: [PATCH 280/456] nexthop: avoid unlocked f6i_list walk in nh_rt_cache_flush nh_rt_cache_flush() walks nh->f6i_list during an RTNL-serialized nexthop replace without holding nh->lock, racing the unlocked IPv6 route add/delete that mutate the list under nh->lock and free fib6_info entries (nh_rt_cache_flush() is inlined into rtm_new_nexthop()): BUG: KASAN: slab-use-after-free in nh_rt_cache_flush (net/ipv4/nexthop.c:2243) Read of size 8 at addr ffff888012953e18 by task exploit/146 nh_rt_cache_flush (net/ipv4/nexthop.c:2243) replace_nexthop (net/ipv4/nexthop.c:2610) rtm_new_nexthop (net/ipv4/nexthop.c:3323) rtnetlink_rcv_msg (net/core/rtnetlink.c:7076) Unlike the other f6i_list walks, this one bumps each route's sernum via fib6_update_sernum_upto_root(), which needs tb6_lock; taking nh->lock around it would invert the established tb6_lock -> nh->lock order and deadlock. As the only purpose is to invalidate cached dsts, bump the IPv6 sernum for the whole netns with rt_genid_bump_ipv6() instead, mirroring the rt_cache_flush() already done for IPv4 just above. Fixes: 081efd18326e ("ipv6: Protect nh->f6i_list with spinlock and flag.") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Xiang Mei (Microsoft) Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260722002951.2614721-2-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- net/ipv4/nexthop.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c index eb5c76ac807b..0f1e21a5c812 100644 --- a/net/ipv4/nexthop.c +++ b/net/ipv4/nexthop.c @@ -2240,18 +2240,18 @@ static void remove_one_nexthop(struct net *net, struct nexthop *nh, static void nh_rt_cache_flush(struct net *net, struct nexthop *nh, struct nexthop *replaced_nh) { - struct fib6_info *f6i; struct nh_group *nhg; + bool have_f6i; int i; if (!list_empty(&nh->fi_list)) rt_cache_flush(net); - list_for_each_entry(f6i, &nh->f6i_list, nh_list) { - spin_lock_bh(&f6i->fib6_table->tb6_lock); - fib6_update_sernum_upto_root(net, f6i); - spin_unlock_bh(&f6i->fib6_table->tb6_lock); - } + spin_lock_bh(&nh->lock); + have_f6i = !list_empty(&nh->f6i_list); + spin_unlock_bh(&nh->lock); + if (have_f6i) + rt_genid_bump_ipv6(net); /* if an IPv6 group was replaced, we have to release all old * dsts to make sure all refcounts are released From 2d66a033864e27ab8d5e44cb36f31d9d2413bee4 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Fri, 24 Jul 2026 18:38:56 +0800 Subject: [PATCH 281/456] bpf, sockmap: Fix cork use-after-free in tcp_bpf_sendmsg() tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. Its error path tries to decide whether msg_tx names the local temporary message by comparing it with the current value of psock->cork. This comparison is unsafe when two threads send on the same socket: Thread A Thread B msg_tx = psock->cork sk_msg_alloc() fails sk_stream_wait_memory() releases the socket lock acquires the socket lock completes the cork psock->cork = NULL frees the cork reacquires the socket lock msg_tx != psock->cork sk_msg_free(msg_tx) The stale cork is therefore mistaken for the local temporary message and freed again. KASAN reported: BUG: KASAN: slab-use-after-free in sk_msg_free+0x49/0x50 Read of size 4 at addr ffff88810c908800 by task poc/90 Call Trace: sk_msg_free+0x49/0x50 tcp_bpf_sendmsg+0x14f5/0x1cc0 __sys_sendto+0x32c/0x3a0 __x64_sys_sendto+0xdb/0x1b0 Allocated by task 89: __kasan_kmalloc+0x8f/0xa0 tcp_bpf_sendmsg+0x16b3/0x1cc0 Freed by task 91: __kasan_slab_free+0x43/0x70 kfree+0x131/0x3c0 tcp_bpf_sendmsg+0xec3/0x1cc0 msg_tx can only name the stack-local tmp or the shared cork. Check for tmp directly so a changed psock->cork cannot turn a shared message into an apparent local one. Fixes: 604326b41a6f ("bpf, sockmap: convert to generic sk_msg interface") Signed-off-by: Chengfeng Ye Reviewed-by: Emil Tsalapatis Reviewed-by: Jakub Sitnicki Link: https://lore.kernel.org/bpf/87fr18lmzo.fsf%40cloudflare.com/ Link: https://lore.kernel.org/netdev/20260719161630.2901208-1-nicoyip.dev%40gmail.com/ [v1] Link: https://patch.msgid.link/20260724103856.3399001-1-nicoyip.dev@gmail.com Signed-off-by: Eduard Zingerman --- net/ipv4/tcp_bpf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index 8e905b50dead..a30475afb6f8 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -604,7 +604,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) wait_for_memory: err = sk_stream_wait_memory(sk, &timeo); if (err) { - if (msg_tx && msg_tx != psock->cork) + if (msg_tx == &tmp) sk_msg_free(sk, msg_tx); goto out_err; } From f0d9c3ffc2b5fc2ffacb56b3036155ce7a940a12 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Sat, 18 Jul 2026 14:29:01 -0400 Subject: [PATCH 282/456] af_unix: fix listen() succeeding on sockets in the wrong state Commit fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid") inserted a prepare_peercred() call between err = -EINVAL and the socket-state check in unix_listen(). Since prepare_peercred() leaves err at 0 on success, listen() on an AF_UNIX socket that is not in TCP_CLOSE or TCP_LISTEN state (e.g. one that is already connected) now silently returns success without doing anything, instead of failing with EINVAL as it did before. Fixes: fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid") Signed-off-by: John Ericson Link: https://patch.msgid.link/20260718182903.2295560-1-John.Ericson@Obsidian.Systems Signed-off-by: Jakub Kicinski --- net/unix/af_unix.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index f7a9d55eee8a..10ed9421e43a 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -823,6 +823,7 @@ static int unix_listen(struct socket *sock, int backlog) if (err) goto out; unix_state_lock(sk); + err = -EINVAL; if (sk->sk_state != TCP_CLOSE && sk->sk_state != TCP_LISTEN) goto out_unlock; if (backlog > sk->sk_max_ack_backlog) From 7f57c650d08b8793bb551bdb33ad876535ef9fe8 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Sat, 18 Jul 2026 14:29:02 -0400 Subject: [PATCH 283/456] selftests/net/af_unix: test listen() rejects wrong socket states Add a regression test for the unix_listen() state check. The key case is listen() on a bound socket that has already been connected: it is no longer in TCP_CLOSE or TCP_LISTEN, so it must fail with EINVAL. A prepare_peercred() call slipped in ahead of that check once left err at 0 and made listen() silently succeed there instead; this guards against a repeat. The neighbouring outcomes are covered too so they cannot regress the same way: a bound socket in TCP_CLOSE listens fine, calling listen() again on a socket already in TCP_LISTEN is allowed, and an unbound socket fails with EINVAL. Each case runs for both listenable socket types (SOCK_STREAM and SOCK_SEQPACKET) and both pathname and abstract addresses. Fixes: fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid") Signed-off-by: John Ericson Link: https://patch.msgid.link/20260718182903.2295560-2-John.Ericson@Obsidian.Systems Signed-off-by: Jakub Kicinski --- .../testing/selftests/net/af_unix/.gitignore | 1 + tools/testing/selftests/net/af_unix/Makefile | 1 + .../selftests/net/af_unix/unix_listen.c | 187 ++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 tools/testing/selftests/net/af_unix/unix_listen.c diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore index 240b26740c9e..973176644103 100644 --- a/tools/testing/selftests/net/af_unix/.gitignore +++ b/tools/testing/selftests/net/af_unix/.gitignore @@ -6,3 +6,4 @@ scm_rights so_peek_off unix_connect unix_connreset +unix_listen diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile index 4c0375e28bbe..57d159803a3a 100644 --- a/tools/testing/selftests/net/af_unix/Makefile +++ b/tools/testing/selftests/net/af_unix/Makefile @@ -14,6 +14,7 @@ TEST_GEN_PROGS := \ so_peek_off \ unix_connect \ unix_connreset \ + unix_listen \ # end of TEST_GEN_PROGS include ../../lib.mk diff --git a/tools/testing/selftests/net/af_unix/unix_listen.c b/tools/testing/selftests/net/af_unix/unix_listen.c new file mode 100644 index 000000000000..416fa3e5bfe9 --- /dev/null +++ b/tools/testing/selftests/net/af_unix/unix_listen.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Tests for the state checks in AF_UNIX listen(). + * + * The central case is a regression test: listen() on a bound socket that + * is already connected (i.e. not in TCP_CLOSE or TCP_LISTEN state) must + * fail with EINVAL. A prior change accidentally let it return success + * without doing anything, because a helper called in between reset the + * error code to 0. The neighbouring checks (unbound, already listening) + * are tested too so they cannot silently regress the same way. + * + * Every case runs for both listenable socket types (SOCK_STREAM and + * SOCK_SEQPACKET) and both pathname and abstract addresses. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include + +#include "kselftest_harness.h" + +#define SK_NAME "unix_listen_sk" +#define SRV_NAME "unix_listen_srv" + +FIXTURE(unix_listen) +{ + int sk; /* socket under test */ + int server; /* a listening peer, when a test needs one */ + struct sockaddr_un addr, srv_addr; + socklen_t addrlen, srv_addrlen; +}; + +FIXTURE_VARIANT(unix_listen) +{ + int type; + int abstract; +}; + +FIXTURE_VARIANT_ADD(unix_listen, stream_pathname) +{ + .type = SOCK_STREAM, + .abstract = 0, +}; + +FIXTURE_VARIANT_ADD(unix_listen, stream_abstract) +{ + .type = SOCK_STREAM, + .abstract = 1, +}; + +FIXTURE_VARIANT_ADD(unix_listen, seqpacket_pathname) +{ + .type = SOCK_SEQPACKET, + .abstract = 0, +}; + +FIXTURE_VARIANT_ADD(unix_listen, seqpacket_abstract) +{ + .type = SOCK_SEQPACKET, + .abstract = 1, +}; + +/* Fill @addr with a pathname or abstract address named @name. */ +static socklen_t unix_set_addr(struct sockaddr_un *addr, const char *name, + int abstract) +{ + size_t len = strlen(name); + + memset(addr, 0, sizeof(*addr)); + addr->sun_family = AF_UNIX; + /* An abstract address leads with a NUL and has no filesystem entry. */ + memcpy(addr->sun_path + (abstract ? 1 : 0), name, len); + + return offsetof(struct sockaddr_un, sun_path) + len + 1; +} + +FIXTURE_SETUP(unix_listen) +{ + self->sk = -1; + self->server = -1; + self->addrlen = unix_set_addr(&self->addr, SK_NAME, variant->abstract); + self->srv_addrlen = unix_set_addr(&self->srv_addr, SRV_NAME, + variant->abstract); +} + +FIXTURE_TEARDOWN(unix_listen) +{ + if (self->sk >= 0) + close(self->sk); + if (self->server >= 0) + close(self->server); + + /* Pathname sockets leave a filesystem entry behind; abstract ones do not. */ + if (!variant->abstract) { + remove(SK_NAME); + remove(SRV_NAME); + } +} + +/* A bound socket in TCP_CLOSE is the normal, allowed case. */ +TEST_F(unix_listen, bound_is_ok) +{ + int err; + + self->sk = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->sk); + + err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen); + ASSERT_EQ(0, err); + + err = listen(self->sk, 8); + EXPECT_EQ(0, err); +} + +/* Listening again on an already-listening socket (TCP_LISTEN) is allowed. */ +TEST_F(unix_listen, relisten_is_ok) +{ + int err; + + self->sk = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->sk); + + err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen); + ASSERT_EQ(0, err); + + err = listen(self->sk, 8); + ASSERT_EQ(0, err); + + err = listen(self->sk, 16); + EXPECT_EQ(0, err); +} + +/* listen() on an unbound socket fails: there is nothing to listen on. */ +TEST_F(unix_listen, unbound_is_einval) +{ + int err; + + self->sk = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->sk); + + err = listen(self->sk, 8); + EXPECT_EQ(-1, err); + EXPECT_EQ(EINVAL, errno); +} + +/* + * The regression: a bound socket that has already been connected is not in + * TCP_CLOSE or TCP_LISTEN, so listen() must reject it with EINVAL rather + * than quietly succeeding. + */ +TEST_F(unix_listen, connected_is_einval) +{ + int err; + + self->server = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->server); + + err = bind(self->server, (struct sockaddr *)&self->srv_addr, + self->srv_addrlen); + ASSERT_EQ(0, err); + + err = listen(self->server, 8); + ASSERT_EQ(0, err); + + self->sk = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->sk); + + /* Bind first so the unbound check does not mask the state check. */ + err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen); + ASSERT_EQ(0, err); + + err = connect(self->sk, (struct sockaddr *)&self->srv_addr, + self->srv_addrlen); + ASSERT_EQ(0, err); + + err = listen(self->sk, 8); + EXPECT_EQ(-1, err); + EXPECT_EQ(EINVAL, errno); +} + +TEST_HARNESS_MAIN From a3c8382ebce4780c6b3ace2c09bc342313ac0186 Mon Sep 17 00:00:00 2001 From: Jason Xing Date: Sun, 19 Jul 2026 15:56:04 +0200 Subject: [PATCH 284/456] xsk: fix buffer leak in xsk_drop_skb() for AF_XDP multi-buffer Tx This patch is inspired by the check[1] from sashiko. It says when overflow happens, the address of cq to be published is invalid. Actually the severer thing is the whole process of publishing the address of cq in this particular case is not right: it should truely publish the address and advance the cached_prod in cq as long as it reads descriptors from txq. The following is the full analysis. xsk_drop_skb() is called in three places, which all discard a partially built multi-buffer skb: 1) xsk_build_skb() -EOVERFLOW error path: packet exceeds MAX_SKB_FRAGS 2) __xsk_generic_xmit() post-loop cleanup: an invalid descriptor in the TX ring prevents the partial packet from completing 3) xsk_release(): socket close while xs->skb holds an incomplete packet In all three cases, the TX descriptors for the already-processed frags have been consumed from the TX ring (xskq_cons_release), and CQ slots have been reserved. However, xsk_drop_skb() calls xsk_consume_skb() which cancels the CQ reservations via xsk_cq_cancel_locked(). Since the buffer addresses never appear in the completion queue, userspace permanently loses track of these buffers. Fix this by letting consume_skb() trigger the existing xsk_destruct_skb destructor, which already submits buffer addresses to the CQ via xsk_cq_submit_addr_locked(). Note that cancelling the descriptors back to the TX ring (via xskq_cons_cancel_n) is not a appropriate option because an oversized packet that always exceeds MAX_SKB_FRAGS would be retried indefinitely, which is an obviously deadlock bug in the TX path. Also move the desc->addr assignment in xsk_build_skb() above the overflow check so that the current descriptor's address is recorded before a potential -EOVERFLOW jump to free_err, consistent with the zerocopy path in xsk_build_skb_zerocopy(). [1]: https://lore.kernel.org/all/20260425041726.85FB3C2BCB2@smtp.kernel.org/ Fixes: cf24f5a5feea ("xsk: add support for AF_XDP multi-buffer on Tx path") Acked-by: Maciej Fijalkowski Signed-off-by: Jason Xing Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260719135609.147823-2-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- net/xdp/xsk.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index b970f30ea9b9..a7a83dc4546a 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -794,8 +794,11 @@ static void xsk_consume_skb(struct sk_buff *skb) static void xsk_drop_skb(struct sk_buff *skb) { - xdp_sk(skb->sk)->tx->invalid_descs += xsk_get_num_desc(skb); - xsk_consume_skb(skb); + struct xdp_sock *xs = xdp_sk(skb->sk); + + xs->tx->invalid_descs += xsk_get_num_desc(skb); + consume_skb(skb); + xs->skb = NULL; } static int xsk_skb_metadata(struct sk_buff *skb, void *buffer, @@ -877,7 +880,7 @@ static struct sk_buff *xsk_build_skb_zerocopy(struct xdp_sock *xs, return ERR_PTR(-ENOMEM); /* in case of -EOVERFLOW that could happen below, - * xsk_consume_skb() will release this node as whole skb + * xsk_drop_skb() will release this node as whole skb * would be dropped, which implies freeing all list elements */ xsk_addr->addrs[xsk_addr->num_descs] = desc->addr; @@ -969,6 +972,8 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs, goto free_err; } + xsk_addr->addrs[xsk_addr->num_descs] = desc->addr; + if (unlikely(nr_frags == (MAX_SKB_FRAGS - 1) && xp_mb_desc(desc))) { err = -EOVERFLOW; goto free_err; @@ -986,8 +991,6 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs, skb_add_rx_frag(skb, nr_frags, page, 0, len, PAGE_SIZE); refcount_add(PAGE_SIZE, &xs->sk.sk_wmem_alloc); - - xsk_addr->addrs[xsk_addr->num_descs] = desc->addr; } } From bd44a6dcd4248883de90f5dad53ae80066e27096 Mon Sep 17 00:00:00 2001 From: Jason Xing Date: Sun, 19 Jul 2026 15:56:05 +0200 Subject: [PATCH 285/456] xsk: drain continuation descs after overflow in xsk_build_skb() Fix generic xmit path multi-buffer logic when packets are either too big (count of descriptors exceed MAX_SKB_FRAGS) or an invalid descriptor is included in fragmented packet. Introduce xdp_sock::drain_cont and act upon this flag - when it is set, keep on consuming descriptors from AF_XDP Tx ring and put them directly onto Cq. Previously these descriptors were silently lost and could never be reached again. Fixes: cf24f5a5feea ("xsk: add support for AF_XDP multi-buffer on Tx path") Closes: https://lore.kernel.org/all/20260425041726.85FB3C2BCB2@smtp.kernel.org/ Reviewed-by: Jason Xing Co-developed-by: Maciej Fijalkowski # wrapped cq addr submission onto routine Signed-off-by: Maciej Fijalkowski Signed-off-by: Jason Xing Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260719135609.147823-3-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- include/net/xdp_sock.h | 1 + net/xdp/xsk.c | 45 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/include/net/xdp_sock.h b/include/net/xdp_sock.h index ebac60a3d8a1..8b51876efbed 100644 --- a/include/net/xdp_sock.h +++ b/include/net/xdp_sock.h @@ -80,6 +80,7 @@ struct xdp_sock { * call of __xsk_generic_xmit(). */ struct sk_buff *skb; + bool drain_cont; struct list_head map_list; /* Protects map_list */ diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index a7a83dc4546a..12a845d012f6 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -737,6 +737,19 @@ static void xsk_cq_submit_addr_locked(struct xsk_buff_pool *pool, spin_unlock_irqrestore(&pool->cq_prod_lock, flags); } +static void xsk_cq_submit_addr_single_locked(struct xsk_buff_pool *pool, + struct xdp_desc *desc) +{ + unsigned long flags; + u32 idx; + + spin_lock_irqsave(&pool->cq_prod_lock, flags); + idx = xskq_get_prod(pool->cq); + xskq_prod_write_addr(pool->cq, idx, desc->addr); + xskq_prod_submit_n(pool->cq, 1); + spin_unlock_irqrestore(&pool->cq_prod_lock, flags); +} + static void xsk_cq_cancel_locked(struct xsk_buff_pool *pool, u32 n) { spin_lock(&pool->cq->cq_cached_prod_lock); @@ -1028,13 +1041,14 @@ static struct sk_buff *xsk_build_skb(struct xdp_sock *xs, static int __xsk_generic_xmit(struct sock *sk) { struct xdp_sock *xs = xdp_sk(sk); - bool sent_frame = false; struct xdp_desc desc; struct sk_buff *skb; + u32 cached_cons; u32 max_batch; int err = 0; mutex_lock(&xs->mutex); + cached_cons = xs->tx->cached_cons; /* Since we dropped the RCU read lock, the socket state might have changed. */ if (unlikely(!xsk_is_bound(xs))) { @@ -1063,11 +1077,21 @@ static int __xsk_generic_xmit(struct sock *sk) goto out; } + if (unlikely(xs->drain_cont)) { + xsk_cq_submit_addr_single_locked(xs->pool, &desc); + xs->tx->invalid_descs++; + xskq_cons_release(xs->tx); + xs->drain_cont = xp_mb_desc(&desc); + continue; + } + skb = xsk_build_skb(xs, &desc); if (IS_ERR(skb)) { err = PTR_ERR(skb); if (err != -EOVERFLOW) goto out; + if (xp_mb_desc(&desc)) + xs->drain_cont = true; err = 0; continue; } @@ -1096,18 +1120,33 @@ static int __xsk_generic_xmit(struct sock *sk) goto out; } - sent_frame = true; xs->skb = NULL; } if (xskq_has_descs(xs->tx)) { + bool drain = xs->skb || xs->drain_cont || xp_mb_desc(&desc); + + err = xsk_cq_reserve_locked(xs->pool); + if (err) { + xs->tx->invalid_descs--; + if (xs->skb) + xsk_drop_skb(xs->skb); + xs->drain_cont = drain; + err = -EAGAIN; + goto out; + } + if (xs->skb) xsk_drop_skb(xs->skb); + + xsk_cq_submit_addr_single_locked(xs->pool, &desc); + xskq_cons_release(xs->tx); + xs->drain_cont = xp_mb_desc(&desc); } out: - if (sent_frame) + if (xs->tx->cached_cons != cached_cons) __xsk_tx_release(xs); mutex_unlock(&xs->mutex); From 08c9a8e794b4694c100dafcb80e069e29ad81b64 Mon Sep 17 00:00:00 2001 From: Maciej Fijalkowski Date: Sun, 19 Jul 2026 15:56:06 +0200 Subject: [PATCH 286/456] xsk: provide sufficient space in pool->tx_descs The temporary Tx descriptor array in an XSK buffer pool is currently sized from the Tx ring of the socket that creates the pool. This is insufficient for shared-UMEM Tx. A later socket may have a larger Tx ring and submit a valid multi-buffer packet containing more descriptors than the first socket's ring, while still remaining within the device's xdp_zc_max_segs limit. A packet-framed batch parser bounded by the temporary array cannot reach the end-of-packet descriptor in that case. It leaves the packet on the Tx ring and encounters the same packet on every subsequent attempt, stalling Tx processing for that socket. Size the temporary descriptor array to the larger of the first Tx ring and the device's xdp_zc_max_segs capability. This keeps the array large enough to inspect one maximum-sized valid packet. Larger shared Tx rings do not require further resizing, as they can be processed over multiple batches. Following commit will actually address the data path side. Fixes: d5581966040f ("xsk: support ZC Tx multi-buffer in batch API") Reviewed-by: Jason Xing Signed-off-by: Maciej Fijalkowski Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260719135609.147823-4-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- include/net/xsk_buff_pool.h | 6 ++++-- net/xdp/xsk.c | 10 +++++++--- net/xdp/xsk_buff_pool.c | 12 ++++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h index ccb3b350001f..f5e737a83055 100644 --- a/include/net/xsk_buff_pool.h +++ b/include/net/xsk_buff_pool.h @@ -102,12 +102,14 @@ struct xsk_buff_pool { /* AF_XDP core. */ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs, - struct xdp_umem *umem); + struct xdp_umem *umem, + u32 max_segs); int xp_assign_dev(struct xsk_buff_pool *pool, struct net_device *dev, u16 queue_id, u16 flags); int xp_assign_dev_shared(struct xsk_buff_pool *pool, struct xdp_sock *umem_xs, struct net_device *dev, u16 queue_id); -int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs); +int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs, + u32 max_segs); void xp_destroy(struct xsk_buff_pool *pool); void xp_get_pool(struct xsk_buff_pool *pool); bool xp_put_pool(struct xsk_buff_pool *pool); diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index 12a845d012f6..091792d1d82d 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -1525,7 +1525,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr * and/or device. */ xs->pool = xp_create_and_assign_umem(xs, - umem_xs->umem); + umem_xs->umem, + dev->xdp_zc_max_segs); if (!xs->pool) { err = -ENOMEM; sockfd_put(sock); @@ -1557,7 +1558,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr * utilizes */ if (xs->tx && !xs->pool->tx_descs) { - err = xp_alloc_tx_descs(xs->pool, xs); + err = xp_alloc_tx_descs(xs->pool, xs, + dev->xdp_zc_max_segs); if (err) { xp_put_pool(xs->pool); xs->pool = NULL; @@ -1575,7 +1577,9 @@ static int xsk_bind(struct socket *sock, struct sockaddr_unsized *addr, int addr goto out_unlock; } else { /* This xsk has its own umem. */ - xs->pool = xp_create_and_assign_umem(xs, xs->umem); + xs->pool = xp_create_and_assign_umem(xs, xs->umem, + dev->xdp_zc_max_segs); + if (!xs->pool) { err = -ENOMEM; goto out_unlock; diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index 1f28a9641571..12c9fb29af05 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -42,9 +42,12 @@ void xp_destroy(struct xsk_buff_pool *pool) kvfree(pool); } -int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs) +int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs, + u32 max_segs) { - pool->tx_descs = kvzalloc_objs(*pool->tx_descs, xs->tx->nentries); + u32 nentries = max(xs->tx->nentries, max_segs); + + pool->tx_descs = kvzalloc_objs(*pool->tx_descs, nentries); if (!pool->tx_descs) return -ENOMEM; @@ -52,7 +55,8 @@ int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs) } struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs, - struct xdp_umem *umem) + struct xdp_umem *umem, + u32 max_segs) { bool unaligned = umem->flags & XDP_UMEM_UNALIGNED_CHUNK_FLAG; struct xsk_buff_pool *pool; @@ -69,7 +73,7 @@ struct xsk_buff_pool *xp_create_and_assign_umem(struct xdp_sock *xs, goto out; if (xs->tx) - if (xp_alloc_tx_descs(pool, xs)) + if (xp_alloc_tx_descs(pool, xs, max_segs)) goto out; pool->chunk_mask = ~((u64)umem->chunk_size - 1); From 72f2b4516faf55d4dfac2414649d3cffa5fd2c5e Mon Sep 17 00:00:00 2001 From: Maciej Fijalkowski Date: Sun, 19 Jul 2026 15:56:07 +0200 Subject: [PATCH 287/456] xsk: reclaim invalid Tx descriptors in ZC batch path The zero-copy Tx batch parser stops when it encounters an invalid descriptor. If this happens after one or more continuation descriptors, the Tx consumer can be advanced past fragments that are neither submitted to the driver nor returned to userspace through the completion ring. A similar problem occurs when a packet exceeds xdp_zc_max_segs. The descriptors consumed up to the limit are released without completion, and the remaining continuation descriptors can subsequently be interpreted as the beginning of another packet. Parse Tx batches in packet units and distinguish descriptors belonging to complete valid packets from descriptors consumed while draining an invalid or oversized packet. Return the former to the driver and append the latter to the CQ address area so userspace can reclaim their UMEM frames. Treat a standalone invalid descriptor as a one-descriptor reclaim-only packet. Advancing the Tx-ring consumer releases the ring slot, but does not by itself return ownership of the referenced UMEM frame to userspace. Once draining starts, continue until the packet's end-of-packet descriptor is consumed. Preserve the drain state on the socket when EOP has not yet been supplied, so draining can continue during a later call. Leave incomplete but otherwise valid packets on the Tx ring. Shared-UMEM pools using multi-buffer Tx also need packet-framed parsing. Walk their Tx sockets one packet at a time, preserving the existing per-socket fairness scheme, instead of using the legacy one-descriptor fallback. Keep that fallback for shared pools that do not use multi-buffer Tx. Since the drain state is maintained per socket and both the singular and shared paths can resume an interrupted drain, changing the socket list from singular to shared requires no special bind-time transition. CQ entries are positional, and drivers may complete only part of the Tx work returned by xsk_tx_peek_release_desc_batch(). Therefore, reclaim-only entries cannot be published immediately when earlier driver-visible descriptors are still outstanding. Track the number of driver-visible CQ entries preceding the reclaim entries. Let xsk_tx_completed() publish partial hardware Tx completions, and publish the reclaim entries only after every earlier Tx descriptor has completed. Complete a reclaim-only batch immediately when there is no driver-visible work in front of it, and prevent another Tx batch from being appended while reclaim entries remain pending. Also cap batch processing by the size of the pool's temporary descriptor array, as Tx rings belonging to sockets sharing a UMEM may have different sizes. This ensures that every invalid Tx descriptor consumed by the ZC batch path is either submitted to the driver as part of a valid packet or returned to userspace without violating CQ completion ordering. Reviewed-by: Jason Xing Signed-off-by: Maciej Fijalkowski Acked-by: Stanislav Fomichev Fixes: cf24f5a5feea ("xsk: add support for AF_XDP multi-buffer on Tx path") Link: https://patch.msgid.link/20260719135609.147823-5-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- Documentation/networking/af_xdp.rst | 54 ++++---- include/net/xsk_buff_pool.h | 3 + net/xdp/xsk.c | 189 ++++++++++++++++++++++++---- net/xdp/xsk_buff_pool.c | 1 + net/xdp/xsk_queue.h | 65 +++++++--- 5 files changed, 249 insertions(+), 63 deletions(-) diff --git a/Documentation/networking/af_xdp.rst b/Documentation/networking/af_xdp.rst index 50d92084a49c..cc3f0d16b28f 100644 --- a/Documentation/networking/af_xdp.rst +++ b/Documentation/networking/af_xdp.rst @@ -43,12 +43,13 @@ UMEM also has two rings: the FILL ring and the COMPLETION ring. The FILL ring is used by the application to send down addr for the kernel to fill in with RX packet data. References to these frames will then appear in the RX ring once each packet has been received. The -COMPLETION ring, on the other hand, contains frame addr that the -kernel has transmitted completely and can now be used again by user -space, for either TX or RX. Thus, the frame addrs appearing in the -COMPLETION ring are addrs that were previously transmitted using the -TX ring. In summary, the RX and FILL rings are used for the RX path -and the TX and COMPLETION rings are used for the TX path. +COMPLETION ring, on the other hand, contains frame addresses from Tx +descriptors that the kernel has finished processing and that can now be +used again by user space, for either Tx or Rx. This includes frames whose +transmission has completed as well as frames referenced by invalid Tx +descriptors rejected by the kernel. A completion therefore returns +ownership of a frame to user space, but does not by itself guarantee that +the packet was successfully transmitted. The socket is then finally bound with a bind() call to a device and a specific queue id on that device, and it is not until bind is @@ -169,14 +170,15 @@ chunks mode, then the incoming addr will be left untouched. UMEM Completion Ring ~~~~~~~~~~~~~~~~~~~~ -The COMPLETION Ring is used transfer ownership of UMEM frames from +The COMPLETION Ring is used to transfer ownership of UMEM frames from kernel-space to user-space. Just like the FILL ring, UMEM indices are -used. - -Frames passed from the kernel to user-space are frames that has been -sent (TX ring) and can be used by user-space again. - -The user application consumes UMEM addrs from this ring. +used. Frames passed from the kernel to user-space are frames referenced +by Tx descriptors that the kernel has finished processing and can be +used by user-space again. This includes both frames whose transmission +has completed and frames referenced by invalid Tx descriptors that were +rejected and reclaimed by the kernel. A completion entry does not +guarantee successful packet transmission. The user application consumes +UMEM addrs from this ring. RX Ring @@ -504,21 +506,25 @@ will be treated as an invalid descriptor. These are the semantics for producing packets onto AF_XDP Tx ring consisting of multiple frames: -* When an invalid descriptor is found, all the other - descriptors/frames of this packet are marked as invalid and not - completed. The next descriptor is treated as the start of a new - packet, even if this was not the intent (because we cannot guess - the intent). As before, if your program is producing invalid - descriptors you have a bug that must be fixed. +* When an invalid descriptor is found, the complete packet is treated as + invalid. The kernel consumes descriptors through the descriptor marking + the end of the packet and returns all their frame addresses through the + COMPLETION ring. A standalone invalid descriptor is treated as a + one-descriptor invalid packet. The descriptor following the end of the + invalid packet is treated as the start of a new packet. As before, if + your program is producing invalid descriptors you have a bug that must + be fixed. Rejected descriptors are reported in the ``tx_invalid_descs`` + statistic. * Zero length descriptors are treated as invalid descriptors. * For copy mode, the maximum supported number of frames in a packet is - equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all - descriptors accumulated so far are dropped and treated as - invalid. To produce an application that will work on any system - regardless of this config setting, limit the number of frags to 18, - as the minimum value of the config is 17. + equal to CONFIG_MAX_SKB_FRAGS + 1. If it is exceeded, all descriptors + through the end of the oversized packet are consumed, treated as invalid, + and their frame addresses are returned through the COMPLETION ring. To + produce an application that will work on any system regardless of this + config setting, limit the number of frags to 18, as the minimum value of + the config is 17. * For zero-copy mode, the limit is up to what the NIC HW supports. Usually at least five on the NICs we have checked. We diff --git a/include/net/xsk_buff_pool.h b/include/net/xsk_buff_pool.h index f5e737a83055..2bb1d122b1bc 100644 --- a/include/net/xsk_buff_pool.h +++ b/include/net/xsk_buff_pool.h @@ -78,6 +78,9 @@ struct xsk_buff_pool { u32 chunk_size; u32 chunk_shift; u32 frame_len; + u32 tx_descs_nentries; + u32 reclaim_descs; + u32 tx_zc_pending_descs; u32 xdp_zc_max_segs; u8 tx_metadata_len; /* inherited from umem */ u8 cached_need_wakeup; diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index 091792d1d82d..f906d51b6699 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -499,6 +499,23 @@ void __xsk_map_flush(struct list_head *flush_list) void xsk_tx_completed(struct xsk_buff_pool *pool, u32 nb_entries) { + u32 reclaim_descs = READ_ONCE(pool->reclaim_descs); + + if (unlikely(reclaim_descs)) { + u32 pending_descs = READ_ONCE(pool->tx_zc_pending_descs); + + if (nb_entries < pending_descs) { + WRITE_ONCE(pool->tx_zc_pending_descs, + pending_descs - nb_entries); + xskq_prod_submit_n(pool->cq, nb_entries); + return; + } + + WRITE_ONCE(pool->tx_zc_pending_descs, 0); + nb_entries += reclaim_descs; + WRITE_ONCE(pool->reclaim_descs, 0); + } + xskq_prod_submit_n(pool->cq, nb_entries); } EXPORT_SYMBOL(xsk_tx_completed); @@ -574,25 +591,158 @@ static u32 xsk_tx_peek_release_fallback(struct xsk_buff_pool *pool, u32 max_entr return nb_pkts; } -u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 nb_pkts) +static void xsk_tx_commit_batch(struct xsk_buff_pool *pool, + struct xsk_tx_batch *batch) { + u32 nb_descs = xsk_tx_batch_cq_descs(batch); + u32 cq_cached_prod; + + if (!nb_descs) + return; + + cq_cached_prod = pool->cq->cached_prod; + xskq_prod_write_addr_batch(pool->cq, pool->tx_descs, nb_descs); + + if (unlikely(batch->reclaim_descs)) { + u32 cq_pending_descs; + + /* CQ is positional. Descriptors already written but not + * submitted must complete before any reclaim-only descriptors + * appended below. + */ + cq_pending_descs = cq_cached_prod - xskq_get_prod(pool->cq); + + WRITE_ONCE(pool->tx_zc_pending_descs, + batch->tx_descs + cq_pending_descs); + WRITE_ONCE(pool->reclaim_descs, batch->reclaim_descs); + if (unlikely(!pool->tx_zc_pending_descs)) + xsk_tx_completed(pool, 0); + } +} + +static struct xsk_tx_batch +__xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, struct xdp_sock *xs, + struct xdp_desc *descs, u32 max_descs) +{ + struct xsk_tx_batch batch = {}; + u32 entries; + + entries = xskq_cons_nb_entries(xs->tx, max_descs); + if (!entries) + return batch; + + batch = xskq_cons_read_desc_batch(xs, pool, descs, max_descs); + if (!xsk_tx_batch_cq_descs(&batch)) { + xs->tx->queue_empty_descs++; + } else { + __xskq_cons_release(xs->tx); + xs->sk.sk_write_space(&xs->sk); + } + return batch; +} + +static struct xsk_tx_batch +xsk_tx_peek_release_shared_desc_batch(struct xsk_buff_pool *pool, u32 max_descs) +{ + u32 cq_descs_before, cq_descs_after; + struct xsk_tx_batch sum_batch = {}; + bool budget_exhausted; + u32 per_socket_budget; struct xdp_sock *xs; + /* The fairness quota must allow one maximum-sized valid packet. */ + per_socket_budget = max_t(u32, MAX_PER_SOCKET_BUDGET, + pool->xdp_zc_max_segs); + +again: + budget_exhausted = false; + cq_descs_before = xsk_tx_batch_cq_descs(&sum_batch); + list_for_each_entry_rcu(xs, &pool->xsk_tx_list, tx_list) { + u32 budget, budget_left, offset, remaining, used; + struct xsk_tx_batch curr_batch; + + /* Once reclaim-only descriptors have been appended to the CQ + * address area, do not append driver-visible Tx descriptors + * from another socket after them. xsk_tx_completed() relies on + * all driver-visible descriptors preceding all reclaim-only + * descriptors in CQ order. + */ + if (sum_batch.reclaim_descs) + break; + + /* be gentle when playing with pool->tx_descs */ + offset = xsk_tx_batch_cq_descs(&sum_batch); + if (offset >= max_descs) + break; + + if (xs->tx_budget_spent >= per_socket_budget) { + if (xskq_cons_nb_entries(xs->tx, 1)) + budget_exhausted = true; + continue; + } + + budget_left = per_socket_budget - xs->tx_budget_spent; + remaining = max_descs - offset; + budget = min(remaining, budget_left); + + curr_batch = __xsk_tx_peek_release_desc_batch(pool, xs, + pool->tx_descs + offset, + budget); + used = xsk_tx_batch_cq_descs(&curr_batch); + if (!used) { + if (curr_batch.budget_limited && budget_left < remaining) + budget_exhausted = true; + continue; + } + + xs->tx_budget_spent += used; + sum_batch.tx_descs += curr_batch.tx_descs; + sum_batch.reclaim_descs = curr_batch.reclaim_descs; + } + + cq_descs_after = xsk_tx_batch_cq_descs(&sum_batch); + + if (sum_batch.reclaim_descs || cq_descs_after >= max_descs) + return sum_batch; + + /* Continue filling the batch while this pass made progress */ + if (cq_descs_before != cq_descs_after) + goto again; + + if (!budget_exhausted) + return sum_batch; + + list_for_each_entry_rcu(xs, &pool->xsk_tx_list, tx_list) + xs->tx_budget_spent = 0; + goto again; +} + +u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 nb_pkts) +{ + struct xsk_tx_batch batch = {}; + struct xdp_sock *xs; + bool umem_shared; + rcu_read_lock(); - if (!list_is_singular(&pool->xsk_tx_list)) { - /* Fallback to the non-batched version */ + if (unlikely(READ_ONCE(pool->reclaim_descs))) + goto out; + + xs = list_first_or_null_rcu(&pool->xsk_tx_list, struct xdp_sock, + tx_list); + if (!xs) + goto out; + + nb_pkts = min(nb_pkts, pool->tx_descs_nentries); + if (!nb_pkts) + goto out; + + umem_shared = !list_is_singular(&pool->xsk_tx_list); + + if (umem_shared && !(pool->umem->flags & XDP_UMEM_SG_FLAG)) { rcu_read_unlock(); return xsk_tx_peek_release_fallback(pool, nb_pkts); } - xs = list_first_or_null_rcu(&pool->xsk_tx_list, struct xdp_sock, tx_list); - if (!xs) { - nb_pkts = 0; - goto out; - } - - nb_pkts = xskq_cons_nb_entries(xs->tx, nb_pkts); - /* This is the backpressure mechanism for the Tx path. Try to * reserve space in the completion queue for all packets, but * if there are fewer slots available, just process that many @@ -603,19 +753,16 @@ u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 nb_pkts) if (!nb_pkts) goto out; - nb_pkts = xskq_cons_read_desc_batch(xs->tx, pool, nb_pkts); - if (!nb_pkts) { - xs->tx->queue_empty_descs++; - goto out; - } - - __xskq_cons_release(xs->tx); - xskq_prod_write_addr_batch(pool->cq, pool->tx_descs, nb_pkts); - xs->sk.sk_write_space(&xs->sk); + batch = umem_shared ? + xsk_tx_peek_release_shared_desc_batch(pool, nb_pkts) : + __xsk_tx_peek_release_desc_batch(pool, xs, + pool->tx_descs, + nb_pkts); + xsk_tx_commit_batch(pool, &batch); out: rcu_read_unlock(); - return nb_pkts; + return batch.tx_descs; } EXPORT_SYMBOL(xsk_tx_peek_release_desc_batch); diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index 12c9fb29af05..a4089480b22b 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -51,6 +51,7 @@ int xp_alloc_tx_descs(struct xsk_buff_pool *pool, struct xdp_sock *xs, if (!pool->tx_descs) return -ENOMEM; + pool->tx_descs_nentries = nentries; return 0; } diff --git a/net/xdp/xsk_queue.h b/net/xdp/xsk_queue.h index 3e3fbb73d23e..1bc42c8902f4 100644 --- a/net/xdp/xsk_queue.h +++ b/net/xdp/xsk_queue.h @@ -58,6 +58,17 @@ struct parsed_desc { u32 valid; }; +struct xsk_tx_batch { + u32 tx_descs; + u32 reclaim_descs; + bool budget_limited; +}; + +static inline u32 xsk_tx_batch_cq_descs(const struct xsk_tx_batch *batch) +{ + return batch->tx_descs + batch->reclaim_descs; +} + /* The structure of the shared state of the rings are a simple * circular buffer, as outlined in * Documentation/core-api/circular-buffers.rst. For the Rx and @@ -263,17 +274,18 @@ static inline void parse_desc(struct xsk_queue *q, struct xsk_buff_pool *pool, parsed->mb = xp_mb_desc(desc); } -static inline -u32 xskq_cons_read_desc_batch(struct xsk_queue *q, struct xsk_buff_pool *pool, - u32 max) +static inline struct xsk_tx_batch +xskq_cons_read_desc_batch(struct xdp_sock *xs, struct xsk_buff_pool *pool, + struct xdp_desc *descs, u32 max) { - u32 cached_cons = q->cached_cons, nb_entries = 0; - struct xdp_desc *descs = pool->tx_descs; - u32 total_descs = 0, nr_frags = 0; + bool drain = READ_ONCE(xs->drain_cont); + u32 cached_cons, nb_entries = 0; + struct xsk_tx_batch batch = {}; + struct xsk_queue *q = xs->tx; + u32 nr_frags = 0; + + cached_cons = q->cached_cons; - /* track first entry, if stumble upon *any* invalid descriptor, rewind - * current packet that consists of frags and stop the processing - */ while (cached_cons != q->cached_prod && nb_entries < max) { struct xdp_rxtx_ring *ring = (struct xdp_rxtx_ring *)q->ring; u32 idx = cached_cons & q->ring_mask; @@ -283,25 +295,42 @@ u32 xskq_cons_read_desc_batch(struct xsk_queue *q, struct xsk_buff_pool *pool, cached_cons++; parse_desc(q, pool, &descs[nb_entries], &parsed); if (unlikely(!parsed.valid)) - break; + drain = true; + + nr_frags++; + nb_entries++; if (likely(!parsed.mb)) { - total_descs += (nr_frags + 1); - nr_frags = 0; - } else { - nr_frags++; - if (nr_frags == pool->xdp_zc_max_segs) { + if (unlikely(drain)) { + batch.reclaim_descs = nr_frags; + WRITE_ONCE(xs->drain_cont, false); nr_frags = 0; break; } + + batch.tx_descs += nr_frags; + nr_frags = 0; + continue; + } + + if (nr_frags == pool->xdp_zc_max_segs) + drain = true; + } + + if (nr_frags) { + if (drain) { + batch.reclaim_descs = nr_frags; + WRITE_ONCE(xs->drain_cont, true); + } else { + if (nb_entries == max) + batch.budget_limited = true; + cached_cons -= nr_frags; } - nb_entries++; } - cached_cons -= nr_frags; /* Release valid plus any invalid entries */ xskq_cons_release_n(q, cached_cons - q->cached_cons); - return total_descs; + return batch; } /* Functions for consumers */ From c5b1ca6a02886f00170ed91b757e244f23259e91 Mon Sep 17 00:00:00 2001 From: Maciej Fijalkowski Date: Sun, 19 Jul 2026 15:56:08 +0200 Subject: [PATCH 288/456] selftests/xsk: fix too-many-frags multi-buffer Tx test The too-many-frags test describes a packet that is valid from the Tx ring ownership point of view, but invalid for transmission because it exceeds the supported number of fragments. Keep the generated Tx descriptors valid so that __send_pkts() accounts them as outstanding descriptors that must be reclaimed through the CQ. Then mark the corresponding Rx packet invalid so the test still does not expect the oversized packet to appear on the receive side. Add a valid synchronization packet after the oversized packet so the test can verify that the Tx path drains the bad packet and resumes at the next packet boundary. Reviewed-by: Jason Xing Signed-off-by: Maciej Fijalkowski Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260719135609.147823-6-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- .../selftests/bpf/prog_tests/test_xsk.c | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index 6eb9096d084c..de17dd48f176 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -2270,7 +2270,7 @@ int testapp_too_many_frags(struct test_spec *test) max_frags += 1; } - pkts = calloc(2 * max_frags + 2, sizeof(struct pkt)); + pkts = calloc(2 * max_frags + 3, sizeof(struct pkt)); if (!pkts) return TEST_FAILURE; @@ -2288,24 +2288,30 @@ int testapp_too_many_frags(struct test_spec *test) } pkts[max_frags].options = 0; - /* An invalid packet with the max amount of frags but signals packet - * continues on the last frag - */ - for (i = max_frags + 1; i < 2 * max_frags + 1; i++) { + /* An invalid packet with the max + 1 amount of frags */ + for (i = max_frags + 1; i < 2 * max_frags + 2; i++) { pkts[i].len = MIN_PKT_SIZE; pkts[i].options = XDP_PKT_CONTD; - pkts[i].valid = false; + pkts[i].valid = true; } + pkts[2 * max_frags + 1].options = 0; /* Valid packet for synch */ - pkts[2 * max_frags + 1].len = MIN_PKT_SIZE; - pkts[2 * max_frags + 1].valid = true; + pkts[2 * max_frags + 2].len = MIN_PKT_SIZE; + pkts[2 * max_frags + 2].valid = true; - if (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 2)) { + if (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 3)) { free(pkts); return TEST_FAILURE; } + /* The generated Tx stream must keep the too-big packet valid so that + * __send_pkts() accounts its descriptors in outstanding_tx. The Rx + * stream, however, must not expect this packet on the wire. + */ + test->ifobj_rx->xsk->pkt_stream->pkts[2].valid = false; + test->ifobj_rx->xsk->pkt_stream->nb_valid_entries--; + ret = testapp_validate_traffic(test); free(pkts); return ret; From f49d99eaee7c32badc7ddfaecbb01ce4d037d695 Mon Sep 17 00:00:00 2001 From: Maciej Fijalkowski Date: Sun, 19 Jul 2026 15:56:09 +0200 Subject: [PATCH 289/456] selftests/xsk: account reclaimed invalid Tx descriptors Invalid Tx descriptors are now returned through the completion ring, regardless of whether they form a standalone packet or belong to an invalid multi-buffer packet. The selftests previously counted only descriptors belonging to valid packets, with a special exception for some invalid multi-buffer packets in verbatim streams. This undercounts completion entries when a standalone invalid descriptor or another invalid packet is reclaimed by the kernel. Keep valid_pkts as the number of packets expected on the Rx side, but count every descriptor submitted to the Tx ring in valid_frags, as every such descriptor is now expected to be returned through the completion ring. Make fragment counting in verbatim mode follow the packet boundary instead of stopping at the first invalid fragment. Update custom stream generation so an invalid middle fragment terminates the generated Rx packet while Tx completion accounting still covers the complete invalid packet. Also add explicit end fragments after invalid middle descriptors. This exercises the kernel drain logic and verifies that subsequent valid packets are not interpreted as continuations of the invalid packet. Reviewed-by: Jason Xing Signed-off-by: Maciej Fijalkowski Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260719135609.147823-7-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- .../selftests/bpf/prog_tests/test_xsk.c | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index de17dd48f176..38ce6060b8fa 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -427,14 +427,14 @@ static u32 pkt_nb_frags(u32 frame_size, struct pkt_stream *pkt_stream, struct pk } /* Search for the end of the packet in verbatim mode */ - if (!pkt_continues(pkt->options) || !pkt->valid) + if (!pkt_continues(pkt->options)) return nb_frags; next_frag = pkt_stream->current_pkt_nb; pkt++; while (next_frag++ < pkt_stream->nb_pkts) { nb_frags++; - if (!pkt_continues(pkt->options) || !pkt->valid) + if (!pkt_continues(pkt->options)) break; pkt++; } @@ -665,11 +665,11 @@ static struct pkt_stream *__pkt_stream_generate_custom(struct ifobject *ifobj, s if (!frame->valid || !pkt_continues(frame->options)) payload++; } else { - if (frame->valid) + if (frame->valid) { len += frame->len; - if (frame->valid && pkt_continues(frame->options)) - continue; - + if (pkt_continues(frame->options)) + continue; + } pkt->pkt_nb = pkt_nb; pkt->len = len; pkt->valid = frame->valid; @@ -1250,10 +1250,9 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk, } } - if (pkt && pkt->valid) { + if (pkt && pkt->valid) valid_pkts++; - valid_frags += nb_frags; - } + valid_frags += nb_frags; } pthread_mutex_lock(&pacing_mutex); @@ -2099,13 +2098,16 @@ int testapp_invalid_desc_mb(struct test_spec *test) {0, 0, 0, false, 0}, /* Invalid address in the second frame */ {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - {umem_sz, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {umem_sz * 2, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {0, MIN_PKT_SIZE, 0, false, 0}, /* Invalid len in the middle */ {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, {0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {0, MIN_PKT_SIZE, 0, false, 0}, /* Invalid options in the middle */ {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XSK_DESC__INVALID_OPTION}, + {0, MIN_PKT_SIZE, 0, false, 0}, /* Transmit 2 frags, receive 3 */ {0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, XDP_PKT_CONTD}, {0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, 0}, @@ -2117,8 +2119,8 @@ int testapp_invalid_desc_mb(struct test_spec *test) if (umem->unaligned_mode) { /* Crossing a chunk boundary allowed */ - pkts[12].valid = true; - pkts[13].valid = true; + pkts[15].valid = true; + pkts[16].valid = true; } test->mtu = MAX_ETH_JUMBO_SIZE; From 79097812153b826fc156a2930ec8a90ed9edf4a2 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 24 Jul 2026 04:05:17 +0100 Subject: [PATCH 290/456] tracing: Fix use-after-free freeing trigger private data Commit 61d445af0a7c ("tracing: Add bulk garbage collection of freeing event_trigger_data") moved the kfree() of event_trigger_data to a kthread that runs tracepoint_synchronize_unregister() before freeing. That removed the synchronization the trigger .free callbacks used to get implicitly and inline from trigger_data_free(). event_hist_trigger_free(), event_hist_trigger_named_free() and event_enable_trigger_free() free their satellite data (hist_data, cmd_ops, enable_data) right after trigger_data_free() returns. With the synchronization now deferred to the kthread, a concurrent tracepoint handler can still reach that data through the list_del_rcu()'d trigger, causing a use-after-free. The histogram teardown must stay synchronous: remove_hist_vars() and unregister_field_var_hists() have to detach a synthetic event from the histogram before the trigger-removal write returns, otherwise a following command races in and the synthetic-event removal fails with -EBUSY, as the trigger-synthetic-eprobe.tc selftest catches. Make those callbacks wait with the correct barrier - tracepoint_synchronize_unregister(), matching the free kthread - before freeing. The enable trigger has no such synchronous requirement, and a blocking synchronize there would re-serialize the path that commit deliberately deferred. Give it an optional private_data_free() callback that the free kthread runs after its grace period, and free enable_data from there. Link: https://patch.msgid.link/20260724030523.19081-1-devnexen@gmail.com Suggested-by: Masami Hiramatsu (Google) Suggested-by: Steven Rostedt Fixes: 61d445af0a7c ("tracing: Add bulk garbage collection of freeing event_trigger_data") Signed-off-by: David Carlier Signed-off-by: Steven Rostedt --- kernel/trace/trace.h | 1 + kernel/trace/trace_events_hist.c | 2 ++ kernel/trace/trace_events_trigger.c | 18 +++++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 80fe152af1dd..bf77331f56a4 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -1941,6 +1941,7 @@ struct event_trigger_data { struct list_head named_list; struct event_trigger_data *named_data; struct llist_node llist; + void (*private_data_free)(struct event_trigger_data *data); }; /* Avoid typos */ diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 82ce492ab268..58d28cd1afa3 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6349,6 +6349,7 @@ static void event_hist_trigger_free(struct event_trigger_data *data) trigger_data_free(data); + tracepoint_synchronize_unregister(); remove_hist_vars(hist_data); unregister_field_var_hists(hist_data); @@ -6388,6 +6389,7 @@ static void event_hist_trigger_named_free(struct event_trigger_data *data) del_named_trigger(data); trigger_data_free(data); + tracepoint_synchronize_unregister(); kfree(cmd_ops); } } diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c index 655db2e82513..46e60b70a4bb 100644 --- a/kernel/trace/trace_events_trigger.c +++ b/kernel/trace/trace_events_trigger.c @@ -38,6 +38,13 @@ static void trigger_create_kthread_locked(void) } } +static void trigger_data_free_one(struct event_trigger_data *data) +{ + if (data->private_data_free) + data->private_data_free(data); + kfree(data); +} + static void trigger_data_free_queued_locked(void) { struct event_trigger_data *data, *tmp; @@ -52,7 +59,7 @@ static void trigger_data_free_queued_locked(void) tracepoint_synchronize_unregister(); llist_for_each_entry_safe(data, tmp, llnodes, llist) - kfree(data); + trigger_data_free_one(data); } /* Bulk garbage collection of event_trigger_data elements */ @@ -75,7 +82,7 @@ static int trigger_kthread_fn(void *ignore) tracepoint_synchronize_unregister(); llist_for_each_entry_safe(data, tmp, llnodes, llist) - kfree(data); + trigger_data_free_one(data); } return 0; @@ -1717,6 +1724,11 @@ int event_enable_trigger_print(struct seq_file *m, return 0; } +static void enable_trigger_private_data_free(struct event_trigger_data *data) +{ + kfree(data->private_data); +} + void event_enable_trigger_free(struct event_trigger_data *data) { struct enable_trigger_data *enable_data = data->private_data; @@ -1728,9 +1740,9 @@ void event_enable_trigger_free(struct event_trigger_data *data) if (!data->ref) { /* Remove the SOFT_MODE flag */ trace_event_enable_disable(enable_data->file, 0, 1); + data->private_data_free = enable_trigger_private_data_free; trace_event_put_ref(enable_data->file->event_call); trigger_data_free(data); - kfree(enable_data); } } From e091351b38818ef620d27f44f4bfd625f13afbff Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 24 Jul 2026 13:24:15 -0400 Subject: [PATCH 291/456] tracing: Delay module ref count for "enable_event" trigger Triggers are now delayed from freeing, but can still be triggered until after the RCU grace period has ended. The freeing of the enable_event data is put into the private_data_free() callback, but the put of the module refcount is done immediately. It is possible that if a module is removed that has an event that would enable (or disable) it is still active, it can read the data of the module after it is removed causing a use-after-free bug. Move the trace_event_put_ref() that releases the module into the delayed callback so that the module can not be removed until any reference to its events are finished. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260724132415.1b5005db@gandalf.local.home Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724030523.19081-1-devnexen%40gmail.com Fixes: 61d445af0a7c ("tracing: Add bulk garbage collection of freeing event_trigger_data") Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_trigger.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c index 46e60b70a4bb..ad83419cb420 100644 --- a/kernel/trace/trace_events_trigger.c +++ b/kernel/trace/trace_events_trigger.c @@ -1726,7 +1726,10 @@ int event_enable_trigger_print(struct seq_file *m, static void enable_trigger_private_data_free(struct event_trigger_data *data) { - kfree(data->private_data); + struct enable_trigger_data *enable_data = data->private_data; + + trace_event_put_ref(enable_data->file->event_call); + kfree(enable_data); } void event_enable_trigger_free(struct event_trigger_data *data) @@ -1741,7 +1744,6 @@ void event_enable_trigger_free(struct event_trigger_data *data) /* Remove the SOFT_MODE flag */ trace_event_enable_disable(enable_data->file, 0, 1); data->private_data_free = enable_trigger_private_data_free; - trace_event_put_ref(enable_data->file->event_call); trigger_data_free(data); } } From 2c1bd78dc8e2221549409769a76c39304b82a1b0 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 14 Jul 2026 14:59:44 +0200 Subject: [PATCH 292/456] dpll: use pin owner's dpll ref for pin-level attribute reporting Commit c191b319f208 ("dpll: allow registering FW-identified pin with a different DPLL") relaxed dpll_pin_register() to let fwnode-identified pins register with DPLLs from a different driver. This allows, for example, the ICE driver to register a zl3073x-created pin with its TXC DPLL using ice_dpll_txclk_ops, which lack frequency_get and phase_adjust_get callbacks. After such cross-driver registration, the pin's dpll_refs xarray contains refs from both drivers. dpll_cmd_pin_get_one() calls dpll_xa_ref_dpll_first() which returns the ref with the lowest DPLL id. When the foreign DPLL (e.g. ICE TXC) has a lower id than the owner DPLL (e.g. zl3073x), the foreign ops are used for reporting. Since those ops lack callbacks like frequency_get, pin-level attributes are silently omitted from the netlink response. For example, a zl3073x output pin that should report frequency and phase-adjust shows neither: Before: # dpll pin show id 45 pin id 45: module-name: zl3073x clock-id: 3427468959636104019 board-label: 156M25_NAC0_CLKREF_SYNC package-label: OUT3 type: synce-eth-port capabilities: 0x0 phase-adjust-min: -2147483648 phase-adjust-max: 2147483647 phase-adjust-gran: 800 parent-device: ... After: # dpll pin show id 19 pin id 19: module-name: zl3073x clock-id: 15964355450360090479 board-label: 156M25_NAC0_CLKREF_SYNC package-label: OUT3 type: synce-eth-port frequency: 156250000 Hz frequency-supported: 156250000 Hz capabilities: 0x0 phase-adjust-min: -2147483648 phase-adjust-max: 2147483647 phase-adjust-gran: 800 phase-adjust: 0 parent-device: ... Fix this by: 1. Adding dpll_pin_own_dpll_ref_first() helper that returns the first ref whose DPLL matches the pin's (module, clock_id) tuple -- i.e. the DPLL from the driver that created the pin and has the complete set of ops. Return NULL if no owner ref is found. 2. Using dpll_pin_own_dpll_ref_first() in dpll_cmd_pin_get_one() with a fallback to dpll_xa_ref_dpll_first() for pin-on-pin child pins whose dpll_refs all point to a different driver's DPLLs. 3. Using dpll_pin_own_dpll_ref_first() in SET operations (dpll_pin_freq_set, dpll_pin_esync_set, dpll_pin_ref_sync_state_set, dpll_pin_phase_adj_set) returning -ENODEV if no owner ref exists. Replacing the validation loops that rejected the entire operation when any ref's ops lacked the required callback -- instead validate only the owner refs so that foreign DPLLs with incomplete ops no longer block SET operations. 4. Guarding all SET and rollback xa_for_each loops against NULL set callbacks so that foreign refs without the operation are safely skipped instead of causing a NULL pointer dereference. Fixes: c191b319f208 ("dpll: allow registering FW-identified pin with a different DPLL") Signed-off-by: Ivan Vecera Acked-by: Vadim Fedorenko Link: https://patch.msgid.link/20260714125945.1823269-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- drivers/dpll/dpll_core.c | 27 +++++++++++++++++ drivers/dpll/dpll_core.h | 1 + drivers/dpll/dpll_netlink.c | 60 ++++++++++++++++++++++++++++++------- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 2e8690cb3c16..43d51d942ead 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -1142,6 +1142,33 @@ void *dpll_pin_on_pin_priv(struct dpll_pin *parent, return reg->priv; } +/** + * dpll_pin_own_dpll_ref_first - find the first owner dpll ref of a pin + * @pin: pointer to a dpll pin + * + * Search pin's dpll_refs for a ref whose dpll matches the pin's + * (module, clock_id) tuple, i.e. the dpll registered by the driver + * that created the pin. This ensures pin-level attributes are + * reported and modified using the owner's ops even when the pin is + * also registered with dplls from other drivers. + * + * Return: pointer to the owner's dpll_pin_ref, or NULL if no + * owner ref is found. + */ +struct dpll_pin_ref *dpll_pin_own_dpll_ref_first(struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long i; + + xa_for_each(&pin->dpll_refs, i, ref) { + if (ref->dpll->module == pin->module && + ref->dpll->clock_id == pin->clock_id) + return ref; + } + + return NULL; +} + const struct dpll_pin_ops *dpll_pin_ops(struct dpll_pin_ref *ref) { struct dpll_pin_registration *reg; diff --git a/drivers/dpll/dpll_core.h b/drivers/dpll/dpll_core.h index e24577113431..da8a369556ed 100644 --- a/drivers/dpll/dpll_core.h +++ b/drivers/dpll/dpll_core.h @@ -93,6 +93,7 @@ void *dpll_pin_on_pin_priv(struct dpll_pin *parent, struct dpll_pin *pin); const struct dpll_device_ops *dpll_device_ops(struct dpll_device *dpll); struct dpll_device *dpll_device_get_by_id(int id); +struct dpll_pin_ref *dpll_pin_own_dpll_ref_first(struct dpll_pin *pin); const struct dpll_pin_ops *dpll_pin_ops(struct dpll_pin_ref *ref); struct dpll_pin_ref *dpll_xa_ref_dpll_first(struct xarray *xa_refs); extern struct xarray dpll_device_xa; diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 5703667593a7..afb31c004038 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -699,7 +699,9 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin, struct dpll_pin_ref *ref; int ret; - ref = dpll_xa_ref_dpll_first(&pin->dpll_refs); + ref = dpll_pin_own_dpll_ref_first(pin); + if (!ref) + ref = dpll_xa_ref_dpll_first(&pin->dpll_refs); ASSERT_NOT_NULL(ref); ret = dpll_msg_add_pin_handle(msg, pin); @@ -1090,12 +1092,19 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, xa_for_each(&pin->dpll_refs, i, ref) { ops = dpll_pin_ops(ref); - if (!ops->frequency_set || !ops->frequency_get) { - NL_SET_ERR_MSG(extack, "frequency set not supported by the device"); + if ((!ops->frequency_set || !ops->frequency_get) && + ref->dpll->module == pin->module && + ref->dpll->clock_id == pin->clock_id) { + NL_SET_ERR_MSG(extack, + "frequency set not supported by the device"); return -EOPNOTSUPP; } } - ref = dpll_xa_ref_dpll_first(&pin->dpll_refs); + ref = dpll_pin_own_dpll_ref_first(pin); + if (!ref) { + NL_SET_ERR_MSG(extack, "pin owner dpll not found"); + return -ENODEV; + } ops = dpll_pin_ops(ref); dpll = ref->dpll; ret = ops->frequency_get(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, @@ -1109,6 +1118,8 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, xa_for_each(&pin->dpll_refs, i, ref) { ops = dpll_pin_ops(ref); + if (!ops->frequency_set) + continue; dpll = ref->dpll; ret = ops->frequency_set(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, dpll_priv(dpll), freq, extack); @@ -1128,6 +1139,8 @@ dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, if (ref == failed) break; ops = dpll_pin_ops(ref); + if (!ops->frequency_set) + continue; dpll = ref->dpll; if (ops->frequency_set(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, dpll_priv(dpll), old_freq, extack)) @@ -1151,13 +1164,19 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, xa_for_each(&pin->dpll_refs, i, ref) { ops = dpll_pin_ops(ref); - if (!ops->esync_set || !ops->esync_get) { + if ((!ops->esync_set || !ops->esync_get) && + ref->dpll->module == pin->module && + ref->dpll->clock_id == pin->clock_id) { NL_SET_ERR_MSG(extack, "embedded sync feature is not supported by this device"); return -EOPNOTSUPP; } } - ref = dpll_xa_ref_dpll_first(&pin->dpll_refs); + ref = dpll_pin_own_dpll_ref_first(pin); + if (!ref) { + NL_SET_ERR_MSG(extack, "pin owner dpll not found"); + return -ENODEV; + } ops = dpll_pin_ops(ref); dpll = ref->dpll; ret = ops->esync_get(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, @@ -1181,6 +1200,8 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, void *pin_dpll_priv; ops = dpll_pin_ops(ref); + if (!ops->esync_set) + continue; dpll = ref->dpll; pin_dpll_priv = dpll_pin_on_dpll_priv(dpll, pin); ret = ops->esync_set(pin, pin_dpll_priv, dpll, dpll_priv(dpll), @@ -1204,6 +1225,8 @@ dpll_pin_esync_set(struct dpll_pin *pin, struct nlattr *a, if (ref == failed) break; ops = dpll_pin_ops(ref); + if (!ops->esync_set) + continue; dpll = ref->dpll; pin_dpll_priv = dpll_pin_on_dpll_priv(dpll, pin); if (ops->esync_set(pin, pin_dpll_priv, dpll, dpll_priv(dpll), @@ -1238,8 +1261,11 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, NL_SET_ERR_MSG(extack, "reference sync pin not available"); return -EINVAL; } - ref = dpll_xa_ref_dpll_first(&pin->dpll_refs); - ASSERT_NOT_NULL(ref); + ref = dpll_pin_own_dpll_ref_first(pin); + if (!ref) { + NL_SET_ERR_MSG(extack, "pin owner dpll not found"); + return -ENODEV; + } ops = dpll_pin_ops(ref); if (!ops->ref_sync_set || !ops->ref_sync_get) { NL_SET_ERR_MSG(extack, "reference sync not supported by this pin"); @@ -1258,6 +1284,8 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, return 0; xa_for_each(&pin->dpll_refs, i, ref) { ops = dpll_pin_ops(ref); + if (!ops->ref_sync_set) + continue; dpll = ref->dpll; ret = ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), ref_sync_pin, @@ -1280,6 +1308,8 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, if (ref == failed) break; ops = dpll_pin_ops(ref); + if (!ops->ref_sync_set) + continue; dpll = ref->dpll; if (ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), ref_sync_pin, @@ -1471,12 +1501,18 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, xa_for_each(&pin->dpll_refs, i, ref) { ops = dpll_pin_ops(ref); - if (!ops->phase_adjust_set || !ops->phase_adjust_get) { + if ((!ops->phase_adjust_set || !ops->phase_adjust_get) && + ref->dpll->module == pin->module && + ref->dpll->clock_id == pin->clock_id) { NL_SET_ERR_MSG(extack, "phase adjust not supported"); return -EOPNOTSUPP; } } - ref = dpll_xa_ref_dpll_first(&pin->dpll_refs); + ref = dpll_pin_own_dpll_ref_first(pin); + if (!ref) { + NL_SET_ERR_MSG(extack, "pin owner dpll not found"); + return -ENODEV; + } ops = dpll_pin_ops(ref); dpll = ref->dpll; ret = ops->phase_adjust_get(pin, dpll_pin_on_dpll_priv(dpll, pin), @@ -1491,6 +1527,8 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, xa_for_each(&pin->dpll_refs, i, ref) { ops = dpll_pin_ops(ref); + if (!ops->phase_adjust_set) + continue; dpll = ref->dpll; ret = ops->phase_adjust_set(pin, dpll_pin_on_dpll_priv(dpll, pin), @@ -1513,6 +1551,8 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, if (ref == failed) break; ops = dpll_pin_ops(ref); + if (!ops->phase_adjust_set) + continue; dpll = ref->dpll; if (ops->phase_adjust_set(pin, dpll_pin_on_dpll_priv(dpll, pin), dpll, dpll_priv(dpll), old_phase_adj, From a3729e0df005a936ceb3c2b0d167f01a2b03f970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20K=C3=B6ppeler?= Date: Mon, 20 Jul 2026 23:14:52 +0200 Subject: [PATCH 293/456] net/sched: sch_cake: skip clearing unused tins during rate adjustment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When cake_configure_rates() is called from the dequeue path with rate_adjust=true, it only needs to update the rate parameters. The loop that clears the unused tins is both unnecessary and harmful in this path: - cake_clear_tin() overwrites q->cur_tin and q->cur_flow, which are actively used by cake_dequeue(), corrupting the dequeue state. - iterating over the unused tins and their internal queues to purge packets adds needless overhead to the hot path. Skip the entire loop when rate_adjust is set, as neither cake_clear_tin() nor the mtu_time update are needed when only the rate changes. The clearing loop runs on every rate adjustment from the dequeue path, clearing (max_tins - cur_tins) tins each time, so the cost grows the fewer tins the configured mode actually uses. Testing cake_mq over veth (8 rx/tx queues, 2 Gbit limit) with flent's [1] rrul and tcp_nup tests and 32 TCP upstreams shows a large drop in loaded latency and a throughput gain, restoring behaviour to pre-15c2715a5264 levels: +------------+------+------+-------+-------+---------+ | kernel | mode | test | base | load | tput | | | | | (ms) | (ms) | (Mbit) | +------------+------+------+-------+-------+---------+ | net-next | be | rrul | 0.810 | 11.78 | 1469.67 | | net-next | be | nup | 0.637 | 85.71 | 1243.15 | | net-next | ds3 | rrul | 0.397 | 15.28 | 1770.06 | | net-next | ds3 | nup | 0.351 | 15.98 | 1799.39 | +------------+------+------+-------+-------+---------+ | patched | be | rrul | 0.092 | 0.56 | 1873.40 | | patched | be | nup | 0.109 | 1.82 | 1869.12 | | patched | ds3 | rrul | 0.097 | 0.98 | 1866.10 | | patched | ds3 | nup | 0.101 | 0.51 | 1861.79 | +------------+------+------+-------+-------+---------+ The same trend holds on real hardware (IPQ8074A, 4 rx/tx queues, OpenWrt): in besteffort mode the tcp_nup loaded latency drops from ~470 ms to ~4 ms. [1] https://flent.org Fixes: 15c2715a5264 ("net/sched: sch_cake: fixup cake_mq rate adjustment for diffserv config") Signed-off-by: Jonas Köppeler Tested-by: Mike Pham Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260720-sch_cake-skip-clearing-tins-v2-1-e6a8b0275c73@tu-berlin.de Signed-off-by: Jakub Kicinski --- net/sched/sch_cake.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index 505f63fecf64..f64be54ead49 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -2609,9 +2609,11 @@ static void cake_configure_rates(struct Qdisc *sch, u64 rate, bool rate_adjust) break; } - for (c = qd->tin_cnt; c < CAKE_MAX_TINS; c++) { - cake_clear_tin(sch, c); - qd->tins[c].cparams.mtu_time = qd->tins[ft].cparams.mtu_time; + if (!rate_adjust) { + for (c = qd->tin_cnt; c < CAKE_MAX_TINS; c++) { + cake_clear_tin(sch, c); + qd->tins[c].cparams.mtu_time = qd->tins[ft].cparams.mtu_time; + } } qd->rate_ns = qd->tins[ft].tin_rate_ns; From 817ff6efdb7f484ea547218e11e17d8e43daa3b4 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 19 Jul 2026 22:57:40 +0800 Subject: [PATCH 294/456] net: pktgen: fix proc entry use-after-free pktgen_change_name() replaces pkt_dev->entry while holding t->if_lock. pktgen_remove_device() removes the same entry before _rem_dev_from_if_list() takes that lock. This allows the following interleaving: CPU 0 (NETDEV_CHANGENAME) CPU 1 (kpktgend) if_lock(t) proc_remove(pkt_dev->entry) proc_remove(pkt_dev->entry) pkt_dev->entry = proc_create_data(...) if_unlock(t) The kthread can pass the stale proc_dir_entry to proc_remove() after the rename path has freed it. A reproducer with a widened race window reports: BUG: KASAN: slab-use-after-free in proc_remove+0x78/0x80 Read of size 8 at addr ffff8881478fea70 by task kpktgend_0/67 Call Trace: proc_remove+0x78/0x80 pktgen_remove_device.isra.0+0x11c/0x4c0 pktgen_thread_worker+0x1214/0x6bc0 kthread+0x2c6/0x3b0 Allocated by task 95: __proc_create+0x204/0x790 proc_create_data+0x72/0xe0 pktgen_thread_write+0xd61/0x1510 Freed by task 28: kmem_cache_free+0xcb/0x3d0 proc_free_inode+0x5b/0x80 rcu_core+0x50a/0x1850 The buggy address belongs to the object at ffff8881478fea00 which belongs to the cache proc_dir_entry of size 192 Move proc_remove() into the if_lock-protected list removal helper. Keep it before list_del_rcu() to preserve the ordering required by add_device(). The rename path must then finish replacing the entry before removal, or it observes that the device is no longer on the list. Fixes: 39df232f1a9b ("[PKTGEN]: fix device name handling") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260719145740.2888967-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/core/pktgen.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/core/pktgen.c b/net/core/pktgen.c index 8e185b318288..ee64f3012321 100644 --- a/net/core/pktgen.c +++ b/net/core/pktgen.c @@ -3972,6 +3972,7 @@ static void _rem_dev_from_if_list(struct pktgen_thread *t, struct pktgen_dev *p; if_lock(t); + proc_remove(pkt_dev->entry); list_for_each_safe(q, n, &t->if_list) { p = list_entry(q, struct pktgen_dev, list); if (p == pkt_dev) @@ -4001,9 +4002,6 @@ static int pktgen_remove_device(struct pktgen_thread *t, * list to determine if interface already exist, avoid race * with proc_create_data() */ - proc_remove(pkt_dev->entry); - - /* And update the thread if_list */ _rem_dev_from_if_list(t, pkt_dev); #ifdef CONFIG_XFRM From d0d6415963040c401e7a7e4e482a698ba52448cb Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Wed, 22 Jul 2026 20:19:25 +0100 Subject: [PATCH 295/456] veth: convert frag_list skbs before running XDP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A frag_list skb can reach veth with data_len set but nr_frags zero. veth_convert_skb_to_xdp_buff() only converts skbs that are shared, locked, have frags[], or do not have enough headroom. It later uses skb_is_nonlinear() to decide whether to set XDP_FLAGS_HAS_FRAGS and xdp_frags_size. That exposes frag_list data to XDP as if it were stored in frags[], but frags[] is empty. AF_XDP copy mode can then trust the bogus XDP fragment metadata, walk an empty fragment entry, and crash in memcpy() from __xsk_rcv(). Route non-linear skbs through skb_pp_cow_data() before exposing them to XDP, and only advertise XDP frags when the resulting skb has frags[]. skb_copy_bits() already handles frag_list input, and skb_pp_cow_data() builds frags[] output with skb_add_rx_frag(), which is the representation XDP multi-buffer expects. Fixes: 718a18a0c8a6 ("veth: Rework veth_xdp_rcv_skb in order to accept non-linear skb") Cc: stable@vger.kernel.org Signed-off-by: Matt Fleming Reviewed-by: Toke Høiland-Jørgensen Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260722191925.2192070-1-matt@readmodwrite.com Signed-off-by: Jakub Kicinski --- drivers/net/veth.c | 4 ++-- net/core/skbuff.c | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 1c5142149175..00e34afd858e 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -756,7 +756,7 @@ static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq, u32 frame_sz; if (skb_shared(skb) || skb_head_is_locked(skb) || - skb_shinfo(skb)->nr_frags || + skb_is_nonlinear(skb) || skb_headroom(skb) < XDP_PACKET_HEADROOM) { if (skb_pp_cow_data(rq->page_pool, pskb, XDP_PACKET_HEADROOM)) goto drop; @@ -771,7 +771,7 @@ static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq, xdp_prepare_buff(xdp, skb->head, skb_headroom(skb), skb_headlen(skb), true); - if (skb_is_nonlinear(skb)) { + if (skb_shinfo(skb)->nr_frags) { skb_shinfo(skb)->xdp_frags_size = skb->data_len; xdp_buff_set_frags_flag(xdp); } else { diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 18dabb4e9cfa..ba3dbac80fb4 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -927,6 +927,18 @@ static void skb_clone_fraglist(struct sk_buff *skb) skb_get(list); } +/** + * skb_pp_cow_data() - copy skb data into page-pool backed storage + * @pool: page pool to allocate from + * @pskb: pointer to skb pointer, replaced with the copied skb on success + * @headroom: headroom to reserve in the copied skb + * + * skb_copy_bits() handles both frags[] and frag_list input. If the copied + * skb remains non-linear, it uses frags[], which is the representation used + * by XDP multi-buffer. + * + * Return: 0 on success or a negative errno on failure. + */ int skb_pp_cow_data(struct page_pool *pool, struct sk_buff **pskb, unsigned int headroom) { @@ -936,12 +948,6 @@ int skb_pp_cow_data(struct page_pool *pool, struct sk_buff **pskb, int err, i, head_off; void *data; - /* XDP does not support fraglist so we need to linearize - * the skb. - */ - if (skb_has_frag_list(skb)) - return -EOPNOTSUPP; - max_head_size = SKB_WITH_OVERHEAD(PAGE_SIZE - headroom); if (skb->len > max_head_size + MAX_SKB_FRAGS * PAGE_SIZE) return -ENOMEM; From 7720b63bcef3f54c7fe288774b720a227d54a306 Mon Sep 17 00:00:00 2001 From: Tengda Wu Date: Sat, 25 Jul 2026 02:47:21 +0000 Subject: [PATCH 296/456] ftrace: Add global mutex to serialize trace_parser access In ftrace, the trace_parser structure is allocated and initialized when a trace file is opened, and is subsequently used across write and release handlers to parse user input. The affected handler paths and their specific functions are: - Open paths: ftrace_regex_open(), ftrace_graph_open() - Write paths: ftrace_regex_write(), ftrace_graph_write() - Release paths: ftrace_regex_release(), ftrace_graph_release() If userspace opens a trace file descriptor and shares it across multiple threads, concurrent write calls will race on the parser's internal state, specifically the 'idx', 'cont', and 'buffer' fields, leading to corrupted input or undefined behavior. Fix this by adding a global mutex, parser_lock, to serialize all access to trace_parser across write and release paths, preventing concurrent corruption of parser state. Fixes: e704eff3ff51 ("ftrace: Have set_graph_function handle multiple functions in one write") Fixes: 689fd8b65d66 ("tracing: trace parser support for function and graph") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260725024721.1983675-1-wutengda@huaweicloud.com Signed-off-by: Tengda Wu Signed-off-by: Steven Rostedt --- kernel/trace/ftrace.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index f93e34dd2328..6c47a94f5924 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -1097,6 +1097,12 @@ struct ftrace_ops global_ops = { FTRACE_OPS_FL_PID, }; +/* + * parser_lock - Protects trace_parser state against concurrent operations. + * Held across trace_get_user() and subsequent buffer parsing to prevent races. + */ +static DEFINE_MUTEX(parser_lock); + /* * Used by the stack unwinder to know about dynamic ftrace trampolines. */ @@ -5842,6 +5848,8 @@ ftrace_regex_write(struct file *file, const char __user *ubuf, /* iter->hash is a local copy, so we don't need regex_lock */ parser = &iter->parser; + + guard(mutex)(&parser_lock); read = trace_get_user(parser, ubuf, cnt, ppos); if (read >= 0 && trace_parser_loaded(parser) && @@ -6984,12 +6992,14 @@ int ftrace_regex_release(struct inode *inode, struct file *file) iter = file->private_data; parser = &iter->parser; + mutex_lock(&parser_lock); if (trace_parser_loaded(parser)) { int enable = !(iter->flags & FTRACE_ITER_NOTRACE); ftrace_process_regex(iter, parser->buffer, parser->idx, enable); } + mutex_unlock(&parser_lock); trace_parser_put(parser); @@ -7321,10 +7331,12 @@ ftrace_graph_release(struct inode *inode, struct file *file) parser = &fgd->parser; + mutex_lock(&parser_lock); if (trace_parser_loaded((parser))) { ret = ftrace_graph_set_hash(fgd->new_hash, parser->buffer); } + mutex_unlock(&parser_lock); trace_parser_put(parser); @@ -7437,6 +7449,7 @@ ftrace_graph_write(struct file *file, const char __user *ubuf, parser = &fgd->parser; + guard(mutex)(&parser_lock); read = trace_get_user(parser, ubuf, cnt, ppos); if (read >= 0 && trace_parser_loaded(parser) && From 2c2b322acdcc78575b8d6afa64a085cf92e03c12 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 24 Jul 2026 19:32:10 -0400 Subject: [PATCH 297/456] tracing: perf: Fix stale head for perf syscall tracing The code that can read the user space parameters of a system call may enable preemption and migrate. The head of the per CPU perf events list may be pointing to the wrong CPU event if the code migrates the task. Reassign the head pointer if the system call event called the code that may have caused a migration. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260724193210.03fae1d6@gandalf.local.home Reported-by: Sashiko <> Link: https://sashiko.dev/#/patchset/20260717173252.3431565-1-usama.arif%40linux.dev Fixes: edca33a56297d ("tracing: Fix failure to read user space from system call trace events") Signed-off-by: Steven Rostedt --- kernel/trace/trace_syscalls.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c index e98ee7e1e66f..8a4f3c75e39f 100644 --- a/kernel/trace/trace_syscalls.c +++ b/kernel/trace/trace_syscalls.c @@ -1451,6 +1451,11 @@ static void perf_syscall_enter(void *ignore, struct pt_regs *regs, long id) if (syscall_get_data(sys_data, args, &user_ptr, &size, user_sizes, &uargs, buf_size) < 0) return; + + /* The above may have caused a migration */ + head = this_cpu_ptr(sys_data->enter_event->perf_events); + if (hlist_empty(head)) + return; } /* get the size after alignment with the u32 buffer size field */ From 015b5bcbcb622b32317642be91a7f79aa5413649 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 19 Jul 2026 14:11:40 +0200 Subject: [PATCH 298/456] pinctrl: devicetree: don't free uninitialized dev_name on error path dt_remember_or_free_map() duplicates dev_name for each map entry. If kstrdup_const() fails, dt_free_map() frees dev_name in all num_maps entries, including entries that have not been initialized. Some pinctrl drivers, including pinctrl-imx, allocate the map with kmalloc() and leave dev_name for the core to initialize. The untouched entries therefore contain uninitialized data which is passed to kfree_const(). Reproduced on qemu's mcimx6ul-evk (pinctrl-imx) with failslab injection while binding the pinctrl-consuming device, under KASAN: BUG: KASAN: double-free in dt_free_map+0x34/0xa4 Free of addr c425a900 by task init/1 kfree from dt_free_map+0x34/0xa4 dt_free_map from dt_remember_or_free_map+0x184/0x198 dt_remember_or_free_map from pinctrl_dt_to_map+0x33c/0x4c8 pinctrl_dt_to_map from create_pinctrl+0x9c/0x5c0 Initialize all dev_name fields to NULL before duplicating the device name, making the full-map cleanup safe after a partial failure. Fixes: be4c60b563ed ("pinctrl: devicetree: Avoid taking direct reference to device name string") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Signed-off-by: Linus Walleij --- drivers/pinctrl/devicetree.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pinctrl/devicetree.c b/drivers/pinctrl/devicetree.c index 02a271dd292f..465b43092eb7 100644 --- a/drivers/pinctrl/devicetree.c +++ b/drivers/pinctrl/devicetree.c @@ -69,6 +69,10 @@ static int dt_remember_or_free_map(struct pinctrl *p, const char *statename, int i; struct pinctrl_dt_map *dt_map; + /* Initialize dev_name before any allocation can fail */ + for (i = 0; i < num_maps; i++) + map[i].dev_name = NULL; + /* Initialize common mapping table entry fields */ for (i = 0; i < num_maps; i++) { const char *devname; From 25cb6e9a13123d1039cdc75b446ac52e1ebdc26d Mon Sep 17 00:00:00 2001 From: Benjamin Boortz Date: Sun, 19 Jul 2026 11:41:46 +0200 Subject: [PATCH 299/456] pinctrl: microchip-sgpio: add missing select REGMAP_MMIO The driver calls ocelot_regmap_from_resource() via , which internally uses devm_regmap_init_mmio() and requires REGMAP_MMIO. The Kconfig entry does not select REGMAP_MMIO, causing a build failure when no other driver in the config happens to pull in REGMAP_MMIO: include/linux/mfd/ocelot.h:34:24: error: implicit declaration of function 'devm_regmap_init_mmio' Found by randconfig testing on arm64; tinyconfig reproducer below. Fixes: 2afbbab45c26 ("pinctrl: microchip-sgpio: update to support regmap") Cc: stable@vger.kernel.org Signed-off-by: Benjamin Boortz Reviewed-by: Andy Shevchenko Signed-off-by: Linus Walleij --- drivers/pinctrl/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index c2cdd7b2c49b..23ea76dc6261 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -426,6 +426,7 @@ config PINCTRL_MICROCHIP_SGPIO select GENERIC_PINCONF select GENERIC_PINCTRL_GROUPS select GENERIC_PINMUX_FUNCTIONS + select REGMAP_MMIO help Support for the serial GPIO interface used on Microsemi and Microchip SoCs. By using a serial interface, the SIO From ffe8a0c6b55285ceaf2f42fc20c3a0594d14f1e9 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 20 Jul 2026 11:28:44 -0500 Subject: [PATCH 300/456] pinctrl-amd: Don't clear S4 wake bits at probe commit 6bc3462a0f5e ("pinctrl: amd: Mask wake bits on probe again") introduced a regression where Wake-on-LAN no longer works after suspend or shutdown on some AMD platforms. Firmware-programmed S4 wake bits for devices like PCIe NICs using PCI PME are cleared at probe, but nothing restores them. Unlike S0i3/S3 wake sources that use enable_irq_wake() -> amd_gpio_irq_set_wake(), PCIe PME does not use GPIO IRQ infrastructure and relies on firmware configuration. The original intent of commit 6bc3462a0f5e ("pinctrl: amd: Mask wake bits on probe again") was to clear spurious wake bits left by firmware to prevent unwanted wakeups. However, S4 wake bits are used for hardware-level wake sources like WoL that bypass the kernel's IRQ wake API. Fix by preserving S4 wake bits at probe and only clearing S0i3/S3 bits: - Firmware-configured S4 wake sources (WoL) continue working - Kernel maintains control of S3/S0i3 wake policy via set_wake() - S3-only wake sources work correctly per commit f31f33dbb3ba ("pinctrl: amd: Take suspend type into consideration which pins are non-wake") The trade-off is that firmware-programmed spurious S4 wake bits remain set, but this is less problematic than breaking WoL. Fixes: 6bc3462a0f5e ("pinctrl: amd: Mask wake bits on probe again") Signed-off-by: Mario Limonciello Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-amd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/pinctrl/pinctrl-amd.c b/drivers/pinctrl/pinctrl-amd.c index e3128b0045d2..15a398bb3be2 100644 --- a/drivers/pinctrl/pinctrl-amd.c +++ b/drivers/pinctrl/pinctrl-amd.c @@ -884,8 +884,7 @@ static void amd_gpio_irq_init(struct amd_gpio *gpio_dev) u32 pin_reg, mask; int i; - mask = BIT(WAKE_CNTRL_OFF_S0I3) | BIT(WAKE_CNTRL_OFF_S3) | - BIT(WAKE_CNTRL_OFF_S4); + mask = BIT(WAKE_CNTRL_OFF_S0I3) | BIT(WAKE_CNTRL_OFF_S3); for (i = 0; i < desc->npins; i++) { int pin = desc->pins[i].number; From dad6e107b3cd9d20514e7799b7ad8674f81e3f30 Mon Sep 17 00:00:00 2001 From: Benjamin Boortz Date: Mon, 20 Jul 2026 19:51:04 +0200 Subject: [PATCH 301/456] pinctrl: bm1880: add missing select GENERIC_PINCONF drivers/pinctrl/pinctrl-bm1880.c initialises its pinconf_ops with .is_generic = true, but that field is only present when CONFIG_GENERIC_PINCONF is enabled (guarded by #ifdef in pinconf.h). The Kconfig entry for PINCTRL_BM1880 never selects GENERIC_PINCONF, so any config that enables CONFIG_PINCTRL_BM1880=y without CONFIG_GENERIC_PINCONF=y fails to compile: drivers/pinctrl/pinctrl-bm1880.c:1288:10: error: 'const struct pinconf_ops' has no member named 'is_generic' Found by randconfig testing on arm64; tinyconfig reproducer below. Add the missing select to fix the build. Fixes: 49bd61ebce5f ("pinctrl: Add pinconf support for BM1880 SoC") Cc: stable@vger.kernel.org Signed-off-by: Benjamin Boortz Signed-off-by: Linus Walleij --- drivers/pinctrl/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 23ea76dc6261..eda54aa5fde6 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -166,6 +166,7 @@ config PINCTRL_BM1880 depends on OF && (ARCH_BITMAIN || COMPILE_TEST) default ARCH_BITMAIN select PINMUX + select GENERIC_PINCONF help Pinctrl driver for Bitmain BM1880 SoC. From 749d7aa0377aae32af8c0a4ad43371e7bf830ab5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 23 Jul 2026 11:37:05 +0200 Subject: [PATCH 302/456] super: fix emergency thaw deadlock on frozen block devices do_thaw_all_callback() calls bdev_thaw() while holding sb->s_umount exclusively. If the block device was frozen via bdev_freeze() dropping the last block layer freeze reference calls fs_bdev_thaw() which reacquires s_umount: do_thaw_all_callback(sb) super_lock_excl(sb) # holds sb->s_umount bdev_thaw(sb->s_bdev) mutex_lock(&bdev->bd_fsfreeze_mutex) # bd_fsfreeze_count drops 1 -> 0 bd_holder_ops->thaw == fs_bdev_thaw get_bdev_super(bdev) bdev_super_lock(bdev, true) super_lock(sb, true) down_write(&sb->s_umount) # same task: deadlock The emergency thaw worker deadlocks against itself holding both s_umount and bd_fsfreeze_mutex. That fscks any subsequent unmount, freeze, or thaw of that filesystem and block device. [ 81.878470] sysrq: Show Blocked State [ 81.880140] task:kworker/0:1 state:D stack:0 pid:11 tgid:11 ppid:2 task_flags:0x4208060 flags:0x00080000 [ 81.884876] Workqueue: events do_thaw_all [ 81.886656] Call Trace: [ 81.887759] [ 81.888763] __schedule+0x579/0x1420 [ 81.890372] schedule+0x3a/0x100 [ 81.891794] schedule_preempt_disabled+0x15/0x30 [ 81.893848] rwsem_down_write_slowpath+0x1ea/0x900 [ 81.895191] ? __pfx_do_thaw_all_callback+0x10/0x10 [ 81.896528] down_write+0xbd/0xc0 [ 81.897505] super_lock+0x91/0x180 [ 81.898457] ? __mutex_lock+0xa99/0x1140 [ 81.900748] ? __mutex_unlock_slowpath+0x1f/0x400 [ 81.902069] bdev_super_lock+0x5b/0x150 [ 81.903132] get_bdev_super+0x10/0x60 [ 81.904042] fs_bdev_thaw+0x23/0xf0 [ 81.904755] bdev_thaw+0x82/0x100 [ 81.905484] do_thaw_all_callback+0x2c/0x50 [ 81.906298] __iterate_supers+0x5d/0x130 [ 81.907067] do_thaw_all+0x20/0x40 [ 81.907739] process_one_work+0x206/0x5e0 [ 81.908545] worker_thread+0x1e2/0x3c0 [ 81.909339] ? __pfx_worker_thread+0x10/0x10 [ 81.910171] kthread+0xf4/0x130 [ 81.910799] ? __pfx_kthread+0x10/0x10 [ 81.911528] ret_from_fork+0x2e2/0x3b0 [ 81.912259] ? __pfx_kthread+0x10/0x10 [ 81.913010] ret_from_fork_asm+0x1a/0x30 [ 81.913806] bdev_super_lock() even documents the violated requirement with lockdep_assert_not_held(&sb->s_umount). Acquiring bd_fsfreeze_mutex under s_umount also inverts the bd_fsfreeze_mutex vs. s_umount ordering established by bdev_{freeze,thaw}() and can thus ABBA against a concurrent block-layer freeze even when the recursive path isn't hit. Fix this by not holding s_umount around the bdev_thaw() loop at all. Pin the superblock with an active reference instead as filesystems_freeze_callback() does. The active reference keeps the superblock from being shut down and so ->s_bdev stays valid without holding s_umount. The block-layer-held freeze is dropped by fs_bdev_thaw() with FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE exactly as a regular unfreeze would and thaw_super_locked() handles filesystem-level freezes as before. The emergency thaw path has deadlocked like this in one form or another for a long long time but the current exclusively-held shape dates back to commit [1] where thaw_bdev() already ended in thaw_super() with s_umount held by do_thaw_all_callback(). Fixes: 08fdc8a0138a ("buffer.c: call thaw_super during emergency thaw") [1] Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260723-work-super-emergency_thaw-v1-1-7c315c600245@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/super.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/fs/super.c b/fs/super.c index 70dcb07e7fa5..ffdcc6a2e0de 100644 --- a/fs/super.c +++ b/fs/super.c @@ -1082,16 +1082,30 @@ void emergency_remount(void) } } +static inline bool get_active_super(struct super_block *sb) +{ + bool active = false; + + if (super_lock_excl(sb)) { + active = atomic_inc_not_zero(&sb->s_active); + super_unlock_excl(sb); + } + return active; +} + static void do_thaw_all_callback(struct super_block *sb, void *unused) { - if (!super_lock_excl(sb)) + if (!get_active_super(sb)) return; + /* fs_bdev_thaw() acquires s_umount so it must not be held here */ if (IS_ENABLED(CONFIG_BLOCK)) while (sb->s_bdev && !bdev_thaw(sb->s_bdev)) pr_warn("Emergency Thaw on %pg\n", sb->s_bdev); - thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE, NULL); + if (super_lock_excl(sb)) + thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE, NULL); + deactivate_super(sb); } static void do_thaw_all(struct work_struct *work) @@ -1117,17 +1131,6 @@ void emergency_thaw_all(void) } } -static inline bool get_active_super(struct super_block *sb) -{ - bool active = false; - - if (super_lock_excl(sb)) { - active = atomic_inc_not_zero(&sb->s_active); - super_unlock_excl(sb); - } - return active; -} - static const char *filesystems_freeze_ptr = "filesystems_freeze"; static void filesystems_freeze_callback(struct super_block *sb, void *freeze_all_ptr) From f5098b6bae761e346ebcd9da7f95622c04733cff Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 26 Jul 2026 14:45:48 -0700 Subject: [PATCH 303/456] Linux 7.2-rc5 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b86c0d7ed0e5..2f5bf24e3389 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 2 SUBLEVEL = 0 -EXTRAVERSION = -rc4 +EXTRAVERSION = -rc5 NAME = Baby Opossum Posse # *DOCUMENTATION* From 96b2dbbe58a1ea5df8d29c2fe24b5f04715f4443 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Mon, 27 Jul 2026 12:27:39 +0800 Subject: [PATCH 304/456] erofs: ensure valid f_path for page cache sharing Previously, backing files for page cache sharing were set up with f_path left as NULL (only f_inode was valid). It worked, but a recent mincore fix relies on f_path.mnt and crashes (found by "erofs/028" on 7.2-rc4): BUG: kernel NULL pointer dereference, address: 0000000000000018 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP PTI CPU: 3 UID: 0 PID: 675528 Comm: fincore Not tainted 7.2.0-rc4-00002-g[]-dirty #1 PREEMPT(lazy) Hardware name: Red Hat KVM, BIOS 1.16.0-4.al8 04/01/2014 RIP: 0010:__do_sys_mincore+0xc0/0x2c0 ... Specify valid paths using valid disconnected dentries together with erofs_ishare_mnt instead of leaving f_path empty, so they are more like real backing files in a pseudo filesystem and standard backing_file_open() can be used directly. Fixes: e187bc02f8fa ("mm: do file ownership checks with the proper mount idmap") Acked-by: Hongbo Li Signed-off-by: Gao Xiang --- fs/erofs/Kconfig | 1 + fs/erofs/internal.h | 4 ++-- fs/erofs/ishare.c | 56 ++++++++++++++++++++++----------------------- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig index 4789b1077d8c..1feb28cfe557 100644 --- a/fs/erofs/Kconfig +++ b/fs/erofs/Kconfig @@ -189,6 +189,7 @@ config EROFS_FS_PCPU_KTHREAD_HIPRI config EROFS_FS_PAGE_CACHE_SHARE bool "EROFS page cache share support (experimental)" depends on EROFS_FS && EROFS_FS_XATTR + select FS_STACK help This enables page cache sharing among inodes with identical content fingerprints on the same machine. diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index 580f8d9f14e7..57bd21859c65 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -288,8 +288,8 @@ struct erofs_inode { struct erofs_inode_fingerprint fingerprint; spinlock_t ishare_lock; }; - /* for each real inode */ - struct inode *sharedinode; + /* for each real filesystem inode */ + struct dentry *sharedentry; }; #endif /* the corresponding vfs inode */ diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c index a1a20a5ba548..fa7d4112dec5 100644 --- a/fs/erofs/ishare.c +++ b/fs/erofs/ishare.c @@ -2,14 +2,13 @@ /* * Copyright (C) 2024, Alibaba Cloud */ +#include #include #include #include #include "internal.h" #include "xattr.h" -#include "../internal.h" - static struct vfsmount *erofs_ishare_mnt; static int erofs_ishare_iget5_eq(struct inode *inode, void *data) @@ -33,10 +32,12 @@ static int erofs_ishare_iget5_set(struct inode *inode, void *data) bool erofs_ishare_fill_inode(struct inode *inode) { + static const struct file_operations empty_fops = {}; struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb); const struct address_space_operations *aops; struct erofs_inode *vi = EROFS_I(inode); struct erofs_inode_fingerprint fp; + struct dentry *sd; struct inode *si; aops = erofs_get_aops(inode); @@ -49,7 +50,9 @@ bool erofs_ishare_fill_inode(struct inode *inode) xxh32(fp.opaque, fp.size, 0), erofs_ishare_iget5_eq, erofs_ishare_iget5_set, &fp); if (si && (inode_state_read_once(si) & I_NEW)) { + si->i_fop = &empty_fops; si->i_mapping->a_ops = aops; + si->i_mode = 0444 | S_IFREG; si->i_size = inode->i_size; unlock_new_inode(si); } else { @@ -65,7 +68,10 @@ bool erofs_ishare_fill_inode(struct inode *inode) return false; } } - vi->sharedinode = si; + sd = d_obtain_alias(si); /* disconnected denties for sharedinodes */ + if (IS_ERR(sd)) + return false; + vi->sharedentry = sd; INIT_LIST_HEAD(&vi->ishare_list); spin_lock(&EROFS_I(si)->ishare_lock); list_add(&vi->ishare_list, &EROFS_I(si)->ishare_list); @@ -75,48 +81,40 @@ bool erofs_ishare_fill_inode(struct inode *inode) void erofs_ishare_free_inode(struct inode *inode) { - struct erofs_inode *vi = EROFS_I(inode); - struct inode *sharedinode = vi->sharedinode; + struct erofs_inode *vi = EROFS_I(inode), *svi; - if (!sharedinode) + if (!vi->sharedentry) return; - spin_lock(&EROFS_I(sharedinode)->ishare_lock); + svi = EROFS_I(d_inode(vi->sharedentry)); + spin_lock(&svi->ishare_lock); list_del(&vi->ishare_list); - spin_unlock(&EROFS_I(sharedinode)->ishare_lock); - iput(sharedinode); - vi->sharedinode = NULL; + spin_unlock(&svi->ishare_lock); + dput(vi->sharedentry); + vi->sharedentry = NULL; } static int erofs_ishare_file_open(struct inode *inode, struct file *file) { - struct inode *sharedinode = EROFS_I(inode)->sharedinode; - struct file *realfile; + struct path sharedpath = { + .mnt = erofs_ishare_mnt, + .dentry = EROFS_I(inode)->sharedentry, + }; + struct file *rf; if (file->f_flags & O_DIRECT) return -EINVAL; - realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred(), - file); - if (IS_ERR(realfile)) - return PTR_ERR(realfile); - ihold(sharedinode); - realfile->f_op = &erofs_file_fops; - realfile->f_inode = sharedinode; - realfile->f_mapping = sharedinode->i_mapping; - path_get(&file->f_path); - backing_file_set_user_path(realfile, &file->f_path); - file_ra_state_init(&realfile->f_ra, file->f_mapping); - realfile->private_data = EROFS_I(inode); - file->private_data = realfile; + rf = backing_file_open(file, file->f_flags | O_NOATIME, + &sharedpath, current_cred()); + if (IS_ERR(rf)) + return PTR_ERR(rf); + file->private_data = rf; return 0; } static int erofs_ishare_file_release(struct inode *inode, struct file *file) { - struct file *realfile = file->private_data; - - iput(realfile->f_inode); - fput(realfile); + fput(file->private_data); file->private_data = NULL; return 0; } From c9b47e6b23114e939b17f818471c7a46e59006e7 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 14 Jul 2026 07:47:29 -0400 Subject: [PATCH 305/456] erofs: cap LZMA stream pool size fs/erofs/decompressor_lzma.c sizes the module-global MicroLZMA stream pool from num_possible_cpus() when the lzma_streams module parameter is unset, then z_erofs_load_lzma_config() preallocates one image-supplied dictionary per stream, accepting dictionaries up to 8 MiB. On high-CPU systems, a small EROFS image can pin hundreds of MiB of vmalloc-backed decoder state until the erofs module is unloaded. Impact: An EROFS image mounted by the system can pin up to 8 MiB of vmalloc memory per LZMA stream, either as intended or unexpectedly. Bound the default stream count by a new CONFIG_EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS option, default 16, so the worst-case default preallocation is 128 MiB if the number of CPUs is no less than 16 while preserving the existing per-image dictionary limit. An explicit lzma_streams module parameter is still honoured as-is, so administrators who deliberately size the pool are not affected. Fixes: 622ceaddb764 ("erofs: lzma compression support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang --- fs/erofs/Kconfig | 14 ++++++++++++++ fs/erofs/decompressor_lzma.c | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig index 1feb28cfe557..8ca1767dafb6 100644 --- a/fs/erofs/Kconfig +++ b/fs/erofs/Kconfig @@ -131,6 +131,20 @@ config EROFS_FS_ZIP_LZMA Say N if you want to disable LZMA compression support. +config EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS + int "EROFS LZMA default maximum decompression streams" + depends on EROFS_FS_ZIP_LZMA + range 1 NR_CPUS + default 16 + help + By default EROFS allocates one LZMA decompression stream per CPU. + Each stream can hold a dictionary of up to 8 MiB taken from the + mounted image, so on systems with many CPUs this can reserve a lot + of memory. This caps the default; the lzma_streams module parameter + still overrides it. + + If unsure, keep the default of 16. + config EROFS_FS_ZIP_DEFLATE bool "EROFS DEFLATE compressed data support" depends on EROFS_FS_ZIP diff --git a/fs/erofs/decompressor_lzma.c b/fs/erofs/decompressor_lzma.c index f6692d0f2f04..6b0cdb446c6a 100644 --- a/fs/erofs/decompressor_lzma.c +++ b/fs/erofs/decompressor_lzma.c @@ -51,7 +51,8 @@ static int __init z_erofs_lzma_init(void) /* by default, use # of possible CPUs instead */ if (!z_erofs_lzma_nstrms) - z_erofs_lzma_nstrms = num_possible_cpus(); + z_erofs_lzma_nstrms = min_t(unsigned int, num_possible_cpus(), + CONFIG_EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS); for (i = 0; i < z_erofs_lzma_nstrms; ++i) { struct z_erofs_lzma *strm = kzalloc_obj(*strm); From 9972befc3e34ff8b6847198c84f11bfc312dde40 Mon Sep 17 00:00:00 2001 From: Jaehoon Kim Date: Fri, 24 Jul 2026 08:39:43 -0500 Subject: [PATCH 306/456] KVM: s390: Fall back to short-term pinning in MAP ioctl FOLL_LONGTERM pinning fails for some memory types, such as file-backed guest memory. As a result, kvm_s390_adapter_map() returns -EINVAL and irqfd adapter registration fails even though interrupt delivery could still work via the existing non-atomic path. When FOLL_LONGTERM pinning fails, verify that the page is accessible using a short-term pin instead. If the short-term pin succeeds, unpin the page and add a map entry with pinned=false to preserve MAP/UNMAP symmetry. The non-atomic irqfd path already performs short-term pinning for interrupt delivery, so this restores the previous behavior for memory that cannot be pinned long-term. get_map_info() is updated to return NULL for unpinned entries so that the atomic irqfd fast path falls back to the non-atomic path. kvm_s390_adapter_unmap() and kvm_s390_unmap_all_adapters() skip dirty marking and unpin for unpinned entries. Update Documentation/virt/kvm/devices/s390_flic.rst to reflect the new MAP/UNMAP behavior. Fixes: c9a568838086 ("KVM: s390: Add map/unmap ioctl and clean mappings post-guest") Signed-off-by: Jaehoon Kim Reviewed-by: Douglas Freimuth Reviewed-by: Matthew Rosato Signed-off-by: Christian Borntraeger --- Documentation/virt/kvm/devices/s390_flic.rst | 15 +++++- arch/s390/include/asm/kvm_host.h | 5 ++ arch/s390/kvm/interrupt.c | 56 +++++++++++++++----- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/Documentation/virt/kvm/devices/s390_flic.rst b/Documentation/virt/kvm/devices/s390_flic.rst index b784f8016748..983c858b444a 100644 --- a/Documentation/virt/kvm/devices/s390_flic.rst +++ b/Documentation/virt/kvm/devices/s390_flic.rst @@ -112,9 +112,20 @@ Groups: mask or unmask the adapter, as specified in mask KVM_S390_IO_ADAPTER_MAP - This is now a no-op. The mapping is purely done by the irq route. + Map an adapter indicator or summary page for long-term pinning so that + interrupt injection can be performed in atomic context. If long-term + pinning is not possible (e.g. file-backed memory), the page is verified + via a short-term pin and the ioctl returns success; interrupt injection + will use the non-atomic irqfd path with short-term pinning on each + interrupt. In Secure Execution mode this is a no-op and the ioctl + returns success. + KVM_S390_IO_ADAPTER_UNMAP - This is now a no-op. The mapping is purely done by the irq route. + Unmap a previously mapped adapter indicator or summary page and release + the long-term pin. If the page was not long-term pinned (e.g. file-backed + memory), the map entry is removed and success is returned; if no prior + map entry exists, -ENOENT is returned. In Secure Execution mode this is + a no-op and the ioctl returns success. KVM_DEV_FLIC_AISM modify the adapter-interruption-suppression mode for a given isc if the diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h index eaa34c5bd3c1..c172f9b212d1 100644 --- a/arch/s390/include/asm/kvm_host.h +++ b/arch/s390/include/asm/kvm_host.h @@ -476,6 +476,11 @@ struct s390_map_info { __u64 guest_addr; __u64 addr; struct page *page; + /* + * True if the page is long-term pinned. False if long-term pinning + * failed and this entry exists only to preserve MAP/UNMAP symmetry. + */ + bool pinned; }; struct s390_io_adapter { diff --git a/arch/s390/kvm/interrupt.c b/arch/s390/kvm/interrupt.c index 9e3e6b0d72ad..7514d9e2403c 100644 --- a/arch/s390/kvm/interrupt.c +++ b/arch/s390/kvm/interrupt.c @@ -2520,8 +2520,22 @@ static int kvm_s390_adapter_map(struct kvm *kvm, unsigned int id, __u64 addr) map->addr = host_addr; map->page = pin_map_page(kvm, host_addr, FOLL_LONGTERM); if (!map->page) { - ret = -EINVAL; - goto out; + /* + * Long-term pinning may fail for memory types such as file-backed + * memory. Verify that short-term pinning succeeds so that the + * non-atomic irqfd path can handle interrupt injection. + */ + map->page = pin_map_page(kvm, host_addr, 0); + if (!map->page) { + ret = -EINVAL; + goto out; + } + unpin_user_page(map->page); + map->page = NULL; + map->pinned = false; + /* Add an entry to preserve MAP/UNMAP symmetry. */ + } else { + map->pinned = true; } spin_lock_irqsave(&adapter->maps_lock, flags); if (adapter->nr_maps < MAX_S390_ADAPTER_MAPS) { @@ -2532,7 +2546,7 @@ static int kvm_s390_adapter_map(struct kvm *kvm, unsigned int id, __u64 addr) ret = -EINVAL; } spin_unlock_irqrestore(&adapter->maps_lock, flags); - if (ret) + if (ret && map->page) unpin_user_page(map->page); out: if (ret) @@ -2546,6 +2560,7 @@ static int kvm_s390_adapter_unmap(struct kvm *kvm, unsigned int id, __u64 addr) struct s390_map_info *map, *tmp, *map_to_free; struct page *map_page_to_put = NULL; u64 map_addr_to_mark = 0; + bool map_pinned = false; unsigned long flags; int found = 0, idx; @@ -2560,6 +2575,7 @@ static int kvm_s390_adapter_unmap(struct kvm *kvm, unsigned int id, __u64 addr) list_del(&map->list); map_page_to_put = map->page; map_addr_to_mark = map->guest_addr; + map_pinned = map->pinned; map_to_free = map; break; } @@ -2568,11 +2584,18 @@ static int kvm_s390_adapter_unmap(struct kvm *kvm, unsigned int id, __u64 addr) if (found) { kfree(map_to_free); - idx = srcu_read_lock(&kvm->srcu); - mark_page_dirty(kvm, map_addr_to_mark >> PAGE_SHIFT); - set_page_dirty_lock(map_page_to_put); - srcu_read_unlock(&kvm->srcu, idx); - unpin_user_page(map_page_to_put); + if (map_pinned) { + /* + * Only long-term pinned pages need to be marked dirty + * and released. Fallback entries exist only for + * MAP/UNMAP symmetry. + */ + idx = srcu_read_lock(&kvm->srcu); + mark_page_dirty(kvm, map_addr_to_mark >> PAGE_SHIFT); + set_page_dirty_lock(map_page_to_put); + srcu_read_unlock(&kvm->srcu, idx); + unpin_user_page(map_page_to_put); + } } return found ? 0 : -ENOENT; @@ -2598,11 +2621,13 @@ void kvm_s390_unmap_all_adapters(struct kvm *kvm) list_for_each_entry_safe(map, tmp, &local_list, list) { list_del(&map->list); - idx = srcu_read_lock(&kvm->srcu); - mark_page_dirty(kvm, map->guest_addr >> PAGE_SHIFT); - set_page_dirty_lock(map->page); - srcu_read_unlock(&kvm->srcu, idx); - unpin_user_page(map->page); + if (map->pinned) { + idx = srcu_read_lock(&kvm->srcu); + mark_page_dirty(kvm, map->guest_addr >> PAGE_SHIFT); + set_page_dirty_lock(map->page); + srcu_read_unlock(&kvm->srcu, idx); + unpin_user_page(map->page); + } kfree(map); } } @@ -2929,8 +2954,11 @@ static struct s390_map_info *get_map_info(struct s390_io_adapter *adapter, return NULL; list_for_each_entry(map, &adapter->maps, list) { - if (map->addr == addr) + if (map->addr == addr) { + if (!map->pinned) + return NULL; return map; + } } return NULL; } From 67ff4bf723c8bd1f1b10450fa3e8f55762418104 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Fri, 24 Jul 2026 09:42:28 -0700 Subject: [PATCH 307/456] gpio: gpio-by-pinctrl: Apply initial value in direction output wrapper After successfully configuring gpio pin as output, set the requested initial output value via the existing gpio set wrapper, so that the pin is not left at its previous level. Fixes: 7671f4949a6c ("gpio: gpio-by-pinctrl: add pinctrl based generic GPIO driver") Signed-off-by: Alex Tran Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260724-gpio-pinctrl-output-set-val-v2-1-cad55d025636@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-by-pinctrl.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-by-pinctrl.c b/drivers/gpio/gpio-by-pinctrl.c index 7d7c48ce5163..fb8440acb31d 100644 --- a/drivers/gpio/gpio-by-pinctrl.c +++ b/drivers/gpio/gpio-by-pinctrl.c @@ -27,12 +27,6 @@ static int pin_control_gpio_get_direction(struct gpio_chip *gc, unsigned int off return GPIO_LINE_DIRECTION_IN; } -static int pin_control_gpio_direction_output(struct gpio_chip *chip, - unsigned int offset, int val) -{ - return pinctrl_gpio_direction_output(chip, offset); -} - static int pin_control_gpio_get(struct gpio_chip *chip, unsigned int offset) { unsigned long config; @@ -55,6 +49,18 @@ static int pin_control_gpio_set(struct gpio_chip *chip, unsigned int offset, return pinctrl_gpio_set_config(chip, offset, config); } +static int pin_control_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int val) +{ + int ret; + + ret = pinctrl_gpio_direction_output(chip, offset); + if (ret) + return ret; + + return pin_control_gpio_set(chip, offset, val); +} + static int pin_control_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; From d233087c19f6607ef926ac3f47d776e2406ffd1f Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Mon, 27 Jul 2026 15:02:05 +0700 Subject: [PATCH 308/456] gpio: pca953x: fix cache_only and IRQ state on restore_context() failure When pca953x_restore_context() fails, cache_only is left disabled and the IRQ left enabled, even though register synchronization may not have completed successfully. Restore cache_only and disable the IRQ again on failure, matching the state set by pca953x_save_context(). Fixes: ec5bde62019b ("gpio: pca953x: Split pca953x_restore_context() and pca953x_save_context()") Fixes: 3e38f946062b ("gpio: pca953x: fix IRQ storm on system wake up") Cc: stable@vger.kernel.org Reviewed-by: Linus Walleij Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260727080205.16353-1-phucduc.bui@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pca953x.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index e03bf1b12091..aac9ea7520b6 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -1378,9 +1378,20 @@ static int pca953x_restore_context(struct pca953x_chip *chip) regcache_mark_dirty(chip->regmap); ret = pca953x_regcache_sync(chip); if (ret) - return ret; + goto err; - return regcache_sync(chip->regmap); + ret = regcache_sync(chip->regmap); + if (ret) + goto err; + + return 0; + +err: + if (chip->client->irq > 0) + disable_irq(chip->client->irq); + regcache_cache_only(chip->regmap, true); + + return ret; } static void pca953x_save_context(struct pca953x_chip *chip) From a02b8950d619123da64f69b70fe1dadef217dfe4 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Thu, 23 Jul 2026 09:41:29 +0800 Subject: [PATCH 309/456] gpio: pch: use raw_spinlock_t for the register lock pch_irq_type() is registered as the irq_chip .irq_set_type callback and takes chip->spinlock with spin_lock_irqsave(). This callback is reached from __setup_irq() -> __irq_set_trigger() -> chip->irq_set_type() while the caller holds desc->lock, a raw_spinlock_t, with hardirqs disabled. That context is not sleepable, but on PREEMPT_RT a regular spinlock_t is an rtmutex-backed sleeping lock, so acquiring it there is invalid. This was confirmed on a PREEMPT_RT kernel with lockdep (PROVE_RAW_LOCK_NESTING and DEBUG_ATOMIC_SLEEP). A grounded PoC mirrored pch_irq_type()'s locking and drove it through the real genirq carrier irq_set_irq_type() -> __irq_set_trigger() -> chip->irq_set_type(), i.e. the same __irq_set_trigger() edge that __setup_irq() takes for a requested IRQ. With the original spin_lock_irqsave() edge lockdep reported an invalid wait context, immediately followed by: BUG: sleeping function called from invalid context at kernel/locking/spinlock_rt.c:48 in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 95, name: insmod hardirqs last disabled at (3784): _raw_spin_lock_irqsave+0x4f/0x60 rt_spin_lock+0x3a/0x1c0 repro_irq_set_type+0x64/0xa0 [pch_repro] __irq_set_trigger+0x69/0x140 irq_set_irq_type+0x78/0xd0 Switching the mirrored lock to raw_spinlock_t made both splats go away. Convert the register lock to raw_spinlock_t. The same lock also serializes the GPIO direction/value callbacks and the suspend/resume register save/restore, but all of those critical sections only perform MMIO register accesses (ioread32()/iowrite32()) and irq_set_handler_locked(); none of them contain sleepable operations. Keeping this register lock non-sleeping is therefore appropriate for the irqchip callbacks and does not change the GPIO-side locking contract. This is the same class of issue and fix as recently addressed for other GPIO controllers, e.g. commit 286533cb14a3 ("gpio: sch: use raw_spinlock_t in the irq startup path") and commit 90f0109019e6 ("gpio: eic-sprd: use raw_spinlock_t in the irq startup path"). Fixes: 38eb18a6f92d ("gpio-pch: Support interrupt function") Cc: stable@vger.kernel.org Signed-off-by: Junjie Cao Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260723014129.1129730-1-junjie.cao@intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pch.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/gpio/gpio-pch.c b/drivers/gpio/gpio-pch.c index 4ffa0955a9e3..07a5617b314b 100644 --- a/drivers/gpio/gpio-pch.c +++ b/drivers/gpio/gpio-pch.c @@ -96,7 +96,7 @@ struct pch_gpio { struct pch_gpio_reg_data pch_gpio_reg; int irq_base; enum pch_type_t ioh; - spinlock_t spinlock; + raw_spinlock_t spinlock; }; static int pch_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val) @@ -105,7 +105,7 @@ static int pch_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val) struct pch_gpio *chip = gpiochip_get_data(gpio); unsigned long flags; - spin_lock_irqsave(&chip->spinlock, flags); + raw_spin_lock_irqsave(&chip->spinlock, flags); reg_val = ioread32(&chip->reg->po); if (val) reg_val |= BIT(nr); @@ -113,7 +113,7 @@ static int pch_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val) reg_val &= ~BIT(nr); iowrite32(reg_val, &chip->reg->po); - spin_unlock_irqrestore(&chip->spinlock, flags); + raw_spin_unlock_irqrestore(&chip->spinlock, flags); return 0; } @@ -133,7 +133,7 @@ static int pch_gpio_direction_output(struct gpio_chip *gpio, unsigned int nr, u32 reg_val; unsigned long flags; - spin_lock_irqsave(&chip->spinlock, flags); + raw_spin_lock_irqsave(&chip->spinlock, flags); reg_val = ioread32(&chip->reg->po); if (val) @@ -147,7 +147,7 @@ static int pch_gpio_direction_output(struct gpio_chip *gpio, unsigned int nr, pm |= BIT(nr); iowrite32(pm, &chip->reg->pm); - spin_unlock_irqrestore(&chip->spinlock, flags); + raw_spin_unlock_irqrestore(&chip->spinlock, flags); return 0; } @@ -158,12 +158,12 @@ static int pch_gpio_direction_input(struct gpio_chip *gpio, unsigned int nr) u32 pm; unsigned long flags; - spin_lock_irqsave(&chip->spinlock, flags); + raw_spin_lock_irqsave(&chip->spinlock, flags); pm = ioread32(&chip->reg->pm); pm &= BIT(gpio_pins[chip->ioh]) - 1; pm &= ~BIT(nr); iowrite32(pm, &chip->reg->pm); - spin_unlock_irqrestore(&chip->spinlock, flags); + raw_spin_unlock_irqrestore(&chip->spinlock, flags); return 0; } @@ -265,7 +265,7 @@ static int pch_irq_type(struct irq_data *d, unsigned int type) return 0; } - spin_lock_irqsave(&chip->spinlock, flags); + raw_spin_lock_irqsave(&chip->spinlock, flags); /* Set interrupt mode */ im = ioread32(im_reg) & ~(PCH_IM_MASK << (im_pos * 4)); @@ -277,7 +277,7 @@ static int pch_irq_type(struct irq_data *d, unsigned int type) else if (type & IRQ_TYPE_EDGE_BOTH) irq_set_handler_locked(d, handle_edge_irq); - spin_unlock_irqrestore(&chip->spinlock, flags); + raw_spin_unlock_irqrestore(&chip->spinlock, flags); return 0; } @@ -374,7 +374,7 @@ static int pch_gpio_probe(struct pci_dev *pdev, chip->ioh = id->driver_data; chip->reg = chip->base; pci_set_drvdata(pdev, chip); - spin_lock_init(&chip->spinlock); + raw_spin_lock_init(&chip->spinlock); pch_gpio_setup(chip); ret = devm_gpiochip_add_data(dev, &chip->gpio, chip); @@ -407,9 +407,9 @@ static int pch_gpio_suspend(struct device *dev) struct pch_gpio *chip = dev_get_drvdata(dev); unsigned long flags; - spin_lock_irqsave(&chip->spinlock, flags); + raw_spin_lock_irqsave(&chip->spinlock, flags); pch_gpio_save_reg_conf(chip); - spin_unlock_irqrestore(&chip->spinlock, flags); + raw_spin_unlock_irqrestore(&chip->spinlock, flags); return 0; } @@ -419,11 +419,11 @@ static int pch_gpio_resume(struct device *dev) struct pch_gpio *chip = dev_get_drvdata(dev); unsigned long flags; - spin_lock_irqsave(&chip->spinlock, flags); + raw_spin_lock_irqsave(&chip->spinlock, flags); iowrite32(0x01, &chip->reg->reset); iowrite32(0x00, &chip->reg->reset); pch_gpio_restore_reg_conf(chip); - spin_unlock_irqrestore(&chip->spinlock, flags); + raw_spin_unlock_irqrestore(&chip->spinlock, flags); return 0; } From 47abd2ca281531deee38a3b3770d885e270e9fc9 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 20 Jul 2026 14:43:22 +0800 Subject: [PATCH 310/456] wifi: ath12k: fix out-of-bounds clear_bit in ath12k_mac_dp_peer_cleanup() ath12k_mac_dp_peer_cleanup() clears the ML peer ID slot on the free_ml_peer_id_map bitmap by indexing it with dp_peer->peer_id. That is wrong: dp_peer->peer_id for an MLO peer always carries the ATH12K_PEER_ML_ID_VALID bit (BIT(13)), so clear_bit() is invoked with index >= 0x2000, which is far outside the bitmap of ATH12K_MAX_MLO_PEERS (256) bits and corrupts memory adjacent to ah->free_ml_peer_id_map. The intended bitmap entry also never gets cleared, so subsequent ath12k_peer_ml_alloc() calls eventually run out of IDs. The ID without the VALID bit is what ath12k_peer_ml_alloc() returned and is stored in ahsta->ml_peer_id. Use that instead. While there, also reset ahsta->ml_peer_id to ATH12K_MLO_PEER_ID_INVALID so the bitmap and ahsta->ml_peer_id stay in sync. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: ee16dcf573d5 ("wifi: ath12k: Define ath12k_dp_peer structure & APIs for create & delete") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260720-ath12k-fw-allocated-ml-peer-id-v2-1-630632758a80@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 51c4df32e716..aa82c8fccc4e 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1287,8 +1287,11 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah) spin_lock_bh(&dp_hw->peer_lock); list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) { if (dp_peer->is_mlo) { + struct ath12k_sta *ahsta = ath12k_sta_to_ahsta(dp_peer->sta); + rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); - clear_bit(dp_peer->peer_id, ah->free_ml_peer_id_map); + clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); + ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; } list_move(&dp_peer->list, &peers); From 21ca38bb6b53a0b610998f370a91e656dc9e0542 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 20 Jul 2026 14:43:23 +0800 Subject: [PATCH 311/456] wifi: ath12k: factor out peer assoc send-and-wait into a helper ath12k_bss_assoc(), ath12k_mac_station_assoc() and ath12k_sta_rc_update_wk() all open-code the same sequence: reinit the peer_assoc_done completion, send the peer assoc WMI command, then wait for the firmware confirmation event. The reinit_completion() was buried in ath12k_peer_assoc_prepare(), far from the wait_for_completion_timeout() that consumes it, making the reinit/send/wait sequence hard to follow, and the three open-coded copies are easy to get out of sync. Move the sequence into a new helper ath12k_mac_peer_assoc() and call it from all three sites. The reinit, send and wait now live together so the completion's lifecycle is easy to read. While at it, ath12k_sta_rc_update_wk() previously warned but still waited the full timeout when the peer assoc command failed to send. Now a send failure returns immediately and skips the pointless 1 second wait, matching the other two callers. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260720-ath12k-fw-allocated-ml-peer-id-v2-2-630632758a80@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 59 +++++++++++++-------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index aa82c8fccc4e..b4df222f0c19 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3598,8 +3598,6 @@ static void ath12k_peer_assoc_prepare(struct ath12k *ar, memset(arg, 0, sizeof(*arg)); - reinit_completion(&ar->peer_assoc_done); - arg->peer_new_assoc = !reassoc; ath12k_peer_assoc_h_basic(ar, arvif, arsta, arg); ath12k_peer_assoc_h_crypto(ar, arvif, arsta, arg); @@ -3839,6 +3837,29 @@ static u32 ath12k_mac_ieee80211_sta_bw_to_wmi(struct ath12k *ar, return bw; } +static int ath12k_mac_peer_assoc(struct ath12k *ar, + struct ath12k_wmi_peer_assoc_arg *peer_arg) +{ + int ret; + + reinit_completion(&ar->peer_assoc_done); + + ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); + if (ret) { + ath12k_warn(ar->ab, "failed to run peer assoc for %pM vdev %i: %d\n", + peer_arg->peer_mac, peer_arg->vdev_id, ret); + return ret; + } + + if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) { + ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", + peer_arg->peer_mac, peer_arg->vdev_id); + return -ETIMEDOUT; + } + + return 0; +} + static void ath12k_bss_assoc(struct ath12k *ar, struct ath12k_link_vif *arvif, struct ieee80211_bss_conf *bss_conf) @@ -3919,18 +3940,10 @@ static void ath12k_bss_assoc(struct ath12k *ar, } peer_arg->is_assoc = true; - ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); - if (ret) { - ath12k_warn(ar->ab, "failed to run peer assoc for %pM vdev %i: %d\n", - bss_conf->bssid, arvif->vdev_id, ret); - return; - } - if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) { - ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", - bss_conf->bssid, arvif->vdev_id); + ret = ath12k_mac_peer_assoc(ar, peer_arg); + if (ret) return; - } ret = ath12k_setup_peer_smps(ar, arvif, bss_conf->bssid, &link_sta->ht_cap, &link_sta->he_6ghz_capa); @@ -6484,18 +6497,10 @@ static int ath12k_mac_station_assoc(struct ath12k *ar, } peer_arg->is_assoc = true; - ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); - if (ret) { - ath12k_warn(ar->ab, "failed to run peer assoc for STA %pM vdev %i: %d\n", - arsta->addr, arvif->vdev_id, ret); - return ret; - } - if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) { - ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", - arsta->addr, arvif->vdev_id); - return -ETIMEDOUT; - } + ret = ath12k_mac_peer_assoc(ar, peer_arg); + if (ret) + return ret; num_vht_rates = ath12k_mac_bitrate_mask_num_vht_rates(ar, band, mask); num_he_rates = ath12k_mac_bitrate_mask_num_he_rates(ar, band, mask); @@ -6844,14 +6849,8 @@ static void ath12k_sta_rc_update_wk(struct wiphy *wiphy, struct wiphy_work *wk) peer_arg, true); peer_arg->is_assoc = false; - err = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); - if (err) - ath12k_warn(ar->ab, "failed to run peer assoc for STA %pM vdev %i: %d\n", - arsta->addr, arvif->vdev_id, err); - if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) - ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", - arsta->addr, arvif->vdev_id); + ath12k_mac_peer_assoc(ar, peer_arg); } } } From dd121ed779dd62c7679815f7c5a0b07da60a39bf Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 20 Jul 2026 14:43:24 +0800 Subject: [PATCH 312/456] wifi: ath12k: keep ATH12K_PEER_ML_ID_VALID set in ath12k_sta::ml_peer_id Several pieces of host bookkeeping for MLD peer IDs encode the same fact in different ways: - ath12k_sta::ml_peer_id stores the raw ID in [0, ATH12K_MAX_MLO_PEERS); - ath12k_dp_peer::peer_id, ath12k_dp_link_peer::ml_id and the index used on ath12k_dp_hw::dp_peers[] always carry the ATH12K_PEER_ML_ID_VALID bit (BIT(13)) when the ID is real; - WMI_MLO_PEER_ASSOC_PARAMS::ml_peer_id sent down to firmware is raw, without the bookkeeping bit. The mismatch leaks into call sites that have to remember to OR the bit in (ath12k_peer_create(), ath12k_mac_op_sta_state()) or remember not to (ath12k_peer_assoc_h_mlo()). Make ath12k_sta::ml_peer_id carry the VALID bit when valid, the same way ath12k_dp_peer::peer_id and ath12k_dp_link_peer::ml_id do: - ath12k_peer_ml_alloc() OR-s the bit in once on the way out; the internal bitmap stays raw [0, ATH12K_MAX_MLO_PEERS); - ath12k_peer_create() and ath12k_mac_op_sta_state() drop the explicit OR; - ath12k_peer_assoc_h_mlo() masks the bit off when populating the WMI ml_peer_id; While there, introduce ath12k_peer_ml_free() to mirror ath12k_peer_ml_alloc(), which helps avoid code duplication. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260720-ath12k-fw-allocated-ml-peer-id-v2-3-630632758a80@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 27 +++++++++++++------------- drivers/net/wireless/ath/ath12k/peer.c | 17 +++++++++++++--- drivers/net/wireless/ath/ath12k/peer.h | 1 + 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index b4df222f0c19..06f1a1ba994a 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1282,16 +1282,15 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah) struct ath12k_dp_peer *dp_peer, *tmp; struct ath12k_dp_hw *dp_hw = &ah->dp_hw; + lockdep_assert_wiphy(ah->hw->wiphy); + INIT_LIST_HEAD(&peers); spin_lock_bh(&dp_hw->peer_lock); list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) { if (dp_peer->is_mlo) { - struct ath12k_sta *ahsta = ath12k_sta_to_ahsta(dp_peer->sta); - rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); - clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); - ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; + ath12k_peer_ml_free(ah, ath12k_sta_to_ahsta(dp_peer->sta)); } list_move(&dp_peer->list, &peers); @@ -3551,7 +3550,11 @@ static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta, ether_addr_copy(ml->mld_addr, sta->addr); ml->logical_link_idx = arsta->link_idx; - ml->ml_peer_id = ahsta->ml_peer_id; + /* + * WMI_MLO_PEER_ASSOC_PARAMS expects the raw ML peer ID without + * the host-side ATH12K_PEER_ML_ID_VALID bookkeeping bit. + */ + ml->ml_peer_id = ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID; ml->ieee_link_id = arsta->link_id; ml->num_partner_links = 0; ml->eml_cap = sta->eml_cap; @@ -7268,10 +7271,8 @@ static void ath12k_mac_ml_station_remove(struct ath12k_vif *ahvif, ath12k_mac_free_unassign_link_sta(ah, ahsta, link_id); } - if (sta->mlo) { - clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); - ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; - } + if (sta->mlo) + ath12k_peer_ml_free(ah, ahsta); } static int ath12k_mac_handle_link_sta_state(struct ieee80211_hw *hw, @@ -7743,7 +7744,7 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw, } dp_params.is_mlo = true; - dp_params.peer_id = ahsta->ml_peer_id | ATH12K_PEER_ML_ID_VALID; + dp_params.peer_id = ahsta->ml_peer_id; } dp_params.sta = sta; @@ -7880,10 +7881,8 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw, peer_delete: ath12k_dp_peer_delete(&ah->dp_hw, sta->addr, sta); ml_peer_id_clear: - if (sta->mlo) { - clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); - ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; - } + if (sta->mlo) + ath12k_peer_ml_free(ah, ahsta); exit: /* update the state if everything went well */ if (!ret) diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index 2681a047d4d5..5dd7c6470219 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -230,7 +230,7 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, /* Fill ML info into created peer */ if (sta->mlo) { ml_peer_id = ahsta->ml_peer_id; - peer->ml_id = ml_peer_id | ATH12K_PEER_ML_ID_VALID; + peer->ml_id = ml_peer_id; ether_addr_copy(peer->ml_addr, sta->addr); /* the assoc link is considered primary for now */ @@ -276,9 +276,20 @@ u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah) } if (ml_peer_id == ATH12K_MAX_MLO_PEERS) - ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; + return ATH12K_MLO_PEER_ID_INVALID; - return ml_peer_id; + return ml_peer_id | ATH12K_PEER_ML_ID_VALID; +} + +void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta) +{ + lockdep_assert_wiphy(ah->hw->wiphy); + + if (ahsta->ml_peer_id < + (ATH12K_MAX_MLO_PEERS | ATH12K_PEER_ML_ID_VALID)) + clear_bit(ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID, + ah->free_ml_peer_id_map); + ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; } int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta) diff --git a/drivers/net/wireless/ath/ath12k/peer.h b/drivers/net/wireless/ath/ath12k/peer.h index 49d89796bc46..0f7f25b8e89c 100644 --- a/drivers/net/wireless/ath/ath12k/peer.h +++ b/drivers/net/wireless/ath/ath12k/peer.h @@ -26,4 +26,5 @@ int ath12k_link_sta_rhash_add(struct ath12k_base *ab, struct ath12k_link_sta *ar struct ath12k_link_sta *ath12k_link_sta_find_by_addr(struct ath12k_base *ab, const u8 *addr); u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah); +void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta); #endif /* _PEER_H_ */ From a08455ee85a2b32a5503b84fdc6b88a144cb2388 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 20 Jul 2026 14:43:25 +0800 Subject: [PATCH 313/456] wifi: ath12k: add support for HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP Firmware on chips that allocate the MLD peer ID itself (WCN7850 and QCC2072) reports the assignment back to the host through HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP. The message carries the chosen MLD peer id, the MLD MAC address etc. Add the message type, the on-the-wire struct, the field masks and a handler that parses them out. The host-side state update (publishing the dp peer into ath12k_dp_hw::dp_peers[], propagating the ID to ath12k_dp_link_peer::ml_id and ath12k_sta::ml_peer_id) is added in a follow-up patch; Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260720-ath12k-fw-allocated-ml-peer-id-v2-4-630632758a80@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/dp_htt.c | 30 ++++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/dp_htt.h | 12 ++++++++++ 2 files changed, 42 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/dp_htt.c b/drivers/net/wireless/ath/ath12k/dp_htt.c index 52e10059c6d5..150b190f9c7f 100644 --- a/drivers/net/wireless/ath/ath12k/dp_htt.c +++ b/drivers/net/wireless/ath/ath12k/dp_htt.c @@ -575,6 +575,33 @@ static void ath12k_htt_mlo_offset_event_handler(struct ath12k_base *ab, rcu_read_unlock(); } +static void ath12k_dp_htt_mlo_peer_map_handler(struct ath12k_base *ab, + struct sk_buff *skb) +{ + struct htt_resp_msg *resp = (struct htt_resp_msg *)skb->data; + struct htt_t2h_mlo_peer_map_event *ev = &resp->mlo_peer_map_ev; + u16 raw_peer_id, peer_id, addr_h16; + u8 peer_addr[ETH_ALEN]; + + if (skb->len < sizeof(*ev)) { + ath12k_warn(ab, "unexpected htt mlo peer map event len %u\n", + skb->len); + return; + } + + raw_peer_id = le32_get_bits(ev->info0, + HTT_T2H_MLO_PEER_MAP_INFO0_MLO_PEER_ID); + peer_id = raw_peer_id | ATH12K_PEER_ML_ID_VALID; + + addr_h16 = le32_get_bits(ev->info1, + HTT_T2H_MLO_PEER_MAP_INFO1_MAC_ADDR_H16); + ath12k_dp_get_mac_addr(le32_to_cpu(ev->mac_addr_l32), addr_h16, + peer_addr); + + ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "htt mlo peer map peer %pM id %u\n", + peer_addr, peer_id); +} + void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab, struct sk_buff *skb) { @@ -659,6 +686,9 @@ void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab, case HTT_T2H_MSG_TYPE_MLO_TIMESTAMP_OFFSET_IND: ath12k_htt_mlo_offset_event_handler(ab, skb); break; + case HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP: + ath12k_dp_htt_mlo_peer_map_handler(ab, skb); + break; default: ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "dp_htt event %d not handled\n", type); diff --git a/drivers/net/wireless/ath/ath12k/dp_htt.h b/drivers/net/wireless/ath/ath12k/dp_htt.h index 987689f11cda..2db7fb27c036 100644 --- a/drivers/net/wireless/ath/ath12k/dp_htt.h +++ b/drivers/net/wireless/ath/ath12k/dp_htt.h @@ -930,6 +930,7 @@ enum htt_t2h_msg_type { HTT_T2H_MSG_TYPE_EXT_STATS_CONF = 0x1c, HTT_T2H_MSG_TYPE_BKPRESSURE_EVENT_IND = 0x24, HTT_T2H_MSG_TYPE_MLO_TIMESTAMP_OFFSET_IND = 0x28, + HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP = 0x29, HTT_T2H_MSG_TYPE_PEER_MAP3 = 0x2b, HTT_T2H_MSG_TYPE_VDEV_TXRX_STATS_PERIODIC_IND = 0x2c, }; @@ -974,11 +975,22 @@ struct htt_t2h_peer_unmap_event { __le32 info1; } __packed; +#define HTT_T2H_MLO_PEER_MAP_INFO0_MLO_PEER_ID GENMASK(23, 8) +#define HTT_T2H_MLO_PEER_MAP_INFO1_MAC_ADDR_H16 GENMASK(15, 0) + +struct htt_t2h_mlo_peer_map_event { + __le32 info0; + __le32 mac_addr_l32; + __le32 info1; + __le32 reserved[5]; +} __packed; + struct htt_resp_msg { union { struct htt_t2h_version_conf_msg version_msg; struct htt_t2h_peer_map_event peer_map_ev; struct htt_t2h_peer_unmap_event peer_unmap_ev; + struct htt_t2h_mlo_peer_map_event mlo_peer_map_ev; }; } __packed; From 378e659029d55cf57ee2eddf1d67672ed53c3bb4 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 20 Jul 2026 14:43:26 +0800 Subject: [PATCH 314/456] wifi: ath12k: introduce host_alloc_ml_id hardware parameter Different ath12k devices diverge on who allocates MLD peer id: WCN7850/QCC2072 have the firmware allocate it and notify the host via HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP event; While others let the host allocate it and pass it down through WMI_PEER_ASSOC_CMDID with ATH12K_WMI_FLAG_MLO_PEER_ID_VALID set. Currently ath12k host allocates this ID and sends it to firmware by default for all devices. This breaks WCN7850/QCC2072, because the host maintained ID may be different from the firmware-allocated one. Consequently data path may fail to find the dp peer and drop some received packets. From user point of view, this results in bugs reported in [1] or the 4-way handshake timeout issue. Add host_alloc_ml_id flag to struct ath12k_hw_params (and a copy on struct ath12k_hw for hot-path access) so subsequent patches can branch on it. Set true for QCN9274/IPQ5332/IPQ5424, false for WCN7850/QCC2072. The flag will be consumed by subsequent patches. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://bugzilla.kernel.org/show_bug.cgi?id=221039 # 1 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260720-ath12k-fw-allocated-ml-peer-id-v2-5-630632758a80@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/hw.h | 2 ++ drivers/net/wireless/ath/ath12k/mac.c | 18 +++++++++++++++++- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 12 ++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index fc5127b5c1a3..1f56474efbea 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -793,6 +793,7 @@ struct ath12k_hw { enum ath12k_hw_state state; bool regd_updated; bool use_6ghz_regd; + bool host_alloc_ml_id; u8 num_radio; diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index 86fb8b719613..8d2fa0bfb96c 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -236,6 +236,8 @@ struct ath12k_hw_params { u32 max_client_dbs; u32 max_client_dbs_sbs; } client; + + bool host_alloc_ml_id; }; struct ath12k_hw_ops { diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 06f1a1ba994a..51641c5ff265 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -15385,8 +15385,9 @@ int ath12k_mac_allocate(struct ath12k_hw_group *ag) int mac_id, device_id, total_radio, num_hw; struct ath12k_base *ab; struct ath12k_hw *ah; - int ret, i, j; + bool conf = false; u8 radio_per_hw; + int ret, i, j; total_radio = 0; for (i = 0; i < ag->num_devices; i++) { @@ -15426,6 +15427,20 @@ int ath12k_mac_allocate(struct ath12k_hw_group *ag) } ab = ag->ab[device_id]; + + /* + * the assumption is all devices within an ah + * share the same host_alloc_ml_id configuration + */ + if (j == 0) { + conf = ab->hw_params->host_alloc_ml_id; + } else if (conf != ab->hw_params->host_alloc_ml_id) { + ath12k_warn(ab, "inconsistent ML ID config within ah, device 0 uses %s allocated ID, while device %u doesn't\n", + conf ? "host" : "firmware", device_id); + ret = -EINVAL; + goto err; + } + pdev_map[j].ab = ab; pdev_map[j].pdev_idx = mac_id; mac_id++; @@ -15450,6 +15465,7 @@ int ath12k_mac_allocate(struct ath12k_hw_group *ag) } ah->dev = ab->dev; + ah->host_alloc_ml_id = conf; ag->ah[i] = ah; ag->num_hw++; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index d9fdd2fc8298..03dedfd907fc 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -442,6 +442,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = true, }, { .name = "wcn7850 hw2.0", @@ -533,6 +535,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = false, }, { .name = "qcn9274 hw2.0", @@ -620,6 +624,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = true, }, { .name = "ipq5332 hw1.0", @@ -700,6 +706,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = true, }, { .name = "qcc2072 hw1.0", @@ -792,6 +800,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = false, }, { .name = "ipq5424 hw1.0", @@ -876,6 +886,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .max_client_dbs = 128, .max_client_dbs_sbs = 128, }, + + .host_alloc_ml_id = true, }, }; From 1726a7a10c4fee262549bc6fa142e1051192be0c Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 20 Jul 2026 14:43:27 +0800 Subject: [PATCH 315/456] wifi: ath12k: do not advertise MLD peer ID for firmware-allocate devices ath12k_peer_assoc_h_mlo() unconditionally sets ml->peer_id_valid and copies ahsta->ml_peer_id (with the ATH12K_PEER_ML_ID_VALID bookkeeping bit masked off) into the WMI_PEER_ASSOC_CMDID ML params, which causes ath12k_wmi_send_peer_assoc_cmd() to set ATH12K_WMI_FLAG_MLO_PEER_ID_VALID. This needs to be gated on chips where the firmware allocates the MLD peer ID: - WCN7850/QCC2072 firmware always picks the ID itself and does not honor a host-supplied one, so the value would be silently ignored anyway; - QCC2072 firmware additionally crashes during MLO disconnect when ATH12K_WMI_FLAG_MLO_PEER_ID_VALID was set in the preceding peer assoc, so the bit must not be sent at all. Branch on ah->host_alloc_ml_id: - When true (QCN9274 etc.), behavior is unchanged: peer_id_valid is set and the raw ahsta->ml_peer_id (without the VALID bit) is sent down. - When false (WCN7850, QCC2072), peer_id_valid stays unset and ml_peer_id is sent as 0. The firmware ignores both fields and reports the ID it allocated through HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP. The early-return on ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID only applies on the host-alloc path, since on the firmware-alloc path the value is ATH12K_MLO_PEER_ID_PENDING here, not INVALID. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260720-ath12k-fw-allocated-ml-peer-id-v2-6-630632758a80@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 51641c5ff265..1004c290e5d0 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3533,11 +3533,16 @@ static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta, struct ath12k_sta *ahsta = arsta->ahsta; struct ath12k_link_sta *arsta_p; struct ath12k_link_vif *arvif; + struct ath12k_hw *ah = arsta->arvif->ar->ah; unsigned long links; u8 link_id; int i; - if (!sta->mlo || ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) + if (!sta->mlo) + return; + + if (ah->host_alloc_ml_id && + ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) return; ml->enabled = true; @@ -3545,16 +3550,25 @@ static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta, /* For now considering the primary umac based on assoc link */ ml->primary_umac = arsta->is_assoc_link; - ml->peer_id_valid = true; + /* + * Only chips that allocate the MLD peer ID on the host send a valid + * ml_peer_id in WMI_PEER_ASSOC_CMDID. For chips where the firmware + * picks the ID, leave peer_id_valid false to avoid unexpected issues. + */ + ml->peer_id_valid = ah->host_alloc_ml_id; ml->logical_link_idx_valid = true; ether_addr_copy(ml->mld_addr, sta->addr); ml->logical_link_idx = arsta->link_idx; /* * WMI_MLO_PEER_ASSOC_PARAMS expects the raw ML peer ID without - * the host-side ATH12K_PEER_ML_ID_VALID bookkeeping bit. + * the host-side ATH12K_PEER_ML_ID_VALID bookkeeping bit. For chips + * where the firmware allocates the ID, the field is unused (the + * firmware always allocates regardless of the value here); send 0 + * to make that intent explicit. */ - ml->ml_peer_id = ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID; + ml->ml_peer_id = ah->host_alloc_ml_id ? + (ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID) : 0; ml->ieee_link_id = arsta->link_id; ml->num_partner_links = 0; ml->eml_cap = sta->eml_cap; From a7619b3bcba42be62b3b4b941d4175234dce34f0 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 20 Jul 2026 14:43:28 +0800 Subject: [PATCH 316/456] wifi: ath12k: defer dp_peer registration when firmware allocates MLD peer ID For chips with host_alloc_ml_id=true (QCN9274 etc.), the host allocates the MLD peer ID up front; ath12k_dp_peer_create() publishes the dp_peer into dp_hw->dp_peers[] using that ID immediately. WCN7850/QCC2072 does not work that way: the firmware picks the ID and only tells the host afterwards via HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP, so the publication has to be delayed until the event arrives. Introduce ATH12K_MLO_PEER_ID_PENDING (0xFFFE) as a sentinel for "is_mlo, but ID not yet known". On the firmware-allocates path: - ath12k_mac_op_sta_state(NOTEXIST->NONE) skips ath12k_peer_ml_alloc() and stores PENDING in ahsta->ml_peer_id and dp_params.peer_id; - ath12k_dp_peer_create() skips dp_peer registration until a real ID is known; - ath12k_peer_create() leaves peer->ml_id at INVALID so consumer sites do not treat PENDING as a real ID; - ath12k_peer_ml_free() and ath12k_mac_dp_peer_cleanup() skip the dp_peers[] write and the free_ml_peer_id_map clear when host_alloc_ml_id is false or the ID is still PENDING. The HTT handler change that resolves the PENDING ID is added in a follow-up patch. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260720-ath12k-fw-allocated-ml-peer-id-v2-7-630632758a80@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/dp_peer.c | 23 +++++++++++++++-------- drivers/net/wireless/ath/ath12k/mac.c | 22 ++++++++++++++++------ drivers/net/wireless/ath/ath12k/peer.c | 20 +++++++++++++++++--- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 1f56474efbea..8769b41f5db5 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -72,6 +72,7 @@ #define ATH12K_MAX_MLO_PEERS 256 #define ATH12K_MLO_PEER_ID_INVALID 0xFFFF +#define ATH12K_MLO_PEER_ID_PENDING 0xFFFE #define ATH12K_INVALID_RSSI_FULL -1 #define ATH12K_INVALID_RSSI_EMPTY -128 diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c index a12073afc307..cd6a0eb207bd 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.c +++ b/drivers/net/wireless/ath/ath12k/dp_peer.c @@ -475,7 +475,9 @@ int ath12k_dp_peer_create(struct ath12k_dp_hw *dp_hw, u8 *addr, dp_peer->is_mlo = params->is_mlo; /* - * For MLO client, the host assigns the ML peer ID, so set peer_id in dp_peer + * For MLO client, the ML peer ID, either known or PENDING, needs to be + * initialized here since the following logic depends on it. + * * For non-MLO client, host gets link peer ID from firmware and will be * assigned at the time of link peer creation */ @@ -491,13 +493,17 @@ int ath12k_dp_peer_create(struct ath12k_dp_hw *dp_hw, u8 *addr, list_add(&dp_peer->list, &dp_hw->dp_peers_list); /* - * For MLO client, the peer_id for ath12k_dp_peer is allocated by host - * and that peer_id is known at this point, and hence this ath12k_dp_peer - * can be added to the RCU table using the peer_id. - * For non-MLO client, this addition to RCU table shall be done at the - * time of assignment of ath12k_dp_link_peer to ath12k_dp_peer. + * For an MLO client whose ML peer ID is allocated by the host, the + * peer_id is known here and the dp_peer can be added to the RCU + * table using it. For an MLO client on chips where the firmware + * allocates the ID, peer_id is ATH12K_MLO_PEER_ID_PENDING and the + * RCU table publish is deferred to the + * HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP handler. For a non-MLO client + * the publish happens later, at the time of assignment of + * ath12k_dp_link_peer to ath12k_dp_peer. */ - if (dp_peer->is_mlo) + if (dp_peer->is_mlo && + dp_peer->peer_id != ATH12K_MLO_PEER_ID_PENDING) rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], dp_peer); spin_unlock_bh(&dp_hw->peer_lock); @@ -518,7 +524,8 @@ void ath12k_dp_peer_delete(struct ath12k_dp_hw *dp_hw, u8 *addr, return; } - if (dp_peer->is_mlo) + if (dp_peer->is_mlo && + dp_peer->peer_id != ATH12K_MLO_PEER_ID_PENDING) rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); list_del(&dp_peer->list); diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 1004c290e5d0..760afe1c7f7a 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1289,7 +1289,9 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah) spin_lock_bh(&dp_hw->peer_lock); list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) { if (dp_peer->is_mlo) { - rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); + if (dp_peer->peer_id != ATH12K_MLO_PEER_ID_PENDING) + rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], + NULL); ath12k_peer_ml_free(ah, ath12k_sta_to_ahsta(dp_peer->sta)); } @@ -7750,11 +7752,19 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw, /* ML sta */ if (sta->mlo && !ahsta->links_map && (hweight16(sta->valid_links) == 1)) { - ahsta->ml_peer_id = ath12k_peer_ml_alloc(ah); - if (ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) { - ath12k_hw_warn(ah, "unable to allocate ML peer id for sta %pM", - sta->addr); - goto exit; + if (ah->host_alloc_ml_id) { + ahsta->ml_peer_id = ath12k_peer_ml_alloc(ah); + if (ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) { + ath12k_hw_warn(ah, "unable to allocate ML peer id for sta %pM", + sta->addr); + goto exit; + } + } else { + /* + * firmware allocates the ML peer ID and notifies + * the host via HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP + */ + ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_PENDING; } dp_params.is_mlo = true; diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index 5dd7c6470219..ed0524ddff80 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -230,7 +230,16 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, /* Fill ML info into created peer */ if (sta->mlo) { ml_peer_id = ahsta->ml_peer_id; - peer->ml_id = ml_peer_id; + /* + * For chips where firmware allocates the ML peer ID, + * ml_peer_id is ATH12K_MLO_PEER_ID_PENDING here. The + * MLO_RX_PEER_MAP HTT event handler fixes up + * peer->ml_id once the ID is known. + */ + if (ml_peer_id == ATH12K_MLO_PEER_ID_PENDING) + peer->ml_id = ATH12K_MLO_PEER_ID_INVALID; + else + peer->ml_id = ml_peer_id; ether_addr_copy(peer->ml_addr, sta->addr); /* the assoc link is considered primary for now */ @@ -285,8 +294,13 @@ void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta) { lockdep_assert_wiphy(ah->hw->wiphy); - if (ahsta->ml_peer_id < - (ATH12K_MAX_MLO_PEERS | ATH12K_PEER_ML_ID_VALID)) + /* + * Only devices that allocate the ID on the host own a slot in + * free_ml_peer_id_map. + */ + if (ah->host_alloc_ml_id && + (ahsta->ml_peer_id < + (ATH12K_MAX_MLO_PEERS | ATH12K_PEER_ML_ID_VALID))) clear_bit(ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID, ah->free_ml_peer_id_map); ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; From 469d7e6077c1665754eaf330e1feabdca7b060ae Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 20 Jul 2026 14:43:29 +0800 Subject: [PATCH 317/456] wifi: ath12k: resolve PENDING ML peer ID from MLO_PEER_MAP HTT event Add ath12k_dp_peer_fixup_peer_id() and call it from the HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP handler. For devices where the firmware allocates the MLD peer ID, this is the point at which all data structures that were left with ATH12K_MLO_PEER_ID_PENDING or ATH12K_MLO_PEER_ID_INVALID get their real ID: - dp_peer->peer_id is updated and the dp_peer is published into dp_hw->dp_peers[]; - every existing dp_link_peer in dp_peer->link_peers[] gets its ml_id set to the same value; - ahsta->ml_peer_id is updated to the same value so peer_assoc, sta_state and cleanup paths see a consistent ID. Devices with host_alloc_ml_id == true also receive the same HTT event, but the firmware-reported ID always matches the host-allocated one and everything has already been populated by ath12k_dp_peer_create(); Skips the helper entirely on those devices. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221039 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260720-ath12k-fw-allocated-ml-peer-id-v2-8-630632758a80@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 2 + drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/dp_htt.c | 19 +++++++++ drivers/net/wireless/ath/ath12k/dp_peer.c | 52 +++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/dp_peer.h | 2 + drivers/net/wireless/ath/ath12k/mac.c | 24 +++++++++++ 6 files changed, 100 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 742d4fd1b598..e87165e4f4b3 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1544,6 +1544,8 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab) } wiphy_unlock(ah->hw->wiphy); + + complete(&ah->peer_ml_id_done); } wake_up(&ab->wmi_ab.tx_credits_wq); diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index 8769b41f5db5..30726e580833 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -795,6 +795,7 @@ struct ath12k_hw { bool regd_updated; bool use_6ghz_regd; bool host_alloc_ml_id; + struct completion peer_ml_id_done; u8 num_radio; diff --git a/drivers/net/wireless/ath/ath12k/dp_htt.c b/drivers/net/wireless/ath/ath12k/dp_htt.c index 150b190f9c7f..68968f96b4f1 100644 --- a/drivers/net/wireless/ath/ath12k/dp_htt.c +++ b/drivers/net/wireless/ath/ath12k/dp_htt.c @@ -6,6 +6,7 @@ #include "core.h" #include "peer.h" +#include "dp_peer.h" #include "htc.h" #include "dp_htt.h" #include "debugfs_htt_stats.h" @@ -582,6 +583,7 @@ static void ath12k_dp_htt_mlo_peer_map_handler(struct ath12k_base *ab, struct htt_t2h_mlo_peer_map_event *ev = &resp->mlo_peer_map_ev; u16 raw_peer_id, peer_id, addr_h16; u8 peer_addr[ETH_ALEN]; + int ret; if (skb->len < sizeof(*ev)) { ath12k_warn(ab, "unexpected htt mlo peer map event len %u\n", @@ -600,6 +602,23 @@ static void ath12k_dp_htt_mlo_peer_map_handler(struct ath12k_base *ab, ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "htt mlo peer map peer %pM id %u\n", peer_addr, peer_id); + + /* + * Fix up the dp_peer entry created with ATH12K_MLO_PEER_ID_PENDING + * earlier; on chips with host_alloc_ml_id == false this is the only + * point at which the host learns the firmware-assigned ID. Chips + * that allocate the ID on the host also receive this event but the + * firmware-reported ID matches the host-allocated one, so there is + * nothing to fix up. + */ + if (!ab->hw_params->host_alloc_ml_id) { + ret = ath12k_dp_peer_fixup_peer_id(ab, peer_addr, + peer_id); + if (ret) + ath12k_warn(ab, + "failed to fix up peer id %u for dp peer %pM: %d\n", + peer_id, peer_addr, ret); + } } void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab, diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c index cd6a0eb207bd..bb5341b4251a 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.c +++ b/drivers/net/wireless/ath/ath12k/dp_peer.c @@ -702,3 +702,55 @@ void ath12k_dp_link_peer_reset_rx_stats(struct ath12k_dp *dp, const u8 *addr) if (rx_stats) memset(rx_stats, 0, sizeof(*rx_stats)); } + +int ath12k_dp_peer_fixup_peer_id(struct ath12k_base *ab, + const u8 *peer_addr, u16 peer_id) +{ + struct ath12k_dp_link_peer *link_peer; + struct ath12k_dp_peer *dp_peer = NULL; + struct ath12k_hw_group *ag = ab->ag; + struct ath12k_dp_hw *dp_hw = NULL; + struct ath12k_hw *ah; + int i; + + if (peer_id >= (ATH12K_PEER_ML_ID_VALID | ATH12K_MAX_MLO_PEERS)) + return -EINVAL; + + for (i = 0; i < ag->num_hw; i++) { + ah = ag->ah[i]; + if (!ah) + continue; + + spin_lock_bh(&ah->dp_hw.peer_lock); + dp_peer = ath12k_dp_peer_find_by_addr(&ah->dp_hw, + (u8 *)peer_addr); + if (dp_peer) { + dp_hw = &ah->dp_hw; + break; + } + spin_unlock_bh(&ah->dp_hw.peer_lock); + } + + if (!dp_peer) + return -ENOENT; + + /* dp_hw->peer_lock is held */ + + dp_peer->peer_id = peer_id; + rcu_assign_pointer(dp_hw->dp_peers[peer_id], dp_peer); + + for (i = 0; i < ATH12K_NUM_MAX_LINKS; i++) { + link_peer = rcu_dereference_protected(dp_peer->link_peers[i], + lockdep_is_held(&dp_hw->peer_lock)); + if (link_peer) + link_peer->ml_id = peer_id; + } + + ath12k_sta_to_ahsta(dp_peer->sta)->ml_peer_id = peer_id; + + spin_unlock_bh(&dp_hw->peer_lock); + + complete(&ah->peer_ml_id_done); + + return 0; +} diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.h b/drivers/net/wireless/ath/ath12k/dp_peer.h index 7c9709bf717b..3503840b0329 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.h +++ b/drivers/net/wireless/ath/ath12k/dp_peer.h @@ -181,4 +181,6 @@ struct ath12k_dp_peer *ath12k_dp_peer_find_by_peerid(struct ath12k_pdev_dp *dp_p struct ath12k_dp_link_peer * ath12k_dp_link_peer_find_by_peerid(struct ath12k_pdev_dp *dp_pdev, u16 peer_id); void ath12k_dp_link_peer_free(struct ath12k_dp_link_peer *peer); +int ath12k_dp_peer_fixup_peer_id(struct ath12k_base *ab, const u8 *peer_addr, + u16 peer_id); #endif diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 760afe1c7f7a..a0928890671a 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3859,9 +3859,11 @@ static u32 ath12k_mac_ieee80211_sta_bw_to_wmi(struct ath12k *ar, static int ath12k_mac_peer_assoc(struct ath12k *ar, struct ath12k_wmi_peer_assoc_arg *peer_arg) { + struct ath12k_hw *ah = ath12k_ar_to_ah(ar); int ret; reinit_completion(&ar->peer_assoc_done); + reinit_completion(&ah->peer_ml_id_done); ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); if (ret) { @@ -3876,6 +3878,27 @@ static int ath12k_mac_peer_assoc(struct ath12k *ar, return -ETIMEDOUT; } + /* + * For devices where the firmware allocates the MLD peer ID, the host + * learns the real ID only from the MLO_RX_PEER_MAP HTT event, which is + * handled in a softirq (BH workqueue) context that cannot take the + * wiphy lock. Block here, while still holding the wiphy lock, until + * that event has fixed up the ID. This serialises the fixup against + * all other wiphy-locked ml_peer_id accesses. + * + * The firmware sends the event only once, in response to the assoc-link + * peer assoc, so block only for that link. + */ + if (!ah->host_alloc_ml_id && + peer_arg->is_assoc && + peer_arg->ml.enabled && + peer_arg->ml.assoc_link && + !wait_for_completion_timeout(&ah->peer_ml_id_done, 1 * HZ)) { + ath12k_warn(ar->ab, "failed to get MLO peer map event for %pM vdev %i\n", + peer_arg->peer_mac, peer_arg->vdev_id); + return -ETIMEDOUT; + } + return 0; } @@ -15335,6 +15358,7 @@ static struct ath12k_hw *ath12k_mac_hw_allocate(struct ath12k_hw_group *ag, ah->num_radio = num_pdev_map; mutex_init(&ah->hw_mutex); + init_completion(&ah->peer_ml_id_done); spin_lock_init(&ah->dp_hw.peer_lock); INIT_LIST_HEAD(&ah->dp_hw.dp_peers_list); From dbc3791e3b2472e1ccc08947e0f83b443470ff4f Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 24 Jul 2026 07:29:01 +0000 Subject: [PATCH 318/456] net: do not send ICMP/NDISC Redirects when peer allocation fails When inet_getpeer_v4() or inet_getpeer_v6() fails to allocate a peer entry under memory pressure or tree size caps, redirect handlers previously fell back to sending un-rate-limited ICMP/NDISC Redirect messages. In IPv4, ip_rt_send_redirect() called icmp_send() directly when peer == NULL. In IPv6, ip6_forward() and ndisc_send_redirect() passed a NULL peer into inet_peer_xrlim_allow(), which returned true when peer == NULL. Because ICMP/NDISC Redirects are not part of the default global rate limit mask (sysctl_icmp_ratemask), sending redirects when peer == NULL creates an un-rate-limited ICMP packet storm. Fix this by failing closed in ip_rt_send_redirect(), ip6_forward(), and ndisc_send_redirect() when peer is NULL. Fixes: 92d868292634 ("inetpeer: Move ICMP rate limiting state into inet_peer entries.") Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260724072901.1633601-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv4/route.c | 2 -- net/ipv6/ip6_output.c | 2 +- net/ipv6/ndisc.c | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 3f3de5164d6e..152d8cb28f65 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -892,8 +892,6 @@ void ip_rt_send_redirect(struct sk_buff *skb) peer = inet_getpeer_v4(net->ipv4.peers, ip_hdr(skb)->saddr, vif); if (!peer) { rcu_read_unlock(); - icmp_send(skb, ICMP_REDIRECT, ICMP_REDIR_HOST, - rt_nexthop(rt, ip_hdr(skb)->daddr)); return; } diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 368e4fa3b43c..2c44e5ed6171 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -641,7 +641,7 @@ int ip6_forward(struct sk_buff *skb) /* Limit redirects both by destination (here) and by source (inside ndisc_send_redirect) */ - if (inet_peer_xrlim_allow(peer, 1*HZ)) + if (peer && inet_peer_xrlim_allow(peer, 1*HZ)) ndisc_send_redirect(skb, target); rcu_read_unlock(); } else { diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c index f867ec8d3d90..fe36b3f51285 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -1707,6 +1707,8 @@ void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target) } peer = inet_getpeer_v6(net->ipv6.peers, &ipv6_hdr(skb)->saddr); + if (!peer) + goto release; ret = inet_peer_xrlim_allow(peer, 1*HZ); if (!ret) From 1395a676ec15a0a02a2a6d86602324f2d5fd41d5 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:45 +0000 Subject: [PATCH 319/456] vxlan: re-fetch eth header after route_shortcircuit() Before route_shortcircuit(), the eth header pointer is cached from eth_hdr(skb). Inside route_shortcircuit(), pskb_may_pull() can be called, which may reallocate skb->head. In this case, returning to vxlan_xmit() leaves the cached eth pointer pointing to freed memory, leading to a use-after-free when dereferencing eth->h_dest. Fix this by updating eth = eth_hdr(skb) after calling route_shortcircuit(). Fixes: ae8840825605 ("VXLAN: Allow L2 redirection with L3 switching") Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260723144249.759100-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index d834a4865aec..a05654a55bd6 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2796,6 +2796,7 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev) (ntohs(eth->h_proto) == ETH_P_IP || ntohs(eth->h_proto) == ETH_P_IPV6)) { did_rsc = route_shortcircuit(dev, skb); + eth = eth_hdr(skb); if (did_rsc) f = vxlan_find_mac_tx(vxlan, eth->h_dest, vni); } From 760d36e737f2b3867762f42af36c663f55babcc4 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:46 +0000 Subject: [PATCH 320/456] vxlan: unclone skb head before modifying eth header in route_shortcircuit() When route_shortcircuit() performs L3 short-circuit routing, it modifies the Ethernet header of the skb in-place: memcpy(eth_hdr(skb)->h_source, eth_hdr(skb)->h_dest, dev->addr_len); memcpy(eth_hdr(skb)->h_dest, n->ha, dev->addr_len); If the incoming skb is cloned (for example by packet sockets, tcpdump, or dev_queue_xmit), modifying the Ethernet header without uncloning can corrupt the packet header for other readers holding a reference to the cloned skb. Ensure the skb header is writable and unshared by calling skb_cow_head(skb, 0) prior to updating the Ethernet header. If skb_cow_head() fails, abort short-circuiting and return false to allow standard packet processing fallback. Fixes: e4f67addf158 ("add DOVE extensions for VXLAN") Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260723144249.759100-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index a05654a55bd6..e831fe203442 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2163,6 +2163,10 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb) diff = !ether_addr_equal(eth_hdr(skb)->h_dest, n->ha); if (diff) { + if (skb_cow_head(skb, 0)) { + neigh_release(n); + return false; + } memcpy(eth_hdr(skb)->h_source, eth_hdr(skb)->h_dest, dev->addr_len); memcpy(eth_hdr(skb)->h_dest, n->ha, dev->addr_len); From 8eca411347e1d38964f9ed2c8d3b6ab0e7e4473d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:47 +0000 Subject: [PATCH 321/456] vxlan: use neigh_ha_snapshot() in route_shortcircuit() The neighbour hardware address n->ha can be updated asynchronously by the neighbour subsystem, protected by n->ha_lock seqlock. Reading n->ha without holding the seqlock loop can lead to torn reads or reading a partially updated MAC address. Use neigh_ha_snapshot() in route_shortcircuit() to safely copy n->ha under read_seqbegin()/read_seqretry() lock protection before using it. Note that arp_reduce() and neigh_reduce() seem to have the same issue left for future patches. Fixes: e4f67addf158 ("add DOVE extensions for VXLAN") Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260723144249.759100-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index e831fe203442..be3c2bc2cd9a 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2159,9 +2159,11 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb) } if (n) { + u8 haddr[ETH_ALEN]; bool diff; - diff = !ether_addr_equal(eth_hdr(skb)->h_dest, n->ha); + neigh_ha_snapshot(haddr, n, dev); + diff = !ether_addr_equal_unaligned(eth_hdr(skb)->h_dest, haddr); if (diff) { if (skb_cow_head(skb, 0)) { neigh_release(n); @@ -2169,7 +2171,7 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb) } memcpy(eth_hdr(skb)->h_source, eth_hdr(skb)->h_dest, dev->addr_len); - memcpy(eth_hdr(skb)->h_dest, n->ha, dev->addr_len); + memcpy(eth_hdr(skb)->h_dest, haddr, dev->addr_len); } neigh_release(n); return diff; From 26bb2dd0a8839617e2c79ffbbe1923f8e4bab9fb Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:48 +0000 Subject: [PATCH 322/456] vxlan: use pskb_network_may_pull() in route_shortcircuit() route_shortcircuit() currently calls pskb_may_pull(skb, sizeof(struct iphdr)) (or ipv6hdr), which checks if bytes are available starting from skb->data. However, in vxlan_xmit(), skb->data points to the MAC header, so skb_network_offset(skb) is ETH_HLEN (14 bytes). Using pskb_may_pull(skb, 20) only checks 20 bytes from skb->data (which is 14 bytes MAC header + 6 bytes of IP header), leaving the rest of the IP header potentially un-pulled in non-linear frags. Subsequent dereferences of ip_hdr(skb)->daddr can read beyond the pulled linear buffer length. Fix this by using pskb_network_may_pull(), which adds skb_network_offset(skb) to the length check to ensure the full network header is present in the linear buffer. Fixes: e4f67addf158 ("add DOVE extensions for VXLAN") Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260723144249.759100-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index be3c2bc2cd9a..2163e2687db0 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2111,7 +2111,7 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb) { struct iphdr *pip; - if (!pskb_may_pull(skb, sizeof(struct iphdr))) + if (!pskb_network_may_pull(skb, sizeof(struct iphdr))) return false; pip = ip_hdr(skb); n = neigh_lookup(&arp_tbl, &pip->daddr, dev); @@ -2137,7 +2137,7 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb) */ if (!ipv6_mod_enabled()) return false; - if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) + if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr))) return false; pip6 = ipv6_hdr(skb); n = neigh_lookup(&nd_tbl, &pip6->daddr, dev); From b9553558b48db54ac9273e6b98d7263ef5c1a329 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 23 Jul 2026 14:42:49 +0000 Subject: [PATCH 323/456] vxlan: use pskb_network_may_pull() for transmit path header pulls In vxlan_xmit(), arp_reduce(), and vxlan_mdb_entry_skb_get(), pskb_may_pull() was being called to verify the availability of network layer headers (ARP, IPv6/ND, IP/IPv6 MDB keys). However, during transmit skb->data points to the MAC header, so skb_network_offset(skb) is ETH_HLEN (14 bytes). Using pskb_may_pull(skb, len) only checks len bytes from skb->data rather than skb_network_offset(skb) + len, which can leave part of the network header in non-linear frags. Replace these remaining pskb_may_pull() calls with pskb_network_may_pull() to properly account for the MAC header offset. Fixes: e4f67addf158 ("add DOVE extensions for VXLAN") Fixes: f564f45c4518 ("vxlan: add ipv6 proxy support") Fixes: 0f83e69f44bf ("vxlan: Add MDB data path support") Signed-off-by: Eric Dumazet Cc: stable@vger.kernel.org Reviewed-by: Vadim Fedorenko Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260723144249.759100-6-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 6 +++--- drivers/net/vxlan/vxlan_mdb.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 2163e2687db0..1ded27768a97 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -1850,7 +1850,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni) if (dev->flags & IFF_NOARP) goto out; - if (!pskb_may_pull(skb, arp_hdr_len(dev))) { + if (!pskb_network_may_pull(skb, arp_hdr_len(dev))) { dev_dstats_tx_dropped(dev); vxlan_vnifilter_count(vxlan, vni, NULL, VXLAN_VNI_STATS_TX_DROPS, 0); @@ -2763,8 +2763,8 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev) return arp_reduce(dev, skb, vni); #if IS_ENABLED(CONFIG_IPV6) else if (ntohs(eth->h_proto) == ETH_P_IPV6 && - pskb_may_pull(skb, sizeof(struct ipv6hdr) + - sizeof(struct nd_msg)) && + pskb_network_may_pull(skb, sizeof(struct ipv6hdr) + + sizeof(struct nd_msg)) && ipv6_hdr(skb)->nexthdr == IPPROTO_ICMPV6) { struct nd_msg *m = (struct nd_msg *)(ipv6_hdr(skb) + 1); diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c index af7a0d7f95a5..9a9038ae90c1 100644 --- a/drivers/net/vxlan/vxlan_mdb.c +++ b/drivers/net/vxlan/vxlan_mdb.c @@ -1631,7 +1631,7 @@ struct vxlan_mdb_entry *vxlan_mdb_entry_skb_get(struct vxlan_dev *vxlan, switch (skb->protocol) { case htons(ETH_P_IP): - if (!pskb_may_pull(skb, sizeof(struct iphdr))) + if (!pskb_network_may_pull(skb, sizeof(struct iphdr))) return NULL; group.dst.sa.sa_family = AF_INET; group.dst.sin.sin_addr.s_addr = ip_hdr(skb)->daddr; @@ -1640,7 +1640,7 @@ struct vxlan_mdb_entry *vxlan_mdb_entry_skb_get(struct vxlan_dev *vxlan, break; #if IS_ENABLED(CONFIG_IPV6) case htons(ETH_P_IPV6): - if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) + if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr))) return NULL; group.dst.sa.sa_family = AF_INET6; group.dst.sin6.sin6_addr = ipv6_hdr(skb)->daddr; From b4f1719dfea023220e0e6bd892b087d76b2a6a49 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Fri, 24 Jul 2026 00:38:41 +0800 Subject: [PATCH 324/456] tipc: avoid use-after-free in poll trace queue dumps TIPC socket tracepoints dump queue state through tipc_sk_dump(). Most queue-dump callsites already serialize that walk under the socket lock or sk->sk_lock.slock, but tipc_poll() calls trace_tipc_sk_poll(..., TIPC_DUMP_ALL, ...) without holding either lock. That lets the poll trace path reach tipc_list_dump() and backlog head/tail dumping while another context dequeues and frees an skb, leaving the trace helper dereferencing a stale queue entry. Stop the unlocked poll trace site from requesting queue dumps. Other queue dump trace callsites keep their existing output under the locking they already provide, while poll still emits the event itself without walking live queue members from an unlocked context. Fixes: b4b9771bcbbd ("tipc: enable tracepoints in tipc") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zihan Xi Signed-off-by: Ren Wei Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/f8119abd5e5ecc400597de667ae9d39656de56d0.1784794294.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/tipc/socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/tipc/socket.c b/net/tipc/socket.c index 185c24003b82..d5d70eb230b5 100644 --- a/net/tipc/socket.c +++ b/net/tipc/socket.c @@ -796,7 +796,7 @@ static __poll_t tipc_poll(struct file *file, struct socket *sock, __poll_t revents = 0; sock_poll_wait(file, sock, wait); - trace_tipc_sk_poll(sk, NULL, TIPC_DUMP_ALL, " "); + trace_tipc_sk_poll(sk, NULL, TIPC_DUMP_NONE, " "); if (sk->sk_shutdown & RCV_SHUTDOWN) revents |= EPOLLRDHUP | EPOLLIN | EPOLLRDNORM; From 6aea62e433fe1b586202a5fee8b5807ce635e1d7 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Fri, 24 Jul 2026 00:48:52 +0800 Subject: [PATCH 325/456] net: ipv6: clear suppressed fib6 rule result fib6_rule_suppress() drops a suppressed route with ip6_rt_put_flags(), but leaves res->rt6 pointing at the released rt6_info. If no later rule supplies a replacement, fib6_rule_lookup() still sees res.rt6 and returns that stale dst to its caller. A suppressing rule can therefore leak a released route back to rt6_lookup(), and the next put hits rcuref_put_slowpath() from dst_release(). Clear res->rt6 when suppressing the route so suppressed lookups fall through to the null dst instead of reusing the released one. Fixes: cdef485217d3 ("ipv6: fix memory leak in fib6_rule_suppress") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Ren Wei Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/4b8acb7787d54e440155585dd32ebdf0bef7d122.1784710966.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv6/fib6_rules.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c index e1b2b4fa6e18..89ee3c969ca7 100644 --- a/net/ipv6/fib6_rules.c +++ b/net/ipv6/fib6_rules.c @@ -308,6 +308,7 @@ INDIRECT_CALLABLE_SCOPE bool fib6_rule_suppress(struct fib_rule *rule, suppress_route: ip6_rt_put_flags(rt, flags); + res->rt6 = NULL; return true; } From a39789f211b8a4125f0c70e05b30cf715f4f187d Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Fri, 24 Jul 2026 00:52:48 +0800 Subject: [PATCH 326/456] net: bridge: stop fast-leave after deleting a port group br_multicast_leave_group() iterates mp->ports with pp = &p->next in its fast-leave path. After br_multicast_del_pg() removes p, continuing the loop advances pp through the deleted entry. If multicast-to-unicast was enabled, the bridge can hold multiple port groups for the same port and group with different source MAC addresses. Once multicast-to-unicast is disabled, br_port_group_equal() matches those entries by port only. A fast leave can then delete one entry and continue from its stale next pointer, leaving mp->ports pointing at a deleted port group. Fast leave only needs to remove one matching port group. Break after br_multicast_del_pg() so the loop stops before dereferencing the removed entry. Fixes: 6db6f0eae605 ("bridge: multicast to unicast") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Ren Wei Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/1cf0898872ef7c72d5f4c0304414a192c6dac591.1784707712.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/bridge/br_multicast.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 6b3ac473fd22..00aa9b2879d6 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -3687,6 +3687,7 @@ br_multicast_leave_group(struct net_bridge_mcast *brmctx, p->flags |= MDB_PG_FLAGS_FAST_LEAVE; br_multicast_del_pg(mp, p, pp); + break; } goto out; } From 9d8da8e0a9bce4a340af60dd0446bc7eb8d07587 Mon Sep 17 00:00:00 2001 From: Yuxiang Yang Date: Thu, 23 Jul 2026 22:56:23 +0000 Subject: [PATCH 327/456] sctp: reject stale cookies with mismatched verification tags sctp_unpack_cookie() skips cookie expiration checks whenever an association already exists. This is broader than the exception in RFC 9260 Section 5.2.4. For an existing association, Section 5.2.4 permits an expired State Cookie only when both Verification Tags in the cookie match the current association. Otherwise, the packet SHOULD be discarded and a Stale Cookie ERROR MUST be sent. The broad check lets an expired Action A restart cookie reach sctp_sf_do_dupcook_a(). In a runtime test with the default 60 second cookie lifetime, replaying such a cookie after 65 seconds returned a COOKIE-ACK and restarted the association. Check cookie expiration unless both Verification Tags match. This preserves the Action D exception for a lost COOKIE ACK while rejecting expired cookies in all other cases. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Yuxiang Yang Acked-by: Xin Long Link: https://patch.msgid.link/20260723225623.2658868-1-yangyx22@mails.tsinghua.edu.cn Signed-off-by: Jakub Kicinski --- net/sctp/sm_make_chunk.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index c02809264075..a1c0334a1038 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -1802,9 +1802,9 @@ struct sctp_association *sctp_unpack_cookie( goto fail; } - /* Check to see if the cookie is stale. If there is already - * an association, there is no need to check cookie's expiration - * for init collision case of lost COOKIE ACK. + /* Check to see if the cookie is stale. RFC 9260 Section 5.2.4 + * exempts an expired cookie only when both Verification Tags match + * the current association. * If skb has been timestamped, then use the stamp, otherwise * use current time. This introduces a small possibility that * a cookie may be considered expired, but this would only slow @@ -1815,7 +1815,10 @@ struct sctp_association *sctp_unpack_cookie( else kt = ktime_get_real(); - if (!asoc && ktime_before(bear_cookie->expiration, kt)) { + if ((!asoc || + asoc->c.my_vtag != bear_cookie->my_vtag || + asoc->c.peer_vtag != bear_cookie->peer_vtag) && + ktime_before(bear_cookie->expiration, kt)) { suseconds_t usecs = ktime_to_us(ktime_sub(kt, bear_cookie->expiration)); __be32 n = htonl(usecs); From bd0e9289e2642f6a5c54faad304ce0f41e926d22 Mon Sep 17 00:00:00 2001 From: Asim Viladi Oglu Manizada Date: Sat, 25 Jul 2026 03:21:06 +0000 Subject: [PATCH 328/456] sctp: prevent peer transport count overflow sctp_assoc_add_peer() increments the association's 16-bit transport_count for every new unique peer. Adding the 65,536th transport wraps the count to zero. SCTP sock_diag uses transport_count to reserve the INET_DIAG_PEERS payload, then copies one sockaddr_storage for every entry in transport_addr_list. After the wrap, a diagnostic dump reserves an empty payload and writes 8 MiB of peer addresses past the skb tail. Reject a new unique peer when transport_count has reached U16_MAX. Perform the check after the existing-peer lookup so a duplicate address continues to return its existing transport at the limit. Fixes: 8f840e47f190 ("sctp: add the sctp_diag.c file") Cc: stable@vger.kernel.org Signed-off-by: Asim Viladi Oglu Manizada Acked-by: Xin Long Link: https://patch.msgid.link/20260725032053.521705-1-manizada@pm.me Signed-off-by: Jakub Kicinski --- net/sctp/associola.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sctp/associola.c b/net/sctp/associola.c index 62d3cc155809..b6ac0966420a 100644 --- a/net/sctp/associola.c +++ b/net/sctp/associola.c @@ -614,6 +614,9 @@ struct sctp_transport *sctp_assoc_add_peer(struct sctp_association *asoc, return peer; } + if (asoc->peer.transport_count == U16_MAX) + return NULL; + peer = sctp_transport_new(asoc->base.net, addr, gfp); if (!peer) return NULL; From 5546da86894d5906f131b05890705a7abf949d84 Mon Sep 17 00:00:00 2001 From: David Corvaglia Date: Sun, 26 Jul 2026 06:26:05 +0000 Subject: [PATCH 329/456] net: bridge: mrp: fix Option TLV length in MRP_Test frames oui is a pointer, so sizeof(oui) is the pointer size. The MRA Option TLV thus advertises a wrong length (15 vs 10 on x86_64), causing misparsing of the frame on peers. Fix is to replace with sizeof(*oui). Fixes: f7458934b079 ("net: bridge: mrp: Update the Test frames for MRA") Signed-off-by: David Corvaglia Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260726062605.2746-1-david@corvaglia.dev Signed-off-by: Jakub Kicinski --- net/bridge/br_mrp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bridge/br_mrp.c b/net/bridge/br_mrp.c index 3f7126a7d720..179d2470b724 100644 --- a/net/bridge/br_mrp.c +++ b/net/bridge/br_mrp.c @@ -215,7 +215,7 @@ static struct sk_buff *br_mrp_alloc_test_skb(struct br_mrp *mrp, struct br_mrp_oui_hdr *oui = NULL; u8 length; - length = sizeof(*sub_opt) + sizeof(*sub_tlv) + sizeof(oui) + + length = sizeof(*sub_opt) + sizeof(*sub_tlv) + sizeof(*oui) + MRP_OPT_PADDING; br_mrp_skb_tlv(skb, BR_MRP_TLV_HEADER_OPTION, length); From 22666ba1420164753d7b0f5a841986b25ace5435 Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Thu, 23 Jul 2026 17:26:37 +0800 Subject: [PATCH 330/456] forcedeth: fix UAF of txrx_stats in nv_remove nv_remove() frees the per-CPU txrx_stats before unregister_netdev(). Until unregister completes, ndo_get_stats64, the NAPI/xmit data path, and nv_close()/drain may still access txrx_stats, leading to a use-after-free. Free the stats only after unregister_netdev(). Fixes: f4b633b911fd ("forcedeth: use per cpu to collect xmit/recv statistics") Signed-off-by: Chenguang Zhao Reviewed-by: Vadim Fedorenko Reviewed-by: Zhu Yanjun Link: https://patch.msgid.link/20260723092637.2135095-1-chenguang.zhao@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/nvidia/forcedeth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/nvidia/forcedeth.c b/drivers/net/ethernet/nvidia/forcedeth.c index 5b0435d7bc39..58d3e55def48 100644 --- a/drivers/net/ethernet/nvidia/forcedeth.c +++ b/drivers/net/ethernet/nvidia/forcedeth.c @@ -6187,10 +6187,10 @@ static void nv_remove(struct pci_dev *pci_dev) struct net_device *dev = pci_get_drvdata(pci_dev); struct fe_priv *np = netdev_priv(dev); - free_percpu(np->txrx_stats); - unregister_netdev(dev); + free_percpu(np->txrx_stats); + nv_restore_mac_addr(pci_dev); /* restore any phy related changes */ From d211028bac1bd0fff0026bfa2a8328e5b78cd0e6 Mon Sep 17 00:00:00 2001 From: Aswin Karuvally Date: Thu, 23 Jul 2026 16:00:50 +0200 Subject: [PATCH 331/456] s390/qeth: Check CAP_NET_ADMIN for private ioctls Gate the SIOCDEVPRIVATE ioctl commands SIOC_QETH_ADP_SET_SNMP_CONTROL, SIOC_QETH_GET_CARD_TYPE and SIOC_QETH_QUERY_OAT with CAP_NET_ADMIN capable check to ensure unprivileged users cannot invoke them. Fixes: 18787eeebd71 ("qeth: use ndo_siocdevprivate") Cc: stable@vger.kernel.org Suggested-by: Christian Borntraeger Reviewed-by: Christian Borntraeger Reviewed-by: Alexandra Winter Signed-off-by: Aswin Karuvally Link: https://patch.msgid.link/20260723140050.762991-1-aswin@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/qeth_core_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c index 20fb0d2e02a9..f18eed9df3c7 100644 --- a/drivers/s390/net/qeth_core_main.c +++ b/drivers/s390/net/qeth_core_main.c @@ -6525,6 +6525,9 @@ int qeth_siocdevprivate(struct net_device *dev, struct ifreq *rq, void __user *d struct qeth_card *card = dev->ml_priv; int rc = 0; + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + switch (cmd) { case SIOC_QETH_ADP_SET_SNMP_CONTROL: rc = qeth_snmp_command(card, data); From 6fb7b769d6ed6d1d2e02af4a80e57a2477f35086 Mon Sep 17 00:00:00 2001 From: Yun Lu Date: Tue, 21 Jul 2026 10:38:36 +0800 Subject: [PATCH 332/456] rtase: fix double free of multi-frag skb on DMA map failure In rtase_start_xmit(), when the head buffer DMA mapping fails after rtase_xmit_frags() has mapped all fragments, the error path clears the fragment descriptors with rtase_tx_clear_range(), which frees the skb through the last-frag slot and accounts tx_dropped. Control then falls through to the common error label, which frees the same skb a second time and counts it again. Return right after clearing the fragments when the skb owns frags; the no-frag case still drops through and frees the head skb once. Fixes: d6e882b89fdf ("rtase: Implement .ndo_start_xmit function") Signed-off-by: Yun Lu Reviewed-by: Jacob Keller Reviewed-by: Justin Lai Link: https://patch.msgid.link/20260721023836.6691-1-luyun_611@163.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/realtek/rtase/rtase_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/realtek/rtase/rtase_main.c b/drivers/net/ethernet/realtek/rtase/rtase_main.c index 4168ad9e48ea..e3cd4f7c1380 100644 --- a/drivers/net/ethernet/realtek/rtase/rtase_main.c +++ b/drivers/net/ethernet/realtek/rtase/rtase_main.c @@ -1623,6 +1623,9 @@ static netdev_tx_t rtase_start_xmit(struct sk_buff *skb, err_dma_1: ring->skbuff[entry] = NULL; rtase_tx_clear_range(ring, ring->cur_idx + 1, frags); + if (frags) + /* the frags were cleared above, along with the skb */ + return NETDEV_TX_OK; err_dma_0: tp->stats.tx_dropped++; From 97ac08560d236ca17f6606d9e671118e5eae5721 Mon Sep 17 00:00:00 2001 From: Eric Joyner Date: Wed, 22 Jul 2026 21:13:42 -0700 Subject: [PATCH 333/456] ethtool: Embed FEC hist ranges as buffer in struct When a driver's .get_fec_stats() handler is called and the driver supports FEC histogram stats, the driver supplies the histogram bin ranges via a pointer. This pointer is assigned while under the netdev ops lock in fec_prepare_data(), but the actual data is only read after the lock is released; so this allows the driver to change the ranges (e.g. from another .get_fec_stats() call) while the current call chain is reading them in fec_fill_reply(). Fix this by adding an ethtool core-owned buffer, ranges_buf, to struct ethtool_fec_hist. Drivers whose ranges are built dynamically (currently just mlx5) fill ranges_buf and then point the existing ranges pointer at it, giving ethtool a consistent copy that stays valid after the netdev ops lock is dropped and later in fec_fill_reply(). Drivers whose ranges are compile-time constants (bnxt, netdevsim) are unaffected by the potential race and keep setting the existing ranges pointer to their constant array, without making copies. Fixes: cc2f08129925 ("ethtool: add FEC bins histogram report") Signed-off-by: Eric Joyner Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260723041342.39238-1-eric.joyner@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en.h | 1 - .../net/ethernet/mellanox/mlx5/core/en_main.c | 7 ------- .../ethernet/mellanox/mlx5/core/en_stats.c | 19 +++++++++---------- include/linux/ethtool.h | 1 + 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en.h b/drivers/net/ethernet/mellanox/mlx5/core/en.h index d507289096c2..6867a5aed42c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en.h @@ -984,7 +984,6 @@ struct mlx5e_priv { struct mlx5e_mqprio_rl *mqprio_rl; struct dentry *dfs_root; struct mlx5_devcom_comp_dev *devcom; - struct ethtool_fec_hist_range *fec_ranges; }; static inline u16 mlx5e_stats_nch_read(const struct mlx5e_priv *priv) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index c1acb9012d3f..7d47a1da8b6b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -6415,14 +6415,8 @@ int mlx5e_priv_init(struct mlx5e_priv *priv, if (!priv->channel_stats) goto err_free_tx_rates; - priv->fec_ranges = kzalloc_objs(*priv->fec_ranges, ETHTOOL_FEC_HIST_MAX); - if (!priv->fec_ranges) - goto err_free_channel_stats; - return 0; -err_free_channel_stats: - kfree(priv->channel_stats); err_free_tx_rates: kfree(priv->tx_rates); err_free_txq2sq_stats: @@ -6447,7 +6441,6 @@ void mlx5e_priv_cleanup(struct mlx5e_priv *priv) if (!priv->mdev) return; - kfree(priv->fec_ranges); for (i = 0; i < priv->stats_nch; i++) kvfree(priv->channel_stats[i]); kfree(priv->channel_stats); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c index de38b60806c2..e7e6db7f6bf1 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c @@ -1550,7 +1550,7 @@ static bool fec_rs_validate_hist_type(int mode, int hist_type) static u8 fec_rs_histogram_fill_ranges(struct mlx5e_priv *priv, int mode, - const struct ethtool_fec_hist_range **ranges) + struct ethtool_fec_hist_range *ranges) { struct mlx5_core_dev *mdev = priv->mdev; u32 out[MLX5_ST_SZ_DW(pphcr_reg)] = {0}; @@ -1558,8 +1558,6 @@ fec_rs_histogram_fill_ranges(struct mlx5e_priv *priv, int mode, int sz = MLX5_ST_SZ_BYTES(pphcr_reg); u8 hist_type, num_of_bins; - memset(priv->fec_ranges, 0, - ETHTOOL_FEC_HIST_MAX * sizeof(*priv->fec_ranges)); MLX5_SET(pphcr_reg, in, local_port, 1); if (mlx5_core_access_reg(mdev, in, sz, out, sz, MLX5_REG_PPHCR, 0, 0)) return 0; @@ -1575,12 +1573,11 @@ fec_rs_histogram_fill_ranges(struct mlx5e_priv *priv, int mode, for (int i = 0; i < num_of_bins; i++) { void *bin_range = MLX5_ADDR_OF(pphcr_reg, out, bin_range[i]); - priv->fec_ranges[i].high = MLX5_GET(bin_range_layout, bin_range, - high_val); - priv->fec_ranges[i].low = MLX5_GET(bin_range_layout, bin_range, - low_val); + ranges[i].high = MLX5_GET(bin_range_layout, bin_range, + high_val); + ranges[i].low = MLX5_GET(bin_range_layout, bin_range, + low_val); } - *ranges = priv->fec_ranges; return num_of_bins; } @@ -1622,10 +1619,12 @@ static void fec_set_histograms_stats(struct mlx5e_priv *priv, int mode, case MLX5E_FEC_LLRS_272_257_1: case MLX5E_FEC_RS_544_514_INTERLEAVED_QUAD: num_of_bins = - fec_rs_histogram_fill_ranges(priv, mode, &hist->ranges); - if (num_of_bins) + fec_rs_histogram_fill_ranges(priv, mode, hist->ranges_buf); + if (num_of_bins) { + hist->ranges = hist->ranges_buf; return fec_rs_histogram_fill_stats(priv, num_of_bins, hist); + } break; default: return; diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 5d491a98265e..12683b5d125e 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -562,6 +562,7 @@ struct ethtool_fec_hist { u64 per_lane[ETHTOOL_MAX_LANES]; } values[ETHTOOL_FEC_HIST_MAX]; const struct ethtool_fec_hist_range *ranges; + struct ethtool_fec_hist_range ranges_buf[ETHTOOL_FEC_HIST_MAX]; }; /** * struct ethtool_fec_stats - statistics for IEEE 802.3 FEC From 810d07fb4cf7577847f85a6fd6273b69cad8d580 Mon Sep 17 00:00:00 2001 From: Vaibhav Jain Date: Wed, 8 Jul 2026 07:28:00 +0530 Subject: [PATCH 334/456] powerpc/pseries: Ensure vpa,slb_shadow & dtl are unregistered during crash Currently pseries_kexec_cpu_down() skips unregistering vpa, slb_shadow and dtl areas during a crash and kexec shutdown path. It was done to avoid doing an HCALL while crashing. However recently Anushree reported that during kernel crash while the kdump kernel was coming up, Hypervisor reported invalid values for 'vpa.yield_count' while it dispatching L2-KVM Guest vcpus. The error manifested as debug build Hypervisor assert triggering to indicate possible VPA corruption. Looking at the kexec cpu offline path it was discovered that during crash kernel doesn't unregister the VPA/SLB-Shadow/DTL area with Hypervisor. Instead it re-allocates and re-registers these areas for cpus during boot. During kexec boot the previously allocated areas can get overwritten with new content without hypervisor knowledge. This creates a small window where while kexec kernel boots and the L2-VCPUs are being dispatched, Hypervisor may try to read/write to a wrong memory area which previously belonged to older VPA. Fix this possible race and memory corruption by updating pseries_kexec_cpu_down() to also unregister vpa,slb_shadow & dtl areas during a kernel crash. Signed-off-by: Vaibhav Jain Tested-by: Anushree Mathur Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260708015802.274271-1-vaibhav@linux.ibm.com --- arch/powerpc/platforms/pseries/kexec.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/arch/powerpc/platforms/pseries/kexec.c b/arch/powerpc/platforms/pseries/kexec.c index 431be156ca9b..29f7c97ff193 100644 --- a/arch/powerpc/platforms/pseries/kexec.c +++ b/arch/powerpc/platforms/pseries/kexec.c @@ -20,12 +20,15 @@ void pseries_kexec_cpu_down(int crash_shutdown, int secondary) { /* - * Don't risk a hypervisor call if we're crashing - * XXX: Why? The hypervisor is not crashing. It might be better - * to at least attempt unregister to avoid the hypervisor stepping - * on our memory. + * Ensure vpa/slb_shadow/dtl cleanup even while we are crashing. + * Why? The hypervisor is not crashing so at least attempt unregister to + * avoid the hypervisor stepping on our memory. If hypervisor or kexec + * kernel steps on the old memory allocated to these areas before the + * new kexec-kernel happens to allocate and register new areas, + * the hypervisor will see invalid content which may cause + * unexpected behavior. */ - if (firmware_has_feature(FW_FEATURE_SPLPAR) && !crash_shutdown) { + if (firmware_has_feature(FW_FEATURE_SPLPAR)) { int ret; int cpu = smp_processor_id(); int hwcpu = hard_smp_processor_id(); From 8f45abd50aaa4155a72ec539f371dafb039786df Mon Sep 17 00:00:00 2001 From: Vaibhav Jain Date: Wed, 8 Jul 2026 07:28:40 +0530 Subject: [PATCH 335/456] powerpc/pseries: Skip vpa_init() for boot cpu in smp_setup_cpu() During pSeries_setup_arch(), VPA for boot-cpu is first to be initialized. However later in the boot, smp_setup_cpu() is called for setting up VPA on boot and secondary cpus that were brought online. This results in vpa_init() being called twice for boot-cpu and three redundant H_REGISTER_VPA hcalls being made to the hypervisor. Fix this by adding an extra condition in smp_set_cpu() to call vpa_init() only on non boot-cpus. Signed-off-by: Vaibhav Jain Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260708015842.274690-1-vaibhav@linux.ibm.com --- arch/powerpc/platforms/pseries/smp.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/platforms/pseries/smp.c b/arch/powerpc/platforms/pseries/smp.c index db99725e752b..bf3d7ed3be01 100644 --- a/arch/powerpc/platforms/pseries/smp.c +++ b/arch/powerpc/platforms/pseries/smp.c @@ -128,7 +128,12 @@ static void smp_setup_cpu(int cpu) else if (cpu != boot_cpuid) xics_setup_cpu(); - if (firmware_has_feature(FW_FEATURE_SPLPAR)) + /* + * Initialize VPA on non-boot cpus since boot-cpu vpa was + * already initialized in pSeries_setup_arch() + */ + if (firmware_has_feature(FW_FEATURE_SPLPAR) && + cpu != boot_cpuid) vpa_init(cpu); cpumask_clear_cpu(cpu, of_spin_mask); From bddf7540099bf653eaea339e886add6f62555cf3 Mon Sep 17 00:00:00 2001 From: "Mukesh Kumar Chaurasiya (IBM)" Date: Tue, 7 Jul 2026 22:54:30 +0530 Subject: [PATCH 336/456] powerpc/970: fix nap return address corruption on async interrupt exit On PowerMac G5 (PPC970, CONFIG_PPC_970_NAP) the system panics shortly after boot with symptoms including instruction fetch faults, kernel data access faults, and stack corruption, predominantly on SMP and always somewhere inside softirq processing. The PPC970 idle path works by setting _TLF_NAPPING in the current thread's local flags before entering the MSR_POW nap loop. When any async interrupt wakes the CPU, nap_adjust_return() is expected to detect _TLF_NAPPING, clear it, and rewrite regs->NIP to power4_idle_nap_return so that the interrupt returns cleanly to the caller of power4_idle_nap() rather than back into the nap spin loop. DEFINE_INTERRUPT_HANDLER_ASYNC generates the following sequence: irq_enter_rcu(); ____func(regs); /* timer_interrupt / do_IRQ body */ irq_exit_rcu(); /* softirqs run here, irqs re-enabled */ arch_interrupt_async_exit_prepare(regs); /* nap_adjust_return was here */ irqentry_exit(regs, state); irq_exit_rcu() calls invoke_softirq() -> do_softirq_own_stack(), which runs softirqs with hardware interrupts re-enabled. A nested async interrupt can therefore arrive while _TLF_NAPPING is still set. That nested interrupt reaches nap_adjust_return() in its own arch_interrupt_async_exit_prepare() call, finds _TLF_NAPPING set, and redirects *its own* regs->NIP to power4_idle_nap_return. Returning via that blr with an unrelated LR on the softirq stack jumps to a garbage address, causing the observed crashes. The comment that previously lived in arch_interrupt_async_exit_prepare() even described this exact hazard ("must come before irq_exit()"), but nap_adjust_return() was placed after irq_exit_rcu() in the macro, so the protection was never effective. Fix this by calling nap_adjust_return() inside DEFINE_INTERRUPT_HANDLER_ASYNC immediately before irq_exit_rcu(), ensuring _TLF_NAPPING is cleared and regs->NIP is adjusted before any code that can re-enable interrupts or invoke softirqs runs. Move the explanatory comment into nap_adjust_return() itself and remove it from arch_interrupt_async_exit_prepare(). Fixes: bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") Closes: https://lore.kernel.org/all/87wlvazrdy.fsf@igel.home/ Reported-by: Andreas Schwab Signed-off-by: Mukesh Kumar Chaurasiya (IBM) Tested-by: John Ogness Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260707172430.790040-1-mkchauras@gmail.com --- arch/powerpc/include/asm/entry-common.h | 15 +++++++-------- arch/powerpc/include/asm/interrupt.h | 1 + 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/arch/powerpc/include/asm/entry-common.h b/arch/powerpc/include/asm/entry-common.h index fc636c42e89a..c5adb5006361 100644 --- a/arch/powerpc/include/asm/entry-common.h +++ b/arch/powerpc/include/asm/entry-common.h @@ -66,6 +66,13 @@ static inline void srr_regs_clobbered(void) static inline void nap_adjust_return(struct pt_regs *regs) { #ifdef CONFIG_PPC_970_NAP + /* + * Adjust the nap return address before irq_exit_rcu(). irq_exit_rcu() + * may invoke softirqs with interrupts re-enabled, allowing a nested + * async interrupt to arrive. If _TLF_NAPPING is still set at that + * point, the nested interrupt would erroneously redirect its own + * return address to power4_idle_nap_return, corrupting the stack. + */ if (unlikely(test_thread_local_flags(_TLF_NAPPING))) { /* Can avoid a test-and-clear because NMIs do not call this */ clear_thread_local_flags(_TLF_NAPPING); @@ -286,14 +293,6 @@ static inline void arch_interrupt_async_enter_prepare(struct pt_regs *regs) static inline void arch_interrupt_async_exit_prepare(struct pt_regs *regs) { - /* - * Adjust at exit so the main handler sees the true NIA. This must - * come before irq_exit() because irq_exit can enable interrupts, and - * if another interrupt is taken before nap_adjust_return has run - * here, then that interrupt would return directly to idle nap return. - */ - nap_adjust_return(regs); - arch_interrupt_exit_prepare(regs); } diff --git a/arch/powerpc/include/asm/interrupt.h b/arch/powerpc/include/asm/interrupt.h index fb42a664ae54..1b45a49e9bed 100644 --- a/arch/powerpc/include/asm/interrupt.h +++ b/arch/powerpc/include/asm/interrupt.h @@ -246,6 +246,7 @@ interrupt_handler void func(struct pt_regs *regs) \ instrumentation_begin(); \ irq_enter_rcu(); \ ____##func (regs); \ + nap_adjust_return(regs); \ irq_exit_rcu(); \ instrumentation_end(); \ arch_interrupt_async_exit_prepare(regs); \ From 263e5159e00aa46bf26f3496ff7aae1fc9a6c826 Mon Sep 17 00:00:00 2001 From: "Mukesh Kumar Chaurasiya (IBM)" Date: Fri, 24 Jul 2026 01:18:09 +0530 Subject: [PATCH 337/456] powerpc: Fix exit_flags field placement in pt_regs for ptrace Commit d7a6797e0bc1 ("powerpc: add exit_flags field in pt_regs") added the exit_flags field to struct pt_regs to pass internal exit control flags (e.g. _TIF_RESTOREALL) from syscall_exit_prepare() to the low-level assembly exit path. However, the field was placed in a way that was visible to userspace tools such as strace via PTRACE_GETREGS, or caused a struct layout or size regression observable through ptrace. The field is purely kernel-internal and must not be exposed beyond the user_pt_regs boundary. Move exit_flags into struct thread_info where it is only accessible to the kernel, and keep it out of the ptrace-visible register window entirely. Fixes: d7a6797e0bc1 ("powerpc: add exit_flags field in pt_regs") Reported-by: Dmitry V. Levin Closes: https://lore.kernel.org/all/20260722070155.GA11808@strace.io/ Signed-off-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260723194809.4046600-1-mkchauras@gmail.com --- arch/powerpc/include/asm/ptrace.h | 3 --- arch/powerpc/include/asm/thread_info.h | 1 + arch/powerpc/include/uapi/asm/ptrace.h | 14 ++++------- arch/powerpc/kernel/interrupt.c | 32 +++++++++++++++----------- arch/powerpc/kernel/ptrace/ptrace.c | 1 - arch/powerpc/kernel/signal.c | 2 +- 6 files changed, 26 insertions(+), 27 deletions(-) diff --git a/arch/powerpc/include/asm/ptrace.h b/arch/powerpc/include/asm/ptrace.h index fdeb97421785..d53c4dd4d8b6 100644 --- a/arch/powerpc/include/asm/ptrace.h +++ b/arch/powerpc/include/asm/ptrace.h @@ -53,9 +53,6 @@ struct pt_regs unsigned long esr; }; unsigned long result; - unsigned long exit_flags; - /* Maintain 16 byte interrupt stack alignment */ - unsigned long __pt_regs_pad[3]; }; }; #if defined(CONFIG_PPC64) || defined(CONFIG_PPC_KUAP) diff --git a/arch/powerpc/include/asm/thread_info.h b/arch/powerpc/include/asm/thread_info.h index ee3b9adb5b67..0487e94d3416 100644 --- a/arch/powerpc/include/asm/thread_info.h +++ b/arch/powerpc/include/asm/thread_info.h @@ -57,6 +57,7 @@ struct thread_info { #ifdef CONFIG_SMP unsigned int cpu; #endif + unsigned long exit_flags; /* Exit Flags for entry/exit */ unsigned long syscall_work; /* SYSCALL_WORK_ flags */ unsigned long local_flags; /* private flags for thread */ #ifdef CONFIG_LIVEPATCH_64 diff --git a/arch/powerpc/include/uapi/asm/ptrace.h b/arch/powerpc/include/uapi/asm/ptrace.h index a393b7f2760a..01e630149d48 100644 --- a/arch/powerpc/include/uapi/asm/ptrace.h +++ b/arch/powerpc/include/uapi/asm/ptrace.h @@ -55,8 +55,6 @@ struct pt_regs unsigned long dar; /* Fault registers */ unsigned long dsisr; /* on 4xx/Book-E used for ESR */ unsigned long result; /* Result of a system call */ - unsigned long exit_flags; /* System call exit flags */ - unsigned long __pt_regs_pad[3]; /* Maintain 16 byte interrupt stack alignment */ }; #endif /* __ASSEMBLER__ */ @@ -116,12 +114,10 @@ struct pt_regs #define PT_DAR 41 #define PT_DSISR 42 #define PT_RESULT 43 -#define PT_EXIT_FLAGS 44 -#define PT_PAD 47 /* 3 times */ -#define PT_DSCR 48 -#define PT_REGS_COUNT 48 +#define PT_DSCR 44 +#define PT_REGS_COUNT 44 -#define PT_FPR0 (PT_REGS_COUNT + 4) /* each FP reg occupies 2 slots in this space */ +#define PT_FPR0 48 /* each FP reg occupies 2 slots in this space */ #ifndef __powerpc64__ @@ -133,7 +129,7 @@ struct pt_regs #define PT_FPSCR (PT_FPR0 + 32) /* each FP reg occupies 1 slot in 64-bit space */ -#define PT_VR0 (PT_FPSCR + 2) /* <82> each Vector reg occupies 2 slots in 64-bit */ +#define PT_VR0 82 /* each Vector reg occupies 2 slots in 64-bit */ #define PT_VSCR (PT_VR0 + 32*2 + 1) #define PT_VRSAVE (PT_VR0 + 33*2) @@ -141,7 +137,7 @@ struct pt_regs /* * Only store first 32 VSRs here. The second 32 VSRs in VR0-31 */ -#define PT_VSR0 (PT_VRSAVE + 2) /* each VSR reg occupies 2 slots in 64-bit */ +#define PT_VSR0 150 /* each VSR reg occupies 2 slots in 64-bit */ #define PT_VSR31 (PT_VSR0 + 2*31) #endif /* __powerpc64__ */ diff --git a/arch/powerpc/kernel/interrupt.c b/arch/powerpc/kernel/interrupt.c index f04978080837..5b88bf72786c 100644 --- a/arch/powerpc/kernel/interrupt.c +++ b/arch/powerpc/kernel/interrupt.c @@ -89,15 +89,17 @@ notrace unsigned long syscall_exit_prepare(unsigned long r3, long scv) { unsigned long ti_flags; + unsigned long ret = 0; bool is_not_scv = !IS_ENABLED(CONFIG_PPC_BOOK3S_64) || !scv; kuap_assert_locked(); regs->result = r3; - regs->exit_flags = 0; + + /* Clear exit_flags so only flags set during this exit are visible */ + current->thread_info.exit_flags = 0; ti_flags = read_thread_flags(); - if (unlikely(r3 >= (unsigned long)-MAX_ERRNO) && is_not_scv) { if (likely(!(ti_flags & (_TIF_NOERROR | _TIF_RESTOREALL)))) { r3 = -r3; @@ -107,7 +109,7 @@ notrace unsigned long syscall_exit_prepare(unsigned long r3, if (unlikely(ti_flags & _TIF_PERSYSCALL_MASK)) { if (ti_flags & _TIF_RESTOREALL) - regs->exit_flags = _TIF_RESTOREALL; + ret = _TIF_RESTOREALL; else regs->gpr[3] = r3; clear_bits(_TIF_PERSYSCALL_MASK, ¤t_thread_info()->flags); @@ -116,7 +118,7 @@ notrace unsigned long syscall_exit_prepare(unsigned long r3, } if (unlikely(ti_flags & _TIF_SYSCALL_DOTRACE)) { - regs->exit_flags |= _TIF_RESTOREALL; + ret |= _TIF_RESTOREALL; } syscall_exit_to_user_mode(regs); @@ -132,17 +134,19 @@ notrace unsigned long syscall_exit_prepare(unsigned long r3, /* Restore user access locks last */ kuap_user_restore(regs); - + ret |= current->thread_info.exit_flags; #ifdef CONFIG_PPC64 - regs->exit_result = regs->exit_flags; + regs->exit_result = ret; #endif - return regs->exit_flags; + return ret; } #ifdef CONFIG_PPC64 notrace unsigned long syscall_exit_restart(unsigned long r3, struct pt_regs *regs) { + unsigned long ret; + /* * This is called when detecting a soft-pending interrupt as well as * an alternate-return interrupt. So we can't just have the alternate @@ -167,9 +171,11 @@ notrace unsigned long syscall_exit_restart(unsigned long r3, struct pt_regs *reg } kuap_user_restore(regs); - regs->exit_result |= regs->exit_flags; + ret = current_thread_info()->exit_flags & _TIF_RESTOREALL; + current_thread_info()->exit_flags &= ~_TIF_RESTOREALL; + regs->exit_result |= ret; - return regs->exit_result; + return ret; } #endif @@ -186,8 +192,10 @@ notrace unsigned long interrupt_exit_user_prepare(struct pt_regs *regs) */ kuap_assert_locked(); + /* Clear exit_flags so only flags set during this exit are visible */ + current_thread_info()->exit_flags = 0; + local_irq_disable(); - regs->exit_flags = 0; again: check_return_regs_valid(regs); user_enter_irqoff(); @@ -200,9 +208,7 @@ notrace unsigned long interrupt_exit_user_prepare(struct pt_regs *regs) /* Restore user access locks last */ kuap_user_restore(regs); - - ret = regs->exit_flags; - + ret = current_thread_info()->exit_flags & _TIF_RESTOREALL; #ifdef CONFIG_PPC64 regs->exit_result = ret; #endif diff --git a/arch/powerpc/kernel/ptrace/ptrace.c b/arch/powerpc/kernel/ptrace/ptrace.c index 316d4f5ead8e..6cd180bc36ab 100644 --- a/arch/powerpc/kernel/ptrace/ptrace.c +++ b/arch/powerpc/kernel/ptrace/ptrace.c @@ -291,7 +291,6 @@ void __init pt_regs_check(void) CHECK_REG(PT_DAR, dar); CHECK_REG(PT_DSISR, dsisr); CHECK_REG(PT_RESULT, result); - CHECK_REG(PT_EXIT_FLAGS, exit_flags); #undef CHECK_REG BUILD_BUG_ON(PT_REGS_COUNT != sizeof(struct user_pt_regs) / sizeof(unsigned long)); diff --git a/arch/powerpc/kernel/signal.c b/arch/powerpc/kernel/signal.c index bb42a8b6c642..cc6498501610 100644 --- a/arch/powerpc/kernel/signal.c +++ b/arch/powerpc/kernel/signal.c @@ -356,6 +356,6 @@ void signal_fault(struct task_struct *tsk, struct pt_regs *regs, void arch_do_signal_or_restart(struct pt_regs *regs) { BUG_ON(regs != current->thread.regs); - regs->exit_flags |= _TIF_RESTOREALL; + current_thread_info()->exit_flags |= _TIF_RESTOREALL; do_signal(current); } From c824ab65685bb119c6c6a3a200b3428c72862d5a Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 2 Jul 2026 23:15:55 +0200 Subject: [PATCH 338/456] powerpc/boot: Fix simpleboot CPU node lookup check fdt_node_offset_by_prop_value() returns a negative error code on failure - fix the check accordingly. Fixes: d2477b5cc8ca ("[POWERPC] bootwrapper: Add a firmware-independent simpleboot target.") Signed-off-by: Thorsten Blum Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260702211554.56923-4-thorsten.blum@linux.dev --- arch/powerpc/boot/simpleboot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/boot/simpleboot.c b/arch/powerpc/boot/simpleboot.c index c80691d83880..27591df41e9e 100644 --- a/arch/powerpc/boot/simpleboot.c +++ b/arch/powerpc/boot/simpleboot.c @@ -68,7 +68,7 @@ void platform_init(unsigned long r3, unsigned long r4, unsigned long r5, /* finally, setup the timebase */ node = fdt_node_offset_by_prop_value(_dtb_start, -1, "device_type", "cpu", sizeof("cpu")); - if (!node) + if (node < 0) fatal("Cannot find cpu node\n"); timebase = fdt_getprop(_dtb_start, node, "timebase-frequency", &size); if (timebase && (size == 4)) From 43863f6575d2211e8c5157fefb83ad0ad046aab4 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 2 Jul 2026 23:15:56 +0200 Subject: [PATCH 339/456] powerpc/boot: Fix treeboot-currituck CPU node lookup check fdt_node_offset_by_prop_value() returns a negative error code on failure - fix the check accordingly. Fixes: 228d55053397 ("powerpc/47x: Add support for the new IBM currituck platform") Signed-off-by: Thorsten Blum Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260702211554.56923-5-thorsten.blum@linux.dev --- arch/powerpc/boot/treeboot-currituck.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/boot/treeboot-currituck.c b/arch/powerpc/boot/treeboot-currituck.c index d53e8a592f81..5b5363b74f9f 100644 --- a/arch/powerpc/boot/treeboot-currituck.c +++ b/arch/powerpc/boot/treeboot-currituck.c @@ -102,7 +102,7 @@ void platform_init(void) node = fdt_node_offset_by_prop_value(_dtb_start, -1, "device_type", "cpu", sizeof("cpu")); - if (!node) + if (node < 0) fatal("Cannot find cpu node\n"); timebase = fdt_getprop(_dtb_start, node, "timebase-frequency", &size); if (timebase && (size == 4)) From b24fc8278b70a9d27ec801a427ab4de9b769d69a Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 2 Jul 2026 23:15:57 +0200 Subject: [PATCH 340/456] powerpc/boot: Fix treeboot-akebono CPU node lookup check fdt_node_offset_by_prop_value() returns a negative error code on failure - fix the check accordingly. Fixes: 2a2c74b2efcb ("IBM Akebono: Add the Akebono platform") Signed-off-by: Thorsten Blum Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260702211554.56923-6-thorsten.blum@linux.dev --- arch/powerpc/boot/treeboot-akebono.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/boot/treeboot-akebono.c b/arch/powerpc/boot/treeboot-akebono.c index e3cc2599869c..1b529037480f 100644 --- a/arch/powerpc/boot/treeboot-akebono.c +++ b/arch/powerpc/boot/treeboot-akebono.c @@ -146,7 +146,7 @@ void platform_init(char *userdata) node = fdt_node_offset_by_prop_value(_dtb_start, -1, "device_type", "cpu", sizeof("cpu")); - if (!node) + if (node < 0) fatal("Cannot find cpu node\n"); timebase = fdt_getprop(_dtb_start, node, "timebase-frequency", &size); if (timebase && (size == 4)) From 41bf83c1b10781fcb992ff0680db771b7b833308 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 13 Jul 2026 11:17:33 +0200 Subject: [PATCH 341/456] powerpc/ps3: Remove unused struct table in setup_areas() The local table structure is not used - remove it. Reviewed-by: Geert Uytterhoeven Signed-off-by: Thorsten Blum Reviewed-by: Christophe Leroy (CS GROUP) Reviewed-by: Ritesh Harjani (IBM) Reviewed-by: Amit Machhiwal Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260713091731.97212-3-thorsten.blum@linux.dev --- arch/powerpc/platforms/ps3/spu.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/powerpc/platforms/ps3/spu.c b/arch/powerpc/platforms/ps3/spu.c index e4e0b45e1b9d..a71c0b0cfb07 100644 --- a/arch/powerpc/platforms/ps3/spu.c +++ b/arch/powerpc/platforms/ps3/spu.c @@ -191,8 +191,6 @@ static void spu_unmap(struct spu *spu) static int __init setup_areas(struct spu *spu) { - struct table {char* name; unsigned long addr; unsigned long size;}; - spu_pdata(spu)->shadow = ioremap_prot(spu_pdata(spu)->shadow_addr, sizeof(struct spe_shadow), pgprot_noncached_wc(PAGE_KERNEL_RO)); From 0bb024f11d120abff3e8db9144a585b9d7fb8459 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 11 Jul 2026 15:09:32 +0200 Subject: [PATCH 342/456] powerpc/ps3: Fix map failure path in dma_ioc0_map_pages() If lv1_put_iopte() fails in dma_ioc0_map_pages(), the error path decrements iopage but keeps using the failed mapping's offset. As a result, it repeatedly tries to invalidate the failed IOPTE slot and leaves the already installed IOPTEs valid. Recompute offset and invalidate the installed IOPTEs instead. Fixes: 6bb5cf102541 ("[POWERPC] PS3: System-bus rework") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Reviewed-by: Ritesh Harjani (IBM) Reviewed-by: Geert Uytterhoeven Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260711130931.740719-3-thorsten.blum@linux.dev --- arch/powerpc/platforms/ps3/mm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/powerpc/platforms/ps3/mm.c b/arch/powerpc/platforms/ps3/mm.c index 20fc5b68faee..315a32fd75b1 100644 --- a/arch/powerpc/platforms/ps3/mm.c +++ b/arch/powerpc/platforms/ps3/mm.c @@ -615,6 +615,7 @@ static int dma_ioc0_map_pages(struct ps3_dma_region *r, unsigned long phys_addr, fail_map: for (iopage--; 0 <= iopage; iopage--) { + offset = (1 << r->page_size) * iopage; lv1_put_iopte(0, c->bus_addr + offset, c->lpar_addr + offset, From d71b3ca5231750fecdefb3390595e7db30979ac4 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 4 Jul 2026 14:13:54 +0200 Subject: [PATCH 343/456] powerpc/perf: Use strstarts() to simplify is_thread_imc_pmu() Replace the open-coded implementation with strstarts() to simplify is_thread_imc_pmu(). Signed-off-by: Thorsten Blum Reviewed-by: Athira Rajeev Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260704121353.201583-3-thorsten.blum@linux.dev --- arch/powerpc/perf/imc-pmu.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/arch/powerpc/perf/imc-pmu.c b/arch/powerpc/perf/imc-pmu.c index e3822f36c419..1d2db6d3e226 100644 --- a/arch/powerpc/perf/imc-pmu.c +++ b/arch/powerpc/perf/imc-pmu.c @@ -1023,10 +1023,7 @@ static int thread_imc_event_init(struct perf_event *event) static bool is_thread_imc_pmu(struct perf_event *event) { - if (!strncmp(event->pmu->name, "thread_imc", strlen("thread_imc"))) - return true; - - return false; + return strstarts(event->pmu->name, "thread_imc"); } static __be64 *get_event_base_addr(struct perf_event *event) From 86f057c19fbe089ec8a2020ef5f1579b99d87ac7 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 23 Jun 2026 17:38:25 +0200 Subject: [PATCH 344/456] powerpc/serial: Fix include guard comment Replace _PPC64_SERIAL_H with _ASM_POWERPC_SERIAL_H to match the actual macro name. Remove an empty comment while at it. Signed-off-by: Thorsten Blum Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260623153825.403819-2-thorsten.blum@linux.dev --- arch/powerpc/include/asm/serial.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/powerpc/include/asm/serial.h b/arch/powerpc/include/asm/serial.h index cd6c18d0e66e..c3eb4a64b3f5 100644 --- a/arch/powerpc/include/asm/serial.h +++ b/arch/powerpc/include/asm/serial.h @@ -1,6 +1,4 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - */ #ifndef _ASM_POWERPC_SERIAL_H #define _ASM_POWERPC_SERIAL_H @@ -18,4 +16,4 @@ extern void find_legacy_serial_ports(void); #define find_legacy_serial_ports() do { } while (0) #endif -#endif /* _PPC64_SERIAL_H */ +#endif /* _ASM_POWERPC_SERIAL_H */ From b14361aca6350ff7907b0e9903c7b94dc7d5d4a0 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Wed, 22 Jul 2026 16:38:58 +0800 Subject: [PATCH 345/456] fou: Fix use-after-free in fou_create() fou_create() publishes struct fou through sk_user_data before adding the new FOU port to the per-netns list. If fou_add_to_port_list() fails, the error path frees fou while it is still reachable through sk_user_data. A concurrent receive can then dereference the freed object in fou_from_sock(). This ordering issue was previously noted in the linked discussion. The failure is reachable when local port 0 is requested. Each socket binds to a different ephemeral port, but fou_cfg_cmp() compares the requested port 0 and reports -EALREADY once an entry already exists. Release the tunnel socket before freeing fou so sk_user_data is cleared first, and defer reclamation with kfree_rcu() to protect concurrent RCU readers. This matches the lifetime handling in fou_release(). Fixes: 23461551c006 ("fou: Support for foo-over-udp RX path") Suggested-by: Kuniyuki Iwashima Link: https://lore.kernel.org/netdev/20260502031401.3557229-12-kuniyu@google.com/ Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260722083858.182506-1-xuanqiang.luo@linux.dev Signed-off-by: Paolo Abeni --- net/ipv4/fou_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index 865bd7205122..ab09dfcdecbd 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -629,9 +629,9 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, return 0; error: - kfree(fou); if (sock) udp_tunnel_sock_release(sock->sk); + kfree_rcu(fou, rcu); return err; } From 295dd295e2137e10e9a5b1891d97e0f08de76f03 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Thu, 23 Jul 2026 10:08:29 +0900 Subject: [PATCH 346/456] net: mpls: initialize rtm_tos in mpls_getroute() mpls_getroute() builds the RTM_NEWROUTE reply to an RTM_GETROUTE request by filling a struct rtmsg allocated from an skb whose data area is not zeroed (alloc_skb(NLMSG_GOODSIZE, ...)). It sets every field of the header except rtm_tos: r = nlmsg_data(nlh); r->rtm_family = AF_MPLS; r->rtm_dst_len = 20; r->rtm_src_len = 0; r->rtm_table = RT_TABLE_MAIN; r->rtm_type = RTN_UNICAST; r->rtm_scope = RT_SCOPE_UNIVERSE; r->rtm_protocol = rt->rt_protocol; r->rtm_flags = 0; struct rtmsg has no padding, so the one uninitialised byte rtm_tos (offset 3) is copied straight to user space on recvmsg(), leaking a byte of uninitialised heap memory. This is in contrast to mpls_dump_route(), which fills the very same header and does set rtm_tos = 0. Initialize rtm_tos to 0, matching mpls_dump_route(). Reproduced with KMSAN by adding an MPLS route and issuing a non-RTM_F_FIB_MATCH RTM_GETROUTE for its label: BUG: KMSAN: kernel-infoleak in _copy_to_iter+0x36c/0x33f0 _copy_to_iter+0x36c/0x33f0 __skb_datagram_iter+0x196/0x12c0 skb_copy_datagram_iter+0x5b/0x210 netlink_recvmsg+0x37b/0xef0 ... Uninit was created at: __alloc_skb+0x8ca/0x10e0 mpls_getroute+0x1280/0x3a40 rtnetlink_rcv_msg+0x1138/0x15a0 ... Byte 19 of 64 is uninitialized (byte 19 = nlmsghdr(16) + rtmsg offset 3 = rtm_tos) Fixes: 397fc9e5cefe ("mpls: route get support") Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260723010830.289917-1-yhlee@isslab.korea.ac.kr Signed-off-by: Paolo Abeni --- net/mpls/af_mpls.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c index 4406c304b639..961be5054a03 100644 --- a/net/mpls/af_mpls.c +++ b/net/mpls/af_mpls.c @@ -2539,6 +2539,7 @@ static int mpls_getroute(struct sk_buff *in_skb, struct nlmsghdr *in_nlh, r->rtm_family = AF_MPLS; r->rtm_dst_len = 20; r->rtm_src_len = 0; + r->rtm_tos = 0; r->rtm_table = RT_TABLE_MAIN; r->rtm_type = RTN_UNICAST; r->rtm_scope = RT_SCOPE_UNIVERSE; From aef96eead2860cbfa371e4471d4f04412213b958 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Thu, 23 Jul 2026 00:49:55 -0400 Subject: [PATCH 347/456] net/sched: cls_u32: validate offshift to prevent shift-out-of-bounds u32_change() copies the user-provided tc_u32_sel.offshift (unsigned char, 0-255) into the kernel knode object without bounds validation. When a packet later hits u32_classify() with TC_U32_VAROFFSET set, it evaluates `ntohs(offmask & *data) >> offshift` where the left operand is a 16-bit value promoted to a 32-bit int. Any offshift >= 32 is undefined behavior per C11 6.5.7p3, triggerable by an unprivileged user via user/network namespaces. UBSAN: shift-out-of-bounds in net/sched/cls_u32.c:236:43 shift exponent 32 is too large for 32-bit type int Fix this by rejecting offshift >= 16 during filter creation in u32_change(). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: AutonomousCodeSecurity@microsoft.com Link: https://lore.kernel.org/all/20260720034514.23053-1-blbllhy@gmail.com Signed-off-by: Cen Zhang (Microsoft) Acked-by: Jamal Hadi Salim Tested-by: Jamal Hadi Salim Tested-by: Victor Nogueira Link: https://patch.msgid.link/20260723044955.89471-1-blbllhy@gmail.com Signed-off-by: Paolo Abeni --- net/sched/cls_u32.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index 8f30cc82181d..ac98b1c2144a 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -1107,6 +1107,13 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, goto erridr; } + if (s->offshift >= 16) { + NL_SET_ERR_MSG_MOD(extack, + "offshift must be less than 16"); + err = -EINVAL; + goto erridr; + } + n = kzalloc_flex(*n, sel.keys, s->nkeys); if (n == NULL) { err = -ENOBUFS; From f621d6ebeebb6374342571e4ddf45fdbc420f6cd Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 23 Jul 2026 18:54:54 +0800 Subject: [PATCH 348/456] net/smc: fix socket use-after-free during link group termination __smc_lgr_terminate() drops conns_lock after finding a connection in lgr->conns_all, but before taking a reference on its socket. The connection is embedded in the socket, and its registration reference protects it only while the connection remains in the tree. A concurrent close can unregister the connection and drop that reference, freeing the socket before the termination worker reaches sock_hold(). The race is reachable when close overlaps link group termination. Local stress testing reproduced the use-after-free and KASAN reported: BUG: KASAN: slab-use-after-free in __smc_lgr_terminate.part.0 [smc] Write of size 4 by task kworker/3:3 Workqueue: events smc_lgr_terminate_work [smc] __smc_lgr_terminate.part.0 [smc] The socket was allocated by smc_create(), freed through slab_free_after_rcu_debug(), and was followed by: refcount_t: addition on 0; use-after-free. __smc_lgr_terminate.part.0 [smc] Take the socket reference while conns_lock still protects the tree entry. The unregister path then cannot drop the last reference until termination has finished using the socket. Fixes: 69318b5215f2 ("net/smc: improve abnormal termination locking") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Reviewed-by: Mahanta Jambigi Link: https://patch.msgid.link/20260723105454.87016-1-xuanqiang.luo@linux.dev Signed-off-by: Paolo Abeni --- net/smc/smc_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c index cf6b620fef05..b4208cb186c5 100644 --- a/net/smc/smc_core.c +++ b/net/smc/smc_core.c @@ -1572,10 +1572,10 @@ static void __smc_lgr_terminate(struct smc_link_group *lgr, bool soft) read_lock_bh(&lgr->conns_lock); node = rb_first(&lgr->conns_all); while (node) { - read_unlock_bh(&lgr->conns_lock); conn = rb_entry(node, struct smc_connection, alert_node); smc = container_of(conn, struct smc_sock, conn); sock_hold(&smc->sk); /* sock_put below */ + read_unlock_bh(&lgr->conns_lock); lock_sock(&smc->sk); smc_conn_kill(conn, soft); release_sock(&smc->sk); From 88c17de85ddb459c3fe1e3c65d61fa366b1cf0a8 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 23 Jul 2026 14:04:45 +0800 Subject: [PATCH 349/456] bpf: lwt: Fix dst reference leak on reroute failure bpf_lwt_xmit_reroute() obtains a referenced dst from the route lookup. When skb_cow_head() fails before that dst is installed on the skb, the error path only frees the skb. The skb still owns its previous dst, so the newly looked up dst reference is leaked. Release the new dst reference before freeing the skb on this error path. Fixes: 3bd0b15281af ("bpf: add handling of BPF_LWT_REROUTE to lwt_bpf.c") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260723060445.21926-1-xuanqiang.luo@linux.dev Signed-off-by: Paolo Abeni --- net/core/lwt_bpf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/core/lwt_bpf.c b/net/core/lwt_bpf.c index bf588f508b79..652952d416f2 100644 --- a/net/core/lwt_bpf.c +++ b/net/core/lwt_bpf.c @@ -255,8 +255,10 @@ static int bpf_lwt_xmit_reroute(struct sk_buff *skb) * if there is enough header space in skb. */ err = skb_cow_head(skb, LL_RESERVED_SPACE(dst->dev)); - if (unlikely(err)) + if (unlikely(err)) { + dst_release(dst); goto err; + } skb_dst_drop(skb); skb_dst_set(skb, dst); From 080695e6f005e2396f1207fd69d24c442cb230c6 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 24 Jul 2026 09:11:37 +0000 Subject: [PATCH 350/456] net: udp_tunnel: fix memory leak in udp_tunnel_nic_unregister() syzbot reported a memory leak [1] in the UDP tunnel NIC offload code. When device registration fails (e.g. in register_netdevice()), netdev core unwinds by sending a single NETDEV_UNREGISTER notification. If work was queued during NETDEV_REGISTER (utn->work_pending is set), udp_tunnel_nic_unregister() returns early: if (utn->work_pending) return; Because failed registrations do not enter netdev_wait_allrefs_any(), no subsequent NETDEV_UNREGISTER rebroadcast will ever occur. As a result, the struct udp_tunnel_nic allocated in udp_tunnel_nic_alloc() is leaked permanently. Fix this by removing the early return. Instead, synchronously cancel any pending work with cancel_delayed_work_sync() before freeing @utn. To be able to call cancel_delayed_work_sync() while holding RTNL (the work also needs RTNL), switch udp_tunnel_nic_device_sync_work() to rtnl_trylock(). If RTNL is contended, requeue the work with a 1 jiffy delay (via queue_delayed_work()) to prevent high CPU contention while waiting for RTNL lock. The utn->work_pending bookkeeping is no longer needed and is removed, as the workqueue core already tracks the pending/running state of the work. [1] BUG: memory leak unreferenced object 0xffff888127d5f840 (size 96): comm "syz-executor", pid 5806, jiffies 4294942188 backtrace (crc 99fdb6c8): __kmalloc_noprof+0x3bf/0x550 udp_tunnel_nic_alloc net/ipv4/udp_tunnel_nic.c:756 [inline] udp_tunnel_nic_register net/ipv4/udp_tunnel_nic.c:833 [inline] udp_tunnel_nic_netdevice_event+0x804/0xab0 net/ipv4/udp_tunnel_nic.c:931 notifier_call_chain+0x59/0x160 kernel/notifier.c:85 call_netdevice_notifiers_info+0x7d/0xb0 net/core/dev.c:2250 register_netdevice+0xc10/0xeb0 net/core/dev.c:11478 Fixes: cc4e3835eff4 ("udp_tunnel: add central NIC RX port offload infrastructure") Reported-by: syzbot+eca845fb8c18dd6b44c1@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a632b15.dde6c935.cf6c8.0011.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260724091137.1792543-1-edumazet@google.com Signed-off-by: Paolo Abeni --- net/ipv4/udp_tunnel_nic.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/net/ipv4/udp_tunnel_nic.c b/net/ipv4/udp_tunnel_nic.c index 3b32a0afa979..53a1a9c1f8bf 100644 --- a/net/ipv4/udp_tunnel_nic.c +++ b/net/ipv4/udp_tunnel_nic.c @@ -32,13 +32,12 @@ struct udp_tunnel_nic_table_entry { * @lock: protects all fields * @need_sync: at least one port start changed * @need_replay: space was freed, we need a replay of all ports - * @work_pending: @work is currently scheduled * @n_tables: number of tables under @entries * @missed: bitmap of tables which overflown * @entries: table of tables of ports currently offloaded */ struct udp_tunnel_nic { - struct work_struct work; + struct delayed_work work; struct net_device *dev; @@ -46,7 +45,6 @@ struct udp_tunnel_nic { u8 need_sync:1; u8 need_replay:1; - u8 work_pending:1; unsigned int n_tables; unsigned long missed; @@ -301,11 +299,10 @@ __udp_tunnel_nic_device_sync(struct net_device *dev, struct udp_tunnel_nic *utn) static void udp_tunnel_nic_device_sync(struct net_device *dev, struct udp_tunnel_nic *utn) { - if (!utn->need_sync || utn->work_pending) + if (!utn->need_sync) return; - queue_work(udp_tunnel_nic_workqueue, &utn->work); - utn->work_pending = 1; + queue_delayed_work(udp_tunnel_nic_workqueue, &utn->work, 0); } static bool @@ -731,12 +728,17 @@ udp_tunnel_nic_replay(struct net_device *dev, struct udp_tunnel_nic *utn) static void udp_tunnel_nic_device_sync_work(struct work_struct *work) { struct udp_tunnel_nic *utn = - container_of(work, struct udp_tunnel_nic, work); + container_of(work, struct udp_tunnel_nic, work.work); - rtnl_lock(); + /* We cannot block on RTNL here, otherwise we would deadlock with + * udp_tunnel_nic_unregister() calling cancel_delayed_work_sync() + * while holding RTNL. Requeue with 1 jiffy delay if RTNL is contended. + */ + if (!rtnl_trylock()) { + queue_delayed_work(udp_tunnel_nic_workqueue, &utn->work, 1); + return; + } mutex_lock(&utn->lock); - - utn->work_pending = 0; __udp_tunnel_nic_device_sync(utn->dev, utn); if (utn->need_replay) @@ -757,7 +759,7 @@ udp_tunnel_nic_alloc(const struct udp_tunnel_nic_info *info, if (!utn) return NULL; utn->n_tables = n_tables; - INIT_WORK(&utn->work, udp_tunnel_nic_device_sync_work); + INIT_DELAYED_WORK(&utn->work, udp_tunnel_nic_device_sync_work); mutex_init(&utn->lock); for (i = 0; i < n_tables; i++) { @@ -901,11 +903,11 @@ udp_tunnel_nic_unregister(struct net_device *dev, struct udp_tunnel_nic *utn) udp_tunnel_nic_flush(dev, utn); udp_tunnel_nic_unlock(dev); - /* Wait for the work to be done using the state, netdev core will - * retry unregister until we give up our reference on this device. + /* Make sure no work is running or queued before freeing @utn. + * The work handler uses rtnl_trylock(), so it will not deadlock + * against the RTNL we are holding here. */ - if (utn->work_pending) - return; + cancel_delayed_work_sync(&utn->work); udp_tunnel_nic_free(utn); release_dev: From 2f067f5a450ea07efd249142a11d940a068fe29c Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Tue, 28 Jul 2026 19:21:56 +0800 Subject: [PATCH 351/456] wifi: mac80211: fix tid_tx use-after-free on BA session stop ieee80211_stop_tx_ba_cb() hands tid_tx to kfree_rcu() through ieee80211_remove_tid_tx(), and then reads tid_tx->ndp after dropping sta->lock: ieee80211_remove_tid_tx(sta, tid); /* kfree_rcu(tid_tx, rcu_head) */ ... spin_unlock_bh(&sta->lock); if (start_txq) ieee80211_agg_start_txq(sta, tid, false); if (send_delba) ieee80211_send_delba(..., tid_tx->ndp); That read is not covered by an RCU read-side critical section, and it runs in preemptible process context: both callers hold the wiphy mutex, reaching it either from the ieee80211_ba_session_work() wiphy work or from ieee80211_sta_tear_down_BA_sessions() during station teardown. Softirqs can run in that window too, both from the local_bh_enable() that ends ieee80211_agg_start_txq() and from any interrupt exit, so the RCU callback can free tid_tx before the read. Driving the function from a test module with the grace period forced into that window, KASAN reports the read, and the free arrives on the ordinary RCU softirq path: BUG: KASAN: slab-use-after-free in ieee80211_stop_tx_ba_cb+0x3cd/0x400 Read of size 1 at addr ffff888002b9f52e by task kworker/0:1/10 [...] Freed by task 57: __kasan_slab_free+0x47/0x70 __rcu_free_sheaf_prepare+0x70/0x250 rcu_free_sheaf_nobarn+0x18/0x40 rcu_core+0x426/0x1310 handle_softirqs+0x144/0x590 __irq_exit_rcu+0xea/0x150 irq_exit_rcu+0x9/0x20 sysvec_apic_timer_interrupt+0x6b/0x80 asm_sysvec_apic_timer_interrupt+0x1a/0x20 send_delba is only set when tx_stop is set, which happens for AGG_STOP_LOCAL_REQUEST alone, so this is reached on local teardown - session idle timeout, PTK rekey, suspend, HW reconfig - and not from a peer's DELBA. Read ndp into a local before the session is freed, while sta->lock is still held. tid_tx->ndp has a single writer, in ieee80211_tx_ba_session_handle_start(), which cannot run concurrently here: both paths are serialised by the wiphy mutex, and the session is already marked HT_AGG_STATE_STOPPING at this point. tid_tx->ndp is also the only tid_tx dereference left after ieee80211_remove_tid_tx() in this function. Fixes: 98acd4c1d9f7 ("wifi: mac80211: add support for NDP ADDBA/DELBA for S1G") Assisted-by: Codex:gpt-5.6-sol Assisted-by: Kimi:K3 Cc: stable@vger.kernel.org Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260728112156.96822-1-enderaoelyther@gmail.com [move/change the comment a bit to be more general not just on ndp, initialize ndp directly] Signed-off-by: Johannes Berg --- net/mac80211/agg-tx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c index 4833b46770b6..0832213430f4 100644 --- a/net/mac80211/agg-tx.c +++ b/net/mac80211/agg-tx.c @@ -915,6 +915,7 @@ void ieee80211_stop_tx_ba_cb(struct sta_info *sta, int tid, struct tid_ampdu_tx *tid_tx) { struct ieee80211_sub_if_data *sdata = sta->sdata; + bool ndp = ndp = tid_tx->ndp; bool send_delba = false; bool start_txq = false; @@ -934,6 +935,7 @@ void ieee80211_stop_tx_ba_cb(struct sta_info *sta, int tid, send_delba = true; ieee80211_remove_tid_tx(sta, tid); + /* tid_tx is now invalid since ieee80211_remove_tid_tx() frees it */ start_txq = true; unlock_sta: @@ -946,7 +948,7 @@ void ieee80211_stop_tx_ba_cb(struct sta_info *sta, int tid, ieee80211_send_delba(sdata, sta->sta.addr, tid, WLAN_BACK_INITIATOR, WLAN_REASON_QSTA_NOT_USE, - tid_tx->ndp); + ndp); } void ieee80211_stop_tx_ba_cb_irqsafe(struct ieee80211_vif *vif, From a2f5286ca4f304d3fd469f01b96b518608912a5c Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sat, 25 Jul 2026 19:50:28 +0530 Subject: [PATCH 352/456] wifi: cfg80211: validate IEs in cfg80211_wext_siwgenie() The KASAN allocation trace shows that a malformed IE buffer is stored via SIOCSIWGENIE (cfg80211_wext_siwgenie()) without any validation. The crash trace shows that a subsequent SIOCSIWESSID triggers a connection attempt which calls cfg80211_sme_get_conn_ies() to process the stored IE buffer, causing: - An out-of-bounds read in skip_ie() which reads ies[pos+1] (the length byte) past the end of the 1-byte buffer. - An integer underflow in the memcpy size argument when offs returned by ieee80211_ie_split() exceeds ies_len, causing unsigned subtraction to wrap to SIZE_MAX and triggering a fortify panic. Fix this by validating the IE buffer in cfg80211_wext_siwgenie() before storing it. Reported-by: syzbot+cc867e537e4bd36f69bb@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=cc867e537e4bd36f69bb Signed-off-by: Deepanshu Kartikey Link: https://patch.msgid.link/20260725142028.32560-1-kartikey406@gmail.com [drop unnecessary ie_len check, update commit message] Signed-off-by: Johannes Berg --- net/wireless/wext-sme.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/net/wireless/wext-sme.c b/net/wireless/wext-sme.c index 573b6b15a446..b5914f3658db 100644 --- a/net/wireless/wext-sme.c +++ b/net/wireless/wext-sme.c @@ -319,6 +319,15 @@ int cfg80211_wext_siwgenie(struct net_device *dev, return 0; if (ie_len) { + const struct element *elem; + + for_each_element(elem, extra, ie_len) { + /* nothing */ + } + + if (!for_each_element_completed(elem, extra, ie_len)) + return -EINVAL; + ie = kmemdup(extra, ie_len, GFP_KERNEL); if (!ie) return -ENOMEM; From 99a948382af8a225e2d5e54a7052158cd6281cc6 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Tue, 28 Jul 2026 19:53:25 +0800 Subject: [PATCH 353/456] wifi: mwifiex: use the subframe length when parsing A-MSDU TDLS frames mwifiex_11n_dispatch_amsdu_pkt() splits an A-MSDU with ieee80211_amsdu_to_8023s() and walks the resulting subframes. For each subframe it passes the subframe data pointer to mwifiex_process_tdls_action_frame(), but pairs it with skb->len, the length of the A-MSDU parent, instead of rx_skb->len: rx_skb = __skb_dequeue(&list); rx_hdr = (struct rx_packet_hdr *)rx_skb->data; if (ISSUPP_TDLS_ENABLED(priv->adapter->fw_cap_info) && ntohs(rx_hdr->eth803_hdr.h_proto) == ETH_P_TDLS) { mwifiex_process_tdls_action_frame(priv, (u8 *)rx_hdr, skb->len); } The parent is not a valid description of that buffer, and may not be valid memory at all. ieee80211_amsdu_to_8023s() ends with if (!reuse_skb) dev_kfree_skb(skb); and it only sets reuse_skb when the parent is linear, is not a head_frag, and is being consumed as the *last* subframe. So when the parent does not qualify for reuse it has already been freed, and the read of skb->len is a use-after-free. When it is reused, skb->len is the length of the last subframe, applied to every earlier subframe, which over-states the buffer whenever an earlier subframe is shorter. The callee cannot absorb a wrong length, because it derives its own ceiling from the value it is given. Each frame type computes ies_len = len - sizeof(struct ethhdr) - TDLS_*_FIX_LEN; and the element walk is then bounded entirely against that ceiling, for (end = pos + ies_len; pos + 1 < end; pos += 2 + pos[1]) { u8 ie_len = pos[1]; if (pos + 2 + ie_len > end) break; so a too-large len moves end past the end of the subframe and the walk reads and copies beyond it. The A-MSDU layout is chosen by the sender, which makes the difference between the last subframe and a shorter earlier one remotely selectable. Reaching this requires TDLS support in firmware and the TDLS ethertype on the subframe. The other caller, mwifiex_process_rx_packet(), is correct: it passes a pointer and a length that describe the same region of the RX buffer. Pass rx_skb->len, the length of the subframe actually being parsed. Fixes: 776f742040ca ("mwifiex: fix AMPDU not setup on TDLS link problem") Assisted-by: Codex:gpt-5.6-sol Assisted-by: Kimi:K3 Cc: stable@vger.kernel.org Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260728115325.19128-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c b/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c index 610ec8302adf..9deb47f22a61 100644 --- a/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c +++ b/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c @@ -44,7 +44,7 @@ static int mwifiex_11n_dispatch_amsdu_pkt(struct mwifiex_private *priv, ntohs(rx_hdr->eth803_hdr.h_proto) == ETH_P_TDLS) { mwifiex_process_tdls_action_frame(priv, (u8 *)rx_hdr, - skb->len); + rx_skb->len); } if (priv->bss_role == MWIFIEX_BSS_ROLE_UAP) From 04513922958005046f8b481c0f77212c556a9c38 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Fri, 24 Jul 2026 04:22:23 +0800 Subject: [PATCH 354/456] wifi: cfg80211: publish PMSR request before starting the driver nl80211_pmsr_start() assigns the request cookie, calls the driver's ->start_pmsr() callback, and only then adds the request to wdev->pmsr_list, without holding pmsr_lock for the addition. mac80211_hwsim saves the request in its start callback and returns. Since nl80211 uses parallel_ops, an immediate REPORT_PMSR can then run before nl80211_pmsr_start() reaches its post-start list_add_tail(). hwsim also dispatches reports from its virtio receive workqueue. Completion removes the request from wdev->pmsr_list under pmsr_lock and frees it. Thus completion can precede publication, race the unlocked list mutation, or free the request before nl80211_pmsr_start() reads req->cookie for the netlink reply. Add the request to wdev->pmsr_list under pmsr_lock before calling the driver, and use a cookie value saved before the call so the request is not dereferenced after a successful start. On an error return the driver has not retained or completed the request, so remove it from the list under the lock and free it. Fixes: 9bb7e0f24e7e ("cfg80211: add peer measurement with FTM initiator API") Link: https://lore.kernel.org/all/20260723010916.76433-1-enderaoelyther@gmail.com/ Assisted-by: Codex:gpt-5 Assisted-by: Claude:opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260723202223.99661-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/wireless/pmsr.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index d1e2fae5bc0e..97449bcb9a22 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -420,6 +420,7 @@ int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info) const struct cfg80211_pmsr_capabilities *capa; struct cfg80211_pmsr_request *req; struct nlattr *peers, *peer; + u64 cookie; capa = rdev->wiphy.pmsr_capa; @@ -521,14 +522,27 @@ int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info) } req->cookie = cfg80211_assign_cookie(rdev); req->nl_portid = info->snd_portid; + cookie = req->cookie; + + /* + * Add to the list before the driver call; under races or broken + * drivers, completion may free the request before rdev_start_pmsr() + * returns. Use the saved cookie below. + */ + spin_lock_bh(&wdev->pmsr_lock); + list_add_tail(&req->list, &wdev->pmsr_list); + spin_unlock_bh(&wdev->pmsr_lock); err = rdev_start_pmsr(rdev, wdev, req); - if (err) + if (err) { + /* An error return leaves the request owned by this path. */ + spin_lock_bh(&wdev->pmsr_lock); + list_del(&req->list); + spin_unlock_bh(&wdev->pmsr_lock); goto out_err; + } - list_add_tail(&req->list, &wdev->pmsr_list); - - nl_set_extack_cookie_u64(info->extack, req->cookie); + nl_set_extack_cookie_u64(info->extack, cookie); return 0; out_err: kfree(req); From 0502d5077e419427d80f4d46ba95d0067f5fb916 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Thu, 23 Jul 2026 09:09:28 +0800 Subject: [PATCH 355/456] wifi: mac80211: validate individual TWT params before driver setup ieee80211_process_rx_twt_action() only partially validates a received S1G TWT setup frame before queueing it. An individual agreement can therefore reach ieee80211_s1g_rx_twt_setup() with twt->length too short for the full struct ieee80211_twt_params. The individual path passes twt to drv_add_twt_setup(). Both the tracepoint and the driver callback consume the complete parameters block, not merely req_type. Do not pass a short individual agreement to the driver. Broadcast agreements remain unchanged because they are rejected locally after accessing only req_type. Fixes: f5a4c24e689f ("mac80211: introduce individual TWT support in AP mode") Assisted-by: Codex:gpt-5 Assisted-by: Claude:opus-4.8 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260723010928.76551-1-enderaoelyther@gmail.com [edit commit message to not overclaim lack of validation nor understate driver impact] Signed-off-by: Johannes Berg --- net/mac80211/s1g.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/s1g.c b/net/mac80211/s1g.c index 5af4a0c6c642..abc338e22e59 100644 --- a/net/mac80211/s1g.c +++ b/net/mac80211/s1g.c @@ -101,6 +101,10 @@ ieee80211_s1g_rx_twt_setup(struct ieee80211_sub_if_data *sdata, struct ieee80211_twt_setup *twt = (void *)mgmt->u.action.s1g.variable; struct ieee80211_twt_params *twt_agrt = (void *)twt->params; + if (!(twt->control & IEEE80211_TWT_CONTROL_NEG_TYPE_BROADCAST) && + twt->length < sizeof(twt->control) + sizeof(*twt_agrt)) + return; + twt_agrt->req_type &= cpu_to_le16(~IEEE80211_TWT_REQTYPE_REQUEST); /* broadcast TWT not supported yet */ From 57aa1718d5953dd532137d43b696c68545c2e0b3 Mon Sep 17 00:00:00 2001 From: Stanislaw Gruszka Date: Fri, 24 Jul 2026 11:55:45 +0200 Subject: [PATCH 356/456] wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check BUG_ON() for il->num_stations < 0 can happen in real word, see https://bugzilla.kernel.org/show_bug.cgi?id=221733 Replace BUG_ON() with WARN_ON() (and reset the counter to 0) to do not put whole system to inconsistent state on the condition. Also allocate debugfs buffer for all stations (32 or 25) to do not use num_stations since it might not be right. Signed-off-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260724095545.33647-1-stf_xl@wp.pl [clarify commit message wrt. debugfs buffer] Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/iwlegacy/common.c | 7 ++++--- drivers/net/wireless/intel/iwlegacy/debug.c | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intel/iwlegacy/common.c b/drivers/net/wireless/intel/iwlegacy/common.c index 8d0ff339ad08..0bb807ff8edf 100644 --- a/drivers/net/wireless/intel/iwlegacy/common.c +++ b/drivers/net/wireless/intel/iwlegacy/common.c @@ -2179,8 +2179,8 @@ il_remove_station(struct il_priv *il, const u8 sta_id, const u8 * addr) il->stations[sta_id].used &= ~IL_STA_DRIVER_ACTIVE; il->num_stations--; - - BUG_ON(il->num_stations < 0); + if (WARN_ON(il->num_stations < 0)) + il->num_stations = 0; spin_unlock_irqrestore(&il->sta_lock, flags); @@ -2328,7 +2328,8 @@ il_dealloc_bcast_stations(struct il_priv *il) il->stations[i].used &= ~IL_STA_UCODE_ACTIVE; il->num_stations--; - BUG_ON(il->num_stations < 0); + if (WARN_ON(il->num_stations < 0)) + il->num_stations = 0; kfree(il->stations[i].lq); il->stations[i].lq = NULL; } diff --git a/drivers/net/wireless/intel/iwlegacy/debug.c b/drivers/net/wireless/intel/iwlegacy/debug.c index d998a3f1b056..8a9f79ff1c6e 100644 --- a/drivers/net/wireless/intel/iwlegacy/debug.c +++ b/drivers/net/wireless/intel/iwlegacy/debug.c @@ -396,7 +396,7 @@ il_dbgfs_stations_read(struct file *file, char __user *user_buf, size_t count, int i, j, pos = 0; ssize_t ret; /* Add 30 for initial string */ - const size_t bufsz = 30 + sizeof(char) * 500 * (il->num_stations); + const size_t bufsz = 30 + sizeof(char) * 500 * max_sta; buf = kmalloc(bufsz, GFP_KERNEL); if (!buf) From e095f249e2209674f6366f6db0383a2b96e19239 Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Thu, 23 Jul 2026 13:57:35 +0800 Subject: [PATCH 357/456] net: ethernet: mtk_eth_soc: pass eth to mtk_handle_irq_rx in poll_controller mtk_handle_irq_rx expects a struct mtk_eth * (matching the request_irq cookie), but mtk_poll_controller incorrectly passed the net_device *. Calling ndo_poll_controller with CONFIG_NET_POLL_CONTROLLER enabled would then crash. Fixes: 8186f6e382d8 ("net-next: mediatek: fix compile error inside mtk_poll_controller()") Signed-off-by: Chenguang Zhao Link: https://patch.msgid.link/20260723055735.885112-1-chenguang.zhao@linux.dev Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mediatek/mtk_eth_soc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index 5d291e50a47b..351444fb4871 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -3467,7 +3467,7 @@ static void mtk_poll_controller(struct net_device *dev) mtk_tx_irq_disable(eth, MTK_TX_DONE_INT); mtk_rx_irq_disable(eth, eth->soc->rx.irq_done_mask); - mtk_handle_irq_rx(eth->irq[MTK_FE_IRQ_RX], dev); + mtk_handle_irq_rx(eth->irq[MTK_FE_IRQ_RX], eth); mtk_tx_irq_enable(eth, MTK_TX_DONE_INT); mtk_rx_irq_enable(eth, eth->soc->rx.irq_done_mask); } From 00a8ce2a2a9fa17674e1feec4d9105c1a5d6a419 Mon Sep 17 00:00:00 2001 From: Raushan Patel Date: Fri, 24 Jul 2026 11:14:35 +0530 Subject: [PATCH 358/456] tracing/probes: Reject $arg0 in meta argument expansion traceprobe_expand_meta_args() parses $argN with simple_strtoul() and calls sprint_nth_btf_arg(n - 1, ...). For $arg0, n is 0 so the index is -1. Because ctx->nr_params is signed, the "idx >= nr_params" guard in sprint_nth_btf_arg() does not catch the negative index, and ctx->params[-1].name_off is read out of bounds. The normal per-argument path (parse_probe_vars()) already rejects $arg0 via its argument-number check, but meta-argument expansion runs before per-argument parsing and substitutes the value first, bypassing that check. Reject $arg0 explicitly during expansion. Link: https://lore.kernel.org/all/20260724054435.146279-1-raushan.jhon@gmail.com/ Fixes: 18b1e870a496 ("tracing/probes: Add $arg* meta argument for all function args") Cc: stable@vger.kernel.org Signed-off-by: Raushan Patel Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 506e6037e163..c8fd9b946f44 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -1901,7 +1901,11 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[], trace_probe_log_err(0, BAD_VAR); return ERR_PTR(-ENOENT); } - /* Note: $argN starts from $arg1 */ + /* Note: $argN starts from $arg1, so $arg0 is invalid. */ + if (n == 0) { + trace_probe_log_err(0, BAD_ARG_NUM); + return ERR_PTR(-EINVAL); + } ret = sprint_nth_btf_arg(n - 1, type, buf + used, bufsize - used, ctx); if (ret < 0) From aca0cd1bf16574327ef64f3178e5f5e37a61ef0b Mon Sep 17 00:00:00 2001 From: Raushan Patel Date: Fri, 24 Jul 2026 12:12:08 +0530 Subject: [PATCH 359/456] tracing/fprobe: Roll back on enable_trace_fprobe() failure enable_trace_fprobe() sets the file link or the TP_FLAG_PROFILE flag and then registers each trace_fprobe in the probe list. If __register_trace_fprobe() fails partway through, the function returns immediately without unregistering the trace_fprobes it already registered or undoing the file link / flag it set, leaving the event half-enabled and leaking the registered fprobe(s). enable_trace_kprobe() already handles this with a rollback path. Do the same for fprobe: on failure, unregister all probes and clear the file link or profile flag. Link: https://lore.kernel.org/all/20260724064208.480030-1-raushan.jhon@gmail.com/ Fixes: 334e5519c375 ("tracing/probes: Add fprobe events for tracing function entry and exit.") Cc: stable@vger.kernel.org Signed-off-by: Raushan Patel Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_fprobe.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c index 9f5f08c0e7c2..3120403a5b60 100644 --- a/kernel/trace/trace_fprobe.c +++ b/kernel/trace/trace_fprobe.c @@ -1481,11 +1481,21 @@ static int enable_trace_fprobe(struct trace_event_call *call, list_for_each_entry(tf, trace_probe_probe_list(tp), tp.list) { ret = __register_trace_fprobe(tf); if (ret < 0) - return ret; + goto err; } } return 0; + +err: + /* Failed to enable one of them. Roll back all */ + list_for_each_entry(tf, trace_probe_probe_list(tp), tp.list) + __unregister_trace_fprobe(tf); + if (file) + trace_probe_remove_file(tp, file); + else + trace_probe_clear_flag(tp, TP_FLAG_PROFILE); + return ret; } /* From 0e65cd9e5d41c34f86b7c347967bedac54926041 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 21 Jul 2026 18:31:49 +0200 Subject: [PATCH 360/456] KVM: VMX: add memory clobber to asm for VMX instructions VMCLEAR/VMREAD/VMWRITE/VMPTRLD access the internal VMCS cache, which is not visible to the compiler; without a memory clobber, the compiler can reorder them in troublesome ways because "asm volatile" and "asm goto" only protect against removal of the asm. For example, placing a VMWRITE before the corresponding VMCS pointer is loaded can lead to corruption. While none of this has been observed, it is better to prevent than cure. Likewise, INVEPT and INVVPID access the TLB and, even though in their case the effect is only visible to the next VMLAUNCH/VMRESUME, it is technically correct to add the clobber there too. So avoid any urge to special case them, and simply hardcode "memory" into the clobber list of vmx_asm1() and vmx_asm2(). __vmcs_readl() open-codes its own asm, so add the clobber there as well. Link: https://lore.kernel.org/kvm/CABgObfbL3t21yVeSwiLSjjOUER+rTYDPHYAH9YU4TWGRjx6XHg@mail.gmail.com/ Cc: Sean Christopherson Cc: stable@vger.kernel.org Signed-off-by: Paolo Bonzini --- arch/x86/kvm/vmx/vmx_ops.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/x86/kvm/vmx/vmx_ops.h b/arch/x86/kvm/vmx/vmx_ops.h index 81784befaaf4..9ffe03a0c223 100644 --- a/arch/x86/kvm/vmx/vmx_ops.h +++ b/arch/x86/kvm/vmx/vmx_ops.h @@ -101,7 +101,7 @@ static __always_inline unsigned long __vmcs_readl(unsigned long field) : [output] "=r" (value) : [field] "r" (field) - : "cc" + : "cc", "memory" : do_fail, do_exception); return value; @@ -145,7 +145,7 @@ static __always_inline unsigned long __vmcs_readl(unsigned long field) : ASM_CALL_CONSTRAINT, [output] "=&r" (value) : [field] "r" (field) - : "cc"); + : "cc", "memory"); return value; #endif /* CONFIG_CC_HAS_ASM_GOTO_OUTPUT */ @@ -192,7 +192,7 @@ do { \ asm goto("1: " __stringify(insn) " %0\n\t" \ "jna %l[error]\n\t" \ _ASM_EXTABLE(1b, %l[fault]) \ - : : op1 : "cc" : error, fault); \ + : : op1 : "cc", "memory" : error, fault); \ return; \ error: \ instrumentation_begin(); \ @@ -208,7 +208,7 @@ do { \ asm goto("1: " __stringify(insn) " %1, %0\n\t" \ "jna %l[error]\n\t" \ _ASM_EXTABLE(1b, %l[fault]) \ - : : op1, op2 : "cc" : error, fault); \ + : : op1, op2 : "cc", "memory" : error, fault);\ return; \ error: \ instrumentation_begin(); \ From 9910e835580fef3bef53b70241dd00c4bffad693 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Mon, 27 Jul 2026 10:17:18 -0700 Subject: [PATCH 361/456] KVM: x86: Cancel delayed I/O APIC EOI handling before destroying vCPUs Cancel (and flush) the I/O APIC's delayed EOI handling work during the "pre VM destroy" phase, before vCPUs are destroyed, as processing the EOI broadcast will inject another IRQ if the line is asserted, i.e. will try to deliver an IRQ to the target vCPU(s). Canceling the work after vCPUs are destroyed leads to UAF if the delayed work is processed after vCPUs are destroyed. BUG: KASAN: slab-use-after-free in __kvm_irq_delivery_to_apic_fast+0x9bf/0xa20 arch/x86/kvm/lapic.c:1250 Read of size 8 at addr ffff8880499abea0 by task kworker/1:2/1218 CPU: 1 UID: 0 PID: 1218 Comm: kworker/1:2 Not tainted 7.1.0-rc7 #5 PREEMPT(lazy) Hardware name: QEMU Ubuntu 25.10 PC v2 (i440FX + PIIX, + 10.1 machine, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: events kvm_ioapic_eoi_inject_work Call Trace: __dump_stack lib/dump_stack.c:94 dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 print_report+0x139/0x4ad mm/kasan/report.c:482 kasan_report+0xe4/0x1d0 mm/kasan/report.c:595 __kvm_irq_delivery_to_apic_fast+0x9bf/0xa20 arch/x86/kvm/lapic.c:1250 __kvm_irq_delivery_to_apic+0xd8/0xbf0 arch/x86/kvm/lapic.c:1345 kvm_irq_delivery_to_apic arch/x86/kvm/lapic.h:129 ioapic_service+0x308/0x590 arch/x86/kvm/ioapic.c:492 kvm_ioapic_eoi_inject_work+0x13c/0x190 arch/x86/kvm/ioapic.c:532 process_one_work+0xa59/0x19a0 kernel/workqueue.c:3314 process_scheduled_works kernel/workqueue.c:3397 worker_thread+0x5eb/0xe50 kernel/workqueue.c:3478 kthread+0x370/0x450 kernel/kthread.c:436 ret_from_fork+0x72b/0xd30 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Note, the VM is unreachable once kvm_destroy_vm() starts, and scheduling new work via kvm_ioapic_send_eoi() can only be done via KVM_RUN, i.e. requires a live vCPU. Alternatively, KVM could simply destroy the I/O APIC during the "pre" phase of VM destruction, but that gets more than a bit sketchy as KVM expects the I/O APIC to exist if ioapic_in_kernel() is true, and nested virtualization in particular has a bad habit of touching VM-scope state during vCPU destruction. E.g. attempting to free the PIC during the pre phase would lead to a NULL pointer dereference in kvm_cpu_has_extint(), and it's not hard to imagine the I/O APIC having a similar flaw. Fixes: 17bcd7144263 ("KVM: x86: Free vCPUs before freeing VM state") Reported-by: Reported-by: Zhong Wang Reported-by: Xuanqing Shi Cc: stable@vger.kernel.org Signed-off-by: Weiming Shi Co-developed-by: Sean Christopherson Signed-off-by: Sean Christopherson Message-ID: <20260727171718.543491-1-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/x86.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index afcac1042947..47cb9eba113b 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -13429,9 +13429,15 @@ void kvm_arch_pre_destroy_vm(struct kvm *kvm) * iterating over vCPUs in a different task while vCPUs are being freed * is unsafe, i.e. will lead to use-after-free. The PIT also needs to * be stopped before IRQ routing is freed. + * + * Do NOT free the in-kernel PIC or I/O APIC here (but as above, make + * sure to flush any background work), as KVM expects interrupt routing + * structures to be valid until vCPUs are destroyed. */ #ifdef CONFIG_KVM_IOAPIC kvm_free_pit(kvm); + if (kvm->arch.vioapic) + cancel_delayed_work_sync(&kvm->arch.vioapic->eoi_inject); #endif kvm_mmu_pre_destroy_vm(kvm); From 0f38453cdb2e17566ccb7c0f3dabd5bd21caca26 Mon Sep 17 00:00:00 2001 From: Jinu Kim Date: Tue, 21 Jul 2026 19:35:11 +0900 Subject: [PATCH 362/456] KVM: x86/mmu: Check write tracking in all address spaces kvm_gfn_is_write_tracked() checks only the supplied memslot, but page tracking is per-address-space and shadow pages are shared across all address spaces. With SMM, a GFN can therefore be write-tracked in one address space and appear untracked through the other. Check the supplied slot first, then the slot for the other address space. This ensures all callers honor write tracking regardless of the active address space. In particular, it prevents mmu_try_to_unsync_pages() from marking an upper-level shadow page unsync and eventually triggering the BUG in pte_list_remove(). Fixes: 699023e23965 ("KVM: x86: add SMM to the MMU role, support SMRAM address space") Assisted-by: Codex:GPT-5 Signed-off-by: Jinu Kim Message-ID: <20260721103512.2136240-2-kimjw04271234@gmail.com> [invert direction of the conditional. - Paolo] Signed-off-by: Paolo Bonzini --- arch/x86/kvm/mmu/page_track.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/arch/x86/kvm/mmu/page_track.c b/arch/x86/kvm/mmu/page_track.c index 1b17b12393a8..7e8195a311bb 100644 --- a/arch/x86/kvm/mmu/page_track.c +++ b/arch/x86/kvm/mmu/page_track.c @@ -130,13 +130,23 @@ void __kvm_write_track_remove_gfn(struct kvm *kvm, kvm_mmu_gfn_allow_lpage(slot, gfn); } -/* - * check if the corresponding access on the specified guest page is tracked. - */ +static bool __kvm_gfn_is_write_tracked(const struct kvm_memory_slot *slot, + gfn_t gfn) +{ + int index; + + if (!slot) + return false; + + index = gfn_to_index(gfn, slot->base_gfn, PG_LEVEL_4K); + return !!READ_ONCE(slot->arch.gfn_write_track[index]); +} + +/* check if write access is tracked on the specified guest page. */ bool kvm_gfn_is_write_tracked(struct kvm *kvm, const struct kvm_memory_slot *slot, gfn_t gfn) { - int index; + const struct kvm_memory_slot *other_slot; if (!slot) return false; @@ -144,8 +154,18 @@ bool kvm_gfn_is_write_tracked(struct kvm *kvm, if (!kvm_page_track_write_tracking_enabled(kvm)) return false; - index = gfn_to_index(gfn, slot->base_gfn, PG_LEVEL_4K); - return !!READ_ONCE(slot->arch.gfn_write_track[index]); + BUILD_BUG_ON(KVM_MAX_NR_ADDRESS_SPACES > 2); + + if (__kvm_gfn_is_write_tracked(slot, gfn)) + return true; + + if (kvm_arch_nr_memslot_as_ids(kvm) > 1) { + other_slot = __gfn_to_memslot(__kvm_memslots(kvm, slot->as_id ^ 1), gfn); + if (__kvm_gfn_is_write_tracked(other_slot, gfn)) + return true; + } + + return false; } #ifdef CONFIG_KVM_EXTERNAL_WRITE_TRACKING From 2e8a2c1b03068d76782343446f1b2114ae2ee0bd Mon Sep 17 00:00:00 2001 From: Jinu Kim Date: Tue, 21 Jul 2026 19:35:12 +0900 Subject: [PATCH 363/456] KVM: x86/mmu: Check all address spaces before skipping unsync mmu_try_to_unsync_pages() skips the shadow-page lookup when the supplied memslot allows a hugepage, because a shadow page would disallow hugepages. But hugepage metadata is per-address-space while shadow pages are shared across all address spaces. With SMM, the other address space can therefore have a shadow page even when the supplied memslot allows a hugepage. Check the corresponding memslot in the other address space before taking the fast path. Skip the shadow-page lookup only when all address spaces allow a hugepage. Fixes: b3ae3ceb5569 ("KVM: x86/mmu: KVM: x86/mmu: Skip unsync when large pages are allowed") Assisted-by: Codex:GPT-5 Signed-off-by: Jinu Kim [invert direction of the conditional. - Paolo] Message-ID: <20260721103512.2136240-3-kimjw04271234@gmail.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/mmu/mmu.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 22cf222d3033..66e69d2a41b3 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -722,6 +722,26 @@ static struct kvm_lpage_info *lpage_info_slot(gfn_t gfn, return &slot->arch.lpage_info[level - 2][idx]; } +static bool kvm_gfn_is_lpage_allowed(struct kvm *kvm, + const struct kvm_memory_slot *slot, + gfn_t gfn, int level) +{ + const struct kvm_memory_slot *other_slot; + + BUILD_BUG_ON(KVM_MAX_NR_ADDRESS_SPACES > 2); + + if (lpage_info_slot(gfn, slot, level)->disallow_lpage) + return false; + + if (kvm_arch_nr_memslot_as_ids(kvm) > 1) { + other_slot = __gfn_to_memslot(__kvm_memslots(kvm, slot->as_id ^ 1), gfn); + if (other_slot && lpage_info_slot(gfn, other_slot, level)->disallow_lpage) + return false; + } + + return true; +} + /* * The most significant bit in disallow_lpage tracks whether or not memory * attributes are mixed, i.e. not identical for all gfns at the current level. @@ -2968,7 +2988,7 @@ int mmu_try_to_unsync_pages(struct kvm *kvm, const struct kvm_memory_slot *slot, * write-protected (see above), thus if the gfn can be mapped with a * hugepage and isn't write-tracked, it can't have a shadow page. */ - if (!lpage_info_slot(gfn, slot, PG_LEVEL_2M)->disallow_lpage) + if (kvm_gfn_is_lpage_allowed(kvm, slot, gfn, PG_LEVEL_2M)) return 0; /* From 9f7007ee9858c99aa43101bc8352c672fee85644 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Wed, 17 Jun 2026 17:57:54 -0400 Subject: [PATCH 364/456] idpf: bound interrupt-vector register fill to the allocated array idpf_get_reg_intr_vecs() fills the caller-allocated reg_vals[] array from the VIRTCHNL2_OP_ALLOC_VECTORS reply in adapter->req_vec_chunks, bounding its inner loop only by the per-chunk num_vectors. The array is sized separately: idpf_intr_reg_init() allocates kzalloc_objs(struct idpf_vec_regs, total_vecs) from caps.num_allocated_vectors and only checks the returned count after the fill. The sum of per-chunk num_vectors is never reconciled against total_vecs, so a reply with a small num_allocated_vectors but chunks summing higher writes past the end of reg_vals[]. Impact: a control plane (a PF or hypervisor device model) that returns a VIRTCHNL2_OP_ALLOC_VECTORS reply whose per-chunk num_vectors sum exceeds num_allocated_vectors writes struct idpf_vec_regs entries past the end of the reg_vals kmalloc allocation (KASAN slab-out-of-bounds write). Bound the fill loop to the array capacity passed in by the callers, mirroring the sibling idpf_vport_get_q_reg(). The existing num_regs < num_vecs check then rejects an undersized reply without the out-of-bounds write happening first. Fixes: d4d558718266 ("idpf: initialize interrupts and enable vport") Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_dev.c | 2 +- drivers/net/ethernet/intel/idpf/idpf_vf_dev.c | 2 +- drivers/net/ethernet/intel/idpf/idpf_virtchnl.c | 5 +++-- drivers/net/ethernet/intel/idpf/idpf_virtchnl.h | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_dev.c b/drivers/net/ethernet/intel/idpf/idpf_dev.c index 1a0c71c95ef1..4079a787657f 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_dev.c +++ b/drivers/net/ethernet/intel/idpf/idpf_dev.c @@ -87,7 +87,7 @@ static int idpf_intr_reg_init(struct idpf_vport *vport, if (!reg_vals) return -ENOMEM; - num_regs = idpf_get_reg_intr_vecs(adapter, reg_vals); + num_regs = idpf_get_reg_intr_vecs(adapter, reg_vals, total_vecs); if (num_regs < num_vecs) { err = -EINVAL; goto free_reg_vals; diff --git a/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c b/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c index a07d7e808ca9..6726084f6cfa 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c +++ b/drivers/net/ethernet/intel/idpf/idpf_vf_dev.c @@ -86,7 +86,7 @@ static int idpf_vf_intr_reg_init(struct idpf_vport *vport, if (!reg_vals) return -ENOMEM; - num_regs = idpf_get_reg_intr_vecs(adapter, reg_vals); + num_regs = idpf_get_reg_intr_vecs(adapter, reg_vals, total_vecs); if (num_regs < num_vecs) { err = -EINVAL; goto free_reg_vals; diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c index dc5ad784f456..8bd6cca64c9b 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c @@ -1318,11 +1318,12 @@ idpf_vport_init_queue_reg_chunks(struct idpf_vport_config *vport_config, * idpf_get_reg_intr_vecs - Get vector queue register offset * @adapter: adapter structure to get the vector chunks * @reg_vals: Register offsets to store in + * @num_vecs: number of entries the @reg_vals array can hold * * Return: number of registers that got populated */ int idpf_get_reg_intr_vecs(struct idpf_adapter *adapter, - struct idpf_vec_regs *reg_vals) + struct idpf_vec_regs *reg_vals, int num_vecs) { struct virtchnl2_vector_chunks *chunks; struct idpf_vec_regs reg_val; @@ -1346,7 +1347,7 @@ int idpf_get_reg_intr_vecs(struct idpf_adapter *adapter, dynctl_reg_spacing = le32_to_cpu(chunk->dynctl_reg_spacing); itrn_reg_spacing = le32_to_cpu(chunk->itrn_reg_spacing); - for (i = 0; i < num_vec; i++) { + for (i = 0; i < num_vec && num_regs < num_vecs; i++) { reg_vals[num_regs].dyn_ctl_reg = reg_val.dyn_ctl_reg; reg_vals[num_regs].itrn_reg = reg_val.itrn_reg; reg_vals[num_regs].itrn_index_spacing = diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h index 6876e3ed9d1b..9b1c9c86f6ea 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h +++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.h @@ -104,7 +104,7 @@ int idpf_vc_core_init(struct idpf_adapter *adapter); void idpf_vc_core_deinit(struct idpf_adapter *adapter); int idpf_get_reg_intr_vecs(struct idpf_adapter *adapter, - struct idpf_vec_regs *reg_vals); + struct idpf_vec_regs *reg_vals, int num_vecs); int idpf_queue_reg_init(struct idpf_vport *vport, struct idpf_q_vec_rsrc *rsrc, struct idpf_queue_id_reg_info *chunks); From bef152db47debcd14cbacefc5767f6f026c4bc89 Mon Sep 17 00:00:00 2001 From: Joshua Hay Date: Tue, 30 Jun 2026 16:56:19 -0700 Subject: [PATCH 365/456] idpf: adjust TxQ ring count minimum Set the TxQ ring count minimum to 128 descriptors. Any lower than this, and the queue will stall and trigger Tx timeouts in flow based scheduling mode. This is because next_to_clean might never be updated. In flow based scheduling mode, next_to_clean is only updated after a descriptor completion is processed, i.e. after the RE bit is set in the last descriptor of a Tx packet. This will never happen with a ring size of 64 and an IDPF_TX_SPLITQ_RE_MIN_GAP of 64. No matter what the value of last_re is initialized/set to, the calculated gap will be at most 63 and never trigger the RE bit. Even a ring size of 96 does not solve this. Because of how infrequent next_to_clean is updated and how small the ring is, IDPF_DESC_UNUSED will be much smaller on average. This increases the chance the queue will be stopped because a multi-descriptor packet, e.g. a large LSO packet, does not see enough resources on the ring. In this case, the queue will trigger the stop logic. The queue permanently stalls because there is no chance for a descriptor completion to update next_to_clean since it is dependent on a packet being sent. Fixes: 5f417d551324 ("idpf: replace flow scheduling buffer ring with buffer pool") Signed-off-by: Joshua Hay Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 5 +---- drivers/net/ethernet/intel/idpf/idpf_txrx.h | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 7f9056404f64..c724d429a7aa 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -3097,10 +3097,7 @@ static netdev_tx_t idpf_tx_splitq_frame(struct sk_buff *skb, tx_params.dtype = IDPF_TX_DESC_DTYPE_FLEX_FLOW_SCHE; tx_params.eop_cmd = IDPF_TXD_FLEX_FLOW_CMD_EOP; - /* Set the RE bit to periodically "clean" the descriptor ring. - * MIN_GAP is set to MIN_RING size to ensure it will be set at - * least once each time around the ring. - */ + /* Set the RE bit periodically to "clean" the descriptor ring */ if (idpf_tx_splitq_need_re(tx_q)) { tx_params.eop_cmd |= IDPF_TXD_FLEX_FLOW_CMD_RE; tx_q->txq_grp->num_completions_pending++; diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h b/drivers/net/ethernet/intel/idpf/idpf_txrx.h index 4be5b3b6d3ed..908dfa28674e 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h @@ -21,7 +21,7 @@ /* Mailbox Queue */ #define IDPF_MAX_MBXQ 1 -#define IDPF_MIN_TXQ_DESC 64 +#define IDPF_MIN_TXQ_DESC 128 #define IDPF_MIN_RXQ_DESC 64 #define IDPF_MIN_TXQ_COMPLQ_DESC 256 #define IDPF_MAX_QIDS 256 From 9bff30482c10f70d9e56c0633a6616e07140e217 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Fri, 3 Jul 2026 01:03:32 -0400 Subject: [PATCH 366/456] idpf: Fix mailbox IRQ name leak on request failure idpf_mb_intr_req_irq() allocates the mailbox IRQ name before calling request_irq(). On success, the name is released later through kfree(free_irq()), but request_irq() failure returns without freeing it. Free the allocated name on the request_irq() failure path. Fixes: 4930fbf419a7 ("idpf: add core init and interrupt request") Signed-off-by: Yuho Choi Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c index cf966fe6c759..bb81e620c5c8 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_lib.c +++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c @@ -139,7 +139,7 @@ static int idpf_mb_intr_req_irq(struct idpf_adapter *adapter) if (err) { dev_err(&adapter->pdev->dev, "IRQ request for mailbox failed, error: %d\n", err); - + kfree(name); return err; } From c2816d613f388814d27bc9fd6dbd931a88056e19 Mon Sep 17 00:00:00 2001 From: Aaron Ma Date: Wed, 29 Apr 2026 11:48:49 +0800 Subject: [PATCH 367/456] ice: wait for reset completion in ice_resume() ice_resume() schedules an asynchronous PF reset and returns immediately. The reset runs later in ice_service_task(). If userspace tries to bring up the net device before the reset finishes, ice_open() fails with -EBUSY: ice_resume() ice_schedule_reset() # sets ICE_PFR_REQ, returns ... ice_open() ice_is_reset_in_progress() # ICE_PFR_REQ still set, -EBUSY ... ice_service_task() ice_do_reset() ice_rebuild() # clears ICE_PFR_REQ, too late Reproduced on E800 series NICs during suspend/resume with irdma enabled, where the aux device probe widens the race window. ice 0000:81:00.0: can't open net device while reset is in progress Add a best-effort wait (10s timeout, matching ice_devlink_info_get()) for the reset to complete before returning from ice_resume(). In practice the reset completes in ~300ms. Fixes: 769c500dcc1e ("ice: Add advanced power mgmt for WoL") Cc: stable@vger.kernel.org Reviewed-by: Kohei Enju Reviewed-by: Aleksandr Loktionov Reviewed-by: Przemek Kitszel Signed-off-by: Aaron Ma Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_main.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index e2fd2dab03e3..d88835482d3a 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -5637,6 +5637,16 @@ static int ice_resume(struct device *dev) /* Restart the service task */ mod_timer(&pf->serv_tmr, round_jiffies(jiffies + pf->serv_tmr_period)); + /* Best-effort wait for the scheduled reset to finish so that the + * device is operational before returning. Without this, userspace + * (e.g. NetworkManager) may try to open the net device while the + * asynchronous reset is still in progress, hitting -EBUSY. + */ + ret = ice_wait_for_reset(pf, secs_to_jiffies(10)); + if (ret) + dev_err(dev, "Wait for reset timed out (10s) during resume: %d\n", + ret); + return 0; } From fb096882095e5a8d6b5159e43793d4a38a0c5b1f Mon Sep 17 00:00:00 2001 From: Dawid Osuchowski Date: Thu, 14 May 2026 18:35:55 +0200 Subject: [PATCH 368/456] ice: fix VF interrupts cleanup When a virtual function sends an IRQ map command, the PF will set up interrupts according to that request. However, because these interrupts are never reset, the next time Virtual Function initializes, the interrupts are still enabled for a given VF, which leads to performance degradation in certain cases due to interrupts being unexpectedly enabled and thus causing interrupt floods. Cc: stable@vger.kernel.org Fixes: 1071a8358a28 ("ice: Implement virtchnl commands for AVF support") Suggested-by: Vladimir Medvedkin Reviewed-by: Aleksandr Loktionov Signed-off-by: Dawid Osuchowski Reviewed-by: Simon Horman Tested-by: Patryk Holda Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_vf_lib.c | 27 +++++++++++++++++++ .../ethernet/intel/ice/ice_vf_lib_private.h | 1 + drivers/net/ethernet/intel/ice/virt/queues.c | 21 +++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.c b/drivers/net/ethernet/intel/ice/ice_vf_lib.c index 9052e71e9c99..a54cb2b8d3c7 100644 --- a/drivers/net/ethernet/intel/ice/ice_vf_lib.c +++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.c @@ -848,6 +848,30 @@ static void ice_notify_vf_reset(struct ice_vf *vf) NULL); } +/** + * ice_reset_interrupts - clear all queue interrupt configuration for a VSI + * @vsi: the VSI whose interrupt registers should be cleared + * + * Zero the QINT_RQCTL and QINT_TQCTL registers for all allocated queues + * in the VSI. This clears the entire register including MSIX_INDX, ITR_INDX, + * CAUSE_ENA and NEXTQ fields, unlike ice_vf_dis_rxq_interrupt() which only + * clears the CAUSE_ENA bit. + */ +void ice_reset_interrupts(struct ice_vsi *vsi) +{ + struct ice_pf *pf = vsi->back; + struct ice_hw *hw = &pf->hw; + int i; + + ice_for_each_alloc_rxq(vsi, i) + wr32(hw, QINT_RQCTL(vsi->rxq_map[i]), 0); + + ice_for_each_alloc_txq(vsi, i) + wr32(hw, QINT_TQCTL(vsi->txq_map[i]), 0); + + ice_flush(hw); +} + /** * ice_reset_vf - Reset a particular VF * @vf: pointer to the VF structure @@ -919,6 +943,9 @@ int ice_reset_vf(struct ice_vf *vf, u32 flags) ice_dis_vf_qs(vf); + /* cleanup interrupt registers */ + ice_reset_interrupts(vsi); + /* Call Disable LAN Tx queue AQ whether or not queues are * enabled. This is needed for successful completion of VFR. */ diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib_private.h b/drivers/net/ethernet/intel/ice/ice_vf_lib_private.h index 5392b0404986..321d29c25b7c 100644 --- a/drivers/net/ethernet/intel/ice/ice_vf_lib_private.h +++ b/drivers/net/ethernet/intel/ice/ice_vf_lib_private.h @@ -26,6 +26,7 @@ void ice_initialize_vf_entry(struct ice_vf *vf); void ice_deinitialize_vf_entry(struct ice_vf *vf); void ice_dis_vf_qs(struct ice_vf *vf); +void ice_reset_interrupts(struct ice_vsi *vsi); int ice_check_vf_init(struct ice_vf *vf); enum virtchnl_status_code ice_err_to_virt_err(int err); struct ice_port_info *ice_vf_get_port_info(struct ice_vf *vf); diff --git a/drivers/net/ethernet/intel/ice/virt/queues.c b/drivers/net/ethernet/intel/ice/virt/queues.c index 31be2f76181c..431c9c546b04 100644 --- a/drivers/net/ethernet/intel/ice/virt/queues.c +++ b/drivers/net/ethernet/intel/ice/virt/queues.c @@ -224,6 +224,24 @@ void ice_vf_ena_rxq_interrupt(struct ice_vsi *vsi, u32 q_idx) wr32(hw, QINT_RQCTL(pfq), reg | QINT_RQCTL_CAUSE_ENA_M); } +/** + * ice_vf_dis_rxq_interrupt - disable Rx queue interrupt via QINT_RQCTL + * @vsi: VSI of the VF to configure + * @q_idx: VF queue index used to determine the queue in the PF's space + */ +static void ice_vf_dis_rxq_interrupt(struct ice_vsi *vsi, u32 q_idx) +{ + struct ice_hw *hw = &vsi->back->hw; + u32 pfq = vsi->rxq_map[q_idx]; + u32 reg; + + reg = rd32(hw, QINT_RQCTL(pfq)); + reg &= ~QINT_RQCTL_CAUSE_ENA_M; + wr32(hw, QINT_RQCTL(pfq), reg); + + ice_flush(hw); +} + /** * ice_vc_ena_qs_msg * @vf: pointer to the VF info @@ -416,6 +434,8 @@ int ice_vc_dis_qs_msg(struct ice_vf *vf, u8 *msg) goto error_param; } + for_each_set_bit(vf_q_id, &q_map, ICE_MAX_RSS_QS_PER_VF) + ice_vf_dis_rxq_interrupt(vsi, vf_q_id); bitmap_zero(vf->rxq_ena, ICE_MAX_RSS_QS_PER_VF); } else if (q_map) { for_each_set_bit(vf_q_id, &q_map, ICE_MAX_RSS_QS_PER_VF) { @@ -436,6 +456,7 @@ int ice_vc_dis_qs_msg(struct ice_vf *vf, u8 *msg) goto error_param; } + ice_vf_dis_rxq_interrupt(vsi, vf_q_id); /* Clear enabled queues flag */ clear_bit(vf_q_id, vf->rxq_ena); } From 3a9de5590da4ffd9e9c541c4c4d492aa2b54cf6e Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Tue, 16 Jun 2026 23:57:42 +0800 Subject: [PATCH 369/456] ice: fix memory leak in ice_lbtest_prepare_rings() ice_lbtest_prepare_rings() frees Rx rings only when ice_vsi_start_all_rx_rings() fails. If ice_vsi_setup_rx_rings() fails after allocating some descriptors, or if ice_vsi_cfg_lan() fails after the Rx rings were prepared, the function reaches the Tx cleanup path without releasing the initialized Rx resources. Fix this by adding separate unwind paths for Rx setup failure and LAN configuration failure. The Rx setup failure path releases the partially prepared Rx rings before freeing Tx rings, while later failures first undo the LAN Tx configuration and then release the Rx rings in reverse setup order. The bug was first flagged by an experimental analysis tool we are developing for kernel memory-management bugs while analyzing v6.13-rc1. The tool is still under development and is not yet publicly available. Manual inspection confirms that the bug is still present in v7.1-rc7. An x86_64 allyesconfig build showed no new warnings. As we do not have an Intel E800 Series adapter available to run the ethtool offline loopback selftest, no runtime testing was able to be performed. Fixes: 0e674aeb0b77 ("ice: Add handler for ethtool selftest") Cc: stable@vger.kernel.org Signed-off-by: Dawei Feng Reviewed-by: Jacob Keller Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_ethtool.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool.c b/drivers/net/ethernet/intel/ice/ice_ethtool.c index 49371b065845..7eb380be7ed2 100644 --- a/drivers/net/ethernet/intel/ice/ice_ethtool.c +++ b/drivers/net/ethernet/intel/ice/ice_ethtool.c @@ -1069,18 +1069,18 @@ static int ice_lbtest_prepare_rings(struct ice_vsi *vsi) status = ice_vsi_cfg_lan(vsi); if (status) - goto err_setup_rx_ring; + goto err_cfg_lan; status = ice_vsi_start_all_rx_rings(vsi); if (status) - goto err_start_rx_ring; + goto err_cfg_lan; return 0; -err_start_rx_ring: - ice_vsi_free_rx_rings(vsi); -err_setup_rx_ring: +err_cfg_lan: ice_vsi_stop_lan_tx_rings(vsi, ICE_NO_RESET, 0); +err_setup_rx_ring: + ice_vsi_free_rx_rings(vsi); err_setup_tx_ring: ice_vsi_free_tx_rings(vsi); From b00be7c6b4bd7da3d510753b27ff6cb7ec647d07 Mon Sep 17 00:00:00 2001 From: Przemyslaw Korba Date: Wed, 20 May 2026 13:50:06 +0200 Subject: [PATCH 370/456] ice: suppress DPLL errors during reset recovery During reset recovery, the admin queue returns EBUSY which is expected behavior. However, the DPLL subsystem was logging these as errors and incrementing the error counter, potentially leading to unnecessary warnings and even disabling the DPLL periodic worker if the threshold was reached. Suppress error logging and error counter increments when the admin queue returns EBUSY, as this is expected during reset recovery and not a real failure condition. test case: - ethtool --reset eth3 irq-shared dma-shared filter-shared offload-shared mac-shared phy-shared ram-shared - observe if dmesg EBUSY errors are gone Fixes: d7999f5ea64b ("ice: implement dpll interface to control cgu") Signed-off-by: Przemyslaw Korba Reviewed-by: Simon Horman Tested-by: Rinitha S (A Contingent worker at Intel) Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_dpll.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c index 30c3a4db7d61..85a74cd6ea1f 100644 --- a/drivers/net/ethernet/intel/ice/ice_dpll.c +++ b/drivers/net/ethernet/intel/ice/ice_dpll.c @@ -793,7 +793,7 @@ ice_dpll_pin_state_update(struct ice_pf *pf, struct ice_dpll_pin *pin, ret, libie_aq_str(pf->hw.adminq.sq_last_status), pin_type_name[pin_type], pin->idx); - else + else if (pf->hw.adminq.sq_last_status != LIBIE_AQ_RC_EBUSY) dev_err_ratelimited(ice_pf_to_dev(pf), "err:%d %s failed to update %s pin:%u\n", ret, @@ -3024,7 +3024,8 @@ static int ice_dpll_pps_update_phase_offsets(struct ice_pf *pf, *phase_offset_pins_updated = 0; ret = ice_aq_get_cgu_input_pin_measure(&pf->hw, DPLL_TYPE_PPS, meas, ARRAY_SIZE(meas)); - if (ret && pf->hw.adminq.sq_last_status == LIBIE_AQ_RC_EAGAIN) { + if (ret && (pf->hw.adminq.sq_last_status == LIBIE_AQ_RC_EAGAIN || + pf->hw.adminq.sq_last_status == LIBIE_AQ_RC_EBUSY)) { return 0; } else if (ret) { dev_err(ice_pf_to_dev(pf), @@ -3086,10 +3087,12 @@ ice_dpll_update_state(struct ice_pf *pf, struct ice_dpll *d, bool init) d->dpll_idx, d->prev_input_idx, d->input_idx, d->dpll_state, d->prev_dpll_state, d->mode); if (ret) { - dev_err(ice_pf_to_dev(pf), - "update dpll=%d state failed, ret=%d %s\n", - d->dpll_idx, ret, - libie_aq_str(pf->hw.adminq.sq_last_status)); + /* EBUSY is expected during reset recovery, don't log error */ + if (pf->hw.adminq.sq_last_status != LIBIE_AQ_RC_EBUSY) + dev_err(ice_pf_to_dev(pf), + "update dpll=%d state failed, ret=%d %s\n", + d->dpll_idx, ret, + libie_aq_str(pf->hw.adminq.sq_last_status)); return ret; } if (init) { @@ -3158,7 +3161,9 @@ static void ice_dpll_periodic_work(struct kthread_work *work) d->periodic_counter % dp->phase_offset_monitor_period == 0) ret = ice_dpll_pps_update_phase_offsets(pf, &phase_offset_ntf); if (ret) { - d->cgu_state_acq_err_num++; + /* EBUSY is expected during reset recovery */ + if (pf->hw.adminq.sq_last_status != LIBIE_AQ_RC_EBUSY) + d->cgu_state_acq_err_num++; /* stop rescheduling this worker */ if (d->cgu_state_acq_err_num > ICE_CGU_STATE_ACQ_ERR_THRESHOLD) { From 5ffab5b9589c50e4cfc0cf36ffd76c89422d4019 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 12 Jul 2026 14:22:42 +0100 Subject: [PATCH 371/456] igc: remove napi_synchronize() in igc_down() When an AF_XDP zero-copy application is killed abruptly, the XSK pool is torn down but NAPI keeps polling. igc_clean_rx_irq_zc() then returns the full budget on every poll, so napi_complete_done() never clears NAPI_STATE_SCHED. igc_down() calls napi_synchronize() before napi_disable(), so it spins forever waiting for that bit and the interface never goes down. Drop the napi_synchronize() and let napi_disable() do the job -- it sets NAPI_STATE_DISABLE, which forces the stuck poll to complete. Reorder it ahead of igc_set_queue_napi() so the NAPI mapping is cleared only after polling has stopped, matching the recent igb fix b1e067240379. Fixes: fc9df2a0b520 ("igc: Enable RX via AF_XDP zero-copy") Suggested-by: Maciej Fijalkowski Cc: stable@vger.kernel.org Signed-off-by: David Carlier Reviewed-by: Maciej Fijalkowski Reviewed-by: Dima Ruinskiy Tested-by: Moriya Kadosh Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igc/igc_main.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/igc/igc_main.c b/drivers/net/ethernet/intel/igc/igc_main.c index 2c9e2dfd8499..b3883a5a7d7a 100644 --- a/drivers/net/ethernet/intel/igc/igc_main.c +++ b/drivers/net/ethernet/intel/igc/igc_main.c @@ -5352,9 +5352,8 @@ void igc_down(struct igc_adapter *adapter) for (i = 0; i < adapter->num_q_vectors; i++) { if (adapter->q_vector[i]) { - napi_synchronize(&adapter->q_vector[i]->napi); - igc_set_queue_napi(adapter, i, NULL); napi_disable(&adapter->q_vector[i]->napi); + igc_set_queue_napi(adapter, i, NULL); } } From 0565052b7e2f436b7f1541f4849da96dc0aa7a0e Mon Sep 17 00:00:00 2001 From: Matt Vollrath Date: Thu, 16 Apr 2026 23:34:52 -0400 Subject: [PATCH 372/456] igbvf: Fix leak in TX DMA error cleanup If an error is encountered while mapping TX buffers, the driver should unmap any buffers already mapped for that skb. Because count is incremented before each frag mapping, it will always match the correct number of unmappings needed when dma_error is reached. Decrementing count before the while loop in dma_error causes an off-by-one error. If any mapping was successful before an unsuccessful mapping, exactly one DMA mapping (the head) would leak. This bug was introduced by a 2010 fix for an endless loop in dma_error. All other affected drivers have already been fixed. Fixes: c1fa347f20f1 ("e1000/e1000e/igb/igbvf/ixgb/ixgbe: Fix tests of unsigned in *_tx_map()") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-4-7-opus Signed-off-by: Matt Vollrath Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/igbvf/netdev.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/ethernet/intel/igbvf/netdev.c b/drivers/net/ethernet/intel/igbvf/netdev.c index 0a3d0a1cba43..c686ee120a14 100644 --- a/drivers/net/ethernet/intel/igbvf/netdev.c +++ b/drivers/net/ethernet/intel/igbvf/netdev.c @@ -2190,8 +2190,6 @@ static inline int igbvf_tx_map_adv(struct igbvf_adapter *adapter, buffer_info->time_stamp = 0; buffer_info->length = 0; buffer_info->mapped_as_page = false; - if (count) - count--; /* clear timestamp and dma mappings for remaining portion of packet */ while (count--) { From 816419dfea5c88126f35eb7a1b429a1bf546665e Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Sun, 7 Jun 2026 22:57:06 +0800 Subject: [PATCH 373/456] e1000: fix memory leak in e1000_probe() In the e1000_probe() path, e1000_sw_init() allocates adapter->tx_ring and adapter->rx_ring. If the subsequent CE4100-specific MDIO BAR mapping fails, the error handling jumps past the ring cleanup code, leaking both allocations. Fix this leak by moving the err_mdio_ioremap label above the ring deallocation logic. This guarantees the proper release of these resources and prevents the memory leak. The bug was first flagged by an experimental analysis tool we are developing for kernel memory-management bugs while analyzing v6.13-rc1. The tool is still under development and is not yet publicly available. Manual inspection confirms that the bug is still present in v7.1-rc6. An x86_64 allyesconfig build showed no new warnings. As we do not have a CE4100 reference platform to test with, no runtime testing was able to be performed. Fixes: 5377a4160bb65 ("e1000: Add support for the CE4100 reference platform") Cc: stable@vger.kernel.org Signed-off-by: Zilin Guan Signed-off-by: Dawei Feng Reviewed-by: Dima Ruinskiy Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/e1000/e1000_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/e1000/e1000_main.c b/drivers/net/ethernet/intel/e1000/e1000_main.c index 9b09eb144b81..d7f5c6f16142 100644 --- a/drivers/net/ethernet/intel/e1000/e1000_main.c +++ b/drivers/net/ethernet/intel/e1000/e1000_main.c @@ -1222,11 +1222,11 @@ static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (hw->flash_address) iounmap(hw->flash_address); +err_mdio_ioremap: kfree(adapter->tx_ring); kfree(adapter->rx_ring); err_dma: err_sw_init: -err_mdio_ioremap: iounmap(hw->ce4100_gbe_mdio_base_virt); iounmap(hw->hw_addr); err_ioremap: From d57e506f6a1e3929611340fae87c1e4823f4d85c Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 20 Jul 2026 17:53:33 +0300 Subject: [PATCH 374/456] Bluetooth: ISO: clear iso_data always when detaching conn from hcon When setting conn->hcon = NULL, also conn->hcon->iso_data = NULL is necessary, otherwise later iso_conn_free() will UAF. Fix clearing of iso_data in iso_sock_disconn() Fixes KASAN: slab-use-after-free in iso_conn_hold_unless_zero on iso_sock_release() followed by hci_abort_conn_sync(). Fixes: fbdc4bc47268 ("Bluetooth: ISO: Use defer setup to separate PA sync and BIG sync") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 2e95a153912c..babba61eb335 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -837,6 +837,7 @@ static void iso_sock_disconn(struct sock *sk) sk->sk_state = BT_DISCONN; iso_conn_lock(iso_pi(sk)->conn); hci_conn_drop(iso_pi(sk)->conn->hcon); + iso_pi(sk)->conn->hcon->iso_data = NULL; iso_pi(sk)->conn->hcon = NULL; iso_conn_unlock(iso_pi(sk)->conn); } From d0a7b48ad0921bd88effaee10bf970ab1d5d0ddd Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Tue, 21 Jul 2026 22:36:07 +0800 Subject: [PATCH 375/456] Bluetooth: mgmt: fix UAF in pair command cancellation The pairing completion and authentication failure callbacks look up the pending MGMT_OP_PAIR_DEVICE command by walking hdev->mgmt_pending. The lookup returned a command that was still linked on the shared pending list, without keeping mgmt_pending_lock held for the later dereference and removal. A concurrent MGMT_OP_CANCEL_PAIR_DEVICE request can remove and free the same pending command before the callback uses it. The reverse race is also possible when cancel_pair_device() gets a command from pending_find() and a callback removes it before the cancel path dereferences it. This can lead to a use-after-free and a second list_del(). Make the pairing lookup helpers transfer ownership of the pending command by removing it from hdev->mgmt_pending while holding mgmt_pending_lock. The callbacks and cancel path then complete the command and free it directly, so racing paths cannot find or free the same command again. Take a temporary hci_conn reference in cancel_pair_device() because the command completion drops the reference stored in the pending command. Fixes: e9a416b5ce0c ("Bluetooth: Add mgmt_pair_device command") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zihan Xi Reviewed-by: Ren Wei Reported-by: Vega Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 64 +++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 1db10e0f617f..4fd37ac79986 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -3514,11 +3514,13 @@ static int set_io_capability(struct sock *sk, struct hci_dev *hdev, void *data, NULL, 0); } -static struct mgmt_pending_cmd *find_pairing(struct hci_conn *conn) +static struct mgmt_pending_cmd *remove_pairing(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; struct mgmt_pending_cmd *cmd; + mutex_lock(&hdev->mgmt_pending_lock); + list_for_each_entry(cmd, &hdev->mgmt_pending, list) { if (cmd->opcode != MGMT_OP_PAIR_DEVICE) continue; @@ -3526,9 +3528,39 @@ static struct mgmt_pending_cmd *find_pairing(struct hci_conn *conn) if (cmd->user_data != conn) continue; + list_del(&cmd->list); + mutex_unlock(&hdev->mgmt_pending_lock); return cmd; } + mutex_unlock(&hdev->mgmt_pending_lock); + + return NULL; +} + +static struct mgmt_pending_cmd *remove_pairing_by_addr(struct hci_dev *hdev, + bdaddr_t *bdaddr) +{ + struct mgmt_pending_cmd *cmd; + struct hci_conn *conn; + + mutex_lock(&hdev->mgmt_pending_lock); + + list_for_each_entry(cmd, &hdev->mgmt_pending, list) { + if (cmd->opcode != MGMT_OP_PAIR_DEVICE) + continue; + + conn = cmd->user_data; + if (bacmp(bdaddr, &conn->dst) != 0) + continue; + + list_del(&cmd->list); + mutex_unlock(&hdev->mgmt_pending_lock); + return cmd; + } + + mutex_unlock(&hdev->mgmt_pending_lock); + return NULL; } @@ -3566,10 +3598,10 @@ void mgmt_smp_complete(struct hci_conn *conn, bool complete) u8 status = complete ? MGMT_STATUS_SUCCESS : MGMT_STATUS_FAILED; struct mgmt_pending_cmd *cmd; - cmd = find_pairing(conn); + cmd = remove_pairing(conn); if (cmd) { cmd->cmd_complete(cmd, status); - mgmt_pending_remove(cmd); + mgmt_pending_free(cmd); } } @@ -3579,14 +3611,14 @@ static void pairing_complete_cb(struct hci_conn *conn, u8 status) BT_DBG("status %u", status); - cmd = find_pairing(conn); + cmd = remove_pairing(conn); if (!cmd) { BT_DBG("Unable to find a pending command"); return; } cmd->cmd_complete(cmd, mgmt_status(status)); - mgmt_pending_remove(cmd); + mgmt_pending_free(cmd); } static void le_pairing_complete_cb(struct hci_conn *conn, u8 status) @@ -3598,14 +3630,14 @@ static void le_pairing_complete_cb(struct hci_conn *conn, u8 status) if (!status) return; - cmd = find_pairing(conn); + cmd = remove_pairing(conn); if (!cmd) { BT_DBG("Unable to find a pending command"); return; } cmd->cmd_complete(cmd, mgmt_status(status)); - mgmt_pending_remove(cmd); + mgmt_pending_free(cmd); } static int pair_device(struct sock *sk, struct hci_dev *hdev, void *data, @@ -3762,23 +3794,17 @@ static int cancel_pair_device(struct sock *sk, struct hci_dev *hdev, void *data, goto unlock; } - cmd = pending_find(MGMT_OP_PAIR_DEVICE, hdev); + cmd = remove_pairing_by_addr(hdev, &addr->bdaddr); if (!cmd) { err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, MGMT_STATUS_INVALID_PARAMS); goto unlock; } - conn = cmd->user_data; - - if (bacmp(&addr->bdaddr, &conn->dst) != 0) { - err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, - MGMT_STATUS_INVALID_PARAMS); - goto unlock; - } + conn = hci_conn_get(cmd->user_data); cmd->cmd_complete(cmd, MGMT_STATUS_CANCELLED); - mgmt_pending_remove(cmd); + mgmt_pending_free(cmd); err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_CANCEL_PAIR_DEVICE, 0, addr, sizeof(*addr)); @@ -3796,6 +3822,8 @@ static int cancel_pair_device(struct sock *sk, struct hci_dev *hdev, void *data, if (conn->conn_reason == CONN_REASON_PAIR_DEVICE) hci_abort_conn(conn, HCI_ERROR_REMOTE_USER_TERM); + hci_conn_put(conn); + unlock: hci_dev_unlock(hdev); return err; @@ -10137,14 +10165,14 @@ void mgmt_auth_failed(struct hci_conn *conn, u8 hci_status) ev.addr.type = link_to_bdaddr(conn->type, conn->dst_type); ev.status = status; - cmd = find_pairing(conn); + cmd = remove_pairing(conn); mgmt_event(MGMT_EV_AUTH_FAILED, conn->hdev, &ev, sizeof(ev), cmd ? cmd->sk : NULL); if (cmd) { cmd->cmd_complete(cmd, status); - mgmt_pending_remove(cmd); + mgmt_pending_free(cmd); } } From 8f2f62855a41d1730fb9e8122912bd2c8d6bed5d Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Fri, 24 Jul 2026 00:43:46 +0800 Subject: [PATCH 376/456] Bluetooth: mgmt: fix pending command UAF in EIR updates MGMT_OP_SET_LOCAL_NAME is handled asynchronously on powered controllers and can run set_name_sync(). When the controller is BR/EDR capable, set_name_sync() updates the local name and then rebuilds EIR data through eir_create(). The EIR builder walks hdev->uuids, but the UUID list can be changed and entries can be freed by MGMT_OP_ADD_UUID and MGMT_OP_REMOVE_UUID. pending_eir_or_class() is meant to serialize management commands that can change EIR or the class of device, but it did not include MGMT_OP_SET_LOCAL_NAME. In addition, it walked hdev->mgmt_pending without hdev->mgmt_pending_lock even though pending commands are added and removed under that mutex. A racing command completion can therefore remove and free a pending command while pending_eir_or_class() is still inspecting it, leading to a use-after-free in the pending-command list or allowing a local name update to rebuild EIR while UUID entries are being removed. Take hdev->mgmt_pending_lock while scanning hdev->mgmt_pending and treat MGMT_OP_SET_LOCAL_NAME as an EIR/class-affecting pending command on the powered asynchronous path. Check for a conflicting pending command before copying the new short name so a rejected SET_LOCAL_NAME request does not modify hdev->short_name. Fixes: 6fe26f694c82 ("Bluetooth: MGMT: Protect mgmt_pending list with its own lock") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zihan Xi Signed-off-by: Ren Wei Reported-by: Vega Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 4fd37ac79986..167d75e34526 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -2696,18 +2696,28 @@ static int mgmt_hci_cmd_sync(struct sock *sk, struct hci_dev *hdev, static bool pending_eir_or_class(struct hci_dev *hdev) { struct mgmt_pending_cmd *cmd; + bool pending = false; + + mutex_lock(&hdev->mgmt_pending_lock); list_for_each_entry(cmd, &hdev->mgmt_pending, list) { switch (cmd->opcode) { case MGMT_OP_ADD_UUID: case MGMT_OP_REMOVE_UUID: case MGMT_OP_SET_DEV_CLASS: + case MGMT_OP_SET_LOCAL_NAME: case MGMT_OP_SET_POWERED: - return true; + pending = true; + break; } + + if (pending) + break; } - return false; + mutex_unlock(&hdev->mgmt_pending_lock); + + return pending; } static const u8 bluetooth_base_uuid[] = { @@ -4071,6 +4081,12 @@ static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data, goto failed; } + if (hdev_is_powered(hdev) && pending_eir_or_class(hdev)) { + err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_LOCAL_NAME, + MGMT_STATUS_BUSY); + goto failed; + } + memcpy(hdev->short_name, cp->short_name, sizeof(hdev->short_name)); if (!hdev_is_powered(hdev)) { From 47778d2c2087b5d192398f6fddf692d16a5431cf Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Thu, 23 Jul 2026 12:28:06 +0900 Subject: [PATCH 377/456] Bluetooth: HIDP: reject frames without a transaction header hidp_recv_ctrl_frame() and hidp_recv_intr_frame() read skb->data[0] before checking that the L2CAP SDU contains a transaction header. A connected HIDP peer can send an empty basic-mode SDU and make both paths use an uninitialized byte from skb tailroom. KMSAN reports the use in hidp_session_run(), with the uninitialized value originating in __alloc_skb() through vhci_write(). The control path produces two reports and the interrupt path produces one. The byte can also be controlled by a malformed lower-layer packet. If an HCI ACL packet contains an L2CAP PDU with a declared zero-length payload followed by an extra 0x15 byte, l2cap_recv_acldata() reduces skb->len to the declared PDU length before dispatch. The current HIDP path nevertheless consumes the extra byte as HIDP_TRANS_HID_CONTROL | HIDP_CTRL_VIRTUAL_CABLE_UNPLUG and terminates the HIDP session. With this change, the same packet is discarded and a subsequent feature report request succeeds. Pull the transaction header with skb_pull_data() and discard frames that do not contain it. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Sangho Lee Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hidp/core.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 0e24c5e2955e..194208d03d18 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -560,16 +560,18 @@ static int hidp_process_data(struct hidp_session *session, struct sk_buff *skb, static void hidp_recv_ctrl_frame(struct hidp_session *session, struct sk_buff *skb) { - unsigned char hdr, type, param; + unsigned char type, param; + u8 *hdr; int free_skb = 1; BT_DBG("session %p skb %p len %u", session, skb, skb->len); - hdr = skb->data[0]; - skb_pull(skb, 1); + hdr = skb_pull_data(skb, 1); + if (!hdr) + goto free; - type = hdr & HIDP_HEADER_TRANS_MASK; - param = hdr & HIDP_HEADER_PARAM_MASK; + type = *hdr & HIDP_HEADER_TRANS_MASK; + param = *hdr & HIDP_HEADER_PARAM_MASK; switch (type) { case HIDP_TRANS_HANDSHAKE: @@ -590,6 +592,7 @@ static void hidp_recv_ctrl_frame(struct hidp_session *session, break; } +free: if (free_skb) kfree_skb(skb); } @@ -597,14 +600,15 @@ static void hidp_recv_ctrl_frame(struct hidp_session *session, static void hidp_recv_intr_frame(struct hidp_session *session, struct sk_buff *skb) { - unsigned char hdr; + u8 *hdr; BT_DBG("session %p skb %p len %u", session, skb, skb->len); - hdr = skb->data[0]; - skb_pull(skb, 1); + hdr = skb_pull_data(skb, 1); + if (!hdr) + goto free; - if (hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) { + if (*hdr == (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) { hidp_set_timer(session); if (session->input) @@ -616,9 +620,10 @@ static void hidp_recv_intr_frame(struct hidp_session *session, BT_DBG("report len %d", skb->len); } } else { - BT_DBG("Unsupported protocol header 0x%02x", hdr); + BT_DBG("Unsupported protocol header 0x%02x", *hdr); } +free: kfree_skb(skb); } From 34f53d27b81a16a02828c8fdfa4e02badc326f17 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Thu, 23 Jul 2026 12:28:07 +0900 Subject: [PATCH 378/456] Bluetooth: HIDP: validate numbered report payloads When hidp_get_raw_report() waits for a numbered report, hidp_process_data() compares the expected report number with skb->data[0]. A connected HIDP peer can reply with only a DATA transaction header, leaving the skb empty after the header is removed. KMSAN reports an uninitialized-value use in hidp_session_run(), with the value originating in __alloc_skb() through vhci_write(). The transaction header checks remove the empty-frame reports, but this report remains until the payload check is added. The comparison can also consume a peer-controlled byte beyond the declared L2CAP PDU. A DATA | FEATURE response followed by an extra 0x01 byte made the current code accept that byte as report ID 1 and complete HIDIOCGFEATURE with a zero-byte result. With this change the malformed response is rejected with -EIO, while a subsequent valid response still succeeds. Require a payload byte before comparing a numbered report ID. Unnumbered reports continue to accept an empty payload. Fixes: 0ff1731a1ae5 ("HID: bt: Add support for hidraw HIDIOCGFEATURE and HIDIOCSFEATURE") Cc: stable@vger.kernel.org Signed-off-by: Sangho Lee Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hidp/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 194208d03d18..f5bdf9f1ca63 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -543,9 +543,10 @@ static int hidp_process_data(struct hidp_session *session, struct sk_buff *skb, } if (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags) && - param == session->waiting_report_type) { + param == session->waiting_report_type) { if (session->waiting_report_number < 0 || - session->waiting_report_number == skb->data[0]) { + (skb->len && + session->waiting_report_number == skb->data[0])) { /* hidp_get_raw_report() is waiting on this report. */ session->report_return = skb; done_with_skb = 0; From c4740e7f23ff9a8210198d8b4703259e21b9f69d Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Thu, 23 Jul 2026 14:48:45 +0800 Subject: [PATCH 379/456] Bluetooth: L2CAP: fix UAF in l2cap_le_connect_rsp l2cap_le_connect_rsp() obtains a channel via __l2cap_get_chan_by_ident() but neither holds a reference nor uses l2cap_chan_hold_unless_zero() before locking and operating on it. A concurrent l2cap_chan_del() triggered by a remote disconnect can free the channel between the lookup and l2cap_chan_lock(), causing a use-after-free. The BR/EDR counterpart l2cap_connect_rsp() and the sibling handler l2cap_le_command_rej() already use l2cap_chan_hold_unless_zero() to safely hold a reference, but l2cap_le_connect_rsp() was left unprotected. Fix by adding l2cap_chan_hold_unless_zero() after the ident lookup and l2cap_chan_put() on the exit path, consistent with other L2CAP response handlers. Fixes: f1496dee9cbd ("Bluetooth: Add initial code for LE L2CAP Connect Request") Assisted-by: Claude:deepseek-v4-pro Signed-off-by: Jiale Yao Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 538ae9aa3479..1156aba4e83c 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -4820,6 +4820,10 @@ static int l2cap_le_connect_rsp(struct l2cap_conn *conn, if (!chan) return -EBADSLT; + chan = l2cap_chan_hold_unless_zero(chan); + if (!chan) + return -EBADSLT; + err = 0; l2cap_chan_lock(chan); @@ -4865,6 +4869,7 @@ static int l2cap_le_connect_rsp(struct l2cap_conn *conn, } l2cap_chan_unlock(chan); + l2cap_chan_put(chan); return err; } From b230e5bf501c5edaf2eb0991cb862ac142031d4b Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Wed, 22 Jul 2026 17:26:14 +0800 Subject: [PATCH 380/456] Bluetooth: RFCOMM: validate skb length in rfcomm_recv_frame rfcomm_recv_frame() casts skb->data to struct rfcomm_hdr and dereferences hdr->addr and hdr->ctrl without validating skb->len first. A truncated frame with skb->len less than the minimum header size causes an out-of-bounds read of uninitialized memory. Additionally, a zero-length frame causes skb->len-- to underflow to UINT_MAX, making skb_tail_pointer() read far past the buffer. Commit 23882b828c3c ("Bluetooth: RFCOMM: validate skb length in MCC handlers") fixed the same class of missing-length-check bugs in the MCC sub-handlers, but the top-level rfcomm_recv_frame() was left unfixed. KMSAN reports: BUG: KMSAN: uninit-value in rfcomm_run ... Uninit was created at: __alloc_skb+0x474/0xb60 vhci_write+0xe9/0x870 Fix this by rejecting frames smaller than sizeof(struct rfcomm_hdr) + 1 (the minimum frame must have a 3-byte header and a 1-byte FCS). Signed-off-by: Jiale Yao Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 75f7512dec54..2e8c080b4d9e 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1795,6 +1795,11 @@ static struct rfcomm_session *rfcomm_recv_frame(struct rfcomm_session *s, return s; } + if (skb->len < sizeof(*hdr) + 1) { + kfree_skb(skb); + return s; + } + dlci = __get_dlci(hdr->addr); type = __get_type(hdr->ctrl); From cdc36db204ffd97b947d64374cf23a210dc74777 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Thu, 23 Jul 2026 23:34:40 +0800 Subject: [PATCH 381/456] Bluetooth: hci_sync: Fix advertising data UAFs hci_find_adv_instance() returns an adv_info pointer that is valid only while hdev->lock is held. The advertising command-sync paths perform instance lookups without that lock and, in some cases, retain the pointer while waiting for a controller response. An advertising termination event can therefore interleave as follows: hci_cmd_sync_work hci_rx_work hci_find_adv_instance() __hci_cmd_sync_status() wait for controller reply hci_dev_lock() hci_remove_adv_instance() kfree(adv) adv->scan_rsp_changed = false KASAN reported: BUG: KASAN: slab-use-after-free in hci_set_ext_scan_rsp_data_sync+0x2e1/0x300 Write of size 1 at addr ffff88810a45d21d by task kworker/u17:0/88 Workqueue: hci0 hci_cmd_sync_work Call Trace: hci_set_ext_scan_rsp_data_sync+0x2e1/0x300 hci_schedule_adv_instance_sync+0x390/0x4c0 hci_cmd_sync_work+0x173/0x300 Allocated by task 87: hci_add_adv_instance+0x538/0xac0 add_advertising+0x885/0x1160 Freed by task 89: kfree+0x131/0x3c0 hci_remove_adv_instance+0x1d8/0x3b0 hci_le_ext_adv_term_evt+0x17b/0x730 Protect the instance lookup and payload construction in the extended advertising, scan response, and periodic advertising data paths. Snapshot the advertising parameters under hdev->lock, but release the lock before waiting for the controller. Clear advertising-data dirty bits before issuing their commands and restore them after a failure using a fresh lookup. Likewise, update the reported transmit power through a fresh lookup after the parameter command completes. No adv_info pointer then survives an HCI command wait. Fixes: cba6b758711c ("Bluetooth: hci_sync: Make use of hci_cmd_sync_queue set 2") Cc: stable@vger.kernel.org Suggested-by: Luiz Augusto von Dentz Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 135 ++++++++++++++++++++++++++++----------- 1 file changed, 99 insertions(+), 36 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index c0b1fc293b49..aa3d53818812 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -1233,10 +1233,11 @@ static int hci_set_adv_set_random_addr_sync(struct hci_dev *hdev, u8 instance, } static int -hci_set_ext_adv_params_sync(struct hci_dev *hdev, struct adv_info *adv, +hci_set_ext_adv_params_sync(struct hci_dev *hdev, u8 instance, const struct hci_cp_le_set_ext_adv_params *cp, struct hci_rp_le_set_ext_adv_params *rp) { + struct adv_info *adv; struct sk_buff *skb; skb = __hci_cmd_sync(hdev, HCI_OP_LE_SET_EXT_ADV_PARAMS, sizeof(*cp), @@ -1264,11 +1265,15 @@ hci_set_ext_adv_params_sync(struct hci_dev *hdev, struct adv_info *adv, if (!rp->status) { hdev->adv_addr_type = cp->own_addr_type; - if (!cp->handle) { + if (!instance) { /* Store in hdev for instance 0 */ hdev->adv_tx_power = rp->tx_power; - } else if (adv) { - adv->tx_power = rp->tx_power; + } else { + hci_dev_lock(hdev); + adv = hci_find_adv_instance(hdev, instance); + if (adv) + adv->tx_power = rp->tx_power; + hci_dev_unlock(hdev); } } @@ -1284,9 +1289,13 @@ static int hci_set_ext_adv_data_sync(struct hci_dev *hdev, u8 instance) int err; if (instance) { + hci_dev_lock(hdev); + adv = hci_find_adv_instance(hdev, instance); - if (!adv || !adv->adv_data_changed) + if (!adv || !adv->adv_data_changed) { + hci_dev_unlock(hdev); return 0; + } } len = eir_create_adv_data(hdev, instance, pdu->data, @@ -1297,16 +1306,27 @@ static int hci_set_ext_adv_data_sync(struct hci_dev *hdev, u8 instance) pdu->operation = LE_SET_ADV_DATA_OP_COMPLETE; pdu->frag_pref = LE_SET_ADV_DATA_NO_FRAG; + if (adv) { + adv->adv_data_changed = false; + hci_dev_unlock(hdev); + } + err = __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_EXT_ADV_DATA, struct_size(pdu, data, len), pdu, HCI_CMD_TIMEOUT); - if (err) - return err; + if (err) { + if (instance) { + hci_dev_lock(hdev); + adv = hci_find_adv_instance(hdev, instance); + if (adv) + adv->adv_data_changed = true; + hci_dev_unlock(hdev); + } - /* Update data if the command succeed */ - if (adv) { - adv->adv_data_changed = false; - } else { + return err; + } + + if (!instance) { memcpy(hdev->adv_data, pdu->data, len); hdev->adv_data_len = len; } @@ -1360,22 +1380,22 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) struct adv_info *adv; bool secondary_adv; - if (instance > 0) { - adv = hci_find_adv_instance(hdev, instance); - if (!adv) - return -EINVAL; - } else { - adv = NULL; - } - /* Updating parameters of an active instance will return a - * Command Disallowed error, so we must first disable the - * instance if it is active. + * Command Disallowed error, so disable it before taking a snapshot. */ - if (adv) { + if (instance > 0) { err = hci_disable_ext_adv_instance_sync(hdev, instance); if (err) return err; + + hci_dev_lock(hdev); + adv = hci_find_adv_instance(hdev, instance); + if (!adv) { + hci_dev_unlock(hdev); + return -EINVAL; + } + } else { + adv = NULL; } flags = hci_adv_instance_flags(hdev, instance); @@ -1386,8 +1406,11 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) connectable = (flags & MGMT_ADV_FLAG_CONNECTABLE) || mgmt_get_connectable(hdev); - if (!is_advertising_allowed(hdev, connectable)) + if (!is_advertising_allowed(hdev, connectable)) { + if (instance) + hci_dev_unlock(hdev); return -EPERM; + } /* Set require_privacy to true only when non-connectable * advertising is used and it is not periodic. @@ -1398,8 +1421,11 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) err = hci_get_random_address(hdev, require_privacy, adv_use_rpa(hdev, flags), adv, &own_addr_type, &random_addr); - if (err < 0) + if (err < 0) { + if (instance) + hci_dev_unlock(hdev); return err; + } memset(&cp, 0, sizeof(cp)); @@ -1450,6 +1476,9 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) cp.channel_map = hdev->le_adv_channel_map; cp.handle = adv ? adv->handle : instance; + if (instance) + hci_dev_unlock(hdev); + if (flags & MGMT_ADV_FLAG_SEC_2M) { cp.primary_phy = HCI_ADV_PHY_1M; cp.secondary_phy = HCI_ADV_PHY_2M; @@ -1462,12 +1491,12 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) cp.secondary_phy = HCI_ADV_PHY_1M; } - err = hci_set_ext_adv_params_sync(hdev, adv, &cp, &rp); + err = hci_set_ext_adv_params_sync(hdev, instance, &cp, &rp); if (err) return err; /* Update adv data as tx power is known now */ - err = hci_set_ext_adv_data_sync(hdev, cp.handle); + err = hci_set_ext_adv_data_sync(hdev, instance); if (err) return err; @@ -1475,9 +1504,14 @@ int hci_setup_ext_adv_instance_sync(struct hci_dev *hdev, u8 instance) own_addr_type == ADDR_LE_DEV_RANDOM_RESOLVED) && bacmp(&random_addr, BDADDR_ANY)) { /* Check if random address need to be updated */ - if (adv) { - if (!bacmp(&random_addr, &adv->random_addr)) + if (instance) { + hci_dev_lock(hdev); + adv = hci_find_adv_instance(hdev, instance); + if (!adv || !bacmp(&random_addr, &adv->random_addr)) { + hci_dev_unlock(hdev); return 0; + } + hci_dev_unlock(hdev); } else { if (!bacmp(&random_addr, &hdev->random_addr)) return 0; @@ -1499,9 +1533,13 @@ static int hci_set_ext_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance) int err; if (instance) { + hci_dev_lock(hdev); + adv = hci_find_adv_instance(hdev, instance); - if (!adv || !adv->scan_rsp_changed) + if (!adv || !adv->scan_rsp_changed) { + hci_dev_unlock(hdev); return 0; + } } len = eir_create_scan_rsp(hdev, instance, pdu->data); @@ -1511,15 +1549,27 @@ static int hci_set_ext_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance) pdu->operation = LE_SET_ADV_DATA_OP_COMPLETE; pdu->frag_pref = LE_SET_ADV_DATA_NO_FRAG; + if (adv) { + adv->scan_rsp_changed = false; + hci_dev_unlock(hdev); + } + err = __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_EXT_SCAN_RSP_DATA, struct_size(pdu, data, len), pdu, HCI_CMD_TIMEOUT); - if (err) - return err; + if (err) { + if (instance) { + hci_dev_lock(hdev); + adv = hci_find_adv_instance(hdev, instance); + if (adv) + adv->scan_rsp_changed = true; + hci_dev_unlock(hdev); + } - if (adv) { - adv->scan_rsp_changed = false; - } else { + return err; + } + + if (!instance) { memcpy(hdev->scan_rsp_data, pdu->data, len); hdev->scan_rsp_data_len = len; } @@ -1534,8 +1584,14 @@ static int __hci_set_scan_rsp_data_sync(struct hci_dev *hdev, u8 instance) memset(&cp, 0, sizeof(cp)); + if (instance) + hci_dev_lock(hdev); + len = eir_create_scan_rsp(hdev, instance, cp.data); + if (instance) + hci_dev_unlock(hdev); + if (hdev->scan_rsp_data_len == len && !memcmp(cp.data, hdev->scan_rsp_data, len)) return 0; @@ -1670,9 +1726,13 @@ static int hci_set_per_adv_data_sync(struct hci_dev *hdev, u8 instance) struct adv_info *adv = NULL; if (instance) { + hci_dev_lock(hdev); + adv = hci_find_adv_instance(hdev, instance); - if (!adv || !adv->periodic) + if (!adv || !adv->periodic) { + hci_dev_unlock(hdev); return 0; + } } len = eir_create_per_adv_data(hdev, instance, pdu->data); @@ -1681,6 +1741,9 @@ static int hci_set_per_adv_data_sync(struct hci_dev *hdev, u8 instance) pdu->handle = adv ? adv->handle : instance; pdu->operation = LE_SET_ADV_DATA_OP_COMPLETE; + if (adv) + hci_dev_unlock(hdev); + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_PER_ADV_DATA, struct_size(pdu, data, len), pdu, HCI_CMD_TIMEOUT); @@ -6523,7 +6586,7 @@ static int hci_le_ext_directed_advertising_sync(struct hci_dev *hdev, if (err) return err; - err = hci_set_ext_adv_params_sync(hdev, NULL, &cp, &rp); + err = hci_set_ext_adv_params_sync(hdev, 0, &cp, &rp); if (err) return err; From 0786469ee242952008628ed0e2d386098e2065ab Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:24 +0300 Subject: [PATCH 382/456] Bluetooth: ISO: fix CONNECTED -> CLOSED transition on shutdown/release Commit d57e506f6a1e ("Bluetooth: ISO: clear iso_data always when detaching conn from hcon") merged a version of the UAF fix that breaks releasing connected ISO sockets. Since hci_conn::iso_data is set to NULL, iso_chan_del() won't be called when the hci_conn disconnects, and the ISO socket does not emit POLLHUP correctly. Fix by retaining full hci_conn <-> iso_conn association while in BT_DISCONNECT state, so that local disconnect via shutdown() follows similar ISO socket code path as remote disconnect. Use a separate flag to track whether hci_conn_drop() is needed, instead of setting iso_conn::hcon = NULL In iso_sock_ready(), disallow disconnecting socket going BT_CONNECTED, in case hcon connects while its drop is pending. Fixes: d57e506f6a1e ("Bluetooth: ISO: clear iso_data always when detaching conn from hcon") Fixes: fbdc4bc47268 ("Bluetooth: ISO: Use defer setup to separate PA sync and BIG sync") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index babba61eb335..299a9336b5e1 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -24,8 +24,14 @@ static struct bt_sock_list iso_sk_list = { }; /* ---- ISO connections ---- */ +enum { + ISO_CONN_DROPPED, + __ISO_CONN_NUM_FLAGS +}; + struct iso_conn { struct hci_conn *hcon; + DECLARE_BITMAP(flags, __ISO_CONN_NUM_FLAGS); /* @lock: spinlock protecting changes to iso_conn fields */ spinlock_t lock; @@ -107,7 +113,8 @@ static void iso_conn_free(struct kref *ref) if (conn->hcon) { conn->hcon->iso_data = NULL; - hci_conn_drop(conn->hcon); + if (!test_and_set_bit(ISO_CONN_DROPPED, conn->flags)) + hci_conn_drop(conn->hcon); } /* Ensure no more work items will run since hci_conn has been dropped */ @@ -306,6 +313,7 @@ static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, iso_pi(sk)->conn = conn; conn->sk = sk; + clear_bit(ISO_CONN_DROPPED, conn->flags); if (parent) bt_accept_enqueue(parent, sk, true); @@ -835,11 +843,8 @@ static void iso_sock_disconn(struct sock *sk) } sk->sk_state = BT_DISCONN; - iso_conn_lock(iso_pi(sk)->conn); - hci_conn_drop(iso_pi(sk)->conn->hcon); - iso_pi(sk)->conn->hcon->iso_data = NULL; - iso_pi(sk)->conn->hcon = NULL; - iso_conn_unlock(iso_pi(sk)->conn); + if (!test_and_set_bit(ISO_CONN_DROPPED, iso_pi(sk)->conn->flags)) + hci_conn_drop(iso_pi(sk)->conn->hcon); } static void __iso_sock_close(struct sock *sk) @@ -2042,9 +2047,18 @@ static void iso_sock_ready(struct sock *sk) return; lock_sock(sk); + + switch (sk->sk_state) { + case BT_DISCONN: + case BT_CLOSED: + release_sock(sk); + return; + } + iso_sock_clear_timer(sk); sk->sk_state = BT_CONNECTED; sk->sk_state_change(sk); + release_sock(sk); } From 89cf154d7c18e6e94a3da83051f3cf2bac317ae2 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:25 +0300 Subject: [PATCH 383/456] Bluetooth: ISO: lock sk in iso_sock_getname Accessing iso_pi(sk)->conn requires lock_sock, which is not held here. Fix by adding the lock/release. Fixes: 2df108c227b2 ("Bluetooth: ISO: Fix using BT_SK_PA_SYNC to detect BIS sockets") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 299a9336b5e1..dbb8f43052f0 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1472,6 +1472,8 @@ static int iso_sock_getname(struct socket *sock, struct sockaddr *addr, BT_DBG("sock %p, sk %p", sock, sk); + lock_sock(sk); + addr->sa_family = AF_BLUETOOTH; if (peer) { @@ -1493,6 +1495,8 @@ static int iso_sock_getname(struct socket *sock, struct sockaddr *addr, sa->iso_bdaddr_type = iso_pi(sk)->src_type; } + release_sock(sk); + return len; } From 4311fd6f429065a8ba208660360a895627a00cf3 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:26 +0300 Subject: [PATCH 384/456] Bluetooth: ISO: lock sk in iso_connect_ind Accessing iso_pi(sk)->conn requires lock_sock, which is not taken in the "ev3" part of iso_connect_ind. It may also be NULL if socket has transitioned away from the LISTEN/CONNECT states before locking. Fix by adding lock/release. Recheck hcon is valid after lock acquire where needed. Fixes: 168d9bf9c7f0 ("Bluetooth: ISO: Reassemble PA data for bcast sink") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index dbb8f43052f0..651661833966 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -2369,7 +2369,7 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) lock_sock(sk); - hcon = iso_pi(sk)->conn->hcon; + hcon = iso_pi(sk)->conn ? iso_pi(sk)->conn->hcon : NULL; iso_pi(sk)->qos.bcast.encryption = ev2->encryption; if (ev2->num_bis < iso_pi(sk)->bc_num_bis) @@ -2409,9 +2409,11 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) if (!sk) goto done; - hcon = iso_pi(sk)->conn->hcon; + lock_sock(sk); + + hcon = iso_pi(sk)->conn ? iso_pi(sk)->conn->hcon : NULL; if (!hcon) - goto done; + goto release3; if (ev3->data_status == LE_PA_DATA_TRUNCATED) { /* The controller was unable to retrieve PA data. */ @@ -2419,12 +2421,12 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) HCI_MAX_PER_AD_TOT_LEN); hcon->le_per_adv_data_len = 0; hcon->le_per_adv_data_offset = 0; - goto done; + goto release3; } if (hcon->le_per_adv_data_offset + ev3->length > HCI_MAX_PER_AD_TOT_LEN) - goto done; + goto release3; memcpy(hcon->le_per_adv_data + hcon->le_per_adv_data_offset, ev3->data, ev3->length); @@ -2443,18 +2445,19 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) &base_len); if (!base || base_len > BASE_MAX_LENGTH) - goto done; + goto release3; - lock_sock(sk); memcpy(iso_pi(sk)->base, base, base_len); iso_pi(sk)->base_len = base_len; - release_sock(sk); } else { /* This is a PA data fragment. Keep pa_data_len set to 0 * until all data has been reassembled. */ hcon->le_per_adv_data_len = 0; } + +release3: + release_sock(sk); } else { sk = iso_get_sock(hdev, &hdev->bdaddr, BDADDR_ANY, BT_LISTEN, iso_match_dst, BDADDR_ANY); From e9cb51813d79fc9aae4a2098aab3ab6ebd7fb6c8 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:27 +0300 Subject: [PATCH 385/456] Bluetooth: ISO: fix timeout vs sync_timeout typo in check_bcast_qos In iso.c check_bcast_qos(), missing bcast.timeout is not set to its default value, and appears typoed as bcast.sync_timeout. Fix the typo. Fixes: b37cab587aa3 ("Bluetooth: ISO: Don't reject BT_ISO_QOS if parameters are unset") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 651661833966..e51253e5c161 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1796,7 +1796,7 @@ static bool check_bcast_qos(struct bt_iso_qos *qos) return false; if (!qos->bcast.timeout) - qos->bcast.sync_timeout = BT_ISO_SYNC_TIMEOUT; + qos->bcast.timeout = BT_ISO_SYNC_TIMEOUT; if (qos->bcast.timeout < 0x000a || qos->bcast.timeout > 0x4000) return false; From 4e20192d46a685d73e590a60a4a2419a0a8afcbf Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:28 +0300 Subject: [PATCH 386/456] Bluetooth: ISO: validate sockaddr_iso first in iso_sock_rebind_bis() iso_sock_rebind_bis() updates socket iso_pi(sk)->bc_num_bis before validating the BIS values, so it's possible to end up with bc_num_bis inconsistent. Assign to iso_pi(sk)->bc_num_bis only after validation. Fixes: 80837140c1f2 ("Bluetooth: ISO: Allow binding a PA sync socket") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index e51253e5c161..5de4a2f886eb 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1039,15 +1039,15 @@ static int iso_sock_rebind_bis(struct sock *sk, struct sockaddr_iso *sa, goto done; } - iso_pi(sk)->bc_num_bis = sa->iso_bc->bc_num_bis; - - for (int i = 0; i < iso_pi(sk)->bc_num_bis; i++) + for (int i = 0; i < sa->iso_bc->bc_num_bis; i++) if (sa->iso_bc->bc_bis[i] < 0x01 || sa->iso_bc->bc_bis[i] > 0x1f) { err = -EINVAL; goto done; } + iso_pi(sk)->bc_num_bis = sa->iso_bc->bc_num_bis; + memcpy(iso_pi(sk)->bc_bis, sa->iso_bc->bc_bis, iso_pi(sk)->bc_num_bis); From 0d255e63fcf3f13a570d7ac11678fa1164ac015c Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:29 +0300 Subject: [PATCH 387/456] Bluetooth: ISO: hold sk properly in iso_conn_ready sk deref in iso_conn_ready must be done either under conn->lock, or holding a refcount, to avoid concurrent close. conn->sk is currently accessed without either: [Task 1] [Task 2] iso_sock_release iso_conn_ready sk = conn->sk lock_sock(sk) conn->sk = NULL lock_sock(sk) release_sock(sk) iso_sock_kill(sk) UAF on sk deref Fix possible UAF by holding sk refcount in iso_conn_ready(). Also recheck after lock_sock that the socket is still valid. Adjust locking so conn->sk is cleared only under lock_sock. Fixes: 27c24fda62b60 ("Bluetooth: switch to lock_sock in SCO") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 5de4a2f886eb..80a58275891d 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -805,11 +805,13 @@ static void iso_sock_kill(struct sock *sk) BT_DBG("sk %p state %d", sk, sk->sk_state); /* Sock is dead, so set conn->sk to NULL to avoid possible UAF */ + lock_sock(sk); if (iso_pi(sk)->conn) { iso_conn_lock(iso_pi(sk)->conn); iso_pi(sk)->conn->sk = NULL; iso_conn_unlock(iso_pi(sk)->conn); } + release_sock(sk); /* Kill poor orphan */ bt_sock_unlink(&iso_sk_list, sk); @@ -2047,23 +2049,17 @@ static void iso_sock_ready(struct sock *sk) { BT_DBG("sk %p", sk); - if (!sk) - return; - - lock_sock(sk); + lockdep_assert(lockdep_sock_is_held(sk)); switch (sk->sk_state) { case BT_DISCONN: case BT_CLOSED: - release_sock(sk); return; } iso_sock_clear_timer(sk); sk->sk_state = BT_CONNECTED; sk->sk_state_change(sk); - - release_sock(sk); } static bool iso_match_big(struct sock *sk, void *data) @@ -2093,7 +2089,7 @@ static bool iso_match_dst(struct sock *sk, void *data) static void iso_conn_ready(struct iso_conn *conn) { struct sock *parent = NULL; - struct sock *sk = conn->sk; + struct sock *sk; struct hci_ev_le_big_sync_established *ev = NULL; struct hci_ev_le_pa_sync_established *ev2 = NULL; struct hci_ev_le_per_adv_report *ev3 = NULL; @@ -2102,7 +2098,22 @@ static void iso_conn_ready(struct iso_conn *conn) BT_DBG("conn %p", conn); + iso_conn_lock(conn); + sk = iso_sock_hold(conn); + iso_conn_unlock(conn); + if (sk) { + lock_sock(sk); + + /* conn->sk may have become NULL if racing with sk close, but + * due to held hdev->lock, it can't become different sk. + */ + if (!conn->sk) { + release_sock(sk); + sock_put(sk); + return; + } + /* Attempt to update source address in case of BIS Sender if * the advertisement is using a random address. */ @@ -2115,14 +2126,15 @@ static void iso_conn_ready(struct iso_conn *conn) adv = hci_find_adv_instance(bis->hdev, bis->iso_qos.bcast.bis); if (adv && bacmp(&adv->random_addr, BDADDR_ANY)) { - lock_sock(sk); iso_pi(sk)->src_type = BDADDR_LE_RANDOM; bacpy(&iso_pi(sk)->src, &adv->random_addr); - release_sock(sk); } } - iso_sock_ready(conn->sk); + iso_sock_ready(sk); + + release_sock(sk); + sock_put(sk); } else { hcon = conn->hcon; if (!hcon) From ce57442a379212fe3fda59c9437ee8217eceb5b1 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:30 +0300 Subject: [PATCH 388/456] Bluetooth: ISO: fix leaking sk after socket release iso_sock_kill() tests !sock_flag(sk, SOCK_ZAPPED) || sk->sk_socket || sock_flag(sk, SOCK_DEAD) for early return, but this is always true since sock_orphan(sk) sets SOCK_DEAD, so the sk reference released by socket always leaks, iso_sock_destruct is never called. The socket reference also leaks when __iso_sock_close() does not set SOCK_ZAPPED, since iso_conn_del() does not call iso_sock_kill() after zapping. Fix by replacing SOCK_DEAD by BT_SK_KILLED flag that is not used for something else, and lock_sock to ensure iso_sock_kill() puts sk only after socket release only once. Release and iso_conn_del may run concurrently. Call iso_sock_kill() from iso_conn_del() to clean sk up after zapping. Remove call to iso_sock_kill() from iso_sock_close(), as it's generally no-op there. Fixes: ccf74f2390d6 ("Bluetooth: Add BTPROTO_ISO socket type") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 80a58275891d..5f0f45a573a7 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -62,6 +62,7 @@ static void iso_sock_kill(struct sock *sk); enum { BT_SK_BIG_SYNC, BT_SK_PA_SYNC, + BT_SK_KILLED, }; struct iso_pinfo { @@ -295,6 +296,7 @@ static void iso_conn_del(struct hci_conn *hcon, int err) iso_sock_clear_timer(sk); iso_chan_del(sk, err); release_sock(sk); + iso_sock_kill(sk); sock_put(sk); } @@ -798,24 +800,29 @@ static void iso_sock_cleanup_listen(struct sock *parent) */ static void iso_sock_kill(struct sock *sk) { + lock_sock(sk); + if (!sock_flag(sk, SOCK_ZAPPED) || sk->sk_socket || - sock_flag(sk, SOCK_DEAD)) + test_bit(BT_SK_KILLED, &iso_pi(sk)->flags)) { + release_sock(sk); return; + } BT_DBG("sk %p state %d", sk, sk->sk_state); /* Sock is dead, so set conn->sk to NULL to avoid possible UAF */ - lock_sock(sk); if (iso_pi(sk)->conn) { iso_conn_lock(iso_pi(sk)->conn); iso_pi(sk)->conn->sk = NULL; iso_conn_unlock(iso_pi(sk)->conn); } - release_sock(sk); /* Kill poor orphan */ bt_sock_unlink(&iso_sk_list, sk); sock_set_flag(sk, SOCK_DEAD); + set_bit(BT_SK_KILLED, &iso_pi(sk)->flags); + + release_sock(sk); sock_put(sk); } @@ -892,7 +899,6 @@ static void iso_sock_close(struct sock *sk) iso_sock_clear_timer(sk); __iso_sock_close(sk); release_sock(sk); - iso_sock_kill(sk); } static void iso_sock_init(struct sock *sk, struct sock *parent) @@ -2040,8 +2046,16 @@ static int iso_sock_release(struct socket *sock) release_sock(sk); } + /* Make sure sk is valid even if iso_conn_del() is concurrent */ + sock_hold(sk); + + lock_sock(sk); sock_orphan(sk); + release_sock(sk); + iso_sock_kill(sk); + + sock_put(sk); return err; } From 200fa1629c57a3ca2b03d3ca63fd3a9bfd910c43 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:31 +0300 Subject: [PATCH 389/456] Bluetooth: ISO: avoid deadlocks in iso_sock_timeout iso_sock_timeout() takes lock_sock, so sync disabling the timer while holding that lock may deadlock. iso_sock_timeout() may also run concurrently with iso_conn_del(), which leads to UAF [Task 1] [Task hdev->workqueue] iso_sock_timeout iso_conn_del iso_conn_hold_unless_zero iso_chan_del `------------> iso_conn_put caller frees hcon iso_conn_put iso_conn_free conn->hcon->iso_data = NULL; /* UAF */ Fix the deadlock by removing the disable from the lock_sock sections. Move the timer from iso_conn to iso_pinfo to decouple it from iso_conn which may need to be freed in lock_sock section. Convert some of the clear_timer to disable_timer. Fixes: dc26097bdb86 ("Bluetooth: ISO: Use kref to track lifetime of iso_conn") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 60 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 5f0f45a573a7..0cc08416fde5 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -37,8 +37,6 @@ struct iso_conn { spinlock_t lock; struct sock *sk; - struct delayed_work timeout_work; - struct sk_buff *rx_skb; __u32 rx_len; __u16 tx_sn; @@ -81,6 +79,7 @@ struct iso_pinfo { __u8 base_len; __u8 base[BASE_MAX_LENGTH]; struct iso_conn *conn; + struct delayed_work timeout_work; }; static struct bt_iso_qos default_qos; @@ -118,9 +117,6 @@ static void iso_conn_free(struct kref *ref) hci_conn_drop(conn->hcon); } - /* Ensure no more work items will run since hci_conn has been dropped */ - disable_delayed_work_sync(&conn->timeout_work); - kfree_skb(conn->rx_skb); kfree(conn); @@ -161,48 +157,45 @@ static struct sock *iso_sock_hold(struct iso_conn *conn) static void iso_sock_timeout(struct work_struct *work) { - struct iso_conn *conn = container_of(work, struct iso_conn, - timeout_work.work); - struct sock *sk; - - conn = iso_conn_hold_unless_zero(conn); - if (!conn) - return; - - iso_conn_lock(conn); - sk = iso_sock_hold(conn); - iso_conn_unlock(conn); - iso_conn_put(conn); - - if (!sk) - return; + struct iso_pinfo *pi = container_of(work, struct iso_pinfo, + timeout_work.work); + struct sock *sk = &pi->bt.sk; BT_DBG("sock %p state %d", sk, sk->sk_state); lock_sock(sk); - sk->sk_err = ETIMEDOUT; - sk->sk_state_change(sk); + if (!sock_flag(sk, SOCK_ZAPPED)) { + sk->sk_err = ETIMEDOUT; + sk->sk_state_change(sk); + } release_sock(sk); - sock_put(sk); } static void iso_sock_set_timer(struct sock *sk, long timeout) { + lockdep_assert(lockdep_sock_is_held(sk)); + + cancel_delayed_work(&iso_pi(sk)->timeout_work); + if (!iso_pi(sk)->conn) return; BT_DBG("sock %p state %d timeout %ld", sk, sk->sk_state, timeout); - cancel_delayed_work(&iso_pi(sk)->conn->timeout_work); - schedule_delayed_work(&iso_pi(sk)->conn->timeout_work, timeout); + schedule_delayed_work(&iso_pi(sk)->timeout_work, timeout); } static void iso_sock_clear_timer(struct sock *sk) { - if (!iso_pi(sk)->conn) - return; + BT_DBG("sock %p state %d", sk, sk->sk_state); + cancel_delayed_work(&iso_pi(sk)->timeout_work); +} + +static void iso_sock_disable_timer(struct sock *sk) +{ + lockdep_assert(!lockdep_sock_is_held(sk)); BT_DBG("sock %p state %d", sk, sk->sk_state); - cancel_delayed_work(&iso_pi(sk)->conn->timeout_work); + disable_delayed_work_sync(&iso_pi(sk)->timeout_work); } /* ---- ISO connections ---- */ @@ -227,7 +220,6 @@ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) kref_init(&conn->ref); spin_lock_init(&conn->lock); - INIT_DELAYED_WORK(&conn->timeout_work, iso_sock_timeout); hcon->iso_data = conn; conn->hcon = hcon; @@ -292,8 +284,9 @@ static void iso_conn_del(struct hci_conn *hcon, int err) return; } + iso_sock_disable_timer(sk); + lock_sock(sk); - iso_sock_clear_timer(sk); iso_chan_del(sk, err); release_sock(sk); iso_sock_kill(sk); @@ -800,6 +793,8 @@ static void iso_sock_cleanup_listen(struct sock *parent) */ static void iso_sock_kill(struct sock *sk) { + iso_sock_disable_timer(sk); + lock_sock(sk); if (!sock_flag(sk, SOCK_ZAPPED) || sk->sk_socket || @@ -895,8 +890,9 @@ static void __iso_sock_close(struct sock *sk) /* Must be called on unlocked socket. */ static void iso_sock_close(struct sock *sk) { + iso_sock_disable_timer(sk); + lock_sock(sk); - iso_sock_clear_timer(sk); __iso_sock_close(sk); release_sock(sk); } @@ -965,6 +961,8 @@ static struct sock *iso_sock_alloc(struct net *net, struct socket *sock, iso_pi(sk)->qos = default_qos; iso_pi(sk)->sync_handle = -1; + INIT_DELAYED_WORK(&iso_pi(sk)->timeout_work, iso_sock_timeout); + bt_sock_link(&iso_sk_list, sk); return sk; } From aa9f7cb2bd3a2be998ceb739fc9a2f986eba43eb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:32 +0300 Subject: [PATCH 390/456] Bluetooth: ISO: ensure no dangling hcon references in iso_conn After iso_conn_del(), ISO sockets should not dereference the hcon any more. Currently, clearing iso_conn::hcon relies on iso_conn_del() releasing the last reference to the iso_conn. Simplify this by explicitly clearing conn->hcon in iso_conn_del(), to avoid more complex reasoning on races about who holds the last reference. Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 0cc08416fde5..bfd39baa8503 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -263,6 +263,7 @@ static void iso_chan_del(struct sock *sk, int err) } static void iso_conn_del(struct hci_conn *hcon, int err) + __must_hold(&hcon->hdev->lock) { struct iso_conn *conn = hcon->iso_data; struct sock *sk; @@ -277,11 +278,10 @@ static void iso_conn_del(struct hci_conn *hcon, int err) iso_conn_lock(conn); sk = iso_sock_hold(conn); iso_conn_unlock(conn); - iso_conn_put(conn); if (!sk) { iso_conn_put(conn); - return; + goto done; } iso_sock_disable_timer(sk); @@ -291,6 +291,15 @@ static void iso_conn_del(struct hci_conn *hcon, int err) release_sock(sk); iso_sock_kill(sk); sock_put(sk); + +done: + /* No sk access to conn->hcon any more (lock_sock + hdev->lock) */ + iso_conn_lock(conn); + conn->hcon = NULL; + hcon->iso_data = NULL; + iso_conn_unlock(conn); + + iso_conn_put(conn); } static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, @@ -306,6 +315,11 @@ static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, return -EBUSY; } + if (!conn->hcon) { + BT_ERR("conn->hcon missing"); + return -EIO; + } + iso_pi(sk)->conn = conn; conn->sk = sk; clear_bit(ISO_CONN_DROPPED, conn->flags); @@ -2500,6 +2514,7 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) } static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) + __must_hold(&hcon->hdev->lock) { if (hcon->type != CIS_LINK && hcon->type != BIS_LINK && hcon->type != PA_LINK) { @@ -2511,8 +2526,10 @@ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) struct hci_link *link, *t; list_for_each_entry_safe(link, t, &hcon->link_list, - list) + list) { + lockdep_assert_held(&link->conn->hdev->lock); iso_conn_del(link->conn, bt_to_errno(status)); + } return; } @@ -2542,6 +2559,7 @@ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) } static void iso_disconn_cfm(struct hci_conn *hcon, __u8 reason) + __must_hold(&hcon->hdev->lock) { if (hcon->type != CIS_LINK && hcon->type != BIS_LINK && hcon->type != PA_LINK) From fdfde532ab1caa165fcd8985001157ac8b4db365 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:33 +0300 Subject: [PATCH 391/456] Bluetooth: ISO: fix refcounting of iso_conn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iso_conn_del() and iso_chan_del() have a race that results to double-put of iso_conn: [Task hdev->workqueue] [Task 2] iso_conn_del iso_chan_del iso_conn_hold_unless_zero iso_conn_lock iso_conn_lock conn->sk = NULL iso_conn_unlock sk = iso_sock_hold(conn) <---------´ if (!sk) iso_conn_put iso_conn_put iso_conn_put /* UAF */ The extra put for !sk in iso_conn_del() is currently required since failing iso_chan_add() may leave iso_conn not associated with any sk. Fix by having iso_pi(sk)->conn own refcount when non-NULL, so iso_conn_del does not need to put it. Adjust the iso_conn_add() refcounting so that conn is put if it does not get associated with an sk. Fixes: dc26097bdb86 ("Bluetooth: ISO: Use kref to track lifetime of iso_conn") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index bfd39baa8503..30de99ba4b30 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -108,9 +108,6 @@ static void iso_conn_free(struct kref *ref) BT_DBG("conn %p", conn); - if (conn->sk) - iso_pi(conn->sk)->conn = NULL; - if (conn->hcon) { conn->hcon->iso_data = NULL; if (!test_and_set_bit(ISO_CONN_DROPPED, conn->flags)) @@ -145,6 +142,14 @@ static struct iso_conn *iso_conn_hold_unless_zero(struct iso_conn *conn) return conn; } +static struct iso_conn *iso_conn_hold(struct iso_conn *conn) +{ + BT_DBG("conn %p refcnt %u", conn, kref_read(&conn->ref)); + + kref_get(&conn->ref); + return conn; +} + static struct sock *iso_sock_hold(struct iso_conn *conn) { if (!conn || !bt_sock_linked(&iso_sk_list, conn->sk)) @@ -210,7 +215,6 @@ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) conn->hcon = hcon; iso_conn_unlock(conn); } - iso_conn_put(conn); return conn; } @@ -279,10 +283,8 @@ static void iso_conn_del(struct hci_conn *hcon, int err) sk = iso_sock_hold(conn); iso_conn_unlock(conn); - if (!sk) { - iso_conn_put(conn); + if (!sk) goto done; - } iso_sock_disable_timer(sk); @@ -320,7 +322,7 @@ static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, return -EIO; } - iso_pi(sk)->conn = conn; + iso_pi(sk)->conn = iso_conn_hold(conn); conn->sk = sk; clear_bit(ISO_CONN_DROPPED, conn->flags); @@ -427,6 +429,7 @@ static int iso_connect_bis(struct sock *sk) } err = iso_chan_add(conn, sk, NULL); + iso_conn_put(conn); if (err) goto unlock; @@ -529,6 +532,7 @@ static int iso_connect_cis(struct sock *sk) } err = iso_chan_add(conn, sk, NULL); + iso_conn_put(conn); if (err) goto unlock; @@ -1310,10 +1314,9 @@ static int iso_listen_bis(struct sock *sk) } err = iso_chan_add(conn, sk, NULL); - if (err) { - hci_conn_drop(hcon); + iso_conn_put(conn); + if (err) goto unlock; - } unlock: release_sock(sk); @@ -2551,8 +2554,10 @@ static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) struct iso_conn *conn; conn = iso_conn_add(hcon); - if (conn) + if (conn) { iso_conn_ready(conn); + iso_conn_put(conn); + } } else { iso_conn_del(hcon, bt_to_errno(status)); } From af24e338bf5dafb80f42baa9a0b9e9b57b1c5d9c Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Jul 2026 23:20:34 +0300 Subject: [PATCH 392/456] Bluetooth: ISO: fix race of kfree vs kref_get_unless_zero hci_conn::iso_data is accessed and modified without lock or RCU. This leads to a race [Task hdev->workqueue] [Task 2] iso_recv iso_conn_put(conn) conn = LOAD hcon->iso_data iso_conn_free(conn) iso_conn_hold_unless_zero(conn) hcon->iso_data = NULL kfree(conn) kref_get_unless_zero(&conn->ref) /* UAF */ and also to races in iso_conn_add() vs. iso_conn_free(). Fix by adding spinlock hci_conn::proto_lock and using it to guard hci_conn::iso_data. Fixes: dc26097bdb86 ("Bluetooth: ISO: Use kref to track lifetime of iso_conn") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 4 +- net/bluetooth/hci_conn.c | 2 + net/bluetooth/iso.c | 64 ++++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index e7133ff87fbf..3df59849dcbe 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -767,9 +767,11 @@ struct hci_conn { struct dentry *debugfs; struct hci_dev *hdev; + + spinlock_t proto_lock; /* lock guarding protocol data */ void *l2cap_data; void *sco_data; - void *iso_data; + void *iso_data __guarded_by(&proto_lock); struct list_head link_list; struct hci_conn *parent; diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 1966cd153d97..ebb04badf10c 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1123,6 +1123,8 @@ static struct hci_conn *__hci_conn_add(struct hci_dev *hdev, int type, INIT_DELAYED_WORK(&conn->idle_work, hci_conn_idle); INIT_DELAYED_WORK(&conn->le_conn_timeout, le_conn_timeout); + spin_lock_init(&conn->proto_lock); + atomic_set(&conn->refcnt, 0); hci_dev_hold(hdev); diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 30de99ba4b30..a461c8a4efed 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -109,9 +109,16 @@ static void iso_conn_free(struct kref *ref) BT_DBG("conn %p", conn); if (conn->hcon) { - conn->hcon->iso_data = NULL; - if (!test_and_set_bit(ISO_CONN_DROPPED, conn->flags)) - hci_conn_drop(conn->hcon); + spin_lock(&conn->hcon->proto_lock); + + /* Check we are not racing with iso_conn_add */ + if (conn->hcon->iso_data == conn) { + conn->hcon->iso_data = NULL; + if (!test_and_set_bit(ISO_CONN_DROPPED, conn->flags)) + hci_conn_drop(conn->hcon); + } + + spin_unlock(&conn->hcon->proto_lock); } kfree_skb(conn->rx_skb); @@ -126,7 +133,21 @@ static void iso_conn_put(struct iso_conn *conn) BT_DBG("conn %p refcnt %d", conn, kref_read(&conn->ref)); + /* The following race vs. iso_conn_del() is possible: + * + * 1. conn->hcon != NULL here + * 2. kref_put puts the last reference + * 3. concurrent iso_conn_del() gets iso_conn_hold_unless_zero() -> NULL + * and returns immediately, so conn->hcon is not cleared + * 4. iso_conn_free() dereferences conn->hcon + * + * To avoid UAF in step 4, take RCU before decrementing the refcount. + */ + rcu_read_lock(); + kref_put(&conn->ref, iso_conn_free); + + rcu_read_unlock(); } static struct iso_conn *iso_conn_hold_unless_zero(struct iso_conn *conn) @@ -205,22 +226,28 @@ static void iso_sock_disable_timer(struct sock *sk) /* ---- ISO connections ---- */ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) + __must_hold(&hcon->hdev->lock) { - struct iso_conn *conn = hcon->iso_data; + struct iso_conn *conn; - conn = iso_conn_hold_unless_zero(conn); + spin_lock(&hcon->proto_lock); + + conn = iso_conn_hold_unless_zero(hcon->iso_data); if (conn) { if (!conn->hcon) { iso_conn_lock(conn); conn->hcon = hcon; iso_conn_unlock(conn); } + spin_unlock(&hcon->proto_lock); return conn; } - conn = kzalloc_obj(*conn); - if (!conn) + conn = kzalloc_obj(*conn, GFP_ATOMIC); + if (!conn) { + spin_unlock(&hcon->proto_lock); return NULL; + } kref_init(&conn->ref); spin_lock_init(&conn->lock); @@ -229,6 +256,8 @@ static struct iso_conn *iso_conn_add(struct hci_conn *hcon) conn->hcon = hcon; conn->tx_sn = 0; + spin_unlock(&hcon->proto_lock); + BT_DBG("hcon %p conn %p", hcon, conn); return conn; @@ -269,10 +298,12 @@ static void iso_chan_del(struct sock *sk, int err) static void iso_conn_del(struct hci_conn *hcon, int err) __must_hold(&hcon->hdev->lock) { - struct iso_conn *conn = hcon->iso_data; + struct iso_conn *conn; struct sock *sk; - conn = iso_conn_hold_unless_zero(conn); + spin_lock(&hcon->proto_lock); + conn = iso_conn_hold_unless_zero(hcon->iso_data); + spin_unlock(&hcon->proto_lock); if (!conn) return; @@ -296,10 +327,12 @@ static void iso_conn_del(struct hci_conn *hcon, int err) done: /* No sk access to conn->hcon any more (lock_sock + hdev->lock) */ + spin_lock(&hcon->proto_lock); iso_conn_lock(conn); conn->hcon = NULL; hcon->iso_data = NULL; iso_conn_unlock(conn); + spin_unlock(&hcon->proto_lock); iso_conn_put(conn); } @@ -421,6 +454,8 @@ static int iso_connect_bis(struct sock *sk) iso_pi(sk)->bc_sid = hcon->sid; } + lockdep_assert_held(&hcon->hdev->lock); + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -524,6 +559,8 @@ static int iso_connect_cis(struct sock *sk) } } + lockdep_assert_held(&hcon->hdev->lock); + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -855,8 +892,8 @@ static void iso_sock_disconn(struct sock *sk) */ if (bis_sk) { hcon->state = BT_OPEN; - hcon->iso_data = NULL; - iso_pi(sk)->conn->hcon = NULL; + set_bit(ISO_CONN_DROPPED, iso_pi(sk)->conn->flags); + iso_sock_clear_timer(sk); iso_chan_del(sk, bt_to_errno(hcon->abort_reason)); sock_put(bis_sk); @@ -1306,6 +1343,8 @@ static int iso_listen_bis(struct sock *sk) goto unlock; } + lockdep_assert_held(&hcon->hdev->lock); + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -2591,7 +2630,10 @@ int iso_recv(struct hci_dev *hdev, u16 handle, struct sk_buff *skb, u16 flags) return -ENOENT; } + spin_lock(&hcon->proto_lock); conn = iso_conn_hold_unless_zero(hcon->iso_data); + spin_unlock(&hcon->proto_lock); + hcon = NULL; hci_dev_unlock(hdev); From b640ff9af3c809ff5ea2077fbba17df1594ec1e4 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sat, 25 Jul 2026 01:54:40 -0700 Subject: [PATCH 393/456] Bluetooth: btintel: Validate length before parsing diagnostics TLV btintel_diagnostics() accesses tlv->val[0] without first validating that the diagnostics VSE is long enough to contain that field, so may cause reading data beyond the received frame. Fix by validating the length before access. Fixes: af395330abed ("Bluetooth: btintel: Add Intel devcoredump support") Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 5e9cac090bd8..bf567b7c5f00 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -3771,6 +3771,9 @@ static int btintel_diagnostics(struct hci_dev *hdev, struct sk_buff *skb) { struct intel_tlv *tlv = (void *)&skb->data[5]; + if (skb->len < 5 + sizeof(*tlv) + sizeof(tlv->val[0])) + goto recv_frame; + /* The first event is always an event type TLV */ if (tlv->type != INTEL_TLV_TYPE_ID) goto recv_frame; From 5761d003daa987ac81463f570713ce9c9dd204e5 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Jul 2026 12:59:17 +0300 Subject: [PATCH 394/456] Bluetooth: hci_conn: hold conn reference in abort_conn_sync() There is theoretical UAF if the conn is freed while the hci_sync task is running. Hold refcount to avoid that. Fixes: 227a0cdf4a02 ("Bluetooth: MGMT: Fix not generating command complete for MGMT_OP_DISCONNECT") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index ebb04badf10c..b1f911fd4ad6 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -3165,6 +3165,13 @@ static int abort_conn_sync(struct hci_dev *hdev, void *data) return hci_abort_conn_sync(hdev, conn, conn->abort_reason); } +static void abort_conn_destroy(struct hci_dev *hdev, void *data, int err) +{ + struct hci_conn *conn = data; + + hci_conn_put(conn); +} + int hci_abort_conn(struct hci_conn *conn, u8 reason) { struct hci_dev *hdev = conn->hdev; @@ -3190,7 +3197,10 @@ int hci_abort_conn(struct hci_conn *conn, u8 reason) * as a result to MGMT_OP_DISCONNECT/MGMT_OP_UNPAIR which does * already queue its callback on cmd_sync_work. */ - err = hci_cmd_sync_run_once(hdev, abort_conn_sync, conn, NULL); + err = hci_cmd_sync_run_once(hdev, abort_conn_sync, hci_conn_get(conn), + abort_conn_destroy); + if (err) + hci_conn_put(conn); return (err == -EEXIST) ? 0 : err; } From 2f5d635ad5906b0235bc0c870e8beba3116e1e98 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Jul 2026 12:59:18 +0300 Subject: [PATCH 395/456] Bluetooth: hci_sync: hold conn in hci_connect_acl/le_sync() callbacks There is theoretical UAF if the conn is freed while the hci_sync task is running. Hold refcount to avoid that. Fixes: 881559af5f5c ("Bluetooth: hci_sync: Attempt to dequeue connection attempt") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index aa3d53818812..79cd52974eb0 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -7152,12 +7152,23 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) return err; } +static void hci_acl_create_conn_sync_complete(struct hci_dev *hdev, void *data, + int err) +{ + struct hci_conn *conn = data; + + hci_conn_put(conn); +} + int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn) { int err; - err = hci_cmd_sync_queue_once(hdev, hci_acl_create_conn_sync, conn, - NULL); + err = hci_cmd_sync_queue_once(hdev, hci_acl_create_conn_sync, + hci_conn_get(conn), + hci_acl_create_conn_sync_complete); + if (err) + hci_conn_put(conn); return (err == -EEXIST) ? 0 : err; } @@ -7168,36 +7179,41 @@ static void create_le_conn_complete(struct hci_dev *hdev, void *data, int err) bt_dev_dbg(hdev, "err %d", err); if (err == -ECANCELED) - return; + goto done; hci_dev_lock(hdev); if (!hci_conn_valid(hdev, conn)) - goto done; + goto unlock; if (!err) { hci_connect_le_scan_cleanup(conn, 0x00); - goto done; + goto unlock; } /* Check if connection is still pending */ if (conn != hci_lookup_le_connect(hdev)) - goto done; + goto unlock; /* Flush to make sure we send create conn cancel command if needed */ flush_delayed_work(&conn->le_conn_timeout); hci_conn_failed(conn, bt_status(err)); -done: +unlock: hci_dev_unlock(hdev); +done: + hci_conn_put(conn); } int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn) { int err; - err = hci_cmd_sync_queue_once(hdev, hci_le_create_conn_sync, conn, + err = hci_cmd_sync_queue_once(hdev, hci_le_create_conn_sync, + hci_conn_get(conn), create_le_conn_complete); + if (err) + hci_conn_put(conn); return (err == -EEXIST) ? 0 : err; } From 56e78b670356caab0b607e8aad4cf819a1909d07 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Jul 2026 12:59:19 +0300 Subject: [PATCH 396/456] Bluetooth: hci_sync: hold conn in hci_connect_big_sync() callback There is theoretical UAF if the conn is freed while the hci_sync task is running. Hold refcount to avoid that. Handle NULL hcon, return 0 + do nothing to match the previous behavior. Fixes: 024421cf3992 ("Bluetooth: hci_conn: Fix not setting timeout for BIG Create Sync") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 79cd52974eb0..cdc7dff7054c 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -7522,10 +7522,12 @@ static void create_big_complete(struct hci_dev *hdev, void *data, int err) bt_dev_dbg(hdev, "err %d", err); if (err == -ECANCELED) - return; + goto done; - if (hci_conn_valid(hdev, conn)) - clear_bit(HCI_CONN_CREATE_BIG_SYNC, &conn->flags); + clear_bit(HCI_CONN_CREATE_BIG_SYNC, &conn->flags); + +done: + hci_conn_put(conn); } static int hci_le_big_create_sync(struct hci_dev *hdev, void *data) @@ -7577,8 +7579,14 @@ int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn) { int err; - err = hci_cmd_sync_queue_once(hdev, hci_le_big_create_sync, conn, + if (!conn) + return 0; + + err = hci_cmd_sync_queue_once(hdev, hci_le_big_create_sync, + hci_conn_get(conn), create_big_complete); + if (err) + hci_conn_put(conn); return (err == -EEXIST) ? 0 : err; } From 44fc74069d8988f2825246f9401218e29de2c0ab Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Jul 2026 12:59:20 +0300 Subject: [PATCH 397/456] Bluetooth: hci_sync: hold conn in hci_connect_pa_sync() callback There is theoretical UAF if the conn is freed while the hci_sync task is running. Hold refcount to avoid that. Fixes: 6d0417e4e1cf ("Bluetooth: hci_conn: Fix not setting conn_timeout for Broadcast Receiver") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index cdc7dff7054c..f50d7cd3a331 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -7336,7 +7336,7 @@ static void create_pa_complete(struct hci_dev *hdev, void *data, int err) bt_dev_dbg(hdev, "err %d", err); if (err == -ECANCELED) - return; + goto done; hci_dev_lock(hdev); @@ -7360,6 +7360,8 @@ static void create_pa_complete(struct hci_dev *hdev, void *data, int err) unlock: hci_dev_unlock(hdev); +done: + hci_conn_put(conn); } static int hci_le_past_params_sync(struct hci_dev *hdev, struct hci_conn *conn, @@ -7510,8 +7512,11 @@ int hci_connect_pa_sync(struct hci_dev *hdev, struct hci_conn *conn) { int err; - err = hci_cmd_sync_queue_once(hdev, hci_le_pa_create_sync, conn, + err = hci_cmd_sync_queue_once(hdev, hci_le_pa_create_sync, + hci_conn_get(conn), create_pa_complete); + if (err) + hci_conn_put(conn); return (err == -EEXIST) ? 0 : err; } From abf9753edf3f88282c44a605f3945d8d4f8dd86c Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Jul 2026 12:59:21 +0300 Subject: [PATCH 398/456] Bluetooth: hci_sync: hold conn in hci_past_sync() callback Avoids giving freed pointers to hci_conn_valid(), which kmalloc may have reused. Hold refcount to avoid that. Fixes: d3413703d5f8 ("Bluetooth: ISO: Add support to bind to trigger PAST") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index f50d7cd3a331..0118342ac7ea 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -7606,6 +7606,8 @@ static void past_complete(struct hci_dev *hdev, void *data, int err) bt_dev_dbg(hdev, "err %d", err); + hci_conn_put(past->conn); + hci_conn_put(past->le); kfree(past); } @@ -7670,8 +7672,8 @@ int hci_past_sync(struct hci_conn *conn, struct hci_conn *le) if (!data) return -ENOMEM; - data->conn = conn; - data->le = le; + data->conn = hci_conn_get(conn); + data->le = hci_conn_get(le); if (conn->role == HCI_ROLE_MASTER) err = hci_cmd_sync_queue_once(conn->hdev, @@ -7681,8 +7683,11 @@ int hci_past_sync(struct hci_conn *conn, struct hci_conn *le) err = hci_cmd_sync_queue_once(conn->hdev, hci_le_past_sync, data, past_complete); - if (err) + if (err) { + hci_conn_put(data->conn); + hci_conn_put(data->le); kfree(data); + } return (err == -EEXIST) ? 0 : err; } From 2c1e4e00613dfd105f978be2276e5e265801ec9f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Jul 2026 12:59:22 +0300 Subject: [PATCH 399/456] Bluetooth: hci_sync: fix hci_conn_del() use in hci_le_create_conn_sync hci_conn_del() caller must hold hdev->lock, check the conn was not concurrently deleted, and usually inform socket the conn is going to be deleted. Use hci_abort_conn_sync() instead of calling hci_conn_del() without locks etc. Fixes: 8e8b92ee60de5 ("Bluetooth: hci_sync: Add hci_le_create_conn_sync") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 0118342ac7ea..10bc4c71509f 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -6757,7 +6757,9 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data) if (hci_dev_test_flag(hdev, HCI_LE_SCAN) && hdev->le_scan_type == LE_SCAN_ACTIVE && !hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) { - hci_conn_del(conn); + conn->state = BT_OPEN; + hci_abort_conn_sync(hdev, conn, + HCI_ERROR_REJ_LIMITED_RESOURCES); hci_conn_put(conn); return -EBUSY; } From c0a9dcd2be398eee505d4b254ec3a845aa8ab189 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Jul 2026 12:59:23 +0300 Subject: [PATCH 400/456] Bluetooth: hci_sync: remove unnecessary hci_conn_get in create_conn_sync hci_conn_get() without already held reference is data race against concurrent deletion. In previous patches, the refcount has been changed to be taken before starting the hci_sync task, so remove these extra get() + put() as they are not needed. Fixes: 12917f591cea ("Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 10bc4c71509f..c8d14128c363 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -6741,11 +6741,6 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data) bt_dev_dbg(hdev, "conn %p", conn); - /* Hold a reference so conn stays valid for the HCI_CONN_CREATE - * clear_bit() at done. - */ - hci_conn_get(conn); - clear_bit(HCI_CONN_SCANNING, &conn->flags); conn->state = BT_CONNECT; @@ -6760,7 +6755,6 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data) conn->state = BT_OPEN; hci_abort_conn_sync(hdev, conn, HCI_ERROR_REJ_LIMITED_RESOURCES); - hci_conn_put(conn); return -EBUSY; } @@ -6858,7 +6852,6 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data) /* Re-enable advertising after the connection attempt is finished. */ hci_resume_advertising_sync(hdev); - hci_conn_put(conn); return err; } @@ -7133,11 +7126,6 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) else cp.role_switch = 0x00; - /* Hold a reference so conn stays valid for the HCI_CONN_CREATE - * clear_bit() below. - */ - hci_conn_get(conn); - /* Mark create connection in flight so hci_cancel_connect_sync() can * cancel it while blocking on the connection complete event. */ @@ -7149,7 +7137,6 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) conn->conn_timeout, NULL); clear_bit(HCI_CONN_CREATE, &conn->flags); - hci_conn_put(conn); return err; } From b186c18c4843dd58adc29443369bddc71cb626a3 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 27 Jul 2026 17:57:32 +0200 Subject: [PATCH 401/456] Bluetooth: btmtk: Fix short read errors in btmtk_usb_uhw_reg_read() If btmtk_usb_uhw_reg_read() gets a "short" read from a device, it will accidentally treat that as a "real" read and populate the returned value with some unknown and probably totally invalid data. Fix this logic error up by calling usb_control_msg_recv() which guarantees a "full" read happens, and then simplify the error checking for when btmtk_usb_uhw_reg_read() is called. Note, one caller of btmtk_usb_uhw_reg_read() does not check the return value, but as we pre-initialize the return value as 0, an incorrect read will not do anything wrong. Cc: stable Signed-off-by: Greg Kroah-Hartman Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 50 +++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index 02a96342e964..6f060e4433db 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -804,30 +804,24 @@ static int btmtk_usb_uhw_reg_write(struct hci_dev *hdev, u32 reg, u32 val) static int btmtk_usb_uhw_reg_read(struct hci_dev *hdev, u32 reg, u32 *val) { struct btmtk_data *data = hci_get_priv(hdev); - int pipe, err; - void *buf; + u8 buf[sizeof(u32)]; + int err; - buf = kzalloc(4, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - pipe = usb_rcvctrlpipe(data->udev, 0); - err = usb_control_msg(data->udev, pipe, 0x01, - 0xDE, - reg >> 16, reg & 0xffff, - buf, 4, USB_CTRL_GET_TIMEOUT); - if (err < 0) { + *val = 0; + err = usb_control_msg_recv(data->udev, 0, 0x01, + 0xDE, + reg >> 16, reg & 0xffff, + buf, sizeof(buf), USB_CTRL_GET_TIMEOUT, + GFP_KERNEL); + if (err) { bt_dev_err(hdev, "Failed to read uhw reg(%d)", err); - goto err_free_buf; + return err; } *val = get_unaligned_le32(buf); bt_dev_dbg(hdev, "reg=%x, value=0x%08x", reg, *val); -err_free_buf: - kfree(buf); - - return err; + return 0; } static int btmtk_usb_reg_read(struct hci_dev *hdev, u32 reg, u32 *val) @@ -877,7 +871,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) if (dev_id == 0x7922) { err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_SUBSYS_RST, &val); - if (err < 0) + if (err) return err; val |= 0x00002020; err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_SUBSYS_RST, val); @@ -887,7 +881,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) if (err < 0) return err; err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_SUBSYS_RST, &val); - if (err < 0) + if (err) return err; val |= BIT(0); err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_SUBSYS_RST, val); @@ -896,14 +890,14 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) msleep(100); } else if (dev_id == 0x7925 || dev_id == 0x6639) { err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val); - if (err < 0) + if (err) return err; val |= (1 << 5); err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_RESET_REG_CONNV3, val); if (err < 0) return err; err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val); - if (err < 0) + if (err) return err; val &= 0xFFFF00FF; val |= (1 << 13); @@ -914,7 +908,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) if (err < 0) return err; err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val); - if (err < 0) + if (err) return err; val |= (1 << 0); err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_RESET_REG_CONNV3, val); @@ -924,13 +918,13 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) if (err < 0) return err; err = btmtk_usb_uhw_reg_read(hdev, MTK_UDMA_INT_STA_BT, &val); - if (err < 0) + if (err) return err; err = btmtk_usb_uhw_reg_write(hdev, MTK_UDMA_INT_STA_BT1, 0x000000FF); if (err < 0) return err; err = btmtk_usb_uhw_reg_read(hdev, MTK_UDMA_INT_STA_BT1, &val); - if (err < 0) + if (err) return err; msleep(100); } else { @@ -940,7 +934,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) if (err < 0) return err; err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_WDT_STATUS, &val); - if (err < 0) + if (err) return err; /* Reset the bluetooth chip via USB interface. */ err = btmtk_usb_uhw_reg_write(hdev, MTK_BT_SUBSYS_RST, 1); @@ -950,13 +944,13 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) if (err < 0) return err; err = btmtk_usb_uhw_reg_read(hdev, MTK_UDMA_INT_STA_BT, &val); - if (err < 0) + if (err) return err; err = btmtk_usb_uhw_reg_write(hdev, MTK_UDMA_INT_STA_BT1, 0x000000FF); if (err < 0) return err; err = btmtk_usb_uhw_reg_read(hdev, MTK_UDMA_INT_STA_BT1, &val); - if (err < 0) + if (err) return err; /* MT7921 need to delay 20ms between toggle reset bit */ msleep(20); @@ -964,7 +958,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) if (err < 0) return err; err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_SUBSYS_RST, &val); - if (err < 0) + if (err) return err; } From 0cc4b5649ae83deb8222100dba31aa0f100a19cd Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 27 Jul 2026 17:57:33 +0200 Subject: [PATCH 402/456] Bluetooth: btmtk: Fix short read errors in btmtk_usb_reg_read() If btmtk_usb_reg_read() gets a "short" read from a device, it will accidentally treat that as a "real" read and populate the returned value with some unknown and probably totally invalid data. Fix this logic error up by calling usb_control_msg_recv() which guarantees a "full" read happens, and then simplify the error checking for when btmtk_usb_reg_read() is called (it's really just btmtk_usb_id_get() that calls btmtk_usb_reg_read(), so fix up those return sites. Cc: stable Signed-off-by: Greg Kroah-Hartman Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index 6f060e4433db..66b346761043 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -827,27 +827,21 @@ static int btmtk_usb_uhw_reg_read(struct hci_dev *hdev, u32 reg, u32 *val) static int btmtk_usb_reg_read(struct hci_dev *hdev, u32 reg, u32 *val) { struct btmtk_data *data = hci_get_priv(hdev); - int pipe, err, size = sizeof(u32); - void *buf; + u8 buf[sizeof(u32)]; + int err; - buf = kzalloc(size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - pipe = usb_rcvctrlpipe(data->udev, 0); - err = usb_control_msg(data->udev, pipe, 0x63, - USB_TYPE_VENDOR | USB_DIR_IN, - reg >> 16, reg & 0xffff, - buf, size, USB_CTRL_GET_TIMEOUT); + *val = 0; + err = usb_control_msg_recv(data->udev, 0, 0x63, + USB_TYPE_VENDOR | USB_DIR_IN, + reg >> 16, reg & 0xffff, + buf, sizeof(buf), USB_CTRL_GET_TIMEOUT, + GFP_KERNEL); if (err < 0) - goto err_free_buf; + return err; *val = get_unaligned_le32(buf); -err_free_buf: - kfree(buf); - - return err; + return 0; } static int btmtk_usb_id_get(struct hci_dev *hdev, u32 reg, u32 *id) @@ -974,7 +968,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id) } err = btmtk_usb_id_get(hdev, 0x70010200, &val); - if (err < 0 || (!val && dev_id != 0x6639)) + if (err || (!val && dev_id != 0x6639)) bt_dev_err(hdev, "Can't get device id, subsys reset fail."); return err; @@ -1318,24 +1312,24 @@ int btmtk_usb_setup(struct hci_dev *hdev) calltime = ktime_get(); err = btmtk_usb_id_get(hdev, 0x80000008, &dev_id); - if (err < 0) { + if (err) { bt_dev_err(hdev, "Failed to get device id (%d)", err); return err; } if (!dev_id || dev_id != 0x7663) { err = btmtk_usb_id_get(hdev, 0x70010200, &dev_id); - if (err < 0) { + if (err) { bt_dev_err(hdev, "Failed to get device id (%d)", err); return err; } err = btmtk_usb_id_get(hdev, 0x80021004, &fw_version); - if (err < 0) { + if (err) { bt_dev_err(hdev, "Failed to get fw version (%d)", err); return err; } err = btmtk_usb_id_get(hdev, 0x70010020, &fw_flavor); - if (err < 0) { + if (err) { bt_dev_err(hdev, "Failed to get fw flavor (%d)", err); return err; } From cac43d360c928bc0cbbd18809632388265649761 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 27 Jul 2026 17:57:34 +0200 Subject: [PATCH 403/456] Bluetooth: btusb: Fix short read errors in btusb_qca_send_vendor_req() If btusb_qca_send_vendor_req() gets a "short" read from a device, it will accidentally treat that as a "real" read and populate the returned value with some unknown and probably totally invalid data. Fix this logic error up by calling usb_control_msg_recv() which guarantees a "full" read happens, and then simplify the error checking for when btusb_qca_send_vendor_req() is called. Cc: stable Signed-off-by: Greg Kroah-Hartman Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 8f7ed469cac6..184e95c1625e 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -3424,28 +3424,16 @@ static const char *qca_get_fw_subdirectory(const struct qca_version *ver) static int btusb_qca_send_vendor_req(struct usb_device *udev, u8 request, void *data, u16 size) { - int pipe, err; - u8 *buf; - - buf = kmalloc(size, GFP_KERNEL); - if (!buf) - return -ENOMEM; + int err; /* Found some of USB hosts have IOT issues with ours so that we should * not wait until HCI layer is ready. */ - pipe = usb_rcvctrlpipe(udev, 0); - err = usb_control_msg(udev, pipe, request, USB_TYPE_VENDOR | USB_DIR_IN, - 0, 0, buf, size, USB_CTRL_GET_TIMEOUT); - if (err < 0) { + err = usb_control_msg_recv(udev, 0, request, USB_TYPE_VENDOR | USB_DIR_IN, + 0, 0, data, size, USB_CTRL_GET_TIMEOUT, + GFP_KERNEL); + if (err) dev_err(&udev->dev, "Failed to access otp area (%d)", err); - goto done; - } - - memcpy(data, buf, size); - -done: - kfree(buf); return err; } @@ -3652,7 +3640,7 @@ static bool btusb_qca_need_patch(struct usb_device *udev) struct qca_version ver; if (btusb_qca_send_vendor_req(udev, QCA_GET_TARGET_VERSION, &ver, - sizeof(ver)) < 0) + sizeof(ver))) return false; /* only low ROM versions need patches */ return !(le32_to_cpu(ver.rom_version) & ~0xffffU); @@ -3670,7 +3658,7 @@ static int btusb_setup_qca(struct hci_dev *hdev) err = btusb_qca_send_vendor_req(udev, QCA_GET_TARGET_VERSION, &ver, sizeof(ver)); - if (err < 0) + if (err) return err; ver_rom = le32_to_cpu(ver.rom_version); @@ -3693,7 +3681,7 @@ static int btusb_setup_qca(struct hci_dev *hdev) err = btusb_qca_send_vendor_req(udev, QCA_CHECK_STATUS, &status, sizeof(status)); - if (err < 0) + if (err) return err; if (!(status & QCA_PATCH_UPDATED)) { @@ -3704,7 +3692,7 @@ static int btusb_setup_qca(struct hci_dev *hdev) err = btusb_qca_send_vendor_req(udev, QCA_GET_TARGET_VERSION, &ver, sizeof(ver)); - if (err < 0) + if (err) return err; btdata->qca_dump.fw_version = le32_to_cpu(ver.patch_version); From abd93c85c8667add738ee82aeab95dd9fc8265a2 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Sat, 25 Jul 2026 16:52:30 -0300 Subject: [PATCH 404/456] Bluetooth: SCO: give the socket its own sco_conn reference sco_conn_del() drops a reference it does not own. It takes one transient reference via sco_conn_hold_unless_zero() and releases it with the sco_conn_put() that follows sco_sock_hold(); the additional put in the !sk branch releases a second one: conn = sco_conn_hold_unless_zero(conn); ... sk = sco_sock_hold(conn); sco_conn_unlock(conn); sco_conn_put(conn); if (!sk) { sco_conn_put(conn); return; } When close() races the controller's Disconnection Complete, sco_chan_del() clears conn->sk and drops the socket's reference while sco_conn_del() is running. sco_conn_del() then sees sk == NULL, its own put drops the count to zero and frees the conn, and the second put writes to the freed kref: BUG: KASAN: slab-use-after-free in sco_conn_put.part.0+0x1a/0x190 Write of size 4 at addr ffff8881099dec74 by task kworker/u17:3/413 Workqueue: hci1 hci_rx_work Call Trace: sco_conn_put.part.0+0x1a/0x190 hci_disconn_complete_evt+0x1ee/0x3e0 hci_event_packet+0x54a/0x650 hci_rx_work+0x321/0x3d0 Allocated by task 413: sco_conn_add+0x72/0x1a0 sco_connect_cfm+0x88/0x670 Freed by task 413: sco_conn_del.isra.0+0x3f/0xf0 hci_disconn_complete_evt+0x1ee/0x3e0 refcount_t: underflow; use-after-free. The root cause is that the socket stores the connection without holding a reference of its own. __sco_chan_add() does: sco_pi(sk)->conn = conn; so the socket borrows whatever reference its caller happened to hold, and the callers paper over that with ad-hoc holds and puts. Give the socket a counted reference instead: __sco_chan_add() takes one and it is released together with the channel (sco_chan_del()) and in sco_sock_destruct(). With the socket holding its own reference, sco_conn_del() no longer needs the extra put and the redundant hold in sco_conn_ready() goes away. Making the socket own its reference means the connection is now actually freed on the error paths of sco_connect() where it used to leak, which in turn runs sco_conn_free() and its hci_conn_drop(conn->hcon). To keep the hci_conn accounting balanced, make that ownership explicit as well: sco_conn_add() consumes one hci_conn reference and the sco_conn owns it for its lifetime. sco_connect() hands over the reference returned by hci_connect_sco() and no longer drops it on the error paths; sco_connect_cfm(), which is not given a reference, takes one with hci_conn_hold() before handing it to sco_conn_add() (and drops it again if the allocation fails); and the explicit hci_conn_hold() in sco_conn_ready() is removed. Every reference then has a single, clear owner. Fixes: e6720779ae61 ("Bluetooth: SCO: Use kref to track lifetime of sco_conn") Cc: stable@vger.kernel.org Suggested-by: Pauli Virtanen Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/sco.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index c05f79b7aa31..3d4362a09df4 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -188,6 +188,9 @@ static void sco_sock_clear_timer(struct sock *sk) } /* ---- SCO connections ---- */ +/* Consumes a reference on @hcon, which the returned sco_conn owns until it is + * freed. On failure (NULL return) the reference is left for the caller to drop. + */ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) { struct sco_conn *conn = hcon->sco_data; @@ -198,6 +201,9 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) sco_conn_lock(conn); conn->hcon = hcon; sco_conn_unlock(conn); + } else { + /* conn already owns a reference on hcon */ + hci_conn_drop(hcon); } return conn; } @@ -265,10 +271,8 @@ static void sco_conn_del(struct hci_conn *hcon, int err) sco_conn_unlock(conn); sco_conn_put(conn); - if (!sk) { - sco_conn_put(conn); + if (!sk) return; - } /* Kill socket */ lock_sock(sk); @@ -283,7 +287,7 @@ static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, { BT_DBG("conn %p", conn); - sco_pi(sk)->conn = conn; + sco_pi(sk)->conn = sco_conn_hold(conn); conn->sk = sk; if (parent) @@ -366,15 +370,15 @@ static int sco_connect(struct sock *sk) */ if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) { release_sock(sk); - hci_conn_drop(hcon); + sco_conn_put(conn); err = -EBADFD; goto unlock; } err = sco_chan_add(conn, sk, NULL); + sco_conn_put(conn); if (err) { release_sock(sk); - hci_conn_drop(hcon); goto unlock; } @@ -1452,8 +1456,6 @@ static void sco_conn_ready(struct sco_conn *conn) bacpy(&sco_pi(sk)->src, &conn->hcon->src); bacpy(&sco_pi(sk)->dst, &conn->hcon->dst); - sco_conn_hold(conn); - hci_conn_hold(conn->hcon); __sco_chan_add(conn, sk, parent); if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(parent)->flags)) @@ -1509,10 +1511,12 @@ static void sco_connect_cfm(struct hci_conn *hcon, __u8 status) if (!status) { struct sco_conn *conn; - conn = sco_conn_add(hcon); + conn = sco_conn_add(hci_conn_hold(hcon)); if (conn) { sco_conn_ready(conn); sco_conn_put(conn); + } else { + hci_conn_drop(hcon); } } else sco_conn_del(hcon, bt_to_errno(status)); From 0fe1e3e8f3380d7862296a73b528d164e96c76b8 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Sun, 26 Jul 2026 17:08:05 +0200 Subject: [PATCH 405/456] net: phylink: put link_gpio if phylink_create fails In phylink_create() if phylink_register_sfp() returns an error, link_gpio obtained by phylink_parse_fixedlink() is never released. While this is a very unlikely scenario, it's worth to fix/handle this. This was present from the very first implementation of phylink but got relevant only with the introduction of ce0aa27ff3f6 ("sfp: add sfp-bus to bridge between network devices and sfp cages") where additional function were added after phylink_parse_fixedlink() making the release of link_gpio needed if such additional function errored out. While at it, restructure the exit condition of phylink_create() with the goto pattern to reduce code duplication on handling error conditions. Fixes: ce0aa27ff3f6 ("sfp: add sfp-bus to bridge between network devices and sfp cages") Signed-off-by: Christian Marangi Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260726150806.2437-1-ansuelsmth@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 087ac63f9193..18d2ead97aa5 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -1875,8 +1875,8 @@ struct phylink *phylink_create(struct phylink_config *config, } else if (config->type == PHYLINK_DEV) { pl->dev = config->dev; } else { - kfree(pl); - return ERR_PTR(-EINVAL); + ret = -EINVAL; + goto free_pl; } pl->mac_supports_eee_ops = phylink_mac_implements_lpi(mac_ops); @@ -1909,28 +1909,29 @@ struct phylink *phylink_create(struct phylink_config *config, phylink_validate(pl, pl->supported, &pl->link_config); ret = phylink_parse_mode(pl, fwnode); - if (ret < 0) { - kfree(pl); - return ERR_PTR(ret); - } + if (ret < 0) + goto free_pl; if (pl->cfg_link_an_mode == MLO_AN_FIXED) { ret = phylink_parse_fixedlink(pl, fwnode); - if (ret < 0) { - kfree(pl); - return ERR_PTR(ret); - } + if (ret < 0) + goto release_link_gpio; } pl->req_link_an_mode = pl->cfg_link_an_mode; ret = phylink_register_sfp(pl, fwnode); - if (ret < 0) { - kfree(pl); - return ERR_PTR(ret); - } + if (ret < 0) + goto release_link_gpio; return pl; + +release_link_gpio: + if (pl->link_gpio) + gpiod_put(pl->link_gpio); +free_pl: + kfree(pl); + return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(phylink_create); From 3bd438a58e910db5dc369aa25dfed1fc95f1b596 Mon Sep 17 00:00:00 2001 From: Hariprasad Kelam Date: Wed, 22 Jul 2026 13:42:29 +0530 Subject: [PATCH 406/456] octeontx2-af: Block VFs from clobbering special CGX PKIND state PF and VF NIX LFs that share a CGX LMAC reuse the same hardware PKIND programming. When HiGig2 or EDSA parsing is enabled, a VF NIX LF alloc must not reset the LMAC RX PKIND or default TX parse config over the PF setup. Add cgx_get_pkind() and rvu_cgx_is_pkind_config_permitted() so VFs skip cgx_set_pkind(), rvu_npc_set_pkind(), and NIX_AF_LFX_TX_PARSE_CFG updates when the LMAC is using NPC_RX_HIGIG_PKIND or NPC_RX_EDSA_PKIND. Fixes: 94d942c5fb97 ("octeontx2-af: Config pkind for CGX mapped PFs") Cc: Geetha sowjanya Signed-off-by: Hariprasad Kelam Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260722081229.1653619-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/marvell/octeontx2/af/cgx.c | 12 +++ .../net/ethernet/marvell/octeontx2/af/cgx.h | 1 + .../net/ethernet/marvell/octeontx2/af/rvu.h | 2 + .../ethernet/marvell/octeontx2/af/rvu_cgx.c | 79 +++++++++++++++++++ .../ethernet/marvell/octeontx2/af/rvu_nix.c | 22 +++++- .../ethernet/marvell/octeontx2/af/rvu_npc.c | 29 ++++--- 6 files changed, 131 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cgx.c b/drivers/net/ethernet/marvell/octeontx2/af/cgx.c index 2e94d5105016..f5fd6138c352 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/cgx.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/cgx.c @@ -518,6 +518,18 @@ int cgx_set_pkind(void *cgxd, u8 lmac_id, int pkind) return 0; } +int cgx_get_pkind(void *cgxd, u8 lmac_id, int *pkind) +{ + struct cgx *cgx = cgxd; + + if (!is_lmac_valid(cgx, lmac_id)) + return -ENODEV; + + *pkind = cgx_read(cgx, lmac_id, cgx->mac_ops->rxid_map_offset); + *pkind = *pkind & 0x3F; + return 0; +} + static u8 cgx_get_lmac_type(void *cgxd, int lmac_id) { struct cgx *cgx = cgxd; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cgx.h b/drivers/net/ethernet/marvell/octeontx2/af/cgx.h index 92ccf343dfe0..8411a75dd723 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/cgx.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/cgx.h @@ -141,6 +141,7 @@ int cgx_get_cgxid(void *cgxd); int cgx_get_lmac_cnt(void *cgxd); void *cgx_get_pdata(int cgx_id); int cgx_set_pkind(void *cgxd, u8 lmac_id, int pkind); +int cgx_get_pkind(void *cgxd, u8 lmac_id, int *pkind); int cgx_lmac_evh_register(struct cgx_event_cb *cb, void *cgxd, int lmac_id); int cgx_lmac_evh_unregister(void *cgxd, int lmac_id); int cgx_get_tx_stats(void *cgxd, int lmac_id, int idx, u64 *tx_stat); diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h index 7f3505ae6860..9d5b7b51bdfa 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h @@ -1115,6 +1115,8 @@ void npc_read_mcam_entry(struct rvu *rvu, struct npc_mcam *mcam, u8 *intf, u8 *ena); int npc_config_cntr_default_entries(struct rvu *rvu, bool enable); bool is_cgx_config_permitted(struct rvu *rvu, u16 pcifunc); +bool rvu_cgx_check_permission_and_set_pkind(struct rvu *rvu, u16 pcifunc, int pkind); +bool rvu_cgx_is_pkind_config_permitted(struct rvu *rvu, u16 pcifunc); bool is_mac_feature_supported(struct rvu *rvu, int pf, int feature); u32 rvu_cgx_get_fifolen(struct rvu *rvu); void *rvu_first_cgx_pdata(struct rvu *rvu); diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c index 4ff3935ed3fe..87d21889dc49 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cgx.c @@ -1355,3 +1355,82 @@ void rvu_mac_reset(struct rvu *rvu, u16 pcifunc) if (mac_ops->mac_reset(cgxd, lmac, !is_vf(pcifunc))) dev_err(rvu->dev, "Failed to reset MAC\n"); } + +/* Do not allow CGX-mapped VFs to overwrite PKIND when special parse kinds + * (HiGig, EDSA, etc.) are in use on the shared LMAC. VFs must not program + * NPC_TX_DEF_PKIND on NIX_AF_LFX_TX_PARSE_CFG in that case: the PF owns + * parse mode and no separate NPC_TX_HIGIG_PKIND is installed on the VF LF. + * TX-parse callers skip the write when denied; rvu_lf_reset() clears each LF + * before alloc so the next permitted owner programs NPC_TX_DEF_PKIND. + */ +bool rvu_cgx_is_pkind_config_permitted(struct rvu *rvu, u16 pcifunc) +{ + int pf, err, rxpkind; + u8 cgx_id, lmac_id; + void *cgxd; + + pf = rvu_get_pf(rvu->pdev, pcifunc); + + if (!(pcifunc & RVU_PFVF_FUNC_MASK)) + return true; + + if (!is_pf_cgxmapped(rvu, pf)) + return true; + + rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id); + cgxd = rvu_cgx_pdata(cgx_id, rvu); + err = cgx_get_pkind(cgxd, lmac_id, &rxpkind); + if (err) + return false; + + switch (rxpkind) { + case NPC_RX_HIGIG_PKIND: + case NPC_RX_EDSA_PKIND: + return false; + default: + return true; + } +} + +/* Do not allow CGX-mapped VFs to overwrite PKIND when special parse kinds + * (HiGig, EDSA, etc.) are in use on the shared LMAC. + */ +bool rvu_cgx_check_permission_and_set_pkind(struct rvu *rvu, u16 pcifunc, int pkind) +{ + int pf, err, rxpkind; + u8 cgx_id, lmac_id; + struct cgx *cgxd; + + pf = rvu_get_pf(rvu->pdev, pcifunc); + + if (!is_pf_cgxmapped(rvu, pf)) + return false; + + rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id); + cgxd = rvu_cgx_pdata(cgx_id, rvu); + + mutex_lock(&cgxd->lock); + if (!is_vf(pcifunc)) + goto set; + + err = cgx_get_pkind(cgxd, lmac_id, &rxpkind); + if (err) + goto err; + + switch (rxpkind) { + case NPC_RX_HIGIG_PKIND: + case NPC_RX_EDSA_PKIND: + goto err; + default: + break; + } + +set: + cgx_set_pkind(rvu_cgx_pdata(cgx_id, rvu), lmac_id, pkind); + mutex_unlock(&cgxd->lock); + return true; + +err: + mutex_unlock(&cgxd->lock); + return false; +} diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c index 6a0ce2665031..964bcaae098e 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_nix.c @@ -363,8 +363,8 @@ static int nix_interface_init(struct rvu *rvu, u16 pcifunc, int type, int nixlf, pfvf->tx_chan_cnt = 1; rsp->tx_link = cgx_id * hw->lmac_per_cgx + lmac_id; - cgx_set_pkind(rvu_cgx_pdata(cgx_id, rvu), lmac_id, pkind); - rvu_npc_set_pkind(rvu, pkind, pfvf); + if (rvu_cgx_check_permission_and_set_pkind(rvu, pcifunc, pkind)) + rvu_npc_set_pkind(rvu, pkind, pfvf); break; case NIX_INTF_TYPE_LBK: vf = (pcifunc & RVU_PFVF_FUNC_MASK) - 1; @@ -1505,13 +1505,15 @@ int rvu_mbox_handler_nix_lf_alloc(struct rvu *rvu, struct nix_lf_alloc_req *req, struct nix_lf_alloc_rsp *rsp) { - int nixlf, qints, hwctx_size, intf, rc = 0; + int nixlf, qints, hwctx_size, intf, rc = 0, pf; u16 bcast, mcast, promisc, ucast; struct rvu_hwinfo *hw = rvu->hw; u16 pcifunc = req->hdr.pcifunc; + u8 cgx_id = 0, lmac_id = 0; bool rules_created = false; struct rvu_block *block; struct rvu_pfvf *pfvf; + struct cgx *cgxd; u64 cfg, ctx_cfg; int blkaddr; @@ -1685,8 +1687,20 @@ int rvu_mbox_handler_nix_lf_alloc(struct rvu *rvu, rvu_write64(rvu, blkaddr, NIX_AF_LFX_RX_CFG(nixlf), req->rx_cfg); /* Configure pkind for TX parse config */ + + pf = rvu_get_pf(rvu->pdev, pcifunc); cfg = NPC_TX_DEF_PKIND; - rvu_write64(rvu, blkaddr, NIX_AF_LFX_TX_PARSE_CFG(nixlf), cfg); + + if (is_pf_cgxmapped(rvu, pf) && is_vf(pcifunc)) { + rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id); + cgxd = rvu_cgx_pdata(cgx_id, rvu); + mutex_lock(&cgxd->lock); + if (rvu_cgx_is_pkind_config_permitted(rvu, pcifunc)) + rvu_write64(rvu, blkaddr, NIX_AF_LFX_TX_PARSE_CFG(nixlf), cfg); + mutex_unlock(&cgxd->lock); + } else { + rvu_write64(rvu, blkaddr, NIX_AF_LFX_TX_PARSE_CFG(nixlf), cfg); + } if (is_rep_dev(rvu, pcifunc)) { pfvf->tx_chan_base = RVU_SWITCH_LBK_CHAN; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c index c7bc0b3a29b9..38554d51164e 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c @@ -19,6 +19,7 @@ #include "cn20k/npc.h" #include "rvu_npc.h" #include "cn20k/reg.h" +#include "lmac_common.h" #define RSVD_MCAM_ENTRIES_PER_PF 3 /* Broadcast, Promisc and AllMulticast */ #define RSVD_MCAM_ENTRIES_PER_NIXLF 1 /* Ucast for LFs */ @@ -4200,10 +4201,11 @@ int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, { struct rvu_pfvf *pfvf = rvu_get_pfvf(rvu, pcifunc); - int blkaddr, nixlf, rc, intf_mode; int pf = rvu_get_pf(rvu->pdev, pcifunc); + int blkaddr, nixlf, rc, intf_mode; + u8 cgx_id = 0, lmac_id = 0; u64 rxpkind, txpkind; - u8 cgx_id, lmac_id; + struct cgx *cgxd; /* use default pkind to disable edsa/higig */ rxpkind = rvu_npc_get_pkind(rvu, pf); @@ -4227,12 +4229,8 @@ int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, /* rx pkind set req valid only for cgx mapped PFs */ if (!is_cgx_config_permitted(rvu, pcifunc)) return 0; - rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, &lmac_id); - - rc = cgx_set_pkind(rvu_cgx_pdata(cgx_id, rvu), lmac_id, - rxpkind); - if (rc) - return rc; + if (!rvu_cgx_check_permission_and_set_pkind(rvu, pcifunc, rxpkind)) + return -EINVAL; } if (dir & PKIND_TX) { @@ -4241,8 +4239,19 @@ int rvu_npc_set_parse_mode(struct rvu *rvu, u16 pcifunc, u64 mode, u8 dir, if (rc) return rc; - rvu_write64(rvu, blkaddr, NIX_AF_LFX_TX_PARSE_CFG(nixlf), - txpkind); + if (is_pf_cgxmapped(rvu, pf) && is_vf(pcifunc)) { + rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id, + &lmac_id); + cgxd = rvu_cgx_pdata(cgx_id, rvu); + mutex_lock(&cgxd->lock); + if (rvu_cgx_is_pkind_config_permitted(rvu, pcifunc)) + rvu_write64(rvu, blkaddr, NIX_AF_LFX_TX_PARSE_CFG(nixlf), + txpkind); + mutex_unlock(&cgxd->lock); + } else { + rvu_write64(rvu, blkaddr, NIX_AF_LFX_TX_PARSE_CFG(nixlf), + txpkind); + } } pfvf->intf_mode = intf_mode; From d2fb981384b3a45f690616d550b29046e8ad16a4 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Tue, 28 Jul 2026 07:58:34 +0200 Subject: [PATCH 407/456] can: j1939: use netdevice_tracker for j1939_{priv,session,ecu} tracking syzbot is still reporting unregister_netdevice: waiting for vcan0 to become free. Usage count = 2 problem. A debug printk() patch in linux-next-20260508 identified that there is dev_hold()/dev_put() imbalance in j1939_priv management. Call trace for vcan0[26] +4 at __dev_hold include/linux/netdevice.h:4470 [inline] netdev_hold include/linux/netdevice.h:4513 [inline] dev_hold include/linux/netdevice.h:4536 [inline] j1939_priv_create net/can/j1939/main.c:140 [inline] j1939_netdev_start+0x36b/0xc10 net/can/j1939/main.c:268 j1939_sk_bind+0x853/0xb30 net/can/j1939/socket.c:506 __sys_bind_socket net/socket.c:1948 [inline] __sys_bind+0x2e9/0x410 net/socket.c:1979 Call trace for vcan0[28] -3 at __dev_put include/linux/netdevice.h:4456 [inline] netdev_put include/linux/netdevice.h:4523 [inline] dev_put include/linux/netdevice.h:4548 [inline] __j1939_priv_release net/can/j1939/main.c:166 [inline] kref_put include/linux/kref.h:65 [inline] j1939_priv_put+0x128/0x270 net/can/j1939/main.c:172 j1939_sk_sock_destruct+0x52/0x90 net/can/j1939/socket.c:388 __sk_destruct+0x8d/0x9d0 net/core/sock.c:2352 rcu_do_batch kernel/rcu/tree.c:2617 [inline] rcu_core kernel/rcu/tree.c:2869 [inline] rcu_cpu_kthread+0x99e/0x1470 kernel/rcu/tree.c:2957 smpboot_thread_fn+0x541/0xa50 kernel/smpboot.c:160 kthread+0x388/0x470 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 This refcount leak in j1939_priv might be caused by a refcount leak in j1939_{session,ecu} because j1939_{session,ecu} holds a ref on j1939_priv. For further investigation using upstream kernels, enable netdevice_tracker in j1939_{priv,session,ecu} management. Signed-off-by: Tetsuo Handa Acked-by: Oleksij Rempel Signed-off-by: Oleksij Rempel Link: https://patch.msgid.link/20260728055835.1151785-2-o.rempel@pengutronix.de Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/j1939/bus.c | 2 ++ net/can/j1939/j1939-priv.h | 3 +++ net/can/j1939/main.c | 8 ++++---- net/can/j1939/transport.c | 2 ++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/net/can/j1939/bus.c b/net/can/j1939/bus.c index dc374286eeb6..cdc3c0a71937 100644 --- a/net/can/j1939/bus.c +++ b/net/can/j1939/bus.c @@ -20,6 +20,7 @@ static void __j1939_ecu_release(struct kref *kref) struct j1939_priv *priv = ecu->priv; list_del(&ecu->list); + netdev_put(priv->ndev, &ecu->priv_dev_tracker); kfree(ecu); j1939_priv_put(priv); } @@ -155,6 +156,7 @@ struct j1939_ecu *j1939_ecu_create_locked(struct j1939_priv *priv, name_t name) if (!ecu) return ERR_PTR(-ENOMEM); kref_init(&ecu->kref); + netdev_hold(priv->ndev, &ecu->priv_dev_tracker, gfp_any()); ecu->addr = J1939_IDLE_ADDR; ecu->name = name; diff --git a/net/can/j1939/j1939-priv.h b/net/can/j1939/j1939-priv.h index 81f58924b4ac..cf26352d1d8c 100644 --- a/net/can/j1939/j1939-priv.h +++ b/net/can/j1939/j1939-priv.h @@ -38,6 +38,7 @@ struct j1939_ecu { struct hrtimer ac_timer; struct kref kref; struct j1939_priv *priv; + netdevice_tracker priv_dev_tracker; /* count users, to help transport protocol decide for interaction */ int nusers; @@ -60,6 +61,7 @@ struct j1939_priv { rwlock_t lock; struct net_device *ndev; + netdevice_tracker dev_tracker; /* list of 256 ecu ptrs, that cache the claimed addresses. * also protected by the above lock @@ -230,6 +232,7 @@ enum j1939_session_state { struct j1939_session { struct j1939_priv *priv; + netdevice_tracker priv_dev_tracker; struct list_head active_session_list_entry; struct list_head sk_session_queue_entry; struct kref kref; diff --git a/net/can/j1939/main.c b/net/can/j1939/main.c index 9937c04241bc..5e5e6c228f22 100644 --- a/net/can/j1939/main.c +++ b/net/can/j1939/main.c @@ -137,7 +137,7 @@ static struct j1939_priv *j1939_priv_create(struct net_device *ndev) priv->ndev = ndev; kref_init(&priv->kref); kref_init(&priv->rx_kref); - dev_hold(ndev); + netdev_hold(ndev, &priv->dev_tracker, GFP_KERNEL); netdev_dbg(priv->ndev, "%s : 0x%p\n", __func__, priv); @@ -163,7 +163,7 @@ static void __j1939_priv_release(struct kref *kref) WARN_ON_ONCE(!list_empty(&priv->ecus)); WARN_ON_ONCE(!list_empty(&priv->j1939_socks)); - dev_put(ndev); + netdev_put(ndev, &priv->dev_tracker); kfree(priv); } @@ -281,7 +281,7 @@ struct j1939_priv *j1939_netdev_start(struct net_device *ndev) */ kref_get(&priv_new->rx_kref); mutex_unlock(&j1939_netdev_lock); - dev_put(ndev); + netdev_put(ndev, &priv->dev_tracker); kfree(priv); return priv_new; } @@ -298,7 +298,7 @@ struct j1939_priv *j1939_netdev_start(struct net_device *ndev) j1939_priv_set(ndev, NULL); mutex_unlock(&j1939_netdev_lock); - dev_put(ndev); + netdev_put(ndev, &priv->dev_tracker); kfree(priv); return ERR_PTR(ret); diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c index 8a31cb23bc76..98f96362b20f 100644 --- a/net/can/j1939/transport.c +++ b/net/can/j1939/transport.c @@ -283,6 +283,7 @@ static void j1939_session_destroy(struct j1939_session *session) kfree_skb(skb); } __j1939_session_drop(session); + netdev_put(session->priv->ndev, &session->priv_dev_tracker); j1939_priv_put(session->priv); kfree(session); } @@ -1526,6 +1527,7 @@ static struct j1939_session *j1939_session_new(struct j1939_priv *priv, INIT_LIST_HEAD(&session->active_session_list_entry); INIT_LIST_HEAD(&session->sk_session_queue_entry); kref_init(&session->kref); + netdev_hold(priv->ndev, &session->priv_dev_tracker, gfp_any()); j1939_priv_get(priv); session->priv = priv; From eb96c58907922546e415e545fe9a14ea63b02719 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 28 Jul 2026 07:58:35 +0200 Subject: [PATCH 408/456] can: j1939: transport: j1939_session_fresh_new(): initialize receive buffer Zero the allocated buffer in j1939_session_fresh_new() to ensure it contains no residual data. While there is a potential performance impact if users allocate maximum sized ETP buffers, most real-world use cases are not noticeably affected since the maximum known buffer size is typically around 65K. Fixes: 9d71dd0c7009 ("can: add support of SAE J1939 protocol") Reported-by: Ji'an Zhou Message-ID: Signed-off-by: Oleksij Rempel Link: https://patch.msgid.link/20260728055835.1151785-3-o.rempel@pengutronix.de Cc: stable@kernel.org [mkl: add Message-ID] Signed-off-by: Marc Kleine-Budde --- net/can/j1939/transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c index 98f96362b20f..8fcfd13e5e6f 100644 --- a/net/can/j1939/transport.c +++ b/net/can/j1939/transport.c @@ -1581,7 +1581,7 @@ j1939_session *j1939_session_fresh_new(struct j1939_priv *priv, } /* alloc data area */ - skb_put(skb, size); + skb_put_zero(skb, size); /* skb is recounted in j1939_session_new() */ return session; } From 050f010f920da17c1044a4f174766ad553e770b6 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Fri, 24 Jul 2026 20:15:25 +0200 Subject: [PATCH 409/456] can: isotp: fix timer drain order, wakeup handling and tx_gen ordering This patch is a follow-up to commit cf070fe33bfb ("can: isotp: serialize TX state transitions under so->rx_lock") which addresses following sashiko-bot findings: - isotp_sendmsg(): drain so->txfrtimer first so a stale callback can't re-arm echotimer after the claim - isotp_release(): wake so->wait after forcing ISOTP_SHUTDOWN so a sleeping sendmsg() claim isn't stranded - isotp_sendmsg(): have both wait_event_interruptible() calls in isotp_sendmsg() also wake on ISOTP_SHUTDOWN and do not return claim to IDLE to avoid corrupting a concurrent isotp_release() process. - isotp_sendmsg(): handle potential claim of a new transfer when the wait_event_interruptible() call returns in CAN_ISOTP_WAIT_TX_DONE mode. Don't touch timers and states of the new transfer if a new thread incremented so->tx_gen before getting the lock at err_event_drop. - isotp_sendmsg(): handle a stuck can_send() and omit timer and state changes if a new transfer was claimed. wait_tx_done() returns the error recorded in so->tx_result[], tagged with the caller's own generation. - isotp_tx_timeout(): on a claimed timeout, record the ECOMM error for the timed-out transfer's own generation in so->tx_result[]; sk->sk_err is raised unconditionally, same as every other error path here. - isotp_tx_gen_done()/isotp_tx_timeout(): always read tx.state (acquire) before tx_gen - the reverse order let a weakly ordered CPU pair a fresh tx.state with a stale tx_gen/tx_result slot. - isotp_sendmsg(): wait_tx_done: drain sk_err via sock_error() once we have read the result from so->tx_result[], so an already-reported error doesn't stay latched for a later poll()/SO_ERROR. Also align the remaining lock-free so->tx.state/rx.state/cfecho accesses and use skb->hash as unique loopback echo frame indicator. Fixes: cf070fe33bfb ("can: isotp: serialize TX state transitions under so->rx_lock") Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20260724181525.43556-1-socketcan@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- net/can/isotp.c | 317 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 230 insertions(+), 87 deletions(-) diff --git a/net/can/isotp.c b/net/can/isotp.c index 54becaf6898f..1f11c66b343c 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -127,6 +127,15 @@ MODULE_PARM_DESC(max_pdu_size, "maximum isotp pdu size (default " #define ISOTP_FC_TIMEOUT 1 /* 1 sec */ #define ISOTP_ECHO_TIMEOUT 2 /* 2 secs */ +/* so->tx_result[so->tx_gen % ISOTP_TX_RESULT_SLOTS] holds the packed value + * (err << ISOTP_TX_RESULT_GEN_BITS | gen) for each tx generation slot, so it + * can be handled with a single READ_ONCE()/WRITE_ONCE() access. + */ +#define ISOTP_TX_RESULT_SLOTS 4 +#define ISOTP_TX_RESULT_GEN_BITS 24 +#define ISOTP_TX_RESULT_GEN_MASK ((1U << ISOTP_TX_RESULT_GEN_BITS) - 1) +#define ISOTP_TX_RESULT_ERR_MASK 0xFF + enum { ISOTP_IDLE = 0, ISOTP_WAIT_FIRST_FC, @@ -166,7 +175,8 @@ struct isotp_sock { u32 force_tx_stmin; u32 force_rx_stmin; u32 cfecho; /* consecutive frame echo tag */ - u32 tx_gen; /* generation, bumped per new tx transfer */ + u32 tx_gen; /* transfer generation, increased per new tx transfer */ + u32 tx_result[ISOTP_TX_RESULT_SLOTS]; /* per-generation result slots */ struct tpcon rx, tx; struct list_head notifier; wait_queue_head_t wait; @@ -177,6 +187,65 @@ static LIST_HEAD(isotp_notifier_list); static DEFINE_SPINLOCK(isotp_notifier_lock); static struct isotp_sock *isotp_busy_notifier; +/* increase (24 bit) tx generation value */ +static u32 isotp_inc_tx_gen(u32 gen) +{ + return (gen + 1) & ISOTP_TX_RESULT_GEN_MASK; +} + +/* store 8 bit error and 24 bit tx generation values in packed u32 element */ +static u32 isotp_pack_tx_result(u32 gen, int err) +{ + return gen | ((u32)err << ISOTP_TX_RESULT_GEN_BITS); +} + +/* get the 24 bit tx generation value from the tx result */ +static u32 isotp_get_tx_gen(u32 gen_err) +{ + return gen_err & ISOTP_TX_RESULT_GEN_MASK; +} + +/* get the 8 bit error value from the tx result */ +static u32 isotp_get_tx_err(u32 gen_err) +{ + return (gen_err >> ISOTP_TX_RESULT_GEN_BITS) & ISOTP_TX_RESULT_ERR_MASK; +} + +/* store transfer result in per-generation%4 so->tx_result[] slot */ +static void isotp_set_tx_result(struct isotp_sock *so, u32 gen, int err) +{ + WRITE_ONCE(so->tx_result[gen % ISOTP_TX_RESULT_SLOTS], + isotp_pack_tx_result(gen, err)); +} + +/* fetch the result recorded for 'gen', as a (negative) errno (0 for success) */ +static int isotp_get_tx_result(struct isotp_sock *so, u32 gen) +{ + u32 result = READ_ONCE(so->tx_result[gen % ISOTP_TX_RESULT_SLOTS]); + + if (isotp_get_tx_gen(result) != gen) { + pr_notice_once("can-isotp: tx_result[] slot reused before read\n"); + + /* report failure rather than risk a false success */ + return -ECOMM; + } + + return -(isotp_get_tx_err(result)); +} + +/* true if done, shut down or superseded ('gen' is no longer the active + * transfer). Reads tx.state first (acquire) so tx_gen/tx_result reads + * below see at least what that state write published (common sequence). + */ +static bool isotp_tx_gen_done(struct isotp_sock *so, u32 gen) +{ + /* read tx.state first for the common sequence */ + u32 state = smp_load_acquire(&so->tx.state); + + return state == ISOTP_IDLE || state == ISOTP_SHUTDOWN || + READ_ONCE(so->tx_gen) != gen; +} + static inline struct isotp_sock *isotp_sk(const struct sock *sk) { return (struct isotp_sock *)sk; @@ -199,7 +268,7 @@ static enum hrtimer_restart isotp_rx_timer_handler(struct hrtimer *hrtimer) rxtimer); struct sock *sk = &so->sk; - if (so->rx.state == ISOTP_WAIT_DATA) { + if (READ_ONCE(so->rx.state) == ISOTP_WAIT_DATA) { /* we did not get new data frames in time */ /* report 'connection timed out' */ @@ -208,7 +277,7 @@ static enum hrtimer_restart isotp_rx_timer_handler(struct hrtimer *hrtimer) sk_error_report(sk); /* reset rx state */ - so->rx.state = ISOTP_IDLE; + WRITE_ONCE(so->rx.state, ISOTP_IDLE); } return HRTIMER_NORESTART; @@ -372,20 +441,19 @@ static void isotp_send_cframe(struct isotp_sock *so); static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) { struct sock *sk = &so->sk; + int tx_err = EBADMSG; /* default for unknown FC status */ - if (so->tx.state != ISOTP_WAIT_FC && - so->tx.state != ISOTP_WAIT_FIRST_FC) + if (READ_ONCE(so->tx.state) != ISOTP_WAIT_FC && + READ_ONCE(so->tx.state) != ISOTP_WAIT_FIRST_FC) return 0; hrtimer_cancel(&so->txtimer); /* isotp_tx_timeout() may have given up on this job while - * hrtimer_cancel() above waited for it to finish; so->rx_lock - * (held by our caller isotp_rcv()) rules out a concurrent claim, - * so a plain recheck is enough here. + * hrtimer_cancel() above waited for it to finish => recheck */ - if (so->tx.state != ISOTP_WAIT_FC && - so->tx.state != ISOTP_WAIT_FIRST_FC) + if (READ_ONCE(so->tx.state) != ISOTP_WAIT_FC && + READ_ONCE(so->tx.state) != ISOTP_WAIT_FIRST_FC) return 1; if ((cf->len < ae + FC_CONTENT_SZ) || @@ -396,13 +464,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) if (!sock_flag(sk, SOCK_DEAD)) sk_error_report(sk); - so->tx.state = ISOTP_IDLE; + isotp_set_tx_result(so, so->tx_gen, EBADMSG); + /* set to IDLE after publishing tx_result */ + smp_store_release(&so->tx.state, ISOTP_IDLE); wake_up_interruptible(&so->wait); return 1; } /* get static/dynamic communication params from first/every FC frame */ - if (so->tx.state == ISOTP_WAIT_FIRST_FC || + if (READ_ONCE(so->tx.state) == ISOTP_WAIT_FIRST_FC || so->opt.flags & CAN_ISOTP_DYN_FC_PARMS) { so->txfc.bs = cf->data[ae + 1]; so->txfc.stmin = cf->data[ae + 2]; @@ -426,13 +496,13 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) so->tx_gap = ktime_add_ns(so->tx_gap, (so->txfc.stmin - 0xF0) * 100000); - so->tx.state = ISOTP_WAIT_FC; + WRITE_ONCE(so->tx.state, ISOTP_WAIT_FC); } switch (cf->data[ae] & 0x0F) { case ISOTP_FC_CTS: so->tx.bs = 0; - so->tx.state = ISOTP_SENDING; + WRITE_ONCE(so->tx.state, ISOTP_SENDING); /* send CF frame and enable echo timeout handling */ hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); @@ -447,14 +517,19 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) case ISOTP_FC_OVFLW: /* overflow on receiver side - report 'message too long' */ - sk->sk_err = EMSGSIZE; - if (!sock_flag(sk, SOCK_DEAD)) - sk_error_report(sk); + tx_err = EMSGSIZE; fallthrough; default: - /* stop this tx job */ - so->tx.state = ISOTP_IDLE; + /* reserved/unknown flow status (tx_err defaults to EBADMSG) */ + + sk->sk_err = tx_err; + if (!sock_flag(sk, SOCK_DEAD)) + sk_error_report(sk); + + isotp_set_tx_result(so, so->tx_gen, tx_err); + /* set to IDLE after publishing tx_result */ + smp_store_release(&so->tx.state, ISOTP_IDLE); wake_up_interruptible(&so->wait); } return 0; @@ -467,7 +542,7 @@ static int isotp_rcv_sf(struct sock *sk, struct canfd_frame *cf, int pcilen, struct sk_buff *nskb; hrtimer_cancel(&so->rxtimer); - so->rx.state = ISOTP_IDLE; + WRITE_ONCE(so->rx.state, ISOTP_IDLE); if (!len || len > cf->len - pcilen) return 1; @@ -501,7 +576,7 @@ static int isotp_rcv_ff(struct sock *sk, struct canfd_frame *cf, int ae) int ff_pci_sz; hrtimer_cancel(&so->rxtimer); - so->rx.state = ISOTP_IDLE; + WRITE_ONCE(so->rx.state, ISOTP_IDLE); /* get the used sender LL_DL from the (first) CAN frame data length */ so->rx.ll_dl = padlen(cf->len); @@ -555,7 +630,7 @@ static int isotp_rcv_ff(struct sock *sk, struct canfd_frame *cf, int ae) /* initial setup for this pdu reception */ so->rx.sn = 1; - so->rx.state = ISOTP_WAIT_DATA; + WRITE_ONCE(so->rx.state, ISOTP_WAIT_DATA); /* no creation of flow control frames */ if (so->opt.flags & CAN_ISOTP_LISTEN_MODE) @@ -573,7 +648,7 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, struct sk_buff *nskb; int i; - if (so->rx.state != ISOTP_WAIT_DATA) + if (READ_ONCE(so->rx.state) != ISOTP_WAIT_DATA) return 0; /* drop if timestamp gap is less than force_rx_stmin nano secs */ @@ -588,11 +663,9 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, hrtimer_cancel(&so->rxtimer); /* isotp_rx_timer_handler() may have raced us for so->rx.state - * while hrtimer_cancel() above waited for it to finish, already - * reporting ETIMEDOUT and resetting the reception; don't process - * this CF into a reassembly that has already been given up on. + * while hrtimer_cancel() above waited for it to finish => recheck */ - if (so->rx.state != ISOTP_WAIT_DATA) + if (READ_ONCE(so->rx.state) != ISOTP_WAIT_DATA) return 1; /* CFs are never longer than the FF */ @@ -613,7 +686,7 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, sk_error_report(sk); /* reset rx state */ - so->rx.state = ISOTP_IDLE; + WRITE_ONCE(so->rx.state, ISOTP_IDLE); return 1; } so->rx.sn++; @@ -627,7 +700,7 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, if (so->rx.idx >= so->rx.len) { /* we are done */ - so->rx.state = ISOTP_IDLE; + WRITE_ONCE(so->rx.state, ISOTP_IDLE); if ((so->opt.flags & ISOTP_CHECK_PADDING) && check_pad(so, cf, i + 1, so->opt.rxpad_content)) { @@ -698,8 +771,10 @@ static void isotp_rcv(struct sk_buff *skb, void *data) if (so->opt.flags & CAN_ISOTP_HALF_DUPLEX) { /* check rx/tx path half duplex expectations */ - if ((so->tx.state != ISOTP_IDLE && n_pci_type != N_PCI_FC) || - (so->rx.state != ISOTP_IDLE && n_pci_type == N_PCI_FC)) + if ((READ_ONCE(so->tx.state) != ISOTP_IDLE && + n_pci_type != N_PCI_FC) || + (READ_ONCE(so->rx.state) != ISOTP_IDLE && + n_pci_type == N_PCI_FC)) goto out_unlock; } @@ -794,6 +869,7 @@ static void isotp_send_cframe(struct isotp_sock *so) struct canfd_frame *cf; int can_send_ret; int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; + u32 old_cfecho; dev = dev_get_by_index(sock_net(sk), so->ifindex); if (!dev) @@ -814,6 +890,9 @@ static void isotp_send_cframe(struct isotp_sock *so) csx->can_iif = dev->ifindex; + /* set uid in tx skb to identify CF echo frames */ + can_set_skb_uid(skb); + cf = (struct canfd_frame *)skb->data; skb_put_zero(skb, so->ll.mtu); @@ -830,12 +909,15 @@ static void isotp_send_cframe(struct isotp_sock *so) skb->dev = dev; can_skb_set_owner(skb, sk); - /* cfecho should have been zero'ed by init/isotp_rcv_echo() */ - if (so->cfecho) - pr_notice_once("can-isotp: cfecho is %08X != 0\n", so->cfecho); + /* zero'ed by init/isotp_rcv_echo(); reached lock-free via + * isotp_txfr_timer_handler() too, so use READ_ONCE()/WRITE_ONCE() + */ + old_cfecho = READ_ONCE(so->cfecho); + if (old_cfecho) + pr_notice_once("can-isotp: cfecho is %08X != 0\n", old_cfecho); /* set consecutive frame echo tag */ - so->cfecho = *(u32 *)cf->data; + WRITE_ONCE(so->cfecho, skb->hash); /* send frame with local echo enabled */ can_send_ret = can_send(skb, 1); @@ -887,7 +969,6 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) { struct sock *sk = (struct sock *)data; struct isotp_sock *so = isotp_sk(sk); - struct canfd_frame *cf = (struct canfd_frame *)skb->data; /* only handle my own local echo CF/SF skb's (no FF!) */ if (skb->sk != sk) @@ -899,32 +980,35 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) spin_lock(&so->rx_lock); /* so->cfecho may since belong to a new transfer; recheck under lock */ - if (so->cfecho != *(u32 *)cf->data) + if (READ_ONCE(so->cfecho) != skb->hash) goto out_unlock; /* cancel local echo timeout */ hrtimer_cancel(&so->echotimer); /* local echo skb with consecutive frame has been consumed */ - so->cfecho = 0; + WRITE_ONCE(so->cfecho, 0); /* claiming a transfer also takes so->rx_lock, so a plain recheck * is enough: so->tx.state can't have flipped to ISOTP_SENDING for * a new claim while we're still in here */ - if (so->tx.state != ISOTP_SENDING) + if (READ_ONCE(so->tx.state) != ISOTP_SENDING) goto out_unlock; if (so->tx.idx >= so->tx.len) { /* we are done */ - so->tx.state = ISOTP_IDLE; + + isotp_set_tx_result(so, so->tx_gen, 0); + /* set to IDLE after publishing tx_result */ + smp_store_release(&so->tx.state, ISOTP_IDLE); wake_up_interruptible(&so->wait); goto out_unlock; } if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { /* stop and wait for FC with timeout */ - so->tx.state = ISOTP_WAIT_FC; + WRITE_ONCE(so->tx.state, ISOTP_WAIT_FC); hrtimer_start(&so->txtimer, ktime_set(ISOTP_FC_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); goto out_unlock; @@ -946,16 +1030,20 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) spin_unlock(&so->rx_lock); } -/* shared by so->txtimer's and so->echotimer's callbacks. Both timers get - * cancelled under so->rx_lock elsewhere, so this must stay lock-free to - * avoid deadlocking with that; uses so->tx_gen instead to avoid tainting - * a new transfer with an error from the one that just timed out. +/* isotp_tx_timeout: we did not get any flow control or echo frame in time + * + * Shared by so->txtimer's and so->echotimer's callbacks. Both timers get + * cancelled under so->rx_lock elsewhere, so this must stay lock-free. + * + * tx.state is acquired before tx_gen. Common sequence in isotp_tx_gen_done(). + * cmpxchg() only orders itself, not the two preceding loads. */ static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so) { struct sock *sk = &so->sk; + /* read tx.state first for the common sequence */ + u32 old_state = smp_load_acquire(&so->tx.state); u32 gen = READ_ONCE(so->tx_gen); - u32 old_state = READ_ONCE(so->tx.state); /* don't handle timeouts in IDLE or SHUTDOWN state */ if (old_state == ISOTP_IDLE || old_state == ISOTP_SHUTDOWN) @@ -965,14 +1053,14 @@ static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so) if (cmpxchg(&so->tx.state, old_state, ISOTP_IDLE) != old_state) return HRTIMER_NORESTART; - /* we did not get any flow control or echo frame in time */ + /* detected timeout: report 'communication error on send' */ - if (READ_ONCE(so->tx_gen) == gen) { - /* report 'communication error on send' */ - sk->sk_err = ECOMM; - if (!sock_flag(sk, SOCK_DEAD)) - sk_error_report(sk); - } + /* a stale read of this slot by a waiter still falls back to ECOMM */ + isotp_set_tx_result(so, gen, ECOMM); + + sk->sk_err = ECOMM; + if (!sock_flag(sk, SOCK_DEAD)) + sk_error_report(sk); wake_up_interruptible(&so->wait); @@ -1007,7 +1095,7 @@ static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer) HRTIMER_MODE_REL_SOFT); /* cfecho should be consumed by isotp_rcv_echo() here */ - if (so->tx.state == ISOTP_SENDING && !so->cfecho) + if (READ_ONCE(so->tx.state) == ISOTP_SENDING && !READ_ONCE(so->cfecho)) isotp_send_cframe(so); return HRTIMER_NORESTART; @@ -1026,10 +1114,12 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT; struct hrtimer *tx_hrt = &so->echotimer; u32 new_state = ISOTP_SENDING; + u32 my_gen; + u32 old_cfecho; int off; int err; - if (!so->bound || so->tx.state == ISOTP_SHUTDOWN) + if (!so->bound || READ_ONCE(so->tx.state) == ISOTP_SHUTDOWN) return -EADDRNOTAVAIL; /* claim the socket under so->rx_lock: this serializes the claim @@ -1046,29 +1136,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) if (msg->msg_flags & MSG_DONTWAIT) return -EAGAIN; - if (so->tx.state == ISOTP_SHUTDOWN) + if (READ_ONCE(so->tx.state) == ISOTP_SHUTDOWN) return -EADDRNOTAVAIL; /* wait for complete transmission of current pdu */ err = wait_event_interruptible(so->wait, - so->tx.state == ISOTP_IDLE); + READ_ONCE(so->tx.state) == ISOTP_IDLE || + READ_ONCE(so->tx.state) == ISOTP_SHUTDOWN); if (err) return err; } - /* new transfer: bump so->tx_gen and drain the old one's timers, - * still under the so->rx_lock we just claimed the socket with - */ - WRITE_ONCE(so->tx.state, ISOTP_SENDING); - WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1); + /* txfrtimer's callback re-arms echotimer lock-free: drain it first */ + hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); hrtimer_cancel(&so->echotimer); - hrtimer_cancel(&so->txfrtimer); - so->cfecho = 0; + + /* new transfer: increment so->tx_gen and set tx.state after barrier */ + my_gen = isotp_inc_tx_gen(READ_ONCE(so->tx_gen)); + isotp_set_tx_result(so, my_gen, ECOMM); /* prevent stale slot matching */ + WRITE_ONCE(so->tx_gen, my_gen); + smp_wmb(); /* see smp_load_acquire() in isotp_tx_[timeout|gen_done] */ + WRITE_ONCE(so->tx.state, ISOTP_SENDING); + WRITE_ONCE(so->cfecho, 0); spin_unlock_bh(&so->rx_lock); /* so->bound is only checked once above - a wakeup may have - * unbound/rebound the socket meanwhile, so re-validate it + * unbound/rebound the socket meanwhile => recheck */ if (!so->bound) { err = -EADDRNOTAVAIL; @@ -1127,6 +1221,9 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) csx->can_iif = dev->ifindex; + /* set uid in tx skb to identify CF echo frames */ + can_set_skb_uid(skb); + so->tx.len = size; so->tx.idx = 0; @@ -1134,8 +1231,9 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) skb_put_zero(skb, so->ll.mtu); /* cfecho should have been zero'ed by init / former isotp_rcv_echo() */ - if (so->cfecho) - pr_notice_once("can-isotp: uninit cfecho %08X\n", so->cfecho); + old_cfecho = READ_ONCE(so->cfecho); + if (old_cfecho) + pr_notice_once("can-isotp: uninit cfecho %08X\n", old_cfecho); /* check for single frame transmission depending on TX_DL */ if (size <= so->tx.ll_dl - SF_PCI_SZ4 - ae - off) { @@ -1163,7 +1261,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) cf->data[ae] |= size; /* set CF echo tag for isotp_rcv_echo() (SF-mode) */ - so->cfecho = *(u32 *)cf->data; + WRITE_ONCE(so->cfecho, skb->hash); } else { /* send first frame */ @@ -1180,7 +1278,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) so->txfc.bs = 0; /* set CF echo tag for isotp_rcv_echo() (CF-mode) */ - so->cfecho = *(u32 *)cf->data; + WRITE_ONCE(so->cfecho, skb->hash); } else { /* standard flow control check */ new_state = ISOTP_WAIT_FIRST_FC; @@ -1190,12 +1288,12 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) tx_hrt = &so->txtimer; /* no CF echo tag for isotp_rcv_echo() (FF-mode) */ - so->cfecho = 0; + WRITE_ONCE(so->cfecho, 0); } } spin_lock_bh(&so->rx_lock); - if (so->tx.state == ISOTP_SHUTDOWN) { + if (READ_ONCE(so->tx.state) == ISOTP_SHUTDOWN) { /* isotp_release() has since taken over and already drained * our timers - don't send into a socket that's going away */ @@ -1206,7 +1304,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) return -EADDRNOTAVAIL; } /* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */ - so->tx.state = new_state; + WRITE_ONCE(so->tx.state, new_state); hrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0), HRTIMER_MODE_REL_SOFT); spin_unlock_bh(&so->rx_lock); @@ -1223,20 +1321,49 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) __func__, ERR_PTR(err)); spin_lock_bh(&so->rx_lock); + + /* new transfer already claimed by a concurrent completion, + * timeout or sendmsg() while we were stuck in can_send()? + */ + if (READ_ONCE(so->tx_gen) != my_gen) { + /* don't touch timers and state of the new transfer */ + spin_unlock_bh(&so->rx_lock); + return err; + } + /* no transmission -> no timeout monitoring */ hrtimer_cancel(tx_hrt); goto err_out_drop_locked; } if (wait_tx_done) { - /* wait for complete transmission of current pdu */ - err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); + /* wake up for: + * - concurrent sendmsg() claiming a new transfer + * - complete transmission of current PDU + * - shutdown state change in isotp_release() + * isotp_tx_gen_done() uses common tx.state/tx_gen read sequence + */ + err = wait_event_interruptible(so->wait, + isotp_tx_gen_done(so, my_gen)); if (err) goto err_event_drop; - err = sock_error(sk); - if (err) - return err; + /* still our claim, but isotp_release() force-shut it down */ + if (smp_load_acquire(&so->tx.state) == ISOTP_SHUTDOWN && + READ_ONCE(so->tx_gen) == my_gen) { + err = -EADDRNOTAVAIL; + goto err_event_drop; + } + + /* own completion, or tx_gen moved on - either way this is + * what isotp_get_tx_result() recorded for my_gen + */ + err = isotp_get_tx_result(so, my_gen); + + /* drain to avoid stale error for a later poll()/SO_ERROR */ + sock_error(sk); + + return err ? err : size; } return size; @@ -1246,15 +1373,26 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) spin_lock_bh(&so->rx_lock); goto err_out_drop_locked; err_event_drop: - /* interrupted waiting on our own transfer - drain its timers */ + /* interrupted or shut down while waiting on our own transfer */ spin_lock_bh(&so->rx_lock); + + /* new transfer already started by concurrent sendmsg()? */ + if (READ_ONCE(so->tx_gen) != my_gen) { + /* don't touch timers and states of the new transfer */ + spin_unlock_bh(&so->rx_lock); + return err; + } + hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); hrtimer_cancel(&so->echotimer); err_out_drop_locked: /* release the claim; so->rx_lock still held from above */ - so->cfecho = 0; - so->tx.state = ISOTP_IDLE; + WRITE_ONCE(so->cfecho, 0); + + /* only claim to IDLE if isotp_release() has not taken over */ + if (READ_ONCE(so->tx.state) != ISOTP_SHUTDOWN) + WRITE_ONCE(so->tx.state, ISOTP_IDLE); spin_unlock_bh(&so->rx_lock); wake_up_interruptible(&so->wait); @@ -1320,8 +1458,9 @@ static int isotp_release(struct socket *sock) /* best-effort: wait for a running pdu to finish, but don't block on * it forever - give up after the first signal */ - while (so->tx.state != ISOTP_IDLE && - wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0) + while (READ_ONCE(so->tx.state) != ISOTP_IDLE && + wait_event_interruptible(so->wait, + READ_ONCE(so->tx.state) == ISOTP_IDLE) == 0) ; /* claim the socket under so->rx_lock like sendmsg() does, so its @@ -1329,9 +1468,12 @@ static int isotp_release(struct socket *sock) * unconditionally, even when a signal cut the wait above short */ spin_lock_bh(&so->rx_lock); - so->tx.state = ISOTP_SHUTDOWN; + WRITE_ONCE(so->tx.state, ISOTP_SHUTDOWN); spin_unlock_bh(&so->rx_lock); - so->rx.state = ISOTP_IDLE; + WRITE_ONCE(so->rx.state, ISOTP_IDLE); + + /* forced SHUTDOWN may have skipped IDLE (gave up on a signal) */ + wake_up_interruptible(&so->wait); spin_lock(&isotp_notifier_lock); while (isotp_busy_notifier == so) { @@ -1447,7 +1589,8 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l * with so->bound in the same lock_sock() section above, so there is * no window in which a concurrent isotp_notify() could be missed. */ - if (so->tx.state != ISOTP_IDLE || so->rx.state != ISOTP_IDLE) { + if (READ_ONCE(so->tx.state) != ISOTP_IDLE || + READ_ONCE(so->rx.state) != ISOTP_IDLE) { err = -EAGAIN; goto out; } @@ -1481,7 +1624,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l isotp_rcv, sk, "isotp", sk); /* no consecutive frame echo skb in flight */ - so->cfecho = 0; + WRITE_ONCE(so->cfecho, 0); /* register for echo skb's */ can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id), @@ -1847,7 +1990,7 @@ static __poll_t isotp_poll(struct file *file, struct socket *sock, poll_table *w poll_wait(file, &so->wait, wait); /* Check for false positives due to TX state */ - if ((mask & EPOLLWRNORM) && (so->tx.state != ISOTP_IDLE)) + if ((mask & EPOLLWRNORM) && (READ_ONCE(so->tx.state) != ISOTP_IDLE)) mask &= ~(EPOLLOUT | EPOLLWRNORM); return mask; From c870f7e2890b9f78ac84515a9809cc5c183c975e Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Thu, 23 Jul 2026 10:18:19 +0800 Subject: [PATCH 410/456] net: sxgbe: free TX rings on RX allocation failure When RX descriptor ring allocation fails, init_dma_desc_rings() only frees the partially allocated RX rings and returns. The TX rings that were allocated earlier in the same function are leaked. Rearrange error labels to clean up TX rings upon RX failures. Fixes: 1edb9ca69e8a ("net: sxgbe: add basic framework for Samsung 10Gb ethernet driver") Signed-off-by: Chenguang Zhao Reviewed-by: Vadim Fedorenko Signed-off-by: David S. Miller --- drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c b/drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c index 5051ada43d2f..9b48a587d5c2 100644 --- a/drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c +++ b/drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c @@ -597,14 +597,13 @@ static int init_dma_desc_rings(struct net_device *netd) return 0; -txalloc_err: - while (queue_num--) - free_tx_ring(priv->device, priv->txq[queue_num], tx_rsize); - return ret; - rxalloc_err: while (queue_num--) free_rx_ring(priv->device, priv->rxq[queue_num], rx_rsize); + queue_num = SXGBE_TX_QUEUES; +txalloc_err: + while (queue_num--) + free_tx_ring(priv->device, priv->txq[queue_num], tx_rsize); return ret; } From 51b093a7ba27476e1f639455f005e8d2e75390e4 Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Thu, 23 Jul 2026 10:18:20 +0800 Subject: [PATCH 411/456] net: sxgbe: check descriptor ring allocation failures sxgbe_open() ignores the return value of init_dma_desc_rings() and continues to program DMA with invalid ring addresses when allocation fails. Check the return value and disconnect the PHY on failure. Fixes: 1edb9ca69e8a ("net: sxgbe: add basic framework for Samsung 10Gb ethernet driver") Signed-off-by: Chenguang Zhao Reviewed-by: Vadim Fedorenko Signed-off-by: David S. Miller --- drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c b/drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c index 9b48a587d5c2..70cf3619555f 100644 --- a/drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c +++ b/drivers/net/ethernet/samsung/sxgbe/sxgbe_main.c @@ -1078,7 +1078,9 @@ static int sxgbe_open(struct net_device *dev) priv->dma_buf_sz = SXGBE_ALIGN(DMA_BUFFER_SIZE); priv->tx_tc = TC_DEFAULT; priv->rx_tc = TC_DEFAULT; - init_dma_desc_rings(dev); + ret = init_dma_desc_rings(dev); + if (ret) + goto init_phy_error; /* DMA initialization and SW reset */ ret = sxgbe_init_dma_engine(priv); @@ -1187,6 +1189,7 @@ static int sxgbe_open(struct net_device *dev) init_error: free_dma_desc_resources(priv); +init_phy_error: if (dev->phydev) phy_disconnect(dev->phydev); phy_error: From ef09a13c5afac41a3c4b5f22b8572820d9e7518c Mon Sep 17 00:00:00 2001 From: Minhong He Date: Wed, 29 Jul 2026 16:56:56 +0800 Subject: [PATCH 412/456] can: isotp: check register_netdevice_notifier() error in module init Register the netdevice notifier before can_proto_register() and check the return value. If protocol registration fails, unregister the notifier before returning the error. Align isotp_module_init() with the reordering already done for raw.c (commit c28b3bffe49e ("can: raw: process optimization in raw_init()")) and bcm.c (commit edd1a7e42f1d ("can: bcm: registration process optimization in bcm_module_init()")). Fixes: 8d0caedb7596 ("can: bcm/raw/isotp: use per module netdevice notifier") Signed-off-by: Minhong He Link: https://patch.msgid.link/20260729085656.134523-1-heminhong@kylinos.cn Signed-off-by: Marc Kleine-Budde --- net/can/isotp.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/net/can/isotp.c b/net/can/isotp.c index 1f11c66b343c..155530aedce2 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -2050,13 +2050,18 @@ static __init int isotp_module_init(void) pr_info("can: isotp protocol (max_pdu_size %d)\n", max_pdu_size); - err = can_proto_register(&isotp_can_proto); - if (err < 0) - pr_err("can: registration of isotp protocol failed %pe\n", ERR_PTR(err)); - else - register_netdevice_notifier(&canisotp_notifier); + err = register_netdevice_notifier(&canisotp_notifier); + if (err) + return err; - return err; + err = can_proto_register(&isotp_can_proto); + if (err < 0) { + pr_err("can: registration of isotp protocol failed %pe\n", ERR_PTR(err)); + unregister_netdevice_notifier(&canisotp_notifier); + return err; + } + + return 0; } static __exit void isotp_module_exit(void) From a6873910f983096746d1a2e0af94f36b8003e839 Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Thu, 23 Jul 2026 12:59:34 +0300 Subject: [PATCH 413/456] can: ctucanfd: unmap BAR0 using base address BAR0 is mapped into bar0_base, while cra_addr points to an offset within that mapping and is used for other purposes. Pass bar0_base to pci_iounmap(), instead of cra_addr, on the probe error path so the address returned by pci_iomap() is used for unmapping. Fixes: 792a5b678e81 ("can: ctucanfd: CTU CAN FD open-source IP core - PCI bus support.") Signed-off-by: Avi Weiss Acked-by: Pavel Pisa Link: https://patch.msgid.link/20260723095934.181042-1-thnkslprpt@gmail.com Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/ctucanfd/ctucanfd_pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/ctucanfd/ctucanfd_pci.c b/drivers/net/can/ctucanfd/ctucanfd_pci.c index 625788fa8976..f845951c5d51 100644 --- a/drivers/net/can/ctucanfd/ctucanfd_pci.c +++ b/drivers/net/can/ctucanfd/ctucanfd_pci.c @@ -194,7 +194,7 @@ static int ctucan_pci_probe(struct pci_dev *pdev, pci_set_drvdata(pdev, NULL); kfree(bdata); err_pci_iounmap_bar0: - pci_iounmap(pdev, cra_addr); + pci_iounmap(pdev, bar0_base); err_pci_iounmap_bar1: pci_iounmap(pdev, addr); err_release_regions: From 4e735cbe3affe88001428fdd9cae8e685ce92f21 Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Thu, 23 Jul 2026 18:55:43 +0300 Subject: [PATCH 414/456] can: ctucanfd: mark error-active controller status valid In the CAN_STATE_ERROR_ACTIVE case, cf->data[1] is set to CAN_ERR_CRTL_ACTIVE, but cf->can_id is not set with CAN_ERR_CRTL in that path. Set CAN_ERR_CRTL so consumers know the controller-status information in cf->data[1] is valid. Fixes: 9bd24927e3ee ("can: ctucanfd: handle skb allocation failure") Signed-off-by: Avi Weiss Link: https://patch.msgid.link/20260723155543.318414-1-thnkslprpt@gmail.com Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/ctucanfd/ctucanfd_base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/ctucanfd/ctucanfd_base.c b/drivers/net/can/ctucanfd/ctucanfd_base.c index 0ea1ff28dfce..8f8b1c097ec6 100644 --- a/drivers/net/can/ctucanfd/ctucanfd_base.c +++ b/drivers/net/can/ctucanfd/ctucanfd_base.c @@ -869,7 +869,7 @@ static void ctucan_err_interrupt(struct net_device *ndev, u32 isr) break; case CAN_STATE_ERROR_ACTIVE: if (skb) { - cf->can_id |= CAN_ERR_CNT; + cf->can_id |= CAN_ERR_CRTL | CAN_ERR_CNT; cf->data[1] = CAN_ERR_CRTL_ACTIVE; cf->data[6] = bec.txerr; cf->data[7] = bec.rxerr; From e74bae899529f49c0f375307983d12e8ecad7d4b Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Thu, 23 Jul 2026 10:44:03 +0300 Subject: [PATCH 415/456] can: ctucanfd: handle bus error interrupts Include REG_INT_STAT_BEI in the top-level error interrupt condition. BEI is enabled when CAN_CTRLMODE_BERR_REPORTING is requested and ctucan_err_interrupt() already handles it. Without checking and clearing BEI in the top-level handler, bus error interrupts are not handled or acknowledged. Fixes: 2dcb8e8782d8 ("can: ctucanfd: add support for CTU CAN FD open-source IP core - bus independent part.") Signed-off-by: Avi Weiss Acked-by: Pavel Pisa Link: https://patch.msgid.link/20260723074403.131575-1-thnkslprpt@gmail.com Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/ctucanfd/ctucanfd_base.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/ctucanfd/ctucanfd_base.c b/drivers/net/can/ctucanfd/ctucanfd_base.c index 8f8b1c097ec6..10ebcc13ea65 100644 --- a/drivers/net/can/ctucanfd/ctucanfd_base.c +++ b/drivers/net/can/ctucanfd/ctucanfd_base.c @@ -1136,8 +1136,12 @@ static irqreturn_t ctucan_interrupt(int irq, void *dev_id) /* Error interrupts */ if (FIELD_GET(REG_INT_STAT_EWLI, isr) || FIELD_GET(REG_INT_STAT_FCSI, isr) || - FIELD_GET(REG_INT_STAT_ALI, isr)) { - icr = isr & (REG_INT_STAT_EWLI | REG_INT_STAT_FCSI | REG_INT_STAT_ALI); + FIELD_GET(REG_INT_STAT_ALI, isr) || + FIELD_GET(REG_INT_STAT_BEI, isr)) { + icr = isr & (REG_INT_STAT_EWLI | + REG_INT_STAT_FCSI | + REG_INT_STAT_ALI | + REG_INT_STAT_BEI); ctucan_netdev_dbg(ndev, "some ERR interrupt: clearing 0x%08x\n", icr); ctucan_write32(priv, CTUCANFD_INT_STAT, icr); From c31a435933f18be0f874302161333e9f16e200a0 Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Wed, 22 Jul 2026 22:27:26 +0300 Subject: [PATCH 416/456] can: ctucanfd: use self-test mode for PRESUME_ACK Use self-test mode for CAN_CTRLMODE_PRESUME_ACK so transmitted frames can complete without receiving an ACK. ACK forbidden mode prevents the controller from acknowledging received frames and does not implement the presume-ack behavior. Fixes: 2dcb8e8782d8 ("can: ctucanfd: add support for CTU CAN FD open-source IP core - bus independent part.") Signed-off-by: Avi Weiss Acked-by: Pavel Pisa Link: https://patch.msgid.link/20260722192726.230729-1-thnkslprpt@gmail.com Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/ctucanfd/ctucanfd_base.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/ctucanfd/ctucanfd_base.c b/drivers/net/can/ctucanfd/ctucanfd_base.c index 10ebcc13ea65..07d4aa43c700 100644 --- a/drivers/net/can/ctucanfd/ctucanfd_base.c +++ b/drivers/net/can/ctucanfd/ctucanfd_base.c @@ -340,8 +340,8 @@ static void ctucan_set_mode(struct ctucan_priv *priv, const struct can_ctrlmode (mode_reg & ~REG_MODE_FDE); mode_reg = (mode->flags & CAN_CTRLMODE_PRESUME_ACK) ? - (mode_reg | REG_MODE_ACF) : - (mode_reg & ~REG_MODE_ACF); + (mode_reg | REG_MODE_STM) : + (mode_reg & ~REG_MODE_STM); mode_reg = (mode->flags & CAN_CTRLMODE_FD_NON_ISO) ? (mode_reg | REG_MODE_NISOFD) : From d937bdb244a751fe5967052ea2d64a7b2c476cc0 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 23:19:57 +0800 Subject: [PATCH 417/456] can: ctucanfd: add missing MODULE_DEVICE_TABLE() The driver has a match table for the pci bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Acked-by: Pavel Pisa Link: https://patch.msgid.link/20260704151957.48194-1-pengpeng@iscas.ac.cn Fixes: 792a5b678e81 ("can: ctucanfd: CTU CAN FD open-source IP core - PCI bus support.") Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/ctucanfd/ctucanfd_pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/can/ctucanfd/ctucanfd_pci.c b/drivers/net/can/ctucanfd/ctucanfd_pci.c index f845951c5d51..4b6db28f7b67 100644 --- a/drivers/net/can/ctucanfd/ctucanfd_pci.c +++ b/drivers/net/can/ctucanfd/ctucanfd_pci.c @@ -266,6 +266,7 @@ static const struct pci_device_id ctucan_pci_tbl[] = { CTUCAN_WITH_CTUCAN_ID)}, {}, }; +MODULE_DEVICE_TABLE(pci, ctucan_pci_tbl); static struct pci_driver ctucan_pci_driver = { .name = KBUILD_MODNAME, From 39132f166ca8ce00ae60d8a9068e06a60943cc4b Mon Sep 17 00:00:00 2001 From: James Gao Date: Wed, 20 May 2026 13:40:03 +0800 Subject: [PATCH 418/456] can: peak_usb: add bounds check for USB channel index The channel control index ctrl_idx is derived from rx->len which comes directly from a device USB payload. The mask 0x0f allows values 0-15, but the array size of usb_if->dev[] is only 2. Values 2-15 cause heap out-of-bounds read, eventually causing kernel panic in the IRQ context. Add bounds checking for ctrl_idx before the array access in both pcan_usb_pro_handle_canmsg() and pcan_usb_pro_handle_error(). Fixes: d8a199355f8f ("can: usb: PEAK-System Technik PCAN-USB Pro specific part") Signed-off-by: James Gao Reviewed-by: Vincent Mailhol Link: https://patch.msgid.link/TYWPR01MB8559DBAAAA6A7F410400329CF0012@TYWPR01MB8559.jpnprd01.prod.outlook.com Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/peak_usb/pcan_usb_pro.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c index aefcded8e12a..b6be8c19e537 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_pro.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_pro.c @@ -534,12 +534,18 @@ static int pcan_usb_pro_handle_canmsg(struct pcan_usb_pro_interface *usb_if, struct pcan_usb_pro_rxmsg *rx) { const unsigned int ctrl_idx = (rx->len >> 4) & 0x0f; - struct peak_usb_device *dev = usb_if->dev[ctrl_idx]; - struct net_device *netdev = dev->netdev; + struct peak_usb_device *dev; + struct net_device *netdev; struct can_frame *can_frame; struct sk_buff *skb; struct skb_shared_hwtstamps *hwts; + if (ctrl_idx >= ARRAY_SIZE(usb_if->dev)) + return -EINVAL; + + dev = usb_if->dev[ctrl_idx]; + netdev = dev->netdev; + skb = alloc_can_skb(netdev, &can_frame); if (!skb) return -ENOMEM; @@ -573,14 +579,20 @@ static int pcan_usb_pro_handle_error(struct pcan_usb_pro_interface *usb_if, { const u16 raw_status = le16_to_cpu(er->status); const unsigned int ctrl_idx = (er->channel >> 4) & 0x0f; - struct peak_usb_device *dev = usb_if->dev[ctrl_idx]; - struct net_device *netdev = dev->netdev; + struct peak_usb_device *dev; + struct net_device *netdev; struct can_frame *can_frame; enum can_state new_state = CAN_STATE_ERROR_ACTIVE; u8 err_mask = 0; struct sk_buff *skb; struct skb_shared_hwtstamps *hwts; + if (ctrl_idx >= ARRAY_SIZE(usb_if->dev)) + return -EINVAL; + + dev = usb_if->dev[ctrl_idx]; + netdev = dev->netdev; + /* nothing should be sent while in BUS_OFF state */ if (dev->can.state == CAN_STATE_BUS_OFF) return 0; From 9b3d5a6d952c38bbcf07f903cbeadefdb56b9bc9 Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Wed, 17 Jun 2026 02:15:31 +0800 Subject: [PATCH 419/456] can: peak_usb: peak_usb_start(): fix double free of transfer buffer on URB submit error In peak_usb_start(), each RX URB transfer buffer is allocated with kmalloc() and the URB is flagged URB_FREE_BUFFER so that the final usb_free_urb() also frees the transfer buffer. If usb_submit_urb() fails, the error path frees the buffer explicitly with kfree(buf) and then calls usb_free_urb(urb). Because URB_FREE_BUFFER is set, usb_free_urb() -> urb_destroy() frees the same buffer a second time, a double free of the transfer buffer. BUG: KASAN: double-free in usb_free_urb.part.0+0x91/0xb0 Free of addr ffff8881069ccb80 by task trigger.sh/285 Call Trace: kfree+0x113/0x3c0 usb_free_urb.part.0+0x91/0xb0 Drop the redundant kfree(buf); usb_free_urb() already releases the transfer buffer. This mirrors commit 03819abbeb11 ("net: usb: lan78xx: Fix double free issue with interrupt buffer allocation"). Fixes: bb4785551f64 ("can: usb: PEAK-System Technik USB adapters driver core") Closes: https://lore.kernel.org/linux-can/178159320216.2154888.16953451793788581739@maoyixie.com/T/#u Cc: stable@vger.kernel.org Signed-off-by: Maoyi Xie Reviewed-by: Vincent Mailhol Link: https://patch.msgid.link/178163373110.2507866.216458825145756798@maoyixie.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/peak_usb/pcan_usb_core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_core.c b/drivers/net/can/usb/peak_usb/pcan_usb_core.c index c7933d1acc99..55aad01cd8ca 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_core.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_core.c @@ -470,7 +470,6 @@ static int peak_usb_start(struct peak_usb_device *dev) netif_device_detach(dev->netdev); usb_unanchor_urb(urb); - kfree(buf); usb_free_urb(urb); break; } From 93fcab2c6968446316bbb49548848df604d6346f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 6 Jul 2026 17:28:36 +0800 Subject: [PATCH 420/456] can: peak_usb: validate uCAN receive record lengths pcan_usb_fd_decode_buf() walks uCAN records packed in one USB receive buffer. Require each record to contain the fixed header for its type, and verify CAN payload bytes before copying them into the skb. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260706092836.79754-1-pengpeng@iscas.ac.cn Fixes: 0a25e1f4f185 ("can: peak_usb: add support for PEAK new CANFD USB adapters") Cc: stable@vger.kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/peak_usb/pcan_usb_fd.c | 40 +++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c index ef9fd693e9bd..0d46f4ce5dca 100644 --- a/drivers/net/can/usb/peak_usb/pcan_usb_fd.c +++ b/drivers/net/can/usb/peak_usb/pcan_usb_fd.c @@ -566,6 +566,13 @@ static int pcan_usb_fd_decode_canmsg(struct pcan_usb_fd_if *usb_if, dev->can.ctrlmode); } + if (!(rx_msg_flags & PUCAN_MSG_RTR) && + le16_to_cpu(rx_msg->size) - offsetof(struct pucan_rx_msg, d) < + cfd->len) { + kfree_skb(skb); + return -EBADMSG; + } + cfd->can_id = le32_to_cpu(rm->can_id); if (rx_msg_flags & PUCAN_MSG_EXT_ID) @@ -714,6 +721,24 @@ static void pcan_usb_fd_decode_ts(struct pcan_usb_fd_if *usb_if, peak_usb_set_ts_now(&usb_if->time_ref, le32_to_cpu(ts->ts_low)); } +static size_t pcan_usb_fd_rx_msg_min_size(u16 rx_msg_type) +{ + switch (rx_msg_type) { + case PUCAN_MSG_CAN_RX: + return offsetof(struct pucan_rx_msg, d); + case PCAN_UFD_MSG_CALIBRATION: + return sizeof(struct pcan_ufd_ts_msg); + case PUCAN_MSG_ERROR: + return sizeof(struct pucan_error_msg); + case PUCAN_MSG_STATUS: + return sizeof(struct pucan_status_msg); + case PCAN_UFD_MSG_OVERRUN: + return sizeof(struct pcan_ufd_ovr_msg); + default: + return sizeof(struct pucan_msg); + } +} + /* callback for bulk IN urb */ static int pcan_usb_fd_decode_buf(struct peak_usb_device *dev, struct urb *urb) { @@ -728,6 +753,12 @@ static int pcan_usb_fd_decode_buf(struct peak_usb_device *dev, struct urb *urb) msg_end = urb->transfer_buffer + urb->actual_length; for (; msg_ptr < msg_end;) { u16 rx_msg_type, rx_msg_size; + size_t rx_msg_min_size; + + if (msg_end - msg_ptr < sizeof(*rx_msg)) { + err = -EBADMSG; + break; + } rx_msg = (struct pucan_msg *)msg_ptr; if (!rx_msg->size) { @@ -739,13 +770,20 @@ static int pcan_usb_fd_decode_buf(struct peak_usb_device *dev, struct urb *urb) rx_msg_type = le16_to_cpu(rx_msg->type); /* check if the record goes out of current packet */ - if (msg_ptr + rx_msg_size > msg_end) { + if (rx_msg_size > msg_end - msg_ptr) { netdev_err(netdev, "got frag rec: should inc usb rx buf sze\n"); err = -EBADMSG; break; } + rx_msg_min_size = pcan_usb_fd_rx_msg_min_size(rx_msg_type); + if (rx_msg_size < rx_msg_min_size) { + netdev_err(netdev, "got short rec\n"); + err = -EBADMSG; + break; + } + switch (rx_msg_type) { case PUCAN_MSG_CAN_RX: err = pcan_usb_fd_decode_canmsg(usb_if, rx_msg); From 941eaf9a6d3b33dea49f2c0a1da7546a03b6ff71 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Wed, 22 Jul 2026 16:09:03 +0530 Subject: [PATCH 421/456] can: kvaser_usb: kvaser_usb_hydra_get_busparams(): fix memory leak in kvaser_usb_hydra_get_busparams() The memory allocated for cmd is not freed after the call to kvaser_usb_send_cmd() in both the normal and error paths. Fix that by adding a kfree() immediately after the call. Fixes: 39d3df6b0ea8 ("can: kvaser_usb: Compare requested bittiming parameters with actual parameters in do_set_{,data}_bittiming") Cc: stable@vger.kernel.org Signed-off-by: Abdun Nihaal Link: https://patch.msgid.link/20260722103906.108571-1-nihaal@cse.iitm.ac.in Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/kvaser_usb/kvaser_usb_hydra.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/can/usb/kvaser_usb/kvaser_usb_hydra.c b/drivers/net/can/usb/kvaser_usb/kvaser_usb_hydra.c index e09d663e362f..efbb7bed34c9 100644 --- a/drivers/net/can/usb/kvaser_usb/kvaser_usb_hydra.c +++ b/drivers/net/can/usb/kvaser_usb/kvaser_usb_hydra.c @@ -1626,6 +1626,7 @@ static int kvaser_usb_hydra_get_busparams(struct kvaser_usb_net_priv *priv, reinit_completion(&priv->get_busparams_comp); err = kvaser_usb_send_cmd(dev, cmd, cmd_len); + kfree(cmd); if (err) return err; From 0293dd153f9dbc1ddf5dacdccc76b363bce4a8ee Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 22 Jul 2026 12:22:21 +0800 Subject: [PATCH 422/456] can: kvaser_usb_leaf: kvaser_usb_leaf_wait_cmd(): validate received command extents The wait and bulk receive paths walk variable-length commands from a USB buffer. A nonzero command shorter than CMD_HEADER_LEN can still be dispatched, and the wait path copies a matching command into a fixed caller-owned struct kvaser_cmd using the device-provided length. Reject nonzero commands that do not contain the fixed header or that extend beyond the current USB buffer item. In the wait path, also reject a matching command that exceeds the destination before copying it. Fixes: 080f40a6fa28 ("can: kvaser_usb: Add support for Kvaser CAN/USB devices") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260722042221.44066-1-pengpeng@iscas.ac.cn Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c b/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c index df737cfc5ea0..a876c7819b81 100644 --- a/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c +++ b/drivers/net/can/usb/kvaser_usb/kvaser_usb_leaf.c @@ -691,13 +691,22 @@ static int kvaser_usb_leaf_wait_cmd(const struct kvaser_usb *dev, u8 id, continue; } - if (pos + tmp->len > actual_len) { + if (tmp->len < CMD_HEADER_LEN || + tmp->len > actual_len - pos) { dev_err_ratelimited(&dev->intf->dev, "Format error\n"); break; } if (tmp->id == id) { + if (tmp->len > sizeof(*cmd)) { + dev_err_ratelimited(&dev->intf->dev, + "Received command %u too large (%u)\n", + tmp->id, tmp->len); + err = -EIO; + goto end; + } + memcpy(cmd, tmp, tmp->len); goto end; } @@ -1737,7 +1746,7 @@ static void kvaser_usb_leaf_read_bulk_callback(struct kvaser_usb *dev, continue; } - if (pos + cmd->len > len) { + if (cmd->len < CMD_HEADER_LEN || cmd->len > len - pos) { dev_err_ratelimited(&dev->intf->dev, "Format error\n"); break; } From bef9004c5b91debfceaea2841855a4ebe81ff2b3 Mon Sep 17 00:00:00 2001 From: Tu Nguyen Date: Thu, 25 Jun 2026 14:51:51 +0100 Subject: [PATCH 423/456] can: rcar_canfd: change the initializing flow for clocks and resets Testing CANFD on RZ/G3E shows that many registers do not reset to their initial values with the current flow of deasserting resets first and then enabling clocks. Based on the HW manual, clocks should be supplied first and the resets deasserted afterward. section 7.4.3 Procedure for Activating Modules: RZ/G2L section 4.4.9.3 Procedure for Starting up Units: RZ/G3E So, update the order of the initializing flow for resets and clocks to match the hardware manual, resetting all CANFD registers to their initial values. Also update rcar_canfd_global_deinit() to assert resets before disabling clocks, so the teardown path mirrors the new init ordering. Fixes: 76e9353a80e9 ("can: rcar_canfd: Add support for RZ/G2L family") Signed-off-by: Tu Nguyen Signed-off-by: Biju Das Tested-by: Claudiu Beznea Reviewed-by: Geert Uytterhoeven Reviewed-by: Vincent Mailhol Link: https://patch.msgid.link/20260625135216.130450-1-biju.das.jz@bp.renesas.com Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/rcar/rcar_canfd.c | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/net/can/rcar/rcar_canfd.c b/drivers/net/can/rcar/rcar_canfd.c index eaf8cac78038..fcc37b73ed43 100644 --- a/drivers/net/can/rcar/rcar_canfd.c +++ b/drivers/net/can/rcar/rcar_canfd.c @@ -2003,20 +2003,12 @@ static int rcar_canfd_global_init(struct rcar_canfd_global *gpriv) u32 ch, sts; int err; - err = reset_control_reset(gpriv->rstc1); - if (err) - return err; - - err = reset_control_reset(gpriv->rstc2); - if (err) - goto fail_reset1; - /* Enable peripheral clock for register access */ err = clk_prepare_enable(gpriv->clkp); if (err) { dev_err(dev, "failed to enable peripheral clock: %pe\n", ERR_PTR(err)); - goto fail_reset2; + return err; } /* Enable RAM clock */ @@ -2027,10 +2019,18 @@ static int rcar_canfd_global_init(struct rcar_canfd_global *gpriv) goto fail_clk; } + err = reset_control_reset(gpriv->rstc1); + if (err) + goto fail_ram_clk; + + err = reset_control_reset(gpriv->rstc2); + if (err) + goto fail_reset1; + err = rcar_canfd_reset_controller(gpriv); if (err) { dev_err(dev, "reset controller failed: %pe\n", ERR_PTR(err)); - goto fail_ram_clk; + goto fail_reset2; } /* Controller in Global reset & Channel reset mode */ @@ -2068,14 +2068,14 @@ static int rcar_canfd_global_init(struct rcar_canfd_global *gpriv) fail_mode: rcar_canfd_disable_global_interrupts(gpriv); -fail_ram_clk: - clk_disable_unprepare(gpriv->clk_ram); -fail_clk: - clk_disable_unprepare(gpriv->clkp); fail_reset2: reset_control_assert(gpriv->rstc2); fail_reset1: reset_control_assert(gpriv->rstc1); +fail_ram_clk: + clk_disable_unprepare(gpriv->clk_ram); +fail_clk: + clk_disable_unprepare(gpriv->clkp); return err; } @@ -2090,10 +2090,10 @@ static void rcar_canfd_global_deinit(struct rcar_canfd_global *gpriv, bool full) rcar_canfd_set_bit(gpriv->base, RCANFD_GCTR, RCANFD_GCTR_GSLPR); } - clk_disable_unprepare(gpriv->clk_ram); - clk_disable_unprepare(gpriv->clkp); reset_control_assert(gpriv->rstc2); reset_control_assert(gpriv->rstc1); + clk_disable_unprepare(gpriv->clk_ram); + clk_disable_unprepare(gpriv->clkp); } static int rcar_canfd_probe(struct platform_device *pdev) From 856d6cb04e5407523566b075841dcd6423757d1c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 22 Jul 2026 12:43:47 +0800 Subject: [PATCH 424/456] can: softing: fw_parse(): validate firmware record spans fw_parse() reads a fixed record header, a firmware-provided payload, and a trailing checksum without knowing the end of the firmware blob. A truncated record can therefore make those reads exceed the blob. The same record also supplies addresses and lengths for writes into DPRAM. The generic loader uses wrap-prone mixed signed arithmetic for its bounds check, while the application loader does not bound the staging copy at all. Pass the firmware end to the parser and validate the full source record. Use a signed wide offset for generic DPRAM records and validate the application staging span against the mapped DPRAM before copying. Fixes: 03fd3cf5a179 ("can: add driver for Softing card") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260722044347.2708-1-pengpeng@iscas.ac.cn Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/softing/softing_fw.c | 46 +++++++++++++++++++--------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/drivers/net/can/softing/softing_fw.c b/drivers/net/can/softing/softing_fw.c index 721df91cdbfb..282570daf3ef 100644 --- a/drivers/net/can/softing/softing_fw.c +++ b/drivers/net/can/softing/softing_fw.c @@ -91,12 +91,12 @@ int softing_bootloader_command(struct softing *card, int16_t cmd, return ret; } -static int fw_parse(const uint8_t **pmem, uint16_t *ptype, uint32_t *paddr, - uint16_t *plen, const uint8_t **pdat) +static int fw_parse(const u8 **pmem, const u8 *limit, u16 *ptype, + u32 *paddr, u16 *plen, const u8 **pdat) { uint16_t checksum[2]; - const uint8_t *mem; - const uint8_t *end; + const u8 *mem; + const u8 *record_end; /* * firmware records are a binary, unaligned stream composed of: @@ -114,14 +114,21 @@ static int fw_parse(const uint8_t **pmem, uint16_t *ptype, uint32_t *paddr, * endianness & alignment. */ mem = *pmem; + /* A record needs an 8-byte prefix and a 2-byte checksum. */ + if (mem > limit || limit - mem < 10) + return -EINVAL; + *ptype = le16_to_cpup((void *)&mem[0]); *paddr = le32_to_cpup((void *)&mem[2]); *plen = le16_to_cpup((void *)&mem[6]); + if (*plen > limit - mem - 10) + return -EINVAL; + *pdat = &mem[8]; /* verify checksum */ - end = &mem[8 + *plen]; - checksum[0] = le16_to_cpup((void *)end); - for (checksum[1] = 0; mem < end; ++mem) + record_end = &mem[8 + *plen]; + checksum[0] = le16_to_cpup((void *)record_end); + for (checksum[1] = 0; mem < record_end; ++mem) checksum[1] += *mem; if (checksum[0] != checksum[1]) return -EINVAL; @@ -139,6 +146,7 @@ int softing_load_fw(const char *file, struct softing *card, uint16_t type, len; uint32_t addr; uint8_t *buf = NULL, *new_buf; + s64 dpram_offset; int buflen = 0; int8_t type_end = 0; @@ -153,7 +161,7 @@ int softing_load_fw(const char *file, struct softing *card, mem = fw->data; end = &mem[fw->size]; /* look for header record */ - ret = fw_parse(&mem, &type, &addr, &len, &dat); + ret = fw_parse(&mem, end, &type, &addr, &len, &dat); if (ret < 0) goto failed; if (type != 0xffff) @@ -164,7 +172,7 @@ int softing_load_fw(const char *file, struct softing *card, } /* ok, we had a header */ while (mem < end) { - ret = fw_parse(&mem, &type, &addr, &len, &dat); + ret = fw_parse(&mem, end, &type, &addr, &len, &dat); if (ret < 0) goto failed; if (type == 3) { @@ -179,9 +187,13 @@ int softing_load_fw(const char *file, struct softing *card, goto failed; } - if ((addr + len + offset) > size) + dpram_offset = (s64)addr + offset; + if (dpram_offset < 0 || dpram_offset > size || + len > size - dpram_offset) { + ret = -EINVAL; goto failed; - memcpy_toio(&dpram[addr + offset], dat, len); + } + memcpy_toio(&dpram[dpram_offset], dat, len); /* be sure to flush caches from IO space */ mb(); if (len > buflen) { @@ -195,7 +207,7 @@ int softing_load_fw(const char *file, struct softing *card, buf = new_buf; } /* verify record data */ - memcpy_fromio(buf, &dpram[addr + offset], len); + memcpy_fromio(buf, &dpram[dpram_offset], len); if (memcmp(buf, dat, len)) { /* is not ok */ dev_alert(&card->pdev->dev, "DPRAM readback failed\n"); @@ -237,7 +249,7 @@ int softing_load_app_fw(const char *file, struct softing *card) mem = fw->data; end = &mem[fw->size]; /* look for header record */ - ret = fw_parse(&mem, &type, &addr, &len, &dat); + ret = fw_parse(&mem, end, &type, &addr, &len, &dat); if (ret) goto failed; ret = -EINVAL; @@ -253,7 +265,7 @@ int softing_load_app_fw(const char *file, struct softing *card) } /* ok, we had a header */ while (mem < end) { - ret = fw_parse(&mem, &type, &addr, &len, &dat); + ret = fw_parse(&mem, end, &type, &addr, &len, &dat); if (ret) goto failed; @@ -279,6 +291,12 @@ int softing_load_app_fw(const char *file, struct softing *card) /* work in 16bit (target) */ sum &= 0xffff; + if (card->pdat->app.offs > card->dpram_size || + len > card->dpram_size - card->pdat->app.offs) { + ret = -EINVAL; + goto failed; + } + memcpy_toio(&card->dpram[card->pdat->app.offs], dat, len); iowrite32(card->pdat->app.offs + card->pdat->app.addr, &card->dpram[DPRAM_COMMAND + 2]); From 26504844613fb44c7cab1c5f6fcff77861709baa Mon Sep 17 00:00:00 2001 From: Lucas Martins Alves Date: Tue, 14 Jul 2026 16:48:57 +0000 Subject: [PATCH 425/456] can: c_can: c_can_chip_config(): keep controller in init mode until bittiming is configured c_can_chip_config() was programming C_CAN_CTRL_REG without CONTROL_INIT, which may allow the controller to become active before c_can_set_bittiming() finishes. That creates a short timing window where the peripheral can interact with the bus using a different/default bitrate, potentially generating bus errors and corrupting traffic. Set CONTROL_INIT together with the control-mode writes in c_can_chip_config() (normal, loopback and listen-only paths), so the controller stays halted until bit timing is fully programmed. This prevents transient bus disturbance during startup when the configured bitrate differs from the active bus bitrate. Signed-off-by: Lucas Martins Alves Link: https://patch.msgid.link/20260714164839.771123-1-lucas.alves@lumal21.com.br Fixes: 881ff67ad450 ("can: c_can: Added support for Bosch C_CAN controller") Cc: stable@kernel.org [mkl: remove space before close parenthesis] Signed-off-by: Marc Kleine-Budde --- drivers/net/can/c_can/c_can_main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/can/c_can/c_can_main.c b/drivers/net/can/c_can/c_can_main.c index 3702cac7fbf0..b3b321d9ce68 100644 --- a/drivers/net/can/c_can/c_can_main.c +++ b/drivers/net/can/c_can/c_can_main.c @@ -597,20 +597,20 @@ static int c_can_chip_config(struct net_device *dev) return err; /* enable automatic retransmission */ - priv->write_reg(priv, C_CAN_CTRL_REG, CONTROL_ENABLE_AR); + priv->write_reg(priv, C_CAN_CTRL_REG, CONTROL_ENABLE_AR | CONTROL_INIT); if ((priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) && (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)) { /* loopback + silent mode : useful for hot self-test */ - priv->write_reg(priv, C_CAN_CTRL_REG, CONTROL_TEST); + priv->write_reg(priv, C_CAN_CTRL_REG, CONTROL_TEST | CONTROL_INIT); priv->write_reg(priv, C_CAN_TEST_REG, TEST_LBACK | TEST_SILENT); } else if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK) { /* loopback mode : useful for self-test function */ - priv->write_reg(priv, C_CAN_CTRL_REG, CONTROL_TEST); + priv->write_reg(priv, C_CAN_CTRL_REG, CONTROL_TEST | CONTROL_INIT); priv->write_reg(priv, C_CAN_TEST_REG, TEST_LBACK); } else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) { /* silent mode : bus-monitoring mode */ - priv->write_reg(priv, C_CAN_CTRL_REG, CONTROL_TEST); + priv->write_reg(priv, C_CAN_CTRL_REG, CONTROL_TEST | CONTROL_INIT); priv->write_reg(priv, C_CAN_TEST_REG, TEST_SILENT); } From 68c5724ecd159992f76edb7b57dc508a44c8b7da Mon Sep 17 00:00:00 2001 From: Marc Kleine-Budde Date: Thu, 9 Jul 2026 09:54:26 +0200 Subject: [PATCH 426/456] can: gs_usb: gs_usb_receive_bulk_callback(): resubmit URB on skb allocation failure If the allocation of the SKB in gs_usb_receive_bulk_callback() fails, the driver returns from the callback without resubmitting the URB in order to receive further USB in URBs. This results in a silent performance degradation which, if it occurs repeatedly, results in starvation of USB in traffic. Instead of returning immediately, try to resend the URB. If this also fails, this is logged as an info message. Fixes: d08e973a77d1 ("can: gs_usb: Added support for the GS_USB CAN devices") Fixes: 26949ac935e3 ("can: gs_usb: add CAN-FD support") Link: https://patch.msgid.link/20260709-gs_usb-resubmit-urb-v1-1-4dd40030cc84@pengutronix.de Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/gs_usb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/can/usb/gs_usb.c b/drivers/net/can/usb/gs_usb.c index ec9a7cbbbc69..82508a865095 100644 --- a/drivers/net/can/usb/gs_usb.c +++ b/drivers/net/can/usb/gs_usb.c @@ -674,7 +674,7 @@ static void gs_usb_receive_bulk_callback(struct urb *urb) if (hf->flags & GS_CAN_FLAG_FD) { skb = alloc_canfd_skb(netdev, &cfd); if (!skb) - return; + goto resubmit_urb; cfd->can_id = le32_to_cpu(hf->can_id); cfd->len = data_length; @@ -687,7 +687,7 @@ static void gs_usb_receive_bulk_callback(struct urb *urb) } else { skb = alloc_can_skb(netdev, &cf); if (!skb) - return; + goto resubmit_urb; cf->can_id = le32_to_cpu(hf->can_id); can_frame_set_cc_len(cf, hf->can_dlc, dev->can.ctrlmode); From 7a0cf2b2497c757c3cb1286eddf2986abb0d387b Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 6 Jul 2026 09:46:01 +0800 Subject: [PATCH 427/456] can: etas_es58x: es58x_read_bulk_callback(): fix RX buffer leak on URB resubmit failure es58x_read_bulk_callback() resubmits the RX URB after processing a received packet. If the resubmit succeeds, the URB remains anchored and will be handled by the normal RX path or by teardown. However, if usb_submit_urb() fails, the callback unanchors the URB and then returns directly. This skips the existing free_urb path, so the coherent transfer buffer allocated with usb_alloc_coherent() is not released. Reuse the existing free_urb path after a resubmit failure so that the RX coherent buffer is freed before leaving the callback. Fixes: 5eaad4f76826 ("can: usb: etas_es58x: correctly anchor the urb in the read bulk callback") Signed-off-by: Guangshuo Li Reviewed-by: Vincent Mailhol Link: https://patch.msgid.link/20260706014601.415445-1-lgs201920130244@gmail.com Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/etas_es58x/es58x_core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/can/usb/etas_es58x/es58x_core.c b/drivers/net/can/usb/etas_es58x/es58x_core.c index b259f6109808..e1724ae79c5a 100644 --- a/drivers/net/can/usb/etas_es58x/es58x_core.c +++ b/drivers/net/can/usb/etas_es58x/es58x_core.c @@ -1476,7 +1476,6 @@ static void es58x_read_bulk_callback(struct urb *urb) dev_err_ratelimited(dev, "Failed resubmitting read bulk urb: %pe\n", ERR_PTR(ret)); - return; free_urb: usb_free_coherent(urb->dev, urb->transfer_buffer_length, From 02925f51377f2a42a6724f00549167499c9302e5 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 6 Jul 2026 17:27:52 +0800 Subject: [PATCH 428/456] can: ems_usb: validate CPC message lengths ems_usb_read_bulk_callback() walks CPC messages packed in one USB receive buffer. Check that each declared message fits in the URB payload. Also require the type-specific payload to cover the fields used by the CAN, state, error and overrun handlers. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260706092752.79600-1-pengpeng@iscas.ac.cn Fixes: 702171adeed3 ("ems_usb: Added support for EMS CPC-USB/ARM7 CAN/USB interface") Cc: stable@vger.kernel.org Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/ems_usb.c | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/net/can/usb/ems_usb.c b/drivers/net/can/usb/ems_usb.c index 9b25dda7c183..24cf8f651f8f 100644 --- a/drivers/net/can/usb/ems_usb.c +++ b/drivers/net/can/usb/ems_usb.c @@ -409,6 +409,40 @@ static void ems_usb_rx_err(struct ems_usb *dev, struct ems_cpc_msg *msg) netif_rx(skb); } +static bool ems_usb_rx_msg_len_valid(struct ems_cpc_msg *msg) +{ + size_t len = msg->length; + size_t can_len; + + switch (msg->type) { + case CPC_MSG_TYPE_CAN_STATE: + return len >= sizeof(msg->msg.can_state); + + case CPC_MSG_TYPE_CAN_FRAME: + case CPC_MSG_TYPE_EXT_CAN_FRAME: + case CPC_MSG_TYPE_RTR_FRAME: + case CPC_MSG_TYPE_EXT_RTR_FRAME: + if (len < CPC_CAN_MSG_MIN_SIZE) + return false; + + if (msg->type == CPC_MSG_TYPE_RTR_FRAME || + msg->type == CPC_MSG_TYPE_EXT_RTR_FRAME) + return true; + + can_len = can_cc_dlc2len(msg->msg.can_msg.length & 0xf); + return len >= CPC_CAN_MSG_MIN_SIZE + can_len; + + case CPC_MSG_TYPE_CAN_FRAME_ERROR: + return len >= sizeof(msg->msg.error); + + case CPC_MSG_TYPE_OVERRUN: + return len >= sizeof(msg->msg.overrun); + + default: + return true; + } +} + /* * callback for bulk IN urb */ @@ -451,6 +485,15 @@ static void ems_usb_read_bulk_callback(struct urb *urb) } msg = (struct ems_cpc_msg *)&ibuf[start]; + if (msg->length > + urb->actual_length - start - CPC_MSG_HEADER_LEN) { + netdev_err(netdev, "format error\n"); + break; + } + if (!ems_usb_rx_msg_len_valid(msg)) { + netdev_err(netdev, "format error\n"); + break; + } switch (msg->type) { case CPC_MSG_TYPE_CAN_STATE: From 5732bd17feb9ddf843591c4d038eb6de1f8daf4c Mon Sep 17 00:00:00 2001 From: Michael Ellerman Date: Sat, 9 May 2026 22:50:25 +1000 Subject: [PATCH 429/456] MAINTAINERS: Demote myself to reviewer I haven't been active enough lately to warrant an M: maintainer entry, demote me to reviewer. Signed-off-by: Michael Ellerman Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/87y0hspx0u.fsf@mpe.ellerman.id.au --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 716acfc3d7c1..07088d5ac788 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14887,7 +14887,7 @@ X: drivers/macintosh/via-macii.c LINUX FOR POWERPC (32-BIT AND 64-BIT) M: Madhavan Srinivasan -M: Michael Ellerman +R: Michael Ellerman R: Nicholas Piggin R: Christophe Leroy (CS GROUP) L: linuxppc-dev@lists.ozlabs.org From 8cf2f40ceb85047ad8a84dffae3bebc9fed18216 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Wed, 29 Jul 2026 08:27:33 +0900 Subject: [PATCH 430/456] fprobe: Fix module reference count leak on error in register_fprobe() In register_fprobe(), get_ips_from_filter() resolves target function addresses and increments module reference counts via try_module_get() for symbols in kernel modules. If get_ips_from_filter() fails on the second pass and returns an error, register_fprobe() returned directly without releasing module references acquired up to that point. Fix this by ensuring the cleanup loop executing module_put() runs even when get_ips_from_filter() returns a negative error. Link: https://lore.kernel.org/all/178528125360.101985.4144133640239273153.stgit@devnote2/ Fixes: d24fa977eec5 ("tracing: fprobe: Fix to lock module while registering fprobe") Assisted-by: Antigravity:gemini-3.6-flash Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/fprobe.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c index f215990b9061..f681015413b8 100644 --- a/kernel/trace/fprobe.c +++ b/kernel/trace/fprobe.c @@ -961,10 +961,8 @@ int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter return -ENOMEM; ret = get_ips_from_filter(filter, notfilter, addrs, mods, num); - if (ret < 0) - return ret; - - ret = register_fprobe_ips(fp, addrs, ret); + if (ret >= 0) + ret = register_fprobe_ips(fp, addrs, ret); for (int i = 0; i < num; i++) { if (mods[i]) From 7d3aae206663c4e006b25a1c7a20a4029e67da76 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 10 Jul 2026 09:20:51 -0700 Subject: [PATCH 431/456] KVM: SVM: Update x2APIC MSR intercepts if AVIC is inhibited while L2 is active Always update x2APIC MSR intercepts for L1 when AVIC is deactivated, even if L2 is active and KVM is using a separate MSR bitmap to run L2. If AVIC is fully enabled prior to running L2, and is then inhibited while L2 is active (for a VM-scoped inhibit), then KVM will run L1 with AVIC disabled, but with x2APIC MSR intercepts disabled, i.e. will allow L1 to read most of the host's APIC state, send arbitrary interrupts, change task priority, and ultimately trivially DoS the host. E.g. sending a self-IPI in L1 on HYPERV_REENLIGHTENMENT_VECTOR, 0xee, with CONFIG_HYPERV=n in the host kernel as a "safe" PoC, yields: Spurious interrupt (vector 0xee) on CPU#425. Acked And hacking KVM to abuse kvm_set_posted_intr_wakeup_handler() to register a handler and WARN on POSTED_INTR_WAKEUP_VECTOR yields: ------------[ cut here ]------------ WARNING: arch/x86/kvm/svm/svm.c:5594 at pi_wakeup_handler+0x9/0x10 [kvm_amd], CPU#156: nested_x2apic_t/316940 CPU: 156 UID: 0 PID: 316940 Comm: nested_x2apic_t Tainted: G S U Tainted: [S]=CPU_OUT_OF_SPEC, [U]=USER Hardware name: Google Astoria-Turin/astoria, BIOS 0.20260209.0-0 02/09/2026 RIP: 0010:pi_wakeup_handler+0x9/0x10 [kvm_amd] Call Trace: sysvec_kvm_posted_intr_wakeup_ipi+0x64/0x80 asm_sysvec_kvm_posted_intr_wakeup_ipi+0x1a/0x20 RIP: 0010:vcpu_run+0x1430/0x1e40 [kvm] kvm_arch_vcpu_ioctl_run+0x2c1/0x600 [kvm] kvm_vcpu_ioctl+0x580/0x6b0 [kvm] __se_sys_ioctl+0x6d/0xb0 do_syscall_64+0x10a/0x480 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x46ff4b ---[ end trace 0000000000000000 ]--- Fixes: 091abbf578f9 ("KVM: x86: nSVM: optimize svm_set_x2apic_msr_interception") Cc: stable@vger.kernel.org Cc: Yosry Ahmed Signed-off-by: Sean Christopherson Link: https://patch.msgid.link/20260729213558.639074-1-pbonzini@redhat.com/ Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/avic.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c index 58e493a80cb0..16e09a8fe85e 100644 --- a/arch/x86/kvm/svm/avic.c +++ b/arch/x86/kvm/svm/avic.c @@ -240,14 +240,6 @@ static void avic_deactivate_vmcb(struct vcpu_svm *svm) if (!is_sev_es_guest(&svm->vcpu)) svm_set_intercept(svm, INTERCEPT_CR8_WRITE); - /* - * If running nested and the guest uses its own MSR bitmap, there - * is no need to update L0's msr bitmap - */ - if (is_guest_mode(&svm->vcpu) && - vmcb12_is_intercept(&svm->nested.ctl, INTERCEPT_MSR_PROT)) - return; - /* Enabling MSR intercept for x2APIC registers */ avic_set_x2apic_msr_interception(svm, true); } From a10ea943356b9d70c5616a0a06f6fa97cfdaccb1 Mon Sep 17 00:00:00 2001 From: Hidayath Khan Date: Mon, 27 Jul 2026 11:35:30 +0200 Subject: [PATCH 432/456] dibs: fix use-after-free of dmb_node in loopback attach/detach/unregister dibs_lo_attach_dmb(), dibs_lo_detach_dmb() and dibs_lo_unregister_dmb() look up the dmb_node under dmb_ht_lock, drop the lock and only then operate on the node's refcount. Nothing keeps the node alive across that window: __dibs_lo_unregister_dmb() removes the node from the hash table under the write lock and immediately frees it. A concurrent final put can therefore free the node between the lookup and the refcount operation: CPU0 (attach) CPU1 (owner unregisters) read_lock_bh(&dmb_ht_lock) find dmb_node (refcnt == 1) read_unlock_bh(&dmb_ht_lock) refcount_dec_and_test() 1 -> 0 write_lock_bh(&dmb_ht_lock) hash_del(&dmb_node->list) write_unlock_bh(&dmb_ht_lock) kfree(dmb_node) refcount_inc_not_zero(&dmb_node->refcnt) <-- use-after-free The same window exists for the refcount_dec_and_test() calls in the detach and unregister paths. Close the race structurally by making hash table membership and the refcount transitions atomic with respect to each other: - Perform the final refcount_dec_and_test() and hash_del() in a single dmb_ht_lock write-side critical section, in both the unregister and the detach path. Freeing the node still happens after the lock is dropped, which is safe because a node whose refcount reached zero has left the hash table and can no longer be found. - This establishes the invariant that any node found in the hash table holds at least one reference, and that the final reference can only be dropped under the write lock. dibs_lo_attach_dmb() can thus take its reference with a plain refcount_inc() while still holding the read lock; refcount_inc_not_zero() is no longer needed. __dibs_lo_unregister_dmb() no longer touches the hash table and is renamed to dibs_lo_free_dmb() accordingly. Note: commit cc21191b584c ("dibs: Move data path to dibs layer") moved the code to its current location; the race was introduced earlier by commit c3a910f2380f ("net/smc: implement DMB-merged operations of loopback-ism"). Tested SMC-D via ISM and dibs loopback. Cc: stable@vger.kernel.org Fixes: c3a910f2380f ("net/smc: implement DMB-merged operations of loopback-ism") Reported-by: Rahul Chandelkar Signed-off-by: Hidayath Khan Reviewed-by: Alexandra Winter Link: https://patch.msgid.link/20260727093530.968834-1-hidayath@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/dibs/dibs_loopback.c | 47 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/drivers/dibs/dibs_loopback.c b/drivers/dibs/dibs_loopback.c index 0f2e09311152..fd5caf1e19a8 100644 --- a/drivers/dibs/dibs_loopback.c +++ b/drivers/dibs/dibs_loopback.c @@ -118,14 +118,9 @@ static int dibs_lo_register_dmb(struct dibs_dev *dibs, struct dibs_dmb *dmb, return rc; } -static void __dibs_lo_unregister_dmb(struct dibs_lo_dev *ldev, - struct dibs_lo_dmb_node *dmb_node) +static void dibs_lo_free_dmb(struct dibs_lo_dev *ldev, + struct dibs_lo_dmb_node *dmb_node) { - /* remove dmb from hash table */ - write_lock_bh(&ldev->dmb_ht_lock); - hash_del(&dmb_node->list); - write_unlock_bh(&ldev->dmb_ht_lock); - clear_bit(dmb_node->sba_idx, ldev->sba_idx_mask); folio_put(virt_to_folio(dmb_node->cpu_addr)); kfree(dmb_node); @@ -139,27 +134,33 @@ static int dibs_lo_unregister_dmb(struct dibs_dev *dibs, struct dibs_dmb *dmb) struct dibs_lo_dmb_node *dmb_node = NULL, *tmp_node; struct dibs_lo_dev *ldev; unsigned long flags; + bool last; ldev = dibs->drv_priv; /* find dmb from hash table */ - read_lock_bh(&ldev->dmb_ht_lock); + write_lock_bh(&ldev->dmb_ht_lock); hash_for_each_possible(ldev->dmb_ht, tmp_node, list, dmb->dmb_tok) { if (tmp_node->token == dmb->dmb_tok) { dmb_node = tmp_node; break; } } - read_unlock_bh(&ldev->dmb_ht_lock); - if (!dmb_node) + if (!dmb_node) { + write_unlock_bh(&ldev->dmb_ht_lock); return -EINVAL; + } + last = refcount_dec_and_test(&dmb_node->refcnt); + if (last) + hash_del(&dmb_node->list); + write_unlock_bh(&ldev->dmb_ht_lock); - if (refcount_dec_and_test(&dmb_node->refcnt)) { + if (last) { spin_lock_irqsave(&dibs->lock, flags); dibs->dmb_clientid_arr[dmb_node->sba_idx] = NO_DIBS_CLIENT; spin_unlock_irqrestore(&dibs->lock, flags); - __dibs_lo_unregister_dmb(ldev, dmb_node); + dibs_lo_free_dmb(ldev, dmb_node); } return 0; } @@ -188,14 +189,9 @@ static int dibs_lo_attach_dmb(struct dibs_dev *dibs, struct dibs_dmb *dmb) read_unlock_bh(&ldev->dmb_ht_lock); return -EINVAL; } + refcount_inc(&dmb_node->refcnt); read_unlock_bh(&ldev->dmb_ht_lock); - if (!refcount_inc_not_zero(&dmb_node->refcnt)) - /* the dmb is being unregistered, but has - * not been removed from the hash table. - */ - return -EINVAL; - /* provide dmb information */ dmb->idx = dmb_node->sba_idx; dmb->dmb_tok = dmb_node->token; @@ -209,11 +205,12 @@ static int dibs_lo_detach_dmb(struct dibs_dev *dibs, u64 token) { struct dibs_lo_dmb_node *dmb_node = NULL, *tmp_node; struct dibs_lo_dev *ldev; + bool last; ldev = dibs->drv_priv; /* find dmb_node according to dmb->dmb_tok */ - read_lock_bh(&ldev->dmb_ht_lock); + write_lock_bh(&ldev->dmb_ht_lock); hash_for_each_possible(ldev->dmb_ht, tmp_node, list, token) { if (tmp_node->token == token) { dmb_node = tmp_node; @@ -221,13 +218,17 @@ static int dibs_lo_detach_dmb(struct dibs_dev *dibs, u64 token) } } if (!dmb_node) { - read_unlock_bh(&ldev->dmb_ht_lock); + write_unlock_bh(&ldev->dmb_ht_lock); return -EINVAL; } - read_unlock_bh(&ldev->dmb_ht_lock); + last = refcount_dec_and_test(&dmb_node->refcnt); + if (last) + hash_del(&dmb_node->list); + write_unlock_bh(&ldev->dmb_ht_lock); + + if (last) + dibs_lo_free_dmb(ldev, dmb_node); - if (refcount_dec_and_test(&dmb_node->refcnt)) - __dibs_lo_unregister_dmb(ldev, dmb_node); return 0; } From fc9c7ca5fcbf7fe3bcba87d1ff72f0009071ba86 Mon Sep 17 00:00:00 2001 From: Kiran Kella Date: Mon, 27 Jul 2026 03:16:28 -0700 Subject: [PATCH 433/456] psp: fix NULL genl_sock deref race with concurrent netns teardown The race occurs between network namespace removal and PSP device unregistration. When a netns is deleted while a PSP device associated with that netns is concurrently being removed, psp_dev_unregister() triggers psp_nl_notify_dev() to send a device change notification. Concurrently, cleanup_net() running in the netns workqueue calls genl_pernet_exit(), which sets net->genl_sock to NULL. If genl_pernet_exit() wins the race, two sites in psp_nl_multicast_per_ns() then dereference the NULL socket and crash: CPU 0 (netns teardown) CPU 1 (PSP device unregister) ====================== ============================= cleanup_net [workqueue] genl_pernet_exit() psp_dev_unregister() net->genl_sock = NULL psp_nl_notify_dev() psp_nl_multicast_per_ns() build_ntf() -> netlink_has_listeners(NULL) /* crash */ genlmsg_multicast_netns() -> nlmsg_multicast_filtered(NULL) /* crash */ Fix by replacing the bare dev_net() calls with maybe_get_net(). maybe_get_net() returns NULL if the namespace is already dying. Holding the reference ensures genl_sock remains valid across both the build_ntf() and genlmsg_multicast_netns() calls. Fixes: 00c94ca2b99e ("psp: base PSP device support") Fixes: 06c2dce2d0f6 ("psp: add new netlink cmd for dev-assoc and dev-disassoc") Reviewed-by: Ajit Khaparde Reviewed-by: Vikas Gupta Reviewed-by: Bhargava Marreddy Reviewed-by: Akhilesh Samineni Signed-off-by: Kiran Kella Link: https://patch.msgid.link/20260727101628.502042-1-kiran.kella@broadcom.com Signed-off-by: Jakub Kicinski --- net/psp/psp_nl.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c index 9610d8c456ff..43b066353c65 100644 --- a/net/psp/psp_nl.c +++ b/net/psp/psp_nl.c @@ -62,7 +62,14 @@ psp_nl_multicast_per_ns(struct psp_dev *psd, unsigned int group, struct net *main_net; struct sk_buff *ntf; - main_net = dev_net(psd->main_netdev); + /* device may be changing netns in parallel */ + rcu_read_lock(); + main_net = maybe_get_net(dev_net_rcu(psd->main_netdev)); + rcu_read_unlock(); + + if (!main_net) + return; + xa_init(&sent_nets); list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) { @@ -88,10 +95,10 @@ psp_nl_multicast_per_ns(struct psp_dev *psd, unsigned int group, /* Send to main device netns */ ntf = build_ntf(psd, main_net, ctx); - if (!ntf) - return; - genlmsg_multicast_netns(&psp_nl_family, main_net, ntf, 0, group, - GFP_KERNEL); + if (ntf) + genlmsg_multicast_netns(&psp_nl_family, main_net, ntf, 0, group, + GFP_KERNEL); + put_net(main_net); } static struct sk_buff *psp_nl_clone_ntf(struct psp_dev *psd, struct net *net, From 93cad1f6bd1e27c75c4a5ab000c2a2fc01181ccf Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Mon, 27 Jul 2026 14:53:39 -0400 Subject: [PATCH 434/456] ipv6: release fib6_null_entry on subtree failure When adding a source-specific route creates a new subtree, fib6_add() installs fib6_null_entry as the temporary leaf of the new subtree root and takes a fib6_info reference for that holder. If adding the first source leaf fails, the code frees the just allocated subtree root but leaves that hold behind. fib6_null_entry is a per-netns sentinel and is freed directly at netns teardown, so this does not keep the object alive. However, it leaves its visible refcount permanently elevated and can eventually saturate the refcount on repeated failures. Drop the null-entry reference before freeing the unlinked subtree root. Fixes: 5ea715289af6 ("ipv6: broadly use fib6_info_hold() helper") Signed-off-by: Shuangpeng Bai Reviewed-by: Ido Schimmel Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://patch.msgid.link/20260727185339.1545169-1-shuangpeng.kernel@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_fib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index a130cdfaebfb..e9fc692d4f3b 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1494,6 +1494,7 @@ int fib6_add(struct fib6_node *root, struct fib6_info *rt, root, and then (in failure) stale node in main tree. */ + fib6_info_release(info->nl_net->ipv6.fib6_null_entry); node_free_immediate(info->nl_net, sfn); err = PTR_ERR(sn); goto failure; From 1c15e75dc21fc61f7bb62f2e8c86d86da01608c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 27 Jul 2026 22:29:29 +0200 Subject: [PATCH 435/456] MAINTAINERS: make Luiz a maintainer and myself reviewer for Realtek DSA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I have changed jobs and therefore no longer have access to hardware using Realtek Ethernet switches. Luiz has kindly agreed to take up the role of maintainer, while I will stick around as a reviewer. Also update .mailmap so that mails to my old company email stop bouncing. Use my new work email for Analog Devices Inc. instead. Signed-off-by: Alvin Šipraga Reviewed-by: Linus Walleij Acked-by: Luiz Angelo Daros de Luca Link: https://patch.msgid.link/20260727-realtek-maintainers-v1-1-ab501adc0cdb@analog.com Signed-off-by: Jakub Kicinski --- .mailmap | 1 + MAINTAINERS | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index a0f64c31abcc..b66c317e8f06 100644 --- a/.mailmap +++ b/.mailmap @@ -72,6 +72,7 @@ Alice Mikityanska Alice Mikityanska Alice Mikityanska Aloka Dixit +Alvin Šipraga Al Viro Al Viro Amit Blay diff --git a/MAINTAINERS b/MAINTAINERS index 61126d170e4a..2385c27f14b9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22735,7 +22735,8 @@ F: drivers/watchdog/realtek_otto_wdt.c REALTEK RTL83xx SMI DSA ROUTER CHIPS M: Linus Walleij -M: Alvin Šipraga +M: Luiz Angelo Daros de Luca +R: Alvin Šipraga S: Maintained F: Documentation/devicetree/bindings/net/dsa/realtek.yaml F: drivers/net/dsa/realtek/* From 74b21f52c5c5a71a05c0ff70e513f4f04ff28b17 Mon Sep 17 00:00:00 2001 From: Charles Vosburgh Date: Mon, 27 Jul 2026 19:17:30 -0400 Subject: [PATCH 436/456] sctp: validate Adaptation Indication parameter length The Adaptation Layer Indication parameter contains a fixed 32-bit Adaptation Code Point after its parameter header. However, sctp_verify_param() accepts a header-only parameter because the generic parameter walker only requires the header to be present. sctp_process_param() then reads adaptation_ind beyond the declared parameter. When the malformed parameter is last in an INIT, the read starts at the receive skb tail, and the value is copied into the state cookie returned in the INIT ACK. This may disclose four receive-buffer tail bytes. Require the declared parameter length to match the fixed structure size and abort the association through the existing invalid parameter length path otherwise. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Charles Vosburgh Acked-by: Xin Long Link: https://patch.msgid.link/20260727-sctp-adaptation-length-v1-1-0ab58b2810a5@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_make_chunk.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index a1c0334a1038..0ae30c3c8913 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -2171,7 +2171,13 @@ static enum sctp_ierror sctp_verify_param(struct net *net, case SCTP_PARAM_HEARTBEAT_INFO: case SCTP_PARAM_UNRECOGNIZED_PARAMETERS: case SCTP_PARAM_ECN_CAPABLE: + break; case SCTP_PARAM_ADAPTATION_LAYER_IND: + if (ntohs(param.p->length) != sizeof(*param.aind)) { + sctp_process_inv_paramlength(asoc, param.p, + chunk, err_chunk); + retval = SCTP_IERROR_ABORT; + } break; case SCTP_PARAM_SUPPORTED_EXT: From f11b48aa674b475f196bede7d69593c050107fc5 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Tue, 28 Jul 2026 11:22:37 +0200 Subject: [PATCH 437/456] Revert "tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present" This reverts commit 1d6e569b7d0c0b2736636749e4be0a27f3cefcb3. The commit stops the netdev queue when the ptr_ring is full instead of dropping the packet. My own tests showed no relevant regression, but on Brett Sheffield's librecast testbed an IPv6 multicast testcase got slower. With 8 iperf3 TCP threads sending, the throughput dropped from 13.5 Gbit/s to 9.13 Gbit/s. Reported-by: Brett Sheffield Closes: https://lore.kernel.org/netdev/akVnoOYQOrt8k-Gu@karahi.librecast.net/ Signed-off-by: Simon Schippers Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260728092240.250257-2-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index ffbe6f13fb1f..ec5573f545af 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1018,7 +1018,6 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev) struct netdev_queue *queue; struct tun_file *tfile; int len = skb->len; - int ret; rcu_read_lock(); tfile = rcu_dereference(tun->tfiles[txq]); @@ -1073,33 +1072,13 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev) nf_reset_ct(skb); - queue = netdev_get_tx_queue(dev, txq); - - spin_lock(&tfile->tx_ring.producer_lock); - ret = __ptr_ring_produce(&tfile->tx_ring, skb); - if (!qdisc_txq_has_no_queue(queue) && - __ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) { - netif_tx_stop_queue(queue); - /* Paired with smp_mb() in __tun_wake_queue() */ - smp_mb__after_atomic(); - if (!__ptr_ring_check_produce(&tfile->tx_ring)) - netif_tx_wake_queue(queue); - } - spin_unlock(&tfile->tx_ring.producer_lock); - - if (ret) { - /* This should be a rare case if a qdisc is present, but - * can happen due to lltx. - * Since skb_tx_timestamp(), skb_orphan(), - * run_ebpf_filter() and pskb_trim() could have tinkered - * with the SKB, returning NETDEV_TX_BUSY is unsafe and - * we must drop instead. - */ + if (ptr_ring_produce(&tfile->tx_ring, skb)) { drop_reason = SKB_DROP_REASON_FULL_RING; goto drop; } /* dev->lltx requires to do our own update of trans_start */ + queue = netdev_get_tx_queue(dev, txq); txq_trans_cond_update(queue); /* Notify and wake up reader process */ From 6bc85579c3bbb2f088cbac849c5dc2a134dda736 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Tue, 28 Jul 2026 11:22:38 +0200 Subject: [PATCH 438/456] Revert "ptr_ring: move free-space check into separate helper" This reverts commit fba362c17d9d9211fc51f272156bb84fc23bdf98. __ptr_ring_check_produce() has no users left after reverting commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present"). Signed-off-by: Simon Schippers Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260728092240.250257-3-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- include/linux/ptr_ring.h | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/include/linux/ptr_ring.h b/include/linux/ptr_ring.h index c95e891903f0..d2c3629bbe45 100644 --- a/include/linux/ptr_ring.h +++ b/include/linux/ptr_ring.h @@ -96,20 +96,6 @@ static inline bool ptr_ring_full_bh(struct ptr_ring *r) return ret; } -/* Note: callers invoking this in a loop must use a compiler barrier, - * for example cpu_relax(). Callers must hold producer_lock. - */ -static inline int __ptr_ring_check_produce(struct ptr_ring *r) -{ - if (unlikely(!r->size)) - return -EINVAL; - - if (data_race(r->queue[r->producer])) - return -ENOSPC; - - return 0; -} - /* Note: callers invoking this in a loop must use a compiler barrier, * for example cpu_relax(). Callers must hold producer_lock. * Callers are responsible for making sure pointer that is being queued @@ -117,10 +103,8 @@ static inline int __ptr_ring_check_produce(struct ptr_ring *r) */ static inline int __ptr_ring_produce(struct ptr_ring *r, void *ptr) { - int p = __ptr_ring_check_produce(r); - - if (p) - return p; + if (unlikely(!r->size) || data_race(r->queue[r->producer])) + return -ENOSPC; /* Make sure the pointer we are storing points to a valid data. */ /* Pairs with the dependency ordering in __ptr_ring_consume. */ From 8f83be72d9f5ef16c4a908450d0d993e8ec99d34 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Tue, 28 Jul 2026 11:22:39 +0200 Subject: [PATCH 439/456] Revert "vhost-net: wake queue of tun/tap after ptr_ring consume" This reverts commit baf808fe4fcd35767ab732b4ab2ea80dabfd97a6. There is no netdev queue left to wake after reverting commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present"). Signed-off-by: Simon Schippers Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260728092240.250257-4-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 23 ----------------------- drivers/vhost/net.c | 21 ++++++--------------- include/linux/if_tun.h | 3 --- 3 files changed, 6 insertions(+), 41 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index ec5573f545af..39abc3078097 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -3787,29 +3787,6 @@ struct ptr_ring *tun_get_tx_ring(struct file *file) } EXPORT_SYMBOL_GPL(tun_get_tx_ring); -/* Callers must hold ring.consumer_lock */ -void tun_wake_queue(struct file *file, int consumed) -{ - struct tun_file *tfile; - struct tun_struct *tun; - - if (file->f_op != &tun_fops) - return; - - tfile = file->private_data; - if (!tfile) - return; - - rcu_read_lock(); - - tun = rcu_dereference(tfile->tun); - if (tun) - __tun_wake_queue(tun, tfile, consumed); - - rcu_read_unlock(); -} -EXPORT_SYMBOL_GPL(tun_wake_queue); - module_init(tun_init); module_exit(tun_cleanup); MODULE_DESCRIPTION(DRV_DESCRIPTION); diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index 3e72b9c6af0c..6949b704166d 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -176,21 +176,13 @@ static void *vhost_net_buf_consume(struct vhost_net_buf *rxq) return ret; } -static int vhost_net_buf_produce(struct sock *sk, - struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_produce(struct vhost_net_virtqueue *nvq) { - struct file *file = sk->sk_socket->file; struct vhost_net_buf *rxq = &nvq->rxq; rxq->head = 0; - spin_lock(&nvq->rx_ring->consumer_lock); - rxq->tail = __ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, - VHOST_NET_BATCH); - - if (rxq->tail) - tun_wake_queue(file, rxq->tail); - - spin_unlock(&nvq->rx_ring->consumer_lock); + rxq->tail = ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, + VHOST_NET_BATCH); return rxq->tail; } @@ -217,15 +209,14 @@ static int vhost_net_buf_peek_len(void *ptr) return __skb_array_len_with_tag(ptr); } -static int vhost_net_buf_peek(struct sock *sk, - struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_peek(struct vhost_net_virtqueue *nvq) { struct vhost_net_buf *rxq = &nvq->rxq; if (!vhost_net_buf_is_empty(rxq)) goto out; - if (!vhost_net_buf_produce(sk, nvq)) + if (!vhost_net_buf_produce(nvq)) return 0; out: @@ -1013,7 +1004,7 @@ static int peek_head_len(struct vhost_net_virtqueue *rvq, struct sock *sk) unsigned long flags; if (rvq->rx_ring) - return vhost_net_buf_peek(sk, rvq); + return vhost_net_buf_peek(rvq); spin_lock_irqsave(&sk->sk_receive_queue.lock, flags); head = skb_peek(&sk->sk_receive_queue); diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h index 5f3e206c7a73..80166eb62f41 100644 --- a/include/linux/if_tun.h +++ b/include/linux/if_tun.h @@ -22,7 +22,6 @@ struct tun_msg_ctl { #if defined(CONFIG_TUN) || defined(CONFIG_TUN_MODULE) struct socket *tun_get_socket(struct file *); struct ptr_ring *tun_get_tx_ring(struct file *file); -void tun_wake_queue(struct file *file, int consumed); static inline bool tun_is_xdp_frame(void *ptr) { @@ -56,8 +55,6 @@ static inline struct ptr_ring *tun_get_tx_ring(struct file *f) return ERR_PTR(-EINVAL); } -static inline void tun_wake_queue(struct file *f, int consumed) {} - static inline bool tun_is_xdp_frame(void *ptr) { return false; From c3da92af07eaba43f49910b2e4fbd016e563fa35 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Tue, 28 Jul 2026 11:22:40 +0200 Subject: [PATCH 440/456] Revert "tun/tap: add ptr_ring consume helper with netdev queue wakeup" This reverts commit d4c22d70d7253dd727c71484c58d504f6c630343. There is no netdev queue left to wake after reverting commit 1d6e569b7d0c ("tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present"). Signed-off-by: Simon Schippers Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260728092240.250257-5-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 61 ++++------------------------------------------- 1 file changed, 4 insertions(+), 57 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 39abc3078097..fed9dfdfcc3b 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -145,8 +145,6 @@ struct tun_file { struct list_head next; struct tun_struct *detached; struct ptr_ring tx_ring; - /* Protected by tx_ring.consumer_lock */ - int cons_cnt; struct xdp_rxq_info xdp_rxq; }; @@ -590,13 +588,8 @@ static void __tun_detach(struct tun_file *tfile, bool clean) rcu_assign_pointer(tun->tfiles[index], tun->tfiles[tun->numqueues - 1]); ntfile = rtnl_dereference(tun->tfiles[index]); - spin_lock(&ntfile->tx_ring.consumer_lock); ntfile->queue_index = index; ntfile->xdp_rxq.queue_index = index; - ntfile->cons_cnt = 0; - if (__ptr_ring_empty(&ntfile->tx_ring)) - netif_wake_subqueue(tun->dev, index); - spin_unlock(&ntfile->tx_ring.consumer_lock); rcu_assign_pointer(tun->tfiles[tun->numqueues - 1], NULL); @@ -737,9 +730,6 @@ static int tun_attach(struct tun_struct *tun, struct file *file, goto out; } - spin_lock(&tfile->tx_ring.consumer_lock); - tfile->cons_cnt = 0; - spin_unlock(&tfile->tx_ring.consumer_lock); tfile->queue_index = tun->numqueues; tfile->socket.sk->sk_shutdown &= ~RCV_SHUTDOWN; @@ -2126,46 +2116,13 @@ static ssize_t tun_put_user(struct tun_struct *tun, return total; } -/* Callers must hold ring.consumer_lock */ -static void __tun_wake_queue(struct tun_struct *tun, - struct tun_file *tfile, int consumed) -{ - struct netdev_queue *txq = netdev_get_tx_queue(tun->dev, - tfile->queue_index); - - /* Paired with smp_mb__after_atomic() in tun_net_xmit() */ - smp_mb(); - if (netif_tx_queue_stopped(txq)) { - tfile->cons_cnt += consumed; - if (tfile->cons_cnt >= tfile->tx_ring.size / 2 || - __ptr_ring_empty(&tfile->tx_ring)) { - netif_tx_wake_queue(txq); - tfile->cons_cnt = 0; - } - } -} - -static void *tun_ring_consume(struct tun_struct *tun, struct tun_file *tfile) -{ - void *ptr; - - spin_lock(&tfile->tx_ring.consumer_lock); - ptr = __ptr_ring_consume(&tfile->tx_ring); - if (ptr) - __tun_wake_queue(tun, tfile, 1); - - spin_unlock(&tfile->tx_ring.consumer_lock); - return ptr; -} - -static void *tun_ring_recv(struct tun_struct *tun, struct tun_file *tfile, - int noblock, int *err) +static void *tun_ring_recv(struct tun_file *tfile, int noblock, int *err) { DECLARE_WAITQUEUE(wait, current); void *ptr = NULL; int error = 0; - ptr = tun_ring_consume(tun, tfile); + ptr = ptr_ring_consume(&tfile->tx_ring); if (ptr) goto out; if (noblock) { @@ -2177,7 +2134,7 @@ static void *tun_ring_recv(struct tun_struct *tun, struct tun_file *tfile, while (1) { set_current_state(TASK_INTERRUPTIBLE); - ptr = tun_ring_consume(tun, tfile); + ptr = ptr_ring_consume(&tfile->tx_ring); if (ptr) break; if (signal_pending(current)) { @@ -2214,7 +2171,7 @@ static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile, if (!ptr) { /* Read frames from ring */ - ptr = tun_ring_recv(tun, tfile, noblock, &err); + ptr = tun_ring_recv(tfile, noblock, &err); if (!ptr) return err; } @@ -3669,16 +3626,6 @@ static int tun_queue_resize(struct tun_struct *tun) dev->tx_queue_len, GFP_KERNEL, tun_ptr_free); - if (!ret) { - for (i = 0; i < tun->numqueues; i++) { - tfile = rtnl_dereference(tun->tfiles[i]); - spin_lock(&tfile->tx_ring.consumer_lock); - netif_wake_subqueue(tun->dev, tfile->queue_index); - tfile->cons_cnt = 0; - spin_unlock(&tfile->tx_ring.consumer_lock); - } - } - kfree(rings); return ret; } From b4ce102b2cd88424c5860fbbb20b9eb343a93bf4 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 28 Jul 2026 05:52:14 +0100 Subject: [PATCH 441/456] net: dsa: mt7530: check bus->read() errors in the MDIO regmap backend bus->read() returns a negative errno on failure, but mt7530_regmap_read() assigns it to a u16, truncating e.g. -ETIMEDOUT into 0xff92, and returns success. The garbage word is then consumed as register data, and read-modify-write cycles write it back to the switch. Check both reads and propagate their errors. The same defect existed in mt7530_mii_read() since the driver was introduced and moved into the regmap backend unchanged. Fixes: b8f126a8d543 ("net-next: dsa: add dsa support for Mediatek MT7530 switch") Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/3c628e48276c2e5522c8795a6be60d11c7a76a7d.1785213071.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530-mdio.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c index 11ea924a9f35..784dd58a7158 100644 --- a/drivers/net/dsa/mt7530-mdio.c +++ b/drivers/net/dsa/mt7530-mdio.c @@ -55,8 +55,15 @@ mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val) if (ret < 0) return ret; - lo = bus->read(bus, priv->mdiodev->addr, r); - hi = bus->read(bus, priv->mdiodev->addr, 0x10); + ret = bus->read(bus, priv->mdiodev->addr, r); + if (ret < 0) + return ret; + lo = ret; + + ret = bus->read(bus, priv->mdiodev->addr, 0x10); + if (ret < 0) + return ret; + hi = ret; *val = (hi << 16) | (lo & 0xffff); From ed9adac35b8fac635f40e28461505e2e5b6c8fcc Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 28 Jul 2026 05:52:21 +0100 Subject: [PATCH 442/456] net: dsa: mt7530: error out on failed reads in ATC/VTCR command polling mt7530_fdb_cmd() and mt7530_vlan_cmd() poll the command register through a helper which returns 0 when the underlying read fails. A failed bus transaction thus clears ATC_BUSY/VTCR_BUSY and is treated as successful command completion, and the subsequent ATC_INVALID and VTCR_INVALID checks are defeated the same way. Poll using regmap_read_poll_timeout(), which stops on read errors and propagates them, and check the completion status read as well. Take the MDIO bus lock across the sequence as the switch regmap is set up with locking disabled. Fixes: b8f126a8d543 ("net-next: dsa: add dsa support for Mediatek MT7530 switch") Fixes: 83163f7dca56 ("net: dsa: mediatek: add VLAN support for MT7530") Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/eea1d8f15c54375b3770c23e09fb3217df487169.1785213071.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 3c2a3029b10c..292cde961f1a 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -248,15 +248,20 @@ mt7530_fdb_cmd(struct mt7530_priv *priv, enum mt7530_fdb_cmd cmd, u32 *rsp) { u32 val; int ret; - struct mt7530_dummy_poll p; /* Set the command operating upon the MAC address entries */ val = ATC_BUSY | ATC_MAT(0) | cmd; mt7530_write(priv, MT7530_ATC, val); - INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_ATC); - ret = readx_poll_timeout(_mt7530_read, &p, val, - !(val & ATC_BUSY), 20, 20000); + mt7530_mutex_lock(priv); + + ret = regmap_read_poll_timeout(priv->regmap, MT7530_ATC, val, + !(val & ATC_BUSY), 20, 20000); + if (!ret) + ret = regmap_read(priv->regmap, MT7530_ATC, &val); + + mt7530_mutex_unlock(priv); + if (ret < 0) { dev_err(priv->dev, "reset timeout\n"); return ret; @@ -265,7 +270,6 @@ mt7530_fdb_cmd(struct mt7530_priv *priv, enum mt7530_fdb_cmd cmd, u32 *rsp) /* Additional sanity for read command if the specified * entry is invalid */ - val = mt7530_read(priv, MT7530_ATC); if ((cmd == MT7530_FDB_READ) && (val & ATC_INVALID)) return -EINVAL; @@ -1626,22 +1630,26 @@ mt7530_port_bridge_join(struct dsa_switch *ds, int port, static int mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid) { - struct mt7530_dummy_poll p; u32 val; int ret; val = VTCR_BUSY | VTCR_FUNC(cmd) | vid; mt7530_write(priv, MT7530_VTCR, val); - INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_VTCR); - ret = readx_poll_timeout(_mt7530_read, &p, val, - !(val & VTCR_BUSY), 20, 20000); + mt7530_mutex_lock(priv); + + ret = regmap_read_poll_timeout(priv->regmap, MT7530_VTCR, val, + !(val & VTCR_BUSY), 20, 20000); + if (!ret) + ret = regmap_read(priv->regmap, MT7530_VTCR, &val); + + mt7530_mutex_unlock(priv); + if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); return ret; } - val = mt7530_read(priv, MT7530_VTCR); if (val & VTCR_INVALID) { dev_err(priv->dev, "read VTCR invalid\n"); return -EINVAL; From 77a9ebe8818cf6dd1699bd6728cb5d66307801d7 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 28 Jul 2026 05:52:29 +0100 Subject: [PATCH 443/456] net: dsa: mt7530: error out on failed reads in MT7531 PHY polling The MT7531 indirect PHY access functions poll MT7531_PHY_IAC through a helper which returns 0 when the underlying read fails, so a failed bus transaction clears MT7531_PHY_ACS_ST and the access carries on, returning garbage PHY register data to phylib. Poll using regmap_read_poll_timeout(), which stops on read errors and propagates them. These functions hold the MDIO bus lock across the whole sequence, so the unlocked regmap accesses remain correct. Remove the now-unused _mt7530_unlocked_read(). Fixes: c288575f7810 ("net: dsa: mt7530: Add the support of MT7531 switch") Signed-off-by: Daniel Golle Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/79e85d68d210cc37342978171aa6432aa2954333.1785213071.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530.c | 58 ++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 292cde961f1a..aa33d94e11b5 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -191,12 +191,6 @@ mt7530_write(struct mt7530_priv *priv, u32 reg, u32 val) mt7530_mutex_unlock(priv); } -static u32 -_mt7530_unlocked_read(struct mt7530_dummy_poll *p) -{ - return mt7530_mii_read(p->priv, p->reg); -} - static u32 _mt7530_read(struct mt7530_dummy_poll *p) { @@ -553,16 +547,13 @@ static int mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad, int regnum) { - struct mt7530_dummy_poll p; u32 reg, val; int ret; - INIT_MT7530_DUMMY_POLL(&p, priv, MT7531_PHY_IAC); - mt7530_mutex_lock(priv); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val, - !(val & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, + !(val & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; @@ -572,8 +563,8 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad, MT7531_MDIO_DEV_ADDR(devad) | regnum; mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val, - !(val & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, + !(val & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; @@ -583,8 +574,8 @@ mt7531_ind_c45_phy_read(struct mt7530_priv *priv, int port, int devad, MT7531_MDIO_DEV_ADDR(devad); mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val, - !(val & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, + !(val & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; @@ -601,16 +592,13 @@ static int mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad, int regnum, u16 data) { - struct mt7530_dummy_poll p; u32 val, reg; int ret; - INIT_MT7530_DUMMY_POLL(&p, priv, MT7531_PHY_IAC); - mt7530_mutex_lock(priv); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val, - !(val & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, + !(val & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; @@ -620,8 +608,8 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad, MT7531_MDIO_DEV_ADDR(devad) | regnum; mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val, - !(val & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, + !(val & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; @@ -631,8 +619,8 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad, MT7531_MDIO_DEV_ADDR(devad) | data; mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val, - !(val & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, + !(val & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; @@ -647,16 +635,13 @@ mt7531_ind_c45_phy_write(struct mt7530_priv *priv, int port, int devad, static int mt7531_ind_c22_phy_read(struct mt7530_priv *priv, int port, int regnum) { - struct mt7530_dummy_poll p; int ret; u32 val; - INIT_MT7530_DUMMY_POLL(&p, priv, MT7531_PHY_IAC); - mt7530_mutex_lock(priv); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val, - !(val & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, + !(val & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; @@ -667,8 +652,8 @@ mt7531_ind_c22_phy_read(struct mt7530_priv *priv, int port, int regnum) mt7530_mii_write(priv, MT7531_PHY_IAC, val | MT7531_PHY_ACS_ST); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, val, - !(val & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, val, + !(val & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; @@ -685,16 +670,13 @@ static int mt7531_ind_c22_phy_write(struct mt7530_priv *priv, int port, int regnum, u16 data) { - struct mt7530_dummy_poll p; int ret; u32 reg; - INIT_MT7530_DUMMY_POLL(&p, priv, MT7531_PHY_IAC); - mt7530_mutex_lock(priv); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, reg, - !(reg & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, reg, + !(reg & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; @@ -705,8 +687,8 @@ mt7531_ind_c22_phy_write(struct mt7530_priv *priv, int port, int regnum, mt7530_mii_write(priv, MT7531_PHY_IAC, reg | MT7531_PHY_ACS_ST); - ret = readx_poll_timeout(_mt7530_unlocked_read, &p, reg, - !(reg & MT7531_PHY_ACS_ST), 20, 100000); + ret = regmap_read_poll_timeout(priv->regmap, MT7531_PHY_IAC, reg, + !(reg & MT7531_PHY_ACS_ST), 20, 100000); if (ret < 0) { dev_err(priv->dev, "poll timeout\n"); goto out; From b041ed62aa6e3b2d7d36127e0e5d7bf2701f8231 Mon Sep 17 00:00:00 2001 From: Nazim Amirul Date: Mon, 27 Jul 2026 23:09:04 -0700 Subject: [PATCH 444/456] net: stmmac: Fix E2E delay mechanism For E2E delay mechanism, "received DELAY_REQ without timestamp" error messages show up for dwmac v3.70+ and dwxgmac IPs. This issue affects socfpga platforms, Agilex7 (dwmac 3.70) and Agilex5 (dwxgmac). According to the databook, to enable timestamping for all events, the SNAPTYPSEL bits in the MAC_Timestamp_Control register must be set to 2'b01, and the TSEVNTENA bit must be cleared to 0'b0. Commit 3cb958027cb8 ("net: stmmac: Fix E2E delay mechanism") already addresses this problem for all dwmacs above version v4.10. However, same holds true for v3.70 and above, as well as for dwxgmac. Updates the check accordingly. Fixes: 14f347334bf2 ("net: stmmac: Correctly take timestamp for PTPv2") Fixes: f2fb6b6275eb ("net: stmmac: enable timestamp snapshot for required PTP packets in dwmac v5.10a") Fixes: 3cb958027cb8 ("net: stmmac: Fix E2E delay mechanism") Reviewed-by: Maxime Chevallier Signed-off-by: Rohan G Thomas Signed-off-by: Nazim Amirul Link: https://patch.msgid.link/20260728060904.31993-1-muhammad.nazim.amirul.nazle.asmade@altera.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 151c77713025..3801f9d45278 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -755,7 +755,8 @@ static int stmmac_hwtstamp_set(struct net_device *dev, config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; ptp_v2 = PTP_TCR_TSVER2ENA; snap_type_sel = PTP_TCR_SNAPTYPSEL_1; - if (priv->synopsys_id < DWMAC_CORE_4_10) + if (priv->synopsys_id < DWMAC_CORE_3_70 && + priv->plat->core_type != DWMAC_CORE_XGMAC) ts_event_en = PTP_TCR_TSEVNTENA; ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA; ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA; From e1cf066244dad576221b7123a0e5005967f25a20 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 27 Jul 2026 20:18:30 +0200 Subject: [PATCH 445/456] net: openvswitch: fix skb leak on flow key update failure during recirculation do_execute_actions() returns right away when execute_recirc() fails on the last action as it assumes this function always takes ownership of the skb when 'last' is true. But when the flow key update fails, the function doesn't free the skb and it ends up leaked. This is a very unlikely scenario as it requires the packet to become unparseable by applying a set of actions on a previously parseable skb, but should be fixed nevertheless. Reported by Sashiko. Fixes: 971427f353f3 ("openvswitch: Add recirc and hash action.") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260727181851.306076-2-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/openvswitch/actions.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index 513fca6a8e8a..0118fe3b35e4 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c @@ -1108,6 +1108,10 @@ static int execute_masked_set_action(struct sk_buff *skb, return err; } +/* When 'last' is true, recirc() should always consume the 'skb'. + * Otherwise, recirc() should keep 'skb' intact regardless what + * actions are executed on recirculation. + */ static int execute_recirc(struct datapath *dp, struct sk_buff *skb, struct sw_flow_key *key, const struct nlattr *a, bool last) @@ -1118,8 +1122,12 @@ static int execute_recirc(struct datapath *dp, struct sk_buff *skb, int err; err = ovs_flow_key_update(skb, key); - if (err) + if (err) { + if (last) + ovs_kfree_skb_reason(skb, + OVS_DROP_ACTION_ERROR); return err; + } } BUG_ON(!is_flow_key_valid(key)); From bc62e843bc48f933da765ce47079fd992e535794 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 27 Jul 2026 20:18:31 +0200 Subject: [PATCH 446/456] net: openvswitch: fix skb leak on flow key update failure during ct ovs_ct_execute() always steals or frees the skb on failure while ovs_flow_key_update() does not. So, if it fails and we return right away, the skb ends up leaked. Fix that by breaking instead and letting the common error handling code at the bottom of the loop to free the skb properly. This is a very unlikely scenario as it requires the packet to become unparseable by applying a set of actions on a previously parseable skb, but should be fixed nevertheless. Reported by Sashiko. Fixes: ec0d043d05e6 ("openvswitch: Ensure flow is valid before executing ct") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260727181851.306076-3-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/openvswitch/actions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index 0118fe3b35e4..dc5ff859f114 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c @@ -1380,7 +1380,7 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, if (!is_flow_key_valid(key)) { err = ovs_flow_key_update(skb, key); if (err) - return err; + break; } err = ovs_ct_execute(ovs_dp_get_net(dp), skb, key, From e67cc80b50f587cd1d8ffc8989dcec3291720bc3 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Mon, 27 Jul 2026 04:37:59 -0700 Subject: [PATCH 447/456] net: mana: Return error code from mana_create_rxq() mana_create_rxq() returns a struct mana_rxq pointer and returns NULL on any failure. The caller, mana_add_rx_queues(), cannot tell what went wrong and hardcodes the error as -ENOMEM. As a result the actual failure reported by the lower layers (for example -EPROTO from a failed HW request) is masked and every RX queue creation failure looks like an out-of-memory error. Return an ERR_PTR() encoded error code from mana_create_rxq() on failure instead of NULL. The caller now propagates the returned error code directly instead of substituting -ENOMEM. Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)") Signed-off-by: Aditya Garg Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260727113759.2881500-1-gargaditya@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_en.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 9d9bfd116dab..92bb55935c1c 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -2829,7 +2829,7 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, rxq = kvzalloc_flex(*rxq, rx_oobs, apc->rx_queue_size); if (!rxq) - return NULL; + return ERR_PTR(-ENOMEM); rxq->ndev = ndev; rxq->num_rx_buf = apc->rx_queue_size; @@ -2930,7 +2930,7 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, mana_destroy_rxq(apc, rxq, false); - return NULL; + return ERR_PTR(err); } static void mana_create_rxq_debugfs(struct mana_port_context *apc, int idx) @@ -2964,8 +2964,8 @@ static int mana_add_rx_queues(struct mana_port_context *apc, for (i = 0; i < apc->num_queues; i++) { rxq = mana_create_rxq(apc, i, &apc->eqs[i], ndev); - if (!rxq) { - err = -ENOMEM; + if (IS_ERR(rxq)) { + err = PTR_ERR(rxq); netdev_err(ndev, "Failed to create rxq %d : %d\n", i, err); goto out; } From 54ad7ea45d63146a8e3c57375f8a269d4cf7ecea Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 27 Jul 2026 14:03:48 +0800 Subject: [PATCH 448/456] ptp: netc: fix potential interrupt storm caused by incorrect unbind order In netc_timer_remove(), hardware interrupts are disabled by clearing TMR_TEMASK before ptp_clock_unregister() is called. This may cause a race condition during driver unbind that could leave hardware interrupts active. For example, a concurrent PTP_CLK_REQ_EXTTS ioctl can re-enable TMR_TEMASK after it has been cleared, leaving a pending hardware interrupt when the driver unbinds. Since the NETC Timer does not support PCIe FLR, hardware state is not reset during probe. When the driver is rebound and the IRQ is registered, the pending interrupt fires immediately. At that point priv->tmr_emask is still zero, so netc_timer_isr() does not clear the interrupt status and unconditionally returns IRQ_HANDLED, resulting in an uninterruptible infinite interrupt storm. Fix this in several ways. First, request the IRQ with IRQF_NO_AUTOEN so it is not enabled when request_irq() runs, and clear TMR_TEMASK in netc_timer_init() before enabling it. The IRQ is only enabled at the end of probe once the timer has been reprogrammed and the PTP clock has been registered. This ensures a stale pending interrupt from a previous unbind or an unclean shutdown cannot be delivered before the driver is fully initialized. Second, in netc_timer_remove() call disable_irq() before ptp_clock_unregister() and move the TMR_TEMASK/TMR_CTRL clearing after it. disable_irq() masks the line and waits for any in-flight netc_timer_isr() to finish, so no ISR can dereference priv->clock after ptp_clock_unregister() has freed it. Unregistering the PTP clock before clearing the mask also guarantees that no in-flight or concurrent ioctl can re-enable hardware interrupts. Finally, return IRQ_NONE from netc_timer_isr() when the masked event status is zero, so the kernel's spurious interrupt detection can disable a stuck line instead of looping forever. Fixes: 671e266835b8 ("ptp: netc: add periodic pulse output support") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260720012508.23227-1-wei.fang%40oss.nxp.com Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260727060348.1887464-1-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_netc.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c index 5e381c354d74..1c20d7efab92 100644 --- a/drivers/ptp/ptp_netc.c +++ b/drivers/ptp/ptp_netc.c @@ -769,6 +769,7 @@ static void netc_timer_init(struct netc_timer *priv) TMR_CTRL_TE | TMR_CTRL_FS; netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl); netc_timer_wr(priv, NETC_TMR_PRSC, priv->oclk_prsc); + netc_timer_wr(priv, NETC_TMR_TEMASK, 0); /* Disable FIPER by default */ fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL); @@ -901,6 +902,11 @@ static irqreturn_t netc_timer_isr(int irq, void *data) /* Clear interrupts status */ netc_timer_wr(priv, NETC_TMR_TEVENT, tmr_event); + if (!tmr_event) { + spin_unlock(&priv->lock); + return IRQ_NONE; + } + if (tmr_event & TMR_TEVENT_ALMEN(0)) netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, 0); @@ -936,7 +942,8 @@ static int netc_timer_init_msix_irq(struct netc_timer *priv) } priv->irq = pci_irq_vector(pdev, 0); - err = request_irq(priv->irq, netc_timer_isr, 0, priv->irq_name, priv); + err = request_irq(priv->irq, netc_timer_isr, IRQF_NO_AUTOEN, + priv->irq_name, priv); if (err) { dev_err(&pdev->dev, "request_irq() failed\n"); pci_free_irq_vectors(pdev); @@ -951,7 +958,6 @@ static void netc_timer_free_msix_irq(struct netc_timer *priv) { struct pci_dev *pdev = priv->pdev; - disable_irq(priv->irq); free_irq(priv->irq, priv); pci_free_irq_vectors(pdev); } @@ -1005,6 +1011,8 @@ static int netc_timer_probe(struct pci_dev *pdev, goto free_msix_irq; } + enable_irq(priv->irq); + return 0; free_msix_irq: @@ -1019,9 +1027,10 @@ static void netc_timer_remove(struct pci_dev *pdev) { struct netc_timer *priv = pci_get_drvdata(pdev); + disable_irq(priv->irq); + ptp_clock_unregister(priv->clock); netc_timer_wr(priv, NETC_TMR_TEMASK, 0); netc_timer_wr(priv, NETC_TMR_CTRL, 0); - ptp_clock_unregister(priv->clock); netc_timer_free_msix_irq(priv); netc_timer_pci_remove(pdev); } From 70fd0cf29bc47882c1cb11ad4fb2881ac2c1e640 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Sun, 26 Jul 2026 22:56:08 -0300 Subject: [PATCH 449/456] net: dsa: realtek: rtl8365mb: use devm_mutex_init for mib_lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With CONFIG_DEBUG_MUTEXES enabled, mutex_destroy() needs to be called before the lock is discarded. Use devm_mutex_init() instead so the cleanup is handled automatically. Fixes: 4af2950c50c86 ("net: dsa: realtek-smi: add rtl8365mb subdriver for RTL8365MB-VC") Reviewed-by: Mieczyslaw Nalewaj Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Linus Walleij Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/20260726-realtek_mutext-v2-1-5d62ba998791@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/realtek/rtl8365mb_main.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index 5ac091bf93c9..aa05375b090a 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -1988,16 +1988,19 @@ static void rtl8365mb_get_stats64(struct dsa_switch *ds, int port, spin_unlock(&p->stats_lock); } -static void rtl8365mb_stats_setup(struct realtek_priv *priv) +static int rtl8365mb_stats_setup(struct realtek_priv *priv) { struct rtl8365mb *mb = priv->chip_data; struct dsa_switch *ds = &priv->ds; struct dsa_port *dp; + int ret; /* Per-chip global mutex to protect MIB counter access, since doing * so requires accessing a series of registers in a particular order. */ - mutex_init(&mb->mib_lock); + ret = devm_mutex_init(priv->dev, &mb->mib_lock); + if (ret) + return ret; dsa_switch_for_each_available_port(dp, ds) { struct rtl8365mb_port *p = &mb->ports[dp->index]; @@ -2010,6 +2013,8 @@ static void rtl8365mb_stats_setup(struct realtek_priv *priv) */ INIT_DELAYED_WORK(&p->mib_work, rtl8365mb_stats_poll); } + + return 0; } static void rtl8365mb_stats_teardown(struct realtek_priv *priv) @@ -2567,7 +2572,12 @@ static int rtl8365mb_setup(struct dsa_switch *ds) } /* Start statistics counter polling */ - rtl8365mb_stats_setup(priv); + ret = rtl8365mb_stats_setup(priv); + if (ret) { + dev_err(priv->dev, "failed to setup stats: %pe\n", + ERR_PTR(ret)); + goto out_teardown_irq; + } return 0; From 050e07f8765d84b4e74fae239ff7a9f29eb6869c Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Sun, 26 Jul 2026 22:56:09 -0300 Subject: [PATCH 450/456] net: dsa: realtek: use devm_mutex_init for regmap lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With CONFIG_DEBUG_MUTEXES enabled, mutex_destroy() needs to be called before the lock is discarded. Use devm_mutex_init() instead so the cleanup is handled automatically. Fixes: 907e772f6f6de ("net: dsa: realtek: allow subdrivers to externally lock regmap") Reviewed-by: Mieczyslaw Nalewaj Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Linus Walleij Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/20260726-realtek_mutext-v2-2-5d62ba998791@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/realtek/rtl83xx.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 71124ecca92f..9402bfe4f85a 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -156,7 +156,10 @@ rtl83xx_probe(struct device *dev, if (!priv) return ERR_PTR(-ENOMEM); - mutex_init(&priv->map_lock); + ret = devm_mutex_init(dev, &priv->map_lock); + if (ret) + return ERR_PTR(ret); + mutex_init(&priv->vlan_lock); mutex_init(&priv->l2_lock); From a95f3e9b8985fc0e21bfcf727e941c1f03476927 Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Sun, 26 Jul 2026 22:56:10 -0300 Subject: [PATCH 451/456] net: dsa: realtek: use devm_mutex_init for vlan_lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With CONFIG_DEBUG_MUTEXES enabled, mutex_destroy() needs to be called before the lock is discarded. Use devm_mutex_init() instead so the cleanup is handled automatically. Fixes: 9da2c8672f771 ("net: dsa: realtek: rtl8365mb: add VLAN support") Reviewed-by: Mieczyslaw Nalewaj Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Linus Walleij Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/20260726-realtek_mutext-v2-3-5d62ba998791@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/realtek/rtl83xx.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 9402bfe4f85a..9f40afb19ab2 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -160,7 +160,10 @@ rtl83xx_probe(struct device *dev, if (ret) return ERR_PTR(ret); - mutex_init(&priv->vlan_lock); + ret = devm_mutex_init(dev, &priv->vlan_lock); + if (ret) + return ERR_PTR(ret); + mutex_init(&priv->l2_lock); rc.lock_arg = priv; From 442ecdc83d00d6c2312541c4e0ada47e02805fcb Mon Sep 17 00:00:00 2001 From: Luiz Angelo Daros de Luca Date: Sun, 26 Jul 2026 22:56:11 -0300 Subject: [PATCH 452/456] net: dsa: realtek: use devm_mutex_init for l2_lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With CONFIG_DEBUG_MUTEXES enabled, mutex_destroy() needs to be called before the lock is discarded. Use devm_mutex_init() instead so the cleanup is handled automatically. Fixes: 336e3e4a1ab37 ("net: dsa: realtek: rtl8365mb: add FDB support") Reviewed-by: Mieczyslaw Nalewaj Signed-off-by: Luiz Angelo Daros de Luca Reviewed-by: Linus Walleij Reviewed-by: Alvin Šipraga Link: https://patch.msgid.link/20260726-realtek_mutext-v2-4-5d62ba998791@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/realtek/rtl83xx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 9f40afb19ab2..9dd50b20c000 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -164,7 +164,9 @@ rtl83xx_probe(struct device *dev, if (ret) return ERR_PTR(ret); - mutex_init(&priv->l2_lock); + ret = devm_mutex_init(dev, &priv->l2_lock); + if (ret) + return ERR_PTR(ret); rc.lock_arg = priv; priv->map = devm_regmap_init(dev, NULL, priv, &rc); From 732ed8f75ce583d115716f668dc80d730f3ad610 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Fri, 24 Jul 2026 15:46:57 +0800 Subject: [PATCH 453/456] net: libwx: fix FDIR ATR queue mismatch for software VLAN packets When TX VLAN hardware offload is disabled, VLAN tags are embedded in the packet payload (software VLAN). Previously, the driver failed to set the WX_TX_FLAGS_SW_VLAN flag for these packets during transmission. This missing flag caused the txgbe FDIR ATR logic to fall through to the default hash calculation path. This resulted in asymmetric hash values for Tx and Rx flows, preventing return packets from being steered to the same queue as the transmit packets. Fix this by detecting software VLANs via eth_type_vlan(skb->protocol) and setting WX_TX_FLAGS_SW_VLAN. This ensures the ATR feature selects the correct hashing algorithm to maintain Tx/Rx queue symmetry. Fixes: b501d261a5b3 ("net: txgbe: add FDIR ATR support") Signed-off-by: Jiawen Wu Reviewed-by: Simon Horman Link: https://patch.msgid.link/0879DA38A8E32701+20260724074657.10773-1-jiawenwu@trustnetic.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/wangxun/libwx/wx_lib.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index 814d88d2aee4..5d99e870de5e 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -1606,6 +1606,8 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, if (skb_vlan_tag_present(skb)) { tx_flags |= skb_vlan_tag_get(skb) << WX_TX_FLAGS_VLAN_SHIFT; tx_flags |= WX_TX_FLAGS_HW_VLAN; + } else if (eth_type_vlan(skb->protocol)) { + tx_flags |= WX_TX_FLAGS_SW_VLAN; } if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && From 16809472409d998afcda402e32b8229b389337c4 Mon Sep 17 00:00:00 2001 From: Suman Ghosh Date: Fri, 24 Jul 2026 12:58:31 +0530 Subject: [PATCH 454/456] octeontx2-pf: Set correct sequence for carrier off and tx queue stop During link down event, we were doing netif_tx_stop_all_queues() first and then netif_carrier_off(). This can cause a potential race since carrier is still on during down event. This patch reverse the calling order to fix the issue. Fixes: 50fe6c02e5ad ("octeontx2-pf: Register and handle link notifications") Signed-off-by: Suman Ghosh Signed-off-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260724072831.2415281-1-rkannoth@marvell.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c index 2e33b33ec993..c995f2900859 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c @@ -889,8 +889,8 @@ static void otx2_handle_link_event(struct otx2_nic *pf) netif_carrier_on(netdev); netif_tx_start_all_queues(netdev); } else { - netif_tx_stop_all_queues(netdev); netif_carrier_off(netdev); + netif_tx_stop_all_queues(netdev); } } From a58a2b0ce354df531ebc71fc870058c2feb59f6b Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 27 Jul 2026 14:10:21 +0200 Subject: [PATCH 455/456] net: openvswitch: fix potential UAF on meter attach failure While attaching a newly created meter attach_meter() function makes the new meter visible to other CPUs but can still fail afterwards. On failure, it detaches the meter back and returns an error. However, this is an unexpected behavior for the ovs_meter_cmd_set() that uses a plain kfree(meter) on attach failure without waiting for RCU readers to stop using it, assuming it was never visible. This is never a problem for ovs-vswitchd as it always creates meters before creating any flows that use them. But the UAF can be triggered with a custom application using uAPI: BUG: KASAN: slab-use-after-free in ovs_meter_execute (net/openvswitch/meter.c:653) Read of size 8 at addr ffff88810d152650 by task meter/2508 Call Trace: ovs_meter_execute (net/openvswitch/meter.c:653) do_execute_actions (net/openvswitch/actions.c:1407) ovs_execute_actions (net/openvswitch/actions.c:1584) ovs_packet_cmd_execute (net/openvswitch/datapath.c:703) ... netlink_sendmsg (af_netlink.c:1900) Allocated by task 2519: __kasan_kmalloc (mm/kasan/common.c:398 mm/kasan/common.c:415) ovs_meter_cmd_set (net/openvswitch/meter.c:422) ... netlink_sendmsg (af_netlink.c:1900) Freed by task 2519: kfree (mm/slub.c:2705 mm/slub.c:6405 mm/slub.c:6720) ovs_meter_cmd_set (net/openvswitch/meter.c:479) ... netlink_sendmsg (af_netlink.c:1900) Fix that by making sure attach_meter() doesn't make the meter visible until all the checks are done and the function can't fail anymore. This also makes sure the "hash" value is calculated after the potential re-sizing of the table. Reported by Trend Micro's Zero Day Initiative as ZDI-CAN-31642. Fixes: c7c4c44c9a95 ("net: openvswitch: expand the meters supported number") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Eelco Chaudron Link: https://patch.msgid.link/20260727121022.198461-1-i.maximets@ovn.org Signed-off-by: Paolo Abeni --- net/openvswitch/meter.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/net/openvswitch/meter.c b/net/openvswitch/meter.c index a02c47277337..4aaeeae3af5b 100644 --- a/net/openvswitch/meter.c +++ b/net/openvswitch/meter.c @@ -133,18 +133,10 @@ static void dp_meter_instance_remove(struct dp_meter_instance *ti, static int attach_meter(struct dp_meter_table *tbl, struct dp_meter *meter) { - struct dp_meter_instance *ti = rcu_dereference_ovsl(tbl->ti); - u32 hash = meter_hash(ti, meter->id); + struct dp_meter_instance *ti; + u32 hash; int err; - /* In generally, slots selected should be empty, because - * OvS uses id-pool to fetch a available id. - */ - if (unlikely(rcu_dereference_ovsl(ti->dp_meters[hash]))) - return -EBUSY; - - dp_meter_instance_insert(ti, meter); - /* That function is thread-safe. */ tbl->count++; if (tbl->count >= tbl->max_meters_allowed) { @@ -152,16 +144,29 @@ static int attach_meter(struct dp_meter_table *tbl, struct dp_meter *meter) goto attach_err; } - if (tbl->count >= ti->n_meters && - dp_meter_instance_realloc(tbl, ti->n_meters * 2)) { - err = -ENOMEM; + ti = rcu_dereference_ovsl(tbl->ti); + if (tbl->count >= ti->n_meters) { + err = dp_meter_instance_realloc(tbl, ti->n_meters * 2); + if (err) + goto attach_err; + + ti = rcu_dereference_ovsl(tbl->ti); + } + + hash = meter_hash(ti, meter->id); + + /* In general, selected slots should be empty, because + * OvS uses id-pool to fetch available ids. + */ + if (unlikely(rcu_dereference_ovsl(ti->dp_meters[hash]))) { + err = -EBUSY; goto attach_err; } + dp_meter_instance_insert(ti, meter); return 0; attach_err: - dp_meter_instance_remove(ti, meter); tbl->count--; return err; } From 451c9075d6c53f2438d110addbeeeea6fac18567 Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Sun, 26 Jul 2026 12:43:11 +0200 Subject: [PATCH 456/456] qede: sync udp_tunnel ports outside qede_lock in the recovery path A TX timeout on a qede NIC that has VXLAN/GENEVE tunnel ports configured wedges the rtnetlink control plane of the whole machine: NETDEV WATCHDOG: ens6f1 (qede): transmit queue 2 timed out 10226 ms [qede_tx_timeout:586(ens6f1)]TX timeout on queue 2! [qede_recovery_handler:2665(ens6f0)]Starting a recovery process The recovery path deadlocks on the driver's own mutex: qede_sp_task rtnl_lock() mutex_lock(&edev->qede_lock) <- taken qede_recovery_handler qede_load udp_tunnel_nic_reset_ntf __udp_tunnel_nic_device_sync info->sync_table == qede_udp_tunnel_sync mutex_lock(&edev->qede_lock) <- same task: deadlock The mutex is not recursive, so the kworker blocks on itself with rtnl_lock held, and neither lock is ever released. Every task that calls rtnl_lock() afterwards (ip, ovs-vswitchd, lldpad, IPv6 addrconf, sshd) blocks forever while the node still answers ping. In a vmcore from an affected production node rtnl_mutex.owner decodes to the very kworker blocked at the innermost mutex_lock() above. Re-sync the tunnel ports from qede_sp_task() after the internal lock is dropped, still under rtnl_lock as the udp_tunnel API requires. This mirrors qede_open(), which calls udp_tunnel_nic_reset_ntf() under rtnl without the internal lock. qede_recovery_handler() now returns whether it has successfully reloaded an open device, and the caller re-syncs the ports only in that case. This keeps the old gating exactly: a device that was down or a failed recovery returns false, as those paths never reached the udp_tunnel_nic_reset_ntf() call before either. This was the only user of the qede_lock()/qede_unlock() helpers, so remove them. Fixes: 8cd160a29415 ("qede: convert to new udp_tunnel_nic infra") Signed-off-by: Denis V. Lunev CC: Andrew Lunn CC: "David S. Miller" CC: Eric Dumazet CC: Jakub Kicinski CC: Paolo Abeni Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260726104311.1782900-1-den@openvz.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/qlogic/qede/qede_main.c | 44 ++++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/qlogic/qede/qede_main.c b/drivers/net/ethernet/qlogic/qede/qede_main.c index cb0ae0650905..7ed17faced54 100644 --- a/drivers/net/ethernet/qlogic/qede/qede_main.c +++ b/drivers/net/ethernet/qlogic/qede/qede_main.c @@ -107,7 +107,7 @@ static void qede_remove(struct pci_dev *pdev); static void qede_shutdown(struct pci_dev *pdev); static void qede_link_update(void *dev, struct qed_link_output *link); static void qede_schedule_recovery_handler(void *dev); -static void qede_recovery_handler(struct qede_dev *edev); +static bool qede_recovery_handler(struct qede_dev *edev); static void qede_schedule_hw_err_handler(void *dev, enum qed_hw_err_type err_type); static void qede_get_eth_tlv_data(void *edev, void *data); @@ -1043,21 +1043,6 @@ void __qede_unlock(struct qede_dev *edev) mutex_unlock(&edev->qede_lock); } -/* This version of the lock should be used when acquiring the RTNL lock is also - * needed in addition to the internal qede lock. - */ -static void qede_lock(struct qede_dev *edev) -{ - rtnl_lock(); - __qede_lock(edev); -} - -static void qede_unlock(struct qede_dev *edev) -{ - __qede_unlock(edev); - rtnl_unlock(); -} - static void qede_periodic_task(struct work_struct *work) { struct qede_dev *edev = container_of(work, struct qede_dev, @@ -1094,6 +1079,8 @@ static void qede_sp_task(struct work_struct *work) */ if (test_and_clear_bit(QEDE_SP_RECOVERY, &edev->sp_flags)) { + bool reloaded; + cancel_delayed_work_sync(&edev->periodic_task); #ifdef CONFIG_QED_SRIOV /* SRIOV must be disabled outside the lock to avoid a deadlock. @@ -1102,9 +1089,17 @@ static void qede_sp_task(struct work_struct *work) if (pci_num_vf(edev->pdev)) qede_sriov_configure(edev->pdev, 0); #endif - qede_lock(edev); - qede_recovery_handler(edev); - qede_unlock(edev); + rtnl_lock(); + __qede_lock(edev); + reloaded = qede_recovery_handler(edev); + __qede_unlock(edev); + + /* The udp_tunnel core synchronously calls back into + * qede_udp_tunnel_sync(), which takes the qede lock. + */ + if (reloaded) + udp_tunnel_nic_reset_ntf(edev->ndev); + rtnl_unlock(); } __qede_lock(edev); @@ -2645,9 +2640,13 @@ static void qede_recovery_failed(struct qede_dev *edev) edev->ops->common->set_power_state(edev->cdev, PCI_D3hot); } -static void qede_recovery_handler(struct qede_dev *edev) +/* Returns true if an open device was successfully reloaded and its + * udp_tunnel ports need to be re-synced by the caller. + */ +static bool qede_recovery_handler(struct qede_dev *edev) { u32 curr_state = edev->state; + bool reloaded = false; int rc; DP_NOTICE(edev, "Starting a recovery process\n"); @@ -2677,17 +2676,18 @@ static void qede_recovery_handler(struct qede_dev *edev) goto err; qede_config_rx_mode(edev->ndev); - udp_tunnel_nic_reset_ntf(edev->ndev); + reloaded = true; } edev->state = curr_state; DP_NOTICE(edev, "Recovery handling is done\n"); - return; + return reloaded; err: qede_recovery_failed(edev); + return false; } static void qede_atomic_hw_err_handler(struct qede_dev *edev)