From: Greg Kroah-Hartman Date: Tue, 21 Jul 2026 10:43:59 +0000 (+0200) Subject: 6.12-stable patches X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=91aedc65815229ae8a9aba17145e5498f0ead410;p=thirdparty%2Fkernel%2Fstable-queue.git 6.12-stable patches added patches: acpi-bus-introduce-devm_acpi_install_notify_handler.patch acpi-driver-check-acpi_companion-against-null-during-probe.patch acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch acpi-nfit-core-use-devm_acpi_install_notify_handler.patch alsa-aoa-check-snd_ctl_new1-return-value.patch alsa-hda-cs35l41-fix-firmware-load-work-teardown.patch alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch bluetooth-hci_conn-fix-null-ptr-deref-in-hci_abort_conn.patch bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch bluetooth-separate-cis_link-and-bis_link-link-types.patch bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch bpf-arm64-powerpc-add-bpf_jit_bypass_spec_v1-v4.patch bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch bpf-convert-lpm_trie.c-to-rqspinlock.patch btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch btrfs-concentrate-the-error-handling-of-submit_one_sector.patch btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch btrfs-remove-the-cow-fixup-mechanism.patch btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch crypto-ccp-do-not-initialize-snp-for-ioctl-snp_config.patch crypto-ccp-fix-a-case-where-snp_shutdown-is-missed.patch crypto-ccp-move-dev_info-err-messages-for-sev-snp-init-and-shutdown.patch crypto-ccp-move-sev-snp-platform-initialization-to-kvm.patch crypto-ccp-register-snp-panic-notifier-only-if-snp-is-enabled.patch crypto-ccp-reset-tmr-size-at-snp-shutdown.patch crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch exfat-add-exfat_get_dentry_set_by_ei-helper.patch exfat-fix-incorrect-directory-checksum-after-rename-to-shorter-name.patch exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch exfat-preserve-benign-secondary-entries-during-rename-and-move.patch exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch hid-add-haptics-page-defines.patch hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch hid-pidff-add-missing-spaces.patch hid-pidff-fix-missing-blank-lines-after-declarations.patch hid-pidff-rework-pidff_upload_effect.patch hid-pidff-use-correct-effect-type-in-effect-update.patch iio-adc-ad7380-select-regmap.patch iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch iio-pressure-remove-redundant-pm_runtime_mark_last_busy-calls.patch io_uring-rw-ensure-reissue-path-is-correctly-handled-for-iopoll.patch io_uring-rw-preserve-partial-result-for-iopoll.patch ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch ksmbd-track-the-connection-owning-a-byte-range-lock.patch ksmbd-use-opener-credentials-for-fsctl-mutations.patch ksmbd_vfs_rename-vfs_path_parent_lookup-accepts-err_ptr-as-name.patch media-nxp-imx8-isi-fix-use-after-free-on-remove.patch media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch mm-swap_cgroup-fix-null-deref-in-lookup_swap_cgroup_id-on-swapless-host.patch netfilter-ebtables-use-vmalloc_array-to-improve-code.patch netfilter-ebtables-zero-chainstack-array.patch pci-altera-fix-resource-leaks-on-probe-failure.patch pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch pci-fix-restoring-bars-on-bar-resize-rollback-path.patch pci-free-saved-list-without-holding-pci_bus_sem.patch pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch pci-mediatek-switch-to-msi_create_parent_irq_domain.patch pci-mediatek-use-generic-macro-for-tpvperl-delay.patch pci-move-resizable-bar-code-to-rebar.c.patch pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch pci-skip-resizable-bar-restore-on-read-error.patch pci-use-pbus_select_window-during-bar-resize.patch perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch proc-rename-proc_setattr-to-proc_nochmod_setattr.patch rust-block-fix-gendisk-cleanup-paths.patch seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch seqlock-introduce-scoped_seqlock_read.patch smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch smb-client-resolve-swn-tcon-from-live-registrations.patch staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch staging-rtl8723bs-fix-spaces-around-binary-operators.patch treewide-switch-rename-to-timer_delete.patch usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch usb-iowarrior-fix-use-after-free-on-disconnect-race.patch usb-iowarrior-remove-inherent-race-with-minor-number.patch vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch vfs-make-last_xxx-private-to-fs-namei.c.patch --- diff --git a/queue-6.12/acpi-bus-introduce-devm_acpi_install_notify_handler.patch b/queue-6.12/acpi-bus-introduce-devm_acpi_install_notify_handler.patch new file mode 100644 index 0000000000..7c02d23a88 --- /dev/null +++ b/queue-6.12/acpi-bus-introduce-devm_acpi_install_notify_handler.patch @@ -0,0 +1,123 @@ +From stable+bounces-273278-greg=kroah.com@vger.kernel.org Fri Jul 10 18:14:51 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 12:14:39 -0400 +Subject: ACPI: bus: Introduce devm_acpi_install_notify_handler() +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Sasha Levin +Message-ID: <20260710161442.293558-2-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit ca70ce555a1eb8edf5fb1d5575f1fe19c9ae17f4 ] + +Introduce devm_acpi_install_notify_handler() for installing an ACPI +notify handler managed by devres that will be removed automatically on +driver detach. + +It installs the notify handler on the device object in the ACPI +namespace that corresponds to the owner device's ACPI companion, if +present (an error is returned if the owner device doesn't have an ACPI +companion). + +Currently, there is no way to manually remove the notify handler +installed by it because none of its users brought on subsequently +will need to do that. + +Signed-off-by: Rafael J. Wysocki +[ rjw: Kerneldoc comment refinement ] +Link: https://patch.msgid.link/2268031.irdbgypaU6@rafael.j.wysocki +Signed-off-by: Rafael J. Wysocki +Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/bus.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ + include/acpi/acpi_bus.h | 2 + + 2 files changed, 68 insertions(+) + +--- a/drivers/acpi/bus.c ++++ b/drivers/acpi/bus.c +@@ -622,6 +622,72 @@ void acpi_dev_remove_notify_handler(stru + } + EXPORT_SYMBOL_GPL(acpi_dev_remove_notify_handler); + ++struct acpi_notify_handler_devres { ++ acpi_notify_handler handler; ++ u32 handler_type; ++}; ++ ++static void devm_acpi_notify_handler_release(struct device *dev, void *res) ++{ ++ struct acpi_notify_handler_devres *dr = res; ++ ++ acpi_dev_remove_notify_handler(ACPI_COMPANION(dev), dr->handler_type, ++ dr->handler); ++} ++ ++/** ++ * devm_acpi_install_notify_handler - Install an ACPI notify handler for a ++ * managed device ++ * @dev: Device to install a notify handler for ++ * @handler_type: Type of the notify handler ++ * @handler: Handler function to install ++ * @context: Data passed back to the handler function ++ * ++ * This function performs the same function as acpi_dev_install_notify_handler() ++ * called for the ACPI companion of @dev with the same @handler_type, @handler, ++ * and @context arguments, but the ACPI notify handler installed by it will be ++ * automatically removed on driver detach. ++ * ++ * Callers should ensure that all resources used by @handler have been allocated ++ * prior to invoking this function, in which case those resources should be ++ * devres-managed so that they won't be released before the notify handler ++ * removal. Otherwise, special synchronization between @handler and the ++ * management of those resources is required. ++ * ++ * When the request fails, an error message is printed. Don't add extra error ++ * messages at the call sites. ++ * ++ * Return: 0 on success or a negative error number. ++ */ ++int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type, ++ acpi_notify_handler handler, void *context) ++{ ++ struct acpi_notify_handler_devres *dr; ++ struct acpi_device *adev; ++ int ret; ++ ++ adev = ACPI_COMPANION(dev); ++ if (!adev) ++ return dev_err_probe(dev, -ENODEV, "No ACPI companion in %s()\n", __func__); ++ ++ dr = devres_alloc(devm_acpi_notify_handler_release, sizeof(*dr), GFP_KERNEL); ++ if (!dr) ++ return -ENOMEM; ++ ++ ret = acpi_dev_install_notify_handler(adev, handler_type, handler, context); ++ if (ret) { ++ devres_free(dr); ++ return dev_err_probe(dev, ret, "Failed to install an ACPI notify handler\n"); ++ } ++ ++ dr->handler = handler; ++ dr->handler_type = handler_type; ++ devres_add(dev, dr); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devm_acpi_install_notify_handler); ++ + /* Handle events targeting \_SB device (at present only graceful shutdown) */ + + #define ACPI_SB_NOTIFY_SHUTDOWN_REQUEST 0x81 +--- a/include/acpi/acpi_bus.h ++++ b/include/acpi/acpi_bus.h +@@ -625,6 +625,8 @@ int acpi_dev_install_notify_handler(stru + void acpi_dev_remove_notify_handler(struct acpi_device *adev, + u32 handler_type, + acpi_notify_handler handler); ++int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type, ++ acpi_notify_handler handler, void *context); + extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32); + extern int register_acpi_notifier(struct notifier_block *); + extern int unregister_acpi_notifier(struct notifier_block *); diff --git a/queue-6.12/acpi-driver-check-acpi_companion-against-null-during-probe.patch b/queue-6.12/acpi-driver-check-acpi_companion-against-null-during-probe.patch new file mode 100644 index 0000000000..b7439ca613 --- /dev/null +++ b/queue-6.12/acpi-driver-check-acpi_companion-against-null-during-probe.patch @@ -0,0 +1,144 @@ +From stable+bounces-273277-greg=kroah.com@vger.kernel.org Fri Jul 10 18:18:09 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 12:14:38 -0400 +Subject: ACPI: driver: Check ACPI_COMPANION() against NULL during probe +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Hans de Goede , Andy Shevchenko , Sasha Levin +Message-ID: <20260710161442.293558-1-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit e4865a56d013e86e46ea6acea15bb6eae01898ff ] + +Since every platform driver can be forced to match a device that doesn't +match its list of device IDs because of device_match_driver_override(), +platform drivers that rely on the existence of a device's ACPI companion +object should verify its presence. + +Accordingly, add requisite ACPI_COMPANION() or ACPI_HANDLE() checks +against NULL to 13 platform drivers handling core ACPI devices. + +Also change the value returned by the ACPI thermal zone driver when +the device's ACPI companion is not present to -ENODEV for consistency +with the other drivers. + +Signed-off-by: Rafael J. Wysocki +Reviewed-by: Hans de Goede +Reviewed-by: Andy Shevchenko +Link: https://patch.msgid.link/4516068.ejJDZkT8p0@rafael.j.wysocki +Cc: 7.0+ # 7.0+ +Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/ac.c | 6 +++++- + drivers/acpi/acpi_pad.c | 6 +++++- + drivers/acpi/acpi_tad.c | 6 +++++- + drivers/acpi/pfr_telemetry.c | 6 +++++- + drivers/acpi/pfr_update.c | 6 +++++- + drivers/acpi/thermal.c | 2 +- + 6 files changed, 26 insertions(+), 6 deletions(-) + +--- a/drivers/acpi/ac.c ++++ b/drivers/acpi/ac.c +@@ -203,11 +203,15 @@ static const struct dmi_system_id ac_dmi + + static int acpi_ac_probe(struct platform_device *pdev) + { +- struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct power_supply_config psy_cfg = {}; ++ struct acpi_device *adev; + struct acpi_ac *ac; + int result; + ++ adev = ACPI_COMPANION(&pdev->dev); ++ if (!adev) ++ return -ENODEV; ++ + ac = kzalloc(sizeof(struct acpi_ac), GFP_KERNEL); + if (!ac) + return -ENOMEM; +--- a/drivers/acpi/acpi_pad.c ++++ b/drivers/acpi/acpi_pad.c +@@ -427,9 +427,13 @@ static void acpi_pad_notify(acpi_handle + + static int acpi_pad_probe(struct platform_device *pdev) + { +- struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); ++ struct acpi_device *adev; + acpi_status status; + ++ adev = ACPI_COMPANION(&pdev->dev); ++ if (!adev) ++ return -ENODEV; ++ + strscpy(acpi_device_name(adev), ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME); + strscpy(acpi_device_class(adev), ACPI_PROCESSOR_AGGREGATOR_CLASS); + +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -588,12 +588,16 @@ static void acpi_tad_remove(struct platf + static int acpi_tad_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +- acpi_handle handle = ACPI_HANDLE(dev); + struct acpi_tad_driver_data *dd; ++ acpi_handle handle; + acpi_status status; + unsigned long long caps; + int ret; + ++ handle = ACPI_HANDLE(dev); ++ if (!handle) ++ return -ENODEV; ++ + ret = acpi_install_cmos_rtc_space_handler(handle); + if (ret < 0) { + dev_info(dev, "Unable to install space handler\n"); +--- a/drivers/acpi/pfr_telemetry.c ++++ b/drivers/acpi/pfr_telemetry.c +@@ -363,10 +363,14 @@ static void pfrt_log_put_idx(void *data) + + static int acpi_pfrt_log_probe(struct platform_device *pdev) + { +- acpi_handle handle = ACPI_HANDLE(&pdev->dev); + struct pfrt_log_device *pfrt_log_dev; ++ acpi_handle handle; + int ret; + ++ handle = ACPI_HANDLE(&pdev->dev); ++ if (!handle) ++ return -ENODEV; ++ + if (!acpi_has_method(handle, "_DSM")) { + dev_dbg(&pdev->dev, "Missing _DSM\n"); + return -ENODEV; +--- a/drivers/acpi/pfr_update.c ++++ b/drivers/acpi/pfr_update.c +@@ -505,10 +505,14 @@ static void pfru_put_idx(void *data) + + static int acpi_pfru_probe(struct platform_device *pdev) + { +- acpi_handle handle = ACPI_HANDLE(&pdev->dev); + struct pfru_device *pfru_dev; ++ acpi_handle handle; + int ret; + ++ handle = ACPI_HANDLE(&pdev->dev); ++ if (!handle) ++ return -ENODEV; ++ + if (!acpi_has_method(handle, "_DSM")) { + dev_dbg(&pdev->dev, "Missing _DSM\n"); + return -ENODEV; +--- a/drivers/acpi/thermal.c ++++ b/drivers/acpi/thermal.c +@@ -789,7 +789,7 @@ static int acpi_thermal_add(struct acpi_ + int i; + + if (!device) +- return -EINVAL; ++ return -ENODEV; + + tz = kzalloc(sizeof(struct acpi_thermal), GFP_KERNEL); + if (!tz) diff --git a/queue-6.12/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch b/queue-6.12/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch new file mode 100644 index 0000000000..a90f2b42f5 --- /dev/null +++ b/queue-6.12/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch @@ -0,0 +1,110 @@ +From stable+bounces-273239-greg=kroah.com@vger.kernel.org Fri Jul 10 15:35:09 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 09:34:39 -0400 +Subject: ACPI: NFIT: core: Fix acpi_nfit_init() error cleanup +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Dave Jiang , Sasha Levin +Message-ID: <20260710133439.82432-1-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit 38bf27511ef41bffebd157ec3eba41fc89ba59cd ] + +If acpi_nfit_init() fails after adding the acpi_desc object to the +acpi_descs list, that object is never removed from that list because +the acpi_nfit_shutdown() devm action is not added for the NFIT device +in that case. Next, the acpi_nfit_init() failure causes +acpi_nfit_probe() to fail, the acpi_desc object is freed, and a +dangling pointer is left behind in the acpi_descs. Any subsequent +ACPI Machine Check Exception will trigger nfit_handle_mce() which +iterates over acpi_descs and so a use-after-free will occur. + +Moreover, if acpi_nfit_probe() returns 0 after installing a notify +handler for the NFIT device and without allocating the acpi_desc +object and setting the NFIT device's driver data pointer, the +acpi_desc object will be allocated by acpi_nfit_update_notify() +and acpi_nfit_init() will be called to initialize it. Regardless +of whether or not acpi_nfit_init() fails in that case, the +acpi_nfit_shutdown() devm action is not added for the NFIT device +and acpi_desc is never removed from the acpi_descs list. If the +acpi_desc object is freed subsequently on driver removal, any +subsequent ACPI MCE will lead to a use-after-free like in the +previous case. + +To address the first issue mentioned above, make acpi_nfit_probe() +call acpi_nfit_shutdown() directly on acpi_nfit_init() failures and +to address the other one, add a remove callback to the driver and +make it call acpi_nfit_shutdown(). Also, since it is now possible to +pass NULL to acpi_nfit_shutdown() or the acpi_desc object passed to it +may not have been initialized, add checks against NULL for acpi_desc and +its nvdimm_bus field to that function and make acpi_nfit_unregister() +clear the latter after unregistering the NVDIMM bus. + +Fixes: a61fe6f7902e ("nfit, tools/testing/nvdimm: unify common init for acpi_nfit_desc") +Fixes: fbabd829fe76 ("acpi, nfit: fix module unload vs workqueue shutdown race") +Signed-off-by: Rafael J. Wysocki +Cc: All applicable +Reviewed-by: Dave Jiang +Link: https://patch.msgid.link/1963615.tdWV9SEqCh@rafael.j.wysocki +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/nfit/core.c | 18 +++++++++++++++--- + 1 file changed, 15 insertions(+), 3 deletions(-) + +--- a/drivers/acpi/nfit/core.c ++++ b/drivers/acpi/nfit/core.c +@@ -3061,6 +3061,8 @@ static void acpi_nfit_unregister(void *d + struct acpi_nfit_desc *acpi_desc = data; + + nvdimm_bus_unregister(acpi_desc->nvdimm_bus); ++ /* The nvdimm_bus object may have been freed, so clear the pointer. */ ++ acpi_desc->nvdimm_bus = NULL; + } + + int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *data, acpi_size sz) +@@ -3301,7 +3303,10 @@ static void acpi_nfit_remove_notify_hand + void acpi_nfit_shutdown(void *data) + { + struct acpi_nfit_desc *acpi_desc = data; +- struct device *bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus); ++ struct device *bus_dev; ++ ++ if (!acpi_desc || !acpi_desc->nvdimm_bus) ++ return; + + /* + * Destruct under acpi_desc_lock so that nfit_handle_mce does not +@@ -3316,6 +3321,7 @@ void acpi_nfit_shutdown(void *data) + mutex_unlock(&acpi_desc->init_mutex); + cancel_delayed_work_sync(&acpi_desc->dwork); + ++ bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus); + /* + * Bounce the nvdimm bus lock to make sure any in-flight + * acpi_nfit_ars_rescan() submissions have had a chance to +@@ -3393,9 +3399,14 @@ static int acpi_nfit_add(struct acpi_dev + sz - sizeof(struct acpi_table_nfit)); + + if (rc) +- return rc; ++ acpi_nfit_shutdown(acpi_desc); + +- return devm_add_action_or_reset(dev, acpi_nfit_shutdown, acpi_desc); ++ return rc; ++} ++ ++static void acpi_nfit_remove(struct acpi_device *adev) ++{ ++ acpi_nfit_shutdown(dev_get_drvdata(&adev->dev)); + } + + static void acpi_nfit_update_notify(struct device *dev, acpi_handle handle) +@@ -3482,6 +3493,7 @@ static struct acpi_driver acpi_nfit_driv + .ids = acpi_nfit_ids, + .ops = { + .add = acpi_nfit_add, ++ .remove = acpi_nfit_remove, + }, + }; + diff --git a/queue-6.12/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch b/queue-6.12/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch new file mode 100644 index 0000000000..647994099c --- /dev/null +++ b/queue-6.12/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch @@ -0,0 +1,222 @@ +From stable+bounces-273281-greg=kroah.com@vger.kernel.org Fri Jul 10 18:18:36 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 12:14:42 -0400 +Subject: ACPI: NFIT: core: Fix possible deadlock and missing notifications +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Dave Jiang , Sasha Levin +Message-ID: <20260710161442.293558-5-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit 18a00ed0e718473f5c3fcfa49df46c944575e60c ] + +After commit 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before +getting NFIT table"), ACPI NFIT driver removal may deadlock if an ACPI +notify on the NFIT device is triggered concurrently. A similar deadlock +may occur if an ACPI notify on the NFIT device is triggered during a +failing driver probe. + +The deadlock is possible because acpi_dev_remove_notify_handler() calls +acpi_os_wait_events_complete() after removing the notify handler and the +driver core invokes it under the NFIT platform device lock which is also +acquired by acpi_nfit_notify(). Thus acpi_os_wait_events_complete() may +be waiting for acpi_nfit_notify() to complete, but the latter may not be +able to acquire the device lock which is being held by the driver core +while the former is being executed. + +Moreover, after commit 03667e146f81 ("ACPI: NFIT: core: Convert the +driver to a platform one"), there are no sysfs notifications regarding +NVDIMM devices because __acpi_nvdimm_notify() always bails out after +checking the driver data pointer of the device's parent. That parent +is the ACPI companion of the platform device used for driver binding, +so its driver data pointer is always NULL after the commit in question +which was overlooked by it. + +A remedy for the deadlock is to use a special separate lock for ACPI +notify synchronization with driver probe and removal instead of the +device lock of the NFIT device, while a remedy for the second issue +is to populate the driver data pointer of the NFIT device's ACPI +companion when the driver is ready to operate, so do both these things. +However, since the new lock is not held across the entire teardown and +acpi_nfit_notify() should do nothing when teardown is in progress, make +it check the driver data pointer of the NFIT device's ACPI companion, in +analogy with the existing check in __acpi_nvdimm_notify(), and bail out +if that pointer is NULL. + +Fixes: 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before getting NFIT table") +Fixes: 03667e146f81 ("ACPI: NFIT: core: Convert the driver to a platform one") +Signed-off-by: Rafael J. Wysocki +Cc: All applicable # 9995e4404ea4: ACPI: NFIT: core: Eliminate redundant local variable +Reviewed-by: Dave Jiang +Link: https://patch.msgid.link/3420096.aeNJFYEL58@rafael.j.wysocki +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/bus.c | 6 +++- + drivers/acpi/nfit/core.c | 62 +++++++++++++++++++++++++++++++++++++---------- + 2 files changed, 54 insertions(+), 14 deletions(-) + +--- a/drivers/acpi/bus.c ++++ b/drivers/acpi/bus.c +@@ -623,6 +623,7 @@ void acpi_dev_remove_notify_handler(stru + EXPORT_SYMBOL_GPL(acpi_dev_remove_notify_handler); + + struct acpi_notify_handler_devres { ++ struct acpi_device *adev; + acpi_notify_handler handler; + u32 handler_type; + }; +@@ -631,7 +632,7 @@ static void devm_acpi_notify_handler_rel + { + struct acpi_notify_handler_devres *dr = res; + +- acpi_dev_remove_notify_handler(ACPI_COMPANION(dev), dr->handler_type, ++ acpi_dev_remove_notify_handler(dr->adev, dr->handler_type, + dr->handler); + } + +@@ -667,6 +668,8 @@ int devm_acpi_install_notify_handler(str + int ret; + + adev = ACPI_COMPANION(dev); ++ if (!adev && dev->bus == &acpi_bus_type) ++ adev = to_acpi_device(dev); + if (!adev) + return dev_err_probe(dev, -ENODEV, "No ACPI companion in %s()\n", __func__); + +@@ -680,6 +683,7 @@ int devm_acpi_install_notify_handler(str + return dev_err_probe(dev, ret, "Failed to install an ACPI notify handler\n"); + } + ++ dr->adev = adev; + dr->handler = handler; + dr->handler_type = handler_type; + devres_add(dev, dr); +--- a/drivers/acpi/nfit/core.c ++++ b/drivers/acpi/nfit/core.c +@@ -55,6 +55,8 @@ MODULE_PARM_DESC(force_labels, "Opt-in t + LIST_HEAD(acpi_descs); + DEFINE_MUTEX(acpi_desc_lock); + ++DEFINE_MUTEX(acpi_notify_lock); ++ + static struct workqueue_struct *nfit_wq; + + struct nfit_table_prev { +@@ -1702,9 +1704,15 @@ static void acpi_nvdimm_notify(acpi_hand + struct acpi_device *adev = data; + struct device *dev = &adev->dev; + +- device_lock(dev->parent); +- __acpi_nvdimm_notify(dev, event); +- device_unlock(dev->parent); ++ /* ++ * Locking is needed here for synchronization with driver probe and ++ * removal and the parent NFIT device's ACPI driver data pointer is ++ * NULL when teardown is in progress. ++ */ ++ guard(mutex)(&acpi_notify_lock); ++ ++ if (acpi_driver_data(to_acpi_device(dev->parent))) ++ __acpi_nvdimm_notify(dev, event); + } + + static bool acpi_nvdimm_has_method(struct acpi_device *adev, char *method) +@@ -3150,11 +3158,10 @@ EXPORT_SYMBOL_GPL(acpi_nfit_init); + static int acpi_nfit_flush_probe(struct nvdimm_bus_descriptor *nd_desc) + { + struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc); +- struct device *dev = acpi_desc->dev; + +- /* Bounce the device lock to flush acpi_nfit_add / acpi_nfit_notify */ +- device_lock(dev); +- device_unlock(dev); ++ /* Bounce the notify lock to flush acpi_nfit_add / acpi_nfit_notify */ ++ mutex_lock(&acpi_notify_lock); ++ mutex_unlock(&acpi_notify_lock); + + /* Bounce the init_mutex to complete initial registration */ + mutex_lock(&acpi_desc->init_mutex); +@@ -3286,10 +3293,17 @@ static void acpi_nfit_put_table(void *ta + static void acpi_nfit_notify(acpi_handle handle, u32 event, void *data) + { + struct device *dev = data; ++ struct acpi_device *adev = to_acpi_device(dev); + +- device_lock(dev); +- __acpi_nfit_notify(dev, handle, event); +- device_unlock(dev); ++ /* ++ * Locking is needed here for synchronization with driver probe and ++ * removal and the ACPI driver data pointer is NULL when teardown ++ * is in progress. ++ */ ++ guard(mutex)(&acpi_notify_lock); ++ ++ if (acpi_driver_data(adev)) ++ __acpi_nfit_notify(dev, handle, event); + } + + void acpi_nfit_shutdown(void *data) +@@ -3336,6 +3350,12 @@ static int acpi_nfit_add(struct acpi_dev + acpi_size sz; + int rc = 0; + ++ /* ++ * Prevent acpi_nfit_notify() from progressing until the probe is ++ * complete in case there is a concurrent event to process. ++ */ ++ guard(mutex)(&acpi_notify_lock); ++ + rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY, + acpi_nfit_notify, dev); + if (rc) +@@ -3351,6 +3371,11 @@ static int acpi_nfit_add(struct acpi_dev + * data in the format of a series of NFIT Structures. + */ + dev_dbg(dev, "failed to find NFIT at startup\n"); ++ /* ++ * Let acpi_nfit_update_notify() run in case it will need to ++ * allocate the acpi_desc object. ++ */ ++ adev->driver_data = dev; + return 0; + } + +@@ -3368,7 +3393,7 @@ static int acpi_nfit_add(struct acpi_dev + acpi_desc->acpi_header = *tbl; + + /* Evaluate _FIT and override with that if present */ +- status = acpi_evaluate_object(ACPI_HANDLE(dev), "_FIT", NULL, &buf); ++ status = acpi_evaluate_object(adev->handle, "_FIT", NULL, &buf); + if (ACPI_SUCCESS(status) && buf.length > 0) { + union acpi_object *obj = buf.pointer; + +@@ -3385,14 +3410,25 @@ static int acpi_nfit_add(struct acpi_dev + + sizeof(struct acpi_table_nfit), + sz - sizeof(struct acpi_table_nfit)); + +- if (rc) ++ if (rc) { + acpi_nfit_shutdown(acpi_desc); ++ return rc; ++ } + +- return rc; ++ /* ++ * Let notify handlers operate (the actual value of the ACPI driver ++ * data pointer does not matter here so long as it is not NULL). ++ */ ++ adev->driver_data = dev; ++ return 0; + } + + static void acpi_nfit_remove(struct acpi_device *adev) + { ++ guard(mutex)(&acpi_notify_lock); ++ ++ /* Make notify handlers bail out early going forward. */ ++ adev->driver_data = NULL; + acpi_nfit_shutdown(dev_get_drvdata(&adev->dev)); + } + diff --git a/queue-6.12/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch b/queue-6.12/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch new file mode 100644 index 0000000000..08902d5bfa --- /dev/null +++ b/queue-6.12/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch @@ -0,0 +1,83 @@ +From stable+bounces-273279-greg=kroah.com@vger.kernel.org Fri Jul 10 18:18:23 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 12:14:40 -0400 +Subject: ACPI: NFIT: core: Use devm_acpi_install_notify_handler() +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Sasha Levin +Message-ID: <20260710161442.293558-3-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit 198541ad53c0d0d891fedea4098f9953a0f566c0 ] + +Now that devm_acpi_install_notify_handler() is available, use it in +acpi_nfit_probe() instead of a custom devm action removing an ACPI +notify handler installed via acpi_dev_install_notify_handler(). + +Also drop the explicit ACPI_COMPANION() check against NULL that is +not necessary any more becuase devm_acpi_install_notify_handler() +carries out an equivalent check internally and use ACPI_HANDLE() to +retrieve the platform device's ACPI handle. + +No intentional functional impact. + +Signed-off-by: Rafael J. Wysocki +Link: https://patch.msgid.link/3048737.e9J7NaK4W3@rafael.j.wysocki +Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/nfit/core.c | 27 +++++++-------------------- + 1 file changed, 7 insertions(+), 20 deletions(-) + +--- a/drivers/acpi/nfit/core.c ++++ b/drivers/acpi/nfit/core.c +@@ -3285,19 +3285,11 @@ static void acpi_nfit_put_table(void *ta + + static void acpi_nfit_notify(acpi_handle handle, u32 event, void *data) + { +- struct acpi_device *adev = data; ++ struct device *dev = data; + +- device_lock(&adev->dev); +- __acpi_nfit_notify(&adev->dev, handle, event); +- device_unlock(&adev->dev); +-} +- +-static void acpi_nfit_remove_notify_handler(void *data) +-{ +- struct acpi_device *adev = data; +- +- acpi_dev_remove_notify_handler(adev, ACPI_DEVICE_NOTIFY, +- acpi_nfit_notify); ++ device_lock(dev); ++ __acpi_nfit_notify(dev, handle, event); ++ device_unlock(dev); + } + + void acpi_nfit_shutdown(void *data) +@@ -3344,13 +3336,8 @@ static int acpi_nfit_add(struct acpi_dev + acpi_size sz; + int rc = 0; + +- rc = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, +- acpi_nfit_notify, adev); +- if (rc) +- return rc; +- +- rc = devm_add_action_or_reset(dev, acpi_nfit_remove_notify_handler, +- adev); ++ rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY, ++ acpi_nfit_notify, dev); + if (rc) + return rc; + +@@ -3381,7 +3368,7 @@ static int acpi_nfit_add(struct acpi_dev + acpi_desc->acpi_header = *tbl; + + /* Evaluate _FIT and override with that if present */ +- status = acpi_evaluate_object(adev->handle, "_FIT", NULL, &buf); ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "_FIT", NULL, &buf); + if (ACPI_SUCCESS(status) && buf.length > 0) { + union acpi_object *obj = buf.pointer; + diff --git a/queue-6.12/alsa-aoa-check-snd_ctl_new1-return-value.patch b/queue-6.12/alsa-aoa-check-snd_ctl_new1-return-value.patch new file mode 100644 index 0000000000..d9b4e0d2ae --- /dev/null +++ b/queue-6.12/alsa-aoa-check-snd_ctl_new1-return-value.patch @@ -0,0 +1,61 @@ +From stable+bounces-273999-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:29 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:17 -0400 +Subject: ALSA: aoa: check snd_ctl_new1() return value +To: stable@vger.kernel.org +Cc: Zhao Dongdong , Takashi Iwai , Sasha Levin +Message-ID: <20260713203917.2143472-1-sashal@kernel.org> + +From: Zhao Dongdong + +[ Upstream commit 8df560fefe6fed6a20b7e06720eeaeccec349ac0 ] + +snd_ctl_new1() can return NULL when memory allocation fails. In +layout.c, the function does not check the return value before +dereferencing ctl->id.name or passing to aoa_snd_ctl_add(), which can +lead to a NULL pointer dereference. + +Add NULL checks after snd_ctl_new1() calls and return early if any +fails. + +Assisted-by: Opencode:DeepSeek-V4-Flash +Cc: stable@vger.kernel.org +Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa") +Signed-off-by: Zhao Dongdong +Link: https://patch.msgid.link/tencent_35F3A25FEEBF190A2E15ED787754C57E3708@qq.com +Signed-off-by: Takashi Iwai +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/aoa/fabrics/layout.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/sound/aoa/fabrics/layout.c ++++ b/sound/aoa/fabrics/layout.c +@@ -948,6 +948,8 @@ static void layout_attached_codec(struct + if (lineout == 1) + ldev->gpio.methods->set_lineout(codec->gpio, 1); + ctl = snd_ctl_new1(&lineout_ctl, codec->gpio); ++ if (!ctl) ++ return; + if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) + strscpy(ctl->id.name, + "Headphone Switch", sizeof(ctl->id.name)); +@@ -962,6 +964,8 @@ static void layout_attached_codec(struct + if (ldev->have_lineout_detect) { + ctl = snd_ctl_new1(&lineout_detect_choice, + ldev); ++ if (!ctl) ++ return; + if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) + strscpy(ctl->id.name, + "Headphone Detect Autoswitch", +@@ -969,6 +973,8 @@ static void layout_attached_codec(struct + aoa_snd_ctl_add(ctl); + ctl = snd_ctl_new1(&lineout_detected, + ldev); ++ if (!ctl) ++ return; + if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) + strscpy(ctl->id.name, + "Headphone Detected", diff --git a/queue-6.12/alsa-hda-cs35l41-fix-firmware-load-work-teardown.patch b/queue-6.12/alsa-hda-cs35l41-fix-firmware-load-work-teardown.patch new file mode 100644 index 0000000000..b3f7be11d9 --- /dev/null +++ b/queue-6.12/alsa-hda-cs35l41-fix-firmware-load-work-teardown.patch @@ -0,0 +1,175 @@ +From stable+bounces-274002-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:35 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:27 -0400 +Subject: ALSA: hda/cs35l41: Fix firmware load work teardown +To: stable@vger.kernel.org +Cc: "Cássio Gabriel" , "Stefan Binding" , "Takashi Iwai" , "Sasha Levin" +Message-ID: <20260713203927.2143760-1-sashal@kernel.org> + +From: Cássio Gabriel + +[ Upstream commit b65020d5398f499c09498c9786dba6d67ae57664 ] + +cs35l41_hda creates ALSA controls whose private data points at the +cs35l41_hda object. The firmware load control can also queue +fw_load_work. + +Those controls are not removed on component unbind, and device remove +only cancels fw_load_work through cs35l41_remove_dsp(). That helper is +skipped when halo_initialized is false. With firmware_autostart +disabled, a firmware load can be requested before the DSP has been +initialized. If the component or device is removed before the queued +work runs, the worker can run after teardown and dereference driver +state that is no longer valid. + +Track the created controls and remove them on unbind so no new control +callback can reach the driver data or queue more work. Then cancel +fw_load_work to drain any request that was already queued. Also cancel +the work unconditionally during device remove before runtime PM teardown. + +Fixes: 47ceabd99a28 ("ALSA: hda: cs35l41: Support Firmware switching and reloading") +Fixes: 4c870513fbb0 ("ALSA: hda: cs35l41: Add read-only ALSA control for forced mute") +Cc: stable@vger.kernel.org +Signed-off-by: Cássio Gabriel +Reviewed-by: Stefan Binding +Signed-off-by: Takashi Iwai +Link: https://patch.msgid.link/20260511-alsa-hda-cs35l41-fw-work-teardown-v1-1-1184e9bc4f25@gmail.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/pci/hda/cs35l41_hda.c | 77 +++++++++++++++++++++++++++++++------------- + sound/pci/hda/cs35l41_hda.h | 5 ++ + 2 files changed, 60 insertions(+), 22 deletions(-) + +--- a/sound/pci/hda/cs35l41_hda.c ++++ b/sound/pci/hda/cs35l41_hda.c +@@ -1293,6 +1293,43 @@ static int cs35l41_fw_type_ctl_info(stru + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(hda_cs_dsp_fw_ids), hda_cs_dsp_fw_ids); + } + ++static void cs35l41_remove_controls(struct cs35l41_hda *cs35l41) ++{ ++ if (!cs35l41->codec) ++ return; ++ ++ snd_ctl_remove(cs35l41->codec->card, cs35l41->mute_override_ctl); ++ cs35l41->mute_override_ctl = NULL; ++ ++ snd_ctl_remove(cs35l41->codec->card, cs35l41->fw_load_ctl); ++ cs35l41->fw_load_ctl = NULL; ++ ++ snd_ctl_remove(cs35l41->codec->card, cs35l41->fw_type_ctl); ++ cs35l41->fw_type_ctl = NULL; ++} ++ ++static int cs35l41_add_control(struct cs35l41_hda *cs35l41, ++ struct snd_kcontrol_new *ctl, ++ struct snd_kcontrol **kctl) ++{ ++ int ret; ++ ++ *kctl = snd_ctl_new1(ctl, cs35l41); ++ if (!*kctl) ++ return -ENOMEM; ++ ++ ret = snd_ctl_add(cs35l41->codec->card, *kctl); ++ if (ret) { ++ dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", ctl->name, ret); ++ *kctl = NULL; ++ return ret; ++ } ++ ++ dev_dbg(cs35l41->dev, "Added Control %s\n", ctl->name); ++ ++ return 0; ++} ++ + static int cs35l41_create_controls(struct cs35l41_hda *cs35l41) + { + char fw_type_ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; +@@ -1328,32 +1365,23 @@ static int cs35l41_create_controls(struc + scnprintf(mute_override_ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s Forced Mute Status", + cs35l41->amp_name); + +- ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_type_ctl, cs35l41)); +- if (ret) { +- dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", fw_type_ctl.name, ret); +- return ret; +- } +- +- dev_dbg(cs35l41->dev, "Added Control %s\n", fw_type_ctl.name); +- +- ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_load_ctl, cs35l41)); +- if (ret) { +- dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", fw_load_ctl.name, ret); +- return ret; +- } +- +- dev_dbg(cs35l41->dev, "Added Control %s\n", fw_load_ctl.name); ++ ret = cs35l41_add_control(cs35l41, &fw_type_ctl, &cs35l41->fw_type_ctl); ++ if (ret) ++ goto err; + +- ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&mute_override_ctl, cs35l41)); +- if (ret) { +- dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", mute_override_ctl.name, +- ret); +- return ret; +- } ++ ret = cs35l41_add_control(cs35l41, &fw_load_ctl, &cs35l41->fw_load_ctl); ++ if (ret) ++ goto err; + +- dev_dbg(cs35l41->dev, "Added Control %s\n", mute_override_ctl.name); ++ ret = cs35l41_add_control(cs35l41, &mute_override_ctl, &cs35l41->mute_override_ctl); ++ if (ret) ++ goto err; + + return 0; ++ ++err: ++ cs35l41_remove_controls(cs35l41); ++ return ret; + } + + static bool cs35l41_dsm_supported(acpi_handle handle, unsigned int commands) +@@ -1491,6 +1519,10 @@ static void cs35l41_hda_unbind(struct de + device_link_remove(&cs35l41->codec->core.dev, cs35l41->dev); + unlock_system_sleep(sleep_flags); + memset(comp, 0, sizeof(*comp)); ++ ++ cs35l41_remove_controls(cs35l41); ++ cancel_work_sync(&cs35l41->fw_load_work); ++ cs35l41->codec = NULL; + } + } + +@@ -2030,6 +2062,7 @@ void cs35l41_hda_remove(struct device *d + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + + component_del(cs35l41->dev, &cs35l41_hda_comp_ops); ++ cancel_work_sync(&cs35l41->fw_load_work); + + pm_runtime_get_sync(cs35l41->dev); + pm_runtime_dont_use_autosuspend(cs35l41->dev); +--- a/sound/pci/hda/cs35l41_hda.h ++++ b/sound/pci/hda/cs35l41_hda.h +@@ -56,6 +56,8 @@ enum control_bus { + SPI + }; + ++struct snd_kcontrol; ++ + struct cs35l41_hda { + struct device *dev; + struct regmap *regmap; +@@ -74,6 +76,9 @@ struct cs35l41_hda { + int speaker_id; + struct mutex fw_mutex; + struct work_struct fw_load_work; ++ struct snd_kcontrol *fw_type_ctl; ++ struct snd_kcontrol *fw_load_ctl; ++ struct snd_kcontrol *mute_override_ctl; + + struct regmap_irq_chip_data *irq_data; + bool firmware_running; diff --git a/queue-6.12/alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch b/queue-6.12/alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch new file mode 100644 index 0000000000..10e927e7f1 --- /dev/null +++ b/queue-6.12/alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch @@ -0,0 +1,340 @@ +From stable+bounces-274088-greg=kroah.com@vger.kernel.org Tue Jul 14 05:04:48 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 23:04:37 -0400 +Subject: ALSA: scarlett2: Allow selecting config_set by firmware version +To: stable@vger.kernel.org +Cc: "Geoffrey D. Bennett" , Takashi Iwai , Sasha Levin +Message-ID: <20260714030438.2382402-1-sashal@kernel.org> + +From: "Geoffrey D. Bennett" + +[ Upstream commit 732a6397a526c025cd29c3c9309b0db6a2c08837 ] + +The Scarlett 2i2 Gen 4 firmware 2417 moved the direct monitor gain +parameters, so we now need to allow each device to list multiple +scarlett2_config_set entries, one per applicable firmware version +range, and pick the matching one at probe time. + +No functional change yet: each device gets a single config_sets +entry whose from_firmware_version matches the existing +min_firmware_version (0 where none was set). This both prepares for +selection and lets a follow-up commit remove the now-redundant +min_firmware_version field. + +scarlett2_count_io() depends on the resolved config_set so it moves +out of scarlett2_init_private() into snd_scarlett2_controls_create() +after the firmware version has been read. + +Signed-off-by: Geoffrey D. Bennett +Signed-off-by: Takashi Iwai +Link: https://patch.msgid.link/ae1695b4c4825f365b4c86b22174035f742807e3.1777151532.git.g@b4.vu +Stable-dep-of: 3ca15754b561 ("ALSA: scarlett2: Update offsets for 2i2 Gen 4 firmware 2417") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/usb/mixer_scarlett2.c | 143 +++++++++++++++++++++++++++++++++++++------- + 1 file changed, 121 insertions(+), 22 deletions(-) + +--- a/sound/usb/mixer_scarlett2.c ++++ b/sound/usb/mixer_scarlett2.c +@@ -588,6 +588,20 @@ struct scarlett2_config_set { + const struct scarlett2_config items[SCARLETT2_CONFIG_COUNT]; + }; + ++/* Map firmware versions to config sets per-device. ++ * ++ * Each device lists one or more entries, sorted in ascending order of ++ * from_firmware_version. At probe time the running firmware version ++ * is looked up against this list and the last entry whose ++ * from_firmware_version is <= the running version is selected. ++ * ++ * The list is terminated by a sentinel entry with config_set == NULL. ++ */ ++struct scarlett2_config_set_entry { ++ u16 from_firmware_version; ++ const struct scarlett2_config_set *config_set; ++}; ++ + /* Input gain TLV dB ranges */ + + static const DECLARE_TLV_DB_MINMAX( +@@ -1073,8 +1087,8 @@ struct scarlett2_meter_entry { + }; + + struct scarlett2_device_info { +- /* which set of configuration parameters the device uses */ +- const struct scarlett2_config_set *config_set; ++ /* which sets of configuration parameters the device uses */ ++ const struct scarlett2_config_set_entry *config_sets; + + /* minimum firmware version required */ + u16 min_firmware_version; +@@ -1309,7 +1323,10 @@ struct scarlett2_data { + /*** Model-specific data ***/ + + static const struct scarlett2_device_info s6i6_gen2_info = { +- .config_set = &scarlett2_config_set_gen2a, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen2a }, ++ { } ++ }, + .level_input_count = 2, + .pad_input_count = 2, + +@@ -1359,7 +1376,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s18i8_gen2_info = { +- .config_set = &scarlett2_config_set_gen2a, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen2a }, ++ { } ++ }, + .level_input_count = 2, + .pad_input_count = 4, + +@@ -1412,7 +1432,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s18i20_gen2_info = { +- .config_set = &scarlett2_config_set_gen2b, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen2b }, ++ { } ++ }, + + .line_out_descrs = { + "Monitor L", +@@ -1469,7 +1492,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info solo_gen3_info = { +- .config_set = &scarlett2_config_set_gen3a, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3a }, ++ { } ++ }, + .level_input_count = 1, + .level_input_first = 1, + .air_input_count = 1, +@@ -1479,7 +1505,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s2i2_gen3_info = { +- .config_set = &scarlett2_config_set_gen3a, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3a }, ++ { } ++ }, + .level_input_count = 2, + .air_input_count = 2, + .phantom_count = 1, +@@ -1488,7 +1517,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s4i4_gen3_info = { +- .config_set = &scarlett2_config_set_gen3b, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3b }, ++ { } ++ }, + .level_input_count = 2, + .pad_input_count = 2, + .air_input_count = 2, +@@ -1537,7 +1569,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s8i6_gen3_info = { +- .config_set = &scarlett2_config_set_gen3b, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3b }, ++ { } ++ }, + .level_input_count = 2, + .pad_input_count = 2, + .air_input_count = 2, +@@ -1603,7 +1638,10 @@ static const char * const scarlett2_spdi + }; + + static const struct scarlett2_device_info s18i8_gen3_info = { +- .config_set = &scarlett2_config_set_gen3c, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3c }, ++ { } ++ }, + .has_speaker_switching = 1, + .level_input_count = 2, + .pad_input_count = 4, +@@ -1695,7 +1733,10 @@ static const char * const scarlett2_spdi + }; + + static const struct scarlett2_device_info s18i20_gen3_info = { +- .config_set = &scarlett2_config_set_gen3c, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3c }, ++ { } ++ }, + .has_speaker_switching = 1, + .has_talkback = 1, + .level_input_count = 2, +@@ -1769,7 +1810,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info vocaster_one_info = { +- .config_set = &scarlett2_config_set_vocaster, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 1769, &scarlett2_config_set_vocaster }, ++ { } ++ }, + .min_firmware_version = 1769, + + .phantom_count = 1, +@@ -1811,7 +1855,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info vocaster_two_info = { +- .config_set = &scarlett2_config_set_vocaster, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 1769, &scarlett2_config_set_vocaster }, ++ { } ++ }, + .min_firmware_version = 1769, + + .phantom_count = 2, +@@ -1854,7 +1901,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info solo_gen4_info = { +- .config_set = &scarlett2_config_set_gen4_solo, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 2115, &scarlett2_config_set_gen4_solo }, ++ { } ++ }, + .min_firmware_version = 2115, + + .level_input_count = 1, +@@ -1908,7 +1958,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s2i2_gen4_info = { +- .config_set = &scarlett2_config_set_gen4_2i2, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 2115, &scarlett2_config_set_gen4_2i2 }, ++ { } ++ }, + .min_firmware_version = 2115, + + .level_input_count = 2, +@@ -1962,7 +2015,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s4i4_gen4_info = { +- .config_set = &scarlett2_config_set_gen4_4i4, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 2089, &scarlett2_config_set_gen4_4i4 }, ++ { } ++ }, + .min_firmware_version = 2089, + + .level_input_count = 2, +@@ -2010,7 +2066,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info clarett_2pre_info = { +- .config_set = &scarlett2_config_set_clarett, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_clarett }, ++ { } ++ }, + .level_input_count = 2, + .air_input_count = 2, + +@@ -2066,7 +2125,10 @@ static const char * const scarlett2_spdi + }; + + static const struct scarlett2_device_info clarett_4pre_info = { +- .config_set = &scarlett2_config_set_clarett, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_clarett }, ++ { } ++ }, + .level_input_count = 2, + .air_input_count = 4, + +@@ -2122,7 +2184,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info clarett_8pre_info = { +- .config_set = &scarlett2_config_set_clarett, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_clarett }, ++ { } ++ }, + .level_input_count = 2, + .air_input_count = 8, + +@@ -8539,10 +8604,32 @@ static void scarlett2_private_suspend(st + + /*** Initialisation ***/ + ++/* Select the config_set matching the running firmware version. ++ * ++ * The device info's config_sets array is ordered by ascending ++ * from_firmware_version; pick the last entry whose version is <= the ++ * running firmware version. If the running firmware is older than the ++ * first entry's from_firmware_version (i.e. older than the driver's ++ * minimum supported version for this device), the first entry's ++ * config_set is selected anyway so firmware updates can still be done ++ * (requires only the ACK handler), but the usual mixer controls ++ * aren't created. ++ */ ++static void scarlett2_resolve_config_set(struct scarlett2_data *private) ++{ ++ const struct scarlett2_config_set_entry *entry = ++ private->info->config_sets; ++ ++ private->config_set = entry->config_set; ++ for (entry++; entry->config_set; entry++) ++ if (entry->from_firmware_version <= private->firmware_version) ++ private->config_set = entry->config_set; ++} ++ + static void scarlett2_count_io(struct scarlett2_data *private) + { + const struct scarlett2_device_info *info = private->info; +- const struct scarlett2_config_set *config_set = info->config_set; ++ const struct scarlett2_config_set *config_set = private->config_set; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + int port_type, srcs = 0, dsts = 0, i; + +@@ -8640,9 +8727,14 @@ static int scarlett2_init_private(struct + mixer->private_suspend = scarlett2_private_suspend; + + private->info = entry->info; +- private->config_set = entry->info->config_set; ++ ++ /* Set config_set to the first entry's config_set so the ++ * notify handler has a valid pointer while USB init runs; it ++ * is re-resolved once the firmware version has been read. ++ */ ++ private->config_set = entry->info->config_sets[0].config_set; ++ + private->series_name = entry->series_name; +- scarlett2_count_io(private); + private->scarlett2_seq = 0; + private->mixer = mixer; + +@@ -9046,6 +9138,13 @@ static int snd_scarlett2_controls_create + if (err < 0) + return err; + ++ /* Now that the firmware version is known, pick the matching ++ * config_set ++ */ ++ scarlett2_resolve_config_set(private); ++ ++ scarlett2_count_io(private); ++ + /* Get the upgrade & settings flash segment numbers */ + err = scarlett2_get_flash_segment_nums(mixer); + if (err < 0) diff --git a/queue-6.12/alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch b/queue-6.12/alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch new file mode 100644 index 0000000000..63d4ac6948 --- /dev/null +++ b/queue-6.12/alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch @@ -0,0 +1,103 @@ +From stable+bounces-274089-greg=kroah.com@vger.kernel.org Tue Jul 14 05:06:12 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 23:04:38 -0400 +Subject: ALSA: scarlett2: Update offsets for 2i2 Gen 4 firmware 2417 +To: stable@vger.kernel.org +Cc: "Geoffrey D. Bennett" , Takashi Iwai , Sasha Levin +Message-ID: <20260714030438.2382402-2-sashal@kernel.org> + +From: "Geoffrey D. Bennett" + +[ Upstream commit 3ca15754b561483aa7a1bce51677d6389f8ff5bb ] + +Firmware 2417 for the Scarlett 4th Gen 2i2 moved the direct monitor gain +parameters, so add a second config_set with the shifted offset and +select it for firmware versions >= 2417. + +Fixes: 4e809a299677 ("ALSA: scarlett2: Add support for Solo, 2i2, and 4i4 Gen 4") +Cc: stable@vger.kernel.org # ALSA: scarlett2: Allow selecting config_set by firmware version +Cc: stable@vger.kernel.org # ALSA: scarlett2: Fold min_firmware_version into config_sets +Cc: stable@vger.kernel.org +Signed-off-by: Geoffrey D. Bennett +Signed-off-by: Takashi Iwai +Link: https://patch.msgid.link/ad0fc5a131e76eb656a24e0e198382f7134068fe.1777151532.git.g@b4.vu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/usb/mixer_scarlett2.c | 58 ++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 58 insertions(+) + +--- a/sound/usb/mixer_scarlett2.c ++++ b/sound/usb/mixer_scarlett2.c +@@ -910,6 +910,63 @@ static const struct scarlett2_config_set + } + }; + ++/* 2i2 Gen 4, firmware version 2417 and above ++ * ++ * Firmware 2417 shifted DIRECT_MONITOR_GAIN by 4 bytes; all other ++ * offsets are unchanged from scarlett2_config_set_gen4_2i2. ++ */ ++static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2_2417 = { ++ .notifications = scarlett4_2i2_notifications, ++ .param_buf_addr = 0xfc, ++ .input_gain_tlv = db_scale_gen4_gain, ++ .autogain_status_texts = scarlett2_autogain_status_gen4, ++ .items = { ++ [SCARLETT2_CONFIG_MSD_SWITCH] = { ++ .offset = 0x49, .size = 8, .activate = 4 }, ++ ++ [SCARLETT2_CONFIG_DIRECT_MONITOR] = { ++ .offset = 0x14a, .size = 8, .activate = 16, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { ++ .offset = 0x135, .size = 8, .activate = 10, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { ++ .offset = 0x137, .size = 8 }, ++ ++ [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { ++ .offset = 0x131, .size = 8, .activate = 29, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { ++ .offset = 0x132, .size = 8, .activate = 30, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { ++ .offset = 0x48, .size = 8, .activate = 11, .pbuf = 1, ++ .mute = 1 }, ++ ++ [SCARLETT2_CONFIG_INPUT_GAIN] = { ++ .offset = 0x4b, .size = 8, .activate = 12, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_LEVEL_SWITCH] = { ++ .offset = 0x3c, .size = 8, .activate = 13, .pbuf = 1, ++ .mute = 1 }, ++ ++ [SCARLETT2_CONFIG_SAFE_SWITCH] = { ++ .offset = 0x147, .size = 8, .activate = 14, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_AIR_SWITCH] = { ++ .offset = 0x3e, .size = 8, .activate = 15, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { ++ .offset = 0x14b, .size = 8, .activate = 17, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { ++ .offset = 0x14e, .size = 8, .activate = 18, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { ++ .offset = 0x2a4, .size = 16, .activate = 36 } ++ } ++}; ++ + /* 4i4 Gen 4 */ + static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4 = { + .notifications = scarlett4_4i4_notifications, +@@ -1960,6 +2017,7 @@ static const struct scarlett2_device_inf + static const struct scarlett2_device_info s2i2_gen4_info = { + .config_sets = (const struct scarlett2_config_set_entry[]) { + { 2115, &scarlett2_config_set_gen4_2i2 }, ++ { 2417, &scarlett2_config_set_gen4_2i2_2417 }, + { } + }, + .min_firmware_version = 2115, diff --git a/queue-6.12/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch b/queue-6.12/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch new file mode 100644 index 0000000000..ed185d692e --- /dev/null +++ b/queue-6.12/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch @@ -0,0 +1,101 @@ +From stable+bounces-275049-greg=kroah.com@vger.kernel.org Wed Jul 15 23:22:21 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:21:01 -0400 +Subject: Bluetooth: 6lowpan: fix cyclic locking warning on netdev unregister +To: stable@vger.kernel.org +Cc: Pauli Virtanen , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715212102.274128-1-sashal@kernel.org> + +From: Pauli Virtanen + +[ Upstream commit 9707a015fe8f3ba8ec7c270f3b2b8efb38823d6b ] + +6lowpan.c has theoretically conflicting lock orderings, which lockdep +complains about: + + a) rtnl_lock > hdev->workqueue + + from 6lowpan.c:delete_netdev -> rtnl_lock -> device_del + -> put_device(parent) -> hci_release_dev -> destroy_workqueue + + b) hdev->workqueue > l2cap_conn->lock > chan->lock > rtnl_lock + + from hci_rx_work -> 6lowpan.c:chan_ready_cb + -> lowpan_register_netdev, ifup -> rtnl_lock + +Actual deadlock appears not possible, as hci_rx_work is disabled and +l2cap_conn flushed already on hdev unregister. Hence, do minimal thing +to make lockdep happy by breaking chain a) by holding hdev refcount +until after netdev put in 6lowpan.c. + +Fixes the lockdep complaint: +WARNING: possible circular locking dependency detected. +kworker/0:1/11 is trying to acquire lock: +ffff8880023b3940 ((wq_completion)hci0#2){+.+.}-{0:0}, at: touch_wq_lockdep_map+0x8b/0x130 +but task is already holding lock: +ffffffff95e4f9c0 (rtnl_mutex){+.+.}-{4:4}, at: lowpan_unregister_netdev+0xd/0x30 +Workqueue: events delete_netdev + +Signed-off-by: Pauli Virtanen +Signed-off-by: Luiz Augusto von Dentz +Stable-dep-of: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/bluetooth/6lowpan.c | 25 +++++++++++++++++++++++-- + 1 file changed, 23 insertions(+), 2 deletions(-) + +--- a/net/bluetooth/6lowpan.c ++++ b/net/bluetooth/6lowpan.c +@@ -759,13 +759,33 @@ static inline struct l2cap_chan *chan_ne + return chan; + } + ++static void unregister_dev(struct lowpan_btle_dev *dev) ++{ ++ struct hci_dev *hdev = READ_ONCE(dev->hdev); ++ ++ /* If netdev holds last reference to hci_dev (its parent device), this ++ * leads to theoretical cyclic locking on lowpan_unregister_netdev: ++ * ++ * rtnl_lock -> put_device(parent) -> hci_release_dev -> ++ * destroy_workqueue -> hci_rx_work -> l2cap_recv_acldata -> ++ * chan_ready_cb -> ifup -> rtnl_lock ++ * ++ * However, hci_rx_work is disabled in hci_unregister_dev, so this ++ * should not occur. Make lockdep happy by postponing hdev release after ++ * netdev put. ++ */ ++ hci_dev_hold(hdev); ++ lowpan_unregister_netdev(dev->netdev); ++ hci_dev_put(hdev); ++} ++ + static void delete_netdev(struct work_struct *work) + { + struct lowpan_btle_dev *entry = container_of(work, + struct lowpan_btle_dev, + delete_netdev); + +- lowpan_unregister_netdev(entry->netdev); ++ unregister_dev(entry); + + /* The entry pointer is deleted by the netdev destructor. */ + } +@@ -1254,6 +1274,7 @@ static void disconnect_devices(void) + break; + + new_dev->netdev = entry->netdev; ++ new_dev->hdev = entry->hdev; + INIT_LIST_HEAD(&new_dev->list); + + list_add_rcu(&new_dev->list, &devices); +@@ -1265,7 +1286,7 @@ static void disconnect_devices(void) + ifdown(entry->netdev); + BT_DBG("Unregistering netdev %s %p", + entry->netdev->name, entry->netdev); +- lowpan_unregister_netdev(entry->netdev); ++ unregister_dev(entry); + kfree(entry); + } + } diff --git a/queue-6.12/bluetooth-hci_conn-fix-null-ptr-deref-in-hci_abort_conn.patch b/queue-6.12/bluetooth-hci_conn-fix-null-ptr-deref-in-hci_abort_conn.patch new file mode 100644 index 0000000000..f0950deab6 --- /dev/null +++ b/queue-6.12/bluetooth-hci_conn-fix-null-ptr-deref-in-hci_abort_conn.patch @@ -0,0 +1,283 @@ +From stable+bounces-275047-greg=kroah.com@vger.kernel.org Wed Jul 15 23:14:09 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:13:55 -0400 +Subject: Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn() +To: stable@vger.kernel.org +Cc: Siwei Zhang , XIAO WU , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715211355.253617-3-sashal@kernel.org> + +From: Siwei Zhang + +[ Upstream commit 12917f591cea1af36087dba5b9ec888652f0b42a ] + +hci_abort_conn() read hci_skb_event(hdev->sent_cmd) when a connection +was pending, but hdev->sent_cmd can be NULL while req_status is still +HCI_REQ_PEND, leading to a NULL pointer dereference and a general +protection fault from the hci_rx_work() receive path. + +Instead of inspecting hdev->sent_cmd, track the in-flight create +connection command with a new per-connection HCI_CONN_CREATE flag and +route all cancellation through hci_cancel_connect_sync(), which +dispatches to a dedicated per-type cancel function. The create command +is in exactly one of two states: still queued, or in flight. The cancel +function holds cmd_sync_work_lock across the whole decision: the worker +takes this lock to dequeue every entry, so while it is held a queued +command cannot start running and an in-flight command cannot complete +and let the next command become pending. This keeps the flag test and +hci_cmd_sync_cancel() atomic with respect to the worker, so a queued +command is simply dequeued, and an in-flight command owned by this +connection is cancelled without the risk of cancelling an unrelated +command that became pending in the meantime. CIS uses the same flag +mechanism via HCI_CONN_CREATE_CIS but cannot be dequeued per-connection. + +hci_acl_create_conn_sync() and hci_le_create_conn_sync() clear +HCI_CONN_CREATE after the create command completes, but the command +status handler can free conn via hci_conn_del() (for example when the +controller rejects the connection) while the worker is still blocked on +the connection complete event. Hold a reference on conn across the +create command so the flag can be cleared without a use-after-free. + +Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections") +Cc: stable@vger.kernel.org +Suggested-by: XIAO WU +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Siwei Zhang +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/hci_core.h | 1 + net/bluetooth/hci_conn.c | 21 ------ + net/bluetooth/hci_sync.c | 133 ++++++++++++++++++++++++++++++++++----- + 3 files changed, 123 insertions(+), 32 deletions(-) + +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -958,6 +958,7 @@ enum { + HCI_CONN_AUTH_FAILURE, + HCI_CONN_PER_ADV, + HCI_CONN_BIG_CREATED, ++ HCI_CONN_CREATE, + HCI_CONN_CREATE_CIS, + HCI_CONN_CREATE_BIG_SYNC, + HCI_CONN_BIG_SYNC, +--- a/net/bluetooth/hci_conn.c ++++ b/net/bluetooth/hci_conn.c +@@ -2921,26 +2921,11 @@ int hci_abort_conn(struct hci_conn *conn + + conn->abort_reason = reason; + +- /* If the connection is pending check the command opcode since that +- * might be blocking on hci_cmd_sync_work while waiting its respective +- * event so we need to hci_cmd_sync_cancel to cancel it. +- * +- * hci_connect_le serializes the connection attempts so only one +- * connection can be in BT_CONNECT at time. ++ /* Cancel the connect attempt. A return of 0 means the create command ++ * was still queued and got dequeued, so there is nothing to disconnect. + */ +- if (conn->state == BT_CONNECT && READ_ONCE(hdev->req_status) == HCI_REQ_PEND) { +- switch (hci_skb_event(hdev->sent_cmd)) { +- case HCI_EV_CONN_COMPLETE: +- case HCI_EV_LE_CONN_COMPLETE: +- case HCI_EV_LE_ENHANCED_CONN_COMPLETE: +- case HCI_EVT_LE_CIS_ESTABLISHED: +- hci_cmd_sync_cancel(hdev, ECANCELED); +- break; +- } +- /* Cancel connect attempt if still queued/pending */ +- } else if (!hci_cancel_connect_sync(hdev, conn)) { ++ if (!hci_cancel_connect_sync(hdev, conn)) + return 0; +- } + + /* Run immediately if on cmd_sync_work since this may be called + * as a result to MGMT_OP_DISCONNECT/MGMT_OP_UNPAIR which does +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -6568,6 +6568,11 @@ static int hci_le_create_conn_sync(struc + + 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; + +@@ -6580,6 +6585,7 @@ static int hci_le_create_conn_sync(struc + hdev->le_scan_type == LE_SCAN_ACTIVE && + !hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) { + hci_conn_del(conn); ++ hci_conn_put(conn); + return -EBUSY; + } + +@@ -6625,6 +6631,12 @@ static int hci_le_create_conn_sync(struc + &own_addr_type); + if (err) + goto done; ++ ++ /* Mark create connection in flight so hci_cancel_connect_sync() can ++ * cancel it while blocking on the connection complete event. ++ */ ++ set_bit(HCI_CONN_CREATE, &conn->flags); ++ + /* Send command LE Extended Create Connection if supported */ + if (use_ext_conn(hdev)) { + err = hci_le_ext_create_conn_sync(hdev, conn, own_addr_type); +@@ -6660,11 +6672,14 @@ static int hci_le_create_conn_sync(struc + conn->conn_timeout, NULL); + + done: ++ clear_bit(HCI_CONN_CREATE, &conn->flags); ++ + if (err == -ETIMEDOUT) + hci_le_connect_cancel_sync(hdev, conn, 0x00); + + /* Re-enable advertising after the connection attempt is finished. */ + hci_resume_advertising_sync(hdev); ++ hci_conn_put(conn); + return err; + } + +@@ -6939,10 +6954,25 @@ static int hci_acl_create_conn_sync(stru + else + cp.role_switch = 0x00; + +- return __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN, +- sizeof(cp), &cp, +- HCI_EV_CONN_COMPLETE, +- conn->conn_timeout, NULL); ++ /* 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. ++ */ ++ set_bit(HCI_CONN_CREATE, &conn->flags); ++ ++ err = __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN, ++ sizeof(cp), &cp, ++ HCI_EV_CONN_COMPLETE, ++ conn->conn_timeout, NULL); ++ ++ clear_bit(HCI_CONN_CREATE, &conn->flags); ++ hci_conn_put(conn); ++ ++ return err; + } + + int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn) +@@ -6988,22 +7018,97 @@ int hci_connect_le_sync(struct hci_dev * + create_le_conn_complete); + } + +-int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn) ++static int hci_acl_cancel_create_conn_sync(struct hci_dev *hdev, ++ struct hci_conn *conn) ++{ ++ struct hci_cmd_sync_work_entry *entry; ++ int err = -EBUSY; ++ ++ /* cmd_sync_work_lock makes the HCI_CONN_CREATE test and the cancel ++ * atomic against the worker, which takes this lock to dequeue every ++ * entry: while it is held no other command can become pending, so ++ * hci_cmd_sync_cancel() cannot cancel an unrelated command. ++ */ ++ mutex_lock(&hdev->cmd_sync_work_lock); ++ ++ /* In flight: this connection owns the pending request, cancel it. */ ++ if (test_bit(HCI_CONN_CREATE, &conn->flags)) { ++ hci_cmd_sync_cancel(hdev, ECANCELED); ++ goto unlock; ++ } ++ ++ /* Still queued: a successful dequeue means it never started, so there ++ * is nothing to disconnect. ++ */ ++ entry = _hci_cmd_sync_lookup_entry(hdev, hci_acl_create_conn_sync, conn, ++ NULL); ++ if (entry) { ++ _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED); ++ err = 0; ++ } ++ ++unlock: ++ mutex_unlock(&hdev->cmd_sync_work_lock); ++ return err; ++} ++ ++static int hci_le_cancel_create_conn_sync(struct hci_dev *hdev, ++ struct hci_conn *conn) + { +- if (conn->state != BT_OPEN) +- return -EINVAL; ++ struct hci_cmd_sync_work_entry *entry; ++ int err = -EBUSY; ++ ++ /* cmd_sync_work_lock keeps the HCI_CONN_CREATE test and the cancel ++ * atomic against the cmd_sync worker. ++ */ ++ mutex_lock(&hdev->cmd_sync_work_lock); ++ ++ if (test_bit(HCI_CONN_CREATE, &conn->flags)) { ++ hci_cmd_sync_cancel(hdev, ECANCELED); ++ goto unlock; ++ } + ++ entry = _hci_cmd_sync_lookup_entry(hdev, hci_le_create_conn_sync, conn, ++ create_le_conn_complete); ++ if (entry) { ++ _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED); ++ err = 0; ++ } ++ ++unlock: ++ mutex_unlock(&hdev->cmd_sync_work_lock); ++ return err; ++} ++ ++static int hci_cis_cancel_create_conn_sync(struct hci_dev *hdev, ++ struct hci_conn *conn) ++{ ++ /* LE Create CIS is shared by the whole CIG and cannot be dequeued ++ * per-connection, so only an in-flight command can be cancelled. ++ * cmd_sync_work_lock keeps the test and the cancel atomic against the ++ * cmd_sync worker. ++ */ ++ mutex_lock(&hdev->cmd_sync_work_lock); ++ ++ if (test_bit(HCI_CONN_CREATE_CIS, &conn->flags)) ++ hci_cmd_sync_cancel(hdev, ECANCELED); ++ ++ mutex_unlock(&hdev->cmd_sync_work_lock); ++ return -EBUSY; ++} ++ ++int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn) ++{ + switch (conn->type) { + case ACL_LINK: +- return !hci_cmd_sync_dequeue_once(hdev, +- hci_acl_create_conn_sync, +- conn, NULL); ++ return hci_acl_cancel_create_conn_sync(hdev, conn); + case LE_LINK: +- return !hci_cmd_sync_dequeue_once(hdev, hci_le_create_conn_sync, +- conn, create_le_conn_complete); ++ return hci_le_cancel_create_conn_sync(hdev, conn); ++ case CIS_LINK: ++ return hci_cis_cancel_create_conn_sync(hdev, conn); ++ default: ++ return -ENOENT; + } +- +- return -ENOENT; + } + + int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn, diff --git a/queue-6.12/bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch b/queue-6.12/bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch new file mode 100644 index 0000000000..3e3bd04721 --- /dev/null +++ b/queue-6.12/bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch @@ -0,0 +1,271 @@ +From stable+bounces-275045-greg=kroah.com@vger.kernel.org Wed Jul 15 23:14:07 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:13:53 -0400 +Subject: Bluetooth: hci_core: Enable buffer flow control for SCO/eSCO +To: stable@vger.kernel.org +Cc: Luiz Augusto von Dentz , Pauli Virtanen , Sasha Levin +Message-ID: <20260715211355.253617-1-sashal@kernel.org> + +From: Luiz Augusto von Dentz + +[ Upstream commit 13218453521d75916dfed55efb8e809bfc03cb4b ] + +This enables buffer flow control for SCO/eSCO +(see: Bluetooth Core 6.0 spec: 6.22. Synchronous Flow Control Enable), +recently this has caused the following problem and is actually a nice +addition for the likes of Socket TX complete: + +< HCI Command: Read Buffer Size (0x04|0x0005) plen 0 +> HCI Event: Command Complete (0x0e) plen 11 + Read Buffer Size (0x04|0x0005) ncmd 1 + Status: Success (0x00) + ACL MTU: 1021 ACL max packet: 5 + SCO MTU: 240 SCO max packet: 8 +... +< SCO Data TX: Handle 257 flags 0x00 dlen 120 +< SCO Data TX: Handle 257 flags 0x00 dlen 120 +< SCO Data TX: Handle 257 flags 0x00 dlen 120 +< SCO Data TX: Handle 257 flags 0x00 dlen 120 +< SCO Data TX: Handle 257 flags 0x00 dlen 120 +< SCO Data TX: Handle 257 flags 0x00 dlen 120 +< SCO Data TX: Handle 257 flags 0x00 dlen 120 +< SCO Data TX: Handle 257 flags 0x00 dlen 120 +< SCO Data TX: Handle 257 flags 0x00 dlen 120 +> HCI Event: Hardware Error (0x10) plen 1 + Code: 0x0a + +To fix the code will now attempt to enable buffer flow control when +HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED is set by the driver: + +< HCI Command: Write Sync Fl.. (0x03|0x002f) plen 1 + Flow control: Enabled (0x01) +> HCI Event: Command Complete (0x0e) plen 4 + Write Sync Flow Control Enable (0x03|0x002f) ncmd 1 + Status: Success (0x00) + +On success then HCI_SCO_FLOWCTL would be set which indicates sco_cnt +shall be used for flow contro. + +Fixes: 7fedd3bb6b77 ("Bluetooth: Prioritize SCO traffic") +Signed-off-by: Luiz Augusto von Dentz +Tested-by: Pauli Virtanen +Stable-dep-of: 12917f591cea ("Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/hci.h | 13 ++++++++ + include/net/bluetooth/hci_core.h | 1 + net/bluetooth/hci_core.c | 62 +++++++++++++++++---------------------- + net/bluetooth/hci_event.c | 2 + + net/bluetooth/hci_sync.c | 24 +++++++++++++++ + 5 files changed, 68 insertions(+), 34 deletions(-) + +--- a/include/net/bluetooth/hci.h ++++ b/include/net/bluetooth/hci.h +@@ -207,6 +207,13 @@ enum { + */ + HCI_QUIRK_WIDEBAND_SPEECH_SUPPORTED, + ++ /* When this quirk is set consider Sync Flow Control as supported by ++ * the driver. ++ * ++ * This quirk must be set before hci_register_dev is called. ++ */ ++ HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED, ++ + /* When this quirk is set, the LE states reported through the + * HCI_LE_READ_SUPPORTED_STATES are invalid/broken. + * +@@ -448,6 +455,7 @@ enum { + HCI_WIDEBAND_SPEECH_ENABLED, + HCI_EVENT_FILTER_CONFIGURED, + HCI_PA_SYNC, ++ HCI_SCO_FLOWCTL, + + HCI_DUT_MODE, + HCI_VENDOR_DIAG, +@@ -1545,6 +1553,11 @@ struct hci_rp_read_tx_power { + __s8 tx_power; + } __packed; + ++#define HCI_OP_WRITE_SYNC_FLOWCTL 0x0c2f ++struct hci_cp_write_sync_flowctl { ++ __u8 enable; ++} __packed; ++ + #define HCI_OP_READ_PAGE_SCAN_TYPE 0x0c46 + struct hci_rp_read_page_scan_type { + __u8 status; +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -1891,6 +1891,7 @@ void hci_conn_del_sysfs(struct hci_conn + #define lmp_hold_capable(dev) ((dev)->features[0][0] & LMP_HOLD) + #define lmp_sniff_capable(dev) ((dev)->features[0][0] & LMP_SNIFF) + #define lmp_park_capable(dev) ((dev)->features[0][1] & LMP_PARK) ++#define lmp_sco_capable(dev) ((dev)->features[0][1] & LMP_SCO) + #define lmp_inq_rssi_capable(dev) ((dev)->features[0][3] & LMP_RSSI_INQ) + #define lmp_esco_capable(dev) ((dev)->features[0][3] & LMP_ESCO) + #define lmp_bredr_capable(dev) (!((dev)->features[0][4] & LMP_NO_BREDR)) +--- a/net/bluetooth/hci_core.c ++++ b/net/bluetooth/hci_core.c +@@ -3608,42 +3608,27 @@ static void __check_timeout(struct hci_d + } + + /* Schedule SCO */ +-static void hci_sched_sco(struct hci_dev *hdev) ++static void hci_sched_sco(struct hci_dev *hdev, __u8 type) + { + struct hci_conn *conn; + struct sk_buff *skb; +- int quote; ++ int quote, *cnt; ++ unsigned int pkts = hdev->sco_pkts; + +- BT_DBG("%s", hdev->name); ++ bt_dev_dbg(hdev, "type %u", type); + +- if (!hci_conn_num(hdev, SCO_LINK)) ++ if (!hci_conn_num(hdev, type) || !pkts) + return; + +- while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, "e))) { +- while (quote-- && (skb = skb_dequeue(&conn->data_q))) { +- BT_DBG("skb %p len %d", skb, skb->len); +- hci_send_frame(hdev, skb); +- +- conn->sent++; +- if (conn->sent == ~0) +- conn->sent = 0; +- } +- } +-} +- +-static void hci_sched_esco(struct hci_dev *hdev) +-{ +- struct hci_conn *conn; +- struct sk_buff *skb; +- int quote; +- +- BT_DBG("%s", hdev->name); +- +- if (!hci_conn_num(hdev, ESCO_LINK)) +- return; ++ /* Use sco_pkts if flow control has not been enabled which will limit ++ * the amount of buffer sent in a row. ++ */ ++ if (!hci_dev_test_flag(hdev, HCI_SCO_FLOWCTL)) ++ cnt = &pkts; ++ else ++ cnt = &hdev->sco_cnt; + +- while (hdev->sco_cnt && (conn = hci_low_sent(hdev, ESCO_LINK, +- "e))) { ++ while (*cnt && (conn = hci_low_sent(hdev, type, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); + hci_send_frame(hdev, skb); +@@ -3651,8 +3636,17 @@ static void hci_sched_esco(struct hci_de + conn->sent++; + if (conn->sent == ~0) + conn->sent = 0; ++ (*cnt)--; + } + } ++ ++ /* Rescheduled if all packets were sent and flow control is not enabled ++ * as there could be more packets queued that could not be sent and ++ * since no HCI_EV_NUM_COMP_PKTS event will be generated the reschedule ++ * needs to be forced. ++ */ ++ if (!pkts && !hci_dev_test_flag(hdev, HCI_SCO_FLOWCTL)) ++ queue_work(hdev->workqueue, &hdev->tx_work); + } + + static void hci_sched_acl_pkt(struct hci_dev *hdev) +@@ -3688,8 +3682,8 @@ static void hci_sched_acl_pkt(struct hci + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev); +- hci_sched_esco(hdev); ++ hci_sched_sco(hdev, SCO_LINK); ++ hci_sched_sco(hdev, ESCO_LINK); + } + } + +@@ -3744,8 +3738,8 @@ static void hci_sched_le(struct hci_dev + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev); +- hci_sched_esco(hdev); ++ hci_sched_sco(hdev, SCO_LINK); ++ hci_sched_sco(hdev, ESCO_LINK); + } + } + +@@ -3790,8 +3784,8 @@ static void hci_tx_work(struct work_stru + + if (!hci_dev_test_flag(hdev, HCI_USER_CHANNEL)) { + /* Schedule queues and send stuff to HCI driver */ +- hci_sched_sco(hdev); +- hci_sched_esco(hdev); ++ hci_sched_sco(hdev, SCO_LINK); ++ hci_sched_sco(hdev, ESCO_LINK); + hci_sched_iso(hdev); + hci_sched_acl(hdev); + hci_sched_le(hdev); +--- a/net/bluetooth/hci_event.c ++++ b/net/bluetooth/hci_event.c +@@ -4446,9 +4446,11 @@ static void hci_num_comp_pkts_evt(struct + break; + + case SCO_LINK: ++ case ESCO_LINK: + hdev->sco_cnt += count; + if (hdev->sco_cnt > hdev->sco_pkts) + hdev->sco_cnt = hdev->sco_pkts; ++ + break; + + case ISO_LINK: +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -3888,6 +3888,28 @@ static int hci_write_ca_timeout_sync(str + sizeof(param), ¶m, HCI_CMD_TIMEOUT); + } + ++/* Enable SCO flow control if supported */ ++static int hci_write_sync_flowctl_sync(struct hci_dev *hdev) ++{ ++ struct hci_cp_write_sync_flowctl cp; ++ int err; ++ ++ /* Check if the controller supports SCO and HCI_OP_WRITE_SYNC_FLOWCTL */ ++ if (!lmp_sco_capable(hdev) || !(hdev->commands[10] & BIT(4)) || ++ !test_bit(HCI_QUIRK_SYNC_FLOWCTL_SUPPORTED, &hdev->quirks)) ++ return 0; ++ ++ memset(&cp, 0, sizeof(cp)); ++ cp.enable = 0x01; ++ ++ err = __hci_cmd_sync_status(hdev, HCI_OP_WRITE_SYNC_FLOWCTL, ++ sizeof(cp), &cp, HCI_CMD_TIMEOUT); ++ if (!err) ++ hci_dev_set_flag(hdev, HCI_SCO_FLOWCTL); ++ ++ return err; ++} ++ + /* BR Controller init stage 2 command sequence */ + static const struct hci_init_stage br_init2[] = { + /* HCI_OP_READ_BUFFER_SIZE */ +@@ -3906,6 +3928,8 @@ static const struct hci_init_stage br_in + HCI_INIT(hci_clear_event_filter_sync), + /* HCI_OP_WRITE_CA_TIMEOUT */ + HCI_INIT(hci_write_ca_timeout_sync), ++ /* HCI_OP_WRITE_SYNC_FLOWCTL */ ++ HCI_INIT(hci_write_sync_flowctl_sync), + {} + }; + diff --git a/queue-6.12/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch b/queue-6.12/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch new file mode 100644 index 0000000000..c6c85d6b47 --- /dev/null +++ b/queue-6.12/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch @@ -0,0 +1,69 @@ +From stable+bounces-275037-greg=kroah.com@vger.kernel.org Wed Jul 15 22:49:51 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 16:49:44 -0400 +Subject: Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock +To: stable@vger.kernel.org +Cc: Runyu Xiao , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715204944.205221-2-sashal@kernel.org> + +From: Runyu Xiao + +[ Upstream commit 2641a9e0a1dd4af2e21995470a21d55dd35e5203 ] + +l2cap_conn_del() takes conn->lock and then calls cancel_work_sync() for +pending_rx_work. process_pending_rx() takes the same mutex, so teardown +can deadlock against the worker it is flushing. + +This issue was found by our static analysis tool and then manually +reviewed against the current tree. + +The grounded PoC kept the l2cap_conn_ready() -> queue_work(..., +&conn->pending_rx_work) submit path, the l2cap_conn_del() -> +cancel_work_sync(&conn->pending_rx_work) teardown path, and the +process_pending_rx() -> mutex_lock(&conn->lock) worker edge. Lockdep +reported: + + WARNING: possible circular locking dependency detected + process_pending_rx+0x21/0x2a [vuln_msv] + l2cap_conn_del.constprop.0+0x3f/0x4e [vuln_msv] + *** DEADLOCK *** + +Cancel pending_rx_work before taking conn->lock, matching the existing +lock-before-drain ordering used for the two delayed works in the same +teardown path. The pending_rx queue is still purged after the work has +been cancelled and conn->lock has been acquired. + +Fixes: 7ab56c3a6ecc ("Bluetooth: Fix deadlock in l2cap_conn_del()") +Cc: stable@vger.kernel.org +Signed-off-by: Runyu Xiao +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/bluetooth/l2cap_core.c | 10 ++-------- + 1 file changed, 2 insertions(+), 8 deletions(-) + +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -1755,19 +1755,13 @@ static void l2cap_conn_del(struct hci_co + disable_delayed_work_sync(&conn->info_timer); + disable_delayed_work_sync(&conn->id_addr_timer); + ++ cancel_work_sync(&conn->pending_rx_work); ++ + mutex_lock(&conn->lock); + + kfree_skb(conn->rx_skb); + + skb_queue_purge(&conn->pending_rx); +- +- /* We can not call flush_work(&conn->pending_rx_work) here since we +- * might block if we are running on a worker from the same workqueue +- * pending_rx_work is waiting on. +- */ +- if (work_pending(&conn->pending_rx_work)) +- cancel_work_sync(&conn->pending_rx_work); +- + ida_destroy(&conn->tx_ida); + + l2cap_unregister_all_users(conn); diff --git a/queue-6.12/bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch b/queue-6.12/bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch new file mode 100644 index 0000000000..b7ba5c9f05 --- /dev/null +++ b/queue-6.12/bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch @@ -0,0 +1,154 @@ +From stable+bounces-275036-greg=kroah.com@vger.kernel.org Wed Jul 15 22:50:42 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 16:49:43 -0400 +Subject: Bluetooth: L2CAP: Fix not tracking outstanding TX ident +To: stable@vger.kernel.org +Cc: Luiz Augusto von Dentz , Paul Menzel , Sasha Levin +Message-ID: <20260715204944.205221-1-sashal@kernel.org> + +From: Luiz Augusto von Dentz + +[ Upstream commit 6c3ea155e5ee3e56606233acde8309afda66d483 ] + +This attempts to proper track outstanding request by using struct ida +and allocating from it in l2cap_get_ident using ida_alloc_range which +would reuse ids as they are free, then upon completion release +the id using ida_free. + +This fixes the qualification test case L2CAP/COS/CED/BI-29-C which +attempts to check if the host stack is able to work after 256 attempts +to connect which requires Ident field to use the full range of possible +values in order to pass the test. + +Link: https://github.com/bluez/bluez/issues/1829 +Signed-off-by: Luiz Augusto von Dentz +Reviewed-by: Paul Menzel +Stable-dep-of: 2641a9e0a1dd ("Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/l2cap.h | 3 -- + net/bluetooth/l2cap_core.c | 53 ++++++++++++++++++++++++++++++------------ + 2 files changed, 40 insertions(+), 16 deletions(-) + +--- a/include/net/bluetooth/l2cap.h ++++ b/include/net/bluetooth/l2cap.h +@@ -658,8 +658,7 @@ struct l2cap_conn { + + struct sk_buff *rx_skb; + __u32 rx_len; +- __u8 tx_ident; +- struct mutex ident_lock; ++ struct ida tx_ida; + + struct sk_buff_head pending_rx; + struct work_struct pending_rx_work; +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -928,26 +928,18 @@ int l2cap_chan_check_security(struct l2c + initiator); + } + +-static u8 l2cap_get_ident(struct l2cap_conn *conn) ++static int l2cap_get_ident(struct l2cap_conn *conn) + { +- u8 id; ++ /* LE link does not support tools like l2ping so use the full range */ ++ if (conn->hcon->type == LE_LINK) ++ return ida_alloc_range(&conn->tx_ida, 1, 255, GFP_ATOMIC); + + /* Get next available identificator. + * 1 - 128 are used by kernel. + * 129 - 199 are reserved. + * 200 - 254 are used by utilities like l2ping, etc. + */ +- +- mutex_lock(&conn->ident_lock); +- +- if (++conn->tx_ident > 128) +- conn->tx_ident = 1; +- +- id = conn->tx_ident; +- +- mutex_unlock(&conn->ident_lock); +- +- return id; ++ return ida_alloc_range(&conn->tx_ida, 1, 128, GFP_ATOMIC); + } + + static void l2cap_send_acl(struct l2cap_conn *conn, struct sk_buff *skb, +@@ -1776,6 +1768,8 @@ static void l2cap_conn_del(struct hci_co + if (work_pending(&conn->pending_rx_work)) + cancel_work_sync(&conn->pending_rx_work); + ++ ida_destroy(&conn->tx_ida); ++ + l2cap_unregister_all_users(conn); + + /* Force the connection to be immediately dropped */ +@@ -4776,12 +4770,41 @@ static int l2cap_le_connect_rsp(struct l + return err; + } + ++static void l2cap_put_ident(struct l2cap_conn *conn, u8 code, u8 id) ++{ ++ int ret; ++ ++ switch (code) { ++ case L2CAP_COMMAND_REJ: ++ case L2CAP_CONN_RSP: ++ case L2CAP_CONF_RSP: ++ case L2CAP_DISCONN_RSP: ++ case L2CAP_ECHO_RSP: ++ case L2CAP_INFO_RSP: ++ case L2CAP_CONN_PARAM_UPDATE_RSP: ++ case L2CAP_ECRED_CONN_RSP: ++ case L2CAP_ECRED_RECONF_RSP: ++ /* The remote may send bogus ids that would make ida_free ++ * generate warnings, so only free ids that are actually ++ * allocated: probing the exact id returns -ENOSPC when it ++ * is in use, otherwise the probe allocated it and freeing ++ * is safe either way. Only on -ENOMEM is the id known to ++ * be unallocated and the free must be skipped. ++ */ ++ ret = ida_alloc_range(&conn->tx_ida, id, id, GFP_ATOMIC); ++ if (ret >= 0 || ret == -ENOSPC) ++ ida_free(&conn->tx_ida, id); ++ } ++} ++ + static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u16 cmd_len, + u8 *data) + { + int err = 0; + ++ l2cap_put_ident(conn, cmd->code, cmd->ident); ++ + switch (cmd->code) { + case L2CAP_COMMAND_REJ: + l2cap_command_rej(conn, cmd, cmd_len, data); +@@ -5495,6 +5518,8 @@ static inline int l2cap_le_sig_cmd(struc + { + int err = 0; + ++ l2cap_put_ident(conn, cmd->code, cmd->ident); ++ + switch (cmd->code) { + case L2CAP_COMMAND_REJ: + l2cap_le_command_rej(conn, cmd, cmd_len, data); +@@ -7043,13 +7068,13 @@ static struct l2cap_conn *l2cap_conn_add + hci_dev_test_flag(hcon->hdev, HCI_FORCE_BREDR_SMP))) + conn->local_fixed_chan |= L2CAP_FC_SMP_BREDR; + +- mutex_init(&conn->ident_lock); + mutex_init(&conn->lock); + + INIT_LIST_HEAD(&conn->chan_l); + INIT_LIST_HEAD(&conn->users); + + INIT_DELAYED_WORK(&conn->info_timer, l2cap_info_timeout); ++ ida_init(&conn->tx_ida); + + skb_queue_head_init(&conn->pending_rx); + INIT_WORK(&conn->pending_rx_work, process_pending_rx); diff --git a/queue-6.12/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch b/queue-6.12/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch new file mode 100644 index 0000000000..4189097aa0 --- /dev/null +++ b/queue-6.12/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch @@ -0,0 +1,246 @@ +From stable+bounces-274676-greg=kroah.com@vger.kernel.org Wed Jul 15 04:46:17 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 22:45:35 -0400 +Subject: Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref +To: stable@vger.kernel.org +Cc: Marco Elver , Siwei Zhang , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715024535.105267-1-sashal@kernel.org> + +From: Marco Elver + +[ Upstream commit b66774b48dd98f07254951f74ea6f513efe7ff8b ] + +l2cap_chan_timeout() runs asynchronously and accesses chan->conn. If +the connection is torn down while the timer is running or pending, +chan->conn can be freed, leading to a use-after-free when the timer +worker attempts to lock conn->lock: + +| BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline] +| BUG: KASAN: slab-use-after-free in atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline] +| BUG: KASAN: slab-use-after-free in __mutex_trylock_fast kernel/locking/mutex.c:161 [inline] +| BUG: KASAN: slab-use-after-free in mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318 +| Write of size 8 at addr ffff8881298d9550 by task kworker/2:1/83 +| +| CPU: 2 UID: 0 PID: 83 Comm: kworker/2:1 Not tainted 7.1.0-rc6-next-20260601-dirty #6 PREEMPT(full) +| Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 +| Workqueue: events l2cap_chan_timeout +| Call Trace: +| +| instrument_atomic_read_write include/linux/instrumented.h:112 [inline] +| atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline] +| __mutex_trylock_fast kernel/locking/mutex.c:161 [inline] +| mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318 +| l2cap_chan_timeout+0x5d/0x1b0 net/bluetooth/l2cap_core.c:422 +| process_one_work kernel/workqueue.c:3326 [inline] +| process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409 +| worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490 +| kthread+0x346/0x430 kernel/kthread.c:436 +| ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158 +| ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 +| +| +| Allocated by task 320: +| l2cap_conn_add+0xa7/0x820 net/bluetooth/l2cap_core.c:7075 +| l2cap_connect_cfm+0xdb/0xd70 net/bluetooth/l2cap_core.c:7452 +| hci_connect_cfm include/net/bluetooth/hci_core.h:2139 [inline] +| hci_remote_features_evt+0x52f/0x9f0 net/bluetooth/hci_event.c:3760 +| hci_event_func net/bluetooth/hci_event.c:7796 [inline] +| hci_event_packet+0x561/0xa70 net/bluetooth/hci_event.c:7847 +| hci_rx_work+0x370/0x890 net/bluetooth/hci_core.c:4040 +| process_one_work kernel/workqueue.c:3326 [inline] +| process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409 +| worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490 +| kthread+0x346/0x430 kernel/kthread.c:436 +| ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158 +| ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 +| +| Freed by task 322: +| hci_disconn_cfm include/net/bluetooth/hci_core.h:2154 [inline] +| hci_conn_hash_flush+0x101/0x1f0 net/bluetooth/hci_conn.c:2736 +| hci_dev_close_sync+0x889/0xde0 net/bluetooth/hci_sync.c:5405 +| hci_dev_do_close net/bluetooth/hci_core.c:502 [inline] +| hci_unregister_dev+0x1f7/0x370 net/bluetooth/hci_core.c:2679 +| vhci_release+0x12a/0x180 drivers/bluetooth/hci_vhci.c:690 +| __fput+0x369/0x890 fs/file_table.c:510 +| task_work_run+0x160/0x1d0 kernel/task_work.c:233 +| get_signal+0xf5b/0x1120 kernel/signal.c:2810 +| arch_do_signal_or_restart+0x4d/0x600 arch/x86/kernel/signal.c:337 +| __exit_to_user_mode_loop kernel/entry/common.c:64 [inline] +| exit_to_user_mode_loop+0x85/0x510 kernel/entry/common.c:98 +| do_syscall_64+0x263/0x3d0 arch/x86/entry/syscall_64.c:100 +| entry_SYSCALL_64_after_hwframe+0x77/0x7f +| +| The buggy address belongs to the object at ffff8881298d9400 +| which belongs to the cache kmalloc-512 of size 512 +| The buggy address is located 336 bytes inside of +| freed 512-byte region [ffff8881298d9400, ffff8881298d9600) + +Fix it by having chan->conn hold a reference to l2cap_conn (via +l2cap_conn_get) when the channel is added to the connection, and +releasing it in the channel destructor. This ensures the l2cap_conn +remains alive as long as the channel exists. + +A new FLAG_DEL channel flag is introduced to indicate that the channel +has been deleted from its connection. l2cap_chan_del() atomically sets +this flag using test_and_set_bit() instead of setting chan->conn to +NULL. All asynchronous workers (l2cap_chan_timeout, l2cap_ack_timeout, +l2cap_monitor_timeout, l2cap_retrans_timeout) and l2cap_chan_send() +check FLAG_DEL to determine whether the channel has been torn down, +rather than testing chan->conn for NULL. + +Fixes: 8c8e620467a7 ("Bluetooth: L2CAP: use chan timer to close channels in cleanup_listen()") +Cc: +Cc: Siwei Zhang +Cc: Luiz Augusto von Dentz +Assisted-by: Gemini:gemini-3.1-pro-preview +Reported-by: https://sashiko.dev/#/patchset/20260521021249.3258069-1-oss%40fourdim.xyz +Signed-off-by: Marco Elver +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/l2cap.h | 1 + + net/bluetooth/l2cap_core.c | 34 ++++++++++++++++++++-------------- + 2 files changed, 21 insertions(+), 14 deletions(-) + +--- a/include/net/bluetooth/l2cap.h ++++ b/include/net/bluetooth/l2cap.h +@@ -748,6 +748,7 @@ enum { + FLAG_ECRED_CONN_REQ_SENT, + FLAG_PENDING_SECURITY, + FLAG_HOLD_HCI_CONN, ++ FLAG_DEL, + }; + + /* Lock nesting levels for L2CAP channels. We need these because lockdep +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -411,7 +411,7 @@ static void l2cap_chan_timeout(struct wo + + BT_DBG("chan %p state %s", chan, state_to_string(chan->state)); + +- if (!conn) { ++ if (test_bit(FLAG_DEL, &chan->flags)) { + l2cap_chan_put(chan); + return; + } +@@ -422,6 +422,9 @@ static void l2cap_chan_timeout(struct wo + */ + l2cap_chan_lock(chan); + ++ if (test_bit(FLAG_DEL, &chan->flags)) ++ goto unlock; ++ + if (chan->state == BT_CONNECTED || chan->state == BT_CONFIG) + reason = ECONNREFUSED; + else if (chan->state == BT_CONNECT && +@@ -434,10 +437,10 @@ static void l2cap_chan_timeout(struct wo + + chan->ops->close(chan); + ++unlock: + l2cap_chan_unlock(chan); +- l2cap_chan_put(chan); +- + mutex_unlock(&conn->lock); ++ l2cap_chan_put(chan); + } + + struct l2cap_chan *l2cap_chan_create(void) +@@ -490,6 +493,9 @@ static void l2cap_chan_destroy(struct kr + list_del(&chan->global_l); + write_unlock(&chan_list_lock); + ++ if (chan->conn) ++ l2cap_conn_put(chan->conn); ++ + kfree(chan); + } + +@@ -593,7 +599,7 @@ void __l2cap_chan_add(struct l2cap_conn + + conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM; + +- chan->conn = conn; ++ chan->conn = l2cap_conn_get(conn); + + switch (chan->chan_type) { + case L2CAP_CHAN_CONN_ORIENTED: +@@ -648,30 +654,26 @@ void l2cap_chan_add(struct l2cap_conn *c + + void l2cap_chan_del(struct l2cap_chan *chan, int err) + { +- struct l2cap_conn *conn = chan->conn; +- + __clear_chan_timer(chan); + +- BT_DBG("chan %p, conn %p, err %d, state %s", chan, conn, err, ++ BT_DBG("chan %p, err %d, state %s", chan, err, + state_to_string(chan->state)); + + chan->ops->teardown(chan, err); + +- if (conn) { ++ if (!test_and_set_bit(FLAG_DEL, &chan->flags)) { + /* Delete from channel list */ + list_del(&chan->list); + + l2cap_chan_put(chan); + +- chan->conn = NULL; +- + /* Reference was only held for non-fixed channels or + * fixed channels that explicitly requested it using the + * FLAG_HOLD_HCI_CONN flag. + */ + if (chan->chan_type != L2CAP_CHAN_FIXED || + test_bit(FLAG_HOLD_HCI_CONN, &chan->flags)) +- hci_conn_drop(conn->hcon); ++ hci_conn_drop(chan->conn->hcon); + } + + if (test_bit(CONF_NOT_COMPLETE, &chan->conf_state)) +@@ -1886,7 +1888,7 @@ static void l2cap_monitor_timeout(struct + + l2cap_chan_lock(chan); + +- if (!chan->conn) { ++ if (test_bit(FLAG_DEL, &chan->flags)) { + l2cap_chan_unlock(chan); + l2cap_chan_put(chan); + return; +@@ -1907,7 +1909,7 @@ static void l2cap_retrans_timeout(struct + + l2cap_chan_lock(chan); + +- if (!chan->conn) { ++ if (test_bit(FLAG_DEL, &chan->flags)) { + l2cap_chan_unlock(chan); + l2cap_chan_put(chan); + return; +@@ -2522,7 +2524,7 @@ int l2cap_chan_send(struct l2cap_chan *c + int err; + struct sk_buff_head seg_queue; + +- if (!chan->conn) ++ if (test_bit(FLAG_DEL, &chan->flags)) + return -ENOTCONN; + + /* Connectionless channel */ +@@ -3119,12 +3121,16 @@ static void l2cap_ack_timeout(struct wor + + l2cap_chan_lock(chan); + ++ if (test_bit(FLAG_DEL, &chan->flags)) ++ goto unlock; ++ + frames_to_ack = __seq_offset(chan, chan->buffer_seq, + chan->last_acked_seq); + + if (frames_to_ack) + l2cap_send_rr_or_rnr(chan, 0); + ++unlock: + l2cap_chan_unlock(chan); + l2cap_chan_put(chan); + } diff --git a/queue-6.12/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch b/queue-6.12/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch new file mode 100644 index 0000000000..e9e0369b1c --- /dev/null +++ b/queue-6.12/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch @@ -0,0 +1,542 @@ +From stable+bounces-275050-greg=kroah.com@vger.kernel.org Wed Jul 15 23:25:05 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:21:02 -0400 +Subject: Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb() +To: stable@vger.kernel.org +Cc: Siwei Zhang , stable@kernel.org, Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715212102.274128-2-sashal@kernel.org> + +From: Siwei Zhang + +[ Upstream commit 6fef032af0092ed5ccb767239a9ac1bc38c08a40 ] + +l2cap_sock_new_connection_cb() returned l2cap_pi(sk)->chan after +release_sock(parent). Once the parent lock is dropped the newly +enqueued child socket sk is reachable via the accept queue, so another +task can accept and free it before the callback dereferences sk, +resulting in a use-after-free. + +Rework the ->new_connection() op so the core, rather than the callback, +owns the child channel's lifetime. The op now receives a pre-allocated +new_chan and returns an errno instead of allocating and returning a +channel. l2cap_new_connection() allocates the child channel and links +it into the conn list via __l2cap_chan_add() before invoking the +callback, so the conn-list reference keeps the channel alive once +release_sock(parent) exposes the socket to other tasks. + +Channel configuration that was duplicated in l2cap_sock_init() and the +various new_connection callbacks is consolidated into +l2cap_chan_set_defaults(), which now inherits from the parent channel +when one is supplied. + +Fixes: 8ffb929098a5 ("Bluetooth: Remove parent socket usage from l2cap_core.c") +Cc: stable@kernel.org +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Siwei Zhang +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/l2cap.h | 10 ++-- + net/bluetooth/6lowpan.c | 18 ------- + net/bluetooth/l2cap_core.c | 78 ++++++++++++++++++++++++++----- + net/bluetooth/l2cap_sock.c | 103 +++++++++++++++++++----------------------- + net/bluetooth/smp.c | 27 ++--------- + 5 files changed, 127 insertions(+), 109 deletions(-) + +--- a/include/net/bluetooth/l2cap.h ++++ b/include/net/bluetooth/l2cap.h +@@ -620,7 +620,8 @@ struct l2cap_chan { + struct l2cap_ops { + char *name; + +- struct l2cap_chan *(*new_connection) (struct l2cap_chan *chan); ++ int (*new_connection)(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan); + int (*recv) (struct l2cap_chan * chan, + struct sk_buff *skb); + void (*teardown) (struct l2cap_chan *chan, int err); +@@ -884,9 +885,10 @@ static inline __u16 __next_seq(struct l2 + return (seq + 1) % (chan->tx_win_max + 1); + } + +-static inline struct l2cap_chan *l2cap_chan_no_new_connection(struct l2cap_chan *chan) ++static inline int l2cap_chan_no_new_connection(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan) + { +- return NULL; ++ return -EOPNOTSUPP; + } + + static inline int l2cap_chan_no_recv(struct l2cap_chan *chan, struct sk_buff *skb) +@@ -962,7 +964,7 @@ int l2cap_chan_send(struct l2cap_chan *c + void l2cap_chan_busy(struct l2cap_chan *chan, int busy); + void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail); + int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator); +-void l2cap_chan_set_defaults(struct l2cap_chan *chan); ++void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan); + int l2cap_ertm_init(struct l2cap_chan *chan); + void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan); + void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan); +--- a/net/bluetooth/6lowpan.c ++++ b/net/bluetooth/6lowpan.c +@@ -631,7 +631,7 @@ static struct l2cap_chan *chan_create(vo + if (!chan) + return NULL; + +- l2cap_chan_set_defaults(chan); ++ l2cap_chan_set_defaults(chan, NULL); + + chan->chan_type = L2CAP_CHAN_CONN_ORIENTED; + chan->mode = L2CAP_MODE_LE_FLOWCTL; +@@ -744,21 +744,6 @@ static inline void chan_ready_cb(struct + ifup(dev->netdev); + } + +-static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *pchan) +-{ +- struct l2cap_chan *chan; +- +- chan = chan_create(); +- if (!chan) +- return NULL; +- +- chan->ops = pchan->ops; +- +- BT_DBG("chan %p pchan %p", chan, pchan); +- +- return chan; +-} +- + static void unregister_dev(struct lowpan_btle_dev *dev) + { + struct hci_dev *hdev = READ_ONCE(dev->hdev); +@@ -900,7 +885,6 @@ static long chan_get_sndtimeo_cb(struct + + static const struct l2cap_ops bt_6lowpan_chan_ops = { + .name = "L2CAP 6LoWPAN channel", +- .new_connection = chan_new_conn_cb, + .recv = chan_recv_cb, + .close = chan_close_cb, + .state_change = chan_state_change_cb, +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -525,7 +525,10 @@ void l2cap_chan_put(struct l2cap_chan *c + } + EXPORT_SYMBOL_GPL(l2cap_chan_put); + +-void l2cap_chan_set_defaults(struct l2cap_chan *chan) ++/* Initialise @chan with default values, inheriting from the parent channel ++ * @pchan when it is given. ++ */ ++void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan) + { + chan->fcs = L2CAP_FCS_CRC16; + chan->max_tx = L2CAP_DEFAULT_MAX_TX; +@@ -539,6 +542,31 @@ void l2cap_chan_set_defaults(struct l2ca + chan->retrans_timeout = L2CAP_DEFAULT_RETRANS_TO; + chan->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO; + ++ if (pchan) { ++ BT_DBG("chan %p pchan %p", chan, pchan); ++ ++ chan->chan_type = pchan->chan_type; ++ chan->imtu = pchan->imtu; ++ chan->omtu = pchan->omtu; ++ chan->mode = pchan->mode; ++ chan->fcs = pchan->fcs; ++ chan->max_tx = pchan->max_tx; ++ chan->tx_win = pchan->tx_win; ++ chan->tx_win_max = pchan->tx_win_max; ++ chan->sec_level = pchan->sec_level; ++ chan->conf_state = pchan->conf_state; ++ chan->flags = pchan->flags; ++ chan->tx_credits = pchan->tx_credits; ++ chan->rx_credits = pchan->rx_credits; ++ ++ if (chan->chan_type == L2CAP_CHAN_FIXED) { ++ chan->scid = pchan->scid; ++ chan->dcid = pchan->scid; ++ } ++ ++ return; ++ } ++ + chan->conf_state = 0; + set_bit(CONF_NOT_COMPLETE, &chan->conf_state); + +@@ -3969,6 +3997,38 @@ static inline int l2cap_command_rej(stru + return 0; + } + ++/* Allocate and initialise a channel for an incoming connection. ++ * ++ * The channel inherits its configuration from @pchan and is linked into @conn ++ * before ->new_connection() runs, so the conn list reference keeps it alive if ++ * the callback exposes it (e.g. via the socket accept queue) before this ++ * returns. The l2cap_chan_create() reference is taken over by the subsystem on ++ * success and dropped here on failure. ++ */ ++static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn, ++ struct l2cap_chan *pchan) ++{ ++ struct l2cap_chan *chan; ++ ++ chan = l2cap_chan_create(); ++ if (!chan) ++ return NULL; ++ ++ l2cap_chan_set_defaults(chan, pchan); ++ chan->ops = pchan->ops; ++ ++ __l2cap_chan_add(conn, chan); ++ ++ if (pchan->ops->new_connection && ++ pchan->ops->new_connection(pchan, chan) < 0) { ++ l2cap_chan_del(chan, 0); ++ l2cap_chan_put(chan); ++ return NULL; ++ } ++ ++ return chan; ++} ++ + static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, + u8 *data, u8 rsp_code) + { +@@ -4015,7 +4075,7 @@ static void l2cap_connect(struct l2cap_c + goto response; + } + +- chan = pchan->ops->new_connection(pchan); ++ chan = l2cap_new_connection(conn, pchan); + if (!chan) + goto response; + +@@ -4033,8 +4093,6 @@ static void l2cap_connect(struct l2cap_c + chan->psm = psm; + chan->dcid = scid; + +- __l2cap_chan_add(conn, chan); +- + dcid = chan->scid; + + __set_chan_timer(chan, chan->ops->get_sndtimeo(chan)); +@@ -4932,7 +4990,7 @@ static int l2cap_le_connect_req(struct l + goto response_unlock; + } + +- chan = pchan->ops->new_connection(pchan); ++ chan = l2cap_new_connection(conn, pchan); + if (!chan) { + result = L2CAP_CR_LE_NO_MEM; + goto response_unlock; +@@ -4947,8 +5005,6 @@ static int l2cap_le_connect_req(struct l + chan->omtu = mtu; + chan->remote_mps = mps; + +- __l2cap_chan_add(conn, chan); +- + l2cap_le_flowctl_init(chan, __le16_to_cpu(req->credits)); + + dcid = chan->scid; +@@ -5156,7 +5212,7 @@ static inline int l2cap_ecred_conn_req(s + continue; + } + +- chan = pchan->ops->new_connection(pchan); ++ chan = l2cap_new_connection(conn, pchan); + if (!chan) { + result = L2CAP_CR_LE_NO_MEM; + continue; +@@ -5171,8 +5227,6 @@ static inline int l2cap_ecred_conn_req(s + chan->omtu = mtu; + chan->remote_mps = mps; + +- __l2cap_chan_add(conn, chan); +- + l2cap_ecred_init(chan, __le16_to_cpu(req->credits)); + + /* Init response */ +@@ -7440,14 +7494,12 @@ static void l2cap_connect_cfm(struct hci + goto next; + + l2cap_chan_lock(pchan); +- chan = pchan->ops->new_connection(pchan); ++ chan = l2cap_new_connection(conn, pchan); + if (chan) { + bacpy(&chan->src, &hcon->src); + bacpy(&chan->dst, &hcon->dst); + chan->src_type = bdaddr_src_type(hcon); + chan->dst_type = dst_type; +- +- __l2cap_chan_add(conn, chan); + } + + l2cap_chan_unlock(pchan); +--- a/net/bluetooth/l2cap_sock.c ++++ b/net/bluetooth/l2cap_sock.c +@@ -45,7 +45,8 @@ static struct bt_sock_list l2cap_sk_list + static const struct proto_ops l2cap_sock_ops; + static void l2cap_sock_init(struct sock *sk, struct sock *parent); + static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, +- int proto, gfp_t prio, int kern); ++ int proto, gfp_t prio, int kern, ++ struct l2cap_chan *chan); + static void l2cap_sock_cleanup_listen(struct sock *parent); + + bool l2cap_is_socket(struct socket *sock) +@@ -1243,6 +1244,23 @@ done: + return err; + } + ++/* Release the sock's ref on chan and clear the pointer so that the ref is ++ * dropped exactly once even if both l2cap_sock_kill() and ++ * l2cap_sock_destruct() run. Setting chan->data to NULL first stops any other ++ * task from dereferencing the now-dead sock pointer. ++ */ ++static void l2cap_sock_put_chan(struct sock *sk) ++{ ++ struct l2cap_chan *chan = l2cap_pi(sk)->chan; ++ ++ if (!chan) ++ return; ++ ++ chan->data = NULL; ++ l2cap_pi(sk)->chan = NULL; ++ l2cap_chan_put(chan); ++} ++ + /* Kill socket (only if zapped and orphan) + * Must be called on unlocked socket, with l2cap channel lock. + */ +@@ -1253,13 +1271,9 @@ static void l2cap_sock_kill(struct sock + + BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state)); + +- /* Sock is dead, so set chan data to NULL, avoid other task use invalid +- * sock pointer. +- */ +- l2cap_pi(sk)->chan->data = NULL; +- /* Kill poor orphan */ ++ l2cap_sock_put_chan(sk); + +- l2cap_chan_put(l2cap_pi(sk)->chan); ++ /* Kill poor orphan */ + sock_set_flag(sk, SOCK_DEAD); + sock_put(sk); + } +@@ -1502,12 +1516,13 @@ static void l2cap_sock_cleanup_listen(st + } + } + +-static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan) ++static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan) + { + struct sock *sk, *parent = chan->data; + + if (!parent) +- return NULL; ++ return -EINVAL; + + lock_sock(parent); + +@@ -1515,25 +1530,28 @@ static struct l2cap_chan *l2cap_sock_new + if (sk_acceptq_is_full(parent)) { + BT_DBG("backlog full %d", parent->sk_ack_backlog); + release_sock(parent); +- return NULL; ++ return -ENOBUFS; + } + + sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP, +- GFP_ATOMIC, 0); ++ GFP_ATOMIC, 0, new_chan); + if (!sk) { + release_sock(parent); +- return NULL; +- } ++ return -ENOMEM; ++ } + + bt_sock_reclassify_lock(sk, BTPROTO_L2CAP); + + l2cap_sock_init(sk, parent); + ++ /* The conn list reference taken by l2cap_new_connection() keeps new_chan ++ * alive once release_sock() lets another task free this socket. ++ */ + bt_accept_enqueue(parent, sk, false); + + release_sock(parent); + +- return l2cap_pi(sk)->chan; ++ return 0; + } + + static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) +@@ -1831,10 +1849,7 @@ static void l2cap_sock_destruct(struct s + + BT_DBG("sk %p", sk); + +- if (l2cap_pi(sk)->chan) { +- l2cap_pi(sk)->chan->data = NULL; +- l2cap_chan_put(l2cap_pi(sk)->chan); +- } ++ l2cap_sock_put_chan(sk); + + list_for_each_entry_safe(rx_busy, next, &l2cap_pi(sk)->rx_busy, list) { + kfree_skb(rx_busy->skb); +@@ -1866,30 +1881,12 @@ static void l2cap_sock_init(struct sock + BT_DBG("sk %p", sk); + + if (parent) { +- struct l2cap_chan *pchan = l2cap_pi(parent)->chan; +- + sk->sk_type = parent->sk_type; + bt_sk(sk)->flags = bt_sk(parent)->flags; + +- chan->chan_type = pchan->chan_type; +- chan->imtu = pchan->imtu; +- chan->omtu = pchan->omtu; +- chan->conf_state = pchan->conf_state; +- chan->mode = pchan->mode; +- chan->fcs = pchan->fcs; +- chan->max_tx = pchan->max_tx; +- chan->tx_win = pchan->tx_win; +- chan->tx_win_max = pchan->tx_win_max; +- chan->sec_level = pchan->sec_level; +- chan->flags = pchan->flags; +- chan->tx_credits = pchan->tx_credits; +- chan->rx_credits = pchan->rx_credits; +- +- if (chan->chan_type == L2CAP_CHAN_FIXED) { +- chan->scid = pchan->scid; +- chan->dcid = pchan->scid; +- } +- ++ /* Channel configuration is inherited from the parent by ++ * l2cap_new_connection(). ++ */ + security_sk_clone(parent, sk); + } else { + switch (sk->sk_type) { +@@ -1915,7 +1912,7 @@ static void l2cap_sock_init(struct sock + chan->mode = L2CAP_MODE_BASIC; + } + +- l2cap_chan_set_defaults(chan); ++ l2cap_chan_set_defaults(chan, NULL); + } + + /* Default config options */ +@@ -1934,10 +1931,10 @@ static struct proto l2cap_proto = { + }; + + static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, +- int proto, gfp_t prio, int kern) ++ int proto, gfp_t prio, int kern, ++ struct l2cap_chan *chan) + { + struct sock *sk; +- struct l2cap_chan *chan; + + sk = bt_sock_alloc(net, sock, &l2cap_proto, proto, prio, kern); + if (!sk) +@@ -1948,16 +1945,7 @@ static struct sock *l2cap_sock_alloc(str + + INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy); + +- chan = l2cap_chan_create(); +- if (!chan) { +- sk_free(sk); +- if (sock) +- sock->sk = NULL; +- return NULL; +- } +- +- l2cap_chan_hold(chan); +- ++ /* The sock takes ownership of the caller's reference on chan. */ + l2cap_pi(sk)->chan = chan; + + return sk; +@@ -1967,6 +1955,7 @@ static int l2cap_sock_create(struct net + int kern) + { + struct sock *sk; ++ struct l2cap_chan *chan; + + BT_DBG("sock %p", sock); + +@@ -1981,10 +1970,16 @@ static int l2cap_sock_create(struct net + + sock->ops = &l2cap_sock_ops; + +- sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern); +- if (!sk) ++ chan = l2cap_chan_create(); ++ if (!chan) + return -ENOMEM; + ++ sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern, chan); ++ if (!sk) { ++ l2cap_chan_put(chan); ++ return -ENOMEM; ++ } ++ + l2cap_sock_init(sk, NULL); + bt_sock_link(&l2cap_sk_list, sk); + return 0; +--- a/net/bluetooth/smp.c ++++ b/net/bluetooth/smp.c +@@ -3234,34 +3234,19 @@ static const struct l2cap_ops smp_chan_o + .get_sndtimeo = l2cap_chan_no_get_sndtimeo, + }; + +-static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan) ++static inline int smp_new_conn_cb(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan) + { +- struct l2cap_chan *chan; +- +- BT_DBG("pchan %p", pchan); +- +- chan = l2cap_chan_create(); +- if (!chan) +- return NULL; +- +- chan->chan_type = pchan->chan_type; +- chan->ops = &smp_chan_ops; +- chan->scid = pchan->scid; +- chan->dcid = chan->scid; +- chan->imtu = pchan->imtu; +- chan->omtu = pchan->omtu; +- chan->mode = pchan->mode; ++ new_chan->ops = &smp_chan_ops; + + /* Other L2CAP channels may request SMP routines in order to + * change the security level. This means that the SMP channel + * lock must be considered in its own category to avoid lockdep + * warnings. + */ +- atomic_set(&chan->nesting, L2CAP_NESTING_SMP); +- +- BT_DBG("created chan %p", chan); ++ atomic_set(&new_chan->nesting, L2CAP_NESTING_SMP); + +- return chan; ++ return 0; + } + + static const struct l2cap_ops smp_root_chan_ops = { +@@ -3332,7 +3317,7 @@ create_chan: + + l2cap_add_scid(chan, cid); + +- l2cap_chan_set_defaults(chan); ++ l2cap_chan_set_defaults(chan, NULL); + + if (cid == L2CAP_CID_SMP) { + u8 bdaddr_type; diff --git a/queue-6.12/bluetooth-separate-cis_link-and-bis_link-link-types.patch b/queue-6.12/bluetooth-separate-cis_link-and-bis_link-link-types.patch new file mode 100644 index 0000000000..b013c61b76 --- /dev/null +++ b/queue-6.12/bluetooth-separate-cis_link-and-bis_link-link-types.patch @@ -0,0 +1,603 @@ +From stable+bounces-275046-greg=kroah.com@vger.kernel.org Wed Jul 15 23:14:05 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:13:54 -0400 +Subject: Bluetooth: separate CIS_LINK and BIS_LINK link types +To: stable@vger.kernel.org +Cc: Pauli Virtanen , Luiz Augusto von Dentz , Sasha Levin +Message-ID: <20260715211355.253617-2-sashal@kernel.org> + +From: Pauli Virtanen + +[ Upstream commit 23205562ffc8de20f57afdd984858cab29e77968 ] + +Use separate link type id for unicast and broadcast ISO connections. +These connection types are handled with separate HCI commands, socket +API is different, and hci_conn has union fields that are different in +the two cases, so they shall not be mixed up. + +Currently in most places it is attempted to distinguish ucast by +bacmp(&c->dst, BDADDR_ANY) but it is wrong as dst is set for bcast sink +hci_conn in iso_conn_ready(). Additionally checking sync_handle might be +OK, but depends on details of bcast conn configuration flow. + +To avoid complicating it, use separate link types. + +Fixes: f764a6c2c1e4 ("Bluetooth: ISO: Add broadcast support") +Signed-off-by: Pauli Virtanen +Signed-off-by: Luiz Augusto von Dentz +Stable-dep-of: 12917f591cea ("Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/hci.h | 3 +- + include/net/bluetooth/hci_core.h | 48 +++++++++++++++++---------------------- + net/bluetooth/hci_conn.c | 41 +++++++++++++++++++-------------- + net/bluetooth/hci_core.c | 21 ++++++++++------- + net/bluetooth/hci_event.c | 24 ++++++++++--------- + net/bluetooth/hci_sync.c | 16 ++++++++----- + net/bluetooth/iso.c | 4 +-- + net/bluetooth/mgmt.c | 3 +- + 8 files changed, 87 insertions(+), 73 deletions(-) + +--- a/include/net/bluetooth/hci.h ++++ b/include/net/bluetooth/hci.h +@@ -558,7 +558,8 @@ enum { + #define ESCO_LINK 0x02 + /* Low Energy links do not have defined link type. Use invented one */ + #define LE_LINK 0x80 +-#define ISO_LINK 0x82 ++#define CIS_LINK 0x82 ++#define BIS_LINK 0x83 + #define INVALID_LINK 0xff + + /* LMP features */ +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -998,7 +998,8 @@ static inline void hci_conn_hash_add(str + case ESCO_LINK: + h->sco_num++; + break; +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + h->iso_num++; + break; + } +@@ -1024,7 +1025,8 @@ static inline void hci_conn_hash_del(str + case ESCO_LINK: + h->sco_num--; + break; +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + h->iso_num--; + break; + } +@@ -1041,7 +1043,8 @@ static inline unsigned int hci_conn_num( + case SCO_LINK: + case ESCO_LINK: + return h->sco_num; +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + return h->iso_num; + default: + return 0; +@@ -1102,7 +1105,7 @@ static inline struct hci_conn *hci_conn_ + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (bacmp(&c->dst, ba) || c->type != ISO_LINK) ++ if (bacmp(&c->dst, ba) || c->type != BIS_LINK) + continue; + + if (c->iso_qos.bcast.bis == bis) { +@@ -1124,7 +1127,7 @@ hci_conn_hash_lookup_create_pa_sync(stru + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (c->type != ISO_LINK) ++ if (c->type != BIS_LINK) + continue; + + if (!test_bit(HCI_CONN_CREATE_PA_SYNC, &c->flags)) +@@ -1150,8 +1153,8 @@ hci_conn_hash_lookup_per_adv_bis(struct + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (bacmp(&c->dst, ba) || c->type != ISO_LINK || +- !test_bit(HCI_CONN_PER_ADV, &c->flags)) ++ if (bacmp(&c->dst, ba) || c->type != BIS_LINK || ++ !test_bit(HCI_CONN_PER_ADV, &c->flags)) + continue; + + if (c->iso_qos.bcast.big == big && +@@ -1261,7 +1264,7 @@ static inline struct hci_conn *hci_conn_ + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (c->type != ISO_LINK || !bacmp(&c->dst, BDADDR_ANY)) ++ if (c->type != CIS_LINK) + continue; + + /* Match CIG ID if set */ +@@ -1293,7 +1296,7 @@ static inline struct hci_conn *hci_conn_ + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (c->type != ISO_LINK || !bacmp(&c->dst, BDADDR_ANY)) ++ if (c->type != CIS_LINK) + continue; + + if (handle == c->iso_qos.ucast.cig) { +@@ -1316,17 +1319,7 @@ static inline struct hci_conn *hci_conn_ + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (c->type != ISO_LINK) +- continue; +- +- /* An ISO_LINK hcon with BDADDR_ANY as destination +- * address is a Broadcast connection. A Broadcast +- * slave connection is associated with a PA train, +- * so the sync_handle can be used to differentiate +- * from unicast. +- */ +- if (bacmp(&c->dst, BDADDR_ANY) && +- c->sync_handle == HCI_SYNC_HANDLE_INVALID) ++ if (c->type != BIS_LINK) + continue; + + if (handle == c->iso_qos.bcast.big) { +@@ -1350,7 +1343,7 @@ hci_conn_hash_lookup_big_sync_pend(struc + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (c->type != ISO_LINK) ++ if (c->type != BIS_LINK) + continue; + + if (handle == c->iso_qos.bcast.big && num_bis == c->num_bis) { +@@ -1373,8 +1366,8 @@ hci_conn_hash_lookup_big_state(struct hc + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (bacmp(&c->dst, BDADDR_ANY) || c->type != ISO_LINK || +- c->state != state) ++ if (c->type != BIS_LINK || bacmp(&c->dst, BDADDR_ANY) || ++ c->state != state) + continue; + + if (handle == c->iso_qos.bcast.big) { +@@ -1397,8 +1390,8 @@ hci_conn_hash_lookup_pa_sync_big_handle( + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (c->type != ISO_LINK || +- !test_bit(HCI_CONN_PA_SYNC, &c->flags)) ++ if (c->type != BIS_LINK || ++ !test_bit(HCI_CONN_PA_SYNC, &c->flags)) + continue; + + if (c->iso_qos.bcast.big == big) { +@@ -1420,7 +1413,7 @@ hci_conn_hash_lookup_pa_sync_handle(stru + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (c->type != ISO_LINK) ++ if (c->type != BIS_LINK) + continue; + + /* Ignore the listen hcon, we are looking +@@ -2030,7 +2023,8 @@ static inline int hci_proto_connect_ind( + case ESCO_LINK: + return sco_connect_ind(hdev, bdaddr, flags); + +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + return iso_connect_ind(hdev, bdaddr, flags); + + default: +--- a/net/bluetooth/hci_conn.c ++++ b/net/bluetooth/hci_conn.c +@@ -785,7 +785,7 @@ static int hci_le_big_terminate(struct h + d->sync_handle = conn->sync_handle; + + if (test_and_clear_bit(HCI_CONN_PA_SYNC, &conn->flags)) { +- hci_conn_hash_list_flag(hdev, find_bis, ISO_LINK, ++ hci_conn_hash_list_flag(hdev, find_bis, BIS_LINK, + HCI_CONN_PA_SYNC, d); + + if (!d->count) +@@ -795,7 +795,7 @@ static int hci_le_big_terminate(struct h + } + + if (test_and_clear_bit(HCI_CONN_BIG_SYNC, &conn->flags)) { +- hci_conn_hash_list_flag(hdev, find_bis, ISO_LINK, ++ hci_conn_hash_list_flag(hdev, find_bis, BIS_LINK, + HCI_CONN_BIG_SYNC, d); + + if (!d->count) +@@ -885,9 +885,11 @@ static void cis_cleanup(struct hci_conn + /* Check if ISO connection is a CIS and remove CIG if there are + * no other connections using it. + */ +- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK, BT_BOUND, &d); +- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK, BT_CONNECT, &d); +- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK, BT_CONNECTED, &d); ++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK, BT_BOUND, &d); ++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK, BT_CONNECT, ++ &d); ++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK, BT_CONNECTED, ++ &d); + if (d.count) + return; + +@@ -910,7 +912,8 @@ static struct hci_conn *__hci_conn_add(s + if (!hdev->acl_mtu) + return ERR_PTR(-ECONNREFUSED); + break; +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + if (hdev->iso_mtu) + /* Dedicated ISO Buffer exists */ + break; +@@ -975,7 +978,8 @@ static struct hci_conn *__hci_conn_add(s + hci_copy_identity_address(hdev, &conn->src, &conn->src_type); + conn->mtu = hdev->le_mtu ? hdev->le_mtu : hdev->acl_mtu; + break; +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + /* conn->src should reflect the local identity address */ + hci_copy_identity_address(hdev, &conn->src, &conn->src_type); + +@@ -1071,7 +1075,8 @@ static void hci_conn_cleanup_child(struc + if (HCI_CONN_HANDLE_UNSET(conn->handle)) + hci_conn_failed(conn, reason); + break; +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + if ((conn->state != BT_CONNECTED && + !test_bit(HCI_CONN_CREATE_CIS, &conn->flags)) || + test_bit(HCI_CONN_BIG_CREATED, &conn->flags)) +@@ -1146,7 +1151,8 @@ void hci_conn_del(struct hci_conn *conn) + hdev->acl_cnt += conn->sent; + } else { + /* Unacked ISO frames */ +- if (conn->type == ISO_LINK) { ++ if (conn->type == CIS_LINK || ++ conn->type == BIS_LINK) { + if (hdev->iso_pkts) + hdev->iso_cnt += conn->sent; + else if (hdev->le_pkts) +@@ -1531,7 +1537,7 @@ static struct hci_conn *hci_add_bis(stru + memcmp(conn->le_per_adv_data, base, base_len))) + return ERR_PTR(-EADDRINUSE); + +- conn = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER); ++ conn = hci_conn_add_unset(hdev, BIS_LINK, dst, HCI_ROLE_MASTER); + if (IS_ERR(conn)) + return conn; + +@@ -1740,7 +1746,7 @@ static int hci_le_create_big(struct hci_ + data.count = 0; + + /* Create a BIS for each bound connection */ +- hci_conn_hash_list_state(hdev, bis_list, ISO_LINK, ++ hci_conn_hash_list_state(hdev, bis_list, BIS_LINK, + BT_BOUND, &data); + + cp.handle = qos->bcast.big; +@@ -1835,12 +1841,12 @@ static bool hci_le_set_cig_params(struct + for (data.cig = 0x00; data.cig < 0xf0; data.cig++) { + data.count = 0; + +- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK, ++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK, + BT_CONNECT, &data); + if (data.count) + continue; + +- hci_conn_hash_list_state(hdev, find_cis, ISO_LINK, ++ hci_conn_hash_list_state(hdev, find_cis, CIS_LINK, + BT_CONNECTED, &data); + if (!data.count) + break; +@@ -1892,7 +1898,8 @@ struct hci_conn *hci_bind_cis(struct hci + cis = hci_conn_hash_lookup_cis(hdev, dst, dst_type, qos->ucast.cig, + qos->ucast.cis); + if (!cis) { +- cis = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_MASTER); ++ cis = hci_conn_add_unset(hdev, CIS_LINK, dst, ++ HCI_ROLE_MASTER); + if (IS_ERR(cis)) + return cis; + cis->cleanup = cis_cleanup; +@@ -1982,7 +1989,7 @@ bool hci_iso_setup_path(struct hci_conn + + int hci_conn_check_create_cis(struct hci_conn *conn) + { +- if (conn->type != ISO_LINK || !bacmp(&conn->dst, BDADDR_ANY)) ++ if (conn->type != CIS_LINK) + return -EINVAL; + + if (!conn->parent || conn->parent->state != BT_CONNECTED || +@@ -2082,7 +2089,7 @@ struct hci_conn *hci_pa_create_sync(stru + + bt_dev_dbg(hdev, "dst %pMR type %d sid %d", dst, dst_type, sid); + +- conn = hci_conn_add_unset(hdev, ISO_LINK, dst, HCI_ROLE_SLAVE); ++ conn = hci_conn_add_unset(hdev, BIS_LINK, dst, HCI_ROLE_SLAVE); + if (IS_ERR(conn)) + return conn; + +@@ -2259,7 +2266,7 @@ struct hci_conn *hci_connect_bis(struct + * the start periodic advertising and create BIG commands have + * been queued + */ +- hci_conn_hash_list_state(hdev, bis_mark_per_adv, ISO_LINK, ++ hci_conn_hash_list_state(hdev, bis_mark_per_adv, BIS_LINK, + BT_BOUND, &data); + + /* Queue start periodic advertising and create BIG */ +--- a/net/bluetooth/hci_core.c ++++ b/net/bluetooth/hci_core.c +@@ -2959,12 +2959,13 @@ int hci_recv_frame(struct hci_dev *hdev, + break; + case HCI_ACLDATA_PKT: + /* Detect if ISO packet has been sent as ACL */ +- if (hci_conn_num(hdev, ISO_LINK)) { ++ if (hci_conn_num(hdev, CIS_LINK) || ++ hci_conn_num(hdev, BIS_LINK)) { + __u16 handle = __le16_to_cpu(hci_acl_hdr(skb)->handle); + __u8 type; + + type = hci_conn_lookup_type(hdev, hci_handle(handle)); +- if (type == ISO_LINK) ++ if (type == CIS_LINK || type == BIS_LINK) + hci_skb_pkt_type(skb) = HCI_ISODATA_PKT; + } + break; +@@ -3399,7 +3400,8 @@ static inline void hci_quote_sent(struct + case LE_LINK: + cnt = hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt; + break; +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + cnt = hdev->iso_mtu ? hdev->iso_cnt : + hdev->le_mtu ? hdev->le_cnt : hdev->acl_cnt; + break; +@@ -3413,7 +3415,7 @@ static inline void hci_quote_sent(struct + } + + static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, +- int *quote) ++ __u8 type2, int *quote) + { + struct hci_conn_hash *h = &hdev->conn_hash; + struct hci_conn *conn = NULL, *c; +@@ -3425,7 +3427,8 @@ static struct hci_conn *hci_low_sent(str + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +- if (c->type != type || skb_queue_empty(&c->data_q)) ++ if ((c->type != type && c->type != type2) || ++ skb_queue_empty(&c->data_q)) + continue; + + if (c->state != BT_CONNECTED && c->state != BT_CONFIG) +@@ -3628,7 +3631,7 @@ static void hci_sched_sco(struct hci_dev + else + cnt = &hdev->sco_cnt; + +- while (*cnt && (conn = hci_low_sent(hdev, type, "e))) { ++ while (*cnt && (conn = hci_low_sent(hdev, type, type, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); + hci_send_frame(hdev, skb); +@@ -3756,12 +3759,14 @@ static void hci_sched_iso(struct hci_dev + + BT_DBG("%s", hdev->name); + +- if (!hci_conn_num(hdev, ISO_LINK)) ++ if (!hci_conn_num(hdev, CIS_LINK) && ++ !hci_conn_num(hdev, BIS_LINK)) + return; + + cnt = hdev->iso_pkts ? &hdev->iso_cnt : + hdev->le_pkts ? &hdev->le_cnt : &hdev->acl_cnt; +- while (*cnt && (conn = hci_low_sent(hdev, ISO_LINK, "e))) { ++ while (*cnt && (conn = hci_low_sent(hdev, CIS_LINK, BIS_LINK, ++ "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); + hci_send_frame(hdev, skb); +--- a/net/bluetooth/hci_event.c ++++ b/net/bluetooth/hci_event.c +@@ -3776,7 +3776,7 @@ static void hci_unbound_cis_failed(struc + lockdep_assert_held(&hdev->lock); + + list_for_each_entry_safe(conn, tmp, &hdev->conn_hash.list, list) { +- if (conn->type != ISO_LINK || !bacmp(&conn->dst, BDADDR_ANY) || ++ if (conn->type != CIS_LINK || + conn->state == BT_OPEN || conn->iso_qos.ucast.cig != cig) + continue; + +@@ -4453,7 +4453,8 @@ static void hci_num_comp_pkts_evt(struct + + break; + +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + if (hdev->iso_pkts) { + hdev->iso_cnt += count; + if (hdev->iso_cnt > hdev->iso_pkts) +@@ -6420,7 +6421,8 @@ static void hci_le_pa_sync_estabilished_ + conn->sync_handle = le16_to_cpu(ev->handle); + conn->sid = HCI_SID_INVALID; + +- mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, ISO_LINK, &flags); ++ mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, BIS_LINK, ++ &flags); + if (!(mask & HCI_LM_ACCEPT)) { + hci_le_pa_term_sync(hdev, ev->handle); + goto unlock; +@@ -6430,7 +6432,7 @@ static void hci_le_pa_sync_estabilished_ + goto unlock; + + /* Add connection to indicate PA sync event */ +- pa_sync = hci_conn_add_unset(hdev, ISO_LINK, BDADDR_ANY, ++ pa_sync = hci_conn_add_unset(hdev, BIS_LINK, BDADDR_ANY, + HCI_ROLE_SLAVE); + + if (IS_ERR(pa_sync)) +@@ -6461,7 +6463,7 @@ static void hci_le_per_adv_report_evt(st + + hci_dev_lock(hdev); + +- mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, ISO_LINK, &flags); ++ mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, BIS_LINK, &flags); + if (!(mask & HCI_LM_ACCEPT)) + goto unlock; + +@@ -6752,7 +6754,7 @@ static void hci_le_cis_estabilished_evt( + goto unlock; + } + +- if (conn->type != ISO_LINK) { ++ if (conn->type != CIS_LINK) { + bt_dev_err(hdev, + "Invalid connection link type handle 0x%4.4x", + handle); +@@ -6870,7 +6872,7 @@ static void hci_le_cis_req_evt(struct hc + if (!acl) + goto unlock; + +- mask = hci_proto_connect_ind(hdev, &acl->dst, ISO_LINK, &flags); ++ mask = hci_proto_connect_ind(hdev, &acl->dst, CIS_LINK, &flags); + if (!(mask & HCI_LM_ACCEPT)) { + hci_le_reject_cis(hdev, ev->cis_handle); + goto unlock; +@@ -6878,8 +6880,8 @@ static void hci_le_cis_req_evt(struct hc + + cis = hci_conn_hash_lookup_handle(hdev, cis_handle); + if (!cis) { +- cis = hci_conn_add(hdev, ISO_LINK, &acl->dst, HCI_ROLE_SLAVE, +- cis_handle); ++ cis = hci_conn_add(hdev, CIS_LINK, &acl->dst, ++ HCI_ROLE_SLAVE, cis_handle); + if (IS_ERR(cis)) { + hci_le_reject_cis(hdev, ev->cis_handle); + goto unlock; +@@ -7017,7 +7019,7 @@ static void hci_le_big_sync_established_ + bt_dev_dbg(hdev, "ignore too large handle %u", handle); + continue; + } +- bis = hci_conn_add(hdev, ISO_LINK, BDADDR_ANY, ++ bis = hci_conn_add(hdev, BIS_LINK, BDADDR_ANY, + HCI_ROLE_SLAVE, handle); + if (IS_ERR(bis)) + continue; +@@ -7076,7 +7078,7 @@ static void hci_le_big_info_adv_report_e + + hci_dev_lock(hdev); + +- mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, ISO_LINK, &flags); ++ mask |= hci_proto_connect_ind(hdev, BDADDR_ANY, BIS_LINK, &flags); + if (!(mask & HCI_LM_ACCEPT)) + goto unlock; + +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -2979,7 +2979,7 @@ static int hci_le_set_ext_scan_param_syn + if (sent) { + struct hci_conn *conn; + +- conn = hci_conn_hash_lookup_ba(hdev, ISO_LINK, ++ conn = hci_conn_hash_lookup_ba(hdev, BIS_LINK, + &sent->bdaddr); + if (conn) { + struct bt_iso_qos *qos = &conn->iso_qos; +@@ -5596,7 +5596,7 @@ static int hci_connect_cancel_sync(struc + if (conn->type == LE_LINK) + return hci_le_connect_cancel_sync(hdev, conn, reason); + +- if (conn->type == ISO_LINK) { ++ if (conn->type == CIS_LINK) { + /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 4, Part E + * page 1857: + * +@@ -5609,9 +5609,10 @@ static int hci_connect_cancel_sync(struc + return hci_disconnect_sync(hdev, conn, reason); + + /* CIS with no Create CIS sent have nothing to cancel */ +- if (bacmp(&conn->dst, BDADDR_ANY)) +- return HCI_ERROR_LOCAL_HOST_TERM; ++ return HCI_ERROR_LOCAL_HOST_TERM; ++ } + ++ if (conn->type == BIS_LINK) { + /* There is no way to cancel a BIS without terminating the BIG + * which is done later on connection cleanup. + */ +@@ -5673,9 +5674,12 @@ static int hci_reject_conn_sync(struct h + { + struct hci_cp_reject_conn_req cp; + +- if (conn->type == ISO_LINK) ++ if (conn->type == CIS_LINK) + return hci_le_reject_cis_sync(hdev, conn, reason); + ++ if (conn->type == BIS_LINK) ++ return -EINVAL; ++ + if (conn->type == SCO_LINK || conn->type == ESCO_LINK) + return hci_reject_sco_sync(hdev, conn, reason); + +@@ -7039,7 +7043,7 @@ static void create_pa_complete(struct hc + goto unlock; + + /* Add connection to indicate PA sync error */ +- pa_sync = hci_conn_add_unset(hdev, ISO_LINK, BDADDR_ANY, ++ pa_sync = hci_conn_add_unset(hdev, BIS_LINK, BDADDR_ANY, + HCI_ROLE_SLAVE); + + if (IS_ERR(pa_sync)) +--- a/net/bluetooth/iso.c ++++ b/net/bluetooth/iso.c +@@ -2211,7 +2211,7 @@ done: + + static void iso_connect_cfm(struct hci_conn *hcon, __u8 status) + { +- if (hcon->type != ISO_LINK) { ++ if (hcon->type != CIS_LINK && hcon->type != BIS_LINK) { + if (hcon->type != LE_LINK) + return; + +@@ -2252,7 +2252,7 @@ static void iso_connect_cfm(struct hci_c + + static void iso_disconn_cfm(struct hci_conn *hcon, __u8 reason) + { +- if (hcon->type != ISO_LINK) ++ if (hcon->type != CIS_LINK && hcon->type != BIS_LINK) + return; + + BT_DBG("hcon %p reason %d", hcon, reason); +--- a/net/bluetooth/mgmt.c ++++ b/net/bluetooth/mgmt.c +@@ -3240,7 +3240,8 @@ failed: + static u8 link_to_bdaddr(u8 link_type, u8 addr_type) + { + switch (link_type) { +- case ISO_LINK: ++ case CIS_LINK: ++ case BIS_LINK: + case LE_LINK: + switch (addr_type) { + case ADDR_LE_DEV_PUBLIC: diff --git a/queue-6.12/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch b/queue-6.12/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch new file mode 100644 index 0000000000..b278baa7af --- /dev/null +++ b/queue-6.12/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch @@ -0,0 +1,118 @@ +From stable+bounces-277343-greg=kroah.com@vger.kernel.org Sat Jul 18 17:13:47 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 11:13:29 -0400 +Subject: bpf: Allow LPM map access from sleepable BPF programs +To: stable@vger.kernel.org +Cc: Vlad Poenaru , Emil Tsalapatis , Alexei Starovoitov , Sasha Levin +Message-ID: <20260718151329.3271163-4-sashal@kernel.org> + +From: Vlad Poenaru + +[ Upstream commit 2f884d371fafea137afea504d49ee4a7c8d7985b ] + +trie_lookup_elem() annotates its rcu_dereference_check() walks with +only rcu_read_lock_bh_held(). Because rcu_dereference_check(p, c) +resolves to "c || rcu_read_lock_held()", this passes for XDP/NAPI and +classic RCU readers but fails for sleepable BPF programs, which enter +via __bpf_prog_enter_sleepable() and hold only rcu_read_lock_trace(). + +trie_update_elem() and trie_delete_elem() have the same problem in a +different form: they walk the trie with plain rcu_dereference(), which +asserts rcu_read_lock_held() unconditionally. Both are reachable from +sleepable BPF programs via the bpf_map_update_elem / bpf_map_delete_elem +helpers, and from the syscall path under classic rcu_read_lock(). In +the writer paths the trie is actually protected by trie->lock (an +rqspinlock taken across the walk); we never relied on the RCU read-side +lock to keep nodes alive there. + +A sleepable LSM hook that ends up touching an LPM trie therefore +triggers lockdep on debug kernels: + + ============================= + WARNING: suspicious RCU usage + 7.1.0-... Tainted: G E + ----------------------------- + kernel/bpf/lpm_trie.c:249 suspicious rcu_dereference_check() usage! + 1 lock held by net_tests/540: + #0: (rcu_tasks_trace_srcu_struct){....}-{0:0}, + at: __bpf_prog_enter_sleepable+0x26/0x280 + Call Trace: + dump_stack_lvl + lockdep_rcu_suspicious + trie_lookup_elem + bpf_prog_..._enforce_security_socket_connect + bpf_trampoline_... + security_socket_connect + __sys_connect + do_syscall_64 + +This is lockdep-only -- no UAF, since Tasks Trace RCU does serialize +against the trie's reclaim path -- but it spams the console once per +distinct callsite on every debug kernel running a sleepable BPF LSM +that touches an LPM trie, which is increasingly common. + +For the lookup path, switch the rcu_dereference_check() annotation +from rcu_read_lock_bh_held() to bpf_rcu_lock_held(), which accepts all +three contexts (classic, BH, Tasks Trace). Other map types already +follow this convention. + +For trie_update_elem() and trie_delete_elem(), annotate the walks as +rcu_dereference_protected(*p, 1) -- matching trie_free() in the same +file -- since trie->lock is held across the walk. rqspinlock has no +lockdep_map, so the predicate degenerates to '1' rather than +lockdep_is_held(&trie->lock); the protection is real but not +machine-verifiable. trie_get_next_key() also uses bare +rcu_dereference() but is reachable only from the BPF syscall, which +holds classic rcu_read_lock() before dispatching, so it is left +untouched. + +Fixes: 694cea395fde ("bpf: Allow RCU-protected lookups to happen from bh context") +Cc: stable@vger.kernel.org +Signed-off-by: Vlad Poenaru +Reviewed-by: Emil Tsalapatis +Link: https://lore.kernel.org/r/20260609135558.193287-2-vlad.wing@gmail.com +Signed-off-by: Alexei Starovoitov +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/bpf/lpm_trie.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/kernel/bpf/lpm_trie.c ++++ b/kernel/bpf/lpm_trie.c +@@ -245,7 +245,7 @@ static void *trie_lookup_elem(struct bpf + + /* Start walking the trie from the root node ... */ + +- for (node = rcu_dereference_check(trie->root, rcu_read_lock_bh_held()); ++ for (node = rcu_dereference_check(trie->root, bpf_rcu_lock_held()); + node;) { + unsigned int next_bit; + size_t matchlen; +@@ -279,7 +279,7 @@ static void *trie_lookup_elem(struct bpf + */ + next_bit = extract_bit(key->data, node->prefixlen); + node = rcu_dereference_check(node->child[next_bit], +- rcu_read_lock_bh_held()); ++ bpf_rcu_lock_held()); + } + + if (!found) +@@ -362,7 +362,7 @@ static long trie_update_elem(struct bpf_ + */ + slot = &trie->root; + +- while ((node = rcu_dereference(*slot))) { ++ while ((node = rcu_dereference_protected(*slot, 1))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || +@@ -483,7 +483,7 @@ static long trie_delete_elem(struct bpf_ + trim = &trie->root; + trim2 = trim; + parent = NULL; +- while ((node = rcu_dereference(*trim))) { ++ while ((node = rcu_dereference_protected(*trim, 1))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || diff --git a/queue-6.12/bpf-arm64-powerpc-add-bpf_jit_bypass_spec_v1-v4.patch b/queue-6.12/bpf-arm64-powerpc-add-bpf_jit_bypass_spec_v1-v4.patch new file mode 100644 index 0000000000..e872d2b826 --- /dev/null +++ b/queue-6.12/bpf-arm64-powerpc-add-bpf_jit_bypass_spec_v1-v4.patch @@ -0,0 +1,206 @@ +From stable+bounces-277341-greg=kroah.com@vger.kernel.org Sat Jul 18 17:13:38 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 11:13:27 -0400 +Subject: bpf, arm64, powerpc: Add bpf_jit_bypass_spec_v1/v4() +To: stable@vger.kernel.org +Cc: Luis Gerhorst , Hari Bathini , Henriette Herzog , Maximilian Ott , Milan Stephan , Alexei Starovoitov , Sasha Levin +Message-ID: <20260718151329.3271163-2-sashal@kernel.org> + +From: Luis Gerhorst + +[ Upstream commit 03c68a0f8c68936a0bb915b030693923784724cb ] + +JITs can set bpf_jit_bypass_spec_v1/v4() if they want the verifier to +skip analysis/patching for the respective vulnerability. For v4, this +will reduce the number of barriers the verifier inserts. For v1, it +allows more programs to be accepted. + +The primary motivation for this is to not regress unpriv BPF's +performance on ARM64 in a future commit where BPF_NOSPEC is also used +against Spectre v1. + +This has the user-visible change that v1-induced rejections on +non-vulnerable PowerPC CPUs are avoided. + +For now, this does not change the semantics of BPF_NOSPEC. It is still a +v4-only barrier and must not be implemented if bypass_spec_v4 is always +true for the arch. Changing it to a v1 AND v4-barrier is done in a +future commit. + +As an alternative to bypass_spec_v1/v4, one could introduce NOSPEC_V1 +AND NOSPEC_V4 instructions and allow backends to skip their lowering as +suggested by commit f5e81d111750 ("bpf: Introduce BPF nospec instruction +for mitigating Spectre v4"). Adding bpf_jit_bypass_spec_v1/v4() was +found to be preferable for the following reason: + +* bypass_spec_v1/v4 benefits non-vulnerable CPUs: Always performing the + same analysis (not taking into account whether the current CPU is + vulnerable), needlessly restricts users of CPUs that are not + vulnerable. The only use case for this would be portability-testing, + but this can later be added easily when needed by allowing users to + force bypass_spec_v1/v4 to false. + +* Portability is still acceptable: Directly disabling the analysis + instead of skipping the lowering of BPF_NOSPEC(_V1/V4) might allow + programs on non-vulnerable CPUs to be accepted while the program will + be rejected on vulnerable CPUs. With the fallback to speculation + barriers for Spectre v1 implemented in a future commit, this will only + affect programs that do variable stack-accesses or are very complex. + +For PowerPC, the SEC_FTR checking in bpf_jit_bypass_spec_v4() is based +on the check that was previously located in the BPF_NOSPEC case. + +For LoongArch, it would likely be safe to set both +bpf_jit_bypass_spec_v1() and _v4() according to +commit a6f6a95f2580 ("LoongArch, bpf: Fix jit to skip speculation +barrier opcode"). This is omitted here as I am unable to do any testing +for LoongArch. + +Hari's ack concerns the PowerPC part only. + +Signed-off-by: Luis Gerhorst +Acked-by: Hari Bathini +Cc: Henriette Herzog +Cc: Maximilian Ott +Cc: Milan Stephan +Link: https://lore.kernel.org/r/20250603211318.337474-1-luis.gerhorst@fau.de +Signed-off-by: Alexei Starovoitov +Stable-dep-of: 2f884d371faf ("bpf: Allow LPM map access from sleepable BPF programs") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + arch/arm64/net/bpf_jit_comp.c | 21 ++++++++++++--------- + arch/powerpc/net/bpf_jit_comp64.c | 21 +++++++++++++++++---- + include/linux/bpf.h | 11 +++++++++-- + kernel/bpf/core.c | 15 +++++++++++++++ + 4 files changed, 53 insertions(+), 15 deletions(-) + +--- a/arch/arm64/net/bpf_jit_comp.c ++++ b/arch/arm64/net/bpf_jit_comp.c +@@ -1558,15 +1558,7 @@ emit_cond_jmp: + + /* speculation barrier */ + case BPF_ST | BPF_NOSPEC: +- /* +- * Nothing required here. +- * +- * In case of arm64, we rely on the firmware mitigation of +- * Speculative Store Bypass as controlled via the ssbd kernel +- * parameter. Whenever the mitigation is enabled, it works +- * for all of the kernel code with no need to provide any +- * additional instructions. +- */ ++ /* See bpf_jit_bypass_spec_v4() */ + break; + + /* ST: *(size *)(dst + off) = imm */ +@@ -2730,6 +2722,17 @@ bool bpf_jit_supports_percpu_insn(void) + return true; + } + ++bool bpf_jit_bypass_spec_v4(void) ++{ ++ /* In case of arm64, we rely on the firmware mitigation of Speculative ++ * Store Bypass as controlled via the ssbd kernel parameter. Whenever ++ * the mitigation is enabled, it works for all of the kernel code with ++ * no need to provide any additional instructions. Therefore, skip ++ * inserting nospec insns against Spectre v4. ++ */ ++ return true; ++} ++ + bool bpf_jit_inlines_helper_call(s32 imm) + { + switch (imm) { +--- a/arch/powerpc/net/bpf_jit_comp64.c ++++ b/arch/powerpc/net/bpf_jit_comp64.c +@@ -440,6 +440,23 @@ static int bpf_jit_emit_tail_call(u32 *i + return 0; + } + ++bool bpf_jit_bypass_spec_v1(void) ++{ ++#if defined(CONFIG_PPC_E500) || defined(CONFIG_PPC_BOOK3S_64) ++ return !(security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) && ++ security_ftr_enabled(SEC_FTR_BNDS_CHK_SPEC_BAR)); ++#else ++ return true; ++#endif ++} ++ ++bool bpf_jit_bypass_spec_v4(void) ++{ ++ return !(security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) && ++ security_ftr_enabled(SEC_FTR_STF_BARRIER) && ++ stf_barrier_type_get() != STF_BARRIER_NONE); ++} ++ + /* + * We spill into the redzone always, even if the bpf program has its own stackframe. + * Offsets hardcoded based on BPF_PPC_STACK_SAVE -- see bpf_jit_stack_local() +@@ -864,10 +881,6 @@ emit_clear: + * BPF_ST NOSPEC (speculation barrier) + */ + case BPF_ST | BPF_NOSPEC: +- if (!security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) || +- !security_ftr_enabled(SEC_FTR_STF_BARRIER)) +- break; +- + switch (stf_barrier) { + case STF_BARRIER_EIEIO: + EMIT(PPC_RAW_EIEIO() | 0x02000000); +--- a/include/linux/bpf.h ++++ b/include/linux/bpf.h +@@ -2216,6 +2216,9 @@ bpf_prog_run_array_uprobe(const struct b + return ret; + } + ++bool bpf_jit_bypass_spec_v1(void); ++bool bpf_jit_bypass_spec_v4(void); ++ + #ifdef CONFIG_BPF_SYSCALL + DECLARE_PER_CPU(int, bpf_prog_active); + extern struct mutex bpf_stats_enabled_mutex; +@@ -2386,12 +2389,16 @@ static inline bool bpf_allow_uninit_stac + + static inline bool bpf_bypass_spec_v1(const struct bpf_token *token) + { +- return cpu_mitigations_off() || bpf_token_capable(token, CAP_PERFMON); ++ return bpf_jit_bypass_spec_v1() || ++ cpu_mitigations_off() || ++ bpf_token_capable(token, CAP_PERFMON); + } + + static inline bool bpf_bypass_spec_v4(const struct bpf_token *token) + { +- return cpu_mitigations_off() || bpf_token_capable(token, CAP_PERFMON); ++ return bpf_jit_bypass_spec_v4() || ++ cpu_mitigations_off() || ++ bpf_token_capable(token, CAP_PERFMON); + } + + int bpf_map_new_fd(struct bpf_map *map, int flags); +--- a/kernel/bpf/core.c ++++ b/kernel/bpf/core.c +@@ -3088,6 +3088,21 @@ bool __weak bpf_jit_needs_zext(void) + return false; + } + ++/* By default, enable the verifier's mitigations against Spectre v1 and v4 for ++ * all archs. The value returned must not change at runtime as there is ++ * currently no support for reloading programs that were loaded without ++ * mitigations. ++ */ ++bool __weak bpf_jit_bypass_spec_v1(void) ++{ ++ return false; ++} ++ ++bool __weak bpf_jit_bypass_spec_v4(void) ++{ ++ return false; ++} ++ + /* Return true if the JIT inlines the call to the helper corresponding to + * the imm. + * diff --git a/queue-6.12/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch b/queue-6.12/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch new file mode 100644 index 0000000000..ce7947688c --- /dev/null +++ b/queue-6.12/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch @@ -0,0 +1,168 @@ +From stable+bounces-277342-greg=kroah.com@vger.kernel.org Sat Jul 18 17:13:42 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 11:13:28 -0400 +Subject: bpf: Consistently use bpf_rcu_lock_held() everywhere +To: stable@vger.kernel.org +Cc: Andrii Nakryiko , Daniel Borkmann , Jiri Olsa , Sasha Levin +Message-ID: <20260718151329.3271163-3-sashal@kernel.org> + +From: Andrii Nakryiko + +[ Upstream commit 48a97ffc6c826640907d13b199e29008f4fe2c15 ] + +We have many places which open-code what's now is bpf_rcu_lock_held() +macro, so replace all those places with a clean and short macro invocation. +For that, move bpf_rcu_lock_held() macro into include/linux/bpf.h. + +Signed-off-by: Andrii Nakryiko +Signed-off-by: Daniel Borkmann +Acked-by: Jiri Olsa +Link: https://lore.kernel.org/bpf/20251014201403.4104511-1-andrii@kernel.org +Stable-dep-of: 2f884d371faf ("bpf: Allow LPM map access from sleepable BPF programs") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/bpf.h | 3 +++ + include/linux/bpf_local_storage.h | 3 --- + kernel/bpf/hashtab.c | 21 +++++++-------------- + kernel/bpf/helpers.c | 12 ++++-------- + 4 files changed, 14 insertions(+), 25 deletions(-) + +--- a/include/linux/bpf.h ++++ b/include/linux/bpf.h +@@ -2219,6 +2219,9 @@ bpf_prog_run_array_uprobe(const struct b + bool bpf_jit_bypass_spec_v1(void); + bool bpf_jit_bypass_spec_v4(void); + ++#define bpf_rcu_lock_held() \ ++ (rcu_read_lock_held() || rcu_read_lock_trace_held() || rcu_read_lock_bh_held()) ++ + #ifdef CONFIG_BPF_SYSCALL + DECLARE_PER_CPU(int, bpf_prog_active); + extern struct mutex bpf_stats_enabled_mutex; +--- a/include/linux/bpf_local_storage.h ++++ b/include/linux/bpf_local_storage.h +@@ -18,9 +18,6 @@ + + #define BPF_LOCAL_STORAGE_CACHE_SIZE 16 + +-#define bpf_rcu_lock_held() \ +- (rcu_read_lock_held() || rcu_read_lock_trace_held() || \ +- rcu_read_lock_bh_held()) + struct bpf_local_storage_map_bucket { + struct hlist_head list; + raw_spinlock_t lock; +--- a/kernel/bpf/hashtab.c ++++ b/kernel/bpf/hashtab.c +@@ -685,8 +685,7 @@ static void *__htab_map_lookup_elem(stru + struct htab_elem *l; + u32 hash, key_size; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1122,8 +1121,7 @@ static long htab_map_update_elem(struct + /* unknown flags */ + return -EINVAL; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1240,8 +1238,7 @@ static long htab_lru_map_update_elem(str + /* unknown flags */ + return -EINVAL; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1309,8 +1306,7 @@ static long __htab_percpu_map_update_ele + /* unknown flags */ + return -EINVAL; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1364,8 +1360,7 @@ static long __htab_lru_percpu_map_update + /* unknown flags */ + return -EINVAL; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1442,8 +1437,7 @@ static long htab_map_delete_elem(struct + u32 hash, key_size; + int ret; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +@@ -1478,8 +1472,7 @@ static long htab_lru_map_delete_elem(str + u32 hash, key_size; + int ret; + +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + + key_size = map->key_size; + +--- a/kernel/bpf/helpers.c ++++ b/kernel/bpf/helpers.c +@@ -37,8 +37,7 @@ + */ + BPF_CALL_2(bpf_map_lookup_elem, struct bpf_map *, map, void *, key) + { +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + return (unsigned long) map->ops->map_lookup_elem(map, key); + } + +@@ -54,8 +53,7 @@ const struct bpf_func_proto bpf_map_look + BPF_CALL_4(bpf_map_update_elem, struct bpf_map *, map, void *, key, + void *, value, u64, flags) + { +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + return map->ops->map_update_elem(map, key, value, flags); + } + +@@ -72,8 +70,7 @@ const struct bpf_func_proto bpf_map_upda + + BPF_CALL_2(bpf_map_delete_elem, struct bpf_map *, map, void *, key) + { +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + return map->ops->map_delete_elem(map, key); + } + +@@ -129,8 +126,7 @@ const struct bpf_func_proto bpf_map_peek + + BPF_CALL_3(bpf_map_lookup_percpu_elem, struct bpf_map *, map, void *, key, u32, cpu) + { +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + return (unsigned long) map->ops->map_lookup_percpu_elem(map, key, cpu); + } + diff --git a/queue-6.12/bpf-convert-lpm_trie.c-to-rqspinlock.patch b/queue-6.12/bpf-convert-lpm_trie.c-to-rqspinlock.patch new file mode 100644 index 0000000000..bfbc860112 --- /dev/null +++ b/queue-6.12/bpf-convert-lpm_trie.c-to-rqspinlock.patch @@ -0,0 +1,62 @@ +From stable+bounces-277340-greg=kroah.com@vger.kernel.org Sat Jul 18 17:13:36 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 11:13:26 -0400 +Subject: bpf: Convert lpm_trie.c to rqspinlock +To: stable@vger.kernel.org +Cc: Kumar Kartikeya Dwivedi , Alexei Starovoitov , Sasha Levin +Message-ID: <20260718151329.3271163-1-sashal@kernel.org> + +From: Kumar Kartikeya Dwivedi + +[ Upstream commit 47979314c0fe245ed54306e2f91b3f819c7c0f9f ] + +Convert all LPM trie usage of raw_spinlock to rqspinlock. + +Note that rcu_dereference_protected in trie_delete_elem is switched over +to plain rcu_dereference, the RCU read lock should be held from BPF +program side or eBPF syscall path, and the trie->lock is just acquired +before the dereference. It is not clear the reason the protected variant +was used from the commit history, but the above reasoning makes sense so +switch over. + +Closes: https://lore.kernel.org/lkml/000000000000adb08b061413919e@google.com +Signed-off-by: Kumar Kartikeya Dwivedi +Link: https://lore.kernel.org/r/20250316040541.108729-22-memxor@gmail.com +Signed-off-by: Alexei Starovoitov +Stable-dep-of: 2f884d371faf ("bpf: Allow LPM map access from sleepable BPF programs") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/bpf/lpm_trie.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +--- a/kernel/bpf/lpm_trie.c ++++ b/kernel/bpf/lpm_trie.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + + /* Intermediate node */ + #define LPM_TREE_NODE_FLAG_IM BIT(0) +@@ -361,8 +362,7 @@ static long trie_update_elem(struct bpf_ + */ + slot = &trie->root; + +- while ((node = rcu_dereference_protected(*slot, +- lockdep_is_held(&trie->lock)))) { ++ while ((node = rcu_dereference(*slot))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || +@@ -483,8 +483,7 @@ static long trie_delete_elem(struct bpf_ + trim = &trie->root; + trim2 = trim; + parent = NULL; +- while ((node = rcu_dereference_protected( +- *trim, lockdep_is_held(&trie->lock)))) { ++ while ((node = rcu_dereference(*trim))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || diff --git a/queue-6.12/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch b/queue-6.12/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch new file mode 100644 index 0000000000..bc973a5936 --- /dev/null +++ b/queue-6.12/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch @@ -0,0 +1,268 @@ +From stable+bounces-278144-greg=kroah.com@vger.kernel.org Mon Jul 20 19:39:13 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 12:51:08 -0400 +Subject: btrfs: check and set EXTENT_DELALLOC_NEW before clearing EXTENT_DELALLOC +To: stable@vger.kernel.org +Cc: Qu Wenruo , Filipe Manana , David Sterba , Sasha Levin +Message-ID: <20260720165108.2526667-3-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit 95ee2231896d5f2a31760411429075a99d6045a7 ] + +[WARNING] +When running test cases with injected errors or shutdown, e.g. +generic/388 or generic/475, there is a chance that the following kernel +warning is triggered: + + BTRFS info (device dm-2): first mount of filesystem d8a19a28-3232-4809-b0df-38df83e71bff + BTRFS info (device dm-2): using crc32c checksum algorithm + BTRFS info (device dm-2): checking UUID tree + BTRFS info (device dm-2): turning on async discard + BTRFS info (device dm-2): enabling free space tree + BTRFS critical (device dm-2 state E): emergency shutdown + ------------[ cut here ]------------ + WARNING: extent_io.c:1742 at extent_writepage_io+0x437/0x520 [btrfs], CPU#2: kworker/u43:2/651591 + CPU: 2 UID: 0 PID: 651591 Comm: kworker/u43:2 Tainted: G W OE 7.0.0-rc6-custom+ #365 PREEMPT(full) 5804053f02137e627472d94b5128cc9fcb110e88 + RIP: 0010:extent_writepage_io+0x437/0x520 [btrfs] + Call Trace: + + extent_write_cache_pages+0x2a5/0x820 [btrfs 70299925d0856939e93b17d480651713b3cbba58] + btrfs_writepages+0x74/0x130 [btrfs 70299925d0856939e93b17d480651713b3cbba58] + do_writepages+0xd0/0x160 + __writeback_single_inode+0x42/0x340 + writeback_sb_inodes+0x22d/0x580 + wb_writeback+0xc6/0x360 + wb_workfn+0xbd/0x470 + process_one_work+0x198/0x3b0 + worker_thread+0x1c8/0x330 + kthread+0xee/0x120 + ret_from_fork+0x2a6/0x330 + ret_from_fork_asm+0x11/0x20 + + ---[ end trace 0000000000000000 ]--- + BTRFS error (device dm-2 state E): root 5 ino 259 folio 1323008 is marked dirty without notifying the fs + BTRFS error (device dm-2 state E): failed to submit blocks, root=5 inode=259 folio=1323008 submit_bitmap=0: -117 + BTRFS info (device dm-2 state E): last unmount of filesystem d8a19a28-3232-4809-b0df-38df83e71bff + +[CAUSE] +Inside btrfs we have the following pattern in several locations, for +example inside btrfs_dirty_folio(): + + btrfs_clear_extent_bit(&inode->io_tree, start_pos, end_of_last_block, + EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, + cached); + + ret = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block, + extra_bits, cached); + if (ret) + return ret; + +However btrfs_set_extent_delalloc() can return IO errors other than -ENOMEM +through the following callchain: + + btrfs_set_extent_delalloc() + \- btrfs_find_new_delalloc_bytes() + \- btrfs_get_extent() + \- btrfs_lookup_file_extent() + \- btrfs_search_slot() + +When such IO error happened, the previous btrfs_clear_extent_bit() has +cleared the EXTENT_DELALLOC for the range, and we're expecting +btrfs_set_extent_delalloc() to re-set EXTENT_DELALLOC. + +But since btrfs_set_extent_delalloc() failed before +btrfs_set_extent_bit(), EXTENT_DELALLOC flag is no longer present. + +And if the folio range is dirty before entering +btrfs_set_extent_delalloc(), we got a dirty folio but no EXTENT_DELALLOC +flag now. + +Then we hit the folio writeback: + + extent_writepage() + |- writepage_delalloc() + | No ordered extent is created, as there is no EXTENT_DELALLOC set + | for the folio range. + | This also means the folio has no ordered flag set. + | + |- extent_writepage_io() + \- if (unlikely(!folio_test_ordered(folio)) + Now we hit the warning. + +[FIX] +Introduce a new helper, btrfs_reset_extent_delalloc() to replace the +currently open-coded btrfs_clear_extent_bit() + +btrfs_set_extent_delalloc() combination. + +Instead of calling btrfs_clear_extent_bit() first, update +EXTENT_DELALLOC_NEW first, as that part can fail due to metadata IO, +meanwhile btrfs_clear_extent_bit() and btrfs_set_extent_bit() won't +return any error but retry memory allocation until succeeded. + +This allows us to fail early without clearing EXTENT_DELALLOC bit, so +even if that new btrfs_reset_extent_delalloc() failed before touching +EXTENT_DELALLOC, the existing dirty range will still have their old +EXTENT_DELALLOC flag present, thus avoid the warning. + +CC: stable@vger.kernel.org # 6.1+ +Reviewed-by: Filipe Manana +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/btrfs_inode.h | 2 + + fs/btrfs/file.c | 27 +++-------------------- + fs/btrfs/inode.c | 57 ++++++++++++++++++++++++++++++++++++++++++------- + fs/btrfs/reflink.c | 5 ---- + 4 files changed, 57 insertions(+), 34 deletions(-) + +--- a/fs/btrfs/btrfs_inode.h ++++ b/fs/btrfs/btrfs_inode.h +@@ -547,6 +547,8 @@ int btrfs_start_delalloc_roots(struct bt + int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, + unsigned int extra_bits, + struct extent_state **cached_state); ++int btrfs_reset_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, ++ unsigned int extra_bits, struct extent_state **cached_state); + + struct btrfs_new_inode_args { + /* Input */ +--- a/fs/btrfs/file.c ++++ b/fs/btrfs/file.c +@@ -150,16 +150,8 @@ int btrfs_dirty_pages(struct btrfs_inode + + end_of_last_block = start_pos + num_bytes - 1; + +- /* +- * The pages may have already been dirty, clear out old accounting so +- * we can set things up properly +- */ +- clear_extent_bit(&inode->io_tree, start_pos, end_of_last_block, +- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, +- cached); +- +- ret = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block, +- extra_bits, cached); ++ ret = btrfs_reset_extent_delalloc(inode, start_pos, end_of_last_block, ++ extra_bits, cached); + if (ret) + return ret; + +@@ -1997,19 +1989,8 @@ again: + } + } + +- /* +- * page_mkwrite gets called when the page is firstly dirtied after it's +- * faulted in, but write(2) could also dirty a page and set delalloc +- * bits, thus in this case for space account reason, we still need to +- * clear any delalloc bits within this page range since we have to +- * reserve data&meta space before lock_page() (see above comments). +- */ +- clear_extent_bit(&BTRFS_I(inode)->io_tree, page_start, end, +- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | +- EXTENT_DEFRAG, &cached_state); +- +- ret2 = btrfs_set_extent_delalloc(BTRFS_I(inode), page_start, end, 0, +- &cached_state); ++ ret2 = btrfs_reset_extent_delalloc(BTRFS_I(inode), page_start, end, 0, ++ &cached_state); + if (ret2) { + unlock_extent(io_tree, page_start, page_end, &cached_state); + ret = VM_FAULT_SIGBUS; +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -2799,7 +2799,11 @@ int btrfs_set_extent_delalloc(struct btr + unsigned int extra_bits, + struct extent_state **cached_state) + { +- WARN_ON(PAGE_ALIGNED(end)); ++ const u32 blocksize = inode->root->fs_info->sectorsize; ++ ++ /* Basic alignment check. */ ++ ASSERT(IS_ALIGNED(start, blocksize)); ++ ASSERT(IS_ALIGNED(end + 1, blocksize)); + + if (start >= i_size_read(&inode->vfs_inode) && + !(inode->flags & BTRFS_INODE_PREALLOC)) { +@@ -2822,6 +2826,50 @@ int btrfs_set_extent_delalloc(struct btr + EXTENT_DELALLOC | extra_bits, cached_state); + } + ++/* ++ * Clear the old accounting flags and set EXTENT_DELALLOC for the range. ++ * ++ * Return <0 for error, in that case no range has EXTENT_DELALLOC bit cleared or set. ++ */ ++int btrfs_reset_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, ++ unsigned int extra_bits, struct extent_state **cached_state) ++{ ++ const u32 blocksize = inode->root->fs_info->sectorsize; ++ ++ /* The @extra_bits can only be EXTENT_NORESERVE for now. */ ++ ASSERT(!(extra_bits & ~EXTENT_NORESERVE)); ++ ++ /* Basic alignment check. */ ++ ASSERT(IS_ALIGNED(start, blocksize)); ++ ASSERT(IS_ALIGNED(end + 1, blocksize)); ++ ++ /* ++ * Check and set DELALLOC_NEW flag, this needs to search tree thus can ++ * fail early. Thus we want to do this before clearing EXTENT_DELALLOC. ++ */ ++ if (start >= i_size_read(&inode->vfs_inode) && ++ !(inode->flags & BTRFS_INODE_PREALLOC)) { ++ /* ++ * There can't be any extents following EOF in this case so just ++ * set the delalloc new bit for the range directly. ++ */ ++ extra_bits |= EXTENT_DELALLOC_NEW; ++ } else { ++ int ret; ++ ++ ret = btrfs_find_new_delalloc_bytes(inode, start, end + 1 - start, ++ NULL); ++ if (unlikely(ret)) ++ return ret; ++ } ++ /* Clear the old accounting as the range may already be dirty. */ ++ clear_extent_bit(&inode->io_tree, start, end, ++ EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | ++ EXTENT_DEFRAG, cached_state); ++ return set_extent_bit(&inode->io_tree, start, end, ++ EXTENT_DELALLOC | extra_bits, cached_state); ++} ++ + static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, + struct btrfs_inode *inode, u64 file_pos, + struct btrfs_file_extent_item *stack_fi, +@@ -4745,12 +4793,7 @@ again: + goto again; + } + +- clear_extent_bit(&inode->io_tree, block_start, block_end, +- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, +- &cached_state); +- +- ret = btrfs_set_extent_delalloc(inode, block_start, block_end, 0, +- &cached_state); ++ ret = btrfs_reset_extent_delalloc(inode, block_start, block_end, 0, &cached_state); + if (ret) { + unlock_extent(io_tree, block_start, block_end, &cached_state); + goto out_unlock; +--- a/fs/btrfs/reflink.c ++++ b/fs/btrfs/reflink.c +@@ -95,10 +95,7 @@ static int copy_inline_to_page(struct bt + if (ret < 0) + goto out_unlock; + +- clear_extent_bit(&inode->io_tree, file_offset, range_end, +- EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, +- NULL); +- ret = btrfs_set_extent_delalloc(inode, file_offset, range_end, 0, NULL); ++ ret = btrfs_reset_extent_delalloc(inode, file_offset, range_end, 0, NULL); + if (ret) + goto out_unlock; + diff --git a/queue-6.12/btrfs-concentrate-the-error-handling-of-submit_one_sector.patch b/queue-6.12/btrfs-concentrate-the-error-handling-of-submit_one_sector.patch new file mode 100644 index 0000000000..3733d8251e --- /dev/null +++ b/queue-6.12/btrfs-concentrate-the-error-handling-of-submit_one_sector.patch @@ -0,0 +1,112 @@ +From stable+bounces-278120-greg=kroah.com@vger.kernel.org Mon Jul 20 18:07:18 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 11:56:32 -0400 +Subject: btrfs: concentrate the error handling of submit_one_sector() +To: stable@vger.kernel.org +Cc: Qu Wenruo , Boris Burkov , David Sterba , Sasha Levin +Message-ID: <20260720155635.2425597-1-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit 44820d80026e0b509007d41c83d42f1213ee8589 ] + +Currently submit_one_sector() has only one failure path from +btrfs_get_extent(). + +However the error handling is split into two parts, one inside +submit_one_sector(), which clears the dirty flag and finishes the +writeback for the fs block. + +The other part is to submit any remaining bio inside bio_ctrl and mark +the ordered extent finished for the fs block. + +There is no special reason that we must split the error handling, let's +just concentrate all the error handling into submit_one_sector(). + +Reviewed-by: Boris Burkov +Signed-off-by: Qu Wenruo +Reviewed-by: David Sterba +Signed-off-by: David Sterba +Stable-dep-of: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/extent_io.c | 46 ++++++++++++++++++++++++++++------------------ + 1 file changed, 28 insertions(+), 18 deletions(-) + +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -1417,7 +1417,7 @@ out: + + /* + * Return 0 if we have submitted or queued the sector for submission. +- * Return <0 for critical errors. ++ * Return <0 for critical errors, and the involved sector will be cleaned up. + * + * Caller should make sure filepos < i_size and handle filepos >= i_size case. + */ +@@ -1440,8 +1440,31 @@ static int submit_one_sector(struct btrf + ASSERT(filepos < i_size); + + em = btrfs_get_extent(inode, NULL, filepos, sectorsize); +- if (IS_ERR(em)) +- return PTR_ERR_OR_ZERO(em); ++ if (IS_ERR(em)) { ++ /* ++ * bio_ctrl may contain a bio crossing several folios. ++ * Submit it immediately so that the bio has a chance ++ * to finish normally, other than marked as error. ++ */ ++ submit_one_bio(bio_ctrl); ++ ++ /* ++ * When submission failed, we should still clear the folio dirty. ++ * Or the folio will be written back again but without any ++ * ordered extent. ++ */ ++ btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize); ++ btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize); ++ btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize); ++ ++ /* ++ * Since there is no bio submitted to finish the ordered ++ * extent, we have to manually finish this sector. ++ */ ++ btrfs_mark_ordered_io_finished(inode, folio, filepos, ++ fs_info->sectorsize, false); ++ return PTR_ERR(em); ++ } + + extent_offset = filepos - em->start; + em_end = extent_map_end(em); +@@ -1559,19 +1582,6 @@ static noinline_for_stack int extent_wri + } + ret = submit_one_sector(inode, folio, cur, bio_ctrl, i_size); + if (unlikely(ret < 0)) { +- /* +- * bio_ctrl may contain a bio crossing several folios. +- * Submit it immediately so that the bio has a chance +- * to finish normally, other than marked as error. +- */ +- submit_one_bio(bio_ctrl); +- /* +- * Failed to grab the extent map which should be very rare. +- * Since there is no bio submitted to finish the ordered +- * extent, we have to manually finish this sector. +- */ +- btrfs_mark_ordered_io_finished(inode, folio, cur, +- fs_info->sectorsize, false); + if (!found_error) + found_error = ret; + continue; +@@ -1587,8 +1597,8 @@ static noinline_for_stack int extent_wri + * Here we set writeback and clear for the range. If the full folio + * is no longer dirty then we clear the PAGECACHE_TAG_DIRTY tag. + * +- * If we hit any error, the corresponding sector will still be dirty +- * thus no need to clear PAGECACHE_TAG_DIRTY. ++ * If we hit any error, the corresponding sector will have its dirty ++ * flag cleared and writeback finished, thus no need to handle the error case. + */ + if (!submitted_io && !found_error) { + btrfs_folio_set_writeback(fs_info, folio, start, len); diff --git a/queue-6.12/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch b/queue-6.12/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch new file mode 100644 index 0000000000..f1e62f500c --- /dev/null +++ b/queue-6.12/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch @@ -0,0 +1,189 @@ +From stable+bounces-278124-greg=kroah.com@vger.kernel.org Mon Jul 20 18:07:25 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 11:56:35 -0400 +Subject: btrfs: fix false IO failure after falling back to buffered write +To: stable@vger.kernel.org +Cc: Qu Wenruo , Boris Burkov , David Sterba , Sasha Levin +Message-ID: <20260720155635.2425597-4-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit 66ff4d366e7eb4d31813d2acabf3af512ce03aa5 ] + +[BUG] +The test case generic/362 will fail with "nodatasum" mount option (*): + + MOUNT_OPTIONS -- -o nodatasum /dev/mapper/test-scratch1 /mnt/scratch + +# generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) +# --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930 +# +++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:21:17.574771567 +0930 +# @@ -1,2 +1,3 @@ +# QA output created by 362 +# +First write failed: Input/output error +# Silence is golden +# ... + +*: If the test case has been executed before with default data checksum, +the failure will not reproduce. Need the following fix to make it +reliably reproducible: +https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/ + +[CAUSE] +Inside __iomap_dio_rw(), the -EFAULT/-ENOTBLK error is not directly returned. +Thus we never got an error pointer from __iomap_dio_rw(). + +The call chain looks like this: + + btrfs_direct_write() + |- btrfs_dio_write() + |- __iomap_dio_rw() + | |- iomap_iter() + | | |- btrfs_dio_iomap_begin() + | | Now an ordered extent is allocated for the 4K write. + | | + | |- iomi.status = iomap_dio_iter() + | | Where iomap_dio_iter() returned -EFAULT. + | | + | |- ret = iomap_iter() + | | |- btrfs_dio_iomap_end() + | | | |- btrfs_finish_ordered_extent(uptodate = false) + | | | | |- can_finish_ordered_extent() + | | | | |- btrfs_mark_ordered_extent_error() + | | | | |- mapping_set_error() + | | | | Now the address space is marked error. + | | | | return -ENOTBLK + | | |- return -ENOTBLK + | |- if (ret == -ENOTBLK) { ret = 0; } + | Now the return value is reset to 0. + | Thus no error pointer will be returned. + | + |- ret = iomap_dio_complete() + | Since no byte is submitted, @ret is 0. + | + |- Fallback to buffered IO + | And the buffered write finished without error + | + |- filemap_fdatawait_range() + |- filemap_check_errors() + The previous error is recorded, thus an error is returned + +However the buffered write is properly submitted and finished, the error +is from the btrfs_finish_ordered_extent() call with @uptodate = false. + +[FIX] +When a short dio write happened, any range that is submitted will have +btrfs_extract_ordered_extent() to be called, thus the submitted range +will always have an OE just covering the submitted range. + +The remaining OE range is never submitted, thus they should be treated +as truncated, not an error. So that we can properly reclaim and not +insert an unnecessary file extent item, without marking the mapping as +error. + +Extract a helper, btrfs_mark_ordered_extent_truncated(), and utilize +that helper to mark the direct IO ordered extent as truncated, so it +won't cause failure for the later buffered fallback. + +[REASON FOR NO FIXES TAG] +The bug itself is pretty old, at commit f85781fb505e ("btrfs: switch to +iomap for direct IO") we're already passing @uptodate=false finishing +the OE. +But at that time OE with IOERR won't call mapping_set_error(), so it's +not exposed. +Later commit d61bec08b904 ("btrfs: mark ordered extent and inode with +error if we fail to finish") finally exposed the bug, but that commit +is doing a correct job, not the root cause. + +Anyway the bug is very old, dating back to 5.1x days, thus only CC to +stable. + +CC: stable@vger.kernel.org # 5.15+ +Reviewed-by: Boris Burkov +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/direct-io.c | 17 ++++++++++++++--- + fs/btrfs/inode.c | 6 +----- + fs/btrfs/ordered-data.c | 12 ++++++++++++ + fs/btrfs/ordered-data.h | 2 ++ + 4 files changed, 29 insertions(+), 8 deletions(-) + +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -623,12 +623,23 @@ static int btrfs_dio_iomap_end(struct in + if (submitted < length) { + pos += submitted; + length -= submitted; +- if (write) ++ if (write) { ++ /* ++ * We have a short write, if there is any range ++ * that is submitted properly, that part will have ++ * its own OE split from the original one. ++ * ++ * So for the OE at dio_data->ordered, it's the part ++ * that is not submitted, and should be marked ++ * as fully truncated. ++ */ ++ btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); + btrfs_finish_ordered_extent(dio_data->ordered, +- pos, length, false); +- else ++ pos, length, true); ++ } else { + unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, + pos + length - 1, NULL); ++ } + ret = -ENOTBLK; + } + if (write) { +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -7388,11 +7388,7 @@ static void btrfs_invalidate_folio(struc + EXTENT_LOCKED | EXTENT_DO_ACCOUNTING | + EXTENT_DEFRAG, &cached_state); + +- spin_lock_irq(&inode->ordered_tree_lock); +- set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags); +- ordered->truncated_len = min(ordered->truncated_len, +- cur - ordered->file_offset); +- spin_unlock_irq(&inode->ordered_tree_lock); ++ btrfs_mark_ordered_extent_truncated(ordered, cur - ordered->file_offset); + + /* + * If the ordered extent has finished, we're safe to delete all +--- a/fs/btrfs/ordered-data.c ++++ b/fs/btrfs/ordered-data.c +@@ -329,6 +329,18 @@ void btrfs_mark_ordered_extent_error(str + mapping_set_error(ordered->inode->vfs_inode.i_mapping, -EIO); + } + ++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered, ++ u64 truncate_len) ++{ ++ struct btrfs_inode *inode = ordered->inode; ++ ++ ASSERT(truncate_len <= ordered->num_bytes); ++ spin_lock_irq(&inode->ordered_tree_lock); ++ set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags); ++ ordered->truncated_len = min(ordered->truncated_len, truncate_len); ++ spin_unlock_irq(&inode->ordered_tree_lock); ++} ++ + static void finish_ordered_fn(struct btrfs_work *work) + { + struct btrfs_ordered_extent *ordered_extent; +--- a/fs/btrfs/ordered-data.h ++++ b/fs/btrfs/ordered-data.h +@@ -213,6 +213,8 @@ bool btrfs_try_lock_ordered_range(struct + struct btrfs_ordered_extent *btrfs_split_ordered_extent( + struct btrfs_ordered_extent *ordered, u64 len); + void btrfs_mark_ordered_extent_error(struct btrfs_ordered_extent *ordered); ++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered, ++ u64 truncate_len); + int __init ordered_data_init(void); + void __cold ordered_data_exit(void); + diff --git a/queue-6.12/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch b/queue-6.12/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch new file mode 100644 index 0000000000..2a5546691f --- /dev/null +++ b/queue-6.12/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch @@ -0,0 +1,200 @@ +From stable+bounces-278170-greg=kroah.com@vger.kernel.org Mon Jul 20 19:33:41 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 13:27:30 -0400 +Subject: btrfs: fix incorrect buffered IO fallback for append direct writes +To: stable@vger.kernel.org +Cc: Qu Wenruo , Boris Burkov , David Sterba , Sasha Levin +Message-ID: <20260720172730.2757976-5-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit ff66fe6662330226b3f486014c375538d91c44aa ] + +[BUG] +With the previous bug of short direct writes fixed, test case +generic/362 (*) still fails with the following error with nodatasum +mount option: + +# generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) +# - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) +# --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930 +# +++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:13:09.072485767 +0930 +# @@ -1,2 +1,3 @@ +# QA output created by 362 +# +Wrong file size after first write, got 8192 expected 4096 +# Silence is golden +# ... + +*: If the test case has been executed before with default data checksum, +the failure will not reproduce. Need the following fix to make it +reliably reproducible: +https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/ + +[CAUSE] +Inside btrfs_dio_iomap_begin() for a direct write, we increase the isize +if it's beyond the current isize. + +But if the direct io finished short, we do not revert the isize to the +previous value nor to the short write end. + +Then if we need to fall back to buffered writes, and the write has +IOCB_APPEND flag, then the buffered write will be positioned at the +incorrect isize. + +The call chain looks like this: + + btrfs_direct_write(pos=0, length=4K) + |- __iomap_dio_rw() + | |- iomap_iter() + | | |- btrfs_dio_iomap_begin() + | | |- btrfs_get_blocks_direct_write() + | | |- i_size_write() + | | Which updates the isize to the write end (4K). + | | + | |- iomap_dio_iter() + | | Failed with -EFAULT on the first page. + | | + | |- iomap_iter() + | | |- btrfs_dio_iomap_end() + | | Detects a short write, return -ENOTBLK + | |- if (ret == -ENOTBLK) { ret = 0;} + | Which resets the return value. + | + |- ret = iomap_dio_complet() + | Which returns 0. + | + |- btrfs_buffered_write(iocb, from); + |- generic_write_checks() + |- iocb->ki_pos = i_size_read() + Which is still the new size (4K), other than the original + isize 0. + +[FIX] +Introduce the following btrfs_dio_data members: + +- old_isize + +- updated_isize + If the direct write has enlarged the isize. + +Then if we got a short write, and btrfs_dio_data::updated_isize is set, +revert to the correct isize based on old_isize and current file +position. + +And here we call i_size_write() without holding an extent lock, which is +a very special case that we're safe to do: + + - Only a single writer can be enlarging isize + Enlarging isize will take the exclusive inode lock. + + - Buffered readers need to wait for the OE we're holding + Buffered readers will lock extent and wait for OE of the folio range. + Sometimes we can skip the OE wait, but since all page cache is + invalidated, the OE wait can not be skipped. + +But I do not think this is the most elegant solution, nor covers all +cases. E.g. if the bio is submitted but IO failed, we are unable to do +the revert. + +I believe the more elegant one would be extend the EXTENT_DIO_LOCKED +lifespan for direct writes, so that we can update the isize when a +write beyond EOF finished successfully. + +However that change is too huge for a small bug fix. +So only implement the minimal partial fix for now. + +[REASON FOR NO FIXES TAG] +The bug is again very old, before commit f85781fb505e ("btrfs: switch to +iomap for direct IO") we are already increasing isize without a +proper rollback for short writes. + +Thus only a CC to stable. + +CC: stable@vger.kernel.org # 5.15+ +Reviewed-by: Boris Burkov +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/direct-io.c | 43 ++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 42 insertions(+), 1 deletion(-) + +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -13,10 +13,12 @@ + + struct btrfs_dio_data { + ssize_t submitted; ++ loff_t old_isize; + struct extent_changeset *data_reserved; + struct btrfs_ordered_extent *ordered; + bool data_space_reserved; + bool nocow_done; ++ bool updated_isize; + }; + + struct btrfs_dio_private { +@@ -227,6 +229,7 @@ static int btrfs_get_blocks_direct_write + bool space_reserved = false; + u64 len = *lenp; + u64 prev_len; ++ loff_t old_isize; + int ret = 0; + + /* +@@ -340,8 +343,14 @@ static int btrfs_get_blocks_direct_write + * Need to update the i_size under the extent lock so buffered + * readers will get the updated i_size when we unlock. + */ +- if (start + len > i_size_read(inode)) ++ old_isize = i_size_read(inode); ++ if (start + len > old_isize) { ++ if (!dio_data->updated_isize) { ++ dio_data->old_isize = old_isize; ++ dio_data->updated_isize = true; ++ } + i_size_write(inode, start + len); ++ } + out: + if (ret && space_reserved) { + btrfs_delalloc_release_extents(BTRFS_I(inode), len); +@@ -625,6 +634,38 @@ static int btrfs_dio_iomap_end(struct in + length -= submitted; + if (write) { + /* ++ * Got a short write and have updated the isize, need to ++ * revert the isize change. ++ * ++ * Normally we need to update isize with extent lock hold, ++ * but we're safe due to the following factors: ++ * ++ * - Only a single writer can be enlarging isize ++ * Enlarging isize will take the exclusive inode lock. ++ * ++ * - Buffered readers need to wait for the OE we're holding ++ * Buffered readers will lock extent and wait for OE ++ * of the folio range, and since page cache is invalidated ++ * the OE wait can not be skipped. ++ * ++ * So here we are safe to revert the isize before ++ * finishing the OE, and no reader of the remaining range ++ * can see the enlarged size. ++ * ++ * TODO: Extend the DIO_LOCKED lifespan for direct writes, ++ * and only enlarge isize after a successful write. ++ */ ++ if (dio_data->updated_isize) { ++ u64 new_isize; ++ ++ if (submitted == 0) ++ new_isize = dio_data->old_isize; ++ else ++ new_isize = max(dio_data->old_isize, pos); ++ i_size_write(inode, new_isize); ++ dio_data->updated_isize = false; ++ } ++ /* + * We have a short write, if there is any range + * that is submitted properly, that part will have + * its own OE split from the original one. diff --git a/queue-6.12/btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch b/queue-6.12/btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch new file mode 100644 index 0000000000..e3fa89ef81 --- /dev/null +++ b/queue-6.12/btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch @@ -0,0 +1,340 @@ +From stable+bounces-278122-greg=kroah.com@vger.kernel.org Mon Jul 20 18:37:13 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 11:56:34 -0400 +Subject: btrfs: remove folio parameter from ordered io related functions +To: stable@vger.kernel.org +Cc: Qu Wenruo , Filipe Manana , David Sterba , Sasha Levin +Message-ID: <20260720155635.2425597-3-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit a11d6912fdd9e57aff889ec97256b1d6b4e5bf06 ] + +Both functions btrfs_finish_ordered_extent() and +btrfs_mark_ordered_io_finished() are accepting an optional folio +parameter. + +That @folio is passed into can_finish_ordered_extent(), which later will +test and clear the ordered flag for the involved range. + +However I do not think there is any other call site that can clear +ordered flags of an page cache folio and can affect +can_finish_ordered_extent(). + +There are limited *_clear_ordered() callers out of +can_finish_ordered_extent() function: + +- btrfs_migrate_folio() + This is completely unrelated, it's just migrating the ordered flag to + the new folio. + +- btrfs_cleanup_ordered_extents() + We manually clean the ordered flags of all involved folios, then call + btrfs_mark_ordered_io_finished() without a @folio parameter. + So it doesn't need and didn't pass a @folio parameter in the first + place. + +- btrfs_writepage_fixup_worker() + This function is going to be removed soon, and we should not hit that + function anymore. + +- btrfs_invalidate_folio() + This is the real call site we need to bother with. + + If we already have a bio running, btrfs_finish_ordered_extent() in + end_bbio_data_write() will be executed first, as + btrfs_invalidate_folio() will wait for the writeback to finish. + + Thus if there is a running bio, it will not see the range has + ordered flags, and just skip to the next range. + + If there is no bio running, meaning the ordered extent is created but + the folio is not yet submitted. + + In that case btrfs_invalidate_folio() will manually clear the folio + ordered range, but then manually finish the ordered extent with + btrfs_dec_test_ordered_pending() without bothering the folio ordered + flags. + + Meaning if the OE range with folio ordered flags will be finished + manually without the need to call can_finish_ordered_extent(). + +This means all can_finish_ordered_extent() call sites should get a range +that has folio ordered flag set, thus the old "return false" branch +should never be triggered. + +Now we can: + +- Remove the @folio parameter from involved functions + * btrfs_mark_ordered_io_finished() + * btrfs_finish_ordered_extent() + + For call sites passing a @folio into those functions, let them + manually clear the ordered flag of involved folios. + +- Move btrfs_finish_ordered_extent() out of the loop in + end_bbio_data_write() + + We only need to call btrfs_finish_ordered_extent() once per bbio, + not per folio. + +- Add an ASSERT() to make sure all folio ranges have ordered flags + It's only for end_bbio_data_write(). + + And we already have enough safe nets to catch over-accounting of ordered + extents. + +Reviewed-by: Filipe Manana +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Stable-dep-of: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/compression.c | 2 +- + fs/btrfs/direct-io.c | 9 ++++----- + fs/btrfs/extent_io.c | 23 ++++++++++++++--------- + fs/btrfs/inode.c | 6 ++++-- + fs/btrfs/ordered-data.c | 29 +++++------------------------ + fs/btrfs/ordered-data.h | 6 ++---- + 6 files changed, 30 insertions(+), 45 deletions(-) + +--- a/fs/btrfs/compression.c ++++ b/fs/btrfs/compression.c +@@ -316,7 +316,7 @@ static void btrfs_finish_compressed_writ + struct compressed_bio *cb = + container_of(work, struct compressed_bio, write_end_work); + +- btrfs_finish_ordered_extent(cb->bbio.ordered, NULL, cb->start, cb->len, ++ btrfs_finish_ordered_extent(cb->bbio.ordered, cb->start, cb->len, + cb->bbio.bio.bi_status == BLK_STS_OK); + + if (cb->writeback) +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -624,7 +624,7 @@ static int btrfs_dio_iomap_end(struct in + pos += submitted; + length -= submitted; + if (write) +- btrfs_finish_ordered_extent(dio_data->ordered, NULL, ++ btrfs_finish_ordered_extent(dio_data->ordered, + pos, length, false); + else + unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +@@ -656,9 +656,8 @@ static void btrfs_dio_end_io(struct btrf + } + + if (btrfs_op(bio) == BTRFS_MAP_WRITE) { +- btrfs_finish_ordered_extent(bbio->ordered, NULL, +- dip->file_offset, dip->bytes, +- !bio->bi_status); ++ btrfs_finish_ordered_extent(bbio->ordered, dip->file_offset, ++ dip->bytes, !bio->bi_status); + } else { + unlock_dio_extent(&inode->io_tree, dip->file_offset, + dip->file_offset + dip->bytes - 1, NULL); +@@ -736,7 +735,7 @@ static void btrfs_dio_submit_io(const st + + ret = btrfs_extract_ordered_extent(bbio, dio_data->ordered); + if (ret) { +- btrfs_finish_ordered_extent(dio_data->ordered, NULL, ++ btrfs_finish_ordered_extent(dio_data->ordered, + file_offset, dip->bytes, + !ret); + bio->bi_status = errno_to_blk_status(ret); +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -485,6 +485,7 @@ static void end_bbio_data_write(struct b + int error = blk_status_to_errno(bio->bi_status); + struct folio_iter fi; + const u32 sectorsize = fs_info->sectorsize; ++ u32 bio_size = 0; + + ASSERT(!bio_flagged(bio, BIO_CLONED)); + bio_for_each_folio_all(fi, bio) { +@@ -495,6 +496,7 @@ static void end_bbio_data_write(struct b + /* Only order 0 (single page) folios are allowed for data. */ + ASSERT(folio_order(folio) == 0); + ++ bio_size += len; + /* Our read/write should always be sector aligned. */ + if (!IS_ALIGNED(fi.offset, sectorsize)) + btrfs_err(fs_info, +@@ -505,13 +507,15 @@ static void end_bbio_data_write(struct b + "incomplete page write with offset %zu and length %zu", + fi.offset, fi.length); + +- btrfs_finish_ordered_extent(bbio->ordered, folio, start, len, +- !error); + if (error) + mapping_set_error(folio->mapping, error); ++ ++ ASSERT(btrfs_folio_test_ordered(fs_info, folio, start, len)); ++ btrfs_folio_clear_ordered(fs_info, folio, start, len); + btrfs_folio_clear_writeback(fs_info, folio, start, len); + } + ++ btrfs_finish_ordered_extent(bbio->ordered, bbio->file_offset, bio_size, !error); + bio_put(bio); + } + +@@ -1384,7 +1388,8 @@ static noinline_for_stack int writepage_ + u64 start = page_start + (start_bit << fs_info->sectorsize_bits); + u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits; + +- btrfs_mark_ordered_io_finished(inode, folio, start, len, false); ++ btrfs_folio_clear_ordered(fs_info, folio, start, len); ++ btrfs_mark_ordered_io_finished(inode, start, len, false); + } + return ret; + } +@@ -1460,6 +1465,7 @@ static int submit_one_sector(struct btrf + * ordered extent. + */ + btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize); ++ btrfs_folio_clear_ordered(fs_info, folio, filepos, sectorsize); + btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize); + btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize); + +@@ -1467,8 +1473,8 @@ static int submit_one_sector(struct btrf + * Since there is no bio submitted to finish the ordered + * extent, we have to manually finish this sector. + */ +- btrfs_mark_ordered_io_finished(inode, folio, filepos, +- fs_info->sectorsize, false); ++ btrfs_mark_ordered_io_finished(inode, filepos, fs_info->sectorsize, ++ false); + return PTR_ERR(em); + } + +@@ -1573,8 +1579,8 @@ static noinline_for_stack int extent_wri + spin_unlock_irqrestore(&inode->ordered_tree_lock, flags); + btrfs_put_ordered_extent(ordered); + +- btrfs_mark_ordered_io_finished(inode, folio, cur, +- fs_info->sectorsize, true); ++ btrfs_folio_clear_ordered(fs_info, folio, cur, fs_info->sectorsize); ++ btrfs_mark_ordered_io_finished(inode, cur, fs_info->sectorsize, true); + /* + * This range is beyond i_size, thus we don't need to + * bother writing back. +@@ -2415,8 +2421,7 @@ void extent_write_locked_range(struct in + * code is just in case, but shouldn't actually be run. + */ + if (IS_ERR(folio)) { +- btrfs_mark_ordered_io_finished(BTRFS_I(inode), NULL, +- cur, cur_len, false); ++ btrfs_mark_ordered_io_finished(BTRFS_I(inode), cur, cur_len, false); + mapping_set_error(mapping, PTR_ERR(folio)); + cur = cur_end + 1; + continue; +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -456,7 +456,7 @@ static inline void btrfs_cleanup_ordered + } + } + +- return btrfs_mark_ordered_io_finished(inode, NULL, offset, bytes, false); ++ return btrfs_mark_ordered_io_finished(inode, offset, bytes, false); + } + + static int btrfs_dirty_inode(struct btrfs_inode *inode); +@@ -2937,7 +2937,9 @@ out_page: + * to reflect the errors and clean the page. + */ + mapping_set_error(folio->mapping, ret); +- btrfs_mark_ordered_io_finished(inode, folio, page_start, ++ btrfs_folio_clear_ordered(fs_info, folio, page_start, ++ folio_size(folio)); ++ btrfs_mark_ordered_io_finished(inode, page_start, + folio_size(folio), !ret); + folio_clear_dirty_for_io(folio); + } +--- a/fs/btrfs/ordered-data.c ++++ b/fs/btrfs/ordered-data.c +@@ -338,30 +338,13 @@ static void finish_ordered_fn(struct btr + } + + static bool can_finish_ordered_extent(struct btrfs_ordered_extent *ordered, +- struct folio *folio, u64 file_offset, +- u64 len, bool uptodate) ++ u64 file_offset, u64 len, bool uptodate) + { + struct btrfs_inode *inode = ordered->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + + lockdep_assert_held(&inode->ordered_tree_lock); + +- if (folio) { +- ASSERT(folio->mapping); +- ASSERT(folio_pos(folio) <= file_offset); +- ASSERT(file_offset + len <= folio_pos(folio) + folio_size(folio)); +- +- /* +- * Ordered (Private2) bit indicates whether we still have +- * pending io unfinished for the ordered extent. +- * +- * If there's no such bit, we need to skip to next range. +- */ +- if (!btrfs_folio_test_ordered(fs_info, folio, file_offset, len)) +- return false; +- btrfs_folio_clear_ordered(fs_info, folio, file_offset, len); +- } +- + /* Now we're fine to update the accounting. */ + if (WARN_ON_ONCE(len > ordered->bytes_left)) { + btrfs_crit(fs_info, +@@ -403,8 +386,7 @@ static void btrfs_queue_ordered_fn(struc + } + + void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered, +- struct folio *folio, u64 file_offset, u64 len, +- bool uptodate) ++ u64 file_offset, u64 len, bool uptodate) + { + struct btrfs_inode *inode = ordered->inode; + unsigned long flags; +@@ -413,7 +395,7 @@ void btrfs_finish_ordered_extent(struct + trace_btrfs_finish_ordered_extent(inode, file_offset, len, uptodate); + + spin_lock_irqsave(&inode->ordered_tree_lock, flags); +- ret = can_finish_ordered_extent(ordered, folio, file_offset, len, ++ ret = can_finish_ordered_extent(ordered, file_offset, len, + uptodate); + spin_unlock_irqrestore(&inode->ordered_tree_lock, flags); + +@@ -466,8 +448,7 @@ void btrfs_finish_ordered_extent(struct + * extent(s) covering it. + */ + void btrfs_mark_ordered_io_finished(struct btrfs_inode *inode, +- struct folio *folio, u64 file_offset, +- u64 num_bytes, bool uptodate) ++ u64 file_offset, u64 num_bytes, bool uptodate) + { + struct rb_node *node; + struct btrfs_ordered_extent *entry = NULL; +@@ -530,7 +511,7 @@ void btrfs_mark_ordered_io_finished(stru + ASSERT(end + 1 - cur < U32_MAX); + len = end + 1 - cur; + +- if (can_finish_ordered_extent(entry, folio, cur, len, uptodate)) { ++ if (can_finish_ordered_extent(entry, cur, len, uptodate)) { + spin_unlock_irqrestore(&inode->ordered_tree_lock, flags); + btrfs_queue_ordered_fn(entry); + spin_lock_irqsave(&inode->ordered_tree_lock, flags); +--- a/fs/btrfs/ordered-data.h ++++ b/fs/btrfs/ordered-data.h +@@ -163,11 +163,9 @@ void btrfs_put_ordered_extent(struct btr + void btrfs_remove_ordered_extent(struct btrfs_inode *btrfs_inode, + struct btrfs_ordered_extent *entry); + void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered, +- struct folio *folio, u64 file_offset, u64 len, +- bool uptodate); ++ u64 file_offset, u64 len, bool uptodate); + void btrfs_mark_ordered_io_finished(struct btrfs_inode *inode, +- struct folio *folio, u64 file_offset, +- u64 num_bytes, bool uptodate); ++ u64 file_offset, u64 num_bytes, bool uptodate); + bool btrfs_dec_test_ordered_pending(struct btrfs_inode *inode, + struct btrfs_ordered_extent **cached, + u64 file_offset, u64 io_size); diff --git a/queue-6.12/btrfs-remove-the-cow-fixup-mechanism.patch b/queue-6.12/btrfs-remove-the-cow-fixup-mechanism.patch new file mode 100644 index 0000000000..3c1b8cfdae --- /dev/null +++ b/queue-6.12/btrfs-remove-the-cow-fixup-mechanism.patch @@ -0,0 +1,367 @@ +From stable+bounces-278143-greg=kroah.com@vger.kernel.org Mon Jul 20 19:39:12 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 12:51:07 -0400 +Subject: btrfs: remove the COW fixup mechanism +To: stable@vger.kernel.org +Cc: Qu Wenruo , David Sterba , Sasha Levin +Message-ID: <20260720165108.2526667-2-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit b2a9f217ad3fa8012940744059956b20a3971135 ] + +[BACKGROUND] +Btrfs has a special mechanism called COW fixup, which detects dirty +pages without an ordered extent (folio ordered flag). + +Normally a dirty folio must go through delayed allocation (delalloc) +before it can be submitted, and delalloc will create an ordered extent +for it and mark the range with ordered flag. + +However in older kernels, there are bugs related to get_user_pages() +which can lead to some page marked dirty but without notifying the fs to +properly prepare them for writeback. + +In that case without an ordered extent btrfs is unable to properly +submit such dirty folios, thus the COW fixup mechanism is introduced, +which do the extra space reservation so that they can be written back +properly. + +[MODERN SOLUTIONS] +The MM layer has solved it properly now with the introduction of +pin_user_pages*(), so we're handling cases that are no longer valid. + +So commit 7ca3e84980ef ("btrfs: reject out-of-band dirty folios during +writeback") is introduced to change the behavior from going through +COW fixup to rejecting them directly for experimental builds. + +So far it works fine, but when errors are injected into the IO path, we +have random failures triggering the new warnings. + +It looks like we have error path that cleared the ordered flag but +leaves the folio dirty flag, which later triggers the warning. + +[REMOVAL OF COW FIXUP] +Although I hope to fix all those known warnings cases, I just can not +figure out the root cause yet. + +But on the other hand, if we remove the ordered and checked flags in the +future, and purely rely on the dirty flags and ordered extent search, we +can get a much cleaner handling. + +Considering it's no longer hitting the COW fixup for normal IO paths, I +think it's finally the time to remove the COW fixup completely. + +Furthermore, the function name "btrfs_writepage_cow_fixup()" is no +longer meaningful, and since it's pretty small, only a folio flag check +with error message, there is no need to put it as a dedicated helper, +just open code it inside extent_writepage_io(). + +Signed-off-by: Qu Wenruo +Reviewed-by: David Sterba +Signed-off-by: David Sterba +Stable-dep-of: 95ee2231896d ("btrfs: check and set EXTENT_DELALLOC_NEW before clearing EXTENT_DELALLOC") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/btrfs_inode.h | 1 + fs/btrfs/disk-io.c | 16 ---- + fs/btrfs/extent_io.c | 17 ++-- + fs/btrfs/fs.h | 7 - + fs/btrfs/inode.c | 187 ------------------------------------------------- + 5 files changed, 12 insertions(+), 216 deletions(-) + +--- a/fs/btrfs/btrfs_inode.h ++++ b/fs/btrfs/btrfs_inode.h +@@ -609,7 +609,6 @@ int btrfs_prealloc_file_range_trans(stru + loff_t actual_len, u64 *alloc_hint); + int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct folio *locked_folio, + u64 start, u64 end, struct writeback_control *wbc); +-int btrfs_writepage_cow_fixup(struct folio *folio); + int btrfs_encoded_io_compression_from_extent(struct btrfs_fs_info *fs_info, + int compress_type); + int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, +--- a/fs/btrfs/disk-io.c ++++ b/fs/btrfs/disk-io.c +@@ -1747,7 +1747,6 @@ static int read_backup_root(struct btrfs + /* helper to cleanup workers */ + static void btrfs_stop_all_workers(struct btrfs_fs_info *fs_info) + { +- btrfs_destroy_workqueue(fs_info->fixup_workers); + btrfs_destroy_workqueue(fs_info->delalloc_workers); + btrfs_destroy_workqueue(fs_info->workers); + if (fs_info->endio_workers) +@@ -1954,9 +1953,6 @@ static int btrfs_init_workqueues(struct + fs_info->caching_workers = + btrfs_alloc_workqueue(fs_info, "cache", flags, max_active, 0); + +- fs_info->fixup_workers = +- btrfs_alloc_ordered_workqueue(fs_info, "fixup", ordered_flags); +- + fs_info->endio_workers = + alloc_workqueue("btrfs-endio", flags, max_active); + fs_info->endio_meta_workers = +@@ -1985,7 +1981,7 @@ static int btrfs_init_workqueues(struct + fs_info->compressed_write_workers && + fs_info->endio_write_workers && + fs_info->endio_freespace_worker && fs_info->rmw_workers && +- fs_info->caching_workers && fs_info->fixup_workers && ++ fs_info->caching_workers && + fs_info->delayed_workers && fs_info->qgroup_rescan_workers && + fs_info->discard_ctl.discard_workers)) { + return -ENOMEM; +@@ -4246,16 +4242,6 @@ void __cold close_ctree(struct btrfs_fs_ + btrfs_error_commit_super(fs_info); + + /* +- * Wait for any fixup workers to complete. +- * If we don't wait for them here and they are still running by the time +- * we call kthread_stop() against the cleaner kthread further below, we +- * get an use-after-free on the cleaner because the fixup worker adds an +- * inode to the list of delayed iputs and then attempts to wakeup the +- * cleaner kthread, which was already stopped and destroyed. We parked +- * already the cleaner, but below we run all pending delayed iputs. +- */ +- btrfs_flush_workqueue(fs_info->fixup_workers); +- /* + * Similar case here, we have to wait for delalloc workers before we + * proceed below and stop the cleaner kthread, otherwise we trigger a + * use-after-tree on the cleaner kthread task_struct when a delalloc +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -1543,12 +1543,17 @@ static noinline_for_stack int extent_wri + + ASSERT(start >= folio_start && end <= folio_end); + +- ret = btrfs_writepage_cow_fixup(folio); +- if (ret) { +- /* Fixup worker will requeue */ +- folio_redirty_for_writepage(bio_ctrl->wbc, folio); +- folio_unlock(folio); +- return 1; ++ if (unlikely(!folio_test_ordered(folio))) { ++ WARN_ON(IS_ENABLED(CONFIG_BTRFS_DEBUG)); ++ btrfs_err_rl(fs_info, ++ "root %lld ino %llu folio %llu is marked dirty without notifying the fs", ++ btrfs_root_id(inode->root), ++ btrfs_ino(inode), ++ folio_pos(folio)); ++ btrfs_folio_clear_dirty(fs_info, folio, start, len); ++ btrfs_folio_set_writeback(fs_info, folio, start, len); ++ btrfs_folio_clear_writeback(fs_info, folio, start, len); ++ return -EUCLEAN; + } + + bitmap_set(&range_bitmap, (start - folio_pos(folio)) >> fs_info->sectorsize_bits, +--- a/fs/btrfs/fs.h ++++ b/fs/btrfs/fs.h +@@ -620,13 +620,6 @@ struct btrfs_fs_info { + struct btrfs_workqueue *endio_write_workers; + struct btrfs_workqueue *endio_freespace_worker; + struct btrfs_workqueue *caching_workers; +- +- /* +- * Fixup workers take dirty pages that didn't properly go through the +- * cow mechanism and make them safe to write. It happens for the +- * sys_munmap function call path. +- */ +- struct btrfs_workqueue *fixup_workers; + struct btrfs_workqueue *delayed_workers; + + struct task_struct *transaction_kthread; +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -2822,193 +2822,6 @@ int btrfs_set_extent_delalloc(struct btr + EXTENT_DELALLOC | extra_bits, cached_state); + } + +-/* see btrfs_writepage_start_hook for details on why this is required */ +-struct btrfs_writepage_fixup { +- struct folio *folio; +- struct btrfs_inode *inode; +- struct btrfs_work work; +-}; +- +-static void btrfs_writepage_fixup_worker(struct btrfs_work *work) +-{ +- struct btrfs_writepage_fixup *fixup = +- container_of(work, struct btrfs_writepage_fixup, work); +- struct btrfs_ordered_extent *ordered; +- struct extent_state *cached_state = NULL; +- struct extent_changeset *data_reserved = NULL; +- struct folio *folio = fixup->folio; +- struct btrfs_inode *inode = fixup->inode; +- struct btrfs_fs_info *fs_info = inode->root->fs_info; +- u64 page_start = folio_pos(folio); +- u64 page_end = folio_pos(folio) + folio_size(folio) - 1; +- int ret = 0; +- bool free_delalloc_space = true; +- +- /* +- * This is similar to page_mkwrite, we need to reserve the space before +- * we take the folio lock. +- */ +- ret = btrfs_delalloc_reserve_space(inode, &data_reserved, page_start, +- folio_size(folio)); +-again: +- folio_lock(folio); +- +- /* +- * Before we queued this fixup, we took a reference on the folio. +- * folio->mapping may go NULL, but it shouldn't be moved to a different +- * address space. +- */ +- if (!folio->mapping || !folio_test_dirty(folio) || +- !folio_test_checked(folio)) { +- /* +- * Unfortunately this is a little tricky, either +- * +- * 1) We got here and our folio had already been dealt with and +- * we reserved our space, thus ret == 0, so we need to just +- * drop our space reservation and bail. This can happen the +- * first time we come into the fixup worker, or could happen +- * while waiting for the ordered extent. +- * 2) Our folio was already dealt with, but we happened to get an +- * ENOSPC above from the btrfs_delalloc_reserve_space. In +- * this case we obviously don't have anything to release, but +- * because the folio was already dealt with we don't want to +- * mark the folio with an error, so make sure we're resetting +- * ret to 0. This is why we have this check _before_ the ret +- * check, because we do not want to have a surprise ENOSPC +- * when the folio was already properly dealt with. +- */ +- if (!ret) { +- btrfs_delalloc_release_extents(inode, folio_size(folio)); +- btrfs_delalloc_release_space(inode, data_reserved, +- page_start, folio_size(folio), +- true); +- } +- ret = 0; +- goto out_page; +- } +- +- /* +- * We can't mess with the folio state unless it is locked, so now that +- * it is locked bail if we failed to make our space reservation. +- */ +- if (ret) +- goto out_page; +- +- lock_extent(&inode->io_tree, page_start, page_end, &cached_state); +- +- /* already ordered? We're done */ +- if (folio_test_ordered(folio)) +- goto out_reserved; +- +- ordered = btrfs_lookup_ordered_range(inode, page_start, PAGE_SIZE); +- if (ordered) { +- unlock_extent(&inode->io_tree, page_start, page_end, +- &cached_state); +- folio_unlock(folio); +- btrfs_start_ordered_extent(ordered); +- btrfs_put_ordered_extent(ordered); +- goto again; +- } +- +- ret = btrfs_set_extent_delalloc(inode, page_start, page_end, 0, +- &cached_state); +- if (ret) +- goto out_reserved; +- +- /* +- * Everything went as planned, we're now the owner of a dirty page with +- * delayed allocation bits set and space reserved for our COW +- * destination. +- * +- * The page was dirty when we started, nothing should have cleaned it. +- */ +- BUG_ON(!folio_test_dirty(folio)); +- free_delalloc_space = false; +-out_reserved: +- btrfs_delalloc_release_extents(inode, PAGE_SIZE); +- if (free_delalloc_space) +- btrfs_delalloc_release_space(inode, data_reserved, page_start, +- PAGE_SIZE, true); +- unlock_extent(&inode->io_tree, page_start, page_end, &cached_state); +-out_page: +- if (ret) { +- /* +- * We hit ENOSPC or other errors. Update the mapping and page +- * to reflect the errors and clean the page. +- */ +- mapping_set_error(folio->mapping, ret); +- btrfs_folio_clear_ordered(fs_info, folio, page_start, +- folio_size(folio)); +- btrfs_mark_ordered_io_finished(inode, page_start, +- folio_size(folio), !ret); +- folio_clear_dirty_for_io(folio); +- } +- btrfs_folio_clear_checked(fs_info, folio, page_start, PAGE_SIZE); +- folio_unlock(folio); +- folio_put(folio); +- kfree(fixup); +- extent_changeset_free(data_reserved); +- /* +- * As a precaution, do a delayed iput in case it would be the last iput +- * that could need flushing space. Recursing back to fixup worker would +- * deadlock. +- */ +- btrfs_add_delayed_iput(inode); +-} +- +-/* +- * There are a few paths in the higher layers of the kernel that directly +- * set the folio dirty bit without asking the filesystem if it is a +- * good idea. This causes problems because we want to make sure COW +- * properly happens and the data=ordered rules are followed. +- * +- * In our case any range that doesn't have the ORDERED bit set +- * hasn't been properly setup for IO. We kick off an async process +- * to fix it up. The async helper will wait for ordered extents, set +- * the delalloc bit and make it safe to write the folio. +- */ +-int btrfs_writepage_cow_fixup(struct folio *folio) +-{ +- struct inode *inode = folio->mapping->host; +- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); +- struct btrfs_writepage_fixup *fixup; +- +- /* This folio has ordered extent covering it already */ +- if (folio_test_ordered(folio)) +- return 0; +- +- /* +- * folio_checked is set below when we create a fixup worker for this +- * folio, don't try to create another one if we're already +- * folio_test_checked. +- * +- * The extent_io writepage code will redirty the foio if we send back +- * EAGAIN. +- */ +- if (folio_test_checked(folio)) +- return -EAGAIN; +- +- fixup = kzalloc(sizeof(*fixup), GFP_NOFS); +- if (!fixup) +- return -EAGAIN; +- +- /* +- * We are already holding a reference to this inode from +- * write_cache_pages. We need to hold it because the space reservation +- * takes place outside of the folio lock, and we can't trust +- * page->mapping outside of the folio lock. +- */ +- ihold(inode); +- btrfs_folio_set_checked(fs_info, folio, folio_pos(folio), folio_size(folio)); +- folio_get(folio); +- btrfs_init_work(&fixup->work, btrfs_writepage_fixup_worker, NULL); +- fixup->folio = folio; +- fixup->inode = BTRFS_I(inode); +- btrfs_queue_work(fs_info->fixup_workers, &fixup->work); +- +- return -EAGAIN; +-} +- + static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, + struct btrfs_inode *inode, u64 file_pos, + struct btrfs_file_extent_item *stack_fi, diff --git a/queue-6.12/btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch b/queue-6.12/btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch new file mode 100644 index 0000000000..82a03f953d --- /dev/null +++ b/queue-6.12/btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch @@ -0,0 +1,102 @@ +From stable+bounces-278121-greg=kroah.com@vger.kernel.org Mon Jul 20 18:07:18 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 11:56:33 -0400 +Subject: btrfs: replace for_each_set_bit() with for_each_set_bitmap() +To: stable@vger.kernel.org +Cc: Qu Wenruo , Boris Burkov , David Sterba , Sasha Levin +Message-ID: <20260720155635.2425597-2-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit e6698b34fab33867ef3faeeea6feb165f31aae24 ] + +Inside extent_io.c, there are several simple call sites doing things +like: + + for_each_set_bit(bit, bitmap, bitmap_size) { + /* handle one fs block */ + } + +The workload includes: + +- set_bit() + Inside extent_writepage_io(). + + This can be replaced with a bitmap_set(). + +- btrfs_folio_set_lock() +- btrfs_mark_ordered_io_finished() + Inside writepage_delalloc(). + + Instead of calling it multiple times, we can pass a range into the + function with one call. + +Reviewed-by: Boris Burkov +Signed-off-by: Qu Wenruo +Reviewed-by: David Sterba +Signed-off-by: David Sterba +Stable-dep-of: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/extent_io.c | 26 ++++++++++++++++---------- + 1 file changed, 16 insertions(+), 10 deletions(-) + +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -1245,8 +1245,9 @@ static noinline_for_stack int writepage_ + u64 delalloc_start = page_start; + u64 delalloc_end = page_end; + u64 delalloc_to_write = 0; ++ unsigned int start_bit; ++ unsigned int end_bit; + int ret = 0; +- int bit; + + /* Save the dirty bitmap as our submission bitmap will be a subset of it. */ + if (btrfs_is_subpage(fs_info, inode->vfs_inode.i_mapping)) { +@@ -1256,10 +1257,12 @@ static noinline_for_stack int writepage_ + bio_ctrl->submit_bitmap = 1; + } + +- for_each_set_bit(bit, &bio_ctrl->submit_bitmap, blocks_per_folio) { +- u64 start = page_start + (bit << fs_info->sectorsize_bits); ++ for_each_set_bitrange(start_bit, end_bit, &bio_ctrl->submit_bitmap, ++ blocks_per_folio) { ++ u64 start = page_start + (start_bit << fs_info->sectorsize_bits); ++ u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits; + +- btrfs_folio_set_lock(fs_info, folio, start, fs_info->sectorsize); ++ btrfs_folio_set_lock(fs_info, folio, start, len); + } + + /* Lock all (subpage) delalloc ranges inside the folio first. */ +@@ -1376,10 +1379,13 @@ static noinline_for_stack int writepage_ + fs_info->sectorsize_bits, + blocks_per_folio); + +- for_each_set_bit(bit, &bio_ctrl->submit_bitmap, bitmap_size) +- btrfs_mark_ordered_io_finished(inode, folio, +- page_start + (bit << fs_info->sectorsize_bits), +- fs_info->sectorsize, false); ++ for_each_set_bitrange(start_bit, end_bit, &bio_ctrl->submit_bitmap, ++ bitmap_size) { ++ u64 start = page_start + (start_bit << fs_info->sectorsize_bits); ++ u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits; ++ ++ btrfs_mark_ordered_io_finished(inode, folio, start, len, false); ++ } + return ret; + } + out: +@@ -1539,8 +1545,8 @@ static noinline_for_stack int extent_wri + return 1; + } + +- for (cur = start; cur < end; cur += fs_info->sectorsize) +- set_bit((cur - folio_start) >> fs_info->sectorsize_bits, &range_bitmap); ++ bitmap_set(&range_bitmap, (start - folio_pos(folio)) >> fs_info->sectorsize_bits, ++ len >> fs_info->sectorsize_bits); + bitmap_and(&bio_ctrl->submit_bitmap, &bio_ctrl->submit_bitmap, &range_bitmap, + blocks_per_folio); + diff --git a/queue-6.12/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch b/queue-6.12/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch new file mode 100644 index 0000000000..9179dc4239 --- /dev/null +++ b/queue-6.12/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch @@ -0,0 +1,53 @@ +From stable+bounces-277639-greg=kroah.com@vger.kernel.org Mon Jul 20 13:15:02 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 06:54:41 -0400 +Subject: crypto: atmel - Drop explicit initialization of struct i2c_device_id::driver_data to 0 +To: stable@vger.kernel.org +Cc: "Uwe Kleine-König" , "Herbert Xu" , "Sasha Levin" +Message-ID: <20260720105442.215749-1-sashal@kernel.org> + +From: Uwe Kleine-König + +[ Upstream commit d86ad3911a5d4549297ed810ee450e5772fd665f ] + +These drivers don't use the driver_data member of struct i2c_device_id, +so don't explicitly initialize this member. + +This prepares putting driver_data in an anonymous union which requires +either no initialization or named designators. But it's also a nice +cleanup on its own. + +Signed-off-by: Uwe Kleine-König +Signed-off-by: Herbert Xu +Stable-dep-of: ea5e57cc9718 ("crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/atmel-ecc.c | 2 +- + drivers/crypto/atmel-sha204a.c | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/crypto/atmel-ecc.c ++++ b/drivers/crypto/atmel-ecc.c +@@ -380,7 +380,7 @@ MODULE_DEVICE_TABLE(of, atmel_ecc_dt_ids + #endif + + static const struct i2c_device_id atmel_ecc_id[] = { +- { "atecc508a", 0 }, ++ { "atecc508a" }, + { } + }; + MODULE_DEVICE_TABLE(i2c, atmel_ecc_id); +--- a/drivers/crypto/atmel-sha204a.c ++++ b/drivers/crypto/atmel-sha204a.c +@@ -214,8 +214,8 @@ static const struct of_device_id atmel_s + MODULE_DEVICE_TABLE(of, atmel_sha204a_dt_ids); + + static const struct i2c_device_id atmel_sha204a_id[] = { +- { "atsha204", 0 }, +- { "atsha204a", 0 }, ++ { "atsha204" }, ++ { "atsha204a" }, + { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(i2c, atmel_sha204a_id); diff --git a/queue-6.12/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch b/queue-6.12/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch new file mode 100644 index 0000000000..4f3c601138 --- /dev/null +++ b/queue-6.12/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch @@ -0,0 +1,101 @@ +From stable+bounces-277640-greg=kroah.com@vger.kernel.org Mon Jul 20 13:15:03 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 06:54:42 -0400 +Subject: crypto: atmel-sha204a - drop hwrng quality reduction for ATSHA204A +To: stable@vger.kernel.org +Cc: Thorsten Blum , Ard Biesheuvel , Herbert Xu , Sasha Levin +Message-ID: <20260720105442.215749-2-sashal@kernel.org> + +From: Thorsten Blum + +[ Upstream commit ea5e57cc97185329dcc5ebdcaae7e1500bf0ad0b ] + +Commit 8006aff15516 ("crypto: atmel-sha204a - Set hwrng quality to +lowest possible") reduced the hwrng quality to 1 based on a review by +Bill Cox [1]. However, despite its title, the review only tested the +ATSHA204, not the ATSHA204A. + +In the same thread, Atmel engineer Landon Cox wrote "this behavior has +been eliminated entirely"[2] in the ATSHA204A and "this problem does not +affect the ATECC108 or the ATECC108A (or the ATSHA204A)"[3]. + +According to the official ATSHA204A datasheet [4], the device contains a +high-quality hardware RNG that combines its output with an internal seed +value stored in EEPROM or SRAM to generate random numbers. The device +also implements all security functions using SHA-256, and the driver +uses the chip's Random command in seed-update mode. + +Keep 'quality = 1' for ATSHA204, but drop the explicit hwrng quality +reduction for ATSHA204A and fall back to the hwrng core default. + +[1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html +[2] https://www.metzdowd.com/pipermail/cryptography/2014-December/023852.html +[3] https://www.metzdowd.com/pipermail/cryptography/2014-December/023886.html +[4] https://ww1.microchip.com/downloads/en/DeviceDoc/ATSHA204A-Data-Sheet-40002025A.pdf + +Fixes: 8006aff15516 ("crypto: atmel-sha204a - Set hwrng quality to lowest possible") +Cc: stable@vger.kernel.org +Signed-off-by: Thorsten Blum +Reviewed-by: Ard Biesheuvel +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/atmel-sha204a.c | 19 ++++++++++++------- + 1 file changed, 12 insertions(+), 7 deletions(-) + +--- a/drivers/crypto/atmel-sha204a.c ++++ b/drivers/crypto/atmel-sha204a.c +@@ -19,6 +19,12 @@ + #include + #include "atmel-i2c.h" + ++/* ++ * According to review by Bill Cox [1], the ATSHA204 has very low entropy. ++ * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html ++ */ ++static const unsigned short atsha204_quality = 1; ++ + static void atmel_sha204a_rng_done(struct atmel_i2c_work_data *work_data, + void *areq, int status) + { +@@ -158,6 +164,7 @@ static const struct attribute_group atme + static int atmel_sha204a_probe(struct i2c_client *client) + { + struct atmel_i2c_client_priv *i2c_priv; ++ const unsigned short *quality; + int ret; + + ret = atmel_i2c_probe(client); +@@ -171,11 +178,9 @@ static int atmel_sha204a_probe(struct i2 + i2c_priv->hwrng.name = dev_name(&client->dev); + i2c_priv->hwrng.read = atmel_sha204a_rng_read; + +- /* +- * According to review by Bill Cox [1], this HWRNG has very low entropy. +- * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html +- */ +- i2c_priv->hwrng.quality = 1; ++ quality = i2c_get_match_data(client); ++ if (quality) ++ i2c_priv->hwrng.quality = *quality; + + ret = devm_hwrng_register(&client->dev, &i2c_priv->hwrng); + if (ret) +@@ -207,14 +212,14 @@ static void atmel_sha204a_remove(struct + } + + static const struct of_device_id atmel_sha204a_dt_ids[] __maybe_unused = { +- { .compatible = "atmel,atsha204", }, ++ { .compatible = "atmel,atsha204", .data = &atsha204_quality }, + { .compatible = "atmel,atsha204a", }, + { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(of, atmel_sha204a_dt_ids); + + static const struct i2c_device_id atmel_sha204a_id[] = { +- { "atsha204" }, ++ { "atsha204", (kernel_ulong_t)&atsha204_quality }, + { "atsha204a" }, + { /* sentinel */ } + }; diff --git a/queue-6.12/crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch b/queue-6.12/crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch new file mode 100644 index 0000000000..f6d6f82ad9 --- /dev/null +++ b/queue-6.12/crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch @@ -0,0 +1,55 @@ +From stable+bounces-277813-greg=kroah.com@vger.kernel.org Mon Jul 20 16:02:10 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 09:51:06 -0400 +Subject: crypto: atmel-sha204a - fail on hwrng registration error in probe path +To: stable@vger.kernel.org +Cc: Thorsten Blum , Herbert Xu , Sasha Levin +Message-ID: <20260720135106.1306281-1-sashal@kernel.org> + +From: Thorsten Blum + +[ Upstream commit 49e05bb00f2e8168695f7af4d694c39e1423e8a2 ] + +Commit 13909a0c8897 ("crypto: atmel-sha204a - provide the otp content") +overwrote the hwrng registration return value when creating the sysfs +group, which allowed atmel_sha204a_probe() to succeed even if +devm_hwrng_register() failed. + +Return immediately when devm_hwrng_register() fails, and report both +hwrng and sysfs registration errors with dev_err(). Adjust the sysfs +error log message for consistency. + +Fixes: 13909a0c8897 ("crypto: atmel-sha204a - provide the otp content") +Cc: stable@vger.kernel.org +Signed-off-by: Thorsten Blum +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/atmel-sha204a.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +--- a/drivers/crypto/atmel-sha204a.c ++++ b/drivers/crypto/atmel-sha204a.c +@@ -183,8 +183,10 @@ static int atmel_sha204a_probe(struct i2 + i2c_priv->hwrng.quality = *quality; + + ret = devm_hwrng_register(&client->dev, &i2c_priv->hwrng); +- if (ret) +- dev_warn(&client->dev, "failed to register RNG (%d)\n", ret); ++ if (ret) { ++ dev_err(&client->dev, "failed to register RNG (%d)\n", ret); ++ return ret; ++ } + + /* otp read out */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) +@@ -192,7 +194,7 @@ static int atmel_sha204a_probe(struct i2 + + ret = sysfs_create_group(&client->dev.kobj, &atmel_sha204a_groups); + if (ret) { +- dev_err(&client->dev, "failed to register sysfs entry\n"); ++ dev_err(&client->dev, "failed to create sysfs group (%d)\n", ret); + return ret; + } + diff --git a/queue-6.12/crypto-ccp-do-not-initialize-snp-for-ioctl-snp_config.patch b/queue-6.12/crypto-ccp-do-not-initialize-snp-for-ioctl-snp_config.patch new file mode 100644 index 0000000000..1b10aa7d99 --- /dev/null +++ b/queue-6.12/crypto-ccp-do-not-initialize-snp-for-ioctl-snp_config.patch @@ -0,0 +1,107 @@ +From stable+bounces-278164-greg=kroah.com@vger.kernel.org Mon Jul 20 19:45:04 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 13:15:35 -0400 +Subject: crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG) +To: stable@vger.kernel.org +Cc: "Tycho Andersen (AMD)" , Tom Lendacky , Herbert Xu , Sasha Levin +Message-ID: <20260720171535.2695659-6-sashal@kernel.org> + +From: "Tycho Andersen (AMD)" + +[ Upstream commit 08f0e65e784c4b20e6e620dd4f68d8636073a3d2 ] + +Sashiko notes: + +> if SEV initialization fails and KVM is actively running normal VMs, could a +> userspace process trigger this code path via /dev/sev ioctls (e.g., +> SEV_PDH_GEN) and zero out MSR_VM_HSAVE_PA globally? Would the next VMRUN +> execution for an active VM trigger a general protection fault and crash the +> host? + +Refuse to re-try initialization if SNP is not already initialized for +SNP_CONFIG. + +This is technically an ABI break: before if SNP initialization failed it +could be transparently retriggered by this ioctl, and if no VMs were +running, everything worked fine. Hopefully this is enough of a corner case +that nobody will notice, but someone does, there are a few options: + +* do something like symbol_get() for kvm and refuse to initialize if KVM is + loaded +* check each cpu's HSAVE_PA for non-zero data before re-initializing +* once initialization has failed, continue to refuse to initialize until + the ccp module is unloaded + +Fixes: ceac7fb89e8d ("crypto: ccp - Ensure implicit SEV/SNP init and shutdown in ioctls") +Reported-by: Sashiko +Assisted-by: Gemini:gemini-3.1-pro-preview +Link: https://sashiko.dev/#/patchset/20260324161301.1353976-1-tycho%40kernel.org +CC: +Signed-off-by: Tycho Andersen (AMD) +Reviewed-by: Tom Lendacky +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/ccp/sev-dev.c | 33 ++++----------------------------- + 1 file changed, 4 insertions(+), 29 deletions(-) + +--- a/drivers/crypto/ccp/sev-dev.c ++++ b/drivers/crypto/ccp/sev-dev.c +@@ -1434,21 +1434,6 @@ static int sev_move_to_init_state(struct + return 0; + } + +-static int snp_move_to_init_state(struct sev_issue_cmd *argp, bool *shutdown_required) +-{ +- int error, rc; +- +- rc = __sev_snp_init_locked(&error); +- if (rc) { +- argp->error = SEV_RET_INVALID_PLATFORM_STATE; +- return rc; +- } +- +- *shutdown_required = true; +- +- return 0; +-} +- + static int sev_ioctl_do_reset(struct sev_issue_cmd *argp, bool writable) + { + int state, rc; +@@ -2143,8 +2128,6 @@ static int sev_ioctl_do_snp_set_config(s + { + struct sev_device *sev = psp_master->sev_data; + struct sev_user_data_snp_config config; +- bool shutdown_required = false; +- int ret, error; + + if (!argp->data) + return -EINVAL; +@@ -2152,21 +2135,13 @@ static int sev_ioctl_do_snp_set_config(s + if (!writable) + return -EPERM; + ++ if (!sev->snp_initialized) ++ return -ENODEV; ++ + if (copy_from_user(&config, (void __user *)argp->data, sizeof(config))) + return -EFAULT; + +- if (!sev->snp_initialized) { +- ret = snp_move_to_init_state(argp, &shutdown_required); +- if (ret) +- return ret; +- } +- +- ret = __sev_do_cmd_locked(SEV_CMD_SNP_CONFIG, &config, &argp->error); +- +- if (shutdown_required) +- __sev_snp_shutdown_locked(&error, false); +- +- return ret; ++ return __sev_do_cmd_locked(SEV_CMD_SNP_CONFIG, &config, &argp->error); + } + + static int sev_ioctl_do_snp_vlek_load(struct sev_issue_cmd *argp, bool writable) diff --git a/queue-6.12/crypto-ccp-fix-a-case-where-snp_shutdown-is-missed.patch b/queue-6.12/crypto-ccp-fix-a-case-where-snp_shutdown-is-missed.patch new file mode 100644 index 0000000000..c3f5a69ecc --- /dev/null +++ b/queue-6.12/crypto-ccp-fix-a-case-where-snp_shutdown-is-missed.patch @@ -0,0 +1,110 @@ +From stable+bounces-278163-greg=kroah.com@vger.kernel.org Mon Jul 20 19:16:23 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 13:15:34 -0400 +Subject: crypto: ccp - Fix a case where SNP_SHUTDOWN is missed +To: stable@vger.kernel.org +Cc: Tom Lendacky , "Tycho Andersen (AMD)" , Alexey Kardashevskiy , Herbert Xu , Sasha Levin +Message-ID: <20260720171535.2695659-5-sashal@kernel.org> + +From: Tom Lendacky + +[ Upstream commit 551120148b67e04527b405c5ec33a31593846ba4 ] + +If page reclaim fails in sev_ioctl_do_snp_platform_status() and SNP was +moved from UNINIT to INIT for the function, SNP is not moved back to +UNINIT state. Additionally, SNP is not required to be initialized in order +to execute the SNP_PLATFORM_STATUS command, so don't attempt to move to +INIT state and let SNP_PLATFORM_STATUS report the status as is. + +Fixes: ceac7fb89e8d ("crypto: ccp - Ensure implicit SEV/SNP init and shutdown in ioctls") +Signed-off-by: Tom Lendacky +Reviewed-by: Tycho Andersen (AMD) +Reviewed-by: Alexey Kardashevskiy +Signed-off-by: Tycho Andersen (AMD) +Signed-off-by: Herbert Xu +Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/ccp/sev-dev.c | 46 +++++++++++++++++++++---------------------- + 1 file changed, 23 insertions(+), 23 deletions(-) + +--- a/drivers/crypto/ccp/sev-dev.c ++++ b/drivers/crypto/ccp/sev-dev.c +@@ -2071,11 +2071,10 @@ e_free_pdh: + static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp) + { + struct sev_device *sev = psp_master->sev_data; +- bool shutdown_required = false; + struct sev_data_snp_addr buf; + struct page *status_page; +- int ret, error; + void *data; ++ int ret; + + if (!argp->data) + return -EINVAL; +@@ -2086,31 +2085,35 @@ static int sev_ioctl_do_snp_platform_sta + + data = page_address(status_page); + +- if (!sev->snp_initialized) { +- ret = snp_move_to_init_state(argp, &shutdown_required); +- if (ret) +- goto cleanup; +- } +- + /* +- * Firmware expects status page to be in firmware-owned state, otherwise +- * it will report firmware error code INVALID_PAGE_STATE (0x1A). ++ * SNP_PLATFORM_STATUS can be executed in any SNP state. But if executed ++ * when SNP has been initialized, the status page must be firmware-owned. + */ +- if (rmp_mark_pages_firmware(__pa(data), 1, true)) { +- ret = -EFAULT; +- goto cleanup; ++ if (sev->snp_initialized) { ++ /* ++ * Firmware expects the status page to be in Firmware state, ++ * otherwise it will report an error INVALID_PAGE_STATE. ++ */ ++ if (rmp_mark_pages_firmware(__pa(data), 1, true)) { ++ ret = -EFAULT; ++ goto cleanup; ++ } + } + + buf.address = __psp_pa(data); + ret = __sev_do_cmd_locked(SEV_CMD_SNP_PLATFORM_STATUS, &buf, &argp->error); + +- /* +- * Status page will be transitioned to Reclaim state upon success, or +- * left in Firmware state in failure. Use snp_reclaim_pages() to +- * transition either case back to Hypervisor-owned state. +- */ +- if (snp_reclaim_pages(__pa(data), 1, true)) +- return -EFAULT; ++ if (sev->snp_initialized) { ++ /* ++ * The status page will be in Reclaim state on success, or left ++ * in Firmware state on failure. Use snp_reclaim_pages() to ++ * transition either case back to Hypervisor-owned state. ++ */ ++ if (snp_reclaim_pages(__pa(data), 1, true)) { ++ snp_leak_pages(__page_to_pfn(status_page), 1); ++ return -EFAULT; ++ } ++ } + + if (ret) + goto cleanup; +@@ -2120,9 +2123,6 @@ static int sev_ioctl_do_snp_platform_sta + ret = -EFAULT; + + cleanup: +- if (shutdown_required) +- __sev_snp_shutdown_locked(&error, false); +- + __free_pages(status_page, 0); + return ret; + } diff --git a/queue-6.12/crypto-ccp-move-dev_info-err-messages-for-sev-snp-init-and-shutdown.patch b/queue-6.12/crypto-ccp-move-dev_info-err-messages-for-sev-snp-init-and-shutdown.patch new file mode 100644 index 0000000000..9c72946043 --- /dev/null +++ b/queue-6.12/crypto-ccp-move-dev_info-err-messages-for-sev-snp-init-and-shutdown.patch @@ -0,0 +1,143 @@ +From stable+bounces-278160-greg=kroah.com@vger.kernel.org Mon Jul 20 19:15:46 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 13:15:30 -0400 +Subject: crypto: ccp - Move dev_info/err messages for SEV/SNP init and shutdown +To: stable@vger.kernel.org +Cc: Ashish Kalra , Tom Lendacky , Herbert Xu , Sasha Levin +Message-ID: <20260720171535.2695659-1-sashal@kernel.org> + +From: Ashish Kalra + +[ Upstream commit 9770b428b1a28360663f1f5e524ee458b4cf454b ] + +Move dev_info and dev_err messages related to SEV/SNP initialization +and shutdown into __sev_platform_init_locked(), __sev_snp_init_locked() +and __sev_platform_shutdown_locked(), __sev_snp_shutdown_locked() so +that they don't need to be issued from callers. + +This allows both _sev_platform_init_locked() and various SEV/SNP ioctls +to call __sev_platform_init_locked(), __sev_snp_init_locked() and +__sev_platform_shutdown_locked(), __sev_snp_shutdown_locked() for +implicit SEV/SNP initialization and shutdown without additionally +printing any errors/success messages. + +Reviewed-by: Tom Lendacky +Signed-off-by: Ashish Kalra +Signed-off-by: Herbert Xu +Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/ccp/sev-dev.c | 44 +++++++++++++++++++++++++++++++------------ + 1 file changed, 32 insertions(+), 12 deletions(-) + +--- a/drivers/crypto/ccp/sev-dev.c ++++ b/drivers/crypto/ccp/sev-dev.c +@@ -1174,21 +1174,31 @@ static int __sev_snp_init_locked(int *er + wbinvd_on_all_cpus(); + + rc = __sev_do_cmd_locked(cmd, arg, error); +- if (rc) ++ if (rc) { ++ dev_err(sev->dev, "SEV-SNP: %s failed rc %d, error %#x\n", ++ cmd == SEV_CMD_SNP_INIT_EX ? "SNP_INIT_EX" : "SNP_INIT", ++ rc, *error); + return rc; ++ } + + /* Prepare for first SNP guest launch after INIT. */ + wbinvd_on_all_cpus(); + rc = __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, error); +- if (rc) ++ if (rc) { ++ dev_err(sev->dev, "SEV-SNP: SNP_DF_FLUSH failed rc %d, error %#x\n", ++ rc, *error); + return rc; ++ } + + sev->snp_initialized = true; + dev_dbg(sev->dev, "SEV-SNP firmware initialized\n"); + ++ dev_info(sev->dev, "SEV-SNP API:%d.%d build:%d\n", sev->api_major, ++ sev->api_minor, sev->build); ++ + sev_es_tmr_size = SNP_TMR_SIZE; + +- return rc; ++ return 0; + } + + static void __sev_platform_init_handle_tmr(struct sev_device *sev) +@@ -1285,16 +1295,22 @@ static int __sev_platform_init_locked(in + if (error) + *error = psp_ret; + +- if (rc) ++ if (rc) { ++ dev_err(sev->dev, "SEV: %s failed %#x, rc %d\n", ++ sev_init_ex_buffer ? "INIT_EX" : "INIT", psp_ret, rc); + return rc; ++ } + + sev->state = SEV_STATE_INIT; + + /* Prepare for first SEV guest launch after INIT */ + wbinvd_on_all_cpus(); + rc = __sev_do_cmd_locked(SEV_CMD_DF_FLUSH, NULL, error); +- if (rc) ++ if (rc) { ++ dev_err(sev->dev, "SEV: DF_FLUSH failed %#x, rc %d\n", ++ *error, rc); + return rc; ++ } + + dev_dbg(sev->dev, "SEV firmware initialized\n"); + +@@ -1374,8 +1390,11 @@ static int __sev_platform_shutdown_locke + return 0; + + ret = __sev_do_cmd_locked(SEV_CMD_SHUTDOWN, NULL, error); +- if (ret) ++ if (ret) { ++ dev_err(sev->dev, "SEV: failed to SHUTDOWN error %#x, rc %d\n", ++ *error, ret); + return ret; ++ } + + sev->state = SEV_STATE_UNINIT; + dev_dbg(sev->dev, "SEV firmware shutdown\n"); +@@ -1733,9 +1752,12 @@ static int __sev_snp_shutdown_locked(int + ret = __sev_do_cmd_locked(SEV_CMD_SNP_SHUTDOWN_EX, &data, error); + /* SHUTDOWN may require DF_FLUSH */ + if (*error == SEV_RET_DFFLUSH_REQUIRED) { +- ret = __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, NULL); ++ int dfflush_error; ++ ++ ret = __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, &dfflush_error); + if (ret) { +- dev_err(sev->dev, "SEV-SNP DF_FLUSH failed\n"); ++ dev_err(sev->dev, "SEV-SNP DF_FLUSH failed, ret = %d, error = %#x\n", ++ ret, dfflush_error); + return ret; + } + /* reissue the shutdown command */ +@@ -1743,7 +1765,8 @@ static int __sev_snp_shutdown_locked(int + error); + } + if (ret) { +- dev_err(sev->dev, "SEV-SNP firmware shutdown failed\n"); ++ dev_err(sev->dev, "SEV-SNP firmware shutdown failed, rc %d, error %#x\n", ++ ret, *error); + return ret; + } + +@@ -2511,9 +2534,6 @@ void sev_pci_init(void) + dev_err(sev->dev, "SEV: failed to INIT error %#x, rc %d\n", + args.error, rc); + +- dev_info(sev->dev, "SEV%s API:%d.%d build:%d\n", sev->snp_initialized ? +- "-SNP" : "", sev->api_major, sev->api_minor, sev->build); +- + atomic_notifier_chain_register(&panic_notifier_list, + &snp_panic_notifier); + return; diff --git a/queue-6.12/crypto-ccp-move-sev-snp-platform-initialization-to-kvm.patch b/queue-6.12/crypto-ccp-move-sev-snp-platform-initialization-to-kvm.patch new file mode 100644 index 0000000000..0c08db2352 --- /dev/null +++ b/queue-6.12/crypto-ccp-move-sev-snp-platform-initialization-to-kvm.patch @@ -0,0 +1,70 @@ +From stable+bounces-278165-greg=kroah.com@vger.kernel.org Mon Jul 20 19:15:54 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 13:15:33 -0400 +Subject: crypto: ccp - Move SEV/SNP Platform initialization to KVM +To: stable@vger.kernel.org +Cc: Ashish Kalra , Sean Christopherson , Alexey Kardashevskiy , Herbert Xu , Sasha Levin +Message-ID: <20260720171535.2695659-4-sashal@kernel.org> + +From: Ashish Kalra + +[ Upstream commit 3f8f0133a5fc9b32d0c308530320c3f2430ba5ab ] + +SNP initialization is forced during PSP driver probe purely because SNP +can't be initialized if VMs are running. But the only in-tree user of +SEV/SNP functionality is KVM, and KVM depends on PSP driver for the same. +Forcing SEV/SNP initialization because a hypervisor could be running +legacy non-confidential VMs make no sense. + +This patch removes SEV/SNP initialization from the PSP driver probe +time and moves the requirement to initialize SEV/SNP functionality +to KVM if it wants to use SEV/SNP. + +Suggested-by: Sean Christopherson +Reviewed-by: Alexey Kardashevskiy +Signed-off-by: Ashish Kalra +Signed-off-by: Herbert Xu +Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/ccp/sev-dev.c | 13 ------------- + 1 file changed, 13 deletions(-) + +--- a/drivers/crypto/ccp/sev-dev.c ++++ b/drivers/crypto/ccp/sev-dev.c +@@ -1352,10 +1352,6 @@ static int _sev_platform_init_locked(str + if (sev->state == SEV_STATE_INIT) + return 0; + +- /* +- * Legacy guests cannot be running while SNP_INIT(_EX) is executing, +- * so perform SEV-SNP initialization at probe time. +- */ + rc = __sev_snp_init_locked(&args->error); + if (rc && rc != -ENODEV) { + /* +@@ -2514,9 +2510,7 @@ EXPORT_SYMBOL_GPL(sev_issue_cmd_external + void sev_pci_init(void) + { + struct sev_device *sev = psp_master->sev_data; +- struct sev_platform_init_args args = {0}; + u8 api_major, api_minor, build; +- int rc; + + if (!sev) + return; +@@ -2539,13 +2533,6 @@ void sev_pci_init(void) + api_major, api_minor, build, + sev->api_major, sev->api_minor, sev->build); + +- /* Initialize the platform */ +- args.probe = true; +- rc = sev_platform_init(&args); +- if (rc) +- dev_err(sev->dev, "SEV: failed to INIT error %#x, rc %d\n", +- args.error, rc); +- + return; + + err: diff --git a/queue-6.12/crypto-ccp-register-snp-panic-notifier-only-if-snp-is-enabled.patch b/queue-6.12/crypto-ccp-register-snp-panic-notifier-only-if-snp-is-enabled.patch new file mode 100644 index 0000000000..2cda971692 --- /dev/null +++ b/queue-6.12/crypto-ccp-register-snp-panic-notifier-only-if-snp-is-enabled.patch @@ -0,0 +1,94 @@ +From stable+bounces-278162-greg=kroah.com@vger.kernel.org Mon Jul 20 19:15:47 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 13:15:32 -0400 +Subject: crypto: ccp - Register SNP panic notifier only if SNP is enabled +To: stable@vger.kernel.org +Cc: Ashish Kalra , Dionna Glaze , Alexey Kardashevskiy , Tom Lendacky , Herbert Xu , Sasha Levin +Message-ID: <20260720171535.2695659-3-sashal@kernel.org> + +From: Ashish Kalra + +[ Upstream commit 19860c3274fb209e36ebcba562dc5da301a53cb3 ] + +Currently, the SNP panic notifier is registered on module initialization +regardless of whether SNP is being enabled or initialized. + +Instead, register the SNP panic notifier only when SNP is actually +initialized and unregister the notifier when SNP is shutdown. + +Reviewed-by: Dionna Glaze +Reviewed-by: Alexey Kardashevskiy +Reviewed-by: Tom Lendacky +Signed-off-by: Ashish Kalra +Signed-off-by: Herbert Xu +Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/ccp/sev-dev.c | 22 +++++++++++++--------- + 1 file changed, 13 insertions(+), 9 deletions(-) + +--- a/drivers/crypto/ccp/sev-dev.c ++++ b/drivers/crypto/ccp/sev-dev.c +@@ -105,6 +105,13 @@ static void *sev_init_ex_buffer; + + static void __sev_firmware_shutdown(struct sev_device *sev, bool panic); + ++static int snp_shutdown_on_panic(struct notifier_block *nb, ++ unsigned long reason, void *arg); ++ ++static struct notifier_block snp_panic_notifier = { ++ .notifier_call = snp_shutdown_on_panic, ++}; ++ + static inline bool sev_version_greater_or_equal(u8 maj, u8 min) + { + struct sev_device *sev = psp_master->sev_data; +@@ -1196,6 +1203,9 @@ static int __sev_snp_init_locked(int *er + dev_info(sev->dev, "SEV-SNP API:%d.%d build:%d\n", sev->api_major, + sev->api_minor, sev->build); + ++ atomic_notifier_chain_register(&panic_notifier_list, ++ &snp_panic_notifier); ++ + sev_es_tmr_size = SNP_TMR_SIZE; + + return 0; +@@ -1792,6 +1802,9 @@ static int __sev_snp_shutdown_locked(int + sev->snp_initialized = false; + dev_dbg(sev->dev, "SEV-SNP firmware shutdown\n"); + ++ atomic_notifier_chain_unregister(&panic_notifier_list, ++ &snp_panic_notifier); ++ + /* Reset TMR size back to default */ + sev_es_tmr_size = SEV_TMR_SIZE; + +@@ -2488,10 +2501,6 @@ static int snp_shutdown_on_panic(struct + return NOTIFY_DONE; + } + +-static struct notifier_block snp_panic_notifier = { +- .notifier_call = snp_shutdown_on_panic, +-}; +- + int sev_issue_cmd_external_user(struct file *filep, unsigned int cmd, + void *data, int *error) + { +@@ -2537,8 +2546,6 @@ void sev_pci_init(void) + dev_err(sev->dev, "SEV: failed to INIT error %#x, rc %d\n", + args.error, rc); + +- atomic_notifier_chain_register(&panic_notifier_list, +- &snp_panic_notifier); + return; + + err: +@@ -2555,7 +2562,4 @@ void sev_pci_exit(void) + return; + + sev_firmware_shutdown(sev); +- +- atomic_notifier_chain_unregister(&panic_notifier_list, +- &snp_panic_notifier); + } diff --git a/queue-6.12/crypto-ccp-reset-tmr-size-at-snp-shutdown.patch b/queue-6.12/crypto-ccp-reset-tmr-size-at-snp-shutdown.patch new file mode 100644 index 0000000000..087852ad9f --- /dev/null +++ b/queue-6.12/crypto-ccp-reset-tmr-size-at-snp-shutdown.patch @@ -0,0 +1,40 @@ +From stable+bounces-278161-greg=kroah.com@vger.kernel.org Mon Jul 20 19:15:49 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 13:15:31 -0400 +Subject: crypto: ccp - Reset TMR size at SNP Shutdown +To: stable@vger.kernel.org +Cc: Ashish Kalra , Dionna Glaze , Tom Lendacky , Herbert Xu , Sasha Levin +Message-ID: <20260720171535.2695659-2-sashal@kernel.org> + +From: Ashish Kalra + +[ Upstream commit 65a895a44e641fe47f3419b064288d1d713024a5 ] + +Implicit SNP initialization as part of some SNP ioctls modify TMR size +to be SNP compliant which followed by SNP shutdown will leave the +TMR size modified and then subsequently cause SEV only initialization +to fail, hence, reset TMR size to default at SNP Shutdown. + +Acked-by: Dionna Glaze +Reviewed-by: Tom Lendacky +Signed-off-by: Ashish Kalra +Signed-off-by: Herbert Xu +Stable-dep-of: 08f0e65e784c ("crypto: ccp - Do not initialize SNP for ioctl(SNP_CONFIG)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/ccp/sev-dev.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/drivers/crypto/ccp/sev-dev.c ++++ b/drivers/crypto/ccp/sev-dev.c +@@ -1792,6 +1792,9 @@ static int __sev_snp_shutdown_locked(int + sev->snp_initialized = false; + dev_dbg(sev->dev, "SEV-SNP firmware shutdown\n"); + ++ /* Reset TMR size back to default */ ++ sev_es_tmr_size = SEV_TMR_SIZE; ++ + return ret; + } + diff --git a/queue-6.12/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch b/queue-6.12/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch new file mode 100644 index 0000000000..23d14ba271 --- /dev/null +++ b/queue-6.12/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch @@ -0,0 +1,57 @@ +From stable+bounces-278187-greg=kroah.com@vger.kernel.org Mon Jul 20 20:24:28 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 14:23:46 -0400 +Subject: crypto: qat - fix restarting state leak on allocation failure +To: stable@vger.kernel.org +Cc: Ahsan Atta , Maksim Lukoshkov , Giovanni Cabiddu , Herbert Xu , Sasha Levin +Message-ID: <20260720182346.3021518-1-sashal@kernel.org> + +From: Ahsan Atta + +[ Upstream commit 7d3ed20f7e46b3e991936fedd7a28f3ff4aec8d2 ] + +In adf_dev_aer_schedule_reset(), ADF_STATUS_RESTARTING is set before +allocating reset_data. If the allocation fails, the function returns +-ENOMEM without queuing reset work, so nothing ever clears the bit. +This leaves the device permanently stuck in the restarting state, +causing all subsequent reset attempts to be silently skipped. + +Fix this by using test_and_set_bit() to atomically claim the +RESTARTING state, preventing duplicate reset scheduling races under +concurrent fatal error reporting. If the subsequent allocation fails, +clear the bit to restore clean state so future reset attempts can +proceed. + +Cc: stable@vger.kernel.org +Fixes: d8cba25d2c68 ("crypto: qat - Intel(R) QAT driver framework") +Signed-off-by: Ahsan Atta +Co-developed-by: Maksim Lukoshkov +Signed-off-by: Maksim Lukoshkov +Reviewed-by: Giovanni Cabiddu +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/intel/qat/qat_common/adf_aer.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +--- a/drivers/crypto/intel/qat/qat_common/adf_aer.c ++++ b/drivers/crypto/intel/qat/qat_common/adf_aer.c +@@ -157,13 +157,14 @@ static int adf_dev_aer_schedule_reset(st + struct adf_reset_dev_data *reset_data; + + if (!adf_dev_started(accel_dev) || +- test_bit(ADF_STATUS_RESTARTING, &accel_dev->status)) ++ test_and_set_bit(ADF_STATUS_RESTARTING, &accel_dev->status)) + return 0; + +- set_bit(ADF_STATUS_RESTARTING, &accel_dev->status); + reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL); +- if (!reset_data) ++ if (!reset_data) { ++ clear_bit(ADF_STATUS_RESTARTING, &accel_dev->status); + return -ENOMEM; ++ } + reset_data->accel_dev = accel_dev; + init_completion(&reset_data->compl); + reset_data->mode = mode; diff --git a/queue-6.12/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch b/queue-6.12/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch new file mode 100644 index 0000000000..c80c947ce0 --- /dev/null +++ b/queue-6.12/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch @@ -0,0 +1,218 @@ +From stable+bounces-274655-greg=kroah.com@vger.kernel.org Wed Jul 15 03:01:04 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 21:00:48 -0400 +Subject: crypto: qat - fix VF2PF work teardown race in adf_disable_sriov() +To: stable@vger.kernel.org +Cc: Giovanni Cabiddu , Ahsan Atta , Herbert Xu , Sasha Levin +Message-ID: <20260715010048.4009225-1-sashal@kernel.org> + +From: Giovanni Cabiddu + +[ Upstream commit 277281c10c63791067d24d421f7c43a15faa9096 ] + +The VF2PF interrupt handler queues PF-side response work that stores a +raw pointer to per-VF state (struct adf_accel_vf_info). Currently, +adf_disable_sriov() destroys per-VF mutexes and frees vf_info without +stopping new VF2PF work or waiting for in-flight workers to complete. A +concurrently scheduled or already queued worker can then dereference +freed memory. + +This manifests as a use-after-free when KASAN is enabled: + + BUG: KASAN: null-ptr-deref in mutex_lock+0x76/0xe0 + Write of size 8 at addr 0000000000000260 by task kworker/24:2/... + Workqueue: qat_pf2vf_resp_wq adf_iov_send_resp [intel_qat] + Call Trace: + kasan_report+0x119/0x140 + mutex_lock+0x76/0xe0 + adf_gen4_pfvf_send+0xd4/0x1f0 [intel_qat] + adf_recv_and_handle_vf2pf_msg+0x290/0x360 [intel_qat] + adf_iov_send_resp+0x8c/0xe0 [intel_qat] + process_one_work+0x6ac/0xfd0 + worker_thread+0x4dd/0xd30 + kthread+0x326/0x410 + ret_from_fork+0x33b/0x670 + +Add a PF-local flag, vf2pf_disabled, that gates work queueing, worker +processing, and interrupt re-enabling during teardown. Set this flag +atomically with the hardware interrupt mask inside +adf_disable_all_vf2pf_interrupts(). After masking, synchronize the AE +cluster MSI-X interrupt and flush the PF response workqueue before +tearing down per-VF locks and state so all in-flight work completes +before vf_info is destroyed. + +Introduce adf_enable_all_vf2pf_interrupts() to clear the flag and +unmask all VF2PF interrupts under the same lock when SR-IOV is +re-enabled. This ensures the software flag and hardware state transition +atomically on both the enable and disable paths. + +Cc: stable@vger.kernel.org +Fixes: ed8ccaef52fa ("crypto: qat - Add support for SRIOV") +Signed-off-by: Giovanni Cabiddu +Reviewed-by: Ahsan Atta +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/intel/qat/qat_common/adf_accel_devices.h | 2 + drivers/crypto/intel/qat/qat_common/adf_common_drv.h | 2 + drivers/crypto/intel/qat/qat_common/adf_isr.c | 39 ++++++++++++++++ + drivers/crypto/intel/qat/qat_common/adf_sriov.c | 20 +++++++- + 4 files changed, 61 insertions(+), 2 deletions(-) + +--- a/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h ++++ b/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h +@@ -485,6 +485,8 @@ struct adf_accel_dev { + struct { + /* protects VF2PF interrupts access */ + spinlock_t vf2pf_ints_lock; ++ /* prevents VF2PF handling from racing with VF state teardown */ ++ bool vf2pf_disabled; + /* vf_info is non-zero when SR-IOV is init'ed */ + struct adf_accel_vf_info *vf_info; + } pf; +--- a/drivers/crypto/intel/qat/qat_common/adf_common_drv.h ++++ b/drivers/crypto/intel/qat/qat_common/adf_common_drv.h +@@ -119,6 +119,7 @@ void qat_comp_alg_callback(void *resp); + + int adf_isr_resource_alloc(struct adf_accel_dev *accel_dev); + void adf_isr_resource_free(struct adf_accel_dev *accel_dev); ++void adf_isr_sync_ae_cluster(struct adf_accel_dev *accel_dev); + int adf_vf_isr_resource_alloc(struct adf_accel_dev *accel_dev); + void adf_vf_isr_resource_free(struct adf_accel_dev *accel_dev); + +@@ -192,6 +193,7 @@ int adf_sriov_configure(struct pci_dev * + void adf_disable_sriov(struct adf_accel_dev *accel_dev); + void adf_reenable_sriov(struct adf_accel_dev *accel_dev); + void adf_enable_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 vf_mask); ++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs); + void adf_disable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev); + bool adf_recv_and_handle_pf2vf_msg(struct adf_accel_dev *accel_dev); + bool adf_recv_and_handle_vf2pf_msg(struct adf_accel_dev *accel_dev, u32 vf_nr); +--- a/drivers/crypto/intel/qat/qat_common/adf_isr.c ++++ b/drivers/crypto/intel/qat/qat_common/adf_isr.c +@@ -62,6 +62,23 @@ void adf_enable_vf2pf_interrupts(struct + unsigned long flags; + + spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags); ++ if (!READ_ONCE(accel_dev->pf.vf2pf_disabled)) ++ GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask); ++ spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags); ++} ++ ++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs) ++{ ++ void __iomem *pmisc_addr = adf_get_pmisc_base(accel_dev); ++ unsigned long flags; ++ u32 vf_mask; ++ ++ vf_mask = BIT_ULL(num_vfs) - 1; ++ if (!vf_mask) ++ return; ++ ++ spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags); ++ WRITE_ONCE(accel_dev->pf.vf2pf_disabled, false); + GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask); + spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags); + } +@@ -72,6 +89,7 @@ void adf_disable_all_vf2pf_interrupts(st + unsigned long flags; + + spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags); ++ WRITE_ONCE(accel_dev->pf.vf2pf_disabled, true); + GET_PFVF_OPS(accel_dev)->disable_all_vf2pf_interrupts(pmisc_addr); + spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags); + } +@@ -174,6 +192,27 @@ static irqreturn_t adf_msix_isr_ae(int i + return IRQ_NONE; + } + ++void adf_isr_sync_ae_cluster(struct adf_accel_dev *accel_dev) ++{ ++ struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev; ++ struct adf_hw_device_data *hw_data = GET_HW_DATA(accel_dev); ++ u32 num_entries = pci_dev_info->msix_entries.num_entries; ++ struct adf_irq *irqs = pci_dev_info->msix_entries.irqs; ++ u32 irq_idx; ++ int irq; ++ ++ if (!test_bit(ADF_STATUS_IRQ_ALLOCATED, &accel_dev->status) || !irqs) ++ return; ++ ++ irq_idx = num_entries > 1 ? hw_data->num_banks : 0; ++ if (irq_idx >= num_entries || !irqs[irq_idx].enabled) ++ return; ++ ++ irq = pci_irq_vector(pci_dev_info->pci_dev, hw_data->num_banks); ++ if (irq > 0) ++ synchronize_irq(irq); ++} ++ + static void adf_free_irqs(struct adf_accel_dev *accel_dev) + { + struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev; +--- a/drivers/crypto/intel/qat/qat_common/adf_sriov.c ++++ b/drivers/crypto/intel/qat/qat_common/adf_sriov.c +@@ -26,6 +26,9 @@ static void adf_iov_send_resp(struct wor + u32 vf_nr = vf_info->vf_nr; + bool ret; + ++ if (READ_ONCE(accel_dev->pf.vf2pf_disabled)) ++ goto out; ++ + mutex_lock(&vf_info->pfvf_mig_lock); + ret = adf_recv_and_handle_vf2pf_msg(accel_dev, vf_nr); + if (ret) +@@ -33,13 +36,18 @@ static void adf_iov_send_resp(struct wor + adf_enable_vf2pf_interrupts(accel_dev, 1 << vf_nr); + mutex_unlock(&vf_info->pfvf_mig_lock); + ++out: + kfree(pf2vf_resp); + } + + void adf_schedule_vf2pf_handler(struct adf_accel_vf_info *vf_info) + { ++ struct adf_accel_dev *accel_dev = vf_info->accel_dev; + struct adf_pf2vf_resp *pf2vf_resp; + ++ if (READ_ONCE(accel_dev->pf.vf2pf_disabled)) ++ return; ++ + pf2vf_resp = kzalloc(sizeof(*pf2vf_resp), GFP_ATOMIC); + if (!pf2vf_resp) + return; +@@ -49,6 +57,12 @@ void adf_schedule_vf2pf_handler(struct a + queue_work(pf2vf_resp_wq, &pf2vf_resp->pf2vf_resp_work); + } + ++static void adf_flush_pf2vf_resp_wq(void) ++{ ++ if (pf2vf_resp_wq) ++ flush_workqueue(pf2vf_resp_wq); ++} ++ + static int adf_enable_sriov(struct adf_accel_dev *accel_dev) + { + struct pci_dev *pdev = accel_to_pci_dev(accel_dev); +@@ -75,7 +89,7 @@ static int adf_enable_sriov(struct adf_a + hw_data->configure_iov_threads(accel_dev, true); + + /* Enable VF to PF interrupts for all VFs */ +- adf_enable_vf2pf_interrupts(accel_dev, BIT_ULL(totalvfs) - 1); ++ adf_enable_all_vf2pf_interrupts(accel_dev, totalvfs); + + /* Do not enable SR-IOV if already enabled */ + if (pci_num_vf(pdev)) +@@ -259,8 +273,10 @@ void adf_disable_sriov(struct adf_accel_ + if (!test_bit(ADF_STATUS_RESTARTING, &accel_dev->status)) + pci_disable_sriov(accel_to_pci_dev(accel_dev)); + +- /* Disable VF to PF interrupts */ ++ /* Block VF2PF work and disable VF to PF interrupts */ + adf_disable_all_vf2pf_interrupts(accel_dev); ++ adf_isr_sync_ae_cluster(accel_dev); ++ adf_flush_pf2vf_resp_wq(); + + /* Clear Valid bits in AE Thread to PCIe Function Mapping */ + if (hw_data->configure_iov_threads) diff --git a/queue-6.12/exfat-add-exfat_get_dentry_set_by_ei-helper.patch b/queue-6.12/exfat-add-exfat_get_dentry_set_by_ei-helper.patch new file mode 100644 index 0000000000..3605c9fc4e --- /dev/null +++ b/queue-6.12/exfat-add-exfat_get_dentry_set_by_ei-helper.patch @@ -0,0 +1,216 @@ +From stable+bounces-278322-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:21 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:28:18 -0400 +Subject: exfat: add exfat_get_dentry_set_by_ei() helper +To: stable@vger.kernel.org +Cc: Yuezhang Mo , Aoyama Wataru , Daniel Palmer , Sungjong Seo , Namjae Jeon , Sasha Levin +Message-ID: <20260721012821.3459448-3-sashal@kernel.org> + +From: Yuezhang Mo + +[ Upstream commit ac844e91364a03c35838fd488437605fbe56f8c3 ] + +This helper gets the directory entry set of the file for the exfat +inode which has been created. + +It's used to remove all the instances of the pattern it replaces +making the code cleaner, it's also a preparation for changing ->dir +to record the cluster where the directory entry set is located and +changing ->entry to record the index of the directory entry within +the cluster. + +Signed-off-by: Yuezhang Mo +Reviewed-by: Aoyama Wataru +Reviewed-by: Daniel Palmer +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/exfat/exfat_fs.h | 2 + + fs/exfat/inode.c | 2 - + fs/exfat/namei.c | 53 +++++++++++++++++----------------------------------- + 3 files changed, 21 insertions(+), 36 deletions(-) + +--- a/fs/exfat/exfat_fs.h ++++ b/fs/exfat/exfat_fs.h +@@ -508,6 +508,8 @@ struct exfat_dentry *exfat_get_dentry_ca + int exfat_get_dentry_set(struct exfat_entry_set_cache *es, + struct super_block *sb, struct exfat_chain *p_dir, int entry, + unsigned int num_entries); ++#define exfat_get_dentry_set_by_ei(es, sb, ei) \ ++ exfat_get_dentry_set(es, sb, &(ei)->dir, (ei)->entry, ES_ALL_ENTRIES) + int exfat_get_empty_dentry_set(struct exfat_entry_set_cache *es, + struct super_block *sb, struct exfat_chain *p_dir, int entry, + unsigned int num_entries); +--- a/fs/exfat/inode.c ++++ b/fs/exfat/inode.c +@@ -43,7 +43,7 @@ int __exfat_write_inode(struct inode *in + exfat_set_volume_dirty(sb); + + /* get the directory entry of given file or directory */ +- if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES)) ++ if (exfat_get_dentry_set_by_ei(&es, sb, ei)) + return -EIO; + ep = exfat_get_dentry_cached(&es, ES_IDX_FILE); + ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM); +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -788,26 +788,23 @@ unlock: + /* remove an entry, BUT don't truncate */ + static int exfat_unlink(struct inode *dir, struct dentry *dentry) + { +- struct exfat_chain cdir; + struct super_block *sb = dir->i_sb; + struct inode *inode = dentry->d_inode; + struct exfat_inode_info *ei = EXFAT_I(inode); + struct exfat_entry_set_cache es; +- int entry, err = 0; ++ int err = 0; + + if (unlikely(exfat_forced_shutdown(sb))) + return -EIO; + + mutex_lock(&EXFAT_SB(sb)->s_lock); +- exfat_chain_dup(&cdir, &ei->dir); +- entry = ei->entry; + if (ei->dir.dir == DIR_DELETED) { + exfat_err(sb, "abnormal access to deleted dentry"); + err = -ENOENT; + goto unlock; + } + +- err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES); ++ err = exfat_get_dentry_set_by_ei(&es, sb, ei); + if (err) { + err = -EIO; + goto unlock; +@@ -942,21 +939,18 @@ static int exfat_check_dir_empty(struct + static int exfat_rmdir(struct inode *dir, struct dentry *dentry) + { + struct inode *inode = dentry->d_inode; +- struct exfat_chain cdir, clu_to_free; ++ struct exfat_chain clu_to_free; + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_inode_info *ei = EXFAT_I(inode); + struct exfat_entry_set_cache es; +- int entry, err; ++ int err; + + if (unlikely(exfat_forced_shutdown(sb))) + return -EIO; + + mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); + +- exfat_chain_dup(&cdir, &ei->dir); +- entry = ei->entry; +- + if (ei->dir.dir == DIR_DELETED) { + exfat_err(sb, "abnormal access to deleted dentry"); + err = -ENOENT; +@@ -974,7 +968,7 @@ static int exfat_rmdir(struct inode *dir + goto unlock; + } + +- err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES); ++ err = exfat_get_dentry_set_by_ei(&es, sb, ei); + if (err) { + err = -EIO; + goto unlock; +@@ -1009,8 +1003,8 @@ unlock: + return err; + } + +-static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir, +- int oldentry, struct exfat_uni_name *p_uniname, ++static int exfat_rename_file(struct inode *parent_inode, ++ struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, + struct exfat_inode_info *ei) + { + int ret, num_new_entries; +@@ -1026,7 +1020,7 @@ static int exfat_rename_file(struct inod + if (num_new_entries < 0) + return num_new_entries; + +- ret = exfat_get_dentry_set(&old_es, sb, p_dir, oldentry, ES_ALL_ENTRIES); ++ ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei); + if (ret) { + ret = -EIO; + return ret; +@@ -1080,21 +1074,19 @@ put_old_es: + return ret; + } + +-static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir, +- int oldentry, struct exfat_chain *p_newdir, +- struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) ++static int exfat_move_file(struct inode *parent_inode, ++ struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname, ++ struct exfat_inode_info *ei) + { + int ret, newentry, num_new_entries; + struct exfat_dentry *epmov, *epnew; +- struct super_block *sb = parent_inode->i_sb; + struct exfat_entry_set_cache mov_es, new_es; + + num_new_entries = exfat_calc_num_entries(p_uniname); + if (num_new_entries < 0) + return num_new_entries; + +- ret = exfat_get_dentry_set(&mov_es, sb, p_olddir, oldentry, +- ES_ALL_ENTRIES); ++ ret = exfat_get_dentry_set_by_ei(&mov_es, parent_inode->i_sb, ei); + if (ret) + return -EIO; + +@@ -1143,8 +1135,7 @@ static int __exfat_rename(struct inode * + struct dentry *new_dentry) + { + int ret; +- int dentry; +- struct exfat_chain olddir, newdir; ++ struct exfat_chain newdir; + struct exfat_uni_name uni_name; + struct super_block *sb = old_parent_inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); +@@ -1161,11 +1152,6 @@ static int __exfat_rename(struct inode * + return -ENOENT; + } + +- exfat_chain_set(&olddir, EXFAT_I(old_parent_inode)->start_clu, +- EXFAT_B_TO_CLU_ROUND_UP(i_size_read(old_parent_inode), sbi), +- EXFAT_I(old_parent_inode)->flags); +- dentry = ei->entry; +- + /* check whether new dir is existing directory and empty */ + if (new_inode) { + ret = -EIO; +@@ -1200,21 +1186,18 @@ static int __exfat_rename(struct inode * + + exfat_set_volume_dirty(sb); + +- if (olddir.dir == newdir.dir) +- ret = exfat_rename_file(new_parent_inode, &olddir, dentry, ++ if (new_parent_inode == old_parent_inode) ++ ret = exfat_rename_file(new_parent_inode, &newdir, + &uni_name, ei); + else +- ret = exfat_move_file(new_parent_inode, &olddir, dentry, +- &newdir, &uni_name, ei); ++ ret = exfat_move_file(new_parent_inode, &newdir, ++ &uni_name, ei); + + if (!ret && new_inode) { + struct exfat_entry_set_cache es; +- struct exfat_chain *p_dir = &(new_ei->dir); +- int new_entry = new_ei->entry; + + /* delete entries of new_dir */ +- ret = exfat_get_dentry_set(&es, sb, p_dir, new_entry, +- ES_ALL_ENTRIES); ++ ret = exfat_get_dentry_set_by_ei(&es, sb, new_ei); + if (ret) { + ret = -EIO; + goto del_out; diff --git a/queue-6.12/exfat-fix-incorrect-directory-checksum-after-rename-to-shorter-name.patch b/queue-6.12/exfat-fix-incorrect-directory-checksum-after-rename-to-shorter-name.patch new file mode 100644 index 0000000000..e1baba5af8 --- /dev/null +++ b/queue-6.12/exfat-fix-incorrect-directory-checksum-after-rename-to-shorter-name.patch @@ -0,0 +1,44 @@ +From stable+bounces-278324-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:29 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:28:20 -0400 +Subject: exfat: fix incorrect directory checksum after rename to shorter name +To: stable@vger.kernel.org +Cc: Chi Zhiling , Sungjong Seo , Yuezhang Mo , Namjae Jeon , Sasha Levin +Message-ID: <20260721012821.3459448-5-sashal@kernel.org> + +From: Chi Zhiling + +[ Upstream commit ff37797badd831797b8a27830fe5046d7e23fdc3 ] + +When renaming a file in-place to a shorter name, exfat_remove_entries +marks excess entries as DELETED, but es->num_entries is not updated +accordingly. As a result, exfat_update_dir_chksum iterates over the +deleted entries and computes an incorrect checksum. + +This does not lead to persistent corruption because mark_inode_dirty() +is called afterward, and __exfat_write_inode later recomputes the +checksum using the correct num_entries value. + +Fix by setting es->num_entries = num_entries in exfat_init_ext_entry. + +Signed-off-by: Chi Zhiling +Reviewed-by: Sungjong Seo +Reviewed-by: Yuezhang Mo +Signed-off-by: Namjae Jeon +Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/exfat/dir.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/fs/exfat/dir.c ++++ b/fs/exfat/dir.c +@@ -507,6 +507,7 @@ void exfat_init_ext_entry(struct exfat_e + unsigned short *uniname = p_uniname->name; + struct exfat_dentry *ep; + ++ es->num_entries = num_entries; + ep = exfat_get_dentry_cached(es, ES_IDX_FILE); + ep->dentry.file.num_ext = (unsigned char)(num_entries - 1); + diff --git a/queue-6.12/exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch b/queue-6.12/exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch new file mode 100644 index 0000000000..1d554d1728 --- /dev/null +++ b/queue-6.12/exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch @@ -0,0 +1,217 @@ +From stable+bounces-278323-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:25 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:28:19 -0400 +Subject: exfat: move exfat_chain_set() out of __exfat_resolve_path() +To: stable@vger.kernel.org +Cc: Yuezhang Mo , Aoyama Wataru , Daniel Palmer , Sungjong Seo , Namjae Jeon , Sasha Levin +Message-ID: <20260721012821.3459448-4-sashal@kernel.org> + +From: Yuezhang Mo + +[ Upstream commit 0891c7313d87a1b6baf7162bc2f0d755ce70383f ] + +__exfat_resolve_path() mixes two functions. The first one is to +resolve and check if the path is valid. The second one is to output +the cluster assigned to the directory. + +The second one is only needed when need to traverse the directory +entries, and calling exfat_chain_set() so early causes p_dir to be +passed as an argument multiple times, increasing the complexity of +the code. + +This commit moves the call to exfat_chain_set() before traversing +directory entries. + +Signed-off-by: Yuezhang Mo +Reviewed-by: Aoyama Wataru +Reviewed-by: Daniel Palmer +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/exfat/namei.c | 60 +++++++++++++++++++++++-------------------------------- + 1 file changed, 26 insertions(+), 34 deletions(-) + +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -311,6 +311,9 @@ static int exfat_find_empty_entry(struct + ei->hint_femp.eidx = EXFAT_HINT_NONE; + } + ++ exfat_chain_set(p_dir, ei->start_clu, ++ EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); ++ + while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir, + num_entries, es)) < 0) { + if (dentry == -EIO) +@@ -386,14 +389,11 @@ static int exfat_find_empty_entry(struct + * Zero if it was successful; otherwise nonzero. + */ + static int __exfat_resolve_path(struct inode *inode, const unsigned char *path, +- struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, +- int lookup) ++ struct exfat_uni_name *p_uniname, int lookup) + { + int namelen; + int lossy = NLS_NAME_NO_LOSSY; + struct super_block *sb = inode->i_sb; +- struct exfat_sb_info *sbi = EXFAT_SB(sb); +- struct exfat_inode_info *ei = EXFAT_I(inode); + int pathlen = strlen(path); + + /* +@@ -432,24 +432,19 @@ static int __exfat_resolve_path(struct i + if ((lossy && !lookup) || !namelen) + return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL; + +- exfat_chain_set(p_dir, ei->start_clu, +- EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); +- + return 0; + } + + static inline int exfat_resolve_path(struct inode *inode, +- const unsigned char *path, struct exfat_chain *dir, +- struct exfat_uni_name *uni) ++ const unsigned char *path, struct exfat_uni_name *uni) + { +- return __exfat_resolve_path(inode, path, dir, uni, 0); ++ return __exfat_resolve_path(inode, path, uni, 0); + } + + static inline int exfat_resolve_path_for_lookup(struct inode *inode, +- const unsigned char *path, struct exfat_chain *dir, +- struct exfat_uni_name *uni) ++ const unsigned char *path, struct exfat_uni_name *uni) + { +- return __exfat_resolve_path(inode, path, dir, uni, 1); ++ return __exfat_resolve_path(inode, path, uni, 1); + } + + static inline loff_t exfat_make_i_pos(struct exfat_dir_entry *info) +@@ -471,7 +466,7 @@ static int exfat_add_entry(struct inode + int clu_size = 0; + unsigned int start_clu = EXFAT_FREE_CLUSTER; + +- ret = exfat_resolve_path(inode, path, p_dir, &uniname); ++ ret = exfat_resolve_path(inode, path, &uniname); + if (ret) + goto out; + +@@ -602,10 +597,13 @@ static int exfat_find(struct inode *dir, + return -ENOENT; + + /* check the validity of directory name in the given pathname */ +- ret = exfat_resolve_path_for_lookup(dir, qname->name, &cdir, &uni_name); ++ ret = exfat_resolve_path_for_lookup(dir, qname->name, &uni_name); + if (ret) + return ret; + ++ exfat_chain_set(&cdir, ei->start_clu, ++ EXFAT_B_TO_CLU(i_size_read(dir), sbi), ei->flags); ++ + /* check the validation of hint_stat and initialize it if required */ + if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) { + ei->hint_stat.clu = cdir.dir; +@@ -1004,8 +1002,7 @@ unlock: + } + + static int exfat_rename_file(struct inode *parent_inode, +- struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, +- struct exfat_inode_info *ei) ++ struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) + { + int ret, num_new_entries; + struct exfat_dentry *epold, *epnew; +@@ -1030,9 +1027,10 @@ static int exfat_rename_file(struct inod + + if (old_es.num_entries < num_new_entries) { + int newentry; ++ struct exfat_chain dir; + +- newentry = exfat_find_empty_entry(parent_inode, p_dir, num_new_entries, +- &new_es); ++ newentry = exfat_find_empty_entry(parent_inode, &dir, ++ num_new_entries, &new_es); + if (newentry < 0) { + ret = newentry; /* -EIO or -ENOSPC */ + goto put_old_es; +@@ -1056,7 +1054,7 @@ static int exfat_rename_file(struct inod + goto put_old_es; + + exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE); +- ei->dir = *p_dir; ++ ei->dir = dir; + ei->entry = newentry; + } else { + if (exfat_get_entry_type(epold) == TYPE_FILE) { +@@ -1075,12 +1073,12 @@ put_old_es: + } + + static int exfat_move_file(struct inode *parent_inode, +- struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname, +- struct exfat_inode_info *ei) ++ struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) + { + int ret, newentry, num_new_entries; + struct exfat_dentry *epmov, *epnew; + struct exfat_entry_set_cache mov_es, new_es; ++ struct exfat_chain newdir; + + num_new_entries = exfat_calc_num_entries(p_uniname); + if (num_new_entries < 0) +@@ -1090,8 +1088,8 @@ static int exfat_move_file(struct inode + if (ret) + return -EIO; + +- newentry = exfat_find_empty_entry(parent_inode, p_newdir, num_new_entries, +- &new_es); ++ newentry = exfat_find_empty_entry(parent_inode, &newdir, ++ num_new_entries, &new_es); + if (newentry < 0) { + ret = newentry; /* -EIO or -ENOSPC */ + goto put_mov_es; +@@ -1112,9 +1110,7 @@ static int exfat_move_file(struct inode + exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); + exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE); + +- exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, +- p_newdir->flags); +- ++ ei->dir = newdir; + ei->entry = newentry; + + ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode)); +@@ -1135,7 +1131,6 @@ static int __exfat_rename(struct inode * + struct dentry *new_dentry) + { + int ret; +- struct exfat_chain newdir; + struct exfat_uni_name uni_name; + struct super_block *sb = old_parent_inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); +@@ -1179,19 +1174,16 @@ static int __exfat_rename(struct inode * + } + + /* check the validity of directory name in the given new pathname */ +- ret = exfat_resolve_path(new_parent_inode, new_path, &newdir, +- &uni_name); ++ ret = exfat_resolve_path(new_parent_inode, new_path, &uni_name); + if (ret) + goto out; + + exfat_set_volume_dirty(sb); + + if (new_parent_inode == old_parent_inode) +- ret = exfat_rename_file(new_parent_inode, &newdir, +- &uni_name, ei); ++ ret = exfat_rename_file(new_parent_inode, &uni_name, ei); + else +- ret = exfat_move_file(new_parent_inode, &newdir, +- &uni_name, ei); ++ ret = exfat_move_file(new_parent_inode, &uni_name, ei); + + if (!ret && new_inode) { + struct exfat_entry_set_cache es; diff --git a/queue-6.12/exfat-preserve-benign-secondary-entries-during-rename-and-move.patch b/queue-6.12/exfat-preserve-benign-secondary-entries-during-rename-and-move.patch new file mode 100644 index 0000000000..6d3425f206 --- /dev/null +++ b/queue-6.12/exfat-preserve-benign-secondary-entries-during-rename-and-move.patch @@ -0,0 +1,342 @@ +From stable+bounces-278325-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:34 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:28:21 -0400 +Subject: exfat: preserve benign secondary entries during rename and move +To: stable@vger.kernel.org +Cc: Rochan Avlur , Yuezhang Mo , Namjae Jeon , Sasha Levin +Message-ID: <20260721012821.3459448-6-sashal@kernel.org> + +From: Rochan Avlur + +[ Upstream commit 942296784b2a9439651750c42f540bf2579b330f ] + +Commit 8258ef28001a ("exfat: handle unreconized benign secondary +entries") added cluster freeing for benign secondary entries inside +exfat_remove_entries(). However, exfat_remove_entries() is also called +from the rename and move paths (exfat_rename_file and exfat_move_file), +where the old entry set is being relocated rather than deleted. This +causes benign secondary entries such as vendor extension entries to be +silently destroyed on rename or cross-directory move, violating the +exFAT spec requirement (section 8.2) that implementations preserve +unrecognized benign secondary entries. + +Fix this by adding a free_benign parameter to exfat_remove_entries() +so callers can suppress cluster freeing during relocation, and +extending exfat_init_ext_entry() to copy trailing benign secondary +entries from the old entry set into the new one internally. Also +clean up the error paths to delete newly allocated entries on failure. + +Fixes: 8258ef28001a ("exfat: handle unreconized benign secondary entries") +Cc: stable@vger.kernel.org +Link: https://lore.kernel.org/linux-fsdevel/CAG7tbBV--waov7XVu2FHQEc6paR92dufS=em9DW5Kzsrpu3iQg@mail.gmail.com/ +Signed-off-by: Rochan Avlur +Reviewed-by: Yuezhang Mo +Signed-off-by: Namjae Jeon +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/exfat/dir.c | 50 +++++++++++++++++++++++++---- + fs/exfat/exfat_fs.h | 5 +- + fs/exfat/namei.c | 89 ++++++++++++++++++++++++++++++++++++++++------------ + 3 files changed, 116 insertions(+), 28 deletions(-) + +--- a/fs/exfat/dir.c ++++ b/fs/exfat/dir.c +@@ -500,32 +500,70 @@ static void exfat_free_benign_secondary_ + exfat_free_cluster(inode, &dir); + } + ++/* ++ * exfat_init_ext_entry - initialize extension entries in a directory entry set ++ * @es: target entry set ++ * @num_entries: number of entries excluding benign secondary entries ++ * @p_uniname: filename to store ++ * @old_es: optional source entry set with benign secondary entries, or NULL ++ * @num_extra: number of benign secondary entries to copy from @old_es ++ * ++ * Set up the file, stream extension, and filename entries in @es, optionally ++ * preserving @num_extra benign secondary entries from @old_es. @es and @old_es ++ * may refer to the same entry set; excess entries are marked as deleted. ++ */ + void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries, +- struct exfat_uni_name *p_uniname) ++ struct exfat_uni_name *p_uniname, ++ struct exfat_entry_set_cache *old_es, int num_extra) + { +- int i; ++ int i, src_start = 0, old_num; + unsigned short *uniname = p_uniname->name; + struct exfat_dentry *ep; + +- es->num_entries = num_entries; ++ if (WARN_ON(num_extra < 0 || (num_extra && (!old_es || ++ old_es->num_entries < ES_IDX_FIRST_FILENAME + num_extra)))) ++ num_extra = 0; ++ ++ /* ++ * Save old entry count and source position before modifying ++ * es->num_entries, since old_es and es may point to the same ++ * entry set. ++ */ ++ old_num = es->num_entries; ++ if (old_es && num_extra > 0) ++ src_start = old_es->num_entries - num_extra; ++ ++ es->num_entries = num_entries + num_extra; + ep = exfat_get_dentry_cached(es, ES_IDX_FILE); +- ep->dentry.file.num_ext = (unsigned char)(num_entries - 1); ++ ep->dentry.file.num_ext = (unsigned char)(num_entries - 1 + num_extra); + + ep = exfat_get_dentry_cached(es, ES_IDX_STREAM); + ep->dentry.stream.name_len = p_uniname->name_len; + ep->dentry.stream.name_hash = cpu_to_le16(p_uniname->name_hash); + ++ if (old_es && num_extra > 0) { ++ for (i = 0; i < num_extra; i++) ++ *exfat_get_dentry_cached(es, num_entries + i) = ++ *exfat_get_dentry_cached(old_es, src_start + i); ++ } ++ + for (i = ES_IDX_FIRST_FILENAME; i < num_entries; i++) { + ep = exfat_get_dentry_cached(es, i); + exfat_init_name_entry(ep, uniname); + uniname += EXFAT_FILE_NAME_LEN; + } + ++ /* Mark excess old entries as deleted (in-place shrink) */ ++ for (i = num_entries + num_extra; i < old_num; i++) { ++ ep = exfat_get_dentry_cached(es, i); ++ exfat_set_entry_type(ep, TYPE_DELETED); ++ } ++ + exfat_update_dir_chksum(es); + } + + void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, +- int order) ++ int order, bool free_benign) + { + int i; + struct exfat_dentry *ep; +@@ -533,7 +571,7 @@ void exfat_remove_entries(struct inode * + for (i = order; i < es->num_entries; i++) { + ep = exfat_get_dentry_cached(es, i); + +- if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC) ++ if (free_benign && (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC)) + exfat_free_benign_secondary_clusters(inode, ep); + + exfat_set_entry_type(ep, TYPE_DELETED); +--- a/fs/exfat/exfat_fs.h ++++ b/fs/exfat/exfat_fs.h +@@ -492,9 +492,10 @@ void exfat_init_dir_entry(struct exfat_e + unsigned int type, unsigned int start_clu, + unsigned long long size, struct timespec64 *ts); + void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries, +- struct exfat_uni_name *p_uniname); ++ struct exfat_uni_name *p_uniname, ++ struct exfat_entry_set_cache *old_es, int num_extra); + void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, +- int order); ++ int order, bool free_benign); + void exfat_update_dir_chksum(struct exfat_entry_set_cache *es); + int exfat_calc_num_entries(struct exfat_uni_name *p_uniname); + int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -498,7 +498,7 @@ static int exfat_add_entry(struct inode + * the first cluster is not determined yet. (0) + */ + exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts); +- exfat_init_ext_entry(&es, num_entries, &uniname); ++ exfat_init_ext_entry(&es, num_entries, &uniname, NULL, 0); + + ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); + if (ret) +@@ -811,7 +811,7 @@ static int exfat_unlink(struct inode *di + exfat_set_volume_dirty(sb); + + /* update the directory entry */ +- exfat_remove_entries(inode, &es, ES_IDX_FILE); ++ exfat_remove_entries(inode, &es, ES_IDX_FILE, true); + + err = exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); + if (err) +@@ -974,7 +974,7 @@ static int exfat_rmdir(struct inode *dir + + exfat_set_volume_dirty(sb); + +- exfat_remove_entries(inode, &es, ES_IDX_FILE); ++ exfat_remove_entries(inode, &es, ES_IDX_FILE, true); + + err = exfat_put_dentry_set(&es, IS_DIRSYNC(dir)); + if (err) +@@ -1001,6 +1001,23 @@ unlock: + return err; + } + ++/* ++ * Count benign secondary entries beyond the filename entries. ++ * Returns the count, or -EIO if the entry set is inconsistent. ++ */ ++static int exfat_count_extra_entries(struct exfat_entry_set_cache *es) ++{ ++ struct exfat_dentry *stream; ++ unsigned int name_entries; ++ int extra; ++ ++ stream = exfat_get_dentry_cached(es, ES_IDX_STREAM); ++ name_entries = EXFAT_FILENAME_ENTRY_NUM(stream->dentry.stream.name_len); ++ extra = es->num_entries - (ES_IDX_FIRST_FILENAME + name_entries); ++ ++ return extra >= 0 ? extra : -EIO; ++} ++ + static int exfat_rename_file(struct inode *parent_inode, + struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) + { +@@ -1009,6 +1026,7 @@ static int exfat_rename_file(struct inod + struct super_block *sb = parent_inode->i_sb; + struct exfat_entry_set_cache old_es, new_es; + int sync = IS_DIRSYNC(parent_inode); ++ unsigned int num_extra_entries, num_total_entries; + + if (unlikely(exfat_forced_shutdown(sb))) + return -EIO; +@@ -1018,19 +1036,23 @@ static int exfat_rename_file(struct inod + return num_new_entries; + + ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei); +- if (ret) { +- ret = -EIO; +- return ret; +- } ++ if (ret) ++ return -EIO; + + epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE); + +- if (old_es.num_entries < num_new_entries) { ++ ret = exfat_count_extra_entries(&old_es); ++ if (ret < 0) ++ goto put_old_es; ++ num_extra_entries = ret; ++ num_total_entries = num_new_entries + num_extra_entries; ++ ++ if (old_es.num_entries < num_total_entries) { + int newentry; + struct exfat_chain dir; + + newentry = exfat_find_empty_entry(parent_inode, &dir, +- num_new_entries, &new_es); ++ num_total_entries, &new_es); + if (newentry < 0) { + ret = newentry; /* -EIO or -ENOSPC */ + goto put_old_es; +@@ -1047,13 +1069,23 @@ static int exfat_rename_file(struct inod + epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM); + *epnew = *epold; + +- exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); ++ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname, ++ &old_es, num_extra_entries); + + ret = exfat_put_dentry_set(&new_es, sync); +- if (ret) ++ if (ret) { ++ /* Best-effort delete to avoid duplicate entries */ ++ if (!exfat_get_dentry_set(&new_es, sb, ++ &dir, newentry, ++ ES_ALL_ENTRIES)) { ++ exfat_remove_entries(parent_inode, &new_es, ++ ES_IDX_FILE, false); ++ exfat_put_dentry_set(&new_es, false); ++ } + goto put_old_es; ++ } + +- exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE); ++ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE, false); + ei->dir = dir; + ei->entry = newentry; + } else { +@@ -1062,8 +1094,8 @@ static int exfat_rename_file(struct inod + ei->attr |= EXFAT_ATTR_ARCHIVE; + } + +- exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1); +- exfat_init_ext_entry(&old_es, num_new_entries, p_uniname); ++ exfat_init_ext_entry(&old_es, num_new_entries, p_uniname, ++ &old_es, num_extra_entries); + } + return exfat_put_dentry_set(&old_es, sync); + +@@ -1079,6 +1111,7 @@ static int exfat_move_file(struct inode + struct exfat_dentry *epmov, *epnew; + struct exfat_entry_set_cache mov_es, new_es; + struct exfat_chain newdir; ++ unsigned int num_extra_entries, num_total_entries; + + num_new_entries = exfat_calc_num_entries(p_uniname); + if (num_new_entries < 0) +@@ -1088,8 +1121,14 @@ static int exfat_move_file(struct inode + if (ret) + return -EIO; + ++ ret = exfat_count_extra_entries(&mov_es); ++ if (ret < 0) ++ goto put_mov_es; ++ num_extra_entries = ret; ++ num_total_entries = num_new_entries + num_extra_entries; ++ + newentry = exfat_find_empty_entry(parent_inode, &newdir, +- num_new_entries, &new_es); ++ num_total_entries, &new_es); + if (newentry < 0) { + ret = newentry; /* -EIO or -ENOSPC */ + goto put_mov_es; +@@ -1107,21 +1146,31 @@ static int exfat_move_file(struct inode + epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM); + *epnew = *epmov; + +- exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); +- exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE); ++ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname, ++ &mov_es, num_extra_entries); ++ ++ exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false); + + ei->dir = newdir; + ei->entry = newentry; + + ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode)); +- if (ret) ++ if (ret) { ++ /* Best-effort delete to avoid duplicate entries */ ++ if (!exfat_get_dentry_set(&new_es, parent_inode->i_sb, ++ &newdir, newentry, ++ ES_ALL_ENTRIES)) { ++ exfat_remove_entries(parent_inode, &new_es, ++ ES_IDX_FILE, false); ++ exfat_put_dentry_set(&new_es, false); ++ } + goto put_mov_es; ++ } + + return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode)); + + put_mov_es: + exfat_put_dentry_set(&mov_es, false); +- + return ret; + } + +@@ -1195,7 +1244,7 @@ static int __exfat_rename(struct inode * + goto del_out; + } + +- exfat_remove_entries(new_inode, &es, ES_IDX_FILE); ++ exfat_remove_entries(new_inode, &es, ES_IDX_FILE, true); + + ret = exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode)); + if (ret) diff --git a/queue-6.12/exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch b/queue-6.12/exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch new file mode 100644 index 0000000000..37c1056035 --- /dev/null +++ b/queue-6.12/exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch @@ -0,0 +1,84 @@ +From stable+bounces-278320-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:13 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:28:16 -0400 +Subject: exfat: remove unnecessary read entry in __exfat_rename() +To: stable@vger.kernel.org +Cc: Yuezhang Mo , Aoyama Wataru , Daniel Palmer , Sungjong Seo , Namjae Jeon , Sasha Levin +Message-ID: <20260721012821.3459448-1-sashal@kernel.org> + +From: Yuezhang Mo + +[ Upstream commit 30ef0e0d7ff5b6dceda19d18a85d9d72a4909784 ] + +To determine whether it is a directory, there is no need to read its +directory entry, just use S_ISDIR(inode->i_mode). + +Signed-off-by: Yuezhang Mo +Reviewed-by: Aoyama Wataru +Reviewed-by: Daniel Palmer +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/exfat/namei.c | 20 ++++---------------- + 1 file changed, 4 insertions(+), 16 deletions(-) + +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -1145,17 +1145,12 @@ static int __exfat_rename(struct inode * + int ret; + int dentry; + struct exfat_chain olddir, newdir; +- struct exfat_chain *p_dir = NULL; + struct exfat_uni_name uni_name; +- struct exfat_dentry *ep; + struct super_block *sb = old_parent_inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + const unsigned char *new_path = new_dentry->d_name.name; + struct inode *new_inode = new_dentry->d_inode; + struct exfat_inode_info *new_ei = NULL; +- unsigned int new_entry_type = TYPE_UNUSED; +- int new_entry = 0; +- struct buffer_head *new_bh = NULL; + + /* check the validity of pointer parameters */ + if (new_path == NULL || strlen(new_path) == 0) +@@ -1181,17 +1176,8 @@ static int __exfat_rename(struct inode * + goto out; + } + +- p_dir = &(new_ei->dir); +- new_entry = new_ei->entry; +- ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh); +- if (!ep) +- goto out; +- +- new_entry_type = exfat_get_entry_type(ep); +- brelse(new_bh); +- + /* if new_inode exists, update ei */ +- if (new_entry_type == TYPE_DIR) { ++ if (S_ISDIR(new_inode->i_mode)) { + struct exfat_chain new_clu; + + new_clu.dir = new_ei->start_clu; +@@ -1223,6 +1209,8 @@ static int __exfat_rename(struct inode * + + if (!ret && new_inode) { + struct exfat_entry_set_cache es; ++ struct exfat_chain *p_dir = &(new_ei->dir); ++ int new_entry = new_ei->entry; + + /* delete entries of new_dir */ + ret = exfat_get_dentry_set(&es, sb, p_dir, new_entry, +@@ -1239,7 +1227,7 @@ static int __exfat_rename(struct inode * + goto del_out; + + /* Free the clusters if new_inode is a dir(as if exfat_rmdir) */ +- if (new_entry_type == TYPE_DIR && ++ if (S_ISDIR(new_inode->i_mode) && + new_ei->start_clu != EXFAT_EOF_CLUSTER) { + /* new_ei, new_clu_to_free */ + struct exfat_chain new_clu_to_free; diff --git a/queue-6.12/exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch b/queue-6.12/exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch new file mode 100644 index 0000000000..b13027c012 --- /dev/null +++ b/queue-6.12/exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch @@ -0,0 +1,127 @@ +From stable+bounces-278321-greg=kroah.com@vger.kernel.org Tue Jul 21 03:29:17 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 21:28:17 -0400 +Subject: exfat: rename argument name for exfat_move_file and exfat_rename_file +To: stable@vger.kernel.org +Cc: Yuezhang Mo , Sungjong Seo , Namjae Jeon , Sasha Levin +Message-ID: <20260721012821.3459448-2-sashal@kernel.org> + +From: Yuezhang Mo + +[ Upstream commit 06a2b0b3b490a6103376652c01c3ac6e8e22e654 ] + +In this exfat implementation, the relationship between inode and ei +is ei=EXFAT_I(inode). However, in the arguments of exfat_move_file() +and exfat_rename_file(), argument 'inode' indicates the parent +directory, but argument 'ei' indicates the target file to be renamed. +They do not have the above relationship, which is not friendly to code +readers. + +So this commit renames 'inode' to 'parent_inode', making the argument +name match its role. + +Signed-off-by: Yuezhang Mo +Reviewed-by: Sungjong Seo +Signed-off-by: Namjae Jeon +Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/exfat/namei.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -1009,15 +1009,15 @@ unlock: + return err; + } + +-static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, ++static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir, + int oldentry, struct exfat_uni_name *p_uniname, + struct exfat_inode_info *ei) + { + int ret, num_new_entries; + struct exfat_dentry *epold, *epnew; +- struct super_block *sb = inode->i_sb; ++ struct super_block *sb = parent_inode->i_sb; + struct exfat_entry_set_cache old_es, new_es; +- int sync = IS_DIRSYNC(inode); ++ int sync = IS_DIRSYNC(parent_inode); + + if (unlikely(exfat_forced_shutdown(sb))) + return -EIO; +@@ -1037,7 +1037,7 @@ static int exfat_rename_file(struct inod + if (old_es.num_entries < num_new_entries) { + int newentry; + +- newentry = exfat_find_empty_entry(inode, p_dir, num_new_entries, ++ newentry = exfat_find_empty_entry(parent_inode, p_dir, num_new_entries, + &new_es); + if (newentry < 0) { + ret = newentry; /* -EIO or -ENOSPC */ +@@ -1061,7 +1061,7 @@ static int exfat_rename_file(struct inod + if (ret) + goto put_old_es; + +- exfat_remove_entries(inode, &old_es, ES_IDX_FILE); ++ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE); + ei->dir = *p_dir; + ei->entry = newentry; + } else { +@@ -1070,7 +1070,7 @@ static int exfat_rename_file(struct inod + ei->attr |= EXFAT_ATTR_ARCHIVE; + } + +- exfat_remove_entries(inode, &old_es, ES_IDX_FIRST_FILENAME + 1); ++ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1); + exfat_init_ext_entry(&old_es, num_new_entries, p_uniname); + } + return exfat_put_dentry_set(&old_es, sync); +@@ -1080,13 +1080,13 @@ put_old_es: + return ret; + } + +-static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, ++static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir, + int oldentry, struct exfat_chain *p_newdir, + struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) + { + int ret, newentry, num_new_entries; + struct exfat_dentry *epmov, *epnew; +- struct super_block *sb = inode->i_sb; ++ struct super_block *sb = parent_inode->i_sb; + struct exfat_entry_set_cache mov_es, new_es; + + num_new_entries = exfat_calc_num_entries(p_uniname); +@@ -1098,7 +1098,7 @@ static int exfat_move_file(struct inode + if (ret) + return -EIO; + +- newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries, ++ newentry = exfat_find_empty_entry(parent_inode, p_newdir, num_new_entries, + &new_es); + if (newentry < 0) { + ret = newentry; /* -EIO or -ENOSPC */ +@@ -1118,18 +1118,18 @@ static int exfat_move_file(struct inode + *epnew = *epmov; + + exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); +- exfat_remove_entries(inode, &mov_es, ES_IDX_FILE); ++ exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE); + + exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, + p_newdir->flags); + + ei->entry = newentry; + +- ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(inode)); ++ ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode)); + if (ret) + goto put_mov_es; + +- return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(inode)); ++ return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode)); + + put_mov_es: + exfat_put_dentry_set(&mov_es, false); diff --git a/queue-6.12/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch b/queue-6.12/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch new file mode 100644 index 0000000000..1c2c8c8710 --- /dev/null +++ b/queue-6.12/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch @@ -0,0 +1,164 @@ +From stable+bounces-274889-greg=kroah.com@vger.kernel.org Wed Jul 15 13:15:26 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 07:15:02 -0400 +Subject: gpio: sch: use raw_spinlock_t in the irq startup path +To: stable@vger.kernel.org +Cc: Runyu Xiao , Sebastian Andrzej Siewior , Andy Shevchenko , Bartosz Golaszewski , Sasha Levin +Message-ID: <20260715111502.642327-1-sashal@kernel.org> + +From: Runyu Xiao + +[ Upstream commit 286533cb14a3c8a8bd39ff64ea2fc8e1aa0f638b ] + +sch_irq_unmask() enables the GPIO IRQ and then updates the controller +state through sch_irq_mask_unmask(), which takes sch->lock with +spin_lock_irqsave(). The callback can be reached from irq_startup() +while setting up a requested IRQ. That path is not sleepable, but on +PREEMPT_RT a regular spinlock_t becomes a sleeping lock. + +This issue was found by our static analysis tool and then manually +reviewed against the current tree. + +The grounded PoC kept the request_threaded_irq() -> __setup_irq() -> +irq_startup() -> sch_irq_unmask() -> sch_irq_mask_unmask() carrier and +used the original spin_lock_irqsave(&sch->lock) edge. Lockdep reported: + + BUG: sleeping function called from invalid context + hardirqs last disabled at ... __setup_irq.constprop.0 ... [vuln_msv] + sch_rt_spin_lock_irqsave+0x1c/0x30 [vuln_msv] + sch_irq_mask_unmask.constprop.0+0x31/0x70 [vuln_msv] + __setup_irq.constprop.0+0xd/0x30 [vuln_msv] + +Convert the SCH controller lock to raw_spinlock_t. The same lock is +also used by the GPIO direction and value callbacks, but those critical +sections only update MMIO-backed GPIO registers and do not 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. + +Fixes: 7a81638485c1 ("gpio: sch: Add edge event support") +Cc: stable@vger.kernel.org +Signed-off-by: Runyu Xiao +Reviewed-by: Sebastian Andrzej Siewior +Reviewed-by: Andy Shevchenko +Link: https://patch.msgid.link/20260617154035.1199948-2-runyu.xiao@seu.edu.cn +Signed-off-by: Bartosz Golaszewski +[ dropped the `return 0;` and kept sch_gpio_set()'s void signature since 6.12 lacks the GPIO setter-return-value conversion ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/gpio/gpio-sch.c | 32 ++++++++++++++++---------------- + 1 file changed, 16 insertions(+), 16 deletions(-) + +--- a/drivers/gpio/gpio-sch.c ++++ b/drivers/gpio/gpio-sch.c +@@ -39,7 +39,7 @@ + struct sch_gpio { + struct gpio_chip chip; + void __iomem *regs; +- spinlock_t lock; ++ raw_spinlock_t lock; + unsigned short resume_base; + + /* GPE handling */ +@@ -104,9 +104,9 @@ static int sch_gpio_direction_in(struct + struct sch_gpio *sch = gpiochip_get_data(gc); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GIO, 1); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + return 0; + } + +@@ -122,9 +122,9 @@ static void sch_gpio_set(struct gpio_chi + struct sch_gpio *sch = gpiochip_get_data(gc); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GLV, val); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + } + + static int sch_gpio_direction_out(struct gpio_chip *gc, unsigned int gpio_num, +@@ -133,9 +133,9 @@ static int sch_gpio_direction_out(struct + struct sch_gpio *sch = gpiochip_get_data(gc); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GIO, 0); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + + /* + * according to the datasheet, writing to the level register has no +@@ -195,14 +195,14 @@ static int sch_irq_type(struct irq_data + return -EINVAL; + } + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + + sch_gpio_reg_set(sch, gpio_num, GTPE, rising); + sch_gpio_reg_set(sch, gpio_num, GTNE, falling); + + irq_set_handler_locked(d, handle_edge_irq); + +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + + return 0; + } +@@ -214,9 +214,9 @@ static void sch_irq_ack(struct irq_data + irq_hw_number_t gpio_num = irqd_to_hwirq(d); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GTS, 1); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + } + + static void sch_irq_mask_unmask(struct gpio_chip *gc, irq_hw_number_t gpio_num, int val) +@@ -224,9 +224,9 @@ static void sch_irq_mask_unmask(struct g + struct sch_gpio *sch = gpiochip_get_data(gc); + unsigned long flags; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + sch_gpio_reg_set(sch, gpio_num, GGPE, val); +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + } + + static void sch_irq_mask(struct irq_data *d) +@@ -267,12 +267,12 @@ static u32 sch_gpio_gpe_handler(acpi_han + int offset; + u32 ret; + +- spin_lock_irqsave(&sch->lock, flags); ++ raw_spin_lock_irqsave(&sch->lock, flags); + + core_status = ioread32(sch->regs + CORE_BANK_OFFSET + GTS); + resume_status = ioread32(sch->regs + RESUME_BANK_OFFSET + GTS); + +- spin_unlock_irqrestore(&sch->lock, flags); ++ raw_spin_unlock_irqrestore(&sch->lock, flags); + + pending = (resume_status << sch->resume_base) | core_status; + for_each_set_bit(offset, &pending, sch->chip.ngpio) +@@ -342,7 +342,7 @@ static int sch_gpio_probe(struct platfor + + sch->regs = regs; + +- spin_lock_init(&sch->lock); ++ raw_spin_lock_init(&sch->lock); + sch->chip = sch_gpio_chip; + sch->chip.label = dev_name(dev); + sch->chip.parent = dev; diff --git a/queue-6.12/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch b/queue-6.12/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch new file mode 100644 index 0000000000..336f24b96b --- /dev/null +++ b/queue-6.12/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch @@ -0,0 +1,69 @@ +From stable+bounces-277225-greg=kroah.com@vger.kernel.org Sat Jul 18 02:51:24 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 20:51:15 -0400 +Subject: hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length +To: stable@vger.kernel.org +Cc: Tristan Madani , syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com, syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com, Viacheslav Dubeyko , Sasha Levin +Message-ID: <20260718005115.2484456-2-sashal@kernel.org> + +From: Tristan Madani + +[ Upstream commit 966cb76fb2857a4242cab6ea2ea17acf818a3da7 ] + +check_and_correct_requested_length() compares (off + len) against +node_size using u32 arithmetic. When the caller passes a large len +value (e.g. from an underflowed subtraction in hfs_brec_remove()), +off + len can wrap past 2^32 and produce a small result, causing the +bounds check to pass when it should fail. + +For example, with off=14 and len=0xFFFFFFF2 (underflowed from +data_off - keyoffset - size in hfs_brec_remove), off + len wraps to 6, +which is less than a typical node_size of 512, so the check passes and +the subsequent memmove reads ~4GB past the node buffer. + +Fix this by widening the addition to u64 before comparing against +node_size. This prevents the u32 wrap while keeping the logic +straightforward. + +Reported-by: syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=6df204b70bf3261691c5 +Tested-by: syzbot+6df204b70bf3261691c5@syzkaller.appspotmail.com +Reported-by: syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=e76bf3d19b85350571ac +Tested-by: syzbot+e76bf3d19b85350571ac@syzkaller.appspotmail.com +Fixes: a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()") +Cc: stable@vger.kernel.org +Signed-off-by: Tristan Madani +Reviewed-by: Viacheslav Dubeyko +Signed-off-by: Viacheslav Dubeyko +Link: https://lore.kernel.org/r/20260505111300.3592757-2-tristmd@gmail.com +Signed-off-by: Viacheslav Dubeyko +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/hfs/bnode.c | 2 +- + fs/hfsplus/hfsplus_fs.h | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +--- a/fs/hfs/bnode.c ++++ b/fs/hfs/bnode.c +@@ -41,7 +41,7 @@ u32 check_and_correct_requested_length(s + + node_size = node->tree->node_size; + +- if ((off + len) > node_size) { ++ if ((u64)off + len > node_size) { + u32 new_len = node_size - off; + + pr_err("requested length has been corrected: " +--- a/fs/hfsplus/hfsplus_fs.h ++++ b/fs/hfsplus/hfsplus_fs.h +@@ -614,7 +614,7 @@ u32 check_and_correct_requested_length(s + + node_size = node->tree->node_size; + +- if ((off + len) > node_size) { ++ if ((u64)off + len > node_size) { + u32 new_len = node_size - off; + + pr_err("requested length has been corrected: " diff --git a/queue-6.12/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch b/queue-6.12/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch new file mode 100644 index 0000000000..45d7ae1fdd --- /dev/null +++ b/queue-6.12/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch @@ -0,0 +1,957 @@ +From stable+bounces-277226-greg=kroah.com@vger.kernel.org Sat Jul 18 02:51:25 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 20:51:14 -0400 +Subject: hfs/hfsplus: prevent getting negative values of offset/length +To: stable@vger.kernel.org +Cc: Viacheslav Dubeyko , John Paul Adrian Glaubitz , Yangtao Li , linux-fsdevel@vger.kernel.org, Sasha Levin +Message-ID: <20260718005115.2484456-1-sashal@kernel.org> + +From: Viacheslav Dubeyko + +[ Upstream commit 00c14a09a70e10ae18eb3707d0059291425c04bd ] + +The syzbot reported KASAN out-of-bounds issue in +hfs_bnode_move(): + +[ 45.588165][ T9821] hfs: dst 14, src 65536, len -65536 +[ 45.588895][ T9821] ================================================================== +[ 45.590114][ T9821] BUG: KASAN: out-of-bounds in hfs_bnode_move+0xfd/0x140 +[ 45.591127][ T9821] Read of size 18446744073709486080 at addr ffff888035935400 by task repro/9821 +[ 45.592207][ T9821] +[ 45.592420][ T9821] CPU: 0 UID: 0 PID: 9821 Comm: repro Not tainted 6.16.0-rc7-dirty #42 PREEMPT(full) +[ 45.592428][ T9821] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 +[ 45.592431][ T9821] Call Trace: +[ 45.592434][ T9821] +[ 45.592437][ T9821] dump_stack_lvl+0x1c1/0x2a0 +[ 45.592446][ T9821] ? __virt_addr_valid+0x1c8/0x5c0 +[ 45.592454][ T9821] ? __pfx_dump_stack_lvl+0x10/0x10 +[ 45.592461][ T9821] ? rcu_is_watching+0x15/0xb0 +[ 45.592469][ T9821] ? lock_release+0x4b/0x3e0 +[ 45.592476][ T9821] ? __virt_addr_valid+0x1c8/0x5c0 +[ 45.592483][ T9821] ? __virt_addr_valid+0x4a5/0x5c0 +[ 45.592491][ T9821] print_report+0x17e/0x7c0 +[ 45.592497][ T9821] ? __virt_addr_valid+0x1c8/0x5c0 +[ 45.592504][ T9821] ? __virt_addr_valid+0x4a5/0x5c0 +[ 45.592511][ T9821] ? __phys_addr+0xd3/0x180 +[ 45.592519][ T9821] ? hfs_bnode_move+0xfd/0x140 +[ 45.592526][ T9821] kasan_report+0x147/0x180 +[ 45.592531][ T9821] ? _printk+0xcf/0x120 +[ 45.592537][ T9821] ? hfs_bnode_move+0xfd/0x140 +[ 45.592544][ T9821] ? hfs_bnode_move+0xfd/0x140 +[ 45.592552][ T9821] kasan_check_range+0x2b0/0x2c0 +[ 45.592557][ T9821] ? hfs_bnode_move+0xfd/0x140 +[ 45.592565][ T9821] __asan_memmove+0x29/0x70 +[ 45.592572][ T9821] hfs_bnode_move+0xfd/0x140 +[ 45.592580][ T9821] hfs_brec_remove+0x473/0x560 +[ 45.592589][ T9821] hfs_cat_move+0x6fb/0x960 +[ 45.592598][ T9821] ? __pfx_hfs_cat_move+0x10/0x10 +[ 45.592607][ T9821] ? seqcount_lockdep_reader_access+0x122/0x1c0 +[ 45.592614][ T9821] ? lockdep_hardirqs_on+0x9c/0x150 +[ 45.592631][ T9821] ? __lock_acquire+0xaec/0xd80 +[ 45.592641][ T9821] hfs_rename+0x1dc/0x2d0 +[ 45.592649][ T9821] ? __pfx_hfs_rename+0x10/0x10 +[ 45.592657][ T9821] vfs_rename+0xac6/0xed0 +[ 45.592664][ T9821] ? __pfx_vfs_rename+0x10/0x10 +[ 45.592670][ T9821] ? d_alloc+0x144/0x190 +[ 45.592677][ T9821] ? bpf_lsm_path_rename+0x9/0x20 +[ 45.592683][ T9821] ? security_path_rename+0x17d/0x490 +[ 45.592691][ T9821] do_renameat2+0x890/0xc50 +[ 45.592699][ T9821] ? __pfx_do_renameat2+0x10/0x10 +[ 45.592707][ T9821] ? getname_flags+0x1e5/0x540 +[ 45.592714][ T9821] __x64_sys_rename+0x82/0x90 +[ 45.592720][ T9821] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f +[ 45.592725][ T9821] do_syscall_64+0xf3/0x3a0 +[ 45.592741][ T9821] ? exc_page_fault+0x9f/0xf0 +[ 45.592748][ T9821] entry_SYSCALL_64_after_hwframe+0x77/0x7f +[ 45.592754][ T9821] RIP: 0033:0x7f7f73fe3fc9 +[ 45.592760][ T9821] Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 48 +[ 45.592765][ T9821] RSP: 002b:00007ffc7e116cf8 EFLAGS: 00000283 ORIG_RAX: 0000000000000052 +[ 45.592772][ T9821] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f7f73fe3fc9 +[ 45.592776][ T9821] RDX: 0000200000000871 RSI: 0000200000000780 RDI: 00002000000003c0 +[ 45.592781][ T9821] RBP: 00007ffc7e116d00 R08: 0000000000000000 R09: 00007ffc7e116d30 +[ 45.592784][ T9821] R10: fffffffffffffff0 R11: 0000000000000283 R12: 00005557e81f8250 +[ 45.592788][ T9821] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000 +[ 45.592795][ T9821] +[ 45.592797][ T9821] +[ 45.619721][ T9821] The buggy address belongs to the physical page: +[ 45.620300][ T9821] page: refcount:1 mapcount:1 mapping:0000000000000000 index:0x559a88174 pfn:0x35935 +[ 45.621150][ T9821] memcg:ffff88810a1d5b00 +[ 45.621531][ T9821] anon flags: 0xfff60000020838(uptodate|dirty|lru|owner_2|swapbacked|node=0|zone=1|lastcpupid=0x7ff) +[ 45.622496][ T9821] raw: 00fff60000020838 ffffea0000d64d88 ffff888021753e10 ffff888029da0771 +[ 45.623260][ T9821] raw: 0000000559a88174 0000000000000000 0000000100000000 ffff88810a1d5b00 +[ 45.624030][ T9821] page dumped because: kasan: bad access detected +[ 45.624602][ T9821] page_owner tracks the page as allocated +[ 45.625115][ T9821] page last allocated via order 0, migratetype Movable, gfp_mask 0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_ZERO0 +[ 45.626685][ T9821] post_alloc_hook+0x240/0x2a0 +[ 45.627127][ T9821] get_page_from_freelist+0x2101/0x21e0 +[ 45.627628][ T9821] __alloc_frozen_pages_noprof+0x274/0x380 +[ 45.628154][ T9821] alloc_pages_mpol+0x241/0x4b0 +[ 45.628593][ T9821] vma_alloc_folio_noprof+0xe4/0x210 +[ 45.629066][ T9821] folio_prealloc+0x30/0x180 +[ 45.629487][ T9821] __handle_mm_fault+0x34bd/0x5640 +[ 45.629957][ T9821] handle_mm_fault+0x40e/0x8e0 +[ 45.630392][ T9821] do_user_addr_fault+0xa81/0x1390 +[ 45.630862][ T9821] exc_page_fault+0x76/0xf0 +[ 45.631273][ T9821] asm_exc_page_fault+0x26/0x30 +[ 45.631712][ T9821] page last free pid 5269 tgid 5269 stack trace: +[ 45.632281][ T9821] free_unref_folios+0xc73/0x14c0 +[ 45.632740][ T9821] folios_put_refs+0x55b/0x640 +[ 45.633177][ T9821] free_pages_and_swap_cache+0x26d/0x510 +[ 45.633685][ T9821] tlb_flush_mmu+0x3a0/0x680 +[ 45.634105][ T9821] tlb_finish_mmu+0xd4/0x200 +[ 45.634525][ T9821] exit_mmap+0x44c/0xb70 +[ 45.634914][ T9821] __mmput+0x118/0x420 +[ 45.635286][ T9821] exit_mm+0x1da/0x2c0 +[ 45.635659][ T9821] do_exit+0x652/0x2330 +[ 45.636039][ T9821] do_group_exit+0x21c/0x2d0 +[ 45.636457][ T9821] __x64_sys_exit_group+0x3f/0x40 +[ 45.636915][ T9821] x64_sys_call+0x21ba/0x21c0 +[ 45.637342][ T9821] do_syscall_64+0xf3/0x3a0 +[ 45.637756][ T9821] entry_SYSCALL_64_after_hwframe+0x77/0x7f +[ 45.638290][ T9821] page has been migrated, last migrate reason: numa_misplaced +[ 45.638956][ T9821] +[ 45.639173][ T9821] Memory state around the buggy address: +[ 45.639677][ T9821] ffff888035935300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 45.640397][ T9821] ffff888035935380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 45.641117][ T9821] >ffff888035935400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 45.641837][ T9821] ^ +[ 45.642207][ T9821] ffff888035935480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 45.642929][ T9821] ffff888035935500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 45.643650][ T9821] ================================================================== + +This commit [1] fixes the issue if an offset inside of b-tree node +or length of the request is bigger than b-tree node. However, +this fix is still not ready for negative values +of the offset or length. Moreover, negative values of +the offset or length doesn't make sense for b-tree's +operations. Because we could try to access the memory address +outside of the beginning of memory page's addresses range. +Also, using of negative values make logic very complicated, +unpredictable, and we could access the wrong item(s) +in the b-tree node. + +This patch changes b-tree interface by means of converting +signed integer arguments of offset and length on u32 type. +Such conversion has goal to prevent of using negative values +unintentionally or by mistake in b-tree operations. + +[1] 'commit a431930c9bac ("hfs: fix slab-out-of-bounds in hfs_bnode_read()")' + +Signed-off-by: Viacheslav Dubeyko +cc: John Paul Adrian Glaubitz +cc: Yangtao Li +cc: linux-fsdevel@vger.kernel.org +Link: https://lore.kernel.org/r/20251002200020.2578311-1-slava@dubeyko.com +Signed-off-by: Viacheslav Dubeyko +Stable-dep-of: 966cb76fb285 ("hfs/hfsplus: fix u32 overflow in check_and_correct_requested_length") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/hfs/bfind.c | 2 - + fs/hfs/bnode.c | 52 ++++++++++++++-------------- + fs/hfs/brec.c | 2 - + fs/hfs/btree.c | 2 - + fs/hfs/btree.h | 71 +++++++++++++++++++------------------- + fs/hfs/hfs_fs.h | 88 ++++++++++++++++++++++++++++-------------------- + fs/hfs/inode.c | 3 + + fs/hfsplus/bfind.c | 2 - + fs/hfsplus/bnode.c | 60 ++++++++++++++++---------------- + fs/hfsplus/brec.c | 2 - + fs/hfsplus/btree.c | 2 - + fs/hfsplus/hfsplus_fs.h | 38 ++++++++++---------- + 12 files changed, 171 insertions(+), 153 deletions(-) + +--- a/fs/hfs/bfind.c ++++ b/fs/hfs/bfind.c +@@ -167,7 +167,7 @@ release: + return res; + } + +-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len) ++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len) + { + int res; + +--- a/fs/hfs/bnode.c ++++ b/fs/hfs/bnode.c +@@ -16,14 +16,14 @@ + #include "btree.h" + + static inline +-bool is_bnode_offset_valid(struct hfs_bnode *node, int off) ++bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off) + { + bool is_valid = off < node->tree->node_size; + + if (!is_valid) { + pr_err("requested invalid offset: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d\n", ++ "node_size %u, offset %u\n", + node->this, node->type, node->height, + node->tree->node_size, off); + } +@@ -32,7 +32,7 @@ bool is_bnode_offset_valid(struct hfs_bn + } + + static inline +-int check_and_correct_requested_length(struct hfs_bnode *node, int off, int len) ++u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len) + { + unsigned int node_size; + +@@ -42,12 +42,12 @@ int check_and_correct_requested_length(s + node_size = node->tree->node_size; + + if ((off + len) > node_size) { +- int new_len = (int)node_size - off; ++ u32 new_len = node_size - off; + + pr_err("requested length has been corrected: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, " +- "requested_len %d, corrected_len %d\n", ++ "node_size %u, offset %u, " ++ "requested_len %u, corrected_len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len, new_len); + +@@ -57,12 +57,12 @@ int check_and_correct_requested_length(s + return len; + } + +-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page *page; +- int pagenum; +- int bytes_read; +- int bytes_to_read; ++ u32 pagenum; ++ u32 bytes_read; ++ u32 bytes_to_read; + + memset(buf, 0, len); + +@@ -72,7 +72,7 @@ void hfs_bnode_read(struct hfs_bnode *no + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -88,7 +88,7 @@ void hfs_bnode_read(struct hfs_bnode *no + if (pagenum >= node->tree->pages_per_bnode) + break; + page = node->page[pagenum]; +- bytes_to_read = min_t(int, len - bytes_read, PAGE_SIZE - off); ++ bytes_to_read = min_t(u32, len - bytes_read, PAGE_SIZE - off); + + memcpy_from_page(buf + bytes_read, page, off, bytes_to_read); + +@@ -97,7 +97,7 @@ void hfs_bnode_read(struct hfs_bnode *no + } + } + +-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off) ++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off) + { + __be16 data; + // optimize later... +@@ -105,7 +105,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode + return be16_to_cpu(data); + } + +-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off) ++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off) + { + u8 data; + // optimize later... +@@ -113,10 +113,10 @@ u8 hfs_bnode_read_u8(struct hfs_bnode *n + return data; + } + +-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off) ++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off) + { + struct hfs_btree *tree; +- int key_len; ++ u32 key_len; + + tree = node->tree; + if (node->type == HFS_NODE_LEAF || +@@ -127,14 +127,14 @@ void hfs_bnode_read_key(struct hfs_bnode + + if (key_len > sizeof(hfs_btree_key) || key_len < 1) { + memset(key, 0, sizeof(hfs_btree_key)); +- pr_err("hfs: Invalid key length: %d\n", key_len); ++ pr_err("hfs: Invalid key length: %u\n", key_len); + return; + } + + hfs_bnode_read(node, key, off, key_len); + } + +-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page *page; + +@@ -144,7 +144,7 @@ void hfs_bnode_write(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -159,20 +159,20 @@ void hfs_bnode_write(struct hfs_bnode *n + set_page_dirty(page); + } + +-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data) ++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data) + { + __be16 v = cpu_to_be16(data); + // optimize later... + hfs_bnode_write(node, &v, off, 2); + } + +-void hfs_bnode_write_u8(struct hfs_bnode *node, int off, u8 data) ++void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data) + { + // optimize later... + hfs_bnode_write(node, &data, off, 1); + } + +-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len) ++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len) + { + struct page *page; + +@@ -182,7 +182,7 @@ void hfs_bnode_clear(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -197,8 +197,8 @@ void hfs_bnode_clear(struct hfs_bnode *n + set_page_dirty(page); + } + +-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst, +- struct hfs_bnode *src_node, int src, int len) ++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst, ++ struct hfs_bnode *src_node, u32 src, u32 len) + { + struct page *src_page, *dst_page; + +@@ -218,7 +218,7 @@ void hfs_bnode_copy(struct hfs_bnode *ds + set_page_dirty(dst_page); + } + +-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len) ++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len) + { + struct page *page; + void *ptr; +--- a/fs/hfs/brec.c ++++ b/fs/hfs/brec.c +@@ -62,7 +62,7 @@ u16 hfs_brec_keylen(struct hfs_bnode *no + return retval; + } + +-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len) ++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len) + { + struct hfs_btree *tree; + struct hfs_bnode *node, *new_node; +--- a/fs/hfs/btree.c ++++ b/fs/hfs/btree.c +@@ -259,7 +259,7 @@ static struct hfs_bnode *hfs_bmap_new_bm + } + + /* Make sure @tree has enough space for the @rsvd_nodes */ +-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes) ++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes) + { + struct inode *inode = tree->inode; + u32 count; +--- a/fs/hfs/btree.h ++++ b/fs/hfs/btree.h +@@ -86,48 +86,49 @@ struct hfs_find_data { + + + /* btree.c */ +-extern struct hfs_btree *hfs_btree_open(struct super_block *, u32, btree_keycmp); +-extern void hfs_btree_close(struct hfs_btree *); +-extern void hfs_btree_write(struct hfs_btree *); +-extern int hfs_bmap_reserve(struct hfs_btree *, int); +-extern struct hfs_bnode * hfs_bmap_alloc(struct hfs_btree *); ++extern struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, ++ btree_keycmp keycmp); ++extern void hfs_btree_close(struct hfs_btree *tree); ++extern void hfs_btree_write(struct hfs_btree *tree); ++extern int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes); ++extern struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree); + extern void hfs_bmap_free(struct hfs_bnode *node); + + /* bnode.c */ +-extern void hfs_bnode_read(struct hfs_bnode *, void *, int, int); +-extern u16 hfs_bnode_read_u16(struct hfs_bnode *, int); +-extern u8 hfs_bnode_read_u8(struct hfs_bnode *, int); +-extern void hfs_bnode_read_key(struct hfs_bnode *, void *, int); +-extern void hfs_bnode_write(struct hfs_bnode *, void *, int, int); +-extern void hfs_bnode_write_u16(struct hfs_bnode *, int, u16); +-extern void hfs_bnode_write_u8(struct hfs_bnode *, int, u8); +-extern void hfs_bnode_clear(struct hfs_bnode *, int, int); +-extern void hfs_bnode_copy(struct hfs_bnode *, int, +- struct hfs_bnode *, int, int); +-extern void hfs_bnode_move(struct hfs_bnode *, int, int, int); +-extern void hfs_bnode_dump(struct hfs_bnode *); +-extern void hfs_bnode_unlink(struct hfs_bnode *); +-extern struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *, u32); +-extern struct hfs_bnode *hfs_bnode_find(struct hfs_btree *, u32); +-extern void hfs_bnode_unhash(struct hfs_bnode *); +-extern void hfs_bnode_free(struct hfs_bnode *); +-extern struct hfs_bnode *hfs_bnode_create(struct hfs_btree *, u32); +-extern void hfs_bnode_get(struct hfs_bnode *); +-extern void hfs_bnode_put(struct hfs_bnode *); ++extern void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len); ++extern u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off); ++extern u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off); ++extern void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off); ++extern void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len); ++extern void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data); ++extern void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data); ++extern void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len); ++extern void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst, ++ struct hfs_bnode *src_node, u32 src, u32 len); ++extern void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len); ++extern void hfs_bnode_dump(struct hfs_bnode *node); ++extern void hfs_bnode_unlink(struct hfs_bnode *node); ++extern struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid); ++extern struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num); ++extern void hfs_bnode_unhash(struct hfs_bnode *node); ++extern void hfs_bnode_free(struct hfs_bnode *node); ++extern struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num); ++extern void hfs_bnode_get(struct hfs_bnode *node); ++extern void hfs_bnode_put(struct hfs_bnode *node); + + /* brec.c */ +-extern u16 hfs_brec_lenoff(struct hfs_bnode *, u16, u16 *); +-extern u16 hfs_brec_keylen(struct hfs_bnode *, u16); +-extern int hfs_brec_insert(struct hfs_find_data *, void *, int); +-extern int hfs_brec_remove(struct hfs_find_data *); ++extern u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off); ++extern u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec); ++extern int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len); ++extern int hfs_brec_remove(struct hfs_find_data *fd); + + /* bfind.c */ +-extern int hfs_find_init(struct hfs_btree *, struct hfs_find_data *); +-extern void hfs_find_exit(struct hfs_find_data *); +-extern int __hfs_brec_find(struct hfs_bnode *, struct hfs_find_data *); +-extern int hfs_brec_find(struct hfs_find_data *); +-extern int hfs_brec_read(struct hfs_find_data *, void *, int); +-extern int hfs_brec_goto(struct hfs_find_data *, int); ++extern int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd); ++extern void hfs_find_exit(struct hfs_find_data *fd); ++extern int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd); ++extern int hfs_brec_find(struct hfs_find_data *fd); ++extern int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len); ++extern int hfs_brec_goto(struct hfs_find_data *fd, int cnt); + + + struct hfs_bnode_desc { +--- a/fs/hfs/hfs_fs.h ++++ b/fs/hfs/hfs_fs.h +@@ -171,74 +171,90 @@ struct hfs_sb_info { + #define HFS_FLG_ALT_MDB_DIRTY 2 + + /* bitmap.c */ +-extern u32 hfs_vbm_search_free(struct super_block *, u32, u32 *); +-extern int hfs_clear_vbm_bits(struct super_block *, u16, u16); ++extern u32 hfs_vbm_search_free(struct super_block *sb, u32 goal, u32 *num_bits); ++extern int hfs_clear_vbm_bits(struct super_block *sb, u16 start, u16 count); + + /* catalog.c */ +-extern int hfs_cat_keycmp(const btree_key *, const btree_key *); ++extern int hfs_cat_keycmp(const btree_key *key1, const btree_key *key2); + struct hfs_find_data; +-extern int hfs_cat_find_brec(struct super_block *, u32, struct hfs_find_data *); +-extern int hfs_cat_create(u32, struct inode *, const struct qstr *, struct inode *); +-extern int hfs_cat_delete(u32, struct inode *, const struct qstr *); +-extern int hfs_cat_move(u32, struct inode *, const struct qstr *, +- struct inode *, const struct qstr *); +-extern void hfs_cat_build_key(struct super_block *, btree_key *, u32, const struct qstr *); ++extern int hfs_cat_find_brec(struct super_block *sb, u32 cnid, ++ struct hfs_find_data *fd); ++extern int hfs_cat_create(u32 cnid, struct inode *dir, ++ const struct qstr *str, struct inode *inode); ++extern int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str); ++extern int hfs_cat_move(u32 cnid, struct inode *src_dir, ++ const struct qstr *src_name, ++ struct inode *dst_dir, ++ const struct qstr *dst_name); ++extern void hfs_cat_build_key(struct super_block *sb, btree_key *key, ++ u32 parent, const struct qstr *name); + + /* dir.c */ + extern const struct file_operations hfs_dir_operations; + extern const struct inode_operations hfs_dir_inode_operations; + + /* extent.c */ +-extern int hfs_ext_keycmp(const btree_key *, const btree_key *); ++extern int hfs_ext_keycmp(const btree_key *key1, const btree_key *key2); + extern u16 hfs_ext_find_block(struct hfs_extent *ext, u16 off); +-extern int hfs_free_fork(struct super_block *, struct hfs_cat_file *, int); +-extern int hfs_ext_write_extent(struct inode *); +-extern int hfs_extend_file(struct inode *); +-extern void hfs_file_truncate(struct inode *); ++extern int hfs_free_fork(struct super_block *sb, ++ struct hfs_cat_file *file, int type); ++extern int hfs_ext_write_extent(struct inode *inode); ++extern int hfs_extend_file(struct inode *inode); ++extern void hfs_file_truncate(struct inode *inode); + +-extern int hfs_get_block(struct inode *, sector_t, struct buffer_head *, int); ++extern int hfs_get_block(struct inode *inode, sector_t block, ++ struct buffer_head *bh_result, int create); + + /* inode.c */ + extern const struct address_space_operations hfs_aops; + extern const struct address_space_operations hfs_btree_aops; + + int hfs_write_begin(struct file *file, struct address_space *mapping, +- loff_t pos, unsigned len, struct folio **foliop, void **fsdata); +-extern struct inode *hfs_new_inode(struct inode *, const struct qstr *, umode_t); +-extern void hfs_inode_write_fork(struct inode *, struct hfs_extent *, __be32 *, __be32 *); +-extern int hfs_write_inode(struct inode *, struct writeback_control *); +-extern int hfs_inode_setattr(struct mnt_idmap *, struct dentry *, +- struct iattr *); ++ loff_t pos, unsigned int len, struct folio **foliop, ++ void **fsdata); ++extern struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, ++ umode_t mode); ++extern void hfs_inode_write_fork(struct inode *inode, struct hfs_extent *ext, ++ __be32 *log_size, __be32 *phys_size); ++extern int hfs_write_inode(struct inode *inode, struct writeback_control *wbc); ++extern int hfs_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, ++ struct iattr *attr); + extern void hfs_inode_read_fork(struct inode *inode, struct hfs_extent *ext, +- __be32 log_size, __be32 phys_size, u32 clump_size); +-extern struct inode *hfs_iget(struct super_block *, struct hfs_cat_key *, hfs_cat_rec *); +-extern void hfs_evict_inode(struct inode *); +-extern void hfs_delete_inode(struct inode *); ++ __be32 __log_size, __be32 phys_size, ++ u32 clump_size); ++extern struct inode *hfs_iget(struct super_block *sb, struct hfs_cat_key *key, ++ hfs_cat_rec *rec); ++extern void hfs_evict_inode(struct inode *inode); ++extern void hfs_delete_inode(struct inode *inode); + + /* attr.c */ + extern const struct xattr_handler * const hfs_xattr_handlers[]; + + /* mdb.c */ +-extern int hfs_mdb_get(struct super_block *); +-extern void hfs_mdb_commit(struct super_block *); +-extern void hfs_mdb_close(struct super_block *); +-extern void hfs_mdb_put(struct super_block *); ++extern int hfs_mdb_get(struct super_block *sb); ++extern void hfs_mdb_commit(struct super_block *sb); ++extern void hfs_mdb_close(struct super_block *sb); ++extern void hfs_mdb_put(struct super_block *sb); + + /* part_tbl.c */ +-extern int hfs_part_find(struct super_block *, sector_t *, sector_t *); ++extern int hfs_part_find(struct super_block *sb, ++ sector_t *part_start, sector_t *part_size); + + /* string.c */ + extern const struct dentry_operations hfs_dentry_operations; + +-extern int hfs_hash_dentry(const struct dentry *, struct qstr *); +-extern int hfs_strcmp(const unsigned char *, unsigned int, +- const unsigned char *, unsigned int); ++extern int hfs_hash_dentry(const struct dentry *dentry, struct qstr *this); ++extern int hfs_strcmp(const unsigned char *s1, unsigned int len1, ++ const unsigned char *s2, unsigned int len2); + extern int hfs_compare_dentry(const struct dentry *dentry, +- unsigned int len, const char *str, const struct qstr *name); ++ unsigned int len, const char *str, ++ const struct qstr *name); + + /* trans.c */ +-extern void hfs_asc2mac(struct super_block *, struct hfs_name *, const struct qstr *); +-extern int hfs_mac2asc(struct super_block *, char *, const struct hfs_name *); ++extern void hfs_asc2mac(struct super_block *sb, ++ struct hfs_name *out, const struct qstr *in); ++extern int hfs_mac2asc(struct super_block *sb, ++ char *out, const struct hfs_name *in); + + /* super.c */ + extern void hfs_mark_mdb_dirty(struct super_block *sb); +--- a/fs/hfs/inode.c ++++ b/fs/hfs/inode.c +@@ -45,7 +45,8 @@ static void hfs_write_failed(struct addr + } + + int hfs_write_begin(struct file *file, struct address_space *mapping, +- loff_t pos, unsigned len, struct folio **foliop, void **fsdata) ++ loff_t pos, unsigned int len, struct folio **foliop, ++ void **fsdata) + { + int ret; + +--- a/fs/hfsplus/bfind.c ++++ b/fs/hfsplus/bfind.c +@@ -210,7 +210,7 @@ release: + return res; + } + +-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len) ++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len) + { + int res; + +--- a/fs/hfsplus/bnode.c ++++ b/fs/hfsplus/bnode.c +@@ -20,10 +20,10 @@ + + + /* Copy a specified range of bytes from the raw data of a node */ +-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page **pagep; +- int l; ++ u32 l; + + memset(buf, 0, len); + +@@ -33,7 +33,7 @@ void hfs_bnode_read(struct hfs_bnode *no + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -45,17 +45,17 @@ void hfs_bnode_read(struct hfs_bnode *no + pagep = node->page + (off >> PAGE_SHIFT); + off &= ~PAGE_MASK; + +- l = min_t(int, len, PAGE_SIZE - off); ++ l = min_t(u32, len, PAGE_SIZE - off); + memcpy_from_page(buf, *pagep, off, l); + + while ((len -= l) != 0) { + buf += l; +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memcpy_from_page(buf, *++pagep, 0, l); + } + } + +-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off) ++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off) + { + __be16 data; + /* TODO: optimize later... */ +@@ -63,7 +63,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode + return be16_to_cpu(data); + } + +-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off) ++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off) + { + u8 data; + /* TODO: optimize later... */ +@@ -71,10 +71,10 @@ u8 hfs_bnode_read_u8(struct hfs_bnode *n + return data; + } + +-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off) ++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off) + { + struct hfs_btree *tree; +- int key_len; ++ u32 key_len; + + tree = node->tree; + if (node->type == HFS_NODE_LEAF || +@@ -86,17 +86,17 @@ void hfs_bnode_read_key(struct hfs_bnode + + if (key_len > sizeof(hfsplus_btree_key) || key_len < 1) { + memset(key, 0, sizeof(hfsplus_btree_key)); +- pr_err("hfsplus: Invalid key length: %d\n", key_len); ++ pr_err("hfsplus: Invalid key length: %u\n", key_len); + return; + } + + hfs_bnode_read(node, key, off, key_len); + } + +-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page **pagep; +- int l; ++ u32 l; + + if (!is_bnode_offset_valid(node, off)) + return; +@@ -104,7 +104,7 @@ void hfs_bnode_write(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -116,29 +116,29 @@ void hfs_bnode_write(struct hfs_bnode *n + pagep = node->page + (off >> PAGE_SHIFT); + off &= ~PAGE_MASK; + +- l = min_t(int, len, PAGE_SIZE - off); ++ l = min_t(u32, len, PAGE_SIZE - off); + memcpy_to_page(*pagep, off, buf, l); + set_page_dirty(*pagep); + + while ((len -= l) != 0) { + buf += l; +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memcpy_to_page(*++pagep, 0, buf, l); + set_page_dirty(*pagep); + } + } + +-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data) ++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data) + { + __be16 v = cpu_to_be16(data); + /* TODO: optimize later... */ + hfs_bnode_write(node, &v, off, 2); + } + +-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len) ++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len) + { + struct page **pagep; +- int l; ++ u32 l; + + if (!is_bnode_offset_valid(node, off)) + return; +@@ -146,7 +146,7 @@ void hfs_bnode_clear(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -158,22 +158,22 @@ void hfs_bnode_clear(struct hfs_bnode *n + pagep = node->page + (off >> PAGE_SHIFT); + off &= ~PAGE_MASK; + +- l = min_t(int, len, PAGE_SIZE - off); ++ l = min_t(u32, len, PAGE_SIZE - off); + memzero_page(*pagep, off, l); + set_page_dirty(*pagep); + + while ((len -= l) != 0) { +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memzero_page(*++pagep, 0, l); + set_page_dirty(*pagep); + } + } + +-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst, +- struct hfs_bnode *src_node, int src, int len) ++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst, ++ struct hfs_bnode *src_node, u32 src, u32 len) + { + struct page **src_page, **dst_page; +- int l; ++ u32 l; + + hfs_dbg(BNODE_MOD, "copybytes: %u,%u,%u\n", dst, src, len); + if (!len) +@@ -190,12 +190,12 @@ void hfs_bnode_copy(struct hfs_bnode *ds + dst &= ~PAGE_MASK; + + if (src == dst) { +- l = min_t(int, len, PAGE_SIZE - src); ++ l = min_t(u32, len, PAGE_SIZE - src); + memcpy_page(*dst_page, src, *src_page, src, l); + set_page_dirty(*dst_page); + + while ((len -= l) != 0) { +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memcpy_page(*++dst_page, 0, *++src_page, 0, l); + set_page_dirty(*dst_page); + } +@@ -227,11 +227,11 @@ void hfs_bnode_copy(struct hfs_bnode *ds + } + } + +-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len) ++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len) + { + struct page **src_page, **dst_page; + void *src_ptr, *dst_ptr; +- int l; ++ u32 l; + + hfs_dbg(BNODE_MOD, "movebytes: %u,%u,%u\n", dst, src, len); + if (!len) +@@ -301,7 +301,7 @@ void hfs_bnode_move(struct hfs_bnode *no + dst &= ~PAGE_MASK; + + if (src == dst) { +- l = min_t(int, len, PAGE_SIZE - src); ++ l = min_t(u32, len, PAGE_SIZE - src); + + dst_ptr = kmap_local_page(*dst_page) + src; + src_ptr = kmap_local_page(*src_page) + src; +@@ -311,7 +311,7 @@ void hfs_bnode_move(struct hfs_bnode *no + kunmap_local(dst_ptr); + + while ((len -= l) != 0) { +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + dst_ptr = kmap_local_page(*++dst_page); + src_ptr = kmap_local_page(*++src_page); + memmove(dst_ptr, src_ptr, l); +--- a/fs/hfsplus/brec.c ++++ b/fs/hfsplus/brec.c +@@ -60,7 +60,7 @@ u16 hfs_brec_keylen(struct hfs_bnode *no + return retval; + } + +-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len) ++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len) + { + struct hfs_btree *tree; + struct hfs_bnode *node, *new_node; +--- a/fs/hfsplus/btree.c ++++ b/fs/hfsplus/btree.c +@@ -344,7 +344,7 @@ static struct hfs_bnode *hfs_bmap_new_bm + } + + /* Make sure @tree has enough space for the @rsvd_nodes */ +-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes) ++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes) + { + struct inode *inode = tree->inode; + struct hfsplus_inode_info *hip = HFSPLUS_I(inode); +--- a/fs/hfsplus/hfsplus_fs.h ++++ b/fs/hfsplus/hfsplus_fs.h +@@ -389,21 +389,21 @@ u32 hfsplus_calc_btree_clump_size(u32 bl + struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id); + void hfs_btree_close(struct hfs_btree *tree); + int hfs_btree_write(struct hfs_btree *tree); +-int hfs_bmap_reserve(struct hfs_btree *tree, int rsvd_nodes); ++int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes); + struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree); + void hfs_bmap_free(struct hfs_bnode *node); + + /* bnode.c */ +-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len); +-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off); +-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off); +-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off); +-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len); +-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data); +-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len); +-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst, +- struct hfs_bnode *src_node, int src, int len); +-void hfs_bnode_move(struct hfs_bnode *node, int dst, int src, int len); ++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len); ++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off); ++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off); ++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off); ++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len); ++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data); ++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len); ++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst, ++ struct hfs_bnode *src_node, u32 src, u32 len); ++void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len); + void hfs_bnode_dump(struct hfs_bnode *node); + void hfs_bnode_unlink(struct hfs_bnode *node); + struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid); +@@ -418,7 +418,7 @@ bool hfs_bnode_need_zeroout(struct hfs_b + /* brec.c */ + u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off); + u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec); +-int hfs_brec_insert(struct hfs_find_data *fd, void *entry, int entry_len); ++int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len); + int hfs_brec_remove(struct hfs_find_data *fd); + + /* bfind.c */ +@@ -431,7 +431,7 @@ int hfs_find_rec_by_key(struct hfs_bnode + int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd, + search_strategy_t rec_found); + int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare); +-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len); ++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len); + int hfs_brec_goto(struct hfs_find_data *fd, int cnt); + + /* catalog.c */ +@@ -589,14 +589,14 @@ hfsplus_btree_lock_class(struct hfs_btre + } + + static inline +-bool is_bnode_offset_valid(struct hfs_bnode *node, int off) ++bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off) + { + bool is_valid = off < node->tree->node_size; + + if (!is_valid) { + pr_err("requested invalid offset: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d\n", ++ "node_size %u, offset %u\n", + node->this, node->type, node->height, + node->tree->node_size, off); + } +@@ -605,7 +605,7 @@ bool is_bnode_offset_valid(struct hfs_bn + } + + static inline +-int check_and_correct_requested_length(struct hfs_bnode *node, int off, int len) ++u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len) + { + unsigned int node_size; + +@@ -615,12 +615,12 @@ int check_and_correct_requested_length(s + node_size = node->tree->node_size; + + if ((off + len) > node_size) { +- int new_len = (int)node_size - off; ++ u32 new_len = node_size - off; + + pr_err("requested length has been corrected: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, " +- "requested_len %d, corrected_len %d\n", ++ "node_size %u, offset %u, " ++ "requested_len %u, corrected_len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len, new_len); + diff --git a/queue-6.12/hid-add-haptics-page-defines.patch b/queue-6.12/hid-add-haptics-page-defines.patch new file mode 100644 index 0000000000..3ef7cf0411 --- /dev/null +++ b/queue-6.12/hid-add-haptics-page-defines.patch @@ -0,0 +1,78 @@ +From stable+bounces-277058-greg=kroah.com@vger.kernel.org Fri Jul 17 16:04:05 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:00:20 -0400 +Subject: HID: add haptics page defines +To: stable@vger.kernel.org +Cc: Angela Czubak , Jonathan Denose , Benjamin Tissoires , Sasha Levin +Message-ID: <20260717140021.1717288-1-sashal@kernel.org> + +From: Angela Czubak + +[ Upstream commit 5e0ae59159e3a07391a35865bb79ff335473fa79 ] + +Introduce haptic usages as defined in HID Usage Tables specification. +Add HID units for newton and gram. + +Signed-off-by: Angela Czubak +Co-developed-by: Jonathan Denose +Signed-off-by: Jonathan Denose +Signed-off-by: Benjamin Tissoires +Stable-dep-of: 8813b0612275 ("HID: multitouch: fix out-of-bounds bit access on mt_io_flags") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/hid.h | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +--- a/include/linux/hid.h ++++ b/include/linux/hid.h +@@ -154,6 +154,7 @@ struct hid_item { + #define HID_UP_TELEPHONY 0x000b0000 + #define HID_UP_CONSUMER 0x000c0000 + #define HID_UP_DIGITIZER 0x000d0000 ++#define HID_UP_HAPTIC 0x000e0000 + #define HID_UP_PID 0x000f0000 + #define HID_UP_BATTERY 0x00850000 + #define HID_UP_CAMERA 0x00900000 +@@ -314,6 +315,28 @@ struct hid_item { + #define HID_DG_TOOLSERIALNUMBER 0x000d005b + #define HID_DG_LATENCYMODE 0x000d0060 + ++#define HID_HP_SIMPLECONTROLLER 0x000e0001 ++#define HID_HP_WAVEFORMLIST 0x000e0010 ++#define HID_HP_DURATIONLIST 0x000e0011 ++#define HID_HP_AUTOTRIGGER 0x000e0020 ++#define HID_HP_MANUALTRIGGER 0x000e0021 ++#define HID_HP_AUTOTRIGGERASSOCIATEDCONTROL 0x000e0022 ++#define HID_HP_INTENSITY 0x000e0023 ++#define HID_HP_REPEATCOUNT 0x000e0024 ++#define HID_HP_RETRIGGERPERIOD 0x000e0025 ++#define HID_HP_WAVEFORMVENDORPAGE 0x000e0026 ++#define HID_HP_WAVEFORMVENDORID 0x000e0027 ++#define HID_HP_WAVEFORMCUTOFFTIME 0x000e0028 ++#define HID_HP_WAVEFORMNONE 0x000e1001 ++#define HID_HP_WAVEFORMSTOP 0x000e1002 ++#define HID_HP_WAVEFORMCLICK 0x000e1003 ++#define HID_HP_WAVEFORMBUZZCONTINUOUS 0x000e1004 ++#define HID_HP_WAVEFORMRUMBLECONTINUOUS 0x000e1005 ++#define HID_HP_WAVEFORMPRESS 0x000e1006 ++#define HID_HP_WAVEFORMRELEASE 0x000e1007 ++#define HID_HP_VENDORWAVEFORMMIN 0x000e2001 ++#define HID_HP_VENDORWAVEFORMMAX 0x000e2fff ++ + #define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065 + #define HID_BAT_CHARGING 0x00850044 + +@@ -420,6 +443,12 @@ struct hid_item { + #define HID_BOOT_PROTOCOL 0 + + /* ++ * HID units ++ */ ++#define HID_UNIT_GRAM 0x0101 ++#define HID_UNIT_NEWTON 0xe111 ++ ++/* + * This is the global environment of the parser. This information is + * persistent for main-items. The global environment can be saved and + * restored with PUSH/POP statements. diff --git a/queue-6.12/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch b/queue-6.12/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch new file mode 100644 index 0000000000..2a2a6d039e --- /dev/null +++ b/queue-6.12/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch @@ -0,0 +1,150 @@ +From stable+bounces-277092-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:46 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:55:02 -0400 +Subject: HID: appleir: fix UAF on pending key_up_timer in remove() +To: stable@vger.kernel.org +Cc: Manish Khadka , Jiri Kosina , Sasha Levin +Message-ID: <20260717145502.1925738-2-sashal@kernel.org> + +From: Manish Khadka + +[ Upstream commit 75fe87e19d8aff81eb2c64d15d244ab8da4de945 ] + +appleir_remove() runs hid_hw_stop() before timer_delete_sync(). +hid_hw_stop() synchronously unregisters the HID input device via +hid_disconnect() -> hidinput_disconnect() -> input_unregister_device(), +which drops the last reference and frees the underlying input_dev when +no userspace handle holds it open. + +key_up_tick() reads appleir->input_dev and calls input_report_key() / +input_sync() on it. The timer is armed from appleir_raw_event() with +a HZ/8 (~125 ms) timeout on every keydown and key-repeat report. If a +key was pressed shortly before the device is disconnected, the timer +can fire after hid_hw_stop() has freed input_dev but before the +teardown drains it. + +A simple reorder is not sufficient. Putting the timer drain first +still leaves a window where a USB URB completion (raw_event) running +during hid_hw_stop() can call mod_timer() and re-arm the timer, which +then fires after hidinput_disconnect() has freed input_dev. The same +URB-completion window also lets raw_event() reach key_up(), key_down() +and battery_flat() directly, all of which dereference +appleir->input_dev. + +Introduce a 'removing' flag on struct appleir, gated by the existing +spinlock. appleir_remove() sets the flag under the lock and then +shuts down the timer with timer_shutdown_sync(), which both drains any +in-flight callback and permanently disables further mod_timer() calls. +appleir_raw_event() and key_up_tick() bail out early if the flag is +set, so no path can arm or run the timer, or dereference +appleir->input_dev, after remove() has started tearing down. + +The keyrepeat and flatbattery branches of appleir_raw_event() +previously called into the input layer without holding the spinlock; +take it now so the flag check is well-defined. This incidentally +closes a pre-existing read-side race on appleir->current_key in the +keyrepeat branch. + +This bug is structurally a sibling of commit 4db2af929279 ("HID: +appletb-kbd: fix UAF in inactivity-timer cleanup path") and has been +present since the driver was introduced. + +Fixes: 9a4a5574ce42 ("HID: appleir: add support for Apple ir devices") +Cc: stable@vger.kernel.org +Signed-off-by: Manish Khadka +Signed-off-by: Jiri Kosina +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/hid/hid-appleir.c | 45 +++++++++++++++++++++++++++++++++++---------- + 1 file changed, 35 insertions(+), 10 deletions(-) + +--- a/drivers/hid/hid-appleir.c ++++ b/drivers/hid/hid-appleir.c +@@ -109,9 +109,10 @@ struct appleir { + struct hid_device *hid; + unsigned short keymap[ARRAY_SIZE(appleir_key_table)]; + struct timer_list key_up_timer; /* timer for key up */ +- spinlock_t lock; /* protects .current_key */ ++ spinlock_t lock; /* protects .current_key, .removing */ + int current_key; /* the currently pressed key */ + int prev_key_idx; /* key index in a 2 packets message */ ++ bool removing; /* set during teardown; gates input_dev access */ + }; + + static int get_key(int data) +@@ -172,7 +173,7 @@ static void key_up_tick(struct timer_lis + unsigned long flags; + + spin_lock_irqsave(&appleir->lock, flags); +- if (appleir->current_key) { ++ if (!appleir->removing && appleir->current_key) { + key_up(hid, appleir, appleir->current_key); + appleir->current_key = 0; + } +@@ -195,6 +196,10 @@ static int appleir_raw_event(struct hid_ + int index; + + spin_lock_irqsave(&appleir->lock, flags); ++ if (appleir->removing) { ++ spin_unlock_irqrestore(&appleir->lock, flags); ++ goto out; ++ } + /* + * If we already have a key down, take it up before marking + * this one down +@@ -229,17 +234,25 @@ static int appleir_raw_event(struct hid_ + appleir->prev_key_idx = 0; + + if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) { +- key_down(hid, appleir, appleir->current_key); +- /* +- * Remote doesn't do key up, either pull them up, in the test +- * above, or here set a timer which pulls them up after 1/8 s +- */ +- mod_timer(&appleir->key_up_timer, jiffies + HZ / 8); ++ spin_lock_irqsave(&appleir->lock, flags); ++ if (!appleir->removing) { ++ key_down(hid, appleir, appleir->current_key); ++ /* ++ * Remote doesn't do key up, either pull them up, in ++ * the test above, or here set a timer which pulls them ++ * up after 1/8 s ++ */ ++ mod_timer(&appleir->key_up_timer, jiffies + HZ / 8); ++ } ++ spin_unlock_irqrestore(&appleir->lock, flags); + goto out; + } + + if (!memcmp(data, flatbattery, sizeof(flatbattery))) { +- battery_flat(appleir); ++ spin_lock_irqsave(&appleir->lock, flags); ++ if (!appleir->removing) ++ battery_flat(appleir); ++ spin_unlock_irqrestore(&appleir->lock, flags); + /* Fall through */ + } + +@@ -318,8 +331,20 @@ fail: + static void appleir_remove(struct hid_device *hid) + { + struct appleir *appleir = hid_get_drvdata(hid); ++ unsigned long flags; ++ ++ /* ++ * Mark the driver as tearing down so that any concurrent raw_event ++ * (e.g. from a USB URB completion that hid_hw_stop() has not yet ++ * killed) and the key_up_timer softirq stop touching input_dev ++ * before hid_hw_stop() frees it via hidinput_disconnect(). ++ */ ++ spin_lock_irqsave(&appleir->lock, flags); ++ appleir->removing = true; ++ spin_unlock_irqrestore(&appleir->lock, flags); ++ ++ timer_shutdown_sync(&appleir->key_up_timer); + hid_hw_stop(hid); +- timer_delete_sync(&appleir->key_up_timer); + } + + static const struct hid_device_id appleir_devices[] = { diff --git a/queue-6.12/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch b/queue-6.12/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch new file mode 100644 index 0000000000..cfd68bbb3c --- /dev/null +++ b/queue-6.12/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch @@ -0,0 +1,158 @@ +From stable+bounces-277059-greg=kroah.com@vger.kernel.org Fri Jul 17 16:00:28 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:00:21 -0400 +Subject: HID: multitouch: fix out-of-bounds bit access on mt_io_flags +To: stable@vger.kernel.org +Cc: Trung Nguyen , Benjamin Tissoires , Sasha Levin +Message-ID: <20260717140021.1717288-2-sashal@kernel.org> + +From: Trung Nguyen + +[ Upstream commit 8813b0612275cc61fe9e6603d0ee019247ade6be ] + +mt_io_flags is a single unsigned long, but mt_process_slot(), +mt_release_pending_palms() and mt_release_contacts() use it as a +per-slot bitmap indexed by the slot number. That slot number is only +bounded by td->maxcontacts, which is taken from the device's +ContactCountMaximum feature report and can be up to 255, not by +BITS_PER_LONG. + +As a result, a multitouch device that advertises a large contact count +makes set_bit()/clear_bit() operate past the mt_io_flags word and +corrupt the adjacent members of struct mt_device. The sticky-fingers +release timer is the easiest way to reach this. mt_release_contacts() +runs + + for (i = 0; i < mt->num_slots; i++) + clear_bit(i, &td->mt_io_flags); + +with num_slots == maxcontacts. For maxcontacts around 250 the loop +clears the bits that overlap td->applications.next, zeroing that list +head, and the list_for_each_entry() that immediately follows then +dereferences NULL. The kernel panics from timer (softirq) context. On a +KASAN build this shows up as a general protection fault in +mt_release_contacts() with a null-ptr-deref at offset 0x58, which is +offsetof(struct mt_application, num_received). + +The state is reachable from an untrusted USB or Bluetooth HID +multitouch device; no local privileges are required. + +Store the per-slot active state in a separately allocated bitmap sized +for maxcontacts, the same pattern already used for pending_palm_slots, +and keep only MT_IO_FLAGS_RUNNING in mt_io_flags. The two +"mt_io_flags & MT_IO_SLOTS_MASK" arming checks become +bitmap_empty(td->active_slots, td->maxcontacts). + +Move MT_IO_FLAGS_RUNNING back to bit 0. It was bumped to bit 32 by the +same commit to leave the low byte for the slot bits; with the slot bits +gone it fits in bit 0 again, which also keeps it within the unsigned +long on 32-bit. + +Fixes: 46f781e0d151 ("HID: multitouch: fix sticky fingers") +Cc: stable@vger.kernel.org +Signed-off-by: Trung Nguyen +Signed-off-by: Benjamin Tissoires +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/hid/hid-multitouch.c | 32 ++++++++++++++++++++------------ + 1 file changed, 20 insertions(+), 12 deletions(-) + +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -31,6 +31,7 @@ + * [1] https://gitlab.freedesktop.org/libevdev/hid-tools + */ + ++#include + #include + #include + #include +@@ -84,8 +85,7 @@ enum latency_mode { + HID_LATENCY_HIGH = 1, + }; + +-#define MT_IO_SLOTS_MASK GENMASK(7, 0) /* reserve first 8 bits for slot tracking */ +-#define MT_IO_FLAGS_RUNNING 32 ++#define MT_IO_FLAGS_RUNNING 0 + + static const bool mtrue = true; /* default for true */ + static const bool mfalse; /* default for false */ +@@ -160,10 +160,9 @@ struct mt_device { + struct mt_class mtclass; /* our mt device class */ + struct timer_list release_timer; /* to release sticky fingers */ + struct hid_device *hdev; /* hid_device we're attached to */ +- unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING) +- * first 8 bits are reserved for keeping the slot +- * states, this is fine because we only support up +- * to 250 slots (MT_MAX_MAXCONTACT) ++ unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING) */ ++ unsigned long *active_slots; /* bitmap of slots with an active ++ * contact, sized for maxcontacts + */ + __u8 inputmode_value; /* InputMode HID feature value */ + __u8 maxcontacts; +@@ -968,7 +967,7 @@ static void mt_release_pending_palms(str + + for_each_set_bit(slotnum, app->pending_palm_slots, td->maxcontacts) { + clear_bit(slotnum, app->pending_palm_slots); +- clear_bit(slotnum, &td->mt_io_flags); ++ clear_bit(slotnum, td->active_slots); + + input_mt_slot(input, slotnum); + input_mt_report_slot_inactive(input); +@@ -1174,9 +1173,9 @@ static int mt_process_slot(struct mt_dev + input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major); + input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor); + +- set_bit(slotnum, &td->mt_io_flags); ++ set_bit(slotnum, td->active_slots); + } else { +- clear_bit(slotnum, &td->mt_io_flags); ++ clear_bit(slotnum, td->active_slots); + } + + return 0; +@@ -1311,7 +1310,7 @@ static void mt_touch_report(struct hid_d + * defect. + */ + if (app->quirks & MT_QUIRK_STICKY_FINGERS) { +- if (td->mt_io_flags & MT_IO_SLOTS_MASK) ++ if (!bitmap_empty(td->active_slots, td->maxcontacts)) + mod_timer(&td->release_timer, + jiffies + msecs_to_jiffies(100)); + else +@@ -1351,6 +1350,15 @@ static int mt_touch_input_configured(str + if (td->is_buttonpad) + __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + ++ if (!td->active_slots) { ++ td->active_slots = devm_kcalloc(&td->hdev->dev, ++ BITS_TO_LONGS(td->maxcontacts), ++ sizeof(long), ++ GFP_KERNEL); ++ if (!td->active_slots) ++ return -ENOMEM; ++ } ++ + app->pending_palm_slots = devm_kcalloc(&hi->input->dev, + BITS_TO_LONGS(td->maxcontacts), + sizeof(long), +@@ -1815,7 +1823,7 @@ static void mt_release_contacts(struct h + for (i = 0; i < mt->num_slots; i++) { + input_mt_slot(input_dev, i); + input_mt_report_slot_inactive(input_dev); +- clear_bit(i, &td->mt_io_flags); ++ clear_bit(i, td->active_slots); + } + input_mt_sync_frame(input_dev); + input_sync(input_dev); +@@ -1838,7 +1846,7 @@ static void mt_expired_timeout(struct ti + */ + if (test_and_set_bit_lock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags)) + return; +- if (td->mt_io_flags & MT_IO_SLOTS_MASK) ++ if (!bitmap_empty(td->active_slots, td->maxcontacts)) + mt_release_contacts(hdev); + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } diff --git a/queue-6.12/hid-pidff-add-missing-spaces.patch b/queue-6.12/hid-pidff-add-missing-spaces.patch new file mode 100644 index 0000000000..5dcee42f1e --- /dev/null +++ b/queue-6.12/hid-pidff-add-missing-spaces.patch @@ -0,0 +1,64 @@ +From stable+bounces-277191-greg=kroah.com@vger.kernel.org Fri Jul 17 21:24:10 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 15:24:01 -0400 +Subject: HID: pidff: Add missing spaces +To: stable@vger.kernel.org +Cc: "Tomasz Pakuła" , "Jiri Kosina" , "Sasha Levin" +Message-ID: <20260717192403.2131944-2-sashal@kernel.org> + +From: Tomasz Pakuła + +[ Upstream commit 61ea33ded9327a07d9ef85a6933cb6316e2f185f ] + +Fixes checkpatch.pl errors + +Signed-off-by: Tomasz Pakuła +Signed-off-by: Jiri Kosina +Stable-dep-of: b251598b8bf3 ("HID: pidff: Use correct effect type in effect update") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/hid/usbhid/hid-pidff.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +--- a/drivers/hid/usbhid/hid-pidff.c ++++ b/drivers/hid/usbhid/hid-pidff.c +@@ -251,9 +251,9 @@ static u32 pidff_rescale_time(u16 time, + int exponent = field->unit_exponent; + + pr_debug("time field exponent: %d\n", exponent); +- for (;exponent < FF_TIME_EXPONENT; exponent++) ++ for (; exponent < FF_TIME_EXPONENT; exponent++) + scaled_time *= 10; +- for (;exponent > FF_TIME_EXPONENT; exponent--) ++ for (; exponent > FF_TIME_EXPONENT; exponent--) + scaled_time /= 10; + + pr_debug("time calculated from %d to %d\n", time, scaled_time); +@@ -587,7 +587,7 @@ static void pidff_set_device_control(str + hid_dbg(pidff->hid, "DEVICE_CONTROL is a bitmask\n"); + + /* Clear current bitmask */ +- for(i = 0; i < sizeof(pidff_device_control); i++) { ++ for (i = 0; i < sizeof(pidff_device_control); i++) { + index = pidff->control_id[i]; + if (index < 1) + continue; +@@ -638,7 +638,7 @@ static void pidff_fetch_pool(struct pidf + struct hid_device *hid = pidff->hid; + + /* Repeat if PID_SIMULTANEOUS_MAX < 2 to make sure it's correct */ +- for(i = 0; i < 20; i++) { ++ for (i = 0; i < 20; i++) { + hid_hw_request(hid, pidff->reports[PID_POOL], HID_REQ_GET_REPORT); + hid_hw_wait(hid); + +@@ -869,7 +869,7 @@ static int pidff_upload_effect(struct in + case FF_INERTIA: + case FF_FRICTION: + if (!old) { +- switch(effect->type) { ++ switch (effect->type) { + case FF_SPRING: + type_id = PID_SPRING; + break; diff --git a/queue-6.12/hid-pidff-fix-missing-blank-lines-after-declarations.patch b/queue-6.12/hid-pidff-fix-missing-blank-lines-after-declarations.patch new file mode 100644 index 0000000000..bac92daa35 --- /dev/null +++ b/queue-6.12/hid-pidff-fix-missing-blank-lines-after-declarations.patch @@ -0,0 +1,59 @@ +From stable+bounces-277190-greg=kroah.com@vger.kernel.org Fri Jul 17 21:24:09 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 15:24:00 -0400 +Subject: HID: pidff: Fix missing blank lines after declarations +To: stable@vger.kernel.org +Cc: "Tomasz Pakuła" , "Jiri Kosina" , "Sasha Levin" +Message-ID: <20260717192403.2131944-1-sashal@kernel.org> + +From: Tomasz Pakuła + +[ Upstream commit 42a2bd61650f13605f9283b5a9e6b1a26b7d4ec2 ] + +Fixes chackpatch.pl warnings + +Signed-off-by: Tomasz Pakuła +Signed-off-by: Jiri Kosina +Stable-dep-of: b251598b8bf3 ("HID: pidff: Use correct effect type in effect update") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/hid/usbhid/hid-pidff.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +--- a/drivers/hid/usbhid/hid-pidff.c ++++ b/drivers/hid/usbhid/hid-pidff.c +@@ -249,8 +249,8 @@ static u32 pidff_rescale_time(u16 time, + { + u32 scaled_time = time; + int exponent = field->unit_exponent; +- pr_debug("time field exponent: %d\n", exponent); + ++ pr_debug("time field exponent: %d\n", exponent); + for (;exponent < FF_TIME_EXPONENT; exponent++) + scaled_time *= 10; + for (;exponent > FF_TIME_EXPONENT; exponent--) +@@ -354,6 +354,7 @@ static int pidff_needs_set_envelope(stru + struct ff_envelope *old) + { + bool needs_new_envelope; ++ + needs_new_envelope = envelope->attack_level != 0 || + envelope->fade_level != 0 || + envelope->attack_length != 0 || +@@ -733,6 +734,7 @@ static void pidff_playback_pid(struct pi + static int pidff_playback(struct input_dev *dev, int effect_id, int value) + { + struct pidff_device *pidff = dev->ff->private; ++ + pidff_playback_pid(pidff, pidff->pid_id[effect_id], value); + return 0; + } +@@ -1233,6 +1235,7 @@ static int pidff_find_effects(struct pid + + for (i = 0; i < sizeof(pidff_effect_types); i++) { + int pidff_type = pidff->type_id[i]; ++ + if (pidff->set_effect_type->usage[pidff_type].hid != + pidff->create_new_effect_type->usage[pidff_type].hid) { + hid_err(pidff->hid, diff --git a/queue-6.12/hid-pidff-rework-pidff_upload_effect.patch b/queue-6.12/hid-pidff-rework-pidff_upload_effect.patch new file mode 100644 index 0000000000..b7c4f22d39 --- /dev/null +++ b/queue-6.12/hid-pidff-rework-pidff_upload_effect.patch @@ -0,0 +1,412 @@ +From stable+bounces-277192-greg=kroah.com@vger.kernel.org Fri Jul 17 21:24:15 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 15:24:02 -0400 +Subject: HID: pidff: Rework pidff_upload_effect +To: stable@vger.kernel.org +Cc: "Tomasz Pakuła" , "Jiri Kosina" , "Sasha Levin" +Message-ID: <20260717192403.2131944-3-sashal@kernel.org> + +From: Tomasz Pakuła + +[ Upstream commit 7fbaa031b94182a9c9e58310935a2f74265ef78d ] + +One of the more complicated functions. Expunge some of the logic to +separate functions (FF -> PID id conversion) + +Add a macro for envelope check to make it more readable in the upload +function. + +All this made it possible to to expunge common code from the big switch +statement and reduce the overall function size considerably. Now it can +fit on one screen. + +Move the effect_cout logic from report functions to upload/erase +functions. + +Signed-off-by: Tomasz Pakuła +Signed-off-by: Jiri Kosina +Stable-dep-of: b251598b8bf3 ("HID: pidff: Use correct effect type in effect update") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/hid/usbhid/hid-pidff.c | 243 +++++++++++++++++++---------------------- + 1 file changed, 115 insertions(+), 128 deletions(-) + +--- a/drivers/hid/usbhid/hid-pidff.c ++++ b/drivers/hid/usbhid/hid-pidff.c +@@ -141,7 +141,7 @@ static const u8 pidff_effect_types[] = { + #define PID_BLOCK_LOAD_SUCCESS 0 + #define PID_BLOCK_LOAD_FULL 1 + #define PID_BLOCK_LOAD_ERROR 2 +-static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e}; ++static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e }; + + #define PID_EFFECT_START 0 + #define PID_EFFECT_STOP 1 +@@ -214,6 +214,62 @@ static int pidff_is_effect_conditional(s + } + + /* ++ * Get PID effect index from FF effect type. ++ * Return 0 if invalid. ++ */ ++static int pidff_effect_ff_to_pid(struct ff_effect *effect) ++{ ++ switch (effect->type) { ++ case FF_CONSTANT: ++ return PID_CONSTANT; ++ case FF_RAMP: ++ return PID_RAMP; ++ case FF_SPRING: ++ return PID_SPRING; ++ case FF_DAMPER: ++ return PID_DAMPER; ++ case FF_INERTIA: ++ return PID_INERTIA; ++ case FF_FRICTION: ++ return PID_FRICTION; ++ case FF_PERIODIC: ++ switch (effect->u.periodic.waveform) { ++ case FF_SQUARE: ++ return PID_SQUARE; ++ case FF_TRIANGLE: ++ return PID_TRIANGLE; ++ case FF_SINE: ++ return PID_SINE; ++ case FF_SAW_UP: ++ return PID_SAW_UP; ++ case FF_SAW_DOWN: ++ return PID_SAW_DOWN; ++ } ++ } ++ pr_err("invalid effect type\n"); ++ return -EINVAL; ++} ++ ++/* ++ * Get effect id in the device descriptor. ++ * Return 0 if invalid. ++ */ ++static int pidff_get_effect_type_id(struct pidff_device *pidff, ++ struct ff_effect *effect) ++{ ++ int id = pidff_effect_ff_to_pid(effect); ++ ++ if (id < 0) ++ return 0; ++ ++ if (effect->type == FF_PERIODIC && ++ pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY) ++ id = PID_SINE; ++ ++ return pidff->type_id[id]; ++} ++ ++/* + * Clamp value for a given field + */ + static s32 pidff_clamp(s32 i, struct hid_field *field) +@@ -335,16 +391,16 @@ static void pidff_set_envelope_report(st + pidff->set_envelope[PID_FADE_LEVEL].field); + + pidff_set_time(&pidff->set_envelope[PID_ATTACK_TIME], +- envelope->attack_length); ++ envelope->attack_length); + pidff_set_time(&pidff->set_envelope[PID_FADE_TIME], +- envelope->fade_length); ++ envelope->fade_length); + + hid_dbg(pidff->hid, "attack %u => %d\n", + envelope->attack_level, + pidff->set_envelope[PID_ATTACK_LEVEL].value[0]); + + hid_hw_request(pidff->hid, pidff->reports[PID_SET_ENVELOPE], +- HID_REQ_SET_REPORT); ++ HID_REQ_SET_REPORT); + } + + /* +@@ -353,7 +409,7 @@ static void pidff_set_envelope_report(st + static int pidff_needs_set_envelope(struct ff_envelope *envelope, + struct ff_envelope *old) + { +- bool needs_new_envelope; ++ int needs_new_envelope; + + needs_new_envelope = envelope->attack_level != 0 || + envelope->fade_level != 0 || +@@ -361,8 +417,7 @@ static int pidff_needs_set_envelope(stru + envelope->fade_length != 0; + + if (!needs_new_envelope) +- return false; +- ++ return 0; + if (!old) + return needs_new_envelope; + +@@ -375,8 +430,8 @@ static int pidff_needs_set_envelope(stru + /* + * Send constant force report to the device + */ +-static void pidff_set_constant_force_report(struct pidff_device *pidff, +- struct ff_effect *effect) ++static void pidff_set_constant_report(struct pidff_device *pidff, ++ struct ff_effect *effect) + { + pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; +@@ -384,7 +439,7 @@ static void pidff_set_constant_force_rep + effect->u.constant.level); + + hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONSTANT], +- HID_REQ_SET_REPORT); ++ HID_REQ_SET_REPORT); + } + + /* +@@ -536,8 +591,8 @@ static int pidff_needs_set_condition(str + /* + * Send ramp force report to the device + */ +-static void pidff_set_ramp_force_report(struct pidff_device *pidff, +- struct ff_effect *effect) ++static void pidff_set_ramp_report(struct pidff_device *pidff, ++ struct ff_effect *effect) + { + pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; +@@ -546,7 +601,7 @@ static void pidff_set_ramp_force_report( + pidff_set_signed(&pidff->set_ramp[PID_RAMP_END], + effect->u.ramp.end_level); + hid_hw_request(pidff->hid, pidff->reports[PID_SET_RAMP], +- HID_REQ_SET_REPORT); ++ HID_REQ_SET_REPORT); + } + + /* +@@ -664,9 +719,6 @@ static int pidff_request_effect_upload(s + { + int j; + +- if (!pidff->effect_count) +- pidff_reset(pidff); +- + pidff->create_new_effect_type->value[0] = efnum; + hid_hw_request(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT], + HID_REQ_SET_REPORT); +@@ -686,8 +738,6 @@ static int pidff_request_effect_upload(s + hid_dbg(pidff->hid, "device reported free memory: %d bytes\n", + pidff->block_load[PID_RAM_POOL_AVAILABLE].value ? + pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1); +- +- pidff->effect_count++; + return 0; + } + if (pidff->block_load_status->value[0] == +@@ -725,7 +775,7 @@ static void pidff_playback_pid(struct pi + } + + hid_hw_request(pidff->hid, pidff->reports[PID_EFFECT_OPERATION], +- HID_REQ_SET_REPORT); ++ HID_REQ_SET_REPORT); + } + + /* +@@ -747,10 +797,7 @@ static void pidff_erase_pid(struct pidff + { + pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id; + hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_FREE], +- HID_REQ_SET_REPORT); +- +- if (pidff->effect_count > 0) +- pidff->effect_count--; ++ HID_REQ_SET_REPORT); + } + + /* +@@ -772,139 +819,79 @@ static int pidff_erase_effect(struct inp + pidff_playback_pid(pidff, pid_id, 0); + pidff_erase_pid(pidff, pid_id); + ++ if (pidff->effect_count > 0) ++ pidff->effect_count--; ++ ++ hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count); + return 0; + } + ++#define PIDFF_SET_REPORT_IF_NEEDED(type, effect, old) \ ++ ({ if (!old || pidff_needs_set_## type(effect, old)) \ ++ pidff_set_ ##type## _report(pidff, effect); }) ++ ++#define PIDFF_SET_ENVELOPE_IF_NEEDED(type, effect, old) \ ++ ({ if (pidff_needs_set_envelope(&effect->u.type.envelope, \ ++ old ? &old->u.type.envelope : NULL)) \ ++ pidff_set_envelope_report(pidff, &effect->u.type.envelope); }) ++ + /* + * Effect upload handler + */ +-static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect, ++static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *new, + struct ff_effect *old) + { + struct pidff_device *pidff = dev->ff->private; +- int type_id; +- int error; ++ const int type_id = pidff_get_effect_type_id(pidff, new); + +- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0; +- if (old) { +- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = +- pidff->pid_id[effect->id]; ++ if (!type_id) { ++ hid_err(pidff->hid, "effect type not supported\n"); ++ return -EINVAL; + } + +- switch (effect->type) { ++ if (!pidff->effect_count) ++ pidff_reset(pidff); ++ ++ if (!old) { ++ int error = pidff_request_effect_upload(pidff, type_id); ++ ++ if (error) ++ return error; ++ ++ pidff->effect_count++; ++ hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count); ++ pidff->pid_id[new->id] = ++ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; ++ } ++ ++ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = ++ pidff->pid_id[new->id]; ++ ++ PIDFF_SET_REPORT_IF_NEEDED(effect, new, old); ++ switch (new->type) { + case FF_CONSTANT: +- if (!old) { +- error = pidff_request_effect_upload(pidff, +- pidff->type_id[PID_CONSTANT]); +- if (error) +- return error; +- } +- if (!old || pidff_needs_set_effect(effect, old)) +- pidff_set_effect_report(pidff, effect); +- if (!old || pidff_needs_set_constant(effect, old)) +- pidff_set_constant_force_report(pidff, effect); +- if (pidff_needs_set_envelope(&effect->u.constant.envelope, +- old ? &old->u.constant.envelope : NULL)) +- pidff_set_envelope_report(pidff, &effect->u.constant.envelope); ++ PIDFF_SET_REPORT_IF_NEEDED(constant, new, old); ++ PIDFF_SET_ENVELOPE_IF_NEEDED(constant, new, old); + break; + + case FF_PERIODIC: +- if (!old) { +- switch (effect->u.periodic.waveform) { +- case FF_SQUARE: +- type_id = PID_SQUARE; +- break; +- case FF_TRIANGLE: +- type_id = PID_TRIANGLE; +- break; +- case FF_SINE: +- type_id = PID_SINE; +- break; +- case FF_SAW_UP: +- type_id = PID_SAW_UP; +- break; +- case FF_SAW_DOWN: +- type_id = PID_SAW_DOWN; +- break; +- default: +- hid_err(pidff->hid, "invalid waveform\n"); +- return -EINVAL; +- } +- +- if (pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY) +- type_id = PID_SINE; +- +- error = pidff_request_effect_upload(pidff, +- pidff->type_id[type_id]); +- if (error) +- return error; +- } +- if (!old || pidff_needs_set_effect(effect, old)) +- pidff_set_effect_report(pidff, effect); +- if (!old || pidff_needs_set_periodic(effect, old)) +- pidff_set_periodic_report(pidff, effect); +- if (pidff_needs_set_envelope(&effect->u.periodic.envelope, +- old ? &old->u.periodic.envelope : NULL)) +- pidff_set_envelope_report(pidff, &effect->u.periodic.envelope); ++ PIDFF_SET_REPORT_IF_NEEDED(periodic, new, old); ++ PIDFF_SET_ENVELOPE_IF_NEEDED(periodic, new, old); + break; + + case FF_RAMP: +- if (!old) { +- error = pidff_request_effect_upload(pidff, +- pidff->type_id[PID_RAMP]); +- if (error) +- return error; +- } +- if (!old || pidff_needs_set_effect(effect, old)) +- pidff_set_effect_report(pidff, effect); +- if (!old || pidff_needs_set_ramp(effect, old)) +- pidff_set_ramp_force_report(pidff, effect); +- if (pidff_needs_set_envelope(&effect->u.ramp.envelope, +- old ? &old->u.ramp.envelope : NULL)) +- pidff_set_envelope_report(pidff, &effect->u.ramp.envelope); ++ PIDFF_SET_REPORT_IF_NEEDED(ramp, new, old); ++ PIDFF_SET_ENVELOPE_IF_NEEDED(ramp, new, old); + break; + + case FF_SPRING: + case FF_DAMPER: + case FF_INERTIA: + case FF_FRICTION: +- if (!old) { +- switch (effect->type) { +- case FF_SPRING: +- type_id = PID_SPRING; +- break; +- case FF_DAMPER: +- type_id = PID_DAMPER; +- break; +- case FF_INERTIA: +- type_id = PID_INERTIA; +- break; +- case FF_FRICTION: +- type_id = PID_FRICTION; +- break; +- } +- error = pidff_request_effect_upload(pidff, +- pidff->type_id[type_id]); +- if (error) +- return error; +- } +- if (!old || pidff_needs_set_effect(effect, old)) +- pidff_set_effect_report(pidff, effect); +- if (!old || pidff_needs_set_condition(effect, old)) +- pidff_set_condition_report(pidff, effect); ++ PIDFF_SET_REPORT_IF_NEEDED(condition, new, old); + break; +- +- default: +- hid_err(pidff->hid, "invalid type\n"); +- return -EINVAL; + } +- +- if (!old) +- pidff->pid_id[effect->id] = +- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; +- + hid_dbg(pidff->hid, "uploaded\n"); +- + return 0; + } + diff --git a/queue-6.12/hid-pidff-use-correct-effect-type-in-effect-update.patch b/queue-6.12/hid-pidff-use-correct-effect-type-in-effect-update.patch new file mode 100644 index 0000000000..e52c9c1b4f --- /dev/null +++ b/queue-6.12/hid-pidff-use-correct-effect-type-in-effect-update.patch @@ -0,0 +1,44 @@ +From stable+bounces-277193-greg=kroah.com@vger.kernel.org Fri Jul 17 21:24:18 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 15:24:03 -0400 +Subject: HID: pidff: Use correct effect type in effect update +To: stable@vger.kernel.org +Cc: "Oleg Makarenko" , "Oliver Roundtree" , "Ryno Kotzé" , "Jiri Kosina" , "Sasha Levin" +Message-ID: <20260717192403.2131944-4-sashal@kernel.org> + +From: Oleg Makarenko + +[ Upstream commit b251598b8bf37300510868f739a79e07800d41ce ] + +When updating an existing effect, the effect type from the last created +effect was sent to the device instead of the updated one. +This caused incorrect reports when a game creates multiple different +effects and updates only one that is not the last created. + +Fixes FFB in multiple games that create multiple simultaneous effects +(Forza Horizon 5/6). + +Fixes: 224ee88fe395 ("Input: add force feedback driver for PID devices") +Cc: stable@vger.kernel.org +Tested-by: Oliver Roundtree +Co-developed-by: Ryno Kotzé +Signed-off-by: Ryno Kotzé +Signed-off-by: Oleg Makarenko +Signed-off-by: Jiri Kosina +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/hid/usbhid/hid-pidff.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/hid/usbhid/hid-pidff.c ++++ b/drivers/hid/usbhid/hid-pidff.c +@@ -460,7 +460,7 @@ static void pidff_set_effect_report(stru + pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] = + pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]; + pidff->set_effect_type->value[0] = +- pidff->create_new_effect_type->value[0]; ++ pidff_get_effect_type_id(pidff, effect); + + pidff_set_duration(&pidff->set_effect[PID_DURATION], + effect->replay.length); diff --git a/queue-6.12/iio-adc-ad7380-select-regmap.patch b/queue-6.12/iio-adc-ad7380-select-regmap.patch new file mode 100644 index 0000000000..7365902d1e --- /dev/null +++ b/queue-6.12/iio-adc-ad7380-select-regmap.patch @@ -0,0 +1,41 @@ +From stable+bounces-273950-greg=kroah.com@vger.kernel.org Mon Jul 13 20:16:53 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 14:13:28 -0400 +Subject: iio: adc: ad7380: select REGMAP +To: stable@vger.kernel.org +Cc: "Samuel Moelius" , "Andy Shevchenko" , "Nuno Sá" , Stable@vger.kernel.org, "Jonathan Cameron" , "Sasha Levin" +Message-ID: <20260713181328.1932706-1-sashal@kernel.org> + +From: Samuel Moelius + +[ Upstream commit 6697091b386a4e2830bdd38512c87a4befff2b32 ] + +The AD7380 driver uses generic regmap types and APIs. However, its +Kconfig entry does not select REGMAP. + +As a result, AD7380 can be enabled from an allnoconfig-derived config +with SPI_MASTER=y while REGMAP remains unset, causing ad7380.o to fail +to build. + +Fixes: b095217c104b ("iio: adc: ad7380: new driver for AD7380 ADCs") +Signed-off-by: Samuel Moelius +Reviewed-by: Andy Shevchenko +Reviewed-by: Nuno Sá +Cc: +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/adc/Kconfig | 1 + + 1 file changed, 1 insertion(+) + +--- a/drivers/iio/adc/Kconfig ++++ b/drivers/iio/adc/Kconfig +@@ -188,6 +188,7 @@ config AD7298 + config AD7380 + tristate "Analog Devices AD7380 ADC driver" + depends on SPI_MASTER ++ select REGMAP + select IIO_BUFFER + select IIO_TRIGGER + select IIO_TRIGGERED_BUFFER diff --git a/queue-6.12/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch b/queue-6.12/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch new file mode 100644 index 0000000000..5e0ebb4e23 --- /dev/null +++ b/queue-6.12/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch @@ -0,0 +1,89 @@ +From stable+bounces-273939-greg=kroah.com@vger.kernel.org Mon Jul 13 20:06:54 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 14:03:26 -0400 +Subject: iio: hid-sensor-rotation: Fix stale or zero output when reading raw values +To: stable@vger.kernel.org +Cc: Zhang Lixu , Andy Shevchenko , Stable@vger.kernel.org, Jonathan Cameron , Sasha Levin +Message-ID: <20260713180326.1923554-2-sashal@kernel.org> + +From: Zhang Lixu + +[ Upstream commit 3ce8d099e0afc5a7da75a2007a67f67c4f5a4af1 ] + +When reading the raw quaternion attribute (in_rot_quaternion_raw), the +driver currently returns either all zeros (if the sensor was never enabled) +or stale data (if the sensor was previously enabled) because it reads from +the internal buffer without explicitly requesting a new sample from the +sensor. + +To fix this, power up the sensor, call sensor_hub_input_attr_read_values() +to issue a synchronous GET_REPORT and receive the full quaternion data +directly into a local buffer, then decode the four components. + +Fixes: fc18dddc0625 ("iio: hid-sensors: Added device rotation support") +Signed-off-by: Zhang Lixu +Reviewed-by: Andy Shevchenko +Cc: +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/orientation/hid-sensor-rotation.c | 40 ++++++++++++++++++++++++-- + 1 file changed, 38 insertions(+), 2 deletions(-) + +--- a/drivers/iio/orientation/hid-sensor-rotation.c ++++ b/drivers/iio/orientation/hid-sensor-rotation.c +@@ -69,6 +69,13 @@ static int dev_rot_read_raw(struct iio_d + long mask) + { + struct dev_rot_state *rot_state = iio_priv(indio_dev); ++ struct hid_sensor_hub_device *hsdev = rot_state->common_attributes.hsdev; ++ struct hid_sensor_hub_attribute_info *info = &rot_state->quaternion; ++ u32 usage_id = HID_USAGE_SENSOR_ORIENT_QUATERNION; ++ union { ++ s16 val16[4]; ++ s32 val32[4]; ++ } raw_buf; + int ret_type; + int i; + +@@ -78,8 +85,37 @@ static int dev_rot_read_raw(struct iio_d + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (size >= 4) { +- for (i = 0; i < 4; ++i) +- vals[i] = rot_state->scan.sampled_vals[i]; ++ if (info->size <= 0 || info->size > sizeof(raw_buf)) ++ return -EINVAL; ++ ++ hid_sensor_power_state(&rot_state->common_attributes, true); ++ ++ ret_type = sensor_hub_input_attr_read_values(hsdev, ++ hsdev->usage, ++ usage_id, ++ info->report_id, ++ SENSOR_HUB_SYNC, ++ info->size, ++ (u8 *)&raw_buf); ++ ++ hid_sensor_power_state(&rot_state->common_attributes, false); ++ ++ if (ret_type < 0) ++ return ret_type; ++ ++ switch (info->size) { ++ case sizeof(raw_buf.val16): ++ for (i = 0; i < ARRAY_SIZE(raw_buf.val16); i++) ++ vals[i] = raw_buf.val16[i]; ++ break; ++ case sizeof(raw_buf.val32): ++ for (i = 0; i < ARRAY_SIZE(raw_buf.val32); i++) ++ vals[i] = raw_buf.val32[i]; ++ break; ++ default: ++ return -EINVAL; ++ } ++ + ret_type = IIO_VAL_INT_MULTIPLE; + *val_len = 4; + } else diff --git a/queue-6.12/iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch b/queue-6.12/iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch new file mode 100644 index 0000000000..ad69357ad4 --- /dev/null +++ b/queue-6.12/iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch @@ -0,0 +1,55 @@ +From stable+bounces-273975-greg=kroah.com@vger.kernel.org Mon Jul 13 21:37:16 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 15:37:08 -0400 +Subject: iio: pressure: mpl115: fix runtime PM leak on read error +To: stable@vger.kernel.org +Cc: Biren Pandya , Stable@vger.kernel.org, Jonathan Cameron , Sasha Levin +Message-ID: <20260713193708.2054715-2-sashal@kernel.org> + +From: Biren Pandya + +[ Upstream commit fbe67ff37a6fd855a6c097f84f3738bd13d0a898 ] + +mpl115_read_raw() takes a runtime PM reference with pm_runtime_get_sync() +before reading the processed pressure or raw temperature, but on the read +error path it returns without calling pm_runtime_put_autosuspend(). Each +failed read therefore leaks a runtime PM reference and prevents the device +from autosuspending. + +Drop the reference before checking the return value so both the success +and error paths are balanced. + +Fixes: 0c3a333524a3 ("iio: pressure: mpl115: Implementing low power mode by shutdown gpio") +Signed-off-by: Biren Pandya +Assisted-by: Claude:claude-opus-4-8 coccinelle +Cc: +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/pressure/mpl115.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/iio/pressure/mpl115.c ++++ b/drivers/iio/pressure/mpl115.c +@@ -106,18 +106,18 @@ static int mpl115_read_raw(struct iio_de + case IIO_CHAN_INFO_PROCESSED: + pm_runtime_get_sync(data->dev); + ret = mpl115_comp_pressure(data, val, val2); ++ pm_runtime_put_autosuspend(data->dev); + if (ret < 0) + return ret; +- pm_runtime_put_autosuspend(data->dev); + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + pm_runtime_get_sync(data->dev); + /* temperature -5.35 C / LSB, 472 LSB is 25 C */ + ret = mpl115_read_temp(data); ++ pm_runtime_put_autosuspend(data->dev); + if (ret < 0) + return ret; +- pm_runtime_put_autosuspend(data->dev); + *val = ret >> 6; + + return IIO_VAL_INT; diff --git a/queue-6.12/iio-pressure-remove-redundant-pm_runtime_mark_last_busy-calls.patch b/queue-6.12/iio-pressure-remove-redundant-pm_runtime_mark_last_busy-calls.patch new file mode 100644 index 0000000000..9b219c71b1 --- /dev/null +++ b/queue-6.12/iio-pressure-remove-redundant-pm_runtime_mark_last_busy-calls.patch @@ -0,0 +1,118 @@ +From stable+bounces-273974-greg=kroah.com@vger.kernel.org Mon Jul 13 21:37:14 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 15:37:07 -0400 +Subject: iio: pressure: Remove redundant pm_runtime_mark_last_busy() calls +To: stable@vger.kernel.org +Cc: Sakari Ailus , Jonathan Cameron , Sasha Levin +Message-ID: <20260713193708.2054715-1-sashal@kernel.org> + +From: Sakari Ailus + +[ Upstream commit dfb68a8ebb2e8d9af6e356ae0806bfd8e854ab44 ] + +pm_runtime_put_autosuspend(), pm_runtime_put_sync_autosuspend(), +pm_runtime_autosuspend() and pm_request_autosuspend() now include a call +to pm_runtime_mark_last_busy(). Remove the now-reduntant explicit call to +pm_runtime_mark_last_busy(). + +Signed-off-by: Sakari Ailus +Link: https://patch.msgid.link/20250825135401.1765847-11-sakari.ailus@linux.intel.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: fbe67ff37a6f ("iio: pressure: mpl115: fix runtime PM leak on read error") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/pressure/bmp280-core.c | 5 ----- + drivers/iio/pressure/icp10100.c | 1 - + drivers/iio/pressure/mpl115.c | 2 -- + drivers/iio/pressure/zpa2326.c | 2 -- + 4 files changed, 10 deletions(-) + +--- a/drivers/iio/pressure/bmp280-core.c ++++ b/drivers/iio/pressure/bmp280-core.c +@@ -747,7 +747,6 @@ static int bmp280_read_raw(struct iio_de + + pm_runtime_get_sync(data->dev); + ret = bmp280_read_raw_impl(indio_dev, chan, val, val2, mask); +- pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + + return ret; +@@ -922,7 +921,6 @@ static int bmp280_write_raw(struct iio_d + + pm_runtime_get_sync(data->dev); + ret = bmp280_write_raw_impl(indio_dev, chan, val, val2, mask); +- pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + + return ret; +@@ -1951,7 +1949,6 @@ static int bmp580_nvmem_read(void *priv, + + pm_runtime_get_sync(data->dev); + ret = bmp580_nvmem_read_impl(priv, offset, val, bytes); +- pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + + return ret; +@@ -2026,7 +2023,6 @@ static int bmp580_nvmem_write(void *priv + + pm_runtime_get_sync(data->dev); + ret = bmp580_nvmem_write_impl(priv, offset, val, bytes); +- pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + + return ret; +@@ -2634,7 +2630,6 @@ static int bmp280_buffer_postdisable(str + { + struct bmp280_data *data = iio_priv(indio_dev); + +- pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + + return 0; +--- a/drivers/iio/pressure/icp10100.c ++++ b/drivers/iio/pressure/icp10100.c +@@ -265,7 +265,6 @@ static int icp10100_get_measures(struct + (be16_to_cpu(measures[1]) >> 8); + *temperature = be16_to_cpu(measures[2]); + +- pm_runtime_mark_last_busy(&st->client->dev); + error_measure: + pm_runtime_put_autosuspend(&st->client->dev); + return ret; +--- a/drivers/iio/pressure/mpl115.c ++++ b/drivers/iio/pressure/mpl115.c +@@ -108,7 +108,6 @@ static int mpl115_read_raw(struct iio_de + ret = mpl115_comp_pressure(data, val, val2); + if (ret < 0) + return ret; +- pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + + return IIO_VAL_INT_PLUS_MICRO; +@@ -118,7 +117,6 @@ static int mpl115_read_raw(struct iio_de + ret = mpl115_read_temp(data); + if (ret < 0) + return ret; +- pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); + *val = ret >> 6; + +--- a/drivers/iio/pressure/zpa2326.c ++++ b/drivers/iio/pressure/zpa2326.c +@@ -699,7 +699,6 @@ static void zpa2326_suspend(struct iio_d + + zpa2326_sleep(indio_dev); + +- pm_runtime_mark_last_busy(parent); + pm_runtime_put_autosuspend(parent); + } + +@@ -710,7 +709,6 @@ static void zpa2326_init_runtime(struct + pm_runtime_enable(parent); + pm_runtime_set_autosuspend_delay(parent, 1000); + pm_runtime_use_autosuspend(parent); +- pm_runtime_mark_last_busy(parent); + pm_runtime_put_autosuspend(parent); + } + diff --git a/queue-6.12/io_uring-rw-ensure-reissue-path-is-correctly-handled-for-iopoll.patch b/queue-6.12/io_uring-rw-ensure-reissue-path-is-correctly-handled-for-iopoll.patch new file mode 100644 index 0000000000..10e85265cd --- /dev/null +++ b/queue-6.12/io_uring-rw-ensure-reissue-path-is-correctly-handled-for-iopoll.patch @@ -0,0 +1,48 @@ +From stable+bounces-274911-greg=kroah.com@vger.kernel.org Wed Jul 15 13:43:49 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 07:40:17 -0400 +Subject: io_uring/rw: ensure reissue path is correctly handled for IOPOLL +To: stable@vger.kernel.org +Cc: Jens Axboe , John Garry , Sasha Levin +Message-ID: <20260715114018.726395-1-sashal@kernel.org> + +From: Jens Axboe + +[ Upstream commit bcb0fda3c2da9fe4721d3e73d80e778c038e7d27 ] + +The IOPOLL path posts CQEs when the io_kiocb is marked as completed, +so it cannot rely on the usual retry that non-IOPOLL requests do for +read/write requests. + +If -EAGAIN is received and the request should be retried, go through +the normal completion path and let the normal flush logic catch it and +reissue it, like what is done for !IOPOLL reads or writes. + +Fixes: d803d123948f ("io_uring/rw: handle -EAGAIN retry at IO completion time") +Reported-by: John Garry +Link: https://lore.kernel.org/io-uring/2b43ccfa-644d-4a09-8f8f-39ad71810f41@oracle.com/ +Signed-off-by: Jens Axboe +Stable-dep-of: c554246ff4c6 ("io_uring/rw: preserve partial result for iopoll") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + io_uring/rw.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +--- a/io_uring/rw.c ++++ b/io_uring/rw.c +@@ -550,11 +550,10 @@ static void io_complete_rw_iopoll(struct + if (kiocb->ki_flags & IOCB_WRITE) + io_req_end_write(req); + if (unlikely(res != req->cqe.res)) { +- if (res == -EAGAIN && io_rw_should_reissue(req)) { ++ if (res == -EAGAIN && io_rw_should_reissue(req)) + req->flags |= REQ_F_REISSUE | REQ_F_BL_NO_RECYCLE; +- return; +- } +- req->cqe.res = res; ++ else ++ req->cqe.res = res; + } + + /* order with io_iopoll_complete() checking ->iopoll_completed */ diff --git a/queue-6.12/io_uring-rw-preserve-partial-result-for-iopoll.patch b/queue-6.12/io_uring-rw-preserve-partial-result-for-iopoll.patch new file mode 100644 index 0000000000..157bee735a --- /dev/null +++ b/queue-6.12/io_uring-rw-preserve-partial-result-for-iopoll.patch @@ -0,0 +1,58 @@ +From stable+bounces-274912-greg=kroah.com@vger.kernel.org Wed Jul 15 13:43:53 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 07:40:18 -0400 +Subject: io_uring/rw: preserve partial result for iopoll +To: stable@vger.kernel.org +Cc: Michael Wigham , Jens Axboe , Sasha Levin +Message-ID: <20260715114018.726395-2-sashal@kernel.org> + +From: Michael Wigham + +[ Upstream commit c554246ff4c68abf71b61a89c6e39d3cf94f523e ] + +A partial read will store the completed byte count in io->bytes_done. +The regular completion path applies io_fixup_rw_res() so that, when the +following operation reaches EOF, the number of bytes already read is +returned. + +The iopoll completion path does not apply this fixup to the return value +and can return zero instead. + +Use the fixup result when updating the CQE, and the raw result for the +reissue check. + +Cc: stable@vger.kernel.org +Fixes: 4d9cb92ca41d ("io_uring/rw: fix short rw error handling") +Signed-off-by: Michael Wigham +Link: https://patch.msgid.link/20260613225240.34032-1-michael@wigham.net +Signed-off-by: Jens Axboe +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + io_uring/rw.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +--- a/io_uring/rw.c ++++ b/io_uring/rw.c +@@ -546,15 +546,15 @@ static void io_complete_rw_iopoll(struct + { + struct io_rw *rw = container_of(kiocb, struct io_rw, kiocb); + struct io_kiocb *req = cmd_to_io_kiocb(rw); ++ int final_res = io_fixup_rw_res(req, res); + + if (kiocb->ki_flags & IOCB_WRITE) + io_req_end_write(req); +- if (unlikely(res != req->cqe.res)) { +- if (res == -EAGAIN && io_rw_should_reissue(req)) +- req->flags |= REQ_F_REISSUE | REQ_F_BL_NO_RECYCLE; +- else +- req->cqe.res = res; +- } ++ ++ if (res == -EAGAIN && io_rw_should_reissue(req)) ++ req->flags |= REQ_F_REISSUE | REQ_F_BL_NO_RECYCLE; ++ else if (unlikely(final_res != req->cqe.res)) ++ req->cqe.res = final_res; + + /* order with io_iopoll_complete() checking ->iopoll_completed */ + smp_store_release(&req->iopoll_completed, 1); diff --git a/queue-6.12/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch b/queue-6.12/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch new file mode 100644 index 0000000000..9e73553775 --- /dev/null +++ b/queue-6.12/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch @@ -0,0 +1,457 @@ +From stable+bounces-275317-greg=kroah.com@vger.kernel.org Thu Jul 16 13:52:39 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:52:30 -0400 +Subject: ksmbd: centralize ksmbd_conn final release to plug transport leak +To: stable@vger.kernel.org +Cc: DaeMyung Kang , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716115231.1785404-1-sashal@kernel.org> + +From: DaeMyung Kang + +[ Upstream commit b1f1e80620deb49daf63c2e677046599b693dc1f ] + +ksmbd_conn_free() is one of four sites that can observe the last +refcount drop of a struct ksmbd_conn. The other three + + fs/smb/server/connection.c ksmbd_conn_r_count_dec() + fs/smb/server/oplock.c __free_opinfo() + fs/smb/server/vfs_cache.c session_fd_check() + +end the conn with a bare kfree(), skipping +ida_destroy(&conn->async_ida) and +conn->transport->ops->free_transport(conn->transport). Whenever one +of them is the last putter, the embedded async_ida and the entire +transport struct leak -- for TCP, that is also the struct socket and +the kvec iov. + +__free_opinfo() being a final putter is not theoretical. opinfo_put() +queues the callback via call_rcu(&opinfo->rcu, free_opinfo_rcu), so +ksmbd_server_terminate_conn() can deposit N opinfo releases in RCU and +have ksmbd_conn_free() run in the handler thread before any of them +fire. ksmbd_conn_free() then observes refcnt > 0 and short-circuits; +the last RCU-delivered __free_opinfo() falls onto its bare kfree(conn) +branch and the transport is lost. + +A/B validation in a QEMU/virtme guest, mounting //127.0.0.1/testshare: +each iteration holds 8 files open via sleep processes, force-closes +TCP with "ss -K sport = :445", kills the holders, lazy-umounts; +repeated 10 times, then ksmbd shutdown and kmemleak scan. + + state conn_alloc conn_free tcp_free opi_rcu kmemleak + ---------- ---------- --------- -------- ------- -------- + pre-patch 20 20 10 160 7 + with patch 20 20 20 160 0 + +Pre-patch conn_free=20 with tcp_free=10 directly demonstrates the +bare-kfree paths skipping transport cleanup; kmemleak backtraces point +into struct tcp_transport / iov. With this patch tcp_free matches +conn_free at 20/20 and kmemleak is clean. + +Move the per-struct final release into __ksmbd_conn_release_work() and +route the three bare-kfree final-put sites through a new +ksmbd_conn_put(). Those sites now pair ida_destroy() and +free_transport() with kfree(conn) regardless of which holder happens +to release the last reference. stop_sessions() only triggers the +transport shutdown and does not itself drop the last conn reference, +so it is unaffected. + +The centralized release reaches sock_release() -> tcp_close() -> +lock_sock_nested() (might_sleep) from every final putter, including +__free_opinfo() invoked from an RCU softirq callback, which trips +CONFIG_DEBUG_ATOMIC_SLEEP. Defer the release to a dedicated +ksmbd_conn_wq workqueue so ksmbd_conn_put() is safe from any +non-sleeping context. + +Make ksmbd_file own a strong connection reference while fp->conn is +non-NULL so durable-preserve and final-close paths cannot dereference +a stale connection. ksmbd_open_fd() and ksmbd_reopen_durable_fd() +take the reference via ksmbd_conn_get() (the latter also reorders the +fp->conn / fp->tcon assignments before __open_id() so the published fp +is never observed with fp->conn == NULL); session_fd_check() and +__ksmbd_close_fd() drop it via ksmbd_conn_put(). With that invariant, +session_fd_check() can take a local conn pointer once and use it +across the m_op_list and lock_list iterations even though op->conn +puts may otherwise drop the last reference. + +At module exit the workqueue is flushed and destroyed after +rcu_barrier(), so any release queued by a trailing RCU callback is +drained before the inode hash and module text go away. + +Fixes: ee426bfb9d09 ("ksmbd: add refcnt to ksmbd_conn struct") +Signed-off-by: DaeMyung Kang +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Stable-dep-of: c1016dd1d8b2 ("ksmbd: track the connection owning a byte-range lock") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/connection.c | 101 ++++++++++++++++++++++++++++++++++++++------- + fs/smb/server/connection.h | 6 ++ + fs/smb/server/oplock.c | 7 --- + fs/smb/server/server.c | 12 +++++ + fs/smb/server/vfs_cache.c | 60 ++++++++++++++++++++++---- + 5 files changed, 156 insertions(+), 30 deletions(-) + +--- a/fs/smb/server/connection.c ++++ b/fs/smb/server/connection.c +@@ -22,6 +22,81 @@ static struct ksmbd_conn_ops default_con + DEFINE_HASHTABLE(conn_list, CONN_HASH_BITS); + DECLARE_RWSEM(conn_list_lock); + ++static struct workqueue_struct *ksmbd_conn_wq; ++ ++int ksmbd_conn_wq_init(void) ++{ ++ ksmbd_conn_wq = alloc_workqueue("ksmbd-conn-release", ++ WQ_UNBOUND | WQ_MEM_RECLAIM, 0); ++ if (!ksmbd_conn_wq) ++ return -ENOMEM; ++ return 0; ++} ++ ++void ksmbd_conn_wq_destroy(void) ++{ ++ if (ksmbd_conn_wq) { ++ destroy_workqueue(ksmbd_conn_wq); ++ ksmbd_conn_wq = NULL; ++ } ++} ++ ++/* ++ * __ksmbd_conn_release_work() - perform the final, once-per-struct cleanup ++ * of a ksmbd_conn whose refcount has just dropped to zero. ++ * ++ * This is the common release path used by ksmbd_conn_put() for the embedded ++ * state that outlives the connection thread: async_ida and the attached ++ * transport (which owns the socket and iov for TCP). Called from a workqueue ++ * so that sleep-allowed teardown (sock_release -> tcp_close -> ++ * lock_sock_nested) never runs from an RCU softirq callback (free_opinfo_rcu) ++ * or any other non-sleeping putter context. ++ */ ++static void __ksmbd_conn_release_work(struct work_struct *work) ++{ ++ struct ksmbd_conn *conn = ++ container_of(work, struct ksmbd_conn, release_work); ++ ++ ida_destroy(&conn->async_ida); ++ conn->transport->ops->free_transport(conn->transport); ++ kfree(conn); ++} ++ ++/** ++ * ksmbd_conn_get() - take a reference on @conn and return it. ++ * ++ * Returns @conn unchanged so callers can write ++ * "fp->conn = ksmbd_conn_get(work->conn);" in one expression. Returns NULL ++ * if @conn is NULL. ++ */ ++struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn) ++{ ++ if (!conn) ++ return NULL; ++ ++ atomic_inc(&conn->refcnt); ++ return conn; ++} ++ ++/** ++ * ksmbd_conn_put() - drop a reference and, if it was the last, queue the ++ * release onto ksmbd_conn_wq so it runs from process context. ++ * ++ * Callable from any context including RCU softirq callbacks and non-sleeping ++ * locks; the actual release is deferred to the workqueue. ksmbd_conn_wq is ++ * created in ksmbd_server_init() before any conn can be allocated and is ++ * destroyed in ksmbd_server_exit() after rcu_barrier(), so it is always ++ * non-NULL while a conn reference is held. ++ */ ++void ksmbd_conn_put(struct ksmbd_conn *conn) ++{ ++ if (!conn) ++ return; ++ ++ if (atomic_dec_and_test(&conn->refcnt)) ++ queue_work(ksmbd_conn_wq, &conn->release_work); ++} ++ + /** + * ksmbd_conn_free() - free resources of the connection instance + * +@@ -36,23 +111,19 @@ void ksmbd_conn_free(struct ksmbd_conn * + hash_del(&conn->hlist); + up_write(&conn_list_lock); + ++ /* ++ * request_buf / preauth_info / mechToken are only ever accessed by the ++ * connection handler thread that owns @conn. ksmbd_conn_free() is ++ * called from the transport free_transport() path when that thread is ++ * exiting, so it is safe to release them unconditionally even when ++ * ksmbd_conn_put() below is not the final putter (oplock / ksmbd_file ++ * holders only retain the conn pointer, not these per-thread buffers). ++ */ + xa_destroy(&conn->sessions); + kvfree(conn->request_buf); + kfree(conn->preauth_info); + kfree(conn->mechToken); +- if (atomic_dec_and_test(&conn->refcnt)) { +- /* +- * async_ida is embedded in struct ksmbd_conn, so pair +- * ida_destroy() with the final kfree() rather than with +- * the unconditional field teardown above. This keeps +- * the IDA valid for the entire lifetime of the struct, +- * even while other refcount holders (oplock / vfs +- * durable handles) still reference the connection. +- */ +- ida_destroy(&conn->async_ida); +- conn->transport->ops->free_transport(conn->transport); +- kfree(conn); +- } ++ ksmbd_conn_put(conn); + } + + /** +@@ -79,6 +150,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void + conn->um = ERR_PTR(-EOPNOTSUPP); + if (IS_ERR(conn->um)) + conn->um = NULL; ++ INIT_WORK(&conn->release_work, __ksmbd_conn_release_work); + atomic_set(&conn->req_running, 0); + atomic_set(&conn->r_count, 0); + atomic_set(&conn->refcnt, 1); +@@ -457,8 +529,7 @@ void ksmbd_conn_r_count_dec(struct ksmbd + if (!atomic_dec_return(&conn->r_count) && waitqueue_active(&conn->r_count_q)) + wake_up(&conn->r_count_q); + +- if (atomic_dec_and_test(&conn->refcnt)) +- kfree(conn); ++ ksmbd_conn_put(conn); + } + + int ksmbd_conn_transport_init(void) +--- a/fs/smb/server/connection.h ++++ b/fs/smb/server/connection.h +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + + #include "smb_common.h" + #include "ksmbd_work.h" +@@ -117,6 +118,7 @@ struct ksmbd_conn { + bool binding; + atomic_t refcnt; + bool is_aapl; ++ struct work_struct release_work; + }; + + struct ksmbd_conn_ops { +@@ -162,6 +164,10 @@ void ksmbd_conn_wait_idle(struct ksmbd_c + int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id); + struct ksmbd_conn *ksmbd_conn_alloc(void); + void ksmbd_conn_free(struct ksmbd_conn *conn); ++struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn); ++void ksmbd_conn_put(struct ksmbd_conn *conn); ++int ksmbd_conn_wq_init(void); ++void ksmbd_conn_wq_destroy(void); + bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c); + int ksmbd_conn_write(struct ksmbd_work *work); + int ksmbd_conn_rdma_read(struct ksmbd_conn *conn, +--- a/fs/smb/server/oplock.c ++++ b/fs/smb/server/oplock.c +@@ -30,7 +30,6 @@ static DEFINE_RWLOCK(lease_list_lock); + static struct oplock_info *alloc_opinfo(struct ksmbd_work *work, + u64 id, __u16 Tid) + { +- struct ksmbd_conn *conn = work->conn; + struct ksmbd_session *sess = work->sess; + struct oplock_info *opinfo; + +@@ -39,7 +38,7 @@ static struct oplock_info *alloc_opinfo( + return NULL; + + opinfo->sess = sess; +- opinfo->conn = conn; ++ opinfo->conn = ksmbd_conn_get(work->conn); + opinfo->level = SMB2_OPLOCK_LEVEL_NONE; + opinfo->op_state = OPLOCK_STATE_NONE; + opinfo->pending_break = 0; +@@ -50,7 +49,6 @@ static struct oplock_info *alloc_opinfo( + init_waitqueue_head(&opinfo->oplock_brk); + atomic_set(&opinfo->refcount, 1); + atomic_set(&opinfo->breaking_cnt, 0); +- atomic_inc(&opinfo->conn->refcnt); + + return opinfo; + } +@@ -132,8 +130,7 @@ static void __free_opinfo(struct oplock_ + { + if (opinfo->is_lease) + free_lease(opinfo); +- if (opinfo->conn && atomic_dec_and_test(&opinfo->conn->refcnt)) +- kfree(opinfo->conn); ++ ksmbd_conn_put(opinfo->conn); + kfree(opinfo); + } + +--- a/fs/smb/server/server.c ++++ b/fs/smb/server/server.c +@@ -587,8 +587,14 @@ static int __init ksmbd_server_init(void + if (ret) + goto err_crypto_destroy; + ++ ret = ksmbd_conn_wq_init(); ++ if (ret) ++ goto err_workqueue_destroy; ++ + return 0; + ++err_workqueue_destroy: ++ ksmbd_workqueue_destroy(); + err_crypto_destroy: + ksmbd_crypto_destroy(); + err_release_inode_hash: +@@ -614,6 +620,12 @@ static void __exit ksmbd_server_exit(voi + { + ksmbd_server_shutdown(); + rcu_barrier(); ++ /* ++ * ksmbd_conn_put() defers the final release onto ksmbd_conn_wq, ++ * so drain it after rcu_barrier() has fired any pending RCU ++ * callbacks that may have queued a release. ++ */ ++ ksmbd_conn_wq_destroy(); + ksmbd_release_inode_hash(); + } + +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -402,6 +402,17 @@ static void __ksmbd_close_fd(struct ksmb + kfree(smb_lock); + } + ++ /* ++ * Drop fp's strong reference on conn (taken in ksmbd_open_fd() / ++ * ksmbd_reopen_durable_fd()). Durable fps that reached the ++ * scavenger have already had fp->conn cleared by session_fd_check(), ++ * in which case there is nothing to drop here. ++ */ ++ if (fp->conn) { ++ ksmbd_conn_put(fp->conn); ++ fp->conn = NULL; ++ } ++ + if (ksmbd_stream_fd(fp)) + kfree(fp->stream.name); + kfree(fp->owner.name); +@@ -694,7 +705,14 @@ struct ksmbd_file *ksmbd_open_fd(struct + atomic_set(&fp->refcount, 1); + + fp->filp = filp; +- fp->conn = work->conn; ++ /* ++ * fp owns a strong reference on fp->conn for as long as fp->conn is ++ * non-NULL, so session_fd_check() and __ksmbd_close_fd() never ++ * dereference a dangling pointer. Paired with ksmbd_conn_put() in ++ * session_fd_check() (durable preserve), in __ksmbd_close_fd() ++ * (final close), and on the error paths below. ++ */ ++ fp->conn = ksmbd_conn_get(work->conn); + fp->tcon = work->tcon; + fp->volatile_id = KSMBD_NO_FID; + fp->persistent_id = KSMBD_NO_FID; +@@ -716,6 +734,8 @@ struct ksmbd_file *ksmbd_open_fd(struct + return fp; + + err_out: ++ /* fp->conn was set and refcounted before every branch here. */ ++ ksmbd_conn_put(fp->conn); + kmem_cache_free(filp_cache, fp); + return ERR_PTR(ret); + } +@@ -1043,25 +1063,32 @@ static bool session_fd_check(struct ksmb + if (!is_reconnectable(fp)) + return false; + ++ if (WARN_ON_ONCE(!fp->conn)) ++ return false; ++ + if (ksmbd_vfs_copy_durable_owner(fp, user)) + return false; + ++ /* ++ * fp owns a strong reference on fp->conn (taken in ksmbd_open_fd() ++ * / ksmbd_reopen_durable_fd()), so conn stays valid for the whole ++ * body of this function regardless of any op->conn puts below. ++ */ + conn = fp->conn; + ci = fp->f_ci; + down_write(&ci->m_lock); + list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) { + if (op->conn != conn) + continue; +- if (op->conn && atomic_dec_and_test(&op->conn->refcnt)) +- kfree(op->conn); ++ ksmbd_conn_put(op->conn); + op->conn = NULL; + } + up_write(&ci->m_lock); + + list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) { +- spin_lock(&fp->conn->llist_lock); ++ spin_lock(&conn->llist_lock); + list_del_init(&smb_lock->clist); +- spin_unlock(&fp->conn->llist_lock); ++ spin_unlock(&conn->llist_lock); + } + + fp->conn = NULL; +@@ -1072,6 +1099,8 @@ static bool session_fd_check(struct ksmb + fp->durable_scavenger_timeout = + jiffies_to_msecs(jiffies) + fp->durable_timeout; + ++ /* Drop fp's own reference on conn. */ ++ ksmbd_conn_put(conn); + return true; + } + +@@ -1158,15 +1187,27 @@ int ksmbd_reopen_durable_fd(struct ksmbd + + old_f_state = fp->f_state; + fp->f_state = FP_NEW; ++ ++ /* ++ * Initialize fp's connection binding before publishing fp into the ++ * session's file table. If __open_id() is ordered first, a ++ * concurrent teardown that iterates the table can observe a valid ++ * volatile_id with fp->conn == NULL and preserve a ++ * partially-initialized fp. fp owns a strong reference on the new ++ * conn (see ksmbd_open_fd()); undo it on __open_id() failure. ++ */ ++ fp->conn = ksmbd_conn_get(conn); ++ fp->tcon = work->tcon; ++ + __open_id(&work->sess->file_table, fp, OPEN_ID_TYPE_VOLATILE_ID); + if (!has_file_id(fp->volatile_id)) { ++ fp->conn = NULL; ++ fp->tcon = NULL; ++ ksmbd_conn_put(conn); + fp->f_state = old_f_state; + return -EBADF; + } + +- fp->conn = conn; +- fp->tcon = work->tcon; +- + list_for_each_entry(smb_lock, &fp->lock_list, flist) { + spin_lock(&conn->llist_lock); + list_add_tail(&smb_lock->clist, &conn->lock_list); +@@ -1178,8 +1219,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd + list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) { + if (op->conn) + continue; +- op->conn = fp->conn; +- atomic_inc(&op->conn->refcnt); ++ op->conn = ksmbd_conn_get(fp->conn); + } + up_write(&ci->m_lock); + diff --git a/queue-6.12/ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch b/queue-6.12/ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch new file mode 100644 index 0000000000..1c217a9cae --- /dev/null +++ b/queue-6.12/ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch @@ -0,0 +1,142 @@ +From stable+bounces-275244-greg=kroah.com@vger.kernel.org Thu Jul 16 13:21:00 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:20:43 -0400 +Subject: ksmbd: fix path resolution in ksmbd_vfs_kern_path_create +To: stable@vger.kernel.org +Cc: Davide Ornaghi , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716112043.1732826-3-sashal@kernel.org> + +From: Davide Ornaghi + +[ Upstream commit 1c8951963d8ed357f70f59e0ad4ddce2199d2016 ] + +The SMB2 open lookup is rooted at the share with LOOKUP_BENEATH, but the +create/mkdir/hardlink sink is not: ksmbd_vfs_kern_path_create() builds an +absolute path with convert_to_unix_name() and resolves it from AT_FDCWD +via start_creating_path(), so a ".." component is walked from the real +filesystem root and escapes the export. + +An authenticated client races a missing path component so the rooted open +lookup returns -ENOENT (taking the create branch) while the same component +is present (a directory) when the create walk runs; the create then +resolves ".." out of the share. + +Root the create walk at the share like the lookup and rename paths already +are: resolve the parent with vfs_path_parent_lookup(..., LOOKUP_BENEATH, +&share_conf->vfs_path) and create the final component with +start_creating_noperm(). convert_to_unix_name() then has no callers and is +removed. + +Fixes: 265fd1991c1d ("ksmbd: use LOOKUP_BENEATH to prevent the out of share access") +Cc: stable@vger.kernel.org +Signed-off-by: Davide Ornaghi +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/misc.c | 33 --------------------------------- + fs/smb/server/misc.h | 1 - + fs/smb/server/vfs.c | 36 ++++++++++++++++++++++++++++++------ + 3 files changed, 30 insertions(+), 40 deletions(-) + +--- a/fs/smb/server/misc.c ++++ b/fs/smb/server/misc.c +@@ -278,39 +278,6 @@ char *ksmbd_extract_sharename(struct uni + return ksmbd_casefold_sharename(um, name); + } + +-/** +- * convert_to_unix_name() - convert windows name to unix format +- * @share: ksmbd_share_config pointer +- * @name: file name that is relative to share +- * +- * Return: converted name on success, otherwise NULL +- */ +-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name) +-{ +- int no_slash = 0, name_len, path_len; +- char *new_name; +- +- if (name[0] == '/') +- name++; +- +- path_len = share->path_sz; +- name_len = strlen(name); +- new_name = kmalloc(path_len + name_len + 2, KSMBD_DEFAULT_GFP); +- if (!new_name) +- return new_name; +- +- memcpy(new_name, share->path, path_len); +- if (new_name[path_len - 1] != '/') { +- new_name[path_len] = '/'; +- no_slash = 1; +- } +- +- memcpy(new_name + path_len + no_slash, name, name_len); +- path_len += name_len + no_slash; +- new_name[path_len] = 0x00; +- return new_name; +-} +- + char *ksmbd_convert_dir_info_name(struct ksmbd_dir_info *d_info, + const struct nls_table *local_nls, + int *conv_len) +--- a/fs/smb/server/misc.h ++++ b/fs/smb/server/misc.h +@@ -22,7 +22,6 @@ void ksmbd_strip_last_slash(char *path); + void ksmbd_conv_path_to_windows(char *path); + char *ksmbd_casefold_sharename(struct unicode_map *um, const char *name); + char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename); +-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name); + + #define KSMBD_DIR_INFO_ALIGNMENT 8 + struct ksmbd_dir_info; +--- a/fs/smb/server/vfs.c ++++ b/fs/smb/server/vfs.c +@@ -1319,15 +1319,39 @@ struct dentry *ksmbd_vfs_kern_path_creat + unsigned int flags, + struct path *path) + { +- char *abs_name; ++ struct ksmbd_share_config *share_conf = work->tcon->share_conf; ++ struct qstr last; ++ struct filename *filename; + struct dentry *dent; ++ int err; + +- abs_name = convert_to_unix_name(work->tcon->share_conf, name); +- if (!abs_name) +- return ERR_PTR(-ENOMEM); ++ /* resolve the name beneath the share root so ".." cannot escape */ ++ filename = getname_kernel(name); ++ if (IS_ERR(filename)) ++ return ERR_CAST(filename); + +- dent = kern_path_create(AT_FDCWD, abs_name, path, flags); +- kfree(abs_name); ++ err = vfs_path_parent_lookup(filename, flags | LOOKUP_BENEATH, ++ path, &last, &share_conf->vfs_path); ++ if (err) { ++ putname(filename); ++ return ERR_PTR(err); ++ } ++ ++ err = mnt_want_write(path->mnt); ++ if (err) { ++ path_put(path); ++ putname(filename); ++ return ERR_PTR(err); ++ } ++ ++ inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT); ++ dent = lookup_one_qstr_excl(&last, path->dentry, LOOKUP_CREATE); ++ if (IS_ERR(dent)) { ++ inode_unlock(path->dentry->d_inode); ++ mnt_drop_write(path->mnt); ++ path_put(path); ++ } ++ putname(filename); + return dent; + } + diff --git a/queue-6.12/ksmbd-track-the-connection-owning-a-byte-range-lock.patch b/queue-6.12/ksmbd-track-the-connection-owning-a-byte-range-lock.patch new file mode 100644 index 0000000000..4180fb440c --- /dev/null +++ b/queue-6.12/ksmbd-track-the-connection-owning-a-byte-range-lock.patch @@ -0,0 +1,138 @@ +From stable+bounces-275318-greg=kroah.com@vger.kernel.org Thu Jul 16 13:52:55 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:52:31 -0400 +Subject: ksmbd: track the connection owning a byte-range lock +To: stable@vger.kernel.org +Cc: Namjae Jeon , Musaab Khan , Steve French , Sasha Levin +Message-ID: <20260716115231.1785404-2-sashal@kernel.org> + +From: Namjae Jeon + +[ Upstream commit c1016dd1d8b2bcd1158bbaabe94a31bb7e7431fb ] + +SMB2_LOCK adds each granted byte-range lock to both the file lock list +and the lock list of the connection which handled the request. The +final close and durable handle paths, however, remove the connection +list entry while holding fp->conn->llist_lock. + +With SMB3 multichannel, the connection handling the LOCK request can be +different from the connection which opened the file. The entry can +therefore be removed under a different spinlock from the one protecting +the list it belongs to. A concurrent traversal can then access freed +struct ksmbd_lock and struct file_lock objects. + +Record the connection owning each lock's clist entry and hold a +reference to it while the entry is linked. Use that connection and its +llist_lock for unlock, rollback, close, and durable preserve. Durable +reconnect assigns the new connection as the owner when publishing the +locks again. + +Fixes: f5a544e3bab7 ("ksmbd: add support for SMB3 multichannel") +Cc: stable@vger.kernel.org +Reported-by: Musaab Khan +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/smb2pdu.c | 10 ++++++++-- + fs/smb/server/vfs_cache.c | 23 +++++++++++++++++------ + fs/smb/server/vfs_cache.h | 1 + + 3 files changed, 26 insertions(+), 8 deletions(-) + +--- a/fs/smb/server/smb2pdu.c ++++ b/fs/smb/server/smb2pdu.c +@@ -7602,9 +7602,11 @@ int smb2_lock(struct ksmbd_work *work) + nolock = 0; + list_del(&cmp_lock->flist); + list_del(&cmp_lock->clist); ++ cmp_lock->conn = NULL; + spin_unlock(&conn->llist_lock); + up_read(&conn_list_lock); + ++ ksmbd_conn_put(conn); + locks_free_lock(cmp_lock->fl); + kfree(cmp_lock); + goto out_check_cl; +@@ -7739,6 +7741,7 @@ skip: + goto out2; + } else if (!rc) { + list_add(&smb_lock->llist, &rollback_list); ++ smb_lock->conn = ksmbd_conn_get(work->conn); + spin_lock(&work->conn->llist_lock); + list_add_tail(&smb_lock->clist, + &work->conn->lock_list); +@@ -7793,11 +7796,14 @@ out: + } + + list_del(&smb_lock->llist); +- spin_lock(&work->conn->llist_lock); ++ conn = smb_lock->conn; ++ spin_lock(&conn->llist_lock); + if (!list_empty(&smb_lock->flist)) + list_del(&smb_lock->flist); + list_del(&smb_lock->clist); +- spin_unlock(&work->conn->llist_lock); ++ smb_lock->conn = NULL; ++ spin_unlock(&conn->llist_lock); ++ ksmbd_conn_put(conn); + + locks_free_lock(smb_lock->fl); + if (rlock) +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -391,10 +391,14 @@ static void __ksmbd_close_fd(struct ksmb + * there are not accesses to fp->lock_list. + */ + list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) { +- if (!list_empty(&smb_lock->clist) && fp->conn) { +- spin_lock(&fp->conn->llist_lock); +- list_del(&smb_lock->clist); +- spin_unlock(&fp->conn->llist_lock); ++ struct ksmbd_conn *conn = smb_lock->conn; ++ ++ if (conn) { ++ spin_lock(&conn->llist_lock); ++ list_del_init(&smb_lock->clist); ++ smb_lock->conn = NULL; ++ spin_unlock(&conn->llist_lock); ++ ksmbd_conn_put(conn); + } + + list_del(&smb_lock->flist); +@@ -1086,9 +1090,15 @@ static bool session_fd_check(struct ksmb + up_write(&ci->m_lock); + + list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) { +- spin_lock(&conn->llist_lock); ++ struct ksmbd_conn *lock_conn = smb_lock->conn; ++ ++ if (!lock_conn) ++ continue; ++ spin_lock(&lock_conn->llist_lock); + list_del_init(&smb_lock->clist); +- spin_unlock(&conn->llist_lock); ++ smb_lock->conn = NULL; ++ spin_unlock(&lock_conn->llist_lock); ++ ksmbd_conn_put(lock_conn); + } + + fp->conn = NULL; +@@ -1209,6 +1219,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd + } + + list_for_each_entry(smb_lock, &fp->lock_list, flist) { ++ smb_lock->conn = ksmbd_conn_get(conn); + spin_lock(&conn->llist_lock); + list_add_tail(&smb_lock->clist, &conn->lock_list); + spin_unlock(&conn->llist_lock); +--- a/fs/smb/server/vfs_cache.h ++++ b/fs/smb/server/vfs_cache.h +@@ -32,6 +32,7 @@ struct ksmbd_session; + + struct ksmbd_lock { + struct file_lock *fl; ++ struct ksmbd_conn *conn; + struct list_head clist; + struct list_head flist; + struct list_head llist; diff --git a/queue-6.12/ksmbd-use-opener-credentials-for-fsctl-mutations.patch b/queue-6.12/ksmbd-use-opener-credentials-for-fsctl-mutations.patch new file mode 100644 index 0000000000..4a3483f484 --- /dev/null +++ b/queue-6.12/ksmbd-use-opener-credentials-for-fsctl-mutations.patch @@ -0,0 +1,86 @@ +From stable+bounces-275247-greg=kroah.com@vger.kernel.org Thu Jul 16 13:21:06 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:20:53 -0400 +Subject: ksmbd: use opener credentials for FSCTL mutations +To: stable@vger.kernel.org +Cc: Namjae Jeon , Musaab Khan , Steve French , Sasha Levin +Message-ID: <20260716112053.1733439-1-sashal@kernel.org> + +From: Namjae Jeon + +[ Upstream commit c6394bcaf254c5baf9aff43376020be5db6d3316 ] + +SET_SPARSE, SET_ZERO_DATA and SET_COMPRESSION operate on an open SMB +handle but call VFS xattr, fallocate or fileattr helpers with the current +ksmbd worker credentials. Those helpers can revalidate inode permissions, +ownership and LSM policy independently of the SMB handle access mask. + +Run each operation with the credentials captured in the target file when +the handle was opened. Keep credential handling local to these single-file +FSCTLs rather than applying session credentials to the complete IOCTL +handler, which also contains handle-less and multi-handle operations. + +Cc: stable@vger.kernel.org +Reported-by: Musaab Khan +Signed-off-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/smb2pdu.c | 3 +++ + fs/smb/server/vfs.c | 20 +++++++++++++------- + 2 files changed, 16 insertions(+), 7 deletions(-) + +--- a/fs/smb/server/smb2pdu.c ++++ b/fs/smb/server/smb2pdu.c +@@ -8204,6 +8204,7 @@ static inline int fsctl_set_sparse(struc + if (fp->f_ci->m_fattr != old_fattr && + test_share_config_flag(work->tcon->share_conf, + KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) { ++ const struct cred *saved_cred; + struct xattr_dos_attrib da; + + ret = ksmbd_vfs_get_dos_attrib_xattr(idmap, +@@ -8212,9 +8213,11 @@ static inline int fsctl_set_sparse(struc + goto out; + + da.attr = le32_to_cpu(fp->f_ci->m_fattr); ++ saved_cred = override_creds(fp->filp->f_cred); + ret = ksmbd_vfs_set_dos_attrib_xattr(idmap, + &fp->filp->f_path, + &da, true); ++ revert_creds(saved_cred); + if (ret) + fp->f_ci->m_fattr = old_fattr; + } +--- a/fs/smb/server/vfs.c ++++ b/fs/smb/server/vfs.c +@@ -992,15 +992,21 @@ void ksmbd_vfs_set_fadvise(struct file * + int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp, + loff_t off, loff_t len) + { ++ const struct cred *saved_cred; ++ int err; ++ + smb_break_all_levII_oplock(work, fp, 1); ++ saved_cred = override_creds(fp->filp->f_cred); + if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE) +- return vfs_fallocate(fp->filp, +- FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, +- off, len); +- +- return vfs_fallocate(fp->filp, +- FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE, +- off, len); ++ err = vfs_fallocate(fp->filp, ++ FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, ++ off, len); ++ else ++ err = vfs_fallocate(fp->filp, ++ FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE, ++ off, len); ++ revert_creds(saved_cred); ++ return err; + } + + int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length, diff --git a/queue-6.12/ksmbd_vfs_rename-vfs_path_parent_lookup-accepts-err_ptr-as-name.patch b/queue-6.12/ksmbd_vfs_rename-vfs_path_parent_lookup-accepts-err_ptr-as-name.patch new file mode 100644 index 0000000000..31972b0c34 --- /dev/null +++ b/queue-6.12/ksmbd_vfs_rename-vfs_path_parent_lookup-accepts-err_ptr-as-name.patch @@ -0,0 +1,43 @@ +From stable+bounces-275242-greg=kroah.com@vger.kernel.org Thu Jul 16 13:20:54 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:20:41 -0400 +Subject: ksmbd_vfs_rename(): vfs_path_parent_lookup() accepts ERR_PTR() as name +To: stable@vger.kernel.org +Cc: Al Viro , Sasha Levin +Message-ID: <20260716112043.1732826-1-sashal@kernel.org> + +From: Al Viro + +[ Upstream commit ba33ac100d3feb1efb43b32e63cc0c6430936aa3 ] + +no need to check in the caller + +Signed-off-by: Al Viro +Stable-dep-of: 1c8951963d8e ("ksmbd: fix path resolution in ksmbd_vfs_kern_path_create") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/vfs.c | 5 ----- + 1 file changed, 5 deletions(-) + +--- a/fs/smb/server/vfs.c ++++ b/fs/smb/server/vfs.c +@@ -715,10 +715,6 @@ int ksmbd_vfs_rename(struct ksmbd_work * + return -ENOMEM; + + to = getname_kernel(newname); +- if (IS_ERR(to)) { +- err = PTR_ERR(to); +- goto revert_fsids; +- } + + retry: + err = vfs_path_parent_lookup(to, lookup_flags | LOOKUP_BENEATH, +@@ -819,7 +815,6 @@ out2: + } + out1: + putname(to); +-revert_fsids: + ksmbd_revert_fsids(work); + return err; + } diff --git a/queue-6.12/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch b/queue-6.12/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch new file mode 100644 index 0000000000..6461e520f7 --- /dev/null +++ b/queue-6.12/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch @@ -0,0 +1,73 @@ +From stable+bounces-274994-greg=kroah.com@vger.kernel.org Wed Jul 15 18:37:28 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:32:30 -0400 +Subject: media: nxp: imx8-isi: Fix use-after-free on remove +To: stable@vger.kernel.org +Cc: Xiaolei Wang , Frank Li , Laurent Pinchart , Hans Verkuil , Sasha Levin +Message-ID: <20260715163230.952514-2-sashal@kernel.org> + +From: Xiaolei Wang + +[ Upstream commit b670bf89824ede5d07d20bb9bfbafb754846081d ] + +KASAN reports a slab-use-after-free in __media_entity_remove_link() +during rmmod of imx8_isi: + + BUG: KASAN: slab-use-after-free in __media_entity_remove_link+0x608/0x650 + Read of size 2 at addr ffff0000d47cb02a by task rmmod/724 + + Call trace: + __media_entity_remove_link+0x608/0x650 + __media_entity_remove_links+0x78/0x144 + __media_device_unregister_entity+0x150/0x280 + media_device_unregister_entity+0x48/0x68 + v4l2_device_unregister_subdev+0x158/0x300 + v4l2_async_unbind_subdev_one+0x22c/0x358 + v4l2_async_nf_unbind_all_subdevs+0xfc/0x1c0 + v4l2_async_nf_unregister+0x5c/0x14c + mxc_isi_remove+0x124/0x2a0 [imx8_isi] + + Allocated by task 249: + __kmalloc_noprof+0x27c/0x690 + mxc_isi_crossbar_init+0x22c/0x560 [imx8_isi] + + Freed by task 724: + kfree+0x1e4/0x5b0 + mxc_isi_crossbar_cleanup+0x34/0x80 [imx8_isi] + mxc_isi_remove+0x11c/0x2a0 [imx8_isi] + +The problem is that mxc_isi_remove() calls mxc_isi_crossbar_cleanup() +before mxc_isi_v4l2_cleanup(). The crossbar cleanup frees the media +entity pads, but the subsequent v4l2 cleanup still tries to remove +media links that reference those pads. + +Fix this by calling mxc_isi_v4l2_cleanup() before +mxc_isi_crossbar_cleanup() to ensure all media entities are properly +unregistered while the pads are still valid. + +Fixes: cf21f328fcaf ("media: nxp: Add i.MX8 ISI driver") +Cc: stable@vger.kernel.org +Signed-off-by: Xiaolei Wang +Reviewed-by: Frank Li +Reviewed-by: Laurent Pinchart +Link: https://patch.msgid.link/20260507041318.491594-2-xiaolei.wang@windriver.com +Signed-off-by: Laurent Pinchart +Signed-off-by: Hans Verkuil +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c ++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +@@ -519,8 +519,8 @@ static void mxc_isi_remove(struct platfo + mxc_isi_pipe_cleanup(pipe); + } + +- mxc_isi_crossbar_cleanup(&isi->crossbar); + mxc_isi_v4l2_cleanup(isi); ++ mxc_isi_crossbar_cleanup(&isi->crossbar); + } + + static const struct of_device_id mxc_isi_of_match[] = { diff --git a/queue-6.12/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch b/queue-6.12/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch new file mode 100644 index 0000000000..8d93473f43 --- /dev/null +++ b/queue-6.12/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch @@ -0,0 +1,70 @@ +From stable+bounces-274993-greg=kroah.com@vger.kernel.org Wed Jul 15 18:37:25 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:32:29 -0400 +Subject: media: nxp: imx8-isi: use devm_pm_runtime_enable() to simplify code +To: stable@vger.kernel.org +Cc: Frank Li , Laurent Pinchart , Hans Verkuil , Sasha Levin +Message-ID: <20260715163230.952514-1-sashal@kernel.org> + +From: Frank Li + +[ Upstream commit 078161dd44d6f848a62a473206d69025607736ec ] + +Use devm_pm_runtime_enable() to simplify code. Change to use +dev_err_probe() because previous goto change to return. + +No functional change. + +Signed-off-by: Frank Li +Reviewed-by: Laurent Pinchart +Link: https://patch.msgid.link/20260116-cam_cleanup-v4-2-29ce01640443@nxp.com +Signed-off-by: Laurent Pinchart +Signed-off-by: Hans Verkuil +Stable-dep-of: b670bf89824e ("media: nxp: imx8-isi: Fix use-after-free on remove") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c | 16 +++++++--------- + 1 file changed, 7 insertions(+), 9 deletions(-) + +--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c ++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +@@ -472,13 +472,14 @@ static int mxc_isi_probe(struct platform + return ret; + } + +- pm_runtime_enable(dev); ++ ret = devm_pm_runtime_enable(dev); ++ if (ret) ++ return ret; + + ret = mxc_isi_crossbar_init(isi); +- if (ret) { +- dev_err(dev, "Failed to initialize crossbar: %d\n", ret); +- goto err_pm; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "Failed to initialize crossbar\n"); + + for (i = 0; i < isi->pdata->num_channels; ++i) { + ret = mxc_isi_pipe_init(isi, i); +@@ -501,8 +502,7 @@ static int mxc_isi_probe(struct platform + + err_xbar: + mxc_isi_crossbar_cleanup(&isi->crossbar); +-err_pm: +- pm_runtime_disable(isi->dev); ++ + return ret; + } + +@@ -521,8 +521,6 @@ static void mxc_isi_remove(struct platfo + + mxc_isi_crossbar_cleanup(&isi->crossbar); + mxc_isi_v4l2_cleanup(isi); +- +- pm_runtime_disable(isi->dev); + } + + static const struct of_device_id mxc_isi_of_match[] = { diff --git a/queue-6.12/mm-swap_cgroup-fix-null-deref-in-lookup_swap_cgroup_id-on-swapless-host.patch b/queue-6.12/mm-swap_cgroup-fix-null-deref-in-lookup_swap_cgroup_id-on-swapless-host.patch new file mode 100644 index 0000000000..c2b00d1ece --- /dev/null +++ b/queue-6.12/mm-swap_cgroup-fix-null-deref-in-lookup_swap_cgroup_id-on-swapless-host.patch @@ -0,0 +1,85 @@ +From stable+bounces-275057-greg=kroah.com@vger.kernel.org Wed Jul 15 23:28:16 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:28:07 -0400 +Subject: mm: swap_cgroup: fix NULL deref in lookup_swap_cgroup_id on swapless host +To: stable@vger.kernel.org +Cc: "Jose Fernandez (Anthropic)" , syzbot+e12bd9ca48157add237a@syzkaller.appspotmail.com, Barry Song , David Hildenbrand , Hugh Dickins , Johannes Weiner , Kairui Song , Michal Hocko , Muchun Song , Roman Gushchin , Shakeel Butt , Andrew Morton , Sasha Levin +Message-ID: <20260715212807.292409-1-sashal@kernel.org> + +From: "Jose Fernandez (Anthropic)" + +[ Upstream commit 63b02a9409cb5180398491b093e48bcb5315f5fb ] + +lookup_swap_cgroup_id() passes swap_cgroup_ctrl[type].map to +__swap_cgroup_id_lookup() without checking that the type was ever +registered via swap_cgroup_swapon(). On a swapless host every ctrl->map +is NULL, so __swap_cgroup_id_lookup() dereferences NULL + a scaled +swp_offset(). + +Since commit bea67dcc5eea ("mm: attempt to batch free swap entries for +zap_pte_range()"), zap_pte_range() -> swap_pte_batch() calls +lookup_swap_cgroup_id() on any non-present, non-none PTE that decodes as a +real swap entry, without first validating it against swap_info[]. A +single PTE corrupted into a type-0 swap entry takes the host down at +process exit. + +We hit this in production on a swapless 6.12.58 host: ~1s of +"get_swap_device: Bad swap file entry 3f800204222bb" (do_swap_page() being +correctly defensive about the same entry) followed by + + BUG: unable to handle page fault for address: 000003f800204220 + RIP: 0010:lookup_swap_cgroup_id+0x2b/0x60 + Call Trace: + swap_pte_batch+0xbf/0x230 + zap_pte_range+0x4c8/0x780 + unmap_page_range+0x190/0x3e0 + exit_mmap+0xd9/0x3c0 + do_exit+0x20c/0x4b0 + +syzbot has reported the identical stack. + +The source of the PTE corruption is a separate bug; this change makes the +teardown path as robust as the fault path already is. Every other caller +of lookup_swap_cgroup_id() is downstream of a get_swap_device() that has +already validated the entry, so the new branch is cold. + +Link: https://lore.kernel.org/20260504-swap-cgroup-fix-7-0-v1-1-f53ff41ee553@linux.dev +Fixes: bea67dcc5eea ("mm: attempt to batch free swap entries for zap_pte_range()") +Signed-off-by: Jose Fernandez (Anthropic) +Reported-by: syzbot+e12bd9ca48157add237a@syzkaller.appspotmail.com +Link: https://lore.kernel.org/r/69859728.050a0220.3b3015.0033.GAE@google.com +Assisted-by: Claude:unspecified +Cc: Barry Song +Cc: David Hildenbrand +Cc: Hugh Dickins +Cc: Johannes Weiner +Cc: Kairui Song +Cc: Michal Hocko +Cc: Muchun Song +Cc: Roman Gushchin +Cc: Shakeel Butt +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + mm/swap_cgroup.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/mm/swap_cgroup.c ++++ b/mm/swap_cgroup.c +@@ -161,8 +161,14 @@ unsigned short swap_cgroup_record(swp_en + */ + unsigned short lookup_swap_cgroup_id(swp_entry_t ent) + { ++ struct swap_cgroup_ctrl *ctrl; ++ + if (mem_cgroup_disabled()) + return 0; ++ ++ ctrl = &swap_cgroup_ctrl[swp_type(ent)]; ++ if (unlikely(!ctrl->map)) ++ return 0; + return lookup_swap_cgroup(ent, NULL)->id; + } + diff --git a/queue-6.12/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch b/queue-6.12/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch new file mode 100644 index 0000000000..7226b35acf --- /dev/null +++ b/queue-6.12/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch @@ -0,0 +1,77 @@ +From stable+bounces-275029-greg=kroah.com@vger.kernel.org Wed Jul 15 22:16:28 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 16:16:09 -0400 +Subject: netfilter: ebtables: Use vmalloc_array() to improve code +To: stable@vger.kernel.org +Cc: Qianfeng Rong , Florian Westphal , Sasha Levin +Message-ID: <20260715201610.64283-1-sashal@kernel.org> + +From: Qianfeng Rong + +[ Upstream commit 46015e6b3ea75297b28d4806564f3f692cf11861 ] + +Remove array_size() calls and replace vmalloc() with vmalloc_array() to +simplify the code. vmalloc_array() is also optimized better, uses fewer +instructions, and handles overflow more concisely[1]. + +[1]: https://lore.kernel.org/lkml/abc66ec5-85a4-47e1-9759-2f60ab111971@vivo.com/ +Signed-off-by: Qianfeng Rong +Signed-off-by: Florian Westphal +Stable-dep-of: cbfe53599eeb ("netfilter: ebtables: zero chainstack array") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/bridge/netfilter/ebtables.c | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +--- a/net/bridge/netfilter/ebtables.c ++++ b/net/bridge/netfilter/ebtables.c +@@ -923,8 +923,8 @@ static int translate_table(struct net *n + * if an error occurs + */ + newinfo->chainstack = +- vmalloc(array_size(nr_cpu_ids, +- sizeof(*(newinfo->chainstack)))); ++ vmalloc_array(nr_cpu_ids, ++ sizeof(*(newinfo->chainstack))); + if (!newinfo->chainstack) + return -ENOMEM; + for_each_possible_cpu(i) { +@@ -941,7 +941,7 @@ static int translate_table(struct net *n + } + } + +- cl_s = vmalloc(array_size(udc_cnt, sizeof(*cl_s))); ++ cl_s = vmalloc_array(udc_cnt, sizeof(*cl_s)); + if (!cl_s) + return -ENOMEM; + i = 0; /* the i'th udc */ +@@ -1021,8 +1021,8 @@ static int do_replace_finish(struct net + * the check on the size is done later, when we have the lock + */ + if (repl->num_counters) { +- unsigned long size = repl->num_counters * sizeof(*counterstmp); +- counterstmp = vmalloc(size); ++ counterstmp = vmalloc_array(repl->num_counters, ++ sizeof(*counterstmp)); + if (!counterstmp) + return -ENOMEM; + } +@@ -1389,7 +1389,7 @@ static int do_update_counters(struct net + if (num_counters == 0) + return -EINVAL; + +- tmp = vmalloc(array_size(num_counters, sizeof(*tmp))); ++ tmp = vmalloc_array(num_counters, sizeof(*tmp)); + if (!tmp) + return -ENOMEM; + +@@ -1531,7 +1531,7 @@ static int copy_counters_to_user(struct + if (num_counters != nentries) + return -EINVAL; + +- counterstmp = vmalloc(array_size(nentries, sizeof(*counterstmp))); ++ counterstmp = vmalloc_array(nentries, sizeof(*counterstmp)); + if (!counterstmp) + return -ENOMEM; + diff --git a/queue-6.12/netfilter-ebtables-zero-chainstack-array.patch b/queue-6.12/netfilter-ebtables-zero-chainstack-array.patch new file mode 100644 index 0000000000..d6698b9621 --- /dev/null +++ b/queue-6.12/netfilter-ebtables-zero-chainstack-array.patch @@ -0,0 +1,46 @@ +From stable+bounces-275030-greg=kroah.com@vger.kernel.org Wed Jul 15 22:16:18 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 16:16:10 -0400 +Subject: netfilter: ebtables: zero chainstack array +To: stable@vger.kernel.org +Cc: Florian Westphal , Sasha Levin +Message-ID: <20260715201610.64283-2-sashal@kernel.org> + +From: Florian Westphal + +[ Upstream commit cbfe53599eebffd188938ab6774cc41794f6f9d5 ] + +sashiko reports: + looking at ebtables table + translation, could a sparse cpu_possible_mask lead to an uninitialized pointer + free? + + If cpu_possible_mask is sparse (for example, CPU 0 and CPU 2 are possible, + but CPU 1 is not), the allocation loop skips CPU 1. If vmalloc_node() fails at + CPU 2, the cleanup loop will blindly decrement and call vfree() on + newinfo->chainstack[1]. + +Not a real-world bug, such allocation isn't expected to fail +in the first place. + +Cc: stable@vger.kernel.org +Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") +Signed-off-by: Florian Westphal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + net/bridge/netfilter/ebtables.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +--- a/net/bridge/netfilter/ebtables.c ++++ b/net/bridge/netfilter/ebtables.c +@@ -923,8 +923,7 @@ static int translate_table(struct net *n + * if an error occurs + */ + newinfo->chainstack = +- vmalloc_array(nr_cpu_ids, +- sizeof(*(newinfo->chainstack))); ++ vcalloc(nr_cpu_ids, sizeof(*(newinfo->chainstack))); + if (!newinfo->chainstack) + return -ENOMEM; + for_each_possible_cpu(i) { diff --git a/queue-6.12/pci-altera-fix-resource-leaks-on-probe-failure.patch b/queue-6.12/pci-altera-fix-resource-leaks-on-probe-failure.patch new file mode 100644 index 0000000000..3278482ae8 --- /dev/null +++ b/queue-6.12/pci-altera-fix-resource-leaks-on-probe-failure.patch @@ -0,0 +1,99 @@ +From stable+bounces-274263-greg=kroah.com@vger.kernel.org Tue Jul 14 16:22:59 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 10:06:05 -0400 +Subject: PCI: altera: Fix resource leaks on probe failure +To: stable@vger.kernel.org +Cc: Mahesh Vaidya , Manivannan Sadhasivam , "Subhransu S. Prusty" , Sasha Levin +Message-ID: <20260714140605.2729466-1-sashal@kernel.org> + +From: Mahesh Vaidya + +[ Upstream commit 7a94138caeb27f3c49c1dbd93bf422098925bb28 ] + +The chained IRQ handler is set during probe, but is only removed during the +driver remove(). If pci_host_probe() fails, the handler and INTx IRQ +domain remain set even though the devm-managed host bridge storage +containing struct altera_pcie will be released, leaving the handler with +a stale data pointer. + +Interrupts are also enabled before pci_host_probe() is called. If probe +fails after that point, the controller interrupt source should be disabled +before the chained handler and INTx domain are removed. + +So set the chained handler only after the INTx domain has been created. +Disable controller interrupts during IRQ teardown, and tear the IRQ setup +down if pci_host_probe() fails. + +Fixes: c63aed7334c2 ("PCI: altera: Use pci_host_probe() to register host") +Signed-off-by: Mahesh Vaidya +[mani: commit log] +Signed-off-by: Manivannan Sadhasivam +Reviewed-by: Subhransu S. Prusty +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260430204330.3121003-3-mahesh.vaidya@altera.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/pcie-altera.c | 28 ++++++++++++++++++++++++++-- + 1 file changed, 26 insertions(+), 2 deletions(-) + +--- a/drivers/pci/controller/pcie-altera.c ++++ b/drivers/pci/controller/pcie-altera.c +@@ -679,8 +679,18 @@ static int altera_pcie_init_irq_domain(s + return 0; + } + ++static void altera_pcie_disable_irq(struct altera_pcie *pcie) ++{ ++ if (pcie->pcie_data->version == ALTERA_PCIE_V1 || ++ pcie->pcie_data->version == ALTERA_PCIE_V2) { ++ /* Disable all P2A interrupts */ ++ cra_writel(pcie, 0, P2A_INT_ENABLE); ++ } ++} ++ + static void altera_pcie_irq_teardown(struct altera_pcie *pcie) + { ++ altera_pcie_disable_irq(pcie); + irq_set_chained_handler_and_data(pcie->irq, NULL, NULL); + irq_domain_remove(pcie->irq_domain); + } +@@ -705,7 +715,6 @@ static int altera_pcie_parse_dt(struct a + if (pcie->irq < 0) + return pcie->irq; + +- irq_set_chained_handler_and_data(pcie->irq, altera_pcie_isr, pcie); + return 0; + } + +@@ -790,6 +799,12 @@ static int altera_pcie_probe(struct plat + return ret; + } + ++ /* ++ * The chained handler uses pcie->irq_domain, so set it only after the ++ * INTx domain has been created. ++ */ ++ irq_set_chained_handler_and_data(pcie->irq, altera_pcie_isr, pcie); ++ + /* clear all interrupts */ + cra_writel(pcie, P2A_INT_STS_ALL, P2A_INT_STATUS); + /* enable all interrupts */ +@@ -800,7 +815,16 @@ static int altera_pcie_probe(struct plat + bridge->busnr = pcie->root_bus_nr; + bridge->ops = &altera_pcie_ops; + +- return pci_host_probe(bridge); ++ ret = pci_host_probe(bridge); ++ if (ret) ++ goto err_teardown_irq; ++ ++ return 0; ++ ++err_teardown_irq: ++ altera_pcie_irq_teardown(pcie); ++ ++ return ret; + } + + static void altera_pcie_remove(struct platform_device *pdev) diff --git a/queue-6.12/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch b/queue-6.12/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch new file mode 100644 index 0000000000..604710d99e --- /dev/null +++ b/queue-6.12/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch @@ -0,0 +1,168 @@ +From stable+bounces-274543-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:42 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:22 -0400 +Subject: PCI: controller: Use dev_fwnode() instead of of_fwnode_handle() +To: stable@vger.kernel.org +Cc: "Jiri Slaby (SUSE)" , Arnd Bergmann , Bjorn Helgaas , Sasha Levin +Message-ID: <20260714200226.3152882-1-sashal@kernel.org> + +From: "Jiri Slaby (SUSE)" + +[ Upstream commit a103d2dede5683dabbac2c3374bc24b6a9434478 ] + +All irq_domain functions now accept fwnode instead of of_node. But many +PCI controllers still extract dev to of_node and then of_node to fwnode. + +Instead, clean this up and simply use the dev_fwnode() helper to extract +fwnode directly from dev. Internally, it still does dev => of_node => +fwnode steps, but it's now hidden from the users. + +In the case of altera, this also removes an unused 'node' variable that is +only used when CONFIG_OF is enabled: + + drivers/pci/controller/pcie-altera.c: In function 'altera_pcie_init_irq_domain': + drivers/pci/controller/pcie-altera.c:855:29: error: unused variable 'node' [-Werror=unused-variable] + 855 | struct device_node *node = dev->of_node; + +Signed-off-by: Jiri Slaby (SUSE) +Signed-off-by: Arnd Bergmann # altera +[bhelgaas: squash together, rebase to precede msi-parent] +Signed-off-by: Bjorn Helgaas +Link: https://patch.msgid.link/20250521163329.2137973-1-arnd@kernel.org +Link: https://patch.msgid.link/20250611104348.192092-16-jirislaby@kernel.org +Link: https://patch.msgid.link/20250723065907.1841758-1-jirislaby@kernel.org +Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/dwc/pcie-designware-host.c | 2 +- + drivers/pci/controller/mobiveil/pcie-mobiveil-host.c | 8 +++----- + drivers/pci/controller/pcie-altera-msi.c | 2 +- + drivers/pci/controller/pcie-altera.c | 3 +-- + drivers/pci/controller/pcie-mediatek-gen3.c | 4 ++-- + drivers/pci/controller/pcie-mediatek.c | 2 +- + drivers/pci/controller/pcie-xilinx-dma-pl.c | 2 +- + drivers/pci/controller/pcie-xilinx-nwl.c | 2 +- + drivers/pci/controller/plda/pcie-plda-host.c | 2 +- + 9 files changed, 12 insertions(+), 15 deletions(-) + +--- a/drivers/pci/controller/dwc/pcie-designware-host.c ++++ b/drivers/pci/controller/dwc/pcie-designware-host.c +@@ -227,7 +227,7 @@ static const struct irq_domain_ops dw_pc + int dw_pcie_allocate_domains(struct dw_pcie_rp *pp) + { + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); +- struct fwnode_handle *fwnode = of_node_to_fwnode(pci->dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(pci->dev); + + pp->irq_domain = irq_domain_create_linear(fwnode, pp->num_vectors, + &dw_pcie_msi_domain_ops, pp); +--- a/drivers/pci/controller/mobiveil/pcie-mobiveil-host.c ++++ b/drivers/pci/controller/mobiveil/pcie-mobiveil-host.c +@@ -435,7 +435,7 @@ static const struct irq_domain_ops msi_d + static int mobiveil_allocate_msi_domains(struct mobiveil_pcie *pcie) + { + struct device *dev = &pcie->pdev->dev; +- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(dev); + struct mobiveil_msi *msi = &pcie->rp.msi; + + mutex_init(&msi->lock); +@@ -461,13 +461,11 @@ static int mobiveil_allocate_msi_domains + static int mobiveil_pcie_init_irq_domain(struct mobiveil_pcie *pcie) + { + struct device *dev = &pcie->pdev->dev; +- struct device_node *node = dev->of_node; + struct mobiveil_root_port *rp = &pcie->rp; + + /* setup INTx */ +- rp->intx_domain = irq_domain_add_linear(node, PCI_NUM_INTX, +- &intx_domain_ops, pcie); +- ++ rp->intx_domain = irq_domain_create_linear(dev_fwnode(dev), PCI_NUM_INTX, &intx_domain_ops, ++ pcie); + if (!rp->intx_domain) { + dev_err(dev, "Failed to get a INTx IRQ domain\n"); + return -ENOMEM; +--- a/drivers/pci/controller/pcie-altera-msi.c ++++ b/drivers/pci/controller/pcie-altera-msi.c +@@ -164,7 +164,7 @@ static const struct irq_domain_ops msi_d + + static int altera_allocate_domains(struct altera_msi *msi) + { +- struct fwnode_handle *fwnode = of_node_to_fwnode(msi->pdev->dev.of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(&msi->pdev->dev); + + msi->inner_domain = irq_domain_add_linear(NULL, msi->num_of_vectors, + &msi_domain_ops, msi); +--- a/drivers/pci/controller/pcie-altera.c ++++ b/drivers/pci/controller/pcie-altera.c +@@ -666,10 +666,9 @@ static void altera_pcie_isr(struct irq_d + static int altera_pcie_init_irq_domain(struct altera_pcie *pcie) + { + struct device *dev = &pcie->pdev->dev; +- struct device_node *node = dev->of_node; + + /* Setup INTx */ +- pcie->irq_domain = irq_domain_add_linear(node, PCI_NUM_INTX, ++ pcie->irq_domain = irq_domain_create_linear(dev_fwnode(dev), PCI_NUM_INTX, + &intx_domain_ops, pcie); + if (!pcie->irq_domain) { + dev_err(dev, "Failed to get a INTx IRQ domain\n"); +--- a/drivers/pci/controller/pcie-mediatek-gen3.c ++++ b/drivers/pci/controller/pcie-mediatek-gen3.c +@@ -695,8 +695,8 @@ static int mtk_pcie_init_irq_domains(str + /* Setup MSI */ + mutex_init(&pcie->lock); + +- pcie->msi_bottom_domain = irq_domain_add_linear(node, PCIE_MSI_IRQS_NUM, +- &mtk_msi_bottom_domain_ops, pcie); ++ pcie->msi_bottom_domain = irq_domain_create_linear(dev_fwnode(dev), PCIE_MSI_IRQS_NUM, ++ &mtk_msi_bottom_domain_ops, pcie); + if (!pcie->msi_bottom_domain) { + dev_err(dev, "failed to create MSI bottom domain\n"); + ret = -ENODEV; +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -488,7 +488,7 @@ static struct msi_domain_info mtk_msi_do + + static int mtk_pcie_allocate_msi_domains(struct mtk_pcie_port *port) + { +- struct fwnode_handle *fwnode = of_node_to_fwnode(port->pcie->dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(port->pcie->dev); + + mutex_init(&port->lock); + +--- a/drivers/pci/controller/pcie-xilinx-dma-pl.c ++++ b/drivers/pci/controller/pcie-xilinx-dma-pl.c +@@ -470,7 +470,7 @@ static int xilinx_pl_dma_pcie_init_msi_i + struct device *dev = port->dev; + struct xilinx_msi *msi = &port->msi; + int size = BITS_TO_LONGS(XILINX_NUM_MSI_IRQS) * sizeof(long); +- struct fwnode_handle *fwnode = of_node_to_fwnode(port->dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(port->dev); + + msi->dev_domain = irq_domain_add_linear(NULL, XILINX_NUM_MSI_IRQS, + &dev_msi_domain_ops, port); +--- a/drivers/pci/controller/pcie-xilinx-nwl.c ++++ b/drivers/pci/controller/pcie-xilinx-nwl.c +@@ -495,7 +495,7 @@ static int nwl_pcie_init_msi_irq_domain( + { + #ifdef CONFIG_PCI_MSI + struct device *dev = pcie->dev; +- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(dev); + struct nwl_msi *msi = &pcie->msi; + + msi->dev_domain = irq_domain_add_linear(NULL, INT_PCI_MSI_NR, +--- a/drivers/pci/controller/plda/pcie-plda-host.c ++++ b/drivers/pci/controller/plda/pcie-plda-host.c +@@ -150,7 +150,7 @@ static struct msi_domain_info plda_msi_d + static int plda_allocate_msi_domains(struct plda_pcie_rp *port) + { + struct device *dev = port->dev; +- struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node); ++ struct fwnode_handle *fwnode = dev_fwnode(dev); + struct plda_msi *msi = &port->msi; + + mutex_init(&port->msi.lock); diff --git a/queue-6.12/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch b/queue-6.12/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch new file mode 100644 index 0000000000..ac0af4eb66 --- /dev/null +++ b/queue-6.12/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch @@ -0,0 +1,403 @@ +From stable+bounces-274581-greg=kroah.com@vger.kernel.org Wed Jul 15 00:04:47 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:19 -0400 +Subject: PCI: Fix restoring BARs on BAR resize rollback path +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Simon Richter" , "Alex Bennée" , "Bjorn Helgaas" , "Christian König" , "Sasha Levin" +Message-ID: <20260714220421.3334071-4-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit 337b1b566db087347194e4543ddfdfa5645275cc ] + +BAR resize operation is implemented in the pci_resize_resource() and +pbus_reassign_bridge_resources() functions. pci_resize_resource() can be +called either from __resource_resize_store() from sysfs or directly by the +driver for the Endpoint Device. + +The pci_resize_resource() requires that caller has released the device +resources that share the bridge window with the BAR to be resized as +otherwise the bridge window is pinned in place and cannot be changed. + +pbus_reassign_bridge_resources() rolls back resources if the resize +operation fails, but rollback is performed only for the bridge windows. +Because releasing the device resources are done by the caller of the BAR +resize interface, these functions performing the BAR resize do not have +access to the device resources as they were before the resize. + +pbus_reassign_bridge_resources() could try __pci_bridge_assign_resources() +after rolling back the bridge windows as they were, however, it will not +guarantee the resource are assigned due to differences in how FW and the +kernel assign the resources (alignment of the start address and tail). + +To perform rollback robustly, the BAR resize interface has to be altered to +also release the device resources that share the bridge window with the BAR +to be resized. + +Also, remove restoring from the entries failed list as saved list should +now contain both the bridge windows and device resources so the extra +restore is duplicated work. + +Some drivers (currently only amdgpu) want to prevent releasing some +resources. Add exclude_bars param to pci_resize_resource() and make amdgpu +pass its register BAR (BAR 2 or 5), which should never be released during +resize operation. Normally 64-bit prefetchable resources do not share a +bridge window with the 32-bit only register BAR, but there are various +fallbacks in the resource assignment logic which may make the resources +share the bridge window in rare cases. + +This change (together with the driver side changes) is to counter the +resource releases that had to be done to prevent resource tree corruption +in the ("PCI: Release assigned resource before restoring them") change. As +such, it likely restores functionality in cases where device resources were +released to avoid resource tree conflicts which appeared to be "working" +when such conflicts were not correctly detected by the kernel. + +Reported-by: Simon Richter +Link: https://lore.kernel.org/linux-pci/f9a8c975-f5d3-4dd2-988e-4371a1433a60@hogyros.de/ +Reported-by: Alex Bennée +Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/ +Signed-off-by: Ilpo Järvinen +[bhelgaas: squash amdgpu BAR selection from +https: //lore.kernel.org/r/20251114103053.13778-1-ilpo.jarvinen@linux.intel.com] +Signed-off-by: Bjorn Helgaas +Tested-by: Alex Bennée # AVA, AMD GPU +Reviewed-by: Christian König +Link: https://patch.msgid.link/20251113162628.5946-7-ilpo.jarvinen@linux.intel.com +Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 4 - + drivers/gpu/drm/i915/gt/intel_region_lmem.c | 2 + drivers/gpu/drm/xe/xe_vram.c | 2 + drivers/pci/pci-sysfs.c | 17 ---- + drivers/pci/pci.h | 3 + drivers/pci/setup-bus.c | 101 +++++++++++++++++++--------- + drivers/pci/setup-res.c | 19 +---- + include/linux/pci.h | 4 - + 8 files changed, 87 insertions(+), 65 deletions(-) + +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +@@ -1611,7 +1611,9 @@ int amdgpu_device_resize_fb_bar(struct a + + pci_release_resource(adev->pdev, 0); + +- r = pci_resize_resource(adev->pdev, 0, rbar_size); ++ r = pci_resize_resource(adev->pdev, 0, rbar_size, ++ (adev->asic_type >= CHIP_BONAIRE) ? 1 << 5 ++ : 1 << 2); + if (r == -ENOSPC) + DRM_INFO("Not enough PCI address space for a large BAR."); + else if (r && r != -ENOTSUPP) +--- a/drivers/gpu/drm/i915/gt/intel_region_lmem.c ++++ b/drivers/gpu/drm/i915/gt/intel_region_lmem.c +@@ -37,7 +37,7 @@ _resize_bar(struct drm_i915_private *i91 + + _release_bars(pdev); + +- ret = pci_resize_resource(pdev, resno, bar_size); ++ ret = pci_resize_resource(pdev, resno, bar_size, 0); + if (ret) { + drm_info(&i915->drm, "Failed to resize BAR%d to %dM (%pe)\n", + resno, 1 << bar_size, ERR_PTR(ret)); +--- a/drivers/gpu/drm/xe/xe_vram.c ++++ b/drivers/gpu/drm/xe/xe_vram.c +@@ -33,7 +33,7 @@ _resize_bar(struct xe_device *xe, int re + if (pci_resource_len(pdev, resno)) + pci_release_resource(pdev, resno); + +- ret = pci_resize_resource(pdev, resno, bar_size); ++ ret = pci_resize_resource(pdev, resno, bar_size, 0); + if (ret) { + drm_info(&xe->drm, "Failed to resize BAR%d to %dM (%pe). Consider enabling 'Resizable BAR' support in your BIOS\n", + resno, 1 << bar_size, ERR_PTR(ret)); +--- a/drivers/pci/pci-sysfs.c ++++ b/drivers/pci/pci-sysfs.c +@@ -1427,18 +1427,13 @@ static ssize_t __resource_resize_store(s + { + struct pci_dev *pdev = to_pci_dev(dev); + struct pci_bus *bus = pdev->bus; +- struct resource *b_win, *res; + unsigned long size; +- int ret, i; ++ int ret; + u16 cmd; + + if (kstrtoul(buf, 0, &size) < 0) + return -EINVAL; + +- b_win = pbus_select_window(bus, pci_resource_n(pdev, n)); +- if (!b_win) +- return -EINVAL; +- + device_lock(dev); + if (dev->driver || pci_num_vf(pdev)) { + ret = -EBUSY; +@@ -1460,15 +1455,7 @@ static ssize_t __resource_resize_store(s + + pci_remove_resource_files(pdev); + +- pci_dev_for_each_resource(pdev, res, i) { +- if (i >= PCI_BRIDGE_RESOURCES) +- break; +- +- if (b_win == pbus_select_window(bus, res)) +- pci_release_resource(pdev, i); +- } +- +- ret = pci_resize_resource(pdev, n, size); ++ ret = pci_resize_resource(pdev, n, size, 0); + + pci_assign_unassigned_bus_resources(bus); + +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -330,6 +330,9 @@ enum pci_bar_type { + struct device *pci_get_host_bridge_device(struct pci_dev *dev); + void pci_put_host_bridge_device(struct device *dev); + ++int pci_do_resource_release_and_resize(struct pci_dev *dev, int resno, int size, ++ int exclude_bars); ++ + int pci_configure_extended_tags(struct pci_dev *dev, void *ign); + bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *pl, + int rrs_timeout); +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2297,18 +2297,16 @@ enable_all: + } + EXPORT_SYMBOL_GPL(pci_assign_unassigned_bridge_resources); + +-int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type) ++static int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type, ++ struct list_head *saved) + { + struct pci_dev_resource *dev_res; + struct pci_dev *next; +- LIST_HEAD(saved); + LIST_HEAD(added); + LIST_HEAD(failed); + unsigned int i; + int ret; + +- down_read(&pci_bus_sem); +- + /* Walk to the root hub, releasing bridge BARs when possible */ + next = bridge; + do { +@@ -2325,9 +2323,9 @@ int pci_reassign_bridge_resources(struct + if (res->child) + continue; + +- ret = add_to_list(&saved, bridge, res, 0, 0); ++ ret = add_to_list(saved, bridge, res, 0, 0); + if (ret) +- goto cleanup; ++ return ret; + + pci_info(bridge, "%s %pR: releasing\n", res_name, res); + +@@ -2343,67 +2341,108 @@ int pci_reassign_bridge_resources(struct + next = bridge->bus ? bridge->bus->self : NULL; + } while (next); + +- if (list_empty(&saved)) { +- up_read(&pci_bus_sem); ++ if (list_empty(saved)) + return -ENOENT; +- } + + __pci_bus_size_bridges(bridge->subordinate, &added); + __pci_bridge_assign_resources(bridge, &added, &failed); + BUG_ON(!list_empty(&added)); + + if (!list_empty(&failed)) { +- ret = -ENOSPC; +- goto cleanup; ++ free_list(&failed); ++ return -ENOSPC; + } + +- list_for_each_entry(dev_res, &saved, list) { ++ list_for_each_entry(dev_res, saved, list) { + /* Skip the bridge we just assigned resources for */ + if (bridge == dev_res->dev) + continue; + ++ if (!dev_res->dev->subordinate) ++ continue; ++ + bridge = dev_res->dev; + pci_setup_bridge(bridge->subordinate); + } + +- free_list(&saved); +- up_read(&pci_bus_sem); + return 0; ++} + +-cleanup: +- /* Restore size and flags */ +- list_for_each_entry(dev_res, &failed, list) { +- struct resource *res = dev_res->res; ++int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size, ++ int exclude_bars) ++{ ++ struct resource *res = pdev->resource + resno; ++ unsigned long flags = res->flags; ++ struct pci_dev_resource *dev_res; ++ struct pci_bus *bus = pdev->bus; ++ struct resource *r; ++ LIST_HEAD(saved); ++ unsigned int i; ++ int ret = 0; + +- res->start = dev_res->start; +- res->end = dev_res->end; +- res->flags = dev_res->flags; ++ down_read(&pci_bus_sem); ++ ++ pci_dev_for_each_resource(pdev, r, i) { ++ if (i >= PCI_BRIDGE_RESOURCES) ++ break; ++ ++ if (exclude_bars & BIT(i)) ++ continue; ++ ++ if (!pci_resource_len(pdev, i) || r->flags != flags) ++ continue; ++ ++ ret = add_to_list(&saved, pdev, r, 0, 0); ++ if (ret) ++ goto restore; ++ pci_release_resource(pdev, i); + } +- free_list(&failed); + ++ res->end = res->start + pci_rebar_size_to_bytes(size) - 1; ++ ++ if (!bus->self) ++ goto out; ++ ++ ret = pci_reassign_bridge_resources(bus->self, res->flags, &saved); ++ if (ret) ++ goto restore; ++ ++out: ++ up_read(&pci_bus_sem); ++ free_list(&saved); ++ return ret; ++ ++restore: + /* Revert to the old configuration */ + list_for_each_entry(dev_res, &saved, list) { + struct resource *res = dev_res->res; ++ struct pci_dev *dev = dev_res->dev; + +- bridge = dev_res->dev; +- i = res - bridge->resource; ++ i = res - dev->resource; + + if (res->parent) { + release_child_resources(res); +- pci_release_resource(bridge, i); ++ pci_release_resource(dev, i); + } + + res->start = dev_res->start; + res->end = dev_res->end; + res->flags = dev_res->flags; + +- pci_claim_resource(bridge, i); +- pci_setup_bridge(bridge->subordinate); +- } +- up_read(&pci_bus_sem); +- free_list(&saved); ++ if (pci_claim_resource(dev, i)) ++ continue; + +- return ret; ++ if (i < PCI_BRIDGE_RESOURCES) { ++ const char *res_name = pci_resource_name(dev, i); ++ ++ pci_update_resource(dev, i); ++ pci_info(dev, "%s %pR: old value restored\n", ++ res_name, res); ++ } ++ if (dev->subordinate) ++ pci_setup_bridge(dev->subordinate); ++ } ++ goto out; + } + + void pci_assign_unassigned_bus_resources(struct pci_bus *bus) +--- a/drivers/pci/setup-res.c ++++ b/drivers/pci/setup-res.c +@@ -429,9 +429,9 @@ void pci_release_resource(struct pci_dev + } + EXPORT_SYMBOL(pci_release_resource); + +-int pci_resize_resource(struct pci_dev *dev, int resno, int size) ++int pci_resize_resource(struct pci_dev *dev, int resno, int size, ++ int exclude_bars) + { +- struct resource *res = dev->resource + resno; + struct pci_host_bridge *host; + int old, ret; + u32 sizes; +@@ -442,10 +442,6 @@ int pci_resize_resource(struct pci_dev * + if (host->preserve_config) + return -ENOTSUPP; + +- /* Make sure the resource isn't assigned before resizing it. */ +- if (!(res->flags & IORESOURCE_UNSET)) +- return -EBUSY; +- + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (cmd & PCI_COMMAND_MEMORY) + return -EBUSY; +@@ -465,19 +461,14 @@ int pci_resize_resource(struct pci_dev * + if (ret) + return ret; + +- res->end = res->start + pci_rebar_size_to_bytes(size) - 1; ++ ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars); ++ if (ret) ++ goto error_resize; + +- /* Check if the new config works by trying to assign everything. */ +- if (dev->bus->self) { +- ret = pci_reassign_bridge_resources(dev->bus->self, res->flags); +- if (ret) +- goto error_resize; +- } + return 0; + + error_resize: + pci_rebar_set_size(dev, resno, old); +- res->end = res->start + pci_rebar_size_to_bytes(old) - 1; + return ret; + } + EXPORT_SYMBOL(pci_resize_resource); +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -1418,7 +1418,8 @@ static inline int pci_rebar_bytes_to_siz + } + + u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar); +-int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size); ++int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size, ++ int exclude_bars); + int pci_select_bars(struct pci_dev *dev, unsigned long flags); + bool pci_device_is_present(struct pci_dev *pdev); + void pci_ignore_hotplug(struct pci_dev *dev); +@@ -1488,7 +1489,6 @@ void pci_assign_unassigned_resources(voi + void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge); + void pci_assign_unassigned_bus_resources(struct pci_bus *bus); + void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus); +-int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type); + int pci_enable_resources(struct pci_dev *, int mask); + void pci_assign_irq(struct pci_dev *dev); + struct resource *pci_find_resource(struct pci_dev *dev, struct resource *res); diff --git a/queue-6.12/pci-free-saved-list-without-holding-pci_bus_sem.patch b/queue-6.12/pci-free-saved-list-without-holding-pci_bus_sem.patch new file mode 100644 index 0000000000..4f80b7a667 --- /dev/null +++ b/queue-6.12/pci-free-saved-list-without-holding-pci_bus_sem.patch @@ -0,0 +1,38 @@ +From stable+bounces-274580-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:07 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:18 -0400 +Subject: PCI: Free saved list without holding pci_bus_sem +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Alex Bennée" , "Sasha Levin" +Message-ID: <20260714220421.3334071-3-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit 1d8a0506f69895b7cfd9d5c4546761c508231a8a ] + +Freeing the saved list does not require holding pci_bus_sem, so the +critical section can be made shorter. + +Signed-off-by: Ilpo Järvinen +Signed-off-by: Bjorn Helgaas +Tested-by: Alex Bennée # AVA, AMD GPU +Link: https://patch.msgid.link/20251113162628.5946-6-ilpo.jarvinen@linux.intel.com +Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/setup-bus.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2400,8 +2400,8 @@ cleanup: + pci_claim_resource(bridge, i); + pci_setup_bridge(bridge->subordinate); + } +- free_list(&saved); + up_read(&pci_bus_sem); ++ free_list(&saved); + + return ret; + } diff --git a/queue-6.12/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch b/queue-6.12/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch new file mode 100644 index 0000000000..33b82cc6e1 --- /dev/null +++ b/queue-6.12/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch @@ -0,0 +1,127 @@ +From stable+bounces-274539-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:27 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:24 -0400 +Subject: PCI: mediatek: Convert bool to single quirks entry and bitmap +To: stable@vger.kernel.org +Cc: Christian Marangi , Manivannan Sadhasivam , AngeloGioacchino Del Regno , Sasha Levin +Message-ID: <20260714200226.3152882-3-sashal@kernel.org> + +From: Christian Marangi + +[ Upstream commit 04305367fab7ec9c98eeba315ad09c8b20abce93 ] + +To clean Mediatek SoC PCIe struct, convert all the bool to a bitmap and +use a single quirks to reference all the values. This permits cleaner +addition of new quirk without having to define a new bool in the struct. + +Signed-off-by: Christian Marangi +Signed-off-by: Manivannan Sadhasivam +Reviewed-by: AngeloGioacchino Del Regno +Link: https://patch.msgid.link/20251020111121.31779-4-ansuelsmth@gmail.com +Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/pcie-mediatek.c | 33 ++++++++++++++++++++------------- + 1 file changed, 20 insertions(+), 13 deletions(-) + +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -144,23 +144,31 @@ + struct mtk_pcie_port; + + /** ++ * enum mtk_pcie_quirks - MTK PCIe quirks ++ * @MTK_PCIE_FIX_CLASS_ID: host's class ID needed to be fixed ++ * @MTK_PCIE_FIX_DEVICE_ID: host's device ID needed to be fixed ++ * @MTK_PCIE_NO_MSI: Bridge has no MSI support, and relies on an external block ++ */ ++enum mtk_pcie_quirks { ++ MTK_PCIE_FIX_CLASS_ID = BIT(0), ++ MTK_PCIE_FIX_DEVICE_ID = BIT(1), ++ MTK_PCIE_NO_MSI = BIT(2), ++}; ++ ++/** + * struct mtk_pcie_soc - differentiate between host generations +- * @need_fix_class_id: whether this host's class ID needed to be fixed or not +- * @need_fix_device_id: whether this host's device ID needed to be fixed or not +- * @no_msi: Bridge has no MSI support, and relies on an external block + * @device_id: device ID which this host need to be fixed + * @ops: pointer to configuration access functions + * @startup: pointer to controller setting functions + * @setup_irq: pointer to initialize IRQ functions ++ * @quirks: PCIe device quirks. + */ + struct mtk_pcie_soc { +- bool need_fix_class_id; +- bool need_fix_device_id; +- bool no_msi; + unsigned int device_id; + struct pci_ops *ops; + int (*startup)(struct mtk_pcie_port *port); + int (*setup_irq)(struct mtk_pcie_port *port, struct device_node *node); ++ enum mtk_pcie_quirks quirks; + }; + + /** +@@ -708,7 +716,7 @@ static int mtk_pcie_startup_port_v2(stru + writel(val, port->base + PCIE_RST_CTRL); + + /* Set up vendor ID and class code */ +- if (soc->need_fix_class_id) { ++ if (soc->quirks & MTK_PCIE_FIX_CLASS_ID) { + val = PCI_VENDOR_ID_MEDIATEK; + writew(val, port->base + PCIE_CONF_VEND_ID); + +@@ -716,7 +724,7 @@ static int mtk_pcie_startup_port_v2(stru + writew(val, port->base + PCIE_CONF_CLASS_ID); + } + +- if (soc->need_fix_device_id) ++ if (soc->quirks & MTK_PCIE_FIX_DEVICE_ID) + writew(soc->device_id, port->base + PCIE_CONF_DEVICE_ID); + + /* 100ms timeout value should be enough for Gen1/2 training */ +@@ -1117,7 +1125,7 @@ static int mtk_pcie_probe(struct platfor + + host->ops = pcie->soc->ops; + host->sysdata = pcie; +- host->msi_domain = pcie->soc->no_msi; ++ host->msi_domain = !!(pcie->soc->quirks & MTK_PCIE_NO_MSI); + + err = pci_host_probe(host); + if (err) +@@ -1205,9 +1213,9 @@ static const struct dev_pm_ops mtk_pcie_ + }; + + static const struct mtk_pcie_soc mtk_pcie_soc_v1 = { +- .no_msi = true, + .ops = &mtk_pcie_ops, + .startup = mtk_pcie_startup_port, ++ .quirks = MTK_PCIE_NO_MSI, + }; + + static const struct mtk_pcie_soc mtk_pcie_soc_mt2712 = { +@@ -1217,19 +1225,18 @@ static const struct mtk_pcie_soc mtk_pci + }; + + static const struct mtk_pcie_soc mtk_pcie_soc_mt7622 = { +- .need_fix_class_id = true, + .ops = &mtk_pcie_ops_v2, + .startup = mtk_pcie_startup_port_v2, + .setup_irq = mtk_pcie_setup_irq, ++ .quirks = MTK_PCIE_FIX_CLASS_ID, + }; + + static const struct mtk_pcie_soc mtk_pcie_soc_mt7629 = { +- .need_fix_class_id = true, +- .need_fix_device_id = true, + .device_id = PCI_DEVICE_ID_MEDIATEK_7629, + .ops = &mtk_pcie_ops_v2, + .startup = mtk_pcie_startup_port_v2, + .setup_irq = mtk_pcie_setup_irq, ++ .quirks = MTK_PCIE_FIX_CLASS_ID | MTK_PCIE_FIX_DEVICE_ID, + }; + + static const struct of_device_id mtk_pcie_ids[] = { diff --git a/queue-6.12/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch b/queue-6.12/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch new file mode 100644 index 0000000000..c786e5bb2f --- /dev/null +++ b/queue-6.12/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch @@ -0,0 +1,204 @@ +From stable+bounces-274541-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:40 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:26 -0400 +Subject: PCI: mediatek: Fix IRQ domain leak when port fails to enable +To: stable@vger.kernel.org +Cc: Manivannan Sadhasivam , Manivannan Sadhasivam , Caleb James DeLisle , Sasha Levin +Message-ID: <20260714200226.3152882-5-sashal@kernel.org> + +From: Manivannan Sadhasivam + +[ Upstream commit f865a57896bd92d7662eb2818d8f48872e2cbbc7 ] + +When mtk_pcie_enable_port() fails, mtk_pcie_port_free() removes the port +from pcie->ports and frees the port structure. However, the IRQ domains set +up earlier by mtk_pcie_init_irq_domain() are never freed. + +Fix this by refactoring mtk_pcie_irq_teardown() into a per-port helper, +mtk_pcie_irq_teardown_port(), and calling it from mtk_pcie_setup() when +mtk_pcie_enable_port() fails. Since the IRQ teardown must only happen in +the probe error path (during resume, child devices may have active MSI +mappings and the NOIRQ context prohibits sleeping locks), +mtk_pcie_enable_port() is changed to return an error code so callers can +distinguish the two paths and act accordingly. + +This issue was reported by Sashiko while reviewing the EcoNet EN7528 SoC +support series. + +Fixes: b099631df160 ("PCI: mediatek: Add controller support for MT2712 and MT7622") +Signed-off-by: Manivannan Sadhasivam +Signed-off-by: Manivannan Sadhasivam +Cc: stable@vger.kernel.org # 5.10 +Cc: Caleb James DeLisle +Link: https://patch.msgid.link/20260521174617.17692-1-mani@kernel.org +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/pcie-mediatek.c | 75 +++++++++++++++++++++------------ + 1 file changed, 48 insertions(+), 27 deletions(-) + +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -13,7 +13,7 @@ + #include + #include + #include +-#include ++#include "../../irqchip/irq-msi-lib.h" + #include + #include + #include +@@ -491,7 +491,6 @@ static const struct msi_parent_ops mtk_m + .required_flags = MTK_MSI_FLAGS_REQUIRED, + .supported_flags = MTK_MSI_FLAGS_SUPPORTED, + .bus_select_token = DOMAIN_BUS_PCI_MSI, +- .chip_flags = MSI_CHIP_FLAG_SET_ACK, + .prefix = "MTK-", + .init_dev_msi_info = msi_lib_init_dev_msi_info, + }; +@@ -505,13 +504,18 @@ static int mtk_pcie_allocate_msi_domains + .ops = &msi_domain_ops, + .host_data = port, + .size = MTK_MSI_IRQS_NUM, ++ .hwirq_max = MTK_MSI_IRQS_NUM, ++ .bus_token = DOMAIN_BUS_PCI_MSI, ++ .domain_flags = IRQ_DOMAIN_FLAG_MSI_PARENT, + }; + +- port->inner_domain = msi_create_parent_irq_domain(&info, &mtk_msi_parent_ops); +- if (!port->inner_domain) { ++ port->inner_domain = irq_domain_instantiate(&info); ++ if (IS_ERR(port->inner_domain)) { + dev_err(port->pcie->dev, "failed to create IRQ domain\n"); ++ port->inner_domain = NULL; + return -ENOMEM; + } ++ port->inner_domain->msi_parent_ops = &mtk_msi_parent_ops; + + return 0; + } +@@ -530,23 +534,27 @@ static void mtk_pcie_enable_msi(struct m + writel(val, port->base + PCIE_INT_MASK); + } + +-static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie) ++static void mtk_pcie_irq_teardown_port(struct mtk_pcie_port *port) + { +- struct mtk_pcie_port *port, *tmp; ++ irq_set_chained_handler_and_data(port->irq, NULL, NULL); + +- list_for_each_entry_safe(port, tmp, &pcie->ports, list) { +- irq_set_chained_handler_and_data(port->irq, NULL, NULL); ++ if (port->irq_domain) ++ irq_domain_remove(port->irq_domain); + +- if (port->irq_domain) +- irq_domain_remove(port->irq_domain); ++ if (IS_ENABLED(CONFIG_PCI_MSI)) { ++ if (port->inner_domain) ++ irq_domain_remove(port->inner_domain); ++ } + +- if (IS_ENABLED(CONFIG_PCI_MSI)) { +- if (port->inner_domain) +- irq_domain_remove(port->inner_domain); +- } ++ irq_dispose_mapping(port->irq); ++} + +- irq_dispose_mapping(port->irq); +- } ++static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie) ++{ ++ struct mtk_pcie_port *port, *tmp; ++ ++ list_for_each_entry_safe(port, tmp, &pcie->ports, list) ++ mtk_pcie_irq_teardown_port(port); + } + + static int mtk_pcie_intx_map(struct irq_domain *domain, unsigned int irq, +@@ -829,7 +837,7 @@ static int mtk_pcie_startup_port(struct + return 0; + } + +-static void mtk_pcie_enable_port(struct mtk_pcie_port *port) ++static int mtk_pcie_enable_port(struct mtk_pcie_port *port) + { + struct mtk_pcie *pcie = port->pcie; + struct device *dev = pcie->dev; +@@ -838,7 +846,7 @@ static void mtk_pcie_enable_port(struct + err = clk_prepare_enable(port->sys_ck); + if (err) { + dev_err(dev, "failed to enable sys_ck%d clock\n", port->slot); +- goto err_sys_clk; ++ return err; + } + + err = clk_prepare_enable(port->ahb_ck); +@@ -886,11 +894,15 @@ static void mtk_pcie_enable_port(struct + goto err_phy_on; + } + +- if (!pcie->soc->startup(port)) +- return; ++ err = pcie->soc->startup(port); ++ if (err) { ++ dev_info(dev, "Port%d link down\n", port->slot); ++ goto err_soc_startup; ++ } + +- dev_info(dev, "Port%d link down\n", port->slot); ++ return 0; + ++err_soc_startup: + phy_power_off(port->phy); + err_phy_on: + phy_exit(port->phy); +@@ -906,8 +918,8 @@ err_aux_clk: + clk_disable_unprepare(port->ahb_ck); + err_ahb_clk: + clk_disable_unprepare(port->sys_ck); +-err_sys_clk: +- mtk_pcie_port_free(port); ++ ++ return err; + } + + static int mtk_pcie_parse_port(struct mtk_pcie *pcie, +@@ -1083,8 +1095,13 @@ static int mtk_pcie_setup(struct mtk_pci + return err; + + /* enable each port, and then check link status */ +- list_for_each_entry_safe(port, tmp, &pcie->ports, list) +- mtk_pcie_enable_port(port); ++ list_for_each_entry_safe(port, tmp, &pcie->ports, list) { ++ err = mtk_pcie_enable_port(port); ++ if (err) { ++ mtk_pcie_irq_teardown_port(port); ++ mtk_pcie_port_free(port); ++ } ++ } + + /* power down PCIe subsys if slots are all empty (link down) */ + if (list_empty(&pcie->ports)) +@@ -1186,14 +1203,18 @@ static int mtk_pcie_resume_noirq(struct + { + struct mtk_pcie *pcie = dev_get_drvdata(dev); + struct mtk_pcie_port *port, *tmp; ++ int err; + + if (list_empty(&pcie->ports)) + return 0; + + clk_prepare_enable(pcie->free_ck); + +- list_for_each_entry_safe(port, tmp, &pcie->ports, list) +- mtk_pcie_enable_port(port); ++ list_for_each_entry_safe(port, tmp, &pcie->ports, list) { ++ err = mtk_pcie_enable_port(port); ++ if (err) ++ mtk_pcie_port_free(port); ++ } + + /* In case of EP was removed while system suspend. */ + if (list_empty(&pcie->ports)) diff --git a/queue-6.12/pci-mediatek-switch-to-msi_create_parent_irq_domain.patch b/queue-6.12/pci-mediatek-switch-to-msi_create_parent_irq_domain.patch new file mode 100644 index 0000000000..4864450a48 --- /dev/null +++ b/queue-6.12/pci-mediatek-switch-to-msi_create_parent_irq_domain.patch @@ -0,0 +1,138 @@ +From stable+bounces-274540-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:29 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:23 -0400 +Subject: PCI: mediatek: Switch to msi_create_parent_irq_domain() +To: stable@vger.kernel.org +Cc: Nam Cao , Manivannan Sadhasivam , Bjorn Helgaas , Thomas Gleixner , Sasha Levin +Message-ID: <20260714200226.3152882-2-sashal@kernel.org> + +From: Nam Cao + +[ Upstream commit e449cb9afc963cf9cf47139bb873c412605c83e7 ] + +Switch to msi_create_parent_irq_domain() from pci_msi_create_irq_domain() +which was using legacy MSI domain setup. + +Signed-off-by: Nam Cao +[mani: reworded commit message] +Signed-off-by: Manivannan Sadhasivam +[bhelgaas: rebase on dev_fwnode() conversion, drop fwnode local var] +Signed-off-by: Bjorn Helgaas +Reviewed-by: Thomas Gleixner +Link: https://patch.msgid.link/76f6e6ce6021607cd0fdfd79fef7d2eb69d9f361.1750858083.git.namcao@linutronix.de +Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/Kconfig | 1 + drivers/pci/controller/pcie-mediatek.c | 50 +++++++++++++++------------------ + 2 files changed, 24 insertions(+), 27 deletions(-) + +--- a/drivers/pci/controller/Kconfig ++++ b/drivers/pci/controller/Kconfig +@@ -190,6 +190,7 @@ config PCIE_MEDIATEK + depends on ARCH_AIROHA || ARCH_MEDIATEK || COMPILE_TEST + depends on OF + depends on PCI_MSI ++ select IRQ_MSI_LIB + help + Say Y here if you want to enable PCIe controller support on + MediaTek SoCs. +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -182,7 +183,6 @@ struct mtk_pcie_soc { + * @irq: GIC irq + * @irq_domain: legacy INTx IRQ domain + * @inner_domain: inner IRQ domain +- * @msi_domain: MSI IRQ domain + * @lock: protect the msi_irq_in_use bitmap + * @msi_irq_in_use: bit map for assigned MSI IRQ + */ +@@ -203,7 +203,6 @@ struct mtk_pcie_port { + int irq; + struct irq_domain *irq_domain; + struct irq_domain *inner_domain; +- struct irq_domain *msi_domain; + struct mutex lock; + DECLARE_BITMAP(msi_irq_in_use, MTK_MSI_IRQS_NUM); + }; +@@ -473,40 +472,39 @@ static const struct irq_domain_ops msi_d + .free = mtk_pcie_irq_domain_free, + }; + +-static struct irq_chip mtk_msi_irq_chip = { +- .name = "MTK PCIe MSI", +- .irq_ack = irq_chip_ack_parent, +- .irq_mask = pci_msi_mask_irq, +- .irq_unmask = pci_msi_unmask_irq, +-}; +- +-static struct msi_domain_info mtk_msi_domain_info = { +- .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | +- MSI_FLAG_NO_AFFINITY | MSI_FLAG_PCI_MSIX, +- .chip = &mtk_msi_irq_chip, ++#define MTK_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ ++ MSI_FLAG_USE_DEF_CHIP_OPS | \ ++ MSI_FLAG_NO_AFFINITY) ++ ++#define MTK_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ ++ MSI_FLAG_PCI_MSIX) ++ ++static const struct msi_parent_ops mtk_msi_parent_ops = { ++ .required_flags = MTK_MSI_FLAGS_REQUIRED, ++ .supported_flags = MTK_MSI_FLAGS_SUPPORTED, ++ .bus_select_token = DOMAIN_BUS_PCI_MSI, ++ .chip_flags = MSI_CHIP_FLAG_SET_ACK, ++ .prefix = "MTK-", ++ .init_dev_msi_info = msi_lib_init_dev_msi_info, + }; + + static int mtk_pcie_allocate_msi_domains(struct mtk_pcie_port *port) + { +- struct fwnode_handle *fwnode = dev_fwnode(port->pcie->dev); +- + mutex_init(&port->lock); + +- port->inner_domain = irq_domain_create_linear(fwnode, MTK_MSI_IRQS_NUM, +- &msi_domain_ops, port); ++ struct irq_domain_info info = { ++ .fwnode = dev_fwnode(port->pcie->dev), ++ .ops = &msi_domain_ops, ++ .host_data = port, ++ .size = MTK_MSI_IRQS_NUM, ++ }; ++ ++ port->inner_domain = msi_create_parent_irq_domain(&info, &mtk_msi_parent_ops); + if (!port->inner_domain) { + dev_err(port->pcie->dev, "failed to create IRQ domain\n"); + return -ENOMEM; + } + +- port->msi_domain = pci_msi_create_irq_domain(fwnode, &mtk_msi_domain_info, +- port->inner_domain); +- if (!port->msi_domain) { +- dev_err(port->pcie->dev, "failed to create MSI domain\n"); +- irq_domain_remove(port->inner_domain); +- return -ENOMEM; +- } +- + return 0; + } + +@@ -535,8 +533,6 @@ static void mtk_pcie_irq_teardown(struct + irq_domain_remove(port->irq_domain); + + if (IS_ENABLED(CONFIG_PCI_MSI)) { +- if (port->msi_domain) +- irq_domain_remove(port->msi_domain); + if (port->inner_domain) + irq_domain_remove(port->inner_domain); + } diff --git a/queue-6.12/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch b/queue-6.12/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch new file mode 100644 index 0000000000..6ac7a3250c --- /dev/null +++ b/queue-6.12/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch @@ -0,0 +1,42 @@ +From stable+bounces-274542-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:38 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:25 -0400 +Subject: PCI: mediatek: Use generic MACRO for TPVPERL delay +To: stable@vger.kernel.org +Cc: Christian Marangi , Manivannan Sadhasivam , AngeloGioacchino Del Regno , Sasha Levin +Message-ID: <20260714200226.3152882-4-sashal@kernel.org> + +From: Christian Marangi + +[ Upstream commit 2d58bc777728bfc37aa35dce7b90e72296cceb9f ] + +Use the generic PCI MACRO for TPVPERL delay to wait for clock and power +stabilization after PERST# Signal instead of the raw value of 100 ms. + +Signed-off-by: Christian Marangi +Signed-off-by: Manivannan Sadhasivam +Reviewed-by: AngeloGioacchino Del Regno +Link: https://patch.msgid.link/20251020111121.31779-5-ansuelsmth@gmail.com +Stable-dep-of: f865a57896bd ("PCI: mediatek: Fix IRQ domain leak when port fails to enable") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/pcie-mediatek.c | 7 +------ + 1 file changed, 1 insertion(+), 6 deletions(-) + +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -702,12 +702,7 @@ static int mtk_pcie_startup_port_v2(stru + */ + writel(PCIE_LINKDOWN_RST_EN, port->base + PCIE_RST_CTRL); + +- /* +- * Described in PCIe CEM specification sections 2.2 (PERST# Signal) and +- * 2.2.1 (Initial Power-Up (G3 to S0)). The deassertion of PERST# should +- * be delayed 100ms (TPVPERL) for the power and clock to become stable. +- */ +- msleep(100); ++ msleep(PCIE_T_PVPERL_MS); + + /* De-assert PHY, PE, PIPE, MAC and configuration reset */ + val = readl(port->base + PCIE_RST_CTRL); diff --git a/queue-6.12/pci-move-resizable-bar-code-to-rebar.c.patch b/queue-6.12/pci-move-resizable-bar-code-to-rebar.c.patch new file mode 100644 index 0000000000..8b5d781cfc --- /dev/null +++ b/queue-6.12/pci-move-resizable-bar-code-to-rebar.c.patch @@ -0,0 +1,467 @@ +From stable+bounces-274583-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:15 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:20 -0400 +Subject: PCI: Move Resizable BAR code to rebar.c +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Christian König" , "Sasha Levin" +Message-ID: <20260714220421.3334071-5-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit 9f71938cd77f32a448f40a288e409eca60e55486 ] + +For lack of a better place to put it, Resizable BAR code has been placed +inside pci.c and setup-res.c that do not use it for anything. Upcoming +changes are going to add more Resizable BAR related functions, increasing +the code size. + +As pci.c is huge as is, move the Resizable BAR related code and the BAR +resize code from setup-res.c to rebar.c. + +Signed-off-by: Ilpo Järvinen +Signed-off-by: Bjorn Helgaas +Reviewed-by: Christian König +Link: https://patch.msgid.link/20251113180053.27944-2-ilpo.jarvinen@linux.intel.com +Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + Documentation/driver-api/pci/pci.rst | 3 + drivers/pci/Makefile | 2 + drivers/pci/pci.c | 134 ------------------------ + drivers/pci/pci.h | 1 + drivers/pci/rebar.c | 191 +++++++++++++++++++++++++++++++++++ + drivers/pci/setup-res.c | 44 -------- + 6 files changed, 196 insertions(+), 179 deletions(-) + create mode 100644 drivers/pci/rebar.c + +--- a/Documentation/driver-api/pci/pci.rst ++++ b/Documentation/driver-api/pci/pci.rst +@@ -37,6 +37,9 @@ PCI Support Library + .. kernel-doc:: drivers/pci/slot.c + :export: + ++.. kernel-doc:: drivers/pci/rebar.c ++ :export: ++ + .. kernel-doc:: drivers/pci/rom.c + :export: + +--- a/drivers/pci/Makefile ++++ b/drivers/pci/Makefile +@@ -4,7 +4,7 @@ + + obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \ + remove.o pci.o pci-driver.o search.o \ +- rom.o setup-res.o irq.o vpd.o \ ++ rebar.o rom.o setup-res.o irq.o vpd.o \ + setup-bus.o vc.o mmap.o devres.o + + obj-$(CONFIG_PCI) += msi/ +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -1907,32 +1907,6 @@ static void pci_restore_config_space(str + } + } + +-static void pci_restore_rebar_state(struct pci_dev *pdev) +-{ +- unsigned int pos, nbars, i; +- u32 ctrl; +- +- pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); +- if (!pos) +- return; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); +- +- for (i = 0; i < nbars; i++, pos += 8) { +- struct resource *res; +- int bar_idx, size; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; +- res = pdev->resource + bar_idx; +- size = pci_rebar_bytes_to_size(resource_size(res)); +- ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; +- ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); +- pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); +- } +-} +- + /** + * pci_restore_state - Restore the saved state of a PCI device + * @dev: PCI device that we're dealing with +@@ -3725,114 +3699,6 @@ void pci_acs_init(struct pci_dev *dev) + } + + /** +- * pci_rebar_find_pos - find position of resize ctrl reg for BAR +- * @pdev: PCI device +- * @bar: BAR to find +- * +- * Helper to find the position of the ctrl register for a BAR. +- * Returns -ENOTSUPP if resizable BARs are not supported at all. +- * Returns -ENOENT if no ctrl register for the BAR could be found. +- */ +-static int pci_rebar_find_pos(struct pci_dev *pdev, int bar) +-{ +- unsigned int pos, nbars, i; +- u32 ctrl; +- +- pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); +- if (!pos) +- return -ENOTSUPP; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); +- +- for (i = 0; i < nbars; i++, pos += 8) { +- int bar_idx; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- bar_idx = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, ctrl); +- if (bar_idx == bar) +- return pos; +- } +- +- return -ENOENT; +-} +- +-/** +- * pci_rebar_get_possible_sizes - get possible sizes for BAR +- * @pdev: PCI device +- * @bar: BAR to query +- * +- * Get the possible sizes of a resizable BAR as bitmask defined in the spec +- * (bit 0=1MB, bit 19=512GB). Returns 0 if BAR isn't resizable. +- */ +-u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar) +-{ +- int pos; +- u32 cap; +- +- pos = pci_rebar_find_pos(pdev, bar); +- if (pos < 0) +- return 0; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); +- cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap); +- +- /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */ +- if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f && +- bar == 0 && cap == 0x700) +- return 0x3f00; +- +- return cap; +-} +-EXPORT_SYMBOL(pci_rebar_get_possible_sizes); +- +-/** +- * pci_rebar_get_current_size - get the current size of a BAR +- * @pdev: PCI device +- * @bar: BAR to set size to +- * +- * Read the size of a BAR from the resizable BAR config. +- * Returns size if found or negative error code. +- */ +-int pci_rebar_get_current_size(struct pci_dev *pdev, int bar) +-{ +- int pos; +- u32 ctrl; +- +- pos = pci_rebar_find_pos(pdev, bar); +- if (pos < 0) +- return pos; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- return FIELD_GET(PCI_REBAR_CTRL_BAR_SIZE, ctrl); +-} +- +-/** +- * pci_rebar_set_size - set a new size for a BAR +- * @pdev: PCI device +- * @bar: BAR to set size to +- * @size: new size as defined in the spec (0=1MB, 19=512GB) +- * +- * Set the new size of a BAR as defined in the spec. +- * Returns zero if resizing was successful, error code otherwise. +- */ +-int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size) +-{ +- int pos; +- u32 ctrl; +- +- pos = pci_rebar_find_pos(pdev, bar); +- if (pos < 0) +- return pos; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; +- ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); +- pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); +- return 0; +-} +- +-/** + * pci_enable_atomic_ops_to_root - enable AtomicOp requests to root port + * @dev: the PCI device + * @cap_mask: mask of desired AtomicOp sizes, including one or more of: +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -751,6 +751,7 @@ static inline int acpi_get_rc_resources( + } + #endif + ++void pci_restore_rebar_state(struct pci_dev *pdev); + int pci_rebar_get_current_size(struct pci_dev *pdev, int bar); + int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size); + static inline u64 pci_rebar_size_to_bytes(int size) +--- /dev/null ++++ b/drivers/pci/rebar.c +@@ -0,0 +1,191 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * PCI Resizable BAR Extended Capability handling. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pci.h" ++ ++/** ++ * pci_rebar_find_pos - find position of resize ctrl reg for BAR ++ * @pdev: PCI device ++ * @bar: BAR to find ++ * ++ * Helper to find the position of the ctrl register for a BAR. ++ * Returns -ENOTSUPP if resizable BARs are not supported at all. ++ * Returns -ENOENT if no ctrl register for the BAR could be found. ++ */ ++static int pci_rebar_find_pos(struct pci_dev *pdev, int bar) ++{ ++ unsigned int pos, nbars, i; ++ u32 ctrl; ++ ++ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); ++ if (!pos) ++ return -ENOTSUPP; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); ++ ++ for (i = 0; i < nbars; i++, pos += 8) { ++ int bar_idx; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ bar_idx = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, ctrl); ++ if (bar_idx == bar) ++ return pos; ++ } ++ ++ return -ENOENT; ++} ++ ++/** ++ * pci_rebar_get_possible_sizes - get possible sizes for BAR ++ * @pdev: PCI device ++ * @bar: BAR to query ++ * ++ * Get the possible sizes of a resizable BAR as bitmask defined in the spec ++ * (bit 0=1MB, bit 19=512GB). Returns 0 if BAR isn't resizable. ++ */ ++u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar) ++{ ++ int pos; ++ u32 cap; ++ ++ pos = pci_rebar_find_pos(pdev, bar); ++ if (pos < 0) ++ return 0; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); ++ cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap); ++ ++ /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */ ++ if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f && ++ bar == 0 && cap == 0x700) ++ return 0x3f00; ++ ++ return cap; ++} ++EXPORT_SYMBOL(pci_rebar_get_possible_sizes); ++ ++/** ++ * pci_rebar_get_current_size - get the current size of a BAR ++ * @pdev: PCI device ++ * @bar: BAR to set size to ++ * ++ * Read the size of a BAR from the resizable BAR config. ++ * Returns size if found or negative error code. ++ */ ++int pci_rebar_get_current_size(struct pci_dev *pdev, int bar) ++{ ++ int pos; ++ u32 ctrl; ++ ++ pos = pci_rebar_find_pos(pdev, bar); ++ if (pos < 0) ++ return pos; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ return FIELD_GET(PCI_REBAR_CTRL_BAR_SIZE, ctrl); ++} ++ ++/** ++ * pci_rebar_set_size - set a new size for a BAR ++ * @pdev: PCI device ++ * @bar: BAR to set size to ++ * @size: new size as defined in the spec (0=1MB, 19=512GB) ++ * ++ * Set the new size of a BAR as defined in the spec. ++ * Returns zero if resizing was successful, error code otherwise. ++ */ ++int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size) ++{ ++ int pos; ++ u32 ctrl; ++ ++ pos = pci_rebar_find_pos(pdev, bar); ++ if (pos < 0) ++ return pos; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; ++ ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); ++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); ++ return 0; ++} ++ ++void pci_restore_rebar_state(struct pci_dev *pdev) ++{ ++ unsigned int pos, nbars, i; ++ u32 ctrl; ++ ++ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); ++ if (!pos) ++ return; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); ++ ++ for (i = 0; i < nbars; i++, pos += 8) { ++ struct resource *res; ++ int bar_idx, size; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; ++ res = pci_resource_n(pdev, bar_idx); ++ size = pci_rebar_bytes_to_size(resource_size(res)); ++ ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; ++ ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); ++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); ++ } ++} ++ ++int pci_resize_resource(struct pci_dev *dev, int resno, int size, ++ int exclude_bars) ++{ ++ struct pci_host_bridge *host; ++ int old, ret; ++ u32 sizes; ++ u16 cmd; ++ ++ /* Check if we must preserve the firmware's resource assignment */ ++ host = pci_find_host_bridge(dev->bus); ++ if (host->preserve_config) ++ return -ENOTSUPP; ++ ++ pci_read_config_word(dev, PCI_COMMAND, &cmd); ++ if (cmd & PCI_COMMAND_MEMORY) ++ return -EBUSY; ++ ++ sizes = pci_rebar_get_possible_sizes(dev, resno); ++ if (!sizes) ++ return -ENOTSUPP; ++ ++ if (!(sizes & BIT(size))) ++ return -EINVAL; ++ ++ old = pci_rebar_get_current_size(dev, resno); ++ if (old < 0) ++ return old; ++ ++ ret = pci_rebar_set_size(dev, resno, size); ++ if (ret) ++ return ret; ++ ++ ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars); ++ if (ret) ++ goto error_resize; ++ ++ return 0; ++ ++error_resize: ++ pci_rebar_set_size(dev, resno, old); ++ return ret; ++} ++EXPORT_SYMBOL(pci_resize_resource); +--- a/drivers/pci/setup-res.c ++++ b/drivers/pci/setup-res.c +@@ -429,50 +429,6 @@ void pci_release_resource(struct pci_dev + } + EXPORT_SYMBOL(pci_release_resource); + +-int pci_resize_resource(struct pci_dev *dev, int resno, int size, +- int exclude_bars) +-{ +- struct pci_host_bridge *host; +- int old, ret; +- u32 sizes; +- u16 cmd; +- +- /* Check if we must preserve the firmware's resource assignment */ +- host = pci_find_host_bridge(dev->bus); +- if (host->preserve_config) +- return -ENOTSUPP; +- +- pci_read_config_word(dev, PCI_COMMAND, &cmd); +- if (cmd & PCI_COMMAND_MEMORY) +- return -EBUSY; +- +- sizes = pci_rebar_get_possible_sizes(dev, resno); +- if (!sizes) +- return -ENOTSUPP; +- +- if (!(sizes & BIT(size))) +- return -EINVAL; +- +- old = pci_rebar_get_current_size(dev, resno); +- if (old < 0) +- return old; +- +- ret = pci_rebar_set_size(dev, resno, size); +- if (ret) +- return ret; +- +- ret = pci_do_resource_release_and_resize(dev, resno, size, exclude_bars); +- if (ret) +- goto error_resize; +- +- return 0; +- +-error_resize: +- pci_rebar_set_size(dev, resno, old); +- return ret; +-} +-EXPORT_SYMBOL(pci_resize_resource); +- + int pci_enable_resources(struct pci_dev *dev, int mask) + { + u16 cmd, old_cmd; diff --git a/queue-6.12/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch b/queue-6.12/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch new file mode 100644 index 0000000000..ab4ebb9858 --- /dev/null +++ b/queue-6.12/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch @@ -0,0 +1,71 @@ +From stable+bounces-274579-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:06 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:17 -0400 +Subject: PCI: Prevent resource tree corruption when BAR resize fails +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Simon Richter" , "Alex Bennée" , "Bjorn Helgaas" , "Sasha Levin" +Message-ID: <20260714220421.3334071-2-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit 91c4c89db41499eea1b29c56655f79c3bae66e93 ] + +pbus_reassign_bridge_resources() saves bridge windows into the saved +list before attempting to adjust resource assignments to perform a BAR +resize operation. If resource adjustments cannot be completed fully, +rollback is attempted by restoring the resource from the saved list. + +The rollback, however, does not check whether the resources it restores were +assigned by the partial resize attempt. If restore changes addresses of the +resource, it can result in corrupting the resource tree. + +An example of a corrupted resource tree with overlapping addresses: + + 6200000000000-6203fbfffffff : pciex@620c3c0000000 + 6200000000000-6203fbff0ffff : PCI Bus 0030:01 + 6200020000000-62000207fffff : 0030:01:00.0 + 6200000000000-6203fbff0ffff : PCI Bus 0030:02 + +A resource that are assigned into the resource tree must remain +unchanged. Thus, release such a resource before attempting to restore +and claim it back. + +For simplicity, always do the release and claim back for the resource +even in the cases where it is restored to the same address range. + +Note: this fix may "break" some cases where devices "worked" because +the resource tree corruption allowed address space double counting to +fit more resource than what can now be assigned without double +counting. The upcoming changes to BAR resizing should address those +scenarios (to the extent possible). + +Fixes: 8bb705e3e79d ("PCI: Add pci_resize_resource() for resizing BARs") +Reported-by: Simon Richter +Link: https://lore.kernel.org/linux-pci/67840a16-99b4-4d8c-9b5c-4721ab0970a2@hogyros.de/ +Reported-by: Alex Bennée +Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/ +Signed-off-by: Ilpo Järvinen +Signed-off-by: Bjorn Helgaas +Tested-by: Alex Bennée # AVA, AMD GPU +Link: https://patch.msgid.link/20251113162628.5946-2-ilpo.jarvinen@linux.intel.com +Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/setup-bus.c | 5 +++++ + 1 file changed, 5 insertions(+) + +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2388,6 +2388,11 @@ cleanup: + bridge = dev_res->dev; + i = res - bridge->resource; + ++ if (res->parent) { ++ release_child_resources(res); ++ pci_release_resource(bridge, i); ++ } ++ + res->start = dev_res->start; + res->end = dev_res->end; + res->flags = dev_res->flags; diff --git a/queue-6.12/pci-skip-resizable-bar-restore-on-read-error.patch b/queue-6.12/pci-skip-resizable-bar-restore-on-read-error.patch new file mode 100644 index 0000000000..18fbdfe1dc --- /dev/null +++ b/queue-6.12/pci-skip-resizable-bar-restore-on-read-error.patch @@ -0,0 +1,61 @@ +From stable+bounces-274582-greg=kroah.com@vger.kernel.org Wed Jul 15 00:04:49 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:21 -0400 +Subject: PCI: Skip Resizable BAR restore on read error +To: stable@vger.kernel.org +Cc: Marco Nenciarini , Bjorn Helgaas , Sasha Levin +Message-ID: <20260714220421.3334071-6-sashal@kernel.org> + +From: Marco Nenciarini + +[ Upstream commit ee7471fe968d210939be9046089a924cd23c8c3b ] + +pci_restore_rebar_state() uses the Resizable BAR Control register to decide +how many BARs to restore (nbars) and which BAR each iteration addresses +(bar_idx). + +When a device does not respond, config reads typically return +PCI_ERROR_RESPONSE (~0). Both fields are 3 bits wide, so nbars and bar_idx +both evaluate to 7, past the spec's valid ranges for both fields. +pci_resource_n() then returns an unrelated resource slot, whose size is +used to derive a nonsensical value written back to the Resizable BAR +Control register. + +Bail out if any Resizable BAR Control read returns PCI_ERROR_RESPONSE. No +further BARs are touched, which is safe because a config read that returns +PCI_ERROR_RESPONSE indicates the device is unreachable and restoration is +pointless. + +Fixes: d3252ace0bc6 ("PCI: Restore resized BAR state on resume") +Signed-off-by: Marco Nenciarini +Signed-off-by: Bjorn Helgaas +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/666cac19b5daa0ab0e0ab64454e76b4d24465dbd.1776429882.git.mnencia@kcore.it +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/rebar.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/drivers/pci/rebar.c ++++ b/drivers/pci/rebar.c +@@ -130,6 +130,9 @@ void pci_restore_rebar_state(struct pci_ + return; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ if (PCI_POSSIBLE_ERROR(ctrl)) ++ return; ++ + nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); + + for (i = 0; i < nbars; i++, pos += 8) { +@@ -137,6 +140,9 @@ void pci_restore_rebar_state(struct pci_ + int bar_idx, size; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ if (PCI_POSSIBLE_ERROR(ctrl)) ++ return; ++ + bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; + res = pci_resource_n(pdev, bar_idx); + size = pci_rebar_bytes_to_size(resource_size(res)); diff --git a/queue-6.12/pci-use-pbus_select_window-during-bar-resize.patch b/queue-6.12/pci-use-pbus_select_window-during-bar-resize.patch new file mode 100644 index 0000000000..86e53dffcd --- /dev/null +++ b/queue-6.12/pci-use-pbus_select_window-during-bar-resize.patch @@ -0,0 +1,81 @@ +From stable+bounces-274578-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:07 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:16 -0400 +Subject: PCI: Use pbus_select_window() during BAR resize +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Sasha Levin" +Message-ID: <20260714220421.3334071-1-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit 7dc58aa7f1b32a215fb0b7c6ca30ddf4663dedf4 ] + +Prior to a BAR resize, __resource_resize_store() loops through the normal +resources of the PCI device and releases those that match to the flags of +the BAR to be resized. This is necessary to allow resizing also the +upstream bridge window as only childless bridge windows can be resized. + +While the flags check (mostly) works (if corner cases are ignored), the +more straightforward way is to check if the resources share the bridge +window. Change __resource_resize_store() to do the check using +pbus_select_window(). + +Signed-off-by: Ilpo Järvinen +Signed-off-by: Bjorn Helgaas +Link: https://patch.msgid.link/20250829131113.36754-16-ilpo.jarvinen@linux.intel.com +Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/pci-sysfs.c | 20 +++++++++++++------- + 1 file changed, 13 insertions(+), 7 deletions(-) + +--- a/drivers/pci/pci-sysfs.c ++++ b/drivers/pci/pci-sysfs.c +@@ -1426,13 +1426,19 @@ static ssize_t __resource_resize_store(s + const char *buf, size_t count) + { + struct pci_dev *pdev = to_pci_dev(dev); +- unsigned long size, flags; ++ struct pci_bus *bus = pdev->bus; ++ struct resource *b_win, *res; ++ unsigned long size; + int ret, i; + u16 cmd; + + if (kstrtoul(buf, 0, &size) < 0) + return -EINVAL; + ++ b_win = pbus_select_window(bus, pci_resource_n(pdev, n)); ++ if (!b_win) ++ return -EINVAL; ++ + device_lock(dev); + if (dev->driver || pci_num_vf(pdev)) { + ret = -EBUSY; +@@ -1452,19 +1458,19 @@ static ssize_t __resource_resize_store(s + pci_write_config_word(pdev, PCI_COMMAND, + cmd & ~PCI_COMMAND_MEMORY); + +- flags = pci_resource_flags(pdev, n); +- + pci_remove_resource_files(pdev); + +- for (i = 0; i < PCI_BRIDGE_RESOURCES; i++) { +- if (pci_resource_len(pdev, i) && +- pci_resource_flags(pdev, i) == flags) ++ pci_dev_for_each_resource(pdev, res, i) { ++ if (i >= PCI_BRIDGE_RESOURCES) ++ break; ++ ++ if (b_win == pbus_select_window(bus, res)) + pci_release_resource(pdev, i); + } + + ret = pci_resize_resource(pdev, n, size); + +- pci_assign_unassigned_bus_resources(pdev->bus); ++ pci_assign_unassigned_bus_resources(bus); + + if (pci_create_resource_files(pdev)) + pci_warn(pdev, "Failed to recreate resource files after BAR resizing\n"); diff --git a/queue-6.12/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch b/queue-6.12/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch new file mode 100644 index 0000000000..de65418819 --- /dev/null +++ b/queue-6.12/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch @@ -0,0 +1,57 @@ +From stable+bounces-276815-greg=kroah.com@vger.kernel.org Thu Jul 16 20:49:59 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 14:45:34 -0400 +Subject: perf/x86/intel/uncore: Defer ADL global PMON enable to enable_box() +To: stable@vger.kernel.org +Cc: Zide Chen , "Peter Zijlstra (Intel)" , Dapeng Mi , Sasha Levin +Message-ID: <20260716184534.1012849-1-sashal@kernel.org> + +From: Zide Chen + +[ Upstream commit 9a0bb848a37150aeccc10088e141339917d995dc ] + +On some Raptor Cove CPUs, enabling uncore PMON globally at driver init +may increase power consumption even when no perf events are in use. + +Drop adl_uncore_msr_init_box() and defer programming the global control +register to enable_box(), so it is only set when a box is actually used. + +IMC and IMC freerunning counters use a separate control path and are +unaffected. + +Fixes: 772ed05f3c5c ("perf/x86/intel/uncore: Add Alder Lake support") +Signed-off-by: Zide Chen +Signed-off-by: Peter Zijlstra (Intel) +Reviewed-by: Dapeng Mi +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260602144908.263680-5-zide.chen@intel.com +[ deleted adl_uncore_msr_init_box() in its 6.12 wrmsrl() spelling since the tree predates the wrmsrq() rename ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + arch/x86/events/intel/uncore_snb.c | 7 ------- + 1 file changed, 7 deletions(-) + +--- a/arch/x86/events/intel/uncore_snb.c ++++ b/arch/x86/events/intel/uncore_snb.c +@@ -538,12 +538,6 @@ void tgl_uncore_cpu_init(void) + skl_uncore_msr_ops.init_box = rkl_uncore_msr_init_box; + } + +-static void adl_uncore_msr_init_box(struct intel_uncore_box *box) +-{ +- if (box->pmu->pmu_idx == 0) +- wrmsrl(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN); +-} +- + static void adl_uncore_msr_enable_box(struct intel_uncore_box *box) + { + wrmsrl(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN); +@@ -562,7 +556,6 @@ static void adl_uncore_msr_exit_box(stru + } + + static struct intel_uncore_ops adl_uncore_msr_ops = { +- .init_box = adl_uncore_msr_init_box, + .enable_box = adl_uncore_msr_enable_box, + .disable_box = adl_uncore_msr_disable_box, + .exit_box = adl_uncore_msr_exit_box, diff --git a/queue-6.12/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch b/queue-6.12/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch new file mode 100644 index 0000000000..7296ae425f --- /dev/null +++ b/queue-6.12/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch @@ -0,0 +1,274 @@ +From stable+bounces-276779-greg=kroah.com@vger.kernel.org Thu Jul 16 18:51:49 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 12:49:26 -0400 +Subject: proc: protect ptrace_may_access() with exec_update_lock (FD links) +To: stable@vger.kernel.org +Cc: Jann Horn , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260716164926.673440-2-sashal@kernel.org> + +From: Jann Horn + +[ Upstream commit 6255da28d4bb5349fe18e84cb043ccd394eba75d ] + +proc_pid_get_link() and proc_pid_readlink() currently look up the task from +the pid once, then do the ptrace access check on that task, then look up +the task from the pid a second time to do the actual access. +That's racy in several ways. + +To fix it, pass the task to the ->proc_get_link() handler, and instead of +proc_fd_access_allowed(), introduce a new helper call_proc_get_link() that +looks up and locks the task, does the access check, and calls +->proc_get_link(). + +Fixes: 778c1144771f ("[PATCH] proc: Use sane permission checks on the /proc//fd/ symlinks") +Cc: stable@vger.kernel.org +Signed-off-by: Jann Horn +Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-2-5c3d20e0ac33@google.com +Signed-off-by: Christian Brauner (Amutable) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/base.c | 119 ++++++++++++++++++++--------------------------------- + fs/proc/fd.c | 25 ++++------- + fs/proc/internal.h | 2 + 3 files changed, 58 insertions(+), 88 deletions(-) + +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -218,33 +218,24 @@ static int get_task_root(struct task_str + return result; + } + +-static int proc_cwd_link(struct dentry *dentry, struct path *path) ++static int proc_cwd_link(struct dentry *dentry, struct path *path, ++ struct task_struct *task) + { +- struct task_struct *task = get_proc_task(d_inode(dentry)); + int result = -ENOENT; + +- if (task) { +- task_lock(task); +- if (task->fs) { +- get_fs_pwd(task->fs, path); +- result = 0; +- } +- task_unlock(task); +- put_task_struct(task); ++ task_lock(task); ++ if (task->fs) { ++ get_fs_pwd(task->fs, path); ++ result = 0; + } ++ task_unlock(task); + return result; + } + +-static int proc_root_link(struct dentry *dentry, struct path *path) ++static int proc_root_link(struct dentry *dentry, struct path *path, ++ struct task_struct *task) + { +- struct task_struct *task = get_proc_task(d_inode(dentry)); +- int result = -ENOENT; +- +- if (task) { +- result = get_task_root(task, path); +- put_task_struct(task); +- } +- return result; ++ return get_task_root(task, path); + } + + /* +@@ -704,23 +695,6 @@ static int proc_pid_syscall(struct seq_f + /* Here the fs part begins */ + /************************************************************************/ + +-/* permission checks */ +-static bool proc_fd_access_allowed(struct inode *inode) +-{ +- struct task_struct *task; +- bool allowed = false; +- /* Allow access to a task's file descriptors if it is us or we +- * may use ptrace attach to the process and find out that +- * information. +- */ +- task = get_proc_task(inode); +- if (task) { +- allowed = ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS); +- put_task_struct(task); +- } +- return allowed; +-} +- + int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr) + { +@@ -1778,16 +1752,12 @@ static const struct file_operations proc + .release = single_release, + }; + +-static int proc_exe_link(struct dentry *dentry, struct path *exe_path) ++static int proc_exe_link(struct dentry *dentry, struct path *exe_path, ++ struct task_struct *task) + { +- struct task_struct *task; + struct file *exe_file; + +- task = get_proc_task(d_inode(dentry)); +- if (!task) +- return -ENOENT; + exe_file = get_task_exe_file(task); +- put_task_struct(task); + if (exe_file) { + *exe_path = exe_file->f_path; + path_get(&exe_file->f_path); +@@ -1797,26 +1767,42 @@ static int proc_exe_link(struct dentry * + return -ENOENT; + } + ++static int call_proc_get_link(struct dentry *dentry, struct inode *inode, struct path *path_out) ++{ ++ struct task_struct *task; ++ int ret; ++ ++ task = get_proc_task(inode); ++ if (!task) ++ return -ENOENT; ++ ret = down_read_killable(&task->signal->exec_update_lock); ++ if (ret) ++ goto out_put_task; ++ if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { ++ ret = -EACCES; ++ goto out; ++ } ++ ret = PROC_I(inode)->op.proc_get_link(dentry, path_out, task); ++ ++out: ++ up_read(&task->signal->exec_update_lock); ++out_put_task: ++ put_task_struct(task); ++ return ret; ++} ++ + static const char *proc_pid_get_link(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) + { + struct path path; +- int error = -EACCES; ++ int error; + + if (!dentry) + return ERR_PTR(-ECHILD); +- +- /* Are we allowed to snoop on the tasks file descriptors? */ +- if (!proc_fd_access_allowed(inode)) +- goto out; +- +- error = PROC_I(inode)->op.proc_get_link(dentry, &path); +- if (error) +- goto out; +- +- error = nd_jump_link(&path); +-out: ++ error = call_proc_get_link(dentry, inode, &path); ++ if (!error) ++ error = nd_jump_link(&path); + return ERR_PTR(error); + } + +@@ -1850,17 +1836,11 @@ static int proc_pid_readlink(struct dent + struct inode *inode = d_inode(dentry); + struct path path; + +- /* Are we allowed to snoop on the tasks file descriptors? */ +- if (!proc_fd_access_allowed(inode)) +- goto out; +- +- error = PROC_I(inode)->op.proc_get_link(dentry, &path); +- if (error) +- goto out; +- +- error = do_proc_readlink(&path, buffer, buflen); +- path_put(&path); +-out: ++ error = call_proc_get_link(dentry, inode, &path); ++ if (!error) { ++ error = do_proc_readlink(&path, buffer, buflen); ++ path_put(&path); ++ } + return error; + } + +@@ -2246,21 +2226,16 @@ static const struct dentry_operations ti + .d_delete = pid_delete_dentry, + }; + +-static int map_files_get_link(struct dentry *dentry, struct path *path) ++static int map_files_get_link(struct dentry *dentry, struct path *path, ++ struct task_struct *task) + { + unsigned long vm_start, vm_end; + struct vm_area_struct *vma; +- struct task_struct *task; + struct mm_struct *mm; + int rc; + + rc = -ENOENT; +- task = get_proc_task(d_inode(dentry)); +- if (!task) +- goto out; +- + mm = get_task_mm(task); +- put_task_struct(task); + if (!mm) + goto out; + +--- a/fs/proc/fd.c ++++ b/fs/proc/fd.c +@@ -172,24 +172,19 @@ static const struct dentry_operations ti + .d_delete = pid_delete_dentry, + }; + +-static int proc_fd_link(struct dentry *dentry, struct path *path) ++static int proc_fd_link(struct dentry *dentry, struct path *path, ++ struct task_struct *task) + { +- struct task_struct *task; + int ret = -ENOENT; ++ unsigned int fd = proc_fd(d_inode(dentry)); ++ struct file *fd_file; + +- task = get_proc_task(d_inode(dentry)); +- if (task) { +- unsigned int fd = proc_fd(d_inode(dentry)); +- struct file *fd_file; +- +- fd_file = fget_task(task, fd); +- if (fd_file) { +- *path = fd_file->f_path; +- path_get(&fd_file->f_path); +- ret = 0; +- fput(fd_file); +- } +- put_task_struct(task); ++ fd_file = fget_task(task, fd); ++ if (fd_file) { ++ *path = fd_file->f_path; ++ path_get(&fd_file->f_path); ++ ret = 0; ++ fput(fd_file); + } + + return ret; +--- a/fs/proc/internal.h ++++ b/fs/proc/internal.h +@@ -108,7 +108,7 @@ extern struct kmem_cache *proc_dir_entry + void pde_free(struct proc_dir_entry *pde); + + union proc_op { +- int (*proc_get_link)(struct dentry *, struct path *); ++ int (*proc_get_link)(struct dentry *, struct path *, struct task_struct *); + int (*proc_show)(struct seq_file *m, + struct pid_namespace *ns, struct pid *pid, + struct task_struct *task); diff --git a/queue-6.12/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch b/queue-6.12/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch new file mode 100644 index 0000000000..b870422bd4 --- /dev/null +++ b/queue-6.12/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch @@ -0,0 +1,207 @@ +From stable+bounces-277062-greg=kroah.com@vger.kernel.org Fri Jul 17 16:04:27 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:00:25 -0400 +Subject: proc: protect ptrace_may_access() with exec_update_lock (part 1) +To: stable@vger.kernel.org +Cc: Jann Horn , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260717140025.1717514-3-sashal@kernel.org> + +From: Jann Horn + +[ Upstream commit 6650527444dadc63d84aa939d14ecba4fadb2f69 ] + +Fix the easy cases where procfs currently calls ptrace_may_access() without +exec_update_lock protection, where the fix is to simply add the extra lock +or use mm_access(): + + - do_task_stat(): grab exec_update_lock + - proc_pid_wchan(): grab exec_update_lock + - proc_map_files_lookup(): use mm_access() instead of get_task_mm() + - proc_map_files_readdir(): use mm_access() instead of get_task_mm() + - proc_ns_get_link(): grab exec_update_lock + - proc_ns_readlink(): grab exec_update_lock + +Fixes: f83ce3e6b02d ("proc: avoid information leaks to non-privileged processes") +Cc: stable@vger.kernel.org +Signed-off-by: Jann Horn +Link: https://patch.msgid.link/20260518-procfs-lockfix-part1-v1-1-5c3d20e0ac33@google.com +Signed-off-by: Christian Brauner (Amutable) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/array.c | 6 ++++++ + fs/proc/base.c | 41 ++++++++++++++++++++++------------------- + fs/proc/namespaces.c | 12 ++++++++++++ + 3 files changed, 40 insertions(+), 19 deletions(-) + +--- a/fs/proc/array.c ++++ b/fs/proc/array.c +@@ -483,6 +483,11 @@ static int do_task_stat(struct seq_file + unsigned long flags; + int exit_code = task->exit_code; + struct signal_struct *sig = task->signal; ++ int ret; ++ ++ ret = down_read_killable(&task->signal->exec_update_lock); ++ if (ret) ++ return ret; + + state = *get_task_state(task); + vsize = eip = esp = 0; +@@ -658,6 +663,7 @@ static int do_task_stat(struct seq_file + seq_puts(m, " 0"); + + seq_putc(m, '\n'); ++ up_read(&task->signal->exec_update_lock); + if (mm) + mmput(mm); + return 0; +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -414,18 +414,24 @@ static int proc_pid_wchan(struct seq_fil + { + unsigned long wchan; + char symname[KSYM_NAME_LEN]; ++ int err; + ++ err = down_read_killable(&task->signal->exec_update_lock); ++ if (err) ++ return err; + if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) + goto print0; + + wchan = get_wchan(task); + if (wchan && !lookup_symbol_name(wchan, symname)) { + seq_puts(m, symname); ++ up_read(&task->signal->exec_update_lock); + return 0; + } + + print0: + seq_putc(m, '0'); ++ up_read(&task->signal->exec_update_lock); + return 0; + } + #endif /* CONFIG_KALLSYMS */ +@@ -2331,17 +2337,15 @@ static struct dentry *proc_map_files_loo + if (!task) + goto out; + +- result = ERR_PTR(-EACCES); +- if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) +- goto out_put_task; +- + result = ERR_PTR(-ENOENT); + if (dname_to_vma_addr(dentry, &vm_start, &vm_end)) + goto out_put_task; + +- mm = get_task_mm(task); +- if (!mm) ++ mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); ++ if (IS_ERR(mm)) { ++ result = ERR_CAST(mm); + goto out_put_task; ++ } + + result = ERR_PTR(-EINTR); + if (mmap_read_lock_killable(mm)) +@@ -2391,23 +2395,22 @@ proc_map_files_readdir(struct file *file + if (!task) + goto out; + +- ret = -EACCES; +- if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) +- goto out_put_task; +- + ret = 0; + if (!dir_emit_dots(file, ctx)) + goto out_put_task; + +- mm = get_task_mm(task); +- if (!mm) ++ mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); ++ if (IS_ERR(mm)) { ++ ret = PTR_ERR(mm); ++ /* if the task has no mm, the directory should just be empty */ ++ if (ret == -ESRCH) ++ ret = 0; + goto out_put_task; ++ } + + ret = mmap_read_lock_killable(mm); +- if (ret) { +- mmput(mm); +- goto out_put_task; +- } ++ if (ret) ++ goto out_put_mm; + + nr_files = 0; + +@@ -2433,8 +2436,7 @@ proc_map_files_readdir(struct file *file + if (!p) { + ret = -ENOMEM; + mmap_read_unlock(mm); +- mmput(mm); +- goto out_put_task; ++ goto out_put_mm; + } + + p->start = vma->vm_start; +@@ -2442,7 +2444,6 @@ proc_map_files_readdir(struct file *file + p->mode = vma->vm_file->f_mode; + } + mmap_read_unlock(mm); +- mmput(mm); + + for (i = 0; i < nr_files; i++) { + char buf[4 * sizeof(long) + 2]; /* max: %lx-%lx\0 */ +@@ -2459,6 +2460,8 @@ proc_map_files_readdir(struct file *file + ctx->pos++; + } + ++out_put_mm: ++ mmput(mm); + out_put_task: + put_task_struct(task); + out: +--- a/fs/proc/namespaces.c ++++ b/fs/proc/namespaces.c +@@ -55,6 +55,10 @@ static const char *proc_ns_get_link(stru + if (!task) + return ERR_PTR(-EACCES); + ++ error = down_read_killable(&task->signal->exec_update_lock); ++ if (error) ++ goto out_put_task; ++ + if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) + goto out; + +@@ -64,6 +68,8 @@ static const char *proc_ns_get_link(stru + + error = nd_jump_link(&ns_path); + out: ++ up_read(&task->signal->exec_update_lock); ++out_put_task: + put_task_struct(task); + return ERR_PTR(error); + } +@@ -80,11 +86,17 @@ static int proc_ns_readlink(struct dentr + if (!task) + return res; + ++ res = down_read_killable(&task->signal->exec_update_lock); ++ if (res) ++ goto out_put_task; ++ + if (ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { + res = ns_get_name(name, sizeof(name), task, ns_ops); + if (res >= 0) + res = readlink_copy(buffer, buflen, name); + } ++ up_read(&task->signal->exec_update_lock); ++out_put_task: + put_task_struct(task); + return res; + } diff --git a/queue-6.12/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch b/queue-6.12/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch new file mode 100644 index 0000000000..302456bf2e --- /dev/null +++ b/queue-6.12/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch @@ -0,0 +1,204 @@ +From stable+bounces-276778-greg=kroah.com@vger.kernel.org Thu Jul 16 18:51:48 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 12:49:25 -0400 +Subject: proc: rename proc_setattr to proc_nochmod_setattr +To: stable@vger.kernel.org +Cc: Christoph Hellwig , Jan Kara , Christian Brauner , Sasha Levin +Message-ID: <20260716164926.673440-1-sashal@kernel.org> + +From: Christoph Hellwig + +[ Upstream commit 690005b0b1e6b567c88b7790e6d90d4d6c9e09cc ] + +What is currently proc_setattr is a special version added after the more +general procfs ->seattr in commit 6d76fa58b050 ("Don't allow chmod() on +the /proc// files"). Give it a name that reflects that to free the +proc_setattr name and better describe what is doing. + +Signed-off-by: Christoph Hellwig +Link: https://patch.msgid.link/20260325063711.3298685-5-hch@lst.de +Reviewed-by: Jan Kara +Signed-off-by: Christian Brauner +Stable-dep-of: 6255da28d4bb ("proc: protect ptrace_may_access() with exec_update_lock (FD links)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/base.c | 22 +++++++++++----------- + fs/proc/fd.c | 6 +++--- + fs/proc/internal.h | 4 ++-- + fs/proc/namespaces.c | 4 ++-- + fs/proc/proc_net.c | 2 +- + 5 files changed, 19 insertions(+), 19 deletions(-) + +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -721,7 +721,7 @@ static bool proc_fd_access_allowed(struc + return allowed; + } + +-int proc_setattr(struct mnt_idmap *idmap, struct dentry *dentry, ++int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr) + { + int error; +@@ -794,7 +794,7 @@ static int proc_pid_permission(struct mn + + + static const struct inode_operations proc_def_inode_operations = { +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static int proc_single_show(struct seq_file *m, void *v) +@@ -1867,7 +1867,7 @@ out: + const struct inode_operations proc_pid_link_inode_operations = { + .readlink = proc_pid_readlink, + .get_link = proc_pid_get_link, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + +@@ -2315,7 +2315,7 @@ proc_map_files_get_link(struct dentry *d + static const struct inode_operations proc_map_files_link_inode_operations = { + .readlink = proc_pid_readlink, + .get_link = proc_map_files_get_link, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static struct dentry * +@@ -2394,7 +2394,7 @@ out: + static const struct inode_operations proc_map_files_inode_operations = { + .lookup = proc_map_files_lookup, + .permission = proc_fd_permission, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static int +@@ -2890,7 +2890,7 @@ static struct dentry *proc_##LSM##_attr_ + static const struct inode_operations proc_##LSM##_attr_dir_inode_ops = { \ + .lookup = proc_##LSM##_attr_dir_lookup, \ + .getattr = pid_getattr, \ +- .setattr = proc_setattr, \ ++ .setattr = proc_nochmod_setattr, \ + } + + #ifdef CONFIG_SECURITY_SMACK +@@ -2949,7 +2949,7 @@ static struct dentry *proc_attr_dir_look + static const struct inode_operations proc_attr_dir_inode_operations = { + .lookup = proc_attr_dir_lookup, + .getattr = pid_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + #endif +@@ -3454,7 +3454,7 @@ static struct dentry *proc_tgid_base_loo + static const struct inode_operations proc_tgid_base_inode_operations = { + .lookup = proc_tgid_base_lookup, + .getattr = pid_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + .permission = proc_pid_permission, + }; + +@@ -3654,7 +3654,7 @@ static int proc_tid_comm_permission(stru + } + + static const struct inode_operations proc_tid_comm_inode_operations = { +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + .permission = proc_tid_comm_permission, + }; + +@@ -3785,7 +3785,7 @@ static const struct file_operations proc + static const struct inode_operations proc_tid_base_inode_operations = { + .lookup = proc_tid_base_lookup, + .getattr = pid_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static struct dentry *proc_task_instantiate(struct dentry *dentry, +@@ -3999,7 +3999,7 @@ static loff_t proc_dir_llseek(struct fil + static const struct inode_operations proc_task_inode_operations = { + .lookup = proc_task_lookup, + .getattr = proc_task_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + .permission = proc_pid_permission, + }; + +--- a/fs/proc/fd.c ++++ b/fs/proc/fd.c +@@ -102,7 +102,7 @@ static int proc_fdinfo_permission(struct + + static const struct inode_operations proc_fdinfo_file_inode_operations = { + .permission = proc_fdinfo_permission, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static const struct file_operations proc_fdinfo_file_operations = { +@@ -375,7 +375,7 @@ const struct inode_operations proc_fd_in + .lookup = proc_lookupfd, + .permission = proc_fd_permission, + .getattr = proc_fd_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static struct dentry *proc_fdinfo_instantiate(struct dentry *dentry, +@@ -416,7 +416,7 @@ static int proc_fdinfo_iterate(struct fi + const struct inode_operations proc_fdinfo_inode_operations = { + .lookup = proc_lookupfdinfo, + .permission = proc_fdinfo_permission, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + const struct file_operations proc_fdinfo_operations = { +--- a/fs/proc/internal.h ++++ b/fs/proc/internal.h +@@ -215,8 +215,8 @@ extern int proc_pid_statm(struct seq_fil + extern const struct dentry_operations pid_dentry_operations; + extern int pid_getattr(struct mnt_idmap *, const struct path *, + struct kstat *, u32, unsigned int); +-extern int proc_setattr(struct mnt_idmap *, struct dentry *, +- struct iattr *); ++int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry, ++ struct iattr *attr); + extern void proc_pid_evict_inode(struct proc_inode *); + extern struct inode *proc_pid_make_inode(struct super_block *, struct task_struct *, umode_t); + extern void pid_update_inode(struct task_struct *, struct inode *); +--- a/fs/proc/namespaces.c ++++ b/fs/proc/namespaces.c +@@ -92,7 +92,7 @@ static int proc_ns_readlink(struct dentr + static const struct inode_operations proc_ns_link_inode_operations = { + .readlink = proc_ns_readlink, + .get_link = proc_ns_get_link, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static struct dentry *proc_ns_instantiate(struct dentry *dentry, +@@ -179,5 +179,5 @@ out_no_task: + const struct inode_operations proc_ns_dir_inode_operations = { + .lookup = proc_ns_dir_lookup, + .getattr = pid_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; +--- a/fs/proc/proc_net.c ++++ b/fs/proc/proc_net.c +@@ -322,7 +322,7 @@ static int proc_tgid_net_getattr(struct + const struct inode_operations proc_net_inode_operations = { + .lookup = proc_tgid_net_lookup, + .getattr = proc_tgid_net_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static int proc_tgid_net_readdir(struct file *file, struct dir_context *ctx) diff --git a/queue-6.12/rust-block-fix-gendisk-cleanup-paths.patch b/queue-6.12/rust-block-fix-gendisk-cleanup-paths.patch new file mode 100644 index 0000000000..301bfcda79 --- /dev/null +++ b/queue-6.12/rust-block-fix-gendisk-cleanup-paths.patch @@ -0,0 +1,88 @@ +From stable+bounces-273238-greg=kroah.com@vger.kernel.org Fri Jul 10 15:35:43 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 09:34:36 -0400 +Subject: rust: block: fix GenDisk cleanup paths +To: stable@vger.kernel.org +Cc: Haoze Xie , stable@kernel.org, Yuan Tan , Xin Liu , Andreas Hindborg , Ren Wei , Jens Axboe , Sasha Levin +Message-ID: <20260710133436.82387-1-sashal@kernel.org> + +From: Haoze Xie + +[ Upstream commit 2957771379fa335103a4b539db57bb2271e12142 ] + +GenDiskBuilder::build() still has fallible work after +__blk_mq_alloc_disk(), but its error path only recovers the +foreign queue data. That leaks the temporary gendisk and +request_queue until later teardown. If the caller moved the last +Arc> into build(), the leaked queue can retain blk-mq +state after the tag set is dropped. + +Fix the pre-registration failure path by dropping the temporary +gendisk reference with put_disk() before recovering queue_data, +so disk_release() can tear down the owned queue. + +Also pair GenDisk::drop() with put_disk() after del_gendisk(). +Once a Rust GenDisk has been added with device_add_disk(), +del_gendisk() only unregisters it; the final gendisk reference +still has to be dropped to complete the release path. + +Fixes: 3253aba3408a ("rust: block: introduce `kernel::block::mq` module") +Cc: stable@kernel.org +Reported-by: Yuan Tan +Reported-by: Xin Liu +Reviewed-by: Andreas Hindborg +Signed-off-by: Haoze Xie +Signed-off-by: Ren Wei +Link: https://patch.msgid.link/b70aff9a920cc42110fe5cf454c3099561863519.1780063368.git.royenheart@gmail.com +Signed-off-by: Jens Axboe +[ no queue-data recovery or recover_data.dismiss() in 6.12 ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + rust/kernel/block/mq/gen_disk.rs | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +--- a/rust/kernel/block/mq/gen_disk.rs ++++ b/rust/kernel/block/mq/gen_disk.rs +@@ -6,7 +6,7 @@ + //! C header: [`include/linux/blk-mq.h`](srctree/include/linux/blk-mq.h) + + use crate::block::mq::{raw_writer::RawWriter, Operations, TagSet}; +-use crate::{bindings, error::from_err_ptr, error::Result, sync::Arc}; ++use crate::{bindings, error::from_err_ptr, error::Result, sync::Arc, types::ScopeGuard}; + use crate::{error, static_lock_class}; + use core::fmt::{self, Write}; + +@@ -139,6 +139,12 @@ impl GenDiskBuilder { + // SAFETY: `gendisk` is a valid pointer as we initialized it above + unsafe { (*gendisk).fops = &TABLE }; + ++ let cleanup_failure = ScopeGuard::new_with_data(gendisk, |gendisk| { ++ // SAFETY: `gendisk` came from `__blk_mq_alloc_disk()` above and ++ // has not been added to the VFS on this cleanup path. ++ unsafe { bindings::put_disk(gendisk) }; ++ }); ++ + let mut raw_writer = RawWriter::from_array( + // SAFETY: `gendisk` points to a valid and initialized instance. We + // have exclusive access, since the disk is not added to the VFS +@@ -161,6 +167,8 @@ impl GenDiskBuilder { + }, + )?; + ++ cleanup_failure.dismiss(); ++ + // INVARIANT: `gendisk` was initialized above. + // INVARIANT: `gendisk` was added to the VFS via `device_add_disk` above. + Ok(GenDisk { +@@ -192,5 +200,10 @@ impl Drop for GenDisk + // initialized instance of `struct gendisk`, and it was previously added + // to the VFS. + unsafe { bindings::del_gendisk(self.gendisk) }; ++ ++ // SAFETY: By type invariant, `self.gendisk` was added to the VFS, so ++ // `put_disk()` must follow `del_gendisk()` to drop the final gendisk ++ // reference and trigger the remaining release path. ++ unsafe { bindings::put_disk(self.gendisk) }; + } + } diff --git a/queue-6.12/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch b/queue-6.12/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch new file mode 100644 index 0000000000..3590039675 --- /dev/null +++ b/queue-6.12/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch @@ -0,0 +1,56 @@ +From stable+bounces-277061-greg=kroah.com@vger.kernel.org Fri Jul 17 16:01:41 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:00:24 -0400 +Subject: seqlock: Change do_task_stat() to use scoped_seqlock_read() +To: stable@vger.kernel.org +Cc: Oleg Nesterov , "Peter Zijlstra (Intel)" , Sasha Levin +Message-ID: <20260717140025.1717514-2-sashal@kernel.org> + +From: Oleg Nesterov + +[ Upstream commit b76f72bea2c601afec81829ea427fc0d20f83216 ] + +To simplify the code and make it more readable. + +[peterz: change to new interface] +Signed-off-by: Oleg Nesterov +Signed-off-by: Peter Zijlstra (Intel) +Stable-dep-of: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/array.c | 9 ++------- + 1 file changed, 2 insertions(+), 7 deletions(-) + +--- a/fs/proc/array.c ++++ b/fs/proc/array.c +@@ -483,7 +483,6 @@ static int do_task_stat(struct seq_file + unsigned long flags; + int exit_code = task->exit_code; + struct signal_struct *sig = task->signal; +- unsigned int seq = 1; + + state = *get_task_state(task); + vsize = eip = esp = 0; +@@ -540,10 +539,7 @@ static int do_task_stat(struct seq_file + if (permitted && (!whole || num_threads < 2)) + wchan = !task_is_running(task); + +- do { +- seq++; /* 2 on the 1st/lockless path, otherwise odd */ +- flags = read_seqbegin_or_lock_irqsave(&sig->stats_lock, &seq); +- ++ scoped_seqlock_read (&sig->stats_lock, ss_lock_irqsave) { + cmin_flt = sig->cmin_flt; + cmaj_flt = sig->cmaj_flt; + cutime = sig->cutime; +@@ -565,8 +561,7 @@ static int do_task_stat(struct seq_file + } + rcu_read_unlock(); + } +- } while (need_seqretry(&sig->stats_lock, seq)); +- done_seqretry_irqrestore(&sig->stats_lock, seq, flags); ++ } + + if (whole) { + thread_group_cputime_adjusted(task, &utime, &stime); diff --git a/queue-6.12/seqlock-introduce-scoped_seqlock_read.patch b/queue-6.12/seqlock-introduce-scoped_seqlock_read.patch new file mode 100644 index 0000000000..e946728335 --- /dev/null +++ b/queue-6.12/seqlock-introduce-scoped_seqlock_read.patch @@ -0,0 +1,164 @@ +From stable+bounces-277060-greg=kroah.com@vger.kernel.org Fri Jul 17 16:01:40 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:00:23 -0400 +Subject: seqlock: Introduce scoped_seqlock_read() +To: stable@vger.kernel.org +Cc: Peter Zijlstra , Oleg Nesterov , Sasha Levin +Message-ID: <20260717140025.1717514-1-sashal@kernel.org> + +From: Peter Zijlstra + +[ Upstream commit cc39f3872c0865bef992b713338df369554fa9e0 ] + +The read_seqbegin/need_seqretry/done_seqretry API is cumbersome and +error prone. With the new helper the "typical" code like + + int seq, nextseq; + unsigned long flags; + + nextseq = 0; + do { + seq = nextseq; + flags = read_seqbegin_or_lock_irqsave(&seqlock, &seq); + + // read-side critical section + + nextseq = 1; + } while (need_seqretry(&seqlock, seq)); + done_seqretry_irqrestore(&seqlock, seq, flags); + +can be rewritten as + + scoped_seqlock_read (&seqlock, ss_lock_irqsave) { + // read-side critical section + } + +Original idea by Oleg Nesterov; with contributions from Linus. + +Originally-by: Oleg Nesterov +Signed-off-by: Peter Zijlstra (Intel) +Stable-dep-of: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/seqlock.h | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 111 insertions(+) + +--- a/include/linux/seqlock.h ++++ b/include/linux/seqlock.h +@@ -1186,4 +1186,115 @@ done_seqretry_irqrestore(seqlock_t *lock + if (seq & 1) + read_sequnlock_excl_irqrestore(lock, flags); + } ++ ++enum ss_state { ++ ss_done = 0, ++ ss_lock, ++ ss_lock_irqsave, ++ ss_lockless, ++}; ++ ++struct ss_tmp { ++ enum ss_state state; ++ unsigned long data; ++ spinlock_t *lock; ++ spinlock_t *lock_irqsave; ++}; ++ ++static inline void __scoped_seqlock_cleanup(struct ss_tmp *sst) ++{ ++ if (sst->lock) ++ spin_unlock(sst->lock); ++ if (sst->lock_irqsave) ++ spin_unlock_irqrestore(sst->lock_irqsave, sst->data); ++} ++ ++extern void __scoped_seqlock_invalid_target(void); ++ ++#if defined(CONFIG_CC_IS_GCC) && CONFIG_GCC_VERSION < 90000 ++/* ++ * For some reason some GCC-8 architectures (nios2, alpha) have trouble ++ * determining that the ss_done state is impossible in __scoped_seqlock_next() ++ * below. ++ */ ++static inline void __scoped_seqlock_bug(void) { } ++#else ++/* ++ * Canary for compiler optimization -- if the compiler doesn't realize this is ++ * an impossible state, it very likely generates sub-optimal code here. ++ */ ++extern void __scoped_seqlock_bug(void); ++#endif ++ ++static inline void ++__scoped_seqlock_next(struct ss_tmp *sst, seqlock_t *lock, enum ss_state target) ++{ ++ switch (sst->state) { ++ case ss_done: ++ __scoped_seqlock_bug(); ++ return; ++ ++ case ss_lock: ++ case ss_lock_irqsave: ++ sst->state = ss_done; ++ return; ++ ++ case ss_lockless: ++ if (!read_seqretry(lock, sst->data)) { ++ sst->state = ss_done; ++ return; ++ } ++ break; ++ } ++ ++ switch (target) { ++ case ss_done: ++ __scoped_seqlock_invalid_target(); ++ return; ++ ++ case ss_lock: ++ sst->lock = &lock->lock; ++ spin_lock(sst->lock); ++ sst->state = ss_lock; ++ return; ++ ++ case ss_lock_irqsave: ++ sst->lock_irqsave = &lock->lock; ++ spin_lock_irqsave(sst->lock_irqsave, sst->data); ++ sst->state = ss_lock_irqsave; ++ return; ++ ++ case ss_lockless: ++ sst->data = read_seqbegin(lock); ++ return; ++ } ++} ++ ++#define __scoped_seqlock_read(_seqlock, _target, _s) \ ++ for (struct ss_tmp _s __cleanup(__scoped_seqlock_cleanup) = \ ++ { .state = ss_lockless, .data = read_seqbegin(_seqlock) }; \ ++ _s.state != ss_done; \ ++ __scoped_seqlock_next(&_s, _seqlock, _target)) ++ ++/** ++ * scoped_seqlock_read (lock, ss_state) - execute the read side critical ++ * section without manual sequence ++ * counter handling or calls to other ++ * helpers ++ * @lock: pointer to seqlock_t protecting the data ++ * @ss_state: one of {ss_lock, ss_lock_irqsave, ss_lockless} indicating ++ * the type of critical read section ++ * ++ * Example: ++ * ++ * scoped_seqlock_read (&lock, ss_lock) { ++ * // read-side critical section ++ * } ++ * ++ * Starts with a lockess pass first. If it fails, restarts the critical ++ * section with the lock held. ++ */ ++#define scoped_seqlock_read(_seqlock, _target) \ ++ __scoped_seqlock_read(_seqlock, _target, __UNIQUE_ID(seqlock)) ++ + #endif /* __LINUX_SEQLOCK_H */ diff --git a/queue-6.12/series b/queue-6.12/series index 1dc9abd46c..493860f5bb 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -1131,3 +1131,106 @@ mtd-rawnand-lpc32xx_mlc-fail-dma-transfers-on-timeout.patch mtd-rawnand-lpc32xx_slc-fail-dma-transfer-on-completion-timeout.patch mmc-block-fix-rpmb-device-unregister-ordering.patch mmc-sdhci-of-dwcmshc-check-bus-clock-enable-result-in-the-probe-method.patch +rust-block-fix-gendisk-cleanup-paths.patch +acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch +acpi-driver-check-acpi_companion-against-null-during-probe.patch +acpi-bus-introduce-devm_acpi_install_notify_handler.patch +acpi-nfit-core-use-devm_acpi_install_notify_handler.patch +acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch +iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch +iio-adc-ad7380-select-regmap.patch +iio-pressure-remove-redundant-pm_runtime_mark_last_busy-calls.patch +iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch +alsa-aoa-check-snd_ctl_new1-return-value.patch +alsa-hda-cs35l41-fix-firmware-load-work-teardown.patch +alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch +alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch +vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch +pci-altera-fix-resource-leaks-on-probe-failure.patch +pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch +pci-mediatek-switch-to-msi_create_parent_irq_domain.patch +pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch +pci-mediatek-use-generic-macro-for-tpvperl-delay.patch +pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch +pci-use-pbus_select_window-during-bar-resize.patch +pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch +pci-free-saved-list-without-holding-pci_bus_sem.patch +pci-fix-restoring-bars-on-bar-resize-rollback-path.patch +pci-move-resizable-bar-code-to-rebar.c.patch +pci-skip-resizable-bar-restore-on-read-error.patch +staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch +staging-rtl8723bs-fix-spaces-around-binary-operators.patch +staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch +crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch +bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch +gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch +io_uring-rw-ensure-reissue-path-is-correctly-handled-for-iopoll.patch +io_uring-rw-preserve-partial-result-for-iopoll.patch +media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch +media-nxp-imx8-isi-fix-use-after-free-on-remove.patch +netfilter-ebtables-use-vmalloc_array-to-improve-code.patch +netfilter-ebtables-zero-chainstack-array.patch +bluetooth-l2cap-fix-not-tracking-outstanding-tx-ident.patch +bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch +bluetooth-hci_core-enable-buffer-flow-control-for-sco-esco.patch +bluetooth-separate-cis_link-and-bis_link-link-types.patch +bluetooth-hci_conn-fix-null-ptr-deref-in-hci_abort_conn.patch +bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch +bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch +mm-swap_cgroup-fix-null-deref-in-lookup_swap_cgroup_id-on-swapless-host.patch +smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch +smb-client-resolve-swn-tcon-from-live-registrations.patch +ksmbd_vfs_rename-vfs_path_parent_lookup-accepts-err_ptr-as-name.patch +vfs-make-last_xxx-private-to-fs-namei.c.patch +ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch +ksmbd-use-opener-credentials-for-fsctl-mutations.patch +ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch +ksmbd-track-the-connection-owning-a-byte-range-lock.patch +proc-rename-proc_setattr-to-proc_nochmod_setattr.patch +proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch +perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch +hid-add-haptics-page-defines.patch +hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch +seqlock-introduce-scoped_seqlock_read.patch +seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch +proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch +treewide-switch-rename-to-timer_delete.patch +hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch +hid-pidff-fix-missing-blank-lines-after-declarations.patch +hid-pidff-add-missing-spaces.patch +hid-pidff-rework-pidff_upload_effect.patch +hid-pidff-use-correct-effect-type-in-effect-update.patch +hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch +hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch +bpf-convert-lpm_trie.c-to-rqspinlock.patch +bpf-arm64-powerpc-add-bpf_jit_bypass_spec_v1-v4.patch +bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch +bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch +usb-iowarrior-remove-inherent-race-with-minor-number.patch +usb-iowarrior-fix-use-after-free-on-disconnect-race.patch +usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch +crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch +crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch +usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch +crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch +usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch +btrfs-concentrate-the-error-handling-of-submit_one_sector.patch +btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch +btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch +btrfs-remove-the-cow-fixup-mechanism.patch +btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch +crypto-ccp-move-dev_info-err-messages-for-sev-snp-init-and-shutdown.patch +crypto-ccp-reset-tmr-size-at-snp-shutdown.patch +crypto-ccp-register-snp-panic-notifier-only-if-snp-is-enabled.patch +crypto-ccp-move-sev-snp-platform-initialization-to-kvm.patch +crypto-ccp-fix-a-case-where-snp_shutdown-is-missed.patch +crypto-ccp-do-not-initialize-snp-for-ioctl-snp_config.patch +crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch +exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch +exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch +exfat-add-exfat_get_dentry_set_by_ei-helper.patch +exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch +exfat-fix-incorrect-directory-checksum-after-rename-to-shorter-name.patch +exfat-preserve-benign-secondary-entries-during-rename-and-move.patch +btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch +btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch diff --git a/queue-6.12/smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch b/queue-6.12/smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch new file mode 100644 index 0000000000..d308124714 --- /dev/null +++ b/queue-6.12/smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch @@ -0,0 +1,65 @@ +From stable+bounces-275109-greg=kroah.com@vger.kernel.org Thu Jul 16 02:34:52 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 20:30:31 -0400 +Subject: smb: client: Improve unlocking of a mutex in cifs_get_swn_reg() +To: stable@vger.kernel.org +Cc: Markus Elfring , Steve French , Sasha Levin +Message-ID: <20260716003032.615867-1-sashal@kernel.org> + +From: Markus Elfring + +[ Upstream commit e2080b70c5851a132547bec3bd7dde847e649678 ] + +Use two additional labels so that another bit of common code can be better +reused at the end of this function implementation. + +Signed-off-by: Markus Elfring +Signed-off-by: Steve French +Stable-dep-of: ec457f9afe5a ("smb: client: resolve SWN tcon from live registrations") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifs_swn.c | 13 ++++++------- + 1 file changed, 6 insertions(+), 7 deletions(-) + +--- a/fs/smb/client/cifs_swn.c ++++ b/fs/smb/client/cifs_swn.c +@@ -313,17 +313,15 @@ static struct cifs_swn_reg *cifs_get_swn + reg = cifs_find_swn_reg(tcon); + if (!IS_ERR(reg)) { + kref_get(®->ref_count); +- mutex_unlock(&cifs_swnreg_idr_mutex); +- return reg; ++ goto unlock; + } else if (PTR_ERR(reg) != -EEXIST) { +- mutex_unlock(&cifs_swnreg_idr_mutex); +- return reg; ++ goto unlock; + } + + reg = kmalloc(sizeof(struct cifs_swn_reg), GFP_ATOMIC); + if (reg == NULL) { +- mutex_unlock(&cifs_swnreg_idr_mutex); +- return ERR_PTR(-ENOMEM); ++ ret = -ENOMEM; ++ goto fail_unlock; + } + + kref_init(®->ref_count); +@@ -354,7 +352,7 @@ static struct cifs_swn_reg *cifs_get_swn + reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT); + + reg->tcon = tcon; +- ++unlock: + mutex_unlock(&cifs_swnreg_idr_mutex); + + return reg; +@@ -365,6 +363,7 @@ fail_idr: + idr_remove(&cifs_swnreg_idr, reg->id); + fail: + kfree(reg); ++fail_unlock: + mutex_unlock(&cifs_swnreg_idr_mutex); + return ERR_PTR(ret); + } diff --git a/queue-6.12/smb-client-resolve-swn-tcon-from-live-registrations.patch b/queue-6.12/smb-client-resolve-swn-tcon-from-live-registrations.patch new file mode 100644 index 0000000000..707f942ca5 --- /dev/null +++ b/queue-6.12/smb-client-resolve-swn-tcon-from-live-registrations.patch @@ -0,0 +1,604 @@ +From stable+bounces-275110-greg=kroah.com@vger.kernel.org Thu Jul 16 02:30:42 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 20:30:32 -0400 +Subject: smb: client: resolve SWN tcon from live registrations +To: stable@vger.kernel.org +Cc: Michael Bommarito , Steve French , Sasha Levin +Message-ID: <20260716003032.615867-2-sashal@kernel.org> + +From: Michael Bommarito + +[ Upstream commit ec457f9afe5ae9538bdcd58fd4cb442b9787e183 ] + +cifs_swn_notify() looks up a witness registration by id under +cifs_swnreg_idr_mutex, drops the mutex, and then uses the registration's +cached tcon pointer. That pointer is not a lifetime reference, and it is +not a stable representative once cifs_get_swn_reg() lets multiple tcons +for the same net/share name share one registration id. + +A same-share second mount can keep the cifs_swn_reg alive after the first +tcon unregisters and is freed. The registration then still points at the +freed first tcon, so taking tc_lock or incrementing tc_count through +swnreg->tcon only moves the use-after-free earlier. Taking tc_lock while +holding cifs_swnreg_idr_mutex also violates the documented CIFS lock +order. + +Fix this by making the registration store only the stable witness +identity: id, net name, share name, and notify flags. When a notify +arrives, copy that identity under cifs_swnreg_idr_mutex, drop the mutex, +then find and pin a live witness tcon that currently matches the net/share +pair under the normal cifs_tcp_ses_lock -> tc_lock order. The notification +path uses that pinned tcon directly and drops the reference when done. + +Registration and unregister messages now use the live tcon passed by the +caller instead of a cached tcon in the registration. The final unregister +send is folded into cifs_swn_unregister() while the registration is still +protected by cifs_swnreg_idr_mutex. This removes the previous +find/drop/reacquire raw-pointer window. The release path only removes the +idr entry and frees the stable identity strings. + +This preserves the intended one-registration/many-tcon behavior: a +registration id represents a net/share pair, and notify handling acts on a +live representative selected at use time. It also preserves CLIENT_MOVE +ordering for the representative tcon because the old-IP unregister is sent +before cifs_swn_register() sends the new-IP register. + +Fixes: fed979a7e082 ("cifs: Set witness notification handler for messages from userspace daemon") +Cc: stable@vger.kernel.org +Signed-off-by: Michael Bommarito +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifs_swn.c | 314 ++++++++++++++++++++++++++++++++++++++--------- + fs/smb/client/trace.h | 2 + 2 files changed, 262 insertions(+), 54 deletions(-) + +--- a/fs/smb/client/cifs_swn.c ++++ b/fs/smb/client/cifs_swn.c +@@ -28,10 +28,54 @@ struct cifs_swn_reg { + bool net_name_notify; + bool share_name_notify; + bool ip_notify; ++}; + +- struct cifs_tcon *tcon; ++struct cifs_swn_reg_info { ++ int id; ++ unsigned int ref_count; ++ const char *net_name; ++ const char *share_name; ++ bool net_name_notify; ++ bool share_name_notify; ++ bool ip_notify; + }; + ++static void cifs_swn_snapshot_reg(struct cifs_swn_reg *swnreg, ++ struct cifs_swn_reg_info *info) ++{ ++ info->id = swnreg->id; ++ info->ref_count = kref_read(&swnreg->ref_count); ++ info->net_name = swnreg->net_name; ++ info->share_name = swnreg->share_name; ++ info->net_name_notify = swnreg->net_name_notify; ++ info->share_name_notify = swnreg->share_name_notify; ++ info->ip_notify = swnreg->ip_notify; ++} ++ ++static int cifs_swn_dup_reg(struct cifs_swn_reg *swnreg, ++ struct cifs_swn_reg_info *info) ++{ ++ cifs_swn_snapshot_reg(swnreg, info); ++ ++ info->net_name = kstrdup(swnreg->net_name, GFP_KERNEL); ++ if (!info->net_name) ++ return -ENOMEM; ++ ++ info->share_name = kstrdup(swnreg->share_name, GFP_KERNEL); ++ if (!info->share_name) { ++ kfree(info->net_name); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static void cifs_swn_free_reg_info(struct cifs_swn_reg_info *info) ++{ ++ kfree(info->net_name); ++ kfree(info->share_name); ++} ++ + static int cifs_swn_auth_info_krb(struct cifs_tcon *tcon, struct sk_buff *skb) + { + int ret; +@@ -73,7 +117,8 @@ static int cifs_swn_auth_info_ntlm(struc + * The authentication information to connect to the witness service is bundled + * into the message. + */ +-static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg) ++static int cifs_swn_send_register_message(struct cifs_swn_reg_info *swnreg, ++ struct cifs_tcon *tcon) + { + struct sk_buff *skb; + struct genlmsghdr *hdr; +@@ -111,10 +156,10 @@ static int cifs_swn_send_register_messag + * told to switch to it (client move message). In these cases we unregister from the + * server address and register to the new address when we receive the notification. + */ +- if (swnreg->tcon->ses->server->use_swn_dstaddr) +- addr = &swnreg->tcon->ses->server->swn_dstaddr; ++ if (tcon->ses->server->use_swn_dstaddr) ++ addr = &tcon->ses->server->swn_dstaddr; + else +- addr = &swnreg->tcon->ses->server->dstaddr; ++ addr = &tcon->ses->server->dstaddr; + + ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage), addr); + if (ret < 0) +@@ -138,10 +183,10 @@ static int cifs_swn_send_register_messag + goto nlmsg_fail; + } + +- authtype = cifs_select_sectype(swnreg->tcon->ses->server, swnreg->tcon->ses->sectype); ++ authtype = cifs_select_sectype(tcon->ses->server, tcon->ses->sectype); + switch (authtype) { + case Kerberos: +- ret = cifs_swn_auth_info_krb(swnreg->tcon, skb); ++ ret = cifs_swn_auth_info_krb(tcon, skb); + if (ret < 0) { + cifs_dbg(VFS, "%s: Failed to get kerberos auth info: %d\n", __func__, ret); + goto nlmsg_fail; +@@ -149,7 +194,7 @@ static int cifs_swn_send_register_messag + break; + case NTLMv2: + case RawNTLMSSP: +- ret = cifs_swn_auth_info_ntlm(swnreg->tcon, skb); ++ ret = cifs_swn_auth_info_ntlm(tcon, skb); + if (ret < 0) { + cifs_dbg(VFS, "%s: Failed to get NTLM auth info: %d\n", __func__, ret); + goto nlmsg_fail; +@@ -179,7 +224,8 @@ fail: + /* + * Sends an uregister message to the userspace daemon based on the registration + */ +-static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg) ++static int cifs_swn_send_unregister_message(struct cifs_swn_reg_info *swnreg, ++ struct cifs_tcon *tcon) + { + struct sk_buff *skb; + struct genlmsghdr *hdr; +@@ -208,7 +254,7 @@ static int cifs_swn_send_unregister_mess + goto nlmsg_fail; + + ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage), +- &swnreg->tcon->ses->server->dstaddr); ++ &tcon->ses->server->dstaddr); + if (ret < 0) + goto nlmsg_fail; + +@@ -245,6 +291,88 @@ nlmsg_fail: + } + + /* ++ * Allocation-free mirror of extract_hostname() + extract_sharename() from ++ * fs/smb/client/unc.c. Those helpers kmalloc(GFP_KERNEL); this runs under ++ * cifs_tcp_ses_lock and tcon->tc_lock, both spinlocks, so we mirror their ++ * parsing in place against the caller's stable net_name/share_name strings. ++ * Keep in sync with unc.c. ++ */ ++static bool cifs_swn_tcon_matches(struct cifs_tcon *tcon, ++ const char *net_name, ++ const char *share_name) ++{ ++ const char *unc = tcon->tree_name; ++ const char *host, *share, *delim; ++ size_t host_len, share_len; ++ ++ if (!tcon->use_witness) ++ return false; ++ ++ /* extract_hostname: require strlen(unc) >= 3 */ ++ if (strnlen(unc, 3) < 3) ++ return false; ++ /* extract_hostname: skip all leading '\' characters */ ++ for (host = unc; *host == '\\'; host++) ++ ; ++ if (!*host) ++ return false; ++ delim = strchr(host, '\\'); ++ if (!delim) ++ return false; ++ host_len = delim - host; ++ if (strlen(net_name) != host_len || ++ strncasecmp(host, net_name, host_len)) ++ return false; ++ ++ /* extract_sharename: start at unc + 2, then first '\' onward */ ++ share = unc + 2; ++ delim = strchr(share, '\\'); ++ if (!delim) ++ return false; ++ share = delim + 1; ++ share_len = strlen(share); ++ ++ return strlen(share_name) == share_len && ++ !strncasecmp(share, share_name, share_len); ++} ++ ++/* ++ * One SWN registration id represents one net/share name pair. Multiple ++ * mounted tcons can therefore share the id. Pick a live representative at ++ * use time instead of caching the first tcon pointer in the registration. ++ */ ++static struct cifs_tcon *cifs_swn_get_tcon(struct cifs_swn_reg_info *swnreg) ++{ ++ struct TCP_Server_Info *server; ++ struct cifs_ses *ses; ++ struct cifs_tcon *tcon; ++ ++ spin_lock(&cifs_tcp_ses_lock); ++ list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { ++ list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { ++ list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { ++ spin_lock(&tcon->tc_lock); ++ if (tcon->status == TID_EXITING || ++ !cifs_swn_tcon_matches(tcon, swnreg->net_name, ++ swnreg->share_name)) { ++ spin_unlock(&tcon->tc_lock); ++ continue; ++ } ++ ++tcon->tc_count; ++ trace_smb3_tcon_ref(tcon->debug_id, ++ tcon->tc_count, ++ netfs_trace_tcon_ref_get_swn_notify); ++ spin_unlock(&tcon->tc_lock); ++ spin_unlock(&cifs_tcp_ses_lock); ++ return tcon; ++ } ++ } ++ } ++ spin_unlock(&cifs_tcp_ses_lock); ++ return NULL; ++} ++ ++/* + * Try to find a matching registration for the tcon's server name and share name. + * Calls to this function must be protected by cifs_swnreg_idr_mutex. + * TODO Try to avoid memory allocations +@@ -350,8 +478,6 @@ static struct cifs_swn_reg *cifs_get_swn + reg->net_name_notify = true; + reg->share_name_notify = true; + reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT); +- +- reg->tcon = tcon; + unlock: + mutex_unlock(&cifs_swnreg_idr_mutex); + +@@ -371,11 +497,6 @@ fail_unlock: + static void cifs_swn_reg_release(struct kref *ref) + { + struct cifs_swn_reg *swnreg = container_of(ref, struct cifs_swn_reg, ref_count); +- int ret; +- +- ret = cifs_swn_send_unregister_message(swnreg); +- if (ret < 0) +- cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", __func__, ret); + + idr_remove(&cifs_swnreg_idr, swnreg->id); + kfree(swnreg->net_name); +@@ -383,23 +504,33 @@ static void cifs_swn_reg_release(struct + kfree(swnreg); + } + +-static void cifs_put_swn_reg(struct cifs_swn_reg *swnreg) ++static void cifs_put_swn_reg_locked(struct cifs_swn_reg *swnreg, ++ struct cifs_tcon *tcon) + { +- mutex_lock(&cifs_swnreg_idr_mutex); ++ if (kref_read(&swnreg->ref_count) == 1) { ++ struct cifs_swn_reg_info swnreg_info; ++ int ret; ++ ++ cifs_swn_snapshot_reg(swnreg, &swnreg_info); ++ ret = cifs_swn_send_unregister_message(&swnreg_info, tcon); ++ if (ret < 0) ++ cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", ++ __func__, ret); ++ } ++ + kref_put(&swnreg->ref_count, cifs_swn_reg_release); +- mutex_unlock(&cifs_swnreg_idr_mutex); + } + +-static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const char *name, int state) ++static int cifs_swn_resource_state_changed(struct cifs_tcon *tcon, const char *name, int state) + { + switch (state) { + case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE: + cifs_dbg(FYI, "%s: resource name '%s' become unavailable\n", __func__, name); +- cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true); ++ cifs_signal_cifsd_for_reconnect(tcon->ses->server, true); + break; + case CIFS_SWN_RESOURCE_STATE_AVAILABLE: + cifs_dbg(FYI, "%s: resource name '%s' become available\n", __func__, name); +- cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true); ++ cifs_signal_cifsd_for_reconnect(tcon->ses->server, true); + break; + case CIFS_SWN_RESOURCE_STATE_UNKNOWN: + cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n", __func__, name); +@@ -505,7 +636,7 @@ unlock: + return ret; + } + +-static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_storage *addr) ++static int cifs_swn_client_move(struct cifs_tcon *tcon, struct sockaddr_storage *addr) + { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr; + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr; +@@ -515,14 +646,17 @@ static int cifs_swn_client_move(struct c + else if (addr->ss_family == AF_INET6) + cifs_dbg(FYI, "%s: move to %pI6\n", __func__, &ipv6->sin6_addr); + +- return cifs_swn_reconnect(swnreg->tcon, addr); ++ return cifs_swn_reconnect(tcon, addr); + } + + int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info) + { + struct cifs_swn_reg *swnreg; ++ struct cifs_swn_reg_info swnreg_info; ++ struct cifs_tcon *tcon; + char name[256]; + int type; ++ int ret = 0; + + if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]) { + int swnreg_id; +@@ -530,21 +664,34 @@ int cifs_swn_notify(struct sk_buff *skb, + swnreg_id = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]); + mutex_lock(&cifs_swnreg_idr_mutex); + swnreg = idr_find(&cifs_swnreg_idr, swnreg_id); +- mutex_unlock(&cifs_swnreg_idr_mutex); + if (swnreg == NULL) { ++ mutex_unlock(&cifs_swnreg_idr_mutex); + cifs_dbg(FYI, "%s: registration id %d not found\n", __func__, swnreg_id); + return -EINVAL; + } ++ ret = cifs_swn_dup_reg(swnreg, &swnreg_info); ++ mutex_unlock(&cifs_swnreg_idr_mutex); ++ if (ret) ++ return ret; + } else { + cifs_dbg(FYI, "%s: missing registration id attribute\n", __func__); + return -EINVAL; + } + ++ tcon = cifs_swn_get_tcon(&swnreg_info); ++ if (!tcon) { ++ cifs_dbg(FYI, "%s: registration id %d has no live tcon\n", ++ __func__, swnreg_info.id); ++ ret = -ENODEV; ++ goto free_info; ++ } ++ + if (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]) { + type = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]); + } else { + cifs_dbg(FYI, "%s: missing notification type attribute\n", __func__); +- return -EINVAL; ++ ret = -EINVAL; ++ goto out; + } + + switch (type) { +@@ -556,15 +703,18 @@ int cifs_swn_notify(struct sk_buff *skb, + sizeof(name)); + } else { + cifs_dbg(FYI, "%s: missing resource name attribute\n", __func__); +- return -EINVAL; ++ ret = -EINVAL; ++ goto out; + } + if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]) { + state = nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]); + } else { + cifs_dbg(FYI, "%s: missing resource state attribute\n", __func__); +- return -EINVAL; ++ ret = -EINVAL; ++ goto out; + } +- return cifs_swn_resource_state_changed(swnreg, name, state); ++ ret = cifs_swn_resource_state_changed(tcon, name, state); ++ break; + } + case CIFS_SWN_NOTIFICATION_CLIENT_MOVE: { + struct sockaddr_storage addr; +@@ -573,28 +723,36 @@ int cifs_swn_notify(struct sk_buff *skb, + nla_memcpy(&addr, info->attrs[CIFS_GENL_ATTR_SWN_IP], sizeof(addr)); + } else { + cifs_dbg(FYI, "%s: missing IP address attribute\n", __func__); +- return -EINVAL; ++ ret = -EINVAL; ++ goto out; + } +- return cifs_swn_client_move(swnreg, &addr); ++ ret = cifs_swn_client_move(tcon, &addr); ++ break; + } + default: + cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__, type); + break; + } + +- return 0; ++out: ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify); ++free_info: ++ cifs_swn_free_reg_info(&swnreg_info); ++ return ret; + } + + int cifs_swn_register(struct cifs_tcon *tcon) + { + struct cifs_swn_reg *swnreg; ++ struct cifs_swn_reg_info swnreg_info; + int ret; + + swnreg = cifs_get_swn_reg(tcon); + if (IS_ERR(swnreg)) + return PTR_ERR(swnreg); + +- ret = cifs_swn_send_register_message(swnreg); ++ cifs_swn_snapshot_reg(swnreg, &swnreg_info); ++ ret = cifs_swn_send_register_message(&swnreg_info, tcon); + if (ret < 0) { + cifs_dbg(VFS, "%s: Failed to send swn register message: %d\n", __func__, ret); + /* Do not put the swnreg or return error, the echo task will retry */ +@@ -615,35 +773,68 @@ int cifs_swn_unregister(struct cifs_tcon + return PTR_ERR(swnreg); + } + ++ cifs_put_swn_reg_locked(swnreg, tcon); + mutex_unlock(&cifs_swnreg_idr_mutex); + +- cifs_put_swn_reg(swnreg); +- + return 0; + } + +-void cifs_swn_dump(struct seq_file *m) ++/* ++ * Snapshot one registration under cifs_swnreg_idr_mutex and return. Callers ++ * intentionally do the per-registration network/genlmsg work without the ++ * mutex held, both to keep the critical section short and to avoid nesting ++ * cifs_swnreg_idr_mutex inside the higher tc_lock when a live tcon is then ++ * pinned for the send. ++ */ ++static int cifs_swn_get_next_reg_info(int *id, struct cifs_swn_reg_info *info) + { + struct cifs_swn_reg *swnreg; ++ int ret = 0; ++ ++ mutex_lock(&cifs_swnreg_idr_mutex); ++ swnreg = idr_get_next(&cifs_swnreg_idr, id); ++ if (swnreg) { ++ ret = cifs_swn_dup_reg(swnreg, info); ++ if (!ret) { ++ *id = swnreg->id + 1; ++ ret = 1; ++ } ++ } ++ mutex_unlock(&cifs_swnreg_idr_mutex); ++ ++ return ret; ++} ++ ++void cifs_swn_dump(struct seq_file *m) ++{ ++ struct cifs_swn_reg_info swnreg_info; ++ struct cifs_tcon *tcon; + struct sockaddr_in *sa; + struct sockaddr_in6 *sa6; +- int id; ++ int id = 0; ++ int ret; + + seq_puts(m, "Witness registrations:"); + +- mutex_lock(&cifs_swnreg_idr_mutex); +- idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) { ++ while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) { + seq_printf(m, "\nId: %u Refs: %u Network name: '%s'%s Share name: '%s'%s Ip address: ", +- id, kref_read(&swnreg->ref_count), +- swnreg->net_name, swnreg->net_name_notify ? "(y)" : "(n)", +- swnreg->share_name, swnreg->share_name_notify ? "(y)" : "(n)"); +- switch (swnreg->tcon->ses->server->dstaddr.ss_family) { ++ swnreg_info.id, swnreg_info.ref_count, ++ swnreg_info.net_name, swnreg_info.net_name_notify ? "(y)" : "(n)", ++ swnreg_info.share_name, swnreg_info.share_name_notify ? "(y)" : "(n)"); ++ ++ tcon = cifs_swn_get_tcon(&swnreg_info); ++ if (!tcon) { ++ seq_puts(m, "(no live tcon)"); ++ goto next; ++ } ++ ++ switch (tcon->ses->server->dstaddr.ss_family) { + case AF_INET: +- sa = (struct sockaddr_in *) &swnreg->tcon->ses->server->dstaddr; ++ sa = (struct sockaddr_in *)&tcon->ses->server->dstaddr; + seq_printf(m, "%pI4", &sa->sin_addr.s_addr); + break; + case AF_INET6: +- sa6 = (struct sockaddr_in6 *) &swnreg->tcon->ses->server->dstaddr; ++ sa6 = (struct sockaddr_in6 *)&tcon->ses->server->dstaddr; + seq_printf(m, "%pI6", &sa6->sin6_addr.s6_addr); + if (sa6->sin6_scope_id) + seq_printf(m, "%%%u", sa6->sin6_scope_id); +@@ -651,23 +842,38 @@ void cifs_swn_dump(struct seq_file *m) + default: + seq_puts(m, "(unknown)"); + } +- seq_printf(m, "%s", swnreg->ip_notify ? "(y)" : "(n)"); ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify); ++next: ++ seq_printf(m, "%s", swnreg_info.ip_notify ? "(y)" : "(n)"); ++ cifs_swn_free_reg_info(&swnreg_info); + } +- mutex_unlock(&cifs_swnreg_idr_mutex); ++ if (ret < 0) ++ seq_printf(m, "\nFailed to snapshot witness registration: %d", ret); + seq_puts(m, "\n"); + } + + void cifs_swn_check(void) + { +- struct cifs_swn_reg *swnreg; +- int id; ++ struct cifs_swn_reg_info swnreg_info; ++ struct cifs_tcon *tcon; ++ int id = 0; + int ret; + +- mutex_lock(&cifs_swnreg_idr_mutex); +- idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) { +- ret = cifs_swn_send_register_message(swnreg); ++ while ((ret = cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) { ++ tcon = cifs_swn_get_tcon(&swnreg_info); ++ if (!tcon) { ++ cifs_dbg(FYI, "%s: registration id %d has no live tcon\n", ++ __func__, swnreg_info.id); ++ goto free_info; ++ } ++ ++ ret = cifs_swn_send_register_message(&swnreg_info, tcon); + if (ret < 0) + cifs_dbg(FYI, "%s: Failed to send register message: %d\n", __func__, ret); ++ cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify); ++free_info: ++ cifs_swn_free_reg_info(&swnreg_info); + } +- mutex_unlock(&cifs_swnreg_idr_mutex); ++ if (ret < 0) ++ cifs_dbg(FYI, "%s: Failed to snapshot registration: %d\n", __func__, ret); + } +--- a/fs/smb/client/trace.h ++++ b/fs/smb/client/trace.h +@@ -52,6 +52,7 @@ + EM(netfs_trace_tcon_ref_get_find, "GET Find ") \ + EM(netfs_trace_tcon_ref_get_find_sess_tcon, "GET FndSes") \ + EM(netfs_trace_tcon_ref_get_reconnect_server, "GET Reconn") \ ++ EM(netfs_trace_tcon_ref_get_swn_notify, "GET SwnNot") \ + EM(netfs_trace_tcon_ref_new, "NEW ") \ + EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \ + EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \ +@@ -63,6 +64,7 @@ + EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \ + EM(netfs_trace_tcon_ref_put_dfs_refer, "PUT DfsRfr") \ + EM(netfs_trace_tcon_ref_put_reconnect_server, "PUT Reconn") \ ++ EM(netfs_trace_tcon_ref_put_swn_notify, "PUT SwnNot") \ + EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \ + EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \ + EM(netfs_trace_tcon_ref_see_fscache_collision, "SEE FV-CO!") \ diff --git a/queue-6.12/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch b/queue-6.12/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch new file mode 100644 index 0000000000..2a10a31913 --- /dev/null +++ b/queue-6.12/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch @@ -0,0 +1,56 @@ +From stable+bounces-274643-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:47 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:17:58 -0400 +Subject: staging: rtl8723bs: core: move constants to right side in comparison +To: stable@vger.kernel.org +Cc: William Hansen-Baird , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260715001800.3784291-1-sashal@kernel.org> + +From: William Hansen-Baird + +[ Upstream commit cf0f2680c30d4b438a5e84b73dc70be405936b54 ] + +Move constants to right side in if-statement conditions. + +Signed-off-by: William Hansen-Baird +Link: https://patch.msgid.link/20251224100329.762141-3-william.hansen.baird@gmail.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: 1463ca3ec660 ("staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 4 ++-- + drivers/staging/rtl8723bs/core/rtw_security.c | 2 +- + 2 files changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c ++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +@@ -1013,7 +1013,7 @@ static int rtw_get_cipher_info(struct wl + pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12); + + if (pbuf && (wpa_ielen > 0)) { +- if (_SUCCESS == rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x)) { ++ if (rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) { + pnetwork->bcn_info.pairwise_cipher = pairwise_cipher; + pnetwork->bcn_info.group_cipher = group_cipher; + pnetwork->bcn_info.is_8021x = is8021x; +@@ -1023,7 +1023,7 @@ static int rtw_get_cipher_info(struct wl + pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12); + + if (pbuf && (wpa_ielen > 0)) { +- if (_SUCCESS == rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x)) { ++ if (rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) { + pnetwork->bcn_info.pairwise_cipher = pairwise_cipher; + pnetwork->bcn_info.group_cipher = group_cipher; + pnetwork->bcn_info.is_8021x = is8021x; +--- a/drivers/staging/rtl8723bs/core/rtw_security.c ++++ b/drivers/staging/rtl8723bs/core/rtw_security.c +@@ -1537,7 +1537,7 @@ void rtw_sec_restore_wep_key(struct adap + struct security_priv *securitypriv = &(adapter->securitypriv); + signed int keyid; + +- if ((_WEP40_ == securitypriv->dot11PrivacyAlgrthm) || (_WEP104_ == securitypriv->dot11PrivacyAlgrthm)) { ++ if ((securitypriv->dot11PrivacyAlgrthm == _WEP40_) || (securitypriv->dot11PrivacyAlgrthm == _WEP104_)) { + for (keyid = 0; keyid < 4; keyid++) { + if (securitypriv->key_mask & BIT(keyid)) { + if (keyid == securitypriv->dot11PrivacyKeyIndex) diff --git a/queue-6.12/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch b/queue-6.12/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch new file mode 100644 index 0000000000..24fb27b21e --- /dev/null +++ b/queue-6.12/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch @@ -0,0 +1,94 @@ +From stable+bounces-274645-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:57 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:18:00 -0400 +Subject: staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr() +To: stable@vger.kernel.org +Cc: Alexandru Hossu , stable , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260715001800.3784291-3-sashal@kernel.org> + +From: Alexandru Hossu + +[ Upstream commit 1463ca3ec6601cbb097d8d87dbf5dcf1cb86a344 ] + +Three IE/attribute parsing functions have missing bounds checks. + +rtw_get_sec_ie() and rtw_get_wapi_ie() iterate over a raw IE buffer +without verifying that the header bytes (tag + length) are within the +remaining buffer before reading them. Additionally, rtw_get_sec_ie() +compares the 4-byte WPA OUI at cnt+2 without checking that at least +6 bytes remain, and rtw_get_wapi_ie() compares a 4-byte WAPI OUI at +cnt+6 without checking that at least 10 bytes remain. + +rtw_get_wps_attr() reads wps_ie[0] and wps_ie+2 unconditionally at +entry, before verifying that wps_ielen is large enough to contain +the 6-byte WPS IE header (element_id + length + 4-byte OUI). Inside +the attribute loop, get_unaligned_be16() is called on attr_ptr and +attr_ptr+2 without checking that 4 bytes remain in the buffer. + +Add a cnt+2 bounds check before each loop body in rtw_get_sec_ie() +and rtw_get_wapi_ie(), guard each multi-byte comparison with a minimum +IE length requirement, add a wps_ielen < 6 early return in +rtw_get_wps_attr(), and add a 4-byte bounds check in its inner loop. + +Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver") +Cc: stable +Signed-off-by: Alexandru Hossu +Link: https://patch.msgid.link/20260522004531.1038924-8-hossu.alexandru@gmail.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c ++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +@@ -585,9 +585,14 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l + cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_); + + while (cnt < in_len) { ++ if (cnt + 2 > in_len) ++ break; ++ if (cnt + 2 + in_ie[cnt + 1] > in_len) ++ break; + authmode = in_ie[cnt]; + + if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && ++ in_ie[cnt + 1] >= 8 && + (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) || + !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) { + if (wapi_ie) +@@ -620,9 +625,14 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l + cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_); + + while (cnt < in_len) { ++ if (cnt + 2 > in_len) ++ break; ++ if (cnt + 2 + in_ie[cnt + 1] > in_len) ++ break; + authmode = in_ie[cnt]; + + if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && ++ in_ie[cnt + 1] >= 4 && + (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) { + if (wpa_ie) + memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); +@@ -707,6 +717,9 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp + if (len_attr) + *len_attr = 0; + ++ if (wps_ielen < 6) ++ return attr_ptr; ++ + if ((wps_ie[0] != WLAN_EID_VENDOR_SPECIFIC) || + (memcmp(wps_ie + 2, wps_oui, 4))) { + return attr_ptr; +@@ -717,6 +730,8 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp + + while (attr_ptr - wps_ie < wps_ielen) { + /* 4 = 2(Attribute ID) + 2(Length) */ ++ if (attr_ptr + 4 > wps_ie + wps_ielen) ++ break; + u16 attr_id = get_unaligned_be16(attr_ptr); + u16 attr_data_len = get_unaligned_be16(attr_ptr + 2); + u16 attr_len = attr_data_len + 4; diff --git a/queue-6.12/staging-rtl8723bs-fix-spaces-around-binary-operators.patch b/queue-6.12/staging-rtl8723bs-fix-spaces-around-binary-operators.patch new file mode 100644 index 0000000000..97ca07abd2 --- /dev/null +++ b/queue-6.12/staging-rtl8723bs-fix-spaces-around-binary-operators.patch @@ -0,0 +1,206 @@ +From stable+bounces-274644-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:07 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:17:59 -0400 +Subject: staging: rtl8723bs: fix spaces around binary operators +To: stable@vger.kernel.org +Cc: Nikolay Kulikov , Ethan Tidmore , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260715001800.3784291-2-sashal@kernel.org> + +From: Nikolay Kulikov + +[ Upstream commit 0c9d1b56f9af0762a5be5118e1bb962f23784dc4 ] + +Add missing spaces and fix line length to comply with kernel coding +style. + +Signed-off-by: Nikolay Kulikov +Reviewed-by: Ethan Tidmore +Link: https://patch.msgid.link/20260221172751.52329-1-nikolayof23@gmail.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: 1463ca3ec660 ("staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 78 +++++++++++++------------ + 1 file changed, 42 insertions(+), 36 deletions(-) + +--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c ++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +@@ -456,10 +456,10 @@ int rtw_parse_wpa_ie(u8 *wpa_ie, int wpa + return _FAIL; + } + +- if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) || (*(wpa_ie+1) != (u8)(wpa_ie_len - 2)) || +- (memcmp(wpa_ie+2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN))) { ++ if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) || ++ (*(wpa_ie + 1) != (u8)(wpa_ie_len - 2)) || ++ (memcmp(wpa_ie + 2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN))) + return _FAIL; +- } + + pos = wpa_ie; + +@@ -519,7 +519,7 @@ int rtw_parse_wpa2_ie(u8 *rsn_ie, int rs + return _FAIL; + } + +- if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie+1) != (u8)(rsn_ie_len - 2))) ++ if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie + 1) != (u8)(rsn_ie_len - 2))) + return _FAIL; + + pos = rsn_ie; +@@ -587,18 +587,18 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l + while (cnt < in_len) { + authmode = in_ie[cnt]; + +- /* if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY) */ +- if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && (!memcmp(&in_ie[cnt+6], wapi_oui1, 4) || +- !memcmp(&in_ie[cnt+6], wapi_oui2, 4))) { ++ if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && ++ (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) || ++ !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) { + if (wapi_ie) +- memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt+1]+2); ++ memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + + if (wapi_len) +- *wapi_len = in_ie[cnt+1]+2; ++ *wapi_len = in_ie[cnt + 1] + 2; + +- cnt += in_ie[cnt+1]+2; /* get next */ ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } else { +- cnt += in_ie[cnt+1]+2; /* get next */ ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } + } + +@@ -622,9 +622,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l + while (cnt < in_len) { + authmode = in_ie[cnt]; + +- if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], &wpa_oui[0], 4))) { ++ if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && ++ (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) { + if (wpa_ie) +- memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt+1]+2); ++ memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + + *wpa_len = in_ie[cnt + 1] + 2; + cnt += in_ie[cnt + 1] + 2; /* get next */ +@@ -633,10 +634,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l + if (rsn_ie) + memcpy(rsn_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + +- *rsn_len = in_ie[cnt+1]+2; +- cnt += in_ie[cnt+1]+2; /* get next */ ++ *rsn_len = in_ie[cnt + 1] + 2; ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } else { +- cnt += in_ie[cnt+1]+2; /* get next */ ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } + } + } +@@ -668,20 +669,20 @@ u8 *rtw_get_wps_ie(u8 *in_ie, uint in_le + while (cnt < in_len) { + eid = in_ie[cnt]; + +- if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], wps_oui, 4))) { ++ if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt + 2], wps_oui, 4))) { + wpsie_ptr = &in_ie[cnt]; + + if (wps_ie) +- memcpy(wps_ie, &in_ie[cnt], in_ie[cnt+1]+2); ++ memcpy(wps_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + + if (wps_ielen) +- *wps_ielen = in_ie[cnt+1]+2; ++ *wps_ielen = in_ie[cnt + 1] + 2; + +- cnt += in_ie[cnt+1]+2; ++ cnt += in_ie[cnt + 1] + 2; + + break; + } +- cnt += in_ie[cnt+1]+2; /* goto next */ ++ cnt += in_ie[cnt + 1] + 2; /* goto next */ + } + + return wpsie_ptr; +@@ -759,12 +760,12 @@ u8 *rtw_get_wps_attr_content(u8 *wps_ie, + + if (attr_ptr && attr_len) { + if (buf_content) +- memcpy(buf_content, attr_ptr+4, attr_len-4); ++ memcpy(buf_content, attr_ptr + 4, attr_len - 4); + + if (len_content) +- *len_content = attr_len-4; ++ *len_content = attr_len - 4; + +- return attr_ptr+4; ++ return attr_ptr + 4; + } + + return NULL; +@@ -1010,20 +1011,25 @@ static int rtw_get_cipher_info(struct wl + int group_cipher = 0, pairwise_cipher = 0, is8021x = 0; + int ret = _FAIL; + +- pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12); ++ pbuf = rtw_get_wpa_ie(&pnetwork->network.ies[12], ++ &wpa_ielen, ++ pnetwork->network.ie_length - 12); + + if (pbuf && (wpa_ielen > 0)) { +- if (rtw_parse_wpa_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) { ++ if (rtw_parse_wpa_ie(pbuf, wpa_ielen + 2, &group_cipher, ++ &pairwise_cipher, &is8021x) == _SUCCESS) { + pnetwork->bcn_info.pairwise_cipher = pairwise_cipher; + pnetwork->bcn_info.group_cipher = group_cipher; + pnetwork->bcn_info.is_8021x = is8021x; + ret = _SUCCESS; + } + } else { +- pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, pnetwork->network.ie_length-12); ++ pbuf = rtw_get_wpa2_ie(&pnetwork->network.ies[12], &wpa_ielen, ++ pnetwork->network.ie_length - 12); + + if (pbuf && (wpa_ielen > 0)) { +- if (rtw_parse_wpa2_ie(pbuf, wpa_ielen+2, &group_cipher, &pairwise_cipher, &is8021x) == _SUCCESS) { ++ if (rtw_parse_wpa2_ie(pbuf, wpa_ielen + 2, &group_cipher, ++ &pairwise_cipher, &is8021x) == _SUCCESS) { + pnetwork->bcn_info.pairwise_cipher = pairwise_cipher; + pnetwork->bcn_info.group_cipher = group_cipher; + pnetwork->bcn_info.is_8021x = is8021x; +@@ -1092,21 +1098,21 @@ u16 rtw_mcs_rate(u8 bw_40MHz, u8 short_G + u16 max_rate = 0; + + if (MCS_rate[0] & BIT(7)) +- max_rate = (bw_40MHz) ? ((short_GI)?1500:1350):((short_GI)?722:650); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 1500 : 1350) : ((short_GI) ? 722 : 650); + else if (MCS_rate[0] & BIT(6)) +- max_rate = (bw_40MHz) ? ((short_GI)?1350:1215):((short_GI)?650:585); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 1350 : 1215) : ((short_GI) ? 650 : 585); + else if (MCS_rate[0] & BIT(5)) +- max_rate = (bw_40MHz) ? ((short_GI)?1200:1080):((short_GI)?578:520); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 1200 : 1080) : ((short_GI) ? 578 : 520); + else if (MCS_rate[0] & BIT(4)) +- max_rate = (bw_40MHz) ? ((short_GI)?900:810):((short_GI)?433:390); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 900 : 810) : ((short_GI) ? 433 : 390); + else if (MCS_rate[0] & BIT(3)) +- max_rate = (bw_40MHz) ? ((short_GI)?600:540):((short_GI)?289:260); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 600 : 540) : ((short_GI) ? 289 : 260); + else if (MCS_rate[0] & BIT(2)) +- max_rate = (bw_40MHz) ? ((short_GI)?450:405):((short_GI)?217:195); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 450 : 405) : ((short_GI) ? 217 : 195); + else if (MCS_rate[0] & BIT(1)) +- max_rate = (bw_40MHz) ? ((short_GI)?300:270):((short_GI)?144:130); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 300 : 270) : ((short_GI) ? 144 : 130); + else if (MCS_rate[0] & BIT(0)) +- max_rate = (bw_40MHz) ? ((short_GI)?150:135):((short_GI)?72:65); ++ max_rate = (bw_40MHz) ? ((short_GI) ? 150 : 135) : ((short_GI) ? 72 : 65); + + return max_rate; + } diff --git a/queue-6.12/treewide-switch-rename-to-timer_delete.patch b/queue-6.12/treewide-switch-rename-to-timer_delete.patch new file mode 100644 index 0000000000..9f423229d8 --- /dev/null +++ b/queue-6.12/treewide-switch-rename-to-timer_delete.patch @@ -0,0 +1,37 @@ +From stable+bounces-277091-greg=kroah.com@vger.kernel.org Fri Jul 17 17:03:21 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:55:01 -0400 +Subject: treewide: Switch/rename to timer_delete[_sync]() +To: stable@vger.kernel.org +Cc: Thomas Gleixner , Ingo Molnar , Sasha Levin +Message-ID: <20260717145502.1925738-1-sashal@kernel.org> + +From: Thomas Gleixner + +[ Upstream commit 8fa7292fee5c5240402371ea89ab285ec856c916 ] + +timer_delete[_sync]() replaces del_timer[_sync](). Convert the whole tree +over and remove the historical wrapper inlines. + +Conversion was done with coccinelle plus manual fixups where necessary. + +Signed-off-by: Thomas Gleixner +Signed-off-by: Ingo Molnar +Stable-dep-of: 75fe87e19d8a ("HID: appleir: fix UAF on pending key_up_timer in remove()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/hid/hid-appleir.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/hid/hid-appleir.c ++++ b/drivers/hid/hid-appleir.c +@@ -319,7 +319,7 @@ static void appleir_remove(struct hid_de + { + struct appleir *appleir = hid_get_drvdata(hid); + hid_hw_stop(hid); +- del_timer_sync(&appleir->key_up_timer); ++ timer_delete_sync(&appleir->key_up_timer); + } + + static const struct hid_device_id appleir_devices[] = { diff --git a/queue-6.12/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch b/queue-6.12/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch new file mode 100644 index 0000000000..4e35dcea50 --- /dev/null +++ b/queue-6.12/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch @@ -0,0 +1,175 @@ +From stable+bounces-277505-greg=kroah.com@vger.kernel.org Sun Jul 19 18:41:12 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 12:41:05 -0400 +Subject: usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect() +To: stable@vger.kernel.org +Cc: Mauricio Faria de Oliveira , stable , syzbot+ce1e5a1b4e086b43e56d@syzkaller.appspotmail.com, syzbot+306212936b13e520679d@syzkaller.appspotmail.com, syzbot+457452d30bcdda75ead2@syzkaller.appspotmail.com, Andrey Tsygunka , Stanislaw Gruszka , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260719164105.663963-1-sashal@kernel.org> + +From: Mauricio Faria de Oliveira + +[ Upstream commit e2674dfbed8a30d57e2bc872c4bfa6c3eec918bf ] + +ueagle-atm uses the asynchronous request_firmware_nowait() in .probe(), +but does not wait for its completion, not even in .disconnect(); so, if the +device is unplugged meanwhile, its teardown runs concurrently with that. + +Even though this inconsistency is worth addressing on its own, it has also +triggered several bug reports in syzbot over the years (some auto-closed) +where the firmware sysfs fallback mechanism (CONFIG_FW_LOADER_USER_HELPER) +creates a firmware subdirectory in the device directory during its removal, +which might hit unexpected conditions in kernfs, apparently, depending at +which point the add and remove operations raced. (See links.) + +The pattern is: + +usb ?-?: Direct firmware load for ueagle-atm/eagle?.fw failed with error -2 +usb ?-?: Falling back to sysfs fallback for: ueagle-atm/eagle?.fw + +Call trace: + ... + kernfs_create_dir_ns + sysfs_create_dir_ns + create_dir + kobject_add_internal + kobject_add_varg + kobject_add + class_dir_create_and_add + get_device_parent + device_add + fw_load_sysfs_fallback + fw_load_from_user_helper + firmware_fallback_sysfs + _request_firmware + request_firmware_work_func + ... + +(Some variations are observed, after fw_load_sysfs_fallback(), e.g., [1].) + +While the kernfs side is being looked at, the ueagle-atm side can be fixed +by waiting for the pre-firmware load in the .disconnect() handler. + +This change has a similar approach to previous work by Andrey Tsygunka [2] +(wait_for_completion() in .disconnect()), but it is relatively different in +design/implementation; using the Originally-by tag for credit assignment. + +This has been tested with: +- synthetic reproducer to check the error path; +- USB gadget (virtual device) to check the firmware upload path; +- QEMU device emulator to check the device ID re-enumeration path; +(The latter two were written by Claude; no other code/text in this commit.) + +Links (year first reported): + 2025 https://syzbot.org/bug?extid=ce1e5a1b4e086b43e56d + 2025 https://syzbot.org/bug?extid=9af8471255ac36e34fd4 + 2024 https://syzbot.org/bug?extid=306212936b13e520679d + 2023 https://syzkaller.appspot.com/bug?extid=457452d30bcdda75ead2 + 2022 https://syzbot.org/bug?extid=782984d6f1701b526edb + 2021 https://syzbot.org/bug?id=f3f221579f4ef7e9691281f3c6f56c05f83e8490 + 2021 https://syzbot.org/bug?id=84d86f0d71394829df6fc53daf6642c045983881 + 2021 https://syzbot.org/bug?id=3302dc1c0e2b9c94f2e8edb404eabc9267bc6f90 + +[1] https://syzkaller.appspot.com/bug?extid=457452d30bcdda75ead2 +[2] https://lore.kernel.org/lkml/20250410093146.3776801-2-aitsygunka@yandex.ru/ + +Cc: stable +Reported-by: syzbot+ce1e5a1b4e086b43e56d@syzkaller.appspotmail.com +Closes: https://syzbot.org/bug?extid=ce1e5a1b4e086b43e56d +Reported-by: syzbot+306212936b13e520679d@syzkaller.appspotmail.com +Closes: https://syzbot.org/bug?extid=306212936b13e520679d +Reported-by: syzbot+457452d30bcdda75ead2@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=457452d30bcdda75ead2 +Originally-by: Andrey Tsygunka +Fixes: b72458a80c75 ("[PATCH] USB: Eagle and ADI 930 usb adsl modem driver") +Assisted-by: Claude:claude-opus-4.7 # usb gadget & qemu device for testing +Signed-off-by: Mauricio Faria de Oliveira +Acked-by: Stanislaw Gruszka +Link: https://patch.msgid.link/20260526-ueagle-atm_req-fw-sync-v3-1-93c01961daaf@igalia.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/atm/ueagle-atm.c | 36 +++++++++++++++++++++++++++++++----- + 1 file changed, 31 insertions(+), 5 deletions(-) + +--- a/drivers/usb/atm/ueagle-atm.c ++++ b/drivers/usb/atm/ueagle-atm.c +@@ -599,7 +599,9 @@ static int uea_send_modem_cmd(struct usb + static void uea_upload_pre_firmware(const struct firmware *fw_entry, + void *context) + { +- struct usb_device *usb = context; ++ struct usb_interface *intf = context; ++ struct usb_device *usb = interface_to_usbdev(intf); ++ struct completion *fw_done = usb_get_intfdata(intf); + const u8 *pfw; + u8 value; + u32 crc = 0; +@@ -670,15 +672,17 @@ err_fw_corrupted: + err: + release_firmware(fw_entry); + uea_leaves(usb); ++ complete(fw_done); + } + + /* + * uea_load_firmware - Load usb firmware for pre-firmware devices. + */ +-static int uea_load_firmware(struct usb_device *usb, unsigned int ver) ++static int uea_load_firmware(struct usb_interface *intf, unsigned int ver) + { + int ret; + char *fw_name = EAGLE_FIRMWARE; ++ struct usb_device *usb = interface_to_usbdev(intf); + + uea_enters(usb); + uea_info(usb, "pre-firmware device, uploading firmware\n"); +@@ -702,7 +706,7 @@ static int uea_load_firmware(struct usb_ + } + + ret = request_firmware_nowait(THIS_MODULE, 1, fw_name, &usb->dev, +- GFP_KERNEL, usb, ++ GFP_KERNEL, intf, + uea_upload_pre_firmware); + if (ret) + uea_err(usb, "firmware %s is not available\n", fw_name); +@@ -2597,8 +2601,23 @@ static int uea_probe(struct usb_interfac + + usb_reset_device(usb); + +- if (UEA_IS_PREFIRM(id)) +- return uea_load_firmware(usb, UEA_CHIP_VERSION(id)); ++ if (UEA_IS_PREFIRM(id)) { ++ struct completion *fw_done; ++ ++ /* Wait for the firmware load to be done, in .disconnect() */ ++ fw_done = kzalloc(sizeof(*fw_done), GFP_KERNEL); ++ if (!fw_done) ++ return -ENOMEM; ++ ++ init_completion(fw_done); ++ usb_set_intfdata(intf, fw_done); ++ ++ ret = uea_load_firmware(intf, UEA_CHIP_VERSION(id)); ++ if (ret) ++ kfree(fw_done); ++ ++ return ret; ++ } + + ret = usbatm_usb_probe(intf, id, &uea_usbatm_driver); + if (ret == 0) { +@@ -2629,6 +2648,13 @@ static void uea_disconnect(struct usb_in + usbatm_usb_disconnect(intf); + mutex_unlock(&uea_mutex); + uea_info(usb, "ADSL device removed\n"); ++ } else if (usb->config->desc.bNumInterfaces == 1) { ++ struct completion *fw_done = usb_get_intfdata(intf); ++ ++ uea_dbg(usb, "pre-firmware device, waiting firmware upload\n"); ++ wait_for_completion(fw_done); ++ uea_dbg(usb, "pre-firmware device, finished waiting\n"); ++ kfree(fw_done); + } + + uea_leaves(usb); diff --git a/queue-6.12/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch b/queue-6.12/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch new file mode 100644 index 0000000000..b99029c4aa --- /dev/null +++ b/queue-6.12/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch @@ -0,0 +1,102 @@ +From sashal@kernel.org Mon Jul 20 12:55:29 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 06:55:26 -0400 +Subject: usb: gadget: f_fs: initialize reset_work at allocation time +To: stable@vger.kernel.org +Cc: "Tyler Baker" , stable , "Loic Poulain" , "Dmitry Baryshkov" , "Srinivas Kandagatla" , "Peter Chen" , "Michał Nazarewicz" , "Greg Kroah-Hartman" , "Sasha Levin" +Message-ID: <20260720105526.221348-1-sashal@kernel.org> + +From: Tyler Baker + +[ Upstream commit 3137b243c93982fe3460335e12f9247739766e10 ] + +ffs_fs_kill_sb() unconditionally calls cancel_work_sync() on +ffs->reset_work when a functionfs instance is unmounted: + + ffs_data_reset(ffs); + cancel_work_sync(&ffs->reset_work); + +However ffs->reset_work is only ever initialized via INIT_WORK() in +ffs_func_set_alt() and ffs_func_disable(), and only on the +FFS_DEACTIVATED path. That state is reached solely by ffs_data_closed() +when the instance is mounted with the "no_disconnect" option, so for the +common case (no "no_disconnect", or mounted and unmounted without ever +being deactivated) reset_work is never initialized. + +ffs_data_new() allocates the ffs_data with kzalloc_obj() and does not +initialize reset_work, and ffs_data_reset()/ffs_data_clear() do not touch +it either, so reset_work.func is left NULL. cancel_work_sync() on such a +work then trips the WARN_ON(!work->func) guard in __flush_work(): + + WARNING: kernel/workqueue.c:4301 at __flush_work+0x330/0x360, CPU#3: umount + Call trace: + __flush_work + cancel_work_sync + ffs_fs_kill_sb [usb_f_fs] + deactivate_locked_super + deactivate_super + cleanup_mnt + __cleanup_mnt + task_work_run + exit_to_user_mode_loop + el0_svc + +On older kernels cancel_work_sync() on a zero-initialized work struct was +a silent no-op, which hid the missing initialization. + +Initialize reset_work once in ffs_data_new() so it is always valid for +the lifetime of the ffs_data, and drop the now-redundant INIT_WORK() +calls from the two deactivation paths. + +Fixes: 18d6b32fca38 ("usb: gadget: f_fs: add "no_disconnect" mode") +Cc: stable +Signed-off-by: Tyler Baker +Cc: Loic Poulain +Cc: Dmitry Baryshkov +Cc: Srinivas Kandagatla +Tested-by: Loic Poulain +Reviewed-by: Peter Chen +Acked-by: Michał Nazarewicz +Link: https://patch.msgid.link/20260609193635.2284430-1-tyler.baker@oss.qualcomm.com +Signed-off-by: Greg Kroah-Hartman +[ adjusted context around the removed INIT_WORK() lines since 6.12 lacks the spin_unlock_irqrestore(&ffs->eps_lock) calls from 2005aabe94eaab ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -291,6 +291,7 @@ static int ffs_acquire_dev(const char *d + static void ffs_release_dev(struct ffs_dev *ffs_dev); + static int ffs_ready(struct ffs_data *ffs); + static void ffs_closed(struct ffs_data *ffs); ++static void ffs_reset_work(struct work_struct *work); + + /* Misc helper functions ****************************************************/ + +@@ -2224,6 +2225,7 @@ static struct ffs_data *ffs_data_new(con + init_waitqueue_head(&ffs->ev.waitq); + init_waitqueue_head(&ffs->wait); + init_completion(&ffs->ep0req_completion); ++ INIT_WORK(&ffs->reset_work, ffs_reset_work); + + /* XXX REVISIT need to update it in some places, or do we? */ + ffs->ev.can_stall = 1; +@@ -3770,7 +3772,6 @@ static int ffs_func_set_alt(struct usb_f + + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; +- INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return -ENODEV; + } +@@ -3797,7 +3798,6 @@ static void ffs_func_disable(struct usb_ + + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; +- INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return; + } diff --git a/queue-6.12/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch b/queue-6.12/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch new file mode 100644 index 0000000000..74d7506a4f --- /dev/null +++ b/queue-6.12/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch @@ -0,0 +1,55 @@ +From sashal@kernel.org Mon Jul 20 16:10:29 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 10:10:24 -0400 +Subject: usb: gadget: f_fs: Tie read_buffer lifetime to ffs_epfile +To: stable@vger.kernel.org +Cc: Neill Kapron , stable , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260720141024.1497393-1-sashal@kernel.org> + +From: Neill Kapron + +[ Upstream commit 8bdcf96eb135aebacac319667f87db034fb38406 ] + +Currently, ffs_epfile_release unconditionally frees the endpoint's +read_buffer when a file descriptor is closed. If userspace explicitly +opens the endpoint multiple times and closes one, the read_buffer is +destroyed. This can lead to silent data loss if other file descriptors +are still actively reading from the endpoint. + +By tying the lifetime of the read_buffer to the ffs_epfile structure itself +(which is destroyed when the functionfs instance is torn down in +ffs_epfiles_destroy), we eliminate the brittle dependency on open/release +calls while correctly matching the conceptual lifetime of unread data on +the hardware endpoint. + +Fixes: 9353afbbfa7b ("usb: gadget: f_fs: buffer data from ‘oversized’ OUT requests") +Cc: stable +Assisted-by: Antigravity:gemini-3.1-pro +Signed-off-by: Neill Kapron +Link: https://patch.msgid.link/20260619040609.4010746-3-nkapron@google.com +Signed-off-by: Greg Kroah-Hartman +[ adjusted context in ffs_epfiles_destroy() since 6.12 lacks the simple_remove_by_name() rework and still uses dentry-based cleanup ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -1365,7 +1365,6 @@ ffs_epfile_release(struct inode *inode, + + mutex_unlock(&epfile->dmabufs_mutex); + +- __ffs_epfile_read_buffer_free(epfile); + ffs_data_closed(epfile->ffs); + + return 0; +@@ -2386,6 +2385,7 @@ static void ffs_epfiles_destroy(struct f + + for (; count; --count, ++epfile) { + BUG_ON(mutex_is_locked(&epfile->mutex)); ++ __ffs_epfile_read_buffer_free(epfile); + if (epfile->dentry) { + d_delete(epfile->dentry); + dput(epfile->dentry); diff --git a/queue-6.12/usb-iowarrior-fix-use-after-free-on-disconnect-race.patch b/queue-6.12/usb-iowarrior-fix-use-after-free-on-disconnect-race.patch new file mode 100644 index 0000000000..8eac20a59a --- /dev/null +++ b/queue-6.12/usb-iowarrior-fix-use-after-free-on-disconnect-race.patch @@ -0,0 +1,159 @@ +From sashal@kernel.org Sun Jul 19 17:01:51 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 11:01:47 -0400 +Subject: USB: iowarrior: fix use-after-free on disconnect race +To: stable@vger.kernel.org +Cc: Johan Hovold , stable , Yue Sun , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260719150147.60929-2-sashal@kernel.org> + +From: Johan Hovold + +[ Upstream commit c602254ba4c10f60a73cd99d147874f86a3f485c ] + +mutex_unlock() may access the mutex structure after releasing the lock +and therefore cannot be used to manage lifetime of objects directly +(unlike spinlocks and refcounts). [1][2] + +Use a kref to release the driver data to avoid use-after-free in +mutex_unlock() when release() races with disconnect(). + +[1] a51749ab34d9 ("locking/mutex: Document that mutex_unlock() is non-atomic") +[2] 2b9d9e0a9ba0 ("locking/mutex: Clarify that mutex_unlock(), and most + other sleeping locks, can still use the lock object + after it's unlocked") + +Fixes: 946b960d13c1 ("USB: add driver for iowarrior devices.") +Cc: stable +Reported-by: Yue Sun +Link: https://lore.kernel.org/r/20260618080204.38322-1-samsun1006219@gmail.com +Signed-off-by: Johan Hovold +Link: https://patch.msgid.link/20260622152612.116422-2-johan@kernel.org +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/misc/iowarrior.c | 57 ++++++++++++++++++------------------------- + 1 file changed, 24 insertions(+), 33 deletions(-) + +--- a/drivers/usb/misc/iowarrior.c ++++ b/drivers/usb/misc/iowarrior.c +@@ -71,6 +71,7 @@ static struct usb_driver iowarrior_drive + + /* Structure to hold all of our device specific stuff */ + struct iowarrior { ++ struct kref kref; + struct mutex mutex; /* locks this structure */ + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; /* the interface for this device */ +@@ -243,8 +244,10 @@ static void iowarrior_write_callback(str + /* + * iowarrior_delete + */ +-static inline void iowarrior_delete(struct iowarrior *dev) ++static inline void iowarrior_delete(struct kref *kref) + { ++ struct iowarrior *dev = container_of(kref, struct iowarrior, kref); ++ + kfree(dev->int_in_buffer); + usb_free_urb(dev->int_in_urb); + kfree(dev->read_queue); +@@ -640,6 +643,9 @@ static int iowarrior_open(struct inode * + } + /* increment our usage count for the driver */ + ++dev->opened; ++ ++ kref_get(&dev->kref); ++ + /* save our object in the file's private structure */ + file->private_data = dev; + retval = 0; +@@ -655,7 +661,6 @@ out: + static int iowarrior_release(struct inode *inode, struct file *file) + { + struct iowarrior *dev; +- int retval = 0; + + dev = file->private_data; + if (!dev) +@@ -663,29 +668,18 @@ static int iowarrior_release(struct inod + + /* lock our device */ + mutex_lock(&dev->mutex); ++ dev->opened = 0; /* we're closing now */ + +- if (dev->opened <= 0) { +- retval = -ENODEV; /* close called more than once */ +- mutex_unlock(&dev->mutex); +- } else { +- dev->opened = 0; /* we're closing now */ +- retval = 0; +- if (dev->present) { +- /* +- The device is still connected so we only shutdown +- pending read-/write-ops. +- */ +- usb_kill_urb(dev->int_in_urb); +- wake_up_interruptible(&dev->read_wait); +- wake_up_interruptible(&dev->write_wait); +- mutex_unlock(&dev->mutex); +- } else { +- /* The device was unplugged, cleanup resources */ +- mutex_unlock(&dev->mutex); +- iowarrior_delete(dev); +- } ++ if (dev->present) { ++ usb_kill_urb(dev->int_in_urb); ++ wake_up_interruptible(&dev->read_wait); ++ wake_up_interruptible(&dev->write_wait); + } +- return retval; ++ mutex_unlock(&dev->mutex); ++ ++ kref_put(&dev->kref, iowarrior_delete); ++ ++ return 0; + } + + static __poll_t iowarrior_poll(struct file *file, poll_table * wait) +@@ -770,6 +764,7 @@ static int iowarrior_probe(struct usb_in + if (!dev) + return retval; + ++ kref_init(&dev->kref); + mutex_init(&dev->mutex); + + atomic_set(&dev->intr_idx, 0); +@@ -888,7 +883,8 @@ static int iowarrior_probe(struct usb_in + return retval; + + error: +- iowarrior_delete(dev); ++ kref_put(&dev->kref, iowarrior_delete); ++ + return retval; + } + +@@ -912,19 +908,14 @@ static void iowarrior_disconnect(struct + usb_kill_anchored_urbs(&dev->submitted); + + if (dev->opened) { +- /* There is a process that holds a filedescriptor to the device , +- so we only shutdown read-/write-ops going on. +- Deleting the device is postponed until close() was called. +- */ + usb_kill_urb(dev->int_in_urb); + wake_up_interruptible(&dev->read_wait); + wake_up_interruptible(&dev->write_wait); +- mutex_unlock(&dev->mutex); +- } else { +- /* no process is using the device, cleanup now */ +- mutex_unlock(&dev->mutex); +- iowarrior_delete(dev); + } ++ ++ mutex_unlock(&dev->mutex); ++ ++ kref_put(&dev->kref, iowarrior_delete); + } + + /* usb specific object needed to register this driver with the usb subsystem */ diff --git a/queue-6.12/usb-iowarrior-remove-inherent-race-with-minor-number.patch b/queue-6.12/usb-iowarrior-remove-inherent-race-with-minor-number.patch new file mode 100644 index 0000000000..b5be5cc04f --- /dev/null +++ b/queue-6.12/usb-iowarrior-remove-inherent-race-with-minor-number.patch @@ -0,0 +1,109 @@ +From sashal@kernel.org Sun Jul 19 17:01:50 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 11:01:46 -0400 +Subject: usb: iowarrior: remove inherent race with minor number +To: stable@vger.kernel.org +Cc: Oliver Neukum , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260719150147.60929-1-sashal@kernel.org> + +From: Oliver Neukum + +[ Upstream commit 56dd29088c9d9510c48a8ebad2465248fde36551 ] + +The driver saves the minor number it gets upon registration +in its descriptor for debugging purposes. However, there is +inevitably a window between registration and saving the correct +minor in a descriptor. During this window the debugging output +will be wrong. +As wrong debug output is worse than no debug output, just +remove it. + +Signed-off-by: Oliver Neukum +Link: https://patch.msgid.link/20260312094619.1590556-1-oneukum@suse.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: c602254ba4c1 ("USB: iowarrior: fix use-after-free on disconnect race") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/misc/iowarrior.c | 17 +++-------------- + 1 file changed, 3 insertions(+), 14 deletions(-) + +--- a/drivers/usb/misc/iowarrior.c ++++ b/drivers/usb/misc/iowarrior.c +@@ -74,7 +74,6 @@ struct iowarrior { + struct mutex mutex; /* locks this structure */ + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; /* the interface for this device */ +- unsigned char minor; /* the starting minor number for this device */ + struct usb_endpoint_descriptor *int_out_endpoint; /* endpoint for reading (needed for IOW56 only) */ + struct usb_endpoint_descriptor *int_in_endpoint; /* endpoint for reading */ + struct urb *int_in_urb; /* the urb for reading data */ +@@ -246,7 +245,6 @@ static void iowarrior_write_callback(str + */ + static inline void iowarrior_delete(struct iowarrior *dev) + { +- dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor); + kfree(dev->int_in_buffer); + usb_free_urb(dev->int_in_urb); + kfree(dev->read_queue); +@@ -297,9 +295,6 @@ static ssize_t iowarrior_read(struct fil + goto exit; + } + +- dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n", +- dev->minor, count); +- + /* read count must be packet size (+ time stamp) */ + if ((count != dev->report_size) + && (count != (dev->report_size + 1))) { +@@ -379,8 +374,6 @@ static ssize_t iowarrior_write(struct fi + retval = -ENODEV; + goto exit; + } +- dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n", +- dev->minor, count); + /* if count is 0 we're already done */ + if (count == 0) { + retval = 0; +@@ -523,9 +516,6 @@ static long iowarrior_ioctl(struct file + goto error_out; + } + +- dev_dbg(&dev->interface->dev, "minor %d, cmd 0x%.4x, arg %ld\n", +- dev->minor, cmd, arg); +- + retval = 0; + switch (cmd) { + case IOW_WRITE: +@@ -671,8 +661,6 @@ static int iowarrior_release(struct inod + if (!dev) + return -ENODEV; + +- dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor); +- + /* lock our device */ + mutex_lock(&dev->mutex); + +@@ -775,6 +763,7 @@ static int iowarrior_probe(struct usb_in + struct usb_host_interface *iface_desc; + int retval = -ENOMEM; + int res; ++ int minor; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct iowarrior), GFP_KERNEL); +@@ -890,12 +879,12 @@ static int iowarrior_probe(struct usb_in + goto error; + } + +- dev->minor = interface->minor; ++ minor = interface->minor; + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "IOWarrior product=0x%x, serial=%s interface=%d " + "now attached to iowarrior%d\n", dev->product_id, dev->chip_serial, +- iface_desc->desc.bInterfaceNumber, dev->minor - IOWARRIOR_MINOR_BASE); ++ iface_desc->desc.bInterfaceNumber, minor - IOWARRIOR_MINOR_BASE); + return retval; + + error: diff --git a/queue-6.12/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch b/queue-6.12/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch new file mode 100644 index 0000000000..8bd06c248d --- /dev/null +++ b/queue-6.12/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch @@ -0,0 +1,95 @@ +From stable+bounces-274247-greg=kroah.com@vger.kernel.org Tue Jul 14 15:36:10 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 09:34:43 -0400 +Subject: vfio/mlx5: Fix racy bitfields and tighten struct layout +To: stable@vger.kernel.org +Cc: Alex Williamson , Yishai Hadas , Kevin Tian , Alex Williamson , Sasha Levin +Message-ID: <20260714133443.2672245-1-sashal@kernel.org> + +From: Alex Williamson + +[ Upstream commit f2365a63b02ddea32e7db78b742c2503ec7b81f1 ] + +Bitfield operations are not atomic, they use a read-modify-write +pattern, therefore we should be careful not to pack bitfields that +can be concurrently updated into the same storage unit. + +This split takes a binary approach: flags that are only modified +pre/post open/close remain bitfields, flags modified from user +action, including actions that reach across to another device (ex. +reset) use dedicated storage units. + +Note mlx5_vhca_page_tracker.status is relocated to fill the alignment +hole this split exposes. + +Bitfield justifications: + + migrate_cap: written only in mlx5vf_cmd_set_migratable() at probe + chunk_mode: written only in mlx5vf_cmd_set_migratable() at probe + mig_state_cap: written only in mlx5vf_cmd_set_migratable() at probe + +Dedicated storage units: + + mdev_detach: written in the VF attach/detach event notifier + mlx5fv_vf_event() at runtime + log_active: written in mlx5vf_start_page_tracker()/ + mlx5vf_stop_page_tracker() during runtime dirty tracking + deferred_reset: written in mlx5vf_state_mutex_unlock()/ + mlx5vf_pci_aer_reset_done() during runtime reset handling + is_err: set by tracker error handling and dirty-log polling at runtime + object_changed: set by tracker event handling and cleared by dirty-log + polling at runtime + +Fixes: 61a2f1460fd0 ("vfio/mlx5: Manage the VF attach/detach callback from the PF") +Fixes: 79c3cf279926 ("vfio/mlx5: Init QP based resources for dirty tracking") +Fixes: f886473071d6 ("vfio/mlx5: Add support for tracker object change event") +Cc: Yishai Hadas +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Alex Williamson +Reviewed-by: Kevin Tian +Link: https://lore.kernel.org/r/20260615191241.688297-5-alex.williamson@nvidia.com +Signed-off-by: Alex Williamson +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/vfio/pci/mlx5/cmd.h | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +--- a/drivers/vfio/pci/mlx5/cmd.h ++++ b/drivers/vfio/pci/mlx5/cmd.h +@@ -157,25 +157,28 @@ struct mlx5_vhca_qp { + struct mlx5_vhca_page_tracker { + u32 id; + u32 pdn; +- u8 is_err:1; +- u8 object_changed:1; ++ /* Flags modified at runtime - dedicated storage unit */ ++ u8 is_err; ++ u8 object_changed; ++ int status; + struct mlx5_uars_page *uar; + struct mlx5_vhca_cq cq; + struct mlx5_vhca_qp *host_qp; + struct mlx5_vhca_qp *fw_qp; + struct mlx5_nb nb; +- int status; + }; + + struct mlx5vf_pci_core_device { + struct vfio_pci_core_device core_device; + int vf_id; + u16 vhca_id; ++ /* Flags only modified on setup/release - bitfield ok */ + u8 migrate_cap:1; +- u8 deferred_reset:1; +- u8 mdev_detach:1; +- u8 log_active:1; + u8 chunk_mode:1; ++ /* Flags modified at runtime - dedicated storage unit */ ++ u8 mdev_detach; ++ u8 log_active; ++ u8 deferred_reset; + struct completion tracker_comp; + /* protect migration state */ + struct mutex state_mutex; diff --git a/queue-6.12/vfs-make-last_xxx-private-to-fs-namei.c.patch b/queue-6.12/vfs-make-last_xxx-private-to-fs-namei.c.patch new file mode 100644 index 0000000000..3655f91c51 --- /dev/null +++ b/queue-6.12/vfs-make-last_xxx-private-to-fs-namei.c.patch @@ -0,0 +1,161 @@ +From stable+bounces-275243-greg=kroah.com@vger.kernel.org Thu Jul 16 13:20:57 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:20:42 -0400 +Subject: vfs: make LAST_XXX private to fs/namei.c +To: stable@vger.kernel.org +Cc: Jori Koolstra , Amir Goldstein , NeilBrown , Namjae Jeon , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260716112043.1732826-2-sashal@kernel.org> + +From: Jori Koolstra + +[ Upstream commit f2f1dddccae50f7a1d088285c53c376e26cedf67 ] + +The only user of LAST_XXX outside of fs/namei.c is fs/smb/server/vfs.c; +ksmbd_vfs_path_lookup() calls vfs_path_parent_lookup() and expects a +LAST_NORM last type (or it will be ENOENT). ksmbd_vfs_rename() also calls +vfs_path_parent_lookup() but forgets the LAST_NORM check. + +It does not really make sense to have vfs_path_parent_lookup() expose +the last_type because it is only needed to ensure it is LAST_NORM. So +let's do this check in vfs_path_parent_lookup() instead and keep the +LAST_XXX internal to fs/namei.c. This changes the ENOENT errno in +ksmbd_vfs_path_lookup() to EINVAL, which matches better with how this is +handled by callers of filename_parentat(). + +Signed-off-by: Jori Koolstra +Link: https://patch.msgid.link/20260528175854.57626-1-jkoolstra@xs4all.nl +Reviewed-by: Amir Goldstein +Reviewed-by: NeilBrown +Reviewed-by: Namjae Jeon +Signed-off-by: Christian Brauner (Amutable) +Stable-dep-of: 1c8951963d8e ("ksmbd: fix path resolution in ksmbd_vfs_kern_path_create") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/namei.c | 20 ++++++++++++++++---- + fs/smb/server/vfs.c | 15 +++------------ + include/linux/namei.h | 7 +------ + 3 files changed, 20 insertions(+), 22 deletions(-) + +--- a/fs/namei.c ++++ b/fs/namei.c +@@ -125,6 +125,11 @@ + + #define EMBEDDED_NAME_MAX (PATH_MAX - offsetof(struct filename, iname)) + ++/* ++ * Type of the last component on LOOKUP_PARENT ++ */ ++enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT}; ++ + struct filename * + getname_flags(const char __user *filename, int flags) + { +@@ -2736,15 +2741,22 @@ EXPORT_SYMBOL(kern_path); + * @flags: lookup flags + * @parent: pointer to struct path to fill + * @last: last component +- * @type: type of the last component + * @root: pointer to struct path of the base directory + */ + int vfs_path_parent_lookup(struct filename *filename, unsigned int flags, +- struct path *parent, struct qstr *last, int *type, ++ struct path *parent, struct qstr *last, + const struct path *root) + { +- return __filename_parentat(AT_FDCWD, filename, flags, parent, last, +- type, root); ++ int type; ++ int err = __filename_parentat(AT_FDCWD, filename, flags, parent, last, ++ &type, root); ++ if (err) ++ return err; ++ if (unlikely(type != LAST_NORM)) { ++ path_put(parent); ++ return -EINVAL; ++ } ++ return 0; + } + EXPORT_SYMBOL(vfs_path_parent_lookup); + +--- a/fs/smb/server/vfs.c ++++ b/fs/smb/server/vfs.c +@@ -18,7 +18,6 @@ + #include + #include + #include +-#include + + #include "glob.h" + #include "oplock.h" +@@ -73,7 +72,7 @@ static int ksmbd_vfs_path_lookup_locked( + struct qstr last; + struct filename *filename; + struct path *root_share_path = &share_conf->vfs_path; +- int err, type; ++ int err; + struct dentry *d; + + if (pathname[0] == '\0') { +@@ -88,19 +87,13 @@ static int ksmbd_vfs_path_lookup_locked( + return PTR_ERR(filename); + + err = vfs_path_parent_lookup(filename, flags, +- parent_path, &last, &type, ++ parent_path, &last, + root_share_path); + if (err) { + putname(filename); + return err; + } + +- if (unlikely(type != LAST_NORM)) { +- path_put(parent_path); +- putname(filename); +- return -ENOENT; +- } +- + err = mnt_want_write(parent_path->mnt); + if (err) { + path_put(parent_path); +@@ -708,7 +701,6 @@ int ksmbd_vfs_rename(struct ksmbd_work * + struct filename *to; + struct ksmbd_share_config *share_conf = work->tcon->share_conf; + struct ksmbd_file *parent_fp; +- int new_type; + int err, lookup_flags = LOOKUP_NO_SYMLINKS; + + if (ksmbd_override_fsids(work)) +@@ -718,8 +710,7 @@ int ksmbd_vfs_rename(struct ksmbd_work * + + retry: + err = vfs_path_parent_lookup(to, lookup_flags | LOOKUP_BENEATH, +- &new_path, &new_last, &new_type, +- &share_conf->vfs_path); ++ &new_path, &new_last, &share_conf->vfs_path); + if (err) + goto out1; + +--- a/include/linux/namei.h ++++ b/include/linux/namei.h +@@ -12,11 +12,6 @@ enum { MAX_NESTED_LINKS = 8 }; + + #define MAXSYMLINKS 40 + +-/* +- * Type of the last component on LOOKUP_PARENT +- */ +-enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT}; +- + /* pathwalk mode */ + #define LOOKUP_FOLLOW 0x0001 /* follow links at the end */ + #define LOOKUP_DIRECTORY 0x0002 /* require a directory */ +@@ -63,7 +58,7 @@ extern void done_path_create(struct path + extern struct dentry *kern_path_locked(const char *, struct path *); + extern struct dentry *user_path_locked_at(int , const char __user *, struct path *); + int vfs_path_parent_lookup(struct filename *filename, unsigned int flags, +- struct path *parent, struct qstr *last, int *type, ++ struct path *parent, struct qstr *last, + const struct path *root); + int vfs_path_lookup(struct dentry *, struct vfsmount *, const char *, + unsigned int, struct path *);