From: Greg Kroah-Hartman Date: Tue, 21 Jul 2026 13:36:05 +0000 (+0200) Subject: 6.6-stable patches X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=627e7101d23c760658c37e3250e92f48b1639e49;p=thirdparty%2Fkernel%2Fstable-queue.git 6.6-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 audit-add-audit_log_nf_skb-helper-function.patch audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch bitops-make-bytes_to_bits-treewide-available.patch bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.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 bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch coresight-etb10-restore-atomic_t-for-shared-reading-state.patch cpufreq-make-cpufreq_driver-exit-return-void.patch cpufreq-pcc-remove-empty-exit-callback.patch cpufreq-qcom-cpufreq-hw-fix-possible-double-free.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-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-move-exfat_chain_set-out-of-__exfat_resolve_path.patch exfat-move-free-cluster-out-of-exfat_init_ext_entry.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 iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch iio-invensense-remove-redundant-initialization-of-variable-period.patch iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch ksmbd-track-the-connection-owning-a-byte-range-lock.patch ksmbd-use-opener-credentials-for-fsctl-mutations.patch media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.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-do-file-ownership-checks-with-the-proper-mount-idmap.patch mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.patch netfilter-ebtables-use-vmalloc_array-to-improve-code.patch netfilter-ebtables-zero-chainstack-array.patch nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch nvmet-remove-superfluous-initialization.patch nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch pci-add-kerneldoc-for-pci_resize_resource.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-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.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-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 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 regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch seqlock-introduce-scoped_seqlock_read.patch serial-8250_mid-disable-dma-for-selected-platforms.patch serial-8250_mid-remove-8250_pci-usage.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-remove-inherent-race-with-minor-number.patch usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch --- diff --git a/queue-6.6/acpi-bus-introduce-devm_acpi_install_notify_handler.patch b/queue-6.6/acpi-bus-introduce-devm_acpi_install_notify_handler.patch new file mode 100644 index 0000000000..a52b2b8dcb --- /dev/null +++ b/queue-6.6/acpi-bus-introduce-devm_acpi_install_notify_handler.patch @@ -0,0 +1,127 @@ +From stable+bounces-273310-greg=kroah.com@vger.kernel.org Fri Jul 10 22:06:44 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 16:06:32 -0400 +Subject: ACPI: bus: Introduce devm_acpi_install_notify_handler() +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Sasha Levin +Message-ID: <20260710200635.395836-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 | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ + include/acpi/acpi_bus.h | 2 + + 2 files changed, 72 insertions(+) + +--- a/drivers/acpi/bus.c ++++ b/drivers/acpi/bus.c +@@ -578,6 +578,76 @@ 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; ++ struct acpi_device *adev; ++}; ++ ++static void devm_acpi_notify_handler_release(struct device *dev, void *res) ++{ ++ struct acpi_notify_handler_devres *dr = res; ++ ++ acpi_dev_remove_notify_handler(dr->adev, 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 ++ * ++ * This function performs the same function as acpi_dev_install_notify_handler() ++ * called for the ACPI companion of @dev with the same @handler_type and ++ * @handler 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) ++{ ++ struct acpi_notify_handler_devres *dr; ++ struct acpi_device *adev; ++ 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__); ++ ++ 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); ++ 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; ++ dr->adev = adev; ++ 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 +@@ -521,6 +521,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); + 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.6/acpi-driver-check-acpi_companion-against-null-during-probe.patch b/queue-6.6/acpi-driver-check-acpi_companion-against-null-during-probe.patch new file mode 100644 index 0000000000..0cff255837 --- /dev/null +++ b/queue-6.6/acpi-driver-check-acpi_companion-against-null-during-probe.patch @@ -0,0 +1,106 @@ +From stable+bounces-273309-greg=kroah.com@vger.kernel.org Fri Jul 10 22:06:45 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 16:06:31 -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: <20260710200635.395836-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/acpi_tad.c | 6 +++++- + drivers/acpi/pfr_telemetry.c | 6 +++++- + drivers/acpi/pfr_update.c | 6 +++++- + drivers/acpi/thermal.c | 2 +- + 4 files changed, 16 insertions(+), 4 deletions(-) + +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -588,12 +588,16 @@ static int acpi_tad_remove(struct platfo + 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 +@@ -365,10 +365,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 +@@ -507,10 +507,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 +@@ -926,7 +926,7 @@ static int acpi_thermal_add(struct acpi_ + int result; + + if (!device) +- return -EINVAL; ++ return -ENODEV; + + tz = kzalloc(sizeof(struct acpi_thermal), GFP_KERNEL); + if (!tz) diff --git a/queue-6.6/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch b/queue-6.6/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch new file mode 100644 index 0000000000..98957c903e --- /dev/null +++ b/queue-6.6/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch @@ -0,0 +1,110 @@ +From stable+bounces-273245-greg=kroah.com@vger.kernel.org Fri Jul 10 16:01:51 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 10:00:24 -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: <20260710140024.103597-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 +@@ -3067,6 +3067,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) +@@ -3307,7 +3309,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 +@@ -3322,6 +3327,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 +@@ -3399,9 +3405,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) +@@ -3488,6 +3499,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.6/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch b/queue-6.6/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch new file mode 100644 index 0000000000..5a886edc7e --- /dev/null +++ b/queue-6.6/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch @@ -0,0 +1,174 @@ +From stable+bounces-273313-greg=kroah.com@vger.kernel.org Fri Jul 10 22:07:34 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 16:06:35 -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: <20260710200635.395836-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/nfit/core.c | 59 +++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 47 insertions(+), 12 deletions(-) + +--- 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) +@@ -3156,11 +3164,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); +@@ -3293,9 +3300,15 @@ static void acpi_nfit_notify(acpi_handle + { + struct acpi_device *adev = data; + +- device_lock(&adev->dev); +- __acpi_nfit_notify(&adev->dev, handle, event); +- device_unlock(&adev->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(&adev->dev, handle, event); + } + + void acpi_nfit_shutdown(void *data) +@@ -3342,6 +3355,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); + if (rc) +@@ -3357,6 +3376,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; + } + +@@ -3391,14 +3415,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.6/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch b/queue-6.6/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch new file mode 100644 index 0000000000..fc182be0fe --- /dev/null +++ b/queue-6.6/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch @@ -0,0 +1,65 @@ +From stable+bounces-273311-greg=kroah.com@vger.kernel.org Fri Jul 10 22:06:52 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 16:06:33 -0400 +Subject: ACPI: NFIT: core: Use devm_acpi_install_notify_handler() +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Sasha Levin +Message-ID: <20260710200635.395836-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 | 17 ++--------------- + 1 file changed, 2 insertions(+), 15 deletions(-) + +--- a/drivers/acpi/nfit/core.c ++++ b/drivers/acpi/nfit/core.c +@@ -3298,14 +3298,6 @@ static void acpi_nfit_notify(acpi_handle + 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); +-} +- + void acpi_nfit_shutdown(void *data) + { + struct acpi_nfit_desc *acpi_desc = data; +@@ -3350,13 +3342,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); +- 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); + if (rc) + return rc; + diff --git a/queue-6.6/alsa-aoa-check-snd_ctl_new1-return-value.patch b/queue-6.6/alsa-aoa-check-snd_ctl_new1-return-value.patch new file mode 100644 index 0000000000..0fe683cf05 --- /dev/null +++ b/queue-6.6/alsa-aoa-check-snd_ctl_new1-return-value.patch @@ -0,0 +1,61 @@ +From stable+bounces-274012-greg=kroah.com@vger.kernel.org Mon Jul 13 22:53:49 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:53:39 -0400 +Subject: ALSA: aoa: check snd_ctl_new1() return value +To: stable@vger.kernel.org +Cc: Zhao Dongdong , Takashi Iwai , Sasha Levin +Message-ID: <20260713205339.2148895-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 +@@ -947,6 +947,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)); +@@ -961,6 +963,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", +@@ -968,6 +972,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.6/audit-add-audit_log_nf_skb-helper-function.patch b/queue-6.6/audit-add-audit_log_nf_skb-helper-function.patch new file mode 100644 index 0000000000..307cc3aed4 --- /dev/null +++ b/queue-6.6/audit-add-audit_log_nf_skb-helper-function.patch @@ -0,0 +1,296 @@ +From stable+bounces-278278-greg=kroah.com@vger.kernel.org Tue Jul 21 02:44:58 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 20:44:47 -0400 +Subject: audit: add audit_log_nf_skb helper function +To: stable@vger.kernel.org +Cc: Ricardo Robaina , Florian Westphal , Paul Moore , Sasha Levin +Message-ID: <20260721004448.3420885-1-sashal@kernel.org> + +From: Ricardo Robaina + +[ Upstream commit f19590b07cb620be1fcd5474c49515e21a05d406 ] + +Netfilter code (net/netfilter/nft_log.c and net/netfilter/xt_AUDIT.c) +have to be kept in sync. Both source files had duplicated versions of +audit_ip4() and audit_ip6() functions, which can result in lack of +consistency and/or duplicated work. + +This patch adds a helper function in audit.c that can be called by +netfilter code commonly, aiming to improve maintainability and +consistency. + +Suggested-by: Florian Westphal +Suggested-by: Paul Moore +Signed-off-by: Ricardo Robaina +Acked-by: Florian Westphal +Signed-off-by: Paul Moore +Stable-dep-of: 65dfde57d1e2 ("audit: fix potential integer overflow in audit_log_n_hex()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/audit.h | 8 +++++ + kernel/audit.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++ + net/netfilter/nft_log.c | 58 ------------------------------------------ + net/netfilter/xt_AUDIT.c | 58 ------------------------------------------ + 4 files changed, 74 insertions(+), 114 deletions(-) + +--- a/include/linux/audit.h ++++ b/include/linux/audit.h +@@ -180,6 +180,8 @@ extern void audit_log_lost(const ch + + extern int audit_log_task_context(struct audit_buffer *ab); + extern void audit_log_task_info(struct audit_buffer *ab); ++extern int audit_log_nf_skb(struct audit_buffer *ab, ++ const struct sk_buff *skb, u8 nfproto); + + extern int audit_update_lsm_rules(void); + +@@ -245,6 +247,12 @@ static inline int audit_log_task_context + static inline void audit_log_task_info(struct audit_buffer *ab) + { } + ++static inline int audit_log_nf_skb(struct audit_buffer *ab, ++ const struct sk_buff *skb, u8 nfproto) ++{ ++ return 0; ++} ++ + static inline kuid_t audit_get_loginuid(struct task_struct *tsk) + { + return INVALID_UID; +--- a/kernel/audit.c ++++ b/kernel/audit.c +@@ -57,6 +57,8 @@ + #include + #include + #include ++#include ++#include + + #include "audit.h" + +@@ -2304,6 +2306,68 @@ void audit_log_path_denied(int type, con + audit_log_end(ab); + } + ++int audit_log_nf_skb(struct audit_buffer *ab, ++ const struct sk_buff *skb, u8 nfproto) ++{ ++ /* find the IP protocol in the case of NFPROTO_BRIDGE */ ++ if (nfproto == NFPROTO_BRIDGE) { ++ switch (eth_hdr(skb)->h_proto) { ++ case htons(ETH_P_IP): ++ nfproto = NFPROTO_IPV4; ++ break; ++ case htons(ETH_P_IPV6): ++ nfproto = NFPROTO_IPV6; ++ break; ++ default: ++ goto unknown_proto; ++ } ++ } ++ ++ switch (nfproto) { ++ case NFPROTO_IPV4: { ++ struct iphdr iph; ++ const struct iphdr *ih; ++ ++ ih = skb_header_pointer(skb, skb_network_offset(skb), ++ sizeof(iph), &iph); ++ if (!ih) ++ return -ENOMEM; ++ ++ audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu", ++ &ih->saddr, &ih->daddr, ih->protocol); ++ break; ++ } ++ case NFPROTO_IPV6: { ++ struct ipv6hdr iph; ++ const struct ipv6hdr *ih; ++ u8 nexthdr; ++ __be16 frag_off; ++ ++ ih = skb_header_pointer(skb, skb_network_offset(skb), ++ sizeof(iph), &iph); ++ if (!ih) ++ return -ENOMEM; ++ ++ nexthdr = ih->nexthdr; ++ ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(iph), ++ &nexthdr, &frag_off); ++ ++ audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu", ++ &ih->saddr, &ih->daddr, nexthdr); ++ break; ++ } ++ default: ++ goto unknown_proto; ++ } ++ ++ return 0; ++ ++unknown_proto: ++ audit_log_format(ab, " saddr=? daddr=? proto=?"); ++ return -EPFNOSUPPORT; ++} ++EXPORT_SYMBOL(audit_log_nf_skb); ++ + /* global counter which is incremented every time something logs in */ + static atomic_t session_id = ATOMIC_INIT(0); + +--- a/net/netfilter/nft_log.c ++++ b/net/netfilter/nft_log.c +@@ -26,46 +26,10 @@ struct nft_log { + char *prefix; + }; + +-static bool audit_ip4(struct audit_buffer *ab, struct sk_buff *skb) +-{ +- struct iphdr _iph; +- const struct iphdr *ih; +- +- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_iph), &_iph); +- if (!ih) +- return false; +- +- audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu", +- &ih->saddr, &ih->daddr, ih->protocol); +- +- return true; +-} +- +-static bool audit_ip6(struct audit_buffer *ab, struct sk_buff *skb) +-{ +- struct ipv6hdr _ip6h; +- const struct ipv6hdr *ih; +- u8 nexthdr; +- __be16 frag_off; +- +- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h); +- if (!ih) +- return false; +- +- nexthdr = ih->nexthdr; +- ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h), &nexthdr, &frag_off); +- +- audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu", +- &ih->saddr, &ih->daddr, nexthdr); +- +- return true; +-} +- + static void nft_log_eval_audit(const struct nft_pktinfo *pkt) + { + struct sk_buff *skb = pkt->skb; + struct audit_buffer *ab; +- int fam = -1; + + if (!audit_enabled) + return; +@@ -76,27 +40,7 @@ static void nft_log_eval_audit(const str + + audit_log_format(ab, "mark=%#x", skb->mark); + +- switch (nft_pf(pkt)) { +- case NFPROTO_BRIDGE: +- switch (eth_hdr(skb)->h_proto) { +- case htons(ETH_P_IP): +- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; +- break; +- case htons(ETH_P_IPV6): +- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; +- break; +- } +- break; +- case NFPROTO_IPV4: +- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; +- break; +- case NFPROTO_IPV6: +- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; +- break; +- } +- +- if (fam == -1) +- audit_log_format(ab, " saddr=? daddr=? proto=-1"); ++ audit_log_nf_skb(ab, skb, nft_pf(pkt)); + + audit_log_end(ab); + } +--- a/net/netfilter/xt_AUDIT.c ++++ b/net/netfilter/xt_AUDIT.c +@@ -28,46 +28,10 @@ MODULE_ALIAS("ip6t_AUDIT"); + MODULE_ALIAS("ebt_AUDIT"); + MODULE_ALIAS("arpt_AUDIT"); + +-static bool audit_ip4(struct audit_buffer *ab, struct sk_buff *skb) +-{ +- struct iphdr _iph; +- const struct iphdr *ih; +- +- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_iph), &_iph); +- if (!ih) +- return false; +- +- audit_log_format(ab, " saddr=%pI4 daddr=%pI4 proto=%hhu", +- &ih->saddr, &ih->daddr, ih->protocol); +- +- return true; +-} +- +-static bool audit_ip6(struct audit_buffer *ab, struct sk_buff *skb) +-{ +- struct ipv6hdr _ip6h; +- const struct ipv6hdr *ih; +- u8 nexthdr; +- __be16 frag_off; +- +- ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h); +- if (!ih) +- return false; +- +- nexthdr = ih->nexthdr; +- ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h), &nexthdr, &frag_off); +- +- audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu", +- &ih->saddr, &ih->daddr, nexthdr); +- +- return true; +-} +- + static unsigned int + audit_tg(struct sk_buff *skb, const struct xt_action_param *par) + { + struct audit_buffer *ab; +- int fam = -1; + + if (audit_enabled == AUDIT_OFF) + goto errout; +@@ -77,27 +41,7 @@ audit_tg(struct sk_buff *skb, const stru + + audit_log_format(ab, "mark=%#x", skb->mark); + +- switch (xt_family(par)) { +- case NFPROTO_BRIDGE: +- switch (eth_hdr(skb)->h_proto) { +- case htons(ETH_P_IP): +- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; +- break; +- case htons(ETH_P_IPV6): +- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; +- break; +- } +- break; +- case NFPROTO_IPV4: +- fam = audit_ip4(ab, skb) ? NFPROTO_IPV4 : -1; +- break; +- case NFPROTO_IPV6: +- fam = audit_ip6(ab, skb) ? NFPROTO_IPV6 : -1; +- break; +- } +- +- if (fam == -1) +- audit_log_format(ab, " saddr=? daddr=? proto=-1"); ++ audit_log_nf_skb(ab, skb, xt_family(par)); + + audit_log_end(ab); + diff --git a/queue-6.6/audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch b/queue-6.6/audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch new file mode 100644 index 0000000000..72b81c12f3 --- /dev/null +++ b/queue-6.6/audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch @@ -0,0 +1,66 @@ +From stable+bounces-278279-greg=kroah.com@vger.kernel.org Tue Jul 21 02:45:01 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 20:44:48 -0400 +Subject: audit: fix potential integer overflow in audit_log_n_hex() +To: stable@vger.kernel.org +Cc: Ricardo Robaina , Richard Guy Briggs , Paul Moore , Sasha Levin +Message-ID: <20260721004448.3420885-2-sashal@kernel.org> + +From: Ricardo Robaina + +[ Upstream commit 65dfde57d1e29ce2b76fc23dd565eccd5c0bc0f0 ] + +The function calculates new_len as len << 1 for hex encoding. This +has two overflow risks: the shift itself can overflow when len is +large, and the result can be truncated when assigned to new_len +(declared as int) from the size_t calculation. + +Fix by using check_shl_overflow() to catch shift overflow and +changing new_len and loop counter i to size_t to prevent truncation. + +Cc: stable@vger.kernel.org +Fixes: 168b7173959f ("AUDIT: Clean up logging of untrusted strings") +Reviewed-by: Richard Guy Briggs +Signed-off-by: Ricardo Robaina +[PM: remove vertical whitspace noise] +Signed-off-by: Paul Moore +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/audit.c | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +--- a/kernel/audit.c ++++ b/kernel/audit.c +@@ -59,6 +59,7 @@ + #include + #include + #include ++#include + + #include "audit.h" + +@@ -2034,7 +2035,8 @@ void audit_log_format(struct audit_buffe + void audit_log_n_hex(struct audit_buffer *ab, const unsigned char *buf, + size_t len) + { +- int i, avail, new_len; ++ int avail; ++ size_t i, new_len; + unsigned char *ptr; + struct sk_buff *skb; + +@@ -2044,7 +2046,12 @@ void audit_log_n_hex(struct audit_buffer + BUG_ON(!ab->skb); + skb = ab->skb; + avail = skb_tailroom(skb); +- new_len = len<<1; ++ ++ if (check_shl_overflow(len, 1, &new_len)) { ++ audit_log_format(ab, "?"); ++ return; ++ } ++ + if (new_len >= avail) { + /* Round the buffer request up to the next multiple */ + new_len = AUDIT_BUFSIZ*(((new_len-avail)/AUDIT_BUFSIZ) + 1); diff --git a/queue-6.6/bitops-make-bytes_to_bits-treewide-available.patch b/queue-6.6/bitops-make-bytes_to_bits-treewide-available.patch new file mode 100644 index 0000000000..f5f87f6b03 --- /dev/null +++ b/queue-6.6/bitops-make-bytes_to_bits-treewide-available.patch @@ -0,0 +1,90 @@ +From stable+bounces-274000-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:35 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:23 -0400 +Subject: bitops: make BYTES_TO_BITS() treewide-available +To: stable@vger.kernel.org +Cc: Alexander Lobakin , Andy Shevchenko , Przemek Kitszel , Yury Norov , "David S. Miller" , Sasha Levin +Message-ID: <20260713203924.2143667-1-sashal@kernel.org> + +From: Alexander Lobakin + +[ Upstream commit 7d8296b250f2eed73f1758607926d4d258dea5d4 ] + +Avoid open-coding that simple expression each time by moving +BYTES_TO_BITS() from the probes code to to export +it to the rest of the kernel. +Simplify the macro while at it. `BITS_PER_LONG / sizeof(long)` always +equals to %BITS_PER_BYTE, regardless of the target architecture. +Do the same for the tools ecosystem as well (incl. its version of +bitops.h). The previous implementation had its implicit type of long, +while the new one is int, so adjust the format literal accordingly in +the perf code. + +Suggested-by: Andy Shevchenko +Reviewed-by: Przemek Kitszel +Acked-by: Yury Norov +Signed-off-by: Alexander Lobakin +Signed-off-by: David S. Miller +Stable-dep-of: 55052184ac90 ("iio: common: st_sensors: honour channel endianness in read_axis_data") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/bitops.h | 2 ++ + kernel/trace/trace_probe.c | 2 -- + tools/include/linux/bitops.h | 2 ++ + tools/perf/util/probe-finder.c | 4 +--- + 4 files changed, 5 insertions(+), 5 deletions(-) + +--- a/include/linux/bitops.h ++++ b/include/linux/bitops.h +@@ -20,6 +20,8 @@ + #define BITS_TO_U32(nr) __KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(u32)) + #define BITS_TO_BYTES(nr) __KERNEL_DIV_ROUND_UP(nr, BITS_PER_TYPE(char)) + ++#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_BYTE) ++ + extern unsigned int __sw_hweight8(unsigned int w); + extern unsigned int __sw_hweight16(unsigned int w); + extern unsigned int __sw_hweight32(unsigned int w); +--- a/kernel/trace/trace_probe.c ++++ b/kernel/trace/trace_probe.c +@@ -1220,8 +1220,6 @@ parse_probe_arg(char *arg, const struct + return ret; + } + +-#define BYTES_TO_BITS(nb) ((BITS_PER_LONG * (nb)) / sizeof(long)) +- + /* Bitfield type needs to be parsed into a fetch function */ + static int __parse_bitfield_probe_arg(const char *bf, + const struct fetch_type *t, +--- a/tools/include/linux/bitops.h ++++ b/tools/include/linux/bitops.h +@@ -20,6 +20,8 @@ + #define BITS_TO_U32(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(u32)) + #define BITS_TO_BYTES(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(char)) + ++#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_BYTE) ++ + extern unsigned int __sw_hweight8(unsigned int w); + extern unsigned int __sw_hweight16(unsigned int w); + extern unsigned int __sw_hweight32(unsigned int w); +--- a/tools/perf/util/probe-finder.c ++++ b/tools/perf/util/probe-finder.c +@@ -304,8 +304,6 @@ static_var: + return ret2; + } + +-#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_LONG / sizeof(long)) +- + static int convert_variable_type(Dwarf_Die *vr_die, + struct probe_trace_arg *tvar, + const char *cast, bool user_access) +@@ -335,7 +333,7 @@ static int convert_variable_type(Dwarf_D + total = dwarf_bytesize(vr_die); + if (boffs < 0 || total < 0) + return -ENOENT; +- ret = snprintf(buf, 16, "b%d@%d/%zd", bsize, boffs, ++ ret = snprintf(buf, 16, "b%d@%d/%d", bsize, boffs, + BYTES_TO_BITS(total)); + goto formatted; + } diff --git a/queue-6.6/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch b/queue-6.6/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch new file mode 100644 index 0000000000..7dec330ad2 --- /dev/null +++ b/queue-6.6/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch @@ -0,0 +1,101 @@ +From stable+bounces-275051-greg=kroah.com@vger.kernel.org Wed Jul 15 23:26:50 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:26:42 -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: <20260715212643.278587-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.6/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch b/queue-6.6/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch new file mode 100644 index 0000000000..de55c479fb --- /dev/null +++ b/queue-6.6/bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch @@ -0,0 +1,70 @@ +From stable+bounces-275048-greg=kroah.com@vger.kernel.org Wed Jul 15 23:18:49 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:18:42 -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: <20260715211842.270885-1-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 +[ adjusted context for 6.6 lacking the disable_delayed_work_sync() conversion and the ida_destroy(&conn->tx_ida) line from the tx_ident IDA dependency ] +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 +@@ -1760,19 +1760,13 @@ static void l2cap_conn_del(struct hci_co + + BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); + ++ 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); +- + cancel_delayed_work_sync(&conn->id_addr_timer); + + l2cap_unregister_all_users(conn); diff --git a/queue-6.6/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch b/queue-6.6/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch new file mode 100644 index 0000000000..18e53fae28 --- /dev/null +++ b/queue-6.6/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holding-conn-ref.patch @@ -0,0 +1,246 @@ +From stable+bounces-274704-greg=kroah.com@vger.kernel.org Wed Jul 15 04:52:03 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 22:51:56 -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: <20260715025156.113462-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 +@@ -742,6 +742,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)) +@@ -1888,7 +1890,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; +@@ -1909,7 +1911,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; +@@ -2524,7 +2526,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 */ +@@ -3121,12 +3123,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.6/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch b/queue-6.6/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch new file mode 100644 index 0000000000..befb0698ca --- /dev/null +++ b/queue-6.6/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch @@ -0,0 +1,542 @@ +From stable+bounces-275052-greg=kroah.com@vger.kernel.org Wed Jul 15 23:26:52 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:26:43 -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: <20260715212643.278587-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 +@@ -614,7 +614,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); +@@ -879,9 +880,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) +@@ -957,7 +959,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); + +@@ -3975,6 +4003,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) + { +@@ -4021,7 +4081,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; + +@@ -4039,8 +4099,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)); +@@ -4909,7 +4967,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; +@@ -4924,8 +4982,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; +@@ -5134,7 +5190,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; +@@ -5149,8 +5205,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 */ +@@ -7421,14 +7475,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) +@@ -1240,6 +1241,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. + */ +@@ -1250,13 +1268,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); + } +@@ -1499,12 +1513,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); + +@@ -1512,25 +1527,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) +@@ -1828,10 +1846,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); +@@ -1863,30 +1878,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) { +@@ -1912,7 +1909,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 */ +@@ -1931,10 +1928,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) +@@ -1945,16 +1942,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; +@@ -1964,6 +1952,7 @@ static int l2cap_sock_create(struct net + int kern) + { + struct sock *sk; ++ struct l2cap_chan *chan; + + BT_DBG("sock %p", sock); + +@@ -1978,10 +1967,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 +@@ -3235,34 +3235,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 = { +@@ -3333,7 +3318,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.6/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch b/queue-6.6/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch new file mode 100644 index 0000000000..39fdfb88b4 --- /dev/null +++ b/queue-6.6/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch @@ -0,0 +1,100 @@ +From stable+bounces-277352-greg=kroah.com@vger.kernel.org Sat Jul 18 18:33:23 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 12:31:07 -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: <20260718163107.3338970-2-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 | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/kernel/bpf/lpm_trie.c ++++ b/kernel/bpf/lpm_trie.c +@@ -236,7 +236,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; +@@ -270,7 +270,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) diff --git a/queue-6.6/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch b/queue-6.6/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch new file mode 100644 index 0000000000..b55828eb70 --- /dev/null +++ b/queue-6.6/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch @@ -0,0 +1,191 @@ +From stable+bounces-277351-greg=kroah.com@vger.kernel.org Sat Jul 18 18:33:20 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 12:31:06 -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: <20260718163107.3338970-1-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 ++++-------- + kernel/bpf/lpm_trie.c | 6 ++---- + 5 files changed, 16 insertions(+), 29 deletions(-) + +--- a/include/linux/bpf.h ++++ b/include/linux/bpf.h +@@ -2047,6 +2047,9 @@ bpf_prog_run_array_uprobe(const struct b + return ret; + } + ++#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 +@@ -681,8 +681,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; + +@@ -1117,8 +1116,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; + +@@ -1235,8 +1233,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; + +@@ -1304,8 +1301,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; + +@@ -1359,8 +1355,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; + +@@ -1437,8 +1432,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; + +@@ -1473,8 +1467,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 +@@ -36,8 +36,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); + } + +@@ -53,8 +52,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); + } + +@@ -71,8 +69,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); + } + +@@ -128,8 +125,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); + } + +--- a/kernel/bpf/lpm_trie.c ++++ b/kernel/bpf/lpm_trie.c +@@ -353,8 +353,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_protected(*slot, 1))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || +@@ -475,8 +474,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_protected(*trim, 1))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || diff --git a/queue-6.6/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch b/queue-6.6/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch new file mode 100644 index 0000000000..f22be06af7 --- /dev/null +++ b/queue-6.6/btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch @@ -0,0 +1,268 @@ +From stable+bounces-278182-greg=kroah.com@vger.kernel.org Mon Jul 20 20:08:54 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 14:08:44 -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: <20260720180844.2922739-1-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 | 12 +------- + fs/btrfs/inode.c | 72 +++++++++++++++++++++++++++++++++++-------------- + fs/btrfs/reflink.c | 5 --- + 4 files changed, 57 insertions(+), 34 deletions(-) + +--- a/fs/btrfs/btrfs_inode.h ++++ b/fs/btrfs/btrfs_inode.h +@@ -441,6 +441,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 +@@ -151,16 +151,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); +- +- err = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block, +- extra_bits, cached); ++ err = btrfs_reset_extent_delalloc(inode, start_pos, end_of_last_block, ++ extra_bits, cached); + if (err) + return err; + +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -2666,7 +2666,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)) { +@@ -2872,6 +2876,50 @@ int btrfs_writepage_cow_fixup(struct pag + return -EAGAIN; + } + ++/* ++ * 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, +@@ -4799,12 +4847,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; +@@ -8278,19 +8321,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/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.6/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch b/queue-6.6/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch new file mode 100644 index 0000000000..3ae231e069 --- /dev/null +++ b/queue-6.6/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch @@ -0,0 +1,186 @@ +From stable+bounces-278150-greg=kroah.com@vger.kernel.org Mon Jul 20 18:57:02 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 12:51:20 -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: <20260720165120.2527510-1-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/inode.c | 23 +++++++++++++++-------- + fs/btrfs/ordered-data.c | 12 ++++++++++++ + fs/btrfs/ordered-data.h | 2 ++ + 3 files changed, 29 insertions(+), 8 deletions(-) + +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -7797,12 +7797,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, NULL, +- pos, length, false); +- else ++ pos, length, true); ++ } else { + unlock_extent(&BTRFS_I(inode)->io_tree, pos, + pos + length - 1, NULL); ++ } + ret = -ENOTBLK; + } + if (write) { +@@ -8152,11 +8163,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 +@@ -303,6 +303,18 @@ void btrfs_add_ordered_sum(struct btrfs_ + spin_unlock_irq(&tree->lock); + } + ++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered, ++ u64 truncate_len) ++{ ++ struct btrfs_inode *inode = BTRFS_I(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 +@@ -210,6 +210,8 @@ bool btrfs_try_lock_ordered_range(struct + struct extent_state **cached_state); + struct btrfs_ordered_extent *btrfs_split_ordered_extent( + struct btrfs_ordered_extent *ordered, u64 len); ++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.6/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch b/queue-6.6/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch new file mode 100644 index 0000000000..64ca7c9e11 --- /dev/null +++ b/queue-6.6/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch @@ -0,0 +1,200 @@ +From stable+bounces-278184-greg=kroah.com@vger.kernel.org Mon Jul 20 20:08:58 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 14:08:48 -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: <20260720180848.2923096-2-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/inode.c | 43 ++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 42 insertions(+), 1 deletion(-) + +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -80,10 +80,12 @@ struct btrfs_iget_args { + + 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 { +@@ -7405,6 +7407,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; + + /* +@@ -7520,8 +7523,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); +@@ -7799,6 +7808,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.6/coresight-etb10-restore-atomic_t-for-shared-reading-state.patch b/queue-6.6/coresight-etb10-restore-atomic_t-for-shared-reading-state.patch new file mode 100644 index 0000000000..d0b7d0631b --- /dev/null +++ b/queue-6.6/coresight-etb10-restore-atomic_t-for-shared-reading-state.patch @@ -0,0 +1,123 @@ +From stable+bounces-274917-greg=kroah.com@vger.kernel.org Wed Jul 15 13:41:02 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 07:40:54 -0400 +Subject: coresight: etb10: restore atomic_t for shared reading state +To: stable@vger.kernel.org +Cc: Runyu Xiao , James Clark , Suzuki K Poulose , Sasha Levin +Message-ID: <20260715114054.728724-1-sashal@kernel.org> + +From: Runyu Xiao + +[ Upstream commit fa09f08ede3db3050ae16ae1ed92c902d0cada23 ] + +The etb10 miscdevice uses drvdata->reading as a shared exclusivity gate +for userspace buffer access. etb_open() claims that gate with +local_cmpxchg(), and etb_release() clears it with local_set(). + +That gate is shared per-device state rather than CPU-local state. A +running system can reach it whenever /dev/ is opened, closed, and +reopened by different tasks while the device remains registered, so the +same drvdata->reading variable may be claimed on one CPU and later +cleared on another. + +This code used to use atomic_t for the same gate, but commit +27b10da8fff2 ("coresight: etb10: moving to local atomic operations") +changed it to local_t even though the access pattern remained cross-task +and cross-CPU. Restore atomic_t together with atomic_cmpxchg() and +atomic_set() so the exclusivity gate again uses a primitive intended +for shared state. + +The issue was found on Linux v6.18.21 by our static analysis tool while +scanning surviving local_t-on-shared-state sites, and then manually +reviewed against the live etb10 file-op path. + +It was runtime-validated with a reproducible QEMU no-device KCSAN PoC +that kept the same report-local contract: + + 1. use one shared struct etb_drvdata carrier and its + drvdata->reading gate; + 2. call etb_open() and etb_release() sequentially on that gate to + confirm the original claim/clear path; + 3. bind the open side to CPU0 and the release side to CPU1 for the + same gate to show cross-CPU ownership; + 4. run bound workers that repeatedly race etb_open() and + etb_release() on the same gate until KCSAN reports a target hit. + +The harness recorded: + + L1 passed open=1 release=1 + reading_after_open=1 reading_after_release=0 + L2 passed open_cpu=0 release_cpu=1 + cross_cpu_release=1 reading_after=0 open_ret=0 + +Representative KCSAN excerpt from the no-device validation run: + + BUG: KCSAN: data-race in etb_open.constprop.0.isra.0 [vuln_msv] + + write to 0xffffffffc0003810 of 4 bytes by task 216 on cpu 1: + etb_open.constprop.0.isra.0+0x38/0x80 [vuln_msv] + l3_worker_thread_fn+0x4f/0xf0 [vuln_msv] + kthread+0x17e/0x1c0 + ret_from_fork+0x22/0x30 + + read to 0xffffffffc0003810 of 4 bytes by task 215 on cpu 0: + etb_open.constprop.0.isra.0+0x18/0x80 [vuln_msv] + l3_worker_thread_fn+0x4f/0xf0 [vuln_msv] + kthread+0x17e/0x1c0 + ret_from_fork+0x22/0x30 + + value changed: 0x00000000 -> 0x00000001 + + Reported by Kernel Concurrency Sanitizer on: + CPU: 0 PID: 215 Comm: etb10_l3_a Tainted: G O 6.1.66 #2 + +This no-device harness is not a real ETB10 hardware end-to-end run, but +it preserves the same shared drvdata->reading gate and the same +etb_open()/etb_release() claim/clear contract. No real ETB10 hardware +was available for runtime testing. + +Build-tested with: + make olddefconfig + make -j"$(nproc)" drivers/hwtracing/coresight/coresight-etb10.o + +Fixes: 27b10da8fff2 ("coresight: etb10: moving to local atomic operations") +Cc: stable@vger.kernel.org +Signed-off-by: Runyu Xiao +Reviewed-by: James Clark +Signed-off-by: Suzuki K Poulose +Link: https://lore.kernel.org/r/20260528165201.319452-1-runyu.xiao@seu.edu.cn +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/hwtracing/coresight/coresight-etb10.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/hwtracing/coresight/coresight-etb10.c ++++ b/drivers/hwtracing/coresight/coresight-etb10.c +@@ -86,7 +86,7 @@ struct etb_drvdata { + struct coresight_device *csdev; + struct miscdevice miscdev; + spinlock_t spinlock; +- local_t reading; ++ atomic_t reading; + pid_t pid; + u8 *buf; + u32 mode; +@@ -604,7 +604,7 @@ static int etb_open(struct inode *inode, + struct etb_drvdata *drvdata = container_of(file->private_data, + struct etb_drvdata, miscdev); + +- if (local_cmpxchg(&drvdata->reading, 0, 1)) ++ if (atomic_cmpxchg(&drvdata->reading, 0, 1)) + return -EBUSY; + + dev_dbg(&drvdata->csdev->dev, "%s: successfully opened\n", __func__); +@@ -642,7 +642,7 @@ static int etb_release(struct inode *ino + { + struct etb_drvdata *drvdata = container_of(file->private_data, + struct etb_drvdata, miscdev); +- local_set(&drvdata->reading, 0); ++ atomic_set(&drvdata->reading, 0); + + dev_dbg(&drvdata->csdev->dev, "%s: released\n", __func__); + return 0; diff --git a/queue-6.6/cpufreq-make-cpufreq_driver-exit-return-void.patch b/queue-6.6/cpufreq-make-cpufreq_driver-exit-return-void.patch new file mode 100644 index 0000000000..881db2a94b --- /dev/null +++ b/queue-6.6/cpufreq-make-cpufreq_driver-exit-return-void.patch @@ -0,0 +1,619 @@ +From stable+bounces-276791-greg=kroah.com@vger.kernel.org Thu Jul 16 19:51:29 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 13:45:54 -0400 +Subject: cpufreq: Make cpufreq_driver->exit() return void +To: stable@vger.kernel.org +Cc: Lizhe , Viresh Kumar , AngeloGioacchino Del Regno , Sudeep Holla , Mario Limonciello , Florian Fainelli , "Rafael J. Wysocki" , Kevin Hilman , Sasha Levin +Message-ID: <20260716174555.787913-2-sashal@kernel.org> + +From: Lizhe + +[ Upstream commit b4b1ddc9dfe997a5f492fa3a36487f8e7a5de30d ] + +The cpufreq core doesn't check the return type of the exit() callback +and there is not much the core can do on failures at that point. Just +drop the returned value and make it return void. + +Signed-off-by: Lizhe +[ Viresh: Reworked the patches to fix all missing changes together. ] +Signed-off-by: Viresh Kumar +Reviewed-by: AngeloGioacchino Del Regno # Mediatek +Acked-by: Sudeep Holla # scpi, scmi, vexpress +Acked-by: Mario Limonciello # amd +Reviewed-by: Florian Fainelli # bmips +Acked-by: Rafael J. Wysocki +Acked-by: Kevin Hilman # omap +Stable-dep-of: bcb8889c4981 ("cpufreq: qcom-cpufreq-hw: Fix possible double free") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/cpufreq/acpi-cpufreq.c | 4 +--- + drivers/cpufreq/amd-pstate.c | 7 ++----- + drivers/cpufreq/apple-soc-cpufreq.c | 4 +--- + drivers/cpufreq/bmips-cpufreq.c | 4 +--- + drivers/cpufreq/cppc_cpufreq.c | 3 +-- + drivers/cpufreq/cpufreq-dt.c | 3 +-- + drivers/cpufreq/e_powersaver.c | 3 +-- + drivers/cpufreq/intel_pstate.c | 8 +++----- + drivers/cpufreq/mediatek-cpufreq-hw.c | 4 +--- + drivers/cpufreq/mediatek-cpufreq.c | 4 +--- + drivers/cpufreq/omap-cpufreq.c | 3 +-- + drivers/cpufreq/pasemi-cpufreq.c | 6 ++---- + drivers/cpufreq/powernow-k6.c | 5 ++--- + drivers/cpufreq/powernow-k7.c | 3 +-- + drivers/cpufreq/powernow-k8.c | 6 ++---- + drivers/cpufreq/powernv-cpufreq.c | 4 +--- + drivers/cpufreq/ppc_cbe_cpufreq.c | 3 +-- + drivers/cpufreq/qcom-cpufreq-hw.c | 4 +--- + drivers/cpufreq/qoriq-cpufreq.c | 4 +--- + drivers/cpufreq/scmi-cpufreq.c | 4 +--- + drivers/cpufreq/scpi-cpufreq.c | 4 +--- + drivers/cpufreq/sh-cpufreq.c | 4 +--- + drivers/cpufreq/sparc-us2e-cpufreq.c | 3 +-- + drivers/cpufreq/sparc-us3-cpufreq.c | 3 +-- + drivers/cpufreq/speedstep-centrino.c | 10 +++------- + drivers/cpufreq/tegra194-cpufreq.c | 4 +--- + drivers/cpufreq/vexpress-spc-cpufreq.c | 5 ++--- + include/linux/cpufreq.h | 2 +- + 28 files changed, 37 insertions(+), 84 deletions(-) + +--- a/drivers/cpufreq/acpi-cpufreq.c ++++ b/drivers/cpufreq/acpi-cpufreq.c +@@ -926,7 +926,7 @@ err_free: + return result; + } + +-static int acpi_cpufreq_cpu_exit(struct cpufreq_policy *policy) ++static void acpi_cpufreq_cpu_exit(struct cpufreq_policy *policy) + { + struct acpi_cpufreq_data *data = policy->driver_data; + +@@ -939,8 +939,6 @@ static int acpi_cpufreq_cpu_exit(struct + free_cpumask_var(data->freqdomain_cpus); + kfree(policy->freq_table); + kfree(data); +- +- return 0; + } + + static int acpi_cpufreq_resume(struct cpufreq_policy *policy) +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -900,7 +900,7 @@ free_cpudata1: + return ret; + } + +-static int amd_pstate_cpu_exit(struct cpufreq_policy *policy) ++static void amd_pstate_cpu_exit(struct cpufreq_policy *policy) + { + struct amd_cpudata *cpudata = policy->driver_data; + +@@ -908,8 +908,6 @@ static int amd_pstate_cpu_exit(struct cp + freq_qos_remove_request(&cpudata->req[0]); + policy->fast_switch_possible = false; + kfree(cpudata); +- +- return 0; + } + + static int amd_pstate_cpu_resume(struct cpufreq_policy *policy) +@@ -1353,7 +1351,7 @@ free_cpudata1: + return ret; + } + +-static int amd_pstate_epp_cpu_exit(struct cpufreq_policy *policy) ++static void amd_pstate_epp_cpu_exit(struct cpufreq_policy *policy) + { + struct amd_cpudata *cpudata = policy->driver_data; + +@@ -1363,7 +1361,6 @@ static int amd_pstate_epp_cpu_exit(struc + } + + pr_debug("CPU %d exiting\n", policy->cpu); +- return 0; + } + + static void amd_pstate_epp_update_limit(struct cpufreq_policy *policy) +--- a/drivers/cpufreq/apple-soc-cpufreq.c ++++ b/drivers/cpufreq/apple-soc-cpufreq.c +@@ -311,7 +311,7 @@ out_iounmap: + return ret; + } + +-static int apple_soc_cpufreq_exit(struct cpufreq_policy *policy) ++static void apple_soc_cpufreq_exit(struct cpufreq_policy *policy) + { + struct apple_cpu_priv *priv = policy->driver_data; + +@@ -319,8 +319,6 @@ static int apple_soc_cpufreq_exit(struct + dev_pm_opp_remove_all_dynamic(priv->cpu_dev); + iounmap(priv->reg_base); + kfree(priv); +- +- return 0; + } + + static struct cpufreq_driver apple_soc_cpufreq_driver = { +--- a/drivers/cpufreq/bmips-cpufreq.c ++++ b/drivers/cpufreq/bmips-cpufreq.c +@@ -121,11 +121,9 @@ static int bmips_cpufreq_target_index(st + return 0; + } + +-static int bmips_cpufreq_exit(struct cpufreq_policy *policy) ++static void bmips_cpufreq_exit(struct cpufreq_policy *policy) + { + kfree(policy->freq_table); +- +- return 0; + } + + static int bmips_cpufreq_init(struct cpufreq_policy *policy) +--- a/drivers/cpufreq/cppc_cpufreq.c ++++ b/drivers/cpufreq/cppc_cpufreq.c +@@ -707,7 +707,7 @@ out: + return ret; + } + +-static int cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy) ++static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy) + { + struct cppc_cpudata *cpu_data = policy->driver_data; + struct cppc_perf_caps *caps = &cpu_data->perf_caps; +@@ -724,7 +724,6 @@ static int cppc_cpufreq_cpu_exit(struct + caps->lowest_perf, cpu, ret); + + cppc_cpufreq_put_cpu_data(policy); +- return 0; + } + + static inline u64 get_delta(u64 t1, u64 t0) +--- a/drivers/cpufreq/cpufreq-dt.c ++++ b/drivers/cpufreq/cpufreq-dt.c +@@ -166,10 +166,9 @@ static int cpufreq_offline(struct cpufre + return 0; + } + +-static int cpufreq_exit(struct cpufreq_policy *policy) ++static void cpufreq_exit(struct cpufreq_policy *policy) + { + clk_put(policy->clk); +- return 0; + } + + static struct cpufreq_driver dt_cpufreq_driver = { +--- a/drivers/cpufreq/e_powersaver.c ++++ b/drivers/cpufreq/e_powersaver.c +@@ -360,14 +360,13 @@ static int eps_cpu_init(struct cpufreq_p + return 0; + } + +-static int eps_cpu_exit(struct cpufreq_policy *policy) ++static void eps_cpu_exit(struct cpufreq_policy *policy) + { + unsigned int cpu = policy->cpu; + + /* Bye */ + kfree(eps_cpu[cpu]); + eps_cpu[cpu] = NULL; +- return 0; + } + + static struct cpufreq_driver eps_driver = { +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -2705,13 +2705,11 @@ static int intel_pstate_cpu_offline(stru + return intel_cpufreq_cpu_offline(policy); + } + +-static int intel_pstate_cpu_exit(struct cpufreq_policy *policy) ++static void intel_pstate_cpu_exit(struct cpufreq_policy *policy) + { + pr_debug("CPU %d exiting\n", policy->cpu); + + policy->fast_switch_possible = false; +- +- return 0; + } + + static int __intel_pstate_cpu_init(struct cpufreq_policy *policy) +@@ -3041,7 +3039,7 @@ pstate_exit: + return ret; + } + +-static int intel_cpufreq_cpu_exit(struct cpufreq_policy *policy) ++static void intel_cpufreq_cpu_exit(struct cpufreq_policy *policy) + { + struct freq_qos_request *req; + +@@ -3051,7 +3049,7 @@ static int intel_cpufreq_cpu_exit(struct + freq_qos_remove_request(req); + kfree(req); + +- return intel_pstate_cpu_exit(policy); ++ intel_pstate_cpu_exit(policy); + } + + static int intel_cpufreq_suspend(struct cpufreq_policy *policy) +--- a/drivers/cpufreq/mediatek-cpufreq-hw.c ++++ b/drivers/cpufreq/mediatek-cpufreq-hw.c +@@ -260,7 +260,7 @@ static int mtk_cpufreq_hw_cpu_init(struc + return 0; + } + +-static int mtk_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) ++static void mtk_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) + { + struct mtk_cpufreq_data *data = policy->driver_data; + struct resource *res = data->res; +@@ -270,8 +270,6 @@ static int mtk_cpufreq_hw_cpu_exit(struc + writel_relaxed(0x0, data->reg_bases[REG_FREQ_ENABLE]); + iounmap(base); + release_mem_region(res->start, resource_size(res)); +- +- return 0; + } + + static void mtk_cpufreq_register_em(struct cpufreq_policy *policy) +--- a/drivers/cpufreq/mediatek-cpufreq.c ++++ b/drivers/cpufreq/mediatek-cpufreq.c +@@ -599,13 +599,11 @@ static int mtk_cpufreq_init(struct cpufr + return 0; + } + +-static int mtk_cpufreq_exit(struct cpufreq_policy *policy) ++static void mtk_cpufreq_exit(struct cpufreq_policy *policy) + { + struct mtk_cpu_dvfs_info *info = policy->driver_data; + + dev_pm_opp_free_cpufreq_table(info->cpu_dev, &policy->freq_table); +- +- return 0; + } + + static struct cpufreq_driver mtk_cpufreq_driver = { +--- a/drivers/cpufreq/omap-cpufreq.c ++++ b/drivers/cpufreq/omap-cpufreq.c +@@ -135,11 +135,10 @@ static int omap_cpu_init(struct cpufreq_ + return 0; + } + +-static int omap_cpu_exit(struct cpufreq_policy *policy) ++static void omap_cpu_exit(struct cpufreq_policy *policy) + { + freq_table_free(); + clk_put(policy->clk); +- return 0; + } + + static struct cpufreq_driver omap_driver = { +--- a/drivers/cpufreq/pasemi-cpufreq.c ++++ b/drivers/cpufreq/pasemi-cpufreq.c +@@ -204,21 +204,19 @@ out: + return err; + } + +-static int pas_cpufreq_cpu_exit(struct cpufreq_policy *policy) ++static void pas_cpufreq_cpu_exit(struct cpufreq_policy *policy) + { + /* + * We don't support CPU hotplug. Don't unmap after the system + * has already made it to a running state. + */ + if (system_state >= SYSTEM_RUNNING) +- return 0; ++ return; + + if (sdcasr_mapbase) + iounmap(sdcasr_mapbase); + if (sdcpwr_mapbase) + iounmap(sdcpwr_mapbase); +- +- return 0; + } + + static int pas_cpufreq_target(struct cpufreq_policy *policy, +--- a/drivers/cpufreq/powernow-k6.c ++++ b/drivers/cpufreq/powernow-k6.c +@@ -219,7 +219,7 @@ have_busfreq: + } + + +-static int powernow_k6_cpu_exit(struct cpufreq_policy *policy) ++static void powernow_k6_cpu_exit(struct cpufreq_policy *policy) + { + unsigned int i; + +@@ -234,10 +234,9 @@ static int powernow_k6_cpu_exit(struct c + cpufreq_freq_transition_begin(policy, &freqs); + powernow_k6_target(policy, i); + cpufreq_freq_transition_end(policy, &freqs, 0); +- break; ++ return; + } + } +- return 0; + } + + static unsigned int powernow_k6_get(unsigned int cpu) +--- a/drivers/cpufreq/powernow-k7.c ++++ b/drivers/cpufreq/powernow-k7.c +@@ -644,7 +644,7 @@ static int powernow_cpu_init(struct cpuf + return 0; + } + +-static int powernow_cpu_exit(struct cpufreq_policy *policy) ++static void powernow_cpu_exit(struct cpufreq_policy *policy) + { + #ifdef CONFIG_X86_POWERNOW_K7_ACPI + if (acpi_processor_perf) { +@@ -655,7 +655,6 @@ static int powernow_cpu_exit(struct cpuf + #endif + + kfree(powernow_table); +- return 0; + } + + static struct cpufreq_driver powernow_driver = { +--- a/drivers/cpufreq/powernow-k8.c ++++ b/drivers/cpufreq/powernow-k8.c +@@ -1089,13 +1089,13 @@ err_out: + return -ENODEV; + } + +-static int powernowk8_cpu_exit(struct cpufreq_policy *pol) ++static void powernowk8_cpu_exit(struct cpufreq_policy *pol) + { + struct powernow_k8_data *data = per_cpu(powernow_data, pol->cpu); + int cpu; + + if (!data) +- return -EINVAL; ++ return; + + powernow_k8_cpu_exit_acpi(data); + +@@ -1104,8 +1104,6 @@ static int powernowk8_cpu_exit(struct cp + /* pol->cpus will be empty here, use related_cpus instead. */ + for_each_cpu(cpu, pol->related_cpus) + per_cpu(powernow_data, cpu) = NULL; +- +- return 0; + } + + static void query_values_on_cpu(void *_err) +--- a/drivers/cpufreq/powernv-cpufreq.c ++++ b/drivers/cpufreq/powernv-cpufreq.c +@@ -874,7 +874,7 @@ static int powernv_cpufreq_cpu_init(stru + return 0; + } + +-static int powernv_cpufreq_cpu_exit(struct cpufreq_policy *policy) ++static void powernv_cpufreq_cpu_exit(struct cpufreq_policy *policy) + { + struct powernv_smp_call_data freq_data; + struct global_pstate_info *gpstates = policy->driver_data; +@@ -886,8 +886,6 @@ static int powernv_cpufreq_cpu_exit(stru + del_timer_sync(&gpstates->timer); + + kfree(policy->driver_data); +- +- return 0; + } + + static int powernv_cpufreq_reboot_notifier(struct notifier_block *nb, +--- a/drivers/cpufreq/ppc_cbe_cpufreq.c ++++ b/drivers/cpufreq/ppc_cbe_cpufreq.c +@@ -113,10 +113,9 @@ static int cbe_cpufreq_cpu_init(struct c + return 0; + } + +-static int cbe_cpufreq_cpu_exit(struct cpufreq_policy *policy) ++static void cbe_cpufreq_cpu_exit(struct cpufreq_policy *policy) + { + cbe_cpufreq_pmi_policy_exit(policy); +- return 0; + } + + static int cbe_cpufreq_target(struct cpufreq_policy *policy, +--- a/drivers/cpufreq/qcom-cpufreq-hw.c ++++ b/drivers/cpufreq/qcom-cpufreq-hw.c +@@ -574,7 +574,7 @@ static int qcom_cpufreq_hw_cpu_init(stru + return qcom_cpufreq_hw_lmh_init(policy, index); + } + +-static int qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) ++static void qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) + { + struct device *cpu_dev = get_cpu_device(policy->cpu); + struct qcom_cpufreq_data *data = policy->driver_data; +@@ -584,8 +584,6 @@ static int qcom_cpufreq_hw_cpu_exit(stru + qcom_cpufreq_hw_lmh_exit(data); + kfree(policy->freq_table); + kfree(data); +- +- return 0; + } + + static void qcom_cpufreq_ready(struct cpufreq_policy *policy) +--- a/drivers/cpufreq/qoriq-cpufreq.c ++++ b/drivers/cpufreq/qoriq-cpufreq.c +@@ -225,7 +225,7 @@ err_np: + return -ENODEV; + } + +-static int qoriq_cpufreq_cpu_exit(struct cpufreq_policy *policy) ++static void qoriq_cpufreq_cpu_exit(struct cpufreq_policy *policy) + { + struct cpu_data *data = policy->driver_data; + +@@ -233,8 +233,6 @@ static int qoriq_cpufreq_cpu_exit(struct + kfree(data->table); + kfree(data); + policy->driver_data = NULL; +- +- return 0; + } + + static int qoriq_cpufreq_target(struct cpufreq_policy *policy, +--- a/drivers/cpufreq/scmi-cpufreq.c ++++ b/drivers/cpufreq/scmi-cpufreq.c +@@ -247,7 +247,7 @@ out_free_priv: + return ret; + } + +-static int scmi_cpufreq_exit(struct cpufreq_policy *policy) ++static void scmi_cpufreq_exit(struct cpufreq_policy *policy) + { + struct scmi_data *priv = policy->driver_data; + +@@ -255,8 +255,6 @@ static int scmi_cpufreq_exit(struct cpuf + dev_pm_opp_remove_all_dynamic(priv->cpu_dev); + free_cpumask_var(priv->opp_shared_cpus); + kfree(priv); +- +- return 0; + } + + static void scmi_cpufreq_register_em(struct cpufreq_policy *policy) +--- a/drivers/cpufreq/scpi-cpufreq.c ++++ b/drivers/cpufreq/scpi-cpufreq.c +@@ -175,7 +175,7 @@ out_free_opp: + return ret; + } + +-static int scpi_cpufreq_exit(struct cpufreq_policy *policy) ++static void scpi_cpufreq_exit(struct cpufreq_policy *policy) + { + struct scpi_data *priv = policy->driver_data; + +@@ -183,8 +183,6 @@ static int scpi_cpufreq_exit(struct cpuf + dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); + dev_pm_opp_remove_all_dynamic(priv->cpu_dev); + kfree(priv); +- +- return 0; + } + + static struct cpufreq_driver scpi_cpufreq_driver = { +--- a/drivers/cpufreq/sh-cpufreq.c ++++ b/drivers/cpufreq/sh-cpufreq.c +@@ -135,14 +135,12 @@ static int sh_cpufreq_cpu_init(struct cp + return 0; + } + +-static int sh_cpufreq_cpu_exit(struct cpufreq_policy *policy) ++static void sh_cpufreq_cpu_exit(struct cpufreq_policy *policy) + { + unsigned int cpu = policy->cpu; + struct clk *cpuclk = &per_cpu(sh_cpuclk, cpu); + + clk_put(cpuclk); +- +- return 0; + } + + static struct cpufreq_driver sh_cpufreq_driver = { +--- a/drivers/cpufreq/sparc-us2e-cpufreq.c ++++ b/drivers/cpufreq/sparc-us2e-cpufreq.c +@@ -296,10 +296,9 @@ static int us2e_freq_cpu_init(struct cpu + return 0; + } + +-static int us2e_freq_cpu_exit(struct cpufreq_policy *policy) ++static void us2e_freq_cpu_exit(struct cpufreq_policy *policy) + { + us2e_freq_target(policy, 0); +- return 0; + } + + static struct cpufreq_driver cpufreq_us2e_driver = { +--- a/drivers/cpufreq/sparc-us3-cpufreq.c ++++ b/drivers/cpufreq/sparc-us3-cpufreq.c +@@ -140,10 +140,9 @@ static int us3_freq_cpu_init(struct cpuf + return 0; + } + +-static int us3_freq_cpu_exit(struct cpufreq_policy *policy) ++static void us3_freq_cpu_exit(struct cpufreq_policy *policy) + { + us3_freq_target(policy, 0); +- return 0; + } + + static struct cpufreq_driver cpufreq_us3_driver = { +--- a/drivers/cpufreq/speedstep-centrino.c ++++ b/drivers/cpufreq/speedstep-centrino.c +@@ -400,16 +400,12 @@ static int centrino_cpu_init(struct cpuf + return 0; + } + +-static int centrino_cpu_exit(struct cpufreq_policy *policy) ++static void centrino_cpu_exit(struct cpufreq_policy *policy) + { + unsigned int cpu = policy->cpu; + +- if (!per_cpu(centrino_model, cpu)) +- return -ENODEV; +- +- per_cpu(centrino_model, cpu) = NULL; +- +- return 0; ++ if (per_cpu(centrino_model, cpu)) ++ per_cpu(centrino_model, cpu) = NULL; + } + + /** +--- a/drivers/cpufreq/tegra194-cpufreq.c ++++ b/drivers/cpufreq/tegra194-cpufreq.c +@@ -526,14 +526,12 @@ static int tegra194_cpufreq_offline(stru + return 0; + } + +-static int tegra194_cpufreq_exit(struct cpufreq_policy *policy) ++static void tegra194_cpufreq_exit(struct cpufreq_policy *policy) + { + struct device *cpu_dev = get_cpu_device(policy->cpu); + + dev_pm_opp_remove_all_dynamic(cpu_dev); + dev_pm_opp_of_cpumask_remove_table(policy->related_cpus); +- +- return 0; + } + + static int tegra194_cpufreq_set_target(struct cpufreq_policy *policy, +--- a/drivers/cpufreq/vexpress-spc-cpufreq.c ++++ b/drivers/cpufreq/vexpress-spc-cpufreq.c +@@ -447,7 +447,7 @@ static int ve_spc_cpufreq_init(struct cp + return 0; + } + +-static int ve_spc_cpufreq_exit(struct cpufreq_policy *policy) ++static void ve_spc_cpufreq_exit(struct cpufreq_policy *policy) + { + struct device *cpu_dev; + +@@ -455,11 +455,10 @@ static int ve_spc_cpufreq_exit(struct cp + if (!cpu_dev) { + pr_err("%s: failed to get cpu%d device\n", __func__, + policy->cpu); +- return -ENODEV; ++ return; + } + + put_cluster_clk_and_freq_table(cpu_dev, policy->related_cpus); +- return 0; + } + + static struct cpufreq_driver ve_spc_cpufreq_driver = { +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -388,7 +388,7 @@ struct cpufreq_driver { + + int (*online)(struct cpufreq_policy *policy); + int (*offline)(struct cpufreq_policy *policy); +- int (*exit)(struct cpufreq_policy *policy); ++ void (*exit)(struct cpufreq_policy *policy); + int (*suspend)(struct cpufreq_policy *policy); + int (*resume)(struct cpufreq_policy *policy); + diff --git a/queue-6.6/cpufreq-pcc-remove-empty-exit-callback.patch b/queue-6.6/cpufreq-pcc-remove-empty-exit-callback.patch new file mode 100644 index 0000000000..ada03d989f --- /dev/null +++ b/queue-6.6/cpufreq-pcc-remove-empty-exit-callback.patch @@ -0,0 +1,44 @@ +From stable+bounces-276790-greg=kroah.com@vger.kernel.org Thu Jul 16 19:51:32 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 13:45:53 -0400 +Subject: cpufreq: pcc: Remove empty exit() callback +To: stable@vger.kernel.org +Cc: Viresh Kumar , "Rafael J. Wysocki" , Sasha Levin +Message-ID: <20260716174555.787913-1-sashal@kernel.org> + +From: Viresh Kumar + +[ Upstream commit dfd3e8b90b3660b706c3031b0f746377c571b324 ] + +The exit() callback is optional, remove the empty one. + +Signed-off-by: Viresh Kumar +Acked-by: Rafael J. Wysocki +Stable-dep-of: bcb8889c4981 ("cpufreq: qcom-cpufreq-hw: Fix possible double free") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/cpufreq/pcc-cpufreq.c | 6 ------ + 1 file changed, 6 deletions(-) + +--- a/drivers/cpufreq/pcc-cpufreq.c ++++ b/drivers/cpufreq/pcc-cpufreq.c +@@ -564,18 +564,12 @@ out: + return result; + } + +-static int pcc_cpufreq_cpu_exit(struct cpufreq_policy *policy) +-{ +- return 0; +-} +- + static struct cpufreq_driver pcc_cpufreq_driver = { + .flags = CPUFREQ_CONST_LOOPS, + .get = pcc_get_freq, + .verify = pcc_cpufreq_verify, + .target = pcc_cpufreq_target, + .init = pcc_cpufreq_cpu_init, +- .exit = pcc_cpufreq_cpu_exit, + .name = "pcc-cpufreq", + }; + diff --git a/queue-6.6/cpufreq-qcom-cpufreq-hw-fix-possible-double-free.patch b/queue-6.6/cpufreq-qcom-cpufreq-hw-fix-possible-double-free.patch new file mode 100644 index 0000000000..059dc23100 --- /dev/null +++ b/queue-6.6/cpufreq-qcom-cpufreq-hw-fix-possible-double-free.patch @@ -0,0 +1,48 @@ +From stable+bounces-276792-greg=kroah.com@vger.kernel.org Thu Jul 16 19:51:28 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 13:45:55 -0400 +Subject: cpufreq: qcom-cpufreq-hw: Fix possible double free +To: stable@vger.kernel.org +Cc: Guangshuo Li , Zhongqiu Han , Viresh Kumar , Sasha Levin +Message-ID: <20260716174555.787913-3-sashal@kernel.org> + +From: Guangshuo Li + +[ Upstream commit bcb8889c4981fdde42d4fd2c29a77d510fe21da2 ] + +qcom_cpufreq.data is allocated with devm_kzalloc() in probe() as an +array of per-domain data. qcom_cpufreq_hw_cpu_init() stores a pointer to +one element of this array in policy->driver_data. + +qcom_cpufreq_hw_cpu_exit() currently calls kfree() on policy->driver_data. +This is not valid because the memory is devm-managed. For the first +domain, this can free the devm-managed allocation while the devres entry +is still active, leading to a possible double free when the platform +device is later detached. For other domains, the pointer may refer to an +element inside the array rather than the allocation base. + +Remove the kfree(data) call and let devres release qcom_cpufreq.data. + +This issue was found by a static analysis tool I am developing. + +Fixes: 054a3ef683a1 ("cpufreq: qcom-hw: Allocate qcom_cpufreq_data during probe") +Cc: stable@vger.kernel.org +Signed-off-by: Guangshuo Li +Reviewed-by: Zhongqiu Han +Signed-off-by: Viresh Kumar +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/cpufreq/qcom-cpufreq-hw.c | 1 - + 1 file changed, 1 deletion(-) + +--- a/drivers/cpufreq/qcom-cpufreq-hw.c ++++ b/drivers/cpufreq/qcom-cpufreq-hw.c +@@ -583,7 +583,6 @@ static void qcom_cpufreq_hw_cpu_exit(str + dev_pm_opp_of_cpumask_remove_table(policy->related_cpus); + qcom_cpufreq_hw_lmh_exit(data); + kfree(policy->freq_table); +- kfree(data); + } + + static void qcom_cpufreq_ready(struct cpufreq_policy *policy) diff --git a/queue-6.6/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch b/queue-6.6/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch new file mode 100644 index 0000000000..7afc519cd6 --- /dev/null +++ b/queue-6.6/crypto-atmel-drop-explicit-initialization-of-struct-i2c_device_id-driver_data-to-0.patch @@ -0,0 +1,53 @@ +From stable+bounces-277783-greg=kroah.com@vger.kernel.org Mon Jul 20 15:33:26 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 09:30:25 -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: <20260720133026.1149181-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 +@@ -139,8 +139,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.6/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch b/queue-6.6/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch new file mode 100644 index 0000000000..d217ba7d95 --- /dev/null +++ b/queue-6.6/crypto-atmel-sha204a-drop-hwrng-quality-reduction-for-atsha204a.patch @@ -0,0 +1,101 @@ +From stable+bounces-277784-greg=kroah.com@vger.kernel.org Mon Jul 20 15:58:07 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 09:30:26 -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: <20260720133026.1149181-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 +@@ -18,6 +18,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) + { +@@ -95,6 +101,7 @@ static int atmel_sha204a_rng_read(struct + 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); +@@ -108,11 +115,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) +@@ -132,14 +137,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.6/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch b/queue-6.6/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch new file mode 100644 index 0000000000..500ce377bb --- /dev/null +++ b/queue-6.6/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch @@ -0,0 +1,57 @@ +From stable+bounces-278191-greg=kroah.com@vger.kernel.org Mon Jul 20 20:38:43 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 14:38:34 -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: <20260720183834.3080260-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 +@@ -113,13 +113,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.6/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch b/queue-6.6/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch new file mode 100644 index 0000000000..1f7bc47565 --- /dev/null +++ b/queue-6.6/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch @@ -0,0 +1,216 @@ +From stable+bounces-274667-greg=kroah.com@vger.kernel.org Wed Jul 15 03:45:38 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 21:45:27 -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: <20260715014527.4133831-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 +@@ -314,6 +314,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 +@@ -123,6 +123,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); + +@@ -195,6 +196,7 @@ void adf_misc_wq_flush(void); + int adf_sriov_configure(struct pci_dev *pdev, int numvfs); + void adf_disable_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); + } +@@ -151,6 +169,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,18 +26,26 @@ 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; ++ + ret = adf_recv_and_handle_vf2pf_msg(accel_dev, vf_nr); + if (ret) + /* re-enable interrupt on PF from this VF */ + adf_enable_vf2pf_interrupts(accel_dev, 1 << vf_nr); + ++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; +@@ -47,6 +55,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); +@@ -73,7 +87,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); + + /* + * Due to the hardware design, when SR-IOV and the ring arbiter +@@ -105,8 +119,10 @@ void adf_disable_sriov(struct adf_accel_ + adf_pf2vf_notify_restarting(accel_dev); + 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.6/exfat-add-exfat_get_dentry_set_by_ei-helper.patch b/queue-6.6/exfat-add-exfat_get_dentry_set_by_ei-helper.patch new file mode 100644 index 0000000000..2855960a22 --- /dev/null +++ b/queue-6.6/exfat-add-exfat_get_dentry_set_by_ei-helper.patch @@ -0,0 +1,315 @@ +From stable+bounces-278570-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:35 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 07:47:41 -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: <20260721114743.3689685-4-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 | 82 ++++++++++++++++++++++------------------------------ + 3 files changed, 38 insertions(+), 48 deletions(-) + +--- a/fs/exfat/exfat_fs.h ++++ b/fs/exfat/exfat_fs.h +@@ -498,6 +498,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 type); ++#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_put_dentry_set(struct exfat_entry_set_cache *es, int sync); + int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); + +--- a/fs/exfat/inode.c ++++ b/fs/exfat/inode.c +@@ -42,7 +42,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 +@@ -786,29 +786,26 @@ unlock: + /* remove an entry, BUT don't truncate */ + static int exfat_unlink(struct inode *dir, struct dentry *dentry) + { +- struct exfat_chain cdir; + struct exfat_dentry *ep; + struct super_block *sb = dir->i_sb; + struct inode *inode = dentry->d_inode; + struct exfat_inode_info *ei = EXFAT_I(inode); + struct buffer_head *bh; +- int num_entries, entry, err = 0; ++ int num_entries, err = 0; + + 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; + } + +- ep = exfat_get_dentry(sb, &cdir, entry, &bh); ++ ep = exfat_get_dentry(sb, &ei->dir, ei->entry, &bh); + if (!ep) { + err = -EIO; + goto unlock; + } +- num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep); ++ num_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry, ep); + if (num_entries < 0) { + err = -EIO; + brelse(bh); +@@ -819,7 +816,7 @@ static int exfat_unlink(struct inode *di + + exfat_set_volume_dirty(sb); + /* update the directory entry */ +- if (exfat_remove_entries(dir, &cdir, entry, 0, num_entries)) { ++ if (exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries)) { + err = -EIO; + goto unlock; + } +@@ -944,18 +941,15 @@ static int exfat_rmdir(struct inode *dir + { + struct inode *inode = dentry->d_inode; + struct exfat_dentry *ep; +- 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 buffer_head *bh; +- int num_entries, entry, err; ++ int num_entries, err; + + 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; +@@ -973,13 +967,13 @@ static int exfat_rmdir(struct inode *dir + goto unlock; + } + +- ep = exfat_get_dentry(sb, &cdir, entry, &bh); ++ ep = exfat_get_dentry(sb, &ei->dir, ei->entry, &bh); + if (!ep) { + err = -EIO; + goto unlock; + } + +- num_entries = exfat_count_ext_entries(sb, &cdir, entry, ep); ++ num_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry, ep); + if (num_entries < 0) { + err = -EIO; + brelse(bh); +@@ -989,7 +983,7 @@ static int exfat_rmdir(struct inode *dir + brelse(bh); + + exfat_set_volume_dirty(sb); +- err = exfat_remove_entries(dir, &cdir, entry, 0, num_entries); ++ err = exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries); + if (err) { + exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err); + goto unlock; +@@ -1015,8 +1009,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_old_entries, num_new_entries; +@@ -1025,11 +1019,12 @@ static int exfat_rename_file(struct inod + struct buffer_head *new_bh, *old_bh; + int sync = IS_DIRSYNC(parent_inode); + +- epold = exfat_get_dentry(sb, p_dir, oldentry, &old_bh); ++ epold = exfat_get_dentry(sb, &ei->dir, ei->entry, &old_bh); + if (!epold) + return -EIO; + +- num_old_entries = exfat_count_ext_entries(sb, p_dir, oldentry, epold); ++ num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry, ++ epold); + if (num_old_entries < 0) + return -EIO; + num_old_entries++; +@@ -1059,7 +1054,7 @@ static int exfat_rename_file(struct inod + brelse(old_bh); + brelse(new_bh); + +- epold = exfat_get_dentry(sb, p_dir, oldentry + 1, &old_bh); ++ epold = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &old_bh); + if (!epold) + return -EIO; + epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh); +@@ -1078,7 +1073,7 @@ static int exfat_rename_file(struct inod + if (ret) + return ret; + +- exfat_remove_entries(parent_inode, p_dir, oldentry, 0, ++ exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0, + num_old_entries); + ei->dir = *p_dir; + ei->entry = newentry; +@@ -1090,10 +1085,10 @@ static int exfat_rename_file(struct inod + exfat_update_bh(old_bh, sync); + brelse(old_bh); + +- exfat_remove_entries(parent_inode, p_dir, oldentry, ++ exfat_remove_entries(parent_inode, &ei->dir, ei->entry, + ES_IDX_FIRST_FILENAME + 1, num_old_entries); + +- ret = exfat_init_ext_entry(parent_inode, p_dir, oldentry, ++ ret = exfat_init_ext_entry(parent_inode, &ei->dir, ei->entry, + num_new_entries, p_uniname); + if (ret) + return ret; +@@ -1101,20 +1096,20 @@ static int exfat_rename_file(struct inod + return 0; + } + +-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, num_old_entries; + struct exfat_dentry *epmov, *epnew; + struct super_block *sb = parent_inode->i_sb; + struct buffer_head *mov_bh, *new_bh; + +- epmov = exfat_get_dentry(sb, p_olddir, oldentry, &mov_bh); ++ epmov = exfat_get_dentry(sb, &ei->dir, ei->entry, &mov_bh); + if (!epmov) + return -EIO; + +- num_old_entries = exfat_count_ext_entries(sb, p_olddir, oldentry, ++ num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry, + epmov); + if (num_old_entries < 0) + return -EIO; +@@ -1142,7 +1137,7 @@ static int exfat_move_file(struct inode + brelse(mov_bh); + brelse(new_bh); + +- epmov = exfat_get_dentry(sb, p_olddir, oldentry + 1, &mov_bh); ++ epmov = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &mov_bh); + if (!epmov) + return -EIO; + epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh); +@@ -1161,7 +1156,7 @@ static int exfat_move_file(struct inode + if (ret) + return ret; + +- exfat_remove_entries(parent_inode, p_olddir, oldentry, 0, ++ exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0, + num_old_entries); + + exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, +@@ -1177,8 +1172,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); +@@ -1196,11 +1190,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; +@@ -1235,35 +1224,34 @@ 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_chain *p_dir = &(new_ei->dir); +- int new_entry = new_ei->entry; + struct exfat_dentry *ep; + struct buffer_head *new_bh = NULL; + + /* delete entries of new_dir */ +- ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh); ++ ep = exfat_get_dentry(sb, &new_ei->dir, new_ei->entry, &new_bh); + if (!ep) { + ret = -EIO; + goto del_out; + } + +- num_entries = exfat_count_ext_entries(sb, p_dir, new_entry, ep); ++ num_entries = exfat_count_ext_entries(sb, &new_ei->dir, ++ new_ei->entry, ep); + if (num_entries < 0) { + ret = -EIO; + goto del_out; + } + brelse(new_bh); + +- if (exfat_remove_entries(new_inode, p_dir, new_entry, 0, +- num_entries + 1)) { ++ if (exfat_remove_entries(new_inode, &new_ei->dir, new_ei->entry, ++ 0, num_entries + 1)) { + ret = -EIO; + goto del_out; + } diff --git a/queue-6.6/exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch b/queue-6.6/exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch new file mode 100644 index 0000000000..c50b3c8ed7 --- /dev/null +++ b/queue-6.6/exfat-move-exfat_chain_set-out-of-__exfat_resolve_path.patch @@ -0,0 +1,261 @@ +From stable+bounces-278571-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:40 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 07:47:42 -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: <20260721114743.3689685-5-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 | 68 ++++++++++++++++++++++++------------------------------- + 1 file changed, 30 insertions(+), 38 deletions(-) + +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -343,6 +343,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)) < 0) { + if (dentry == -EIO) +@@ -419,14 +422,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); + + /* +@@ -465,24 +465,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) +@@ -502,7 +497,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; + +@@ -623,10 +618,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; +@@ -1010,8 +1008,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_old_entries, num_new_entries; + struct exfat_dentry *epold, *epnew; +@@ -1035,13 +1032,14 @@ static int exfat_rename_file(struct inod + + if (num_old_entries < num_new_entries) { + int newentry; ++ struct exfat_chain dir; + +- newentry = exfat_find_empty_entry(parent_inode, p_dir, ++ newentry = exfat_find_empty_entry(parent_inode, &dir, + num_new_entries); + if (newentry < 0) + return newentry; /* -EIO or -ENOSPC */ + +- epnew = exfat_get_dentry(sb, p_dir, newentry, &new_bh); ++ epnew = exfat_get_dentry(sb, &dir, newentry, &new_bh); + if (!epnew) + return -EIO; + +@@ -1057,7 +1055,7 @@ static int exfat_rename_file(struct inod + epold = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &old_bh); + if (!epold) + return -EIO; +- epnew = exfat_get_dentry(sb, p_dir, newentry + 1, &new_bh); ++ epnew = exfat_get_dentry(sb, &dir, newentry + 1, &new_bh); + if (!epnew) { + brelse(old_bh); + return -EIO; +@@ -1068,14 +1066,14 @@ static int exfat_rename_file(struct inod + brelse(old_bh); + brelse(new_bh); + +- ret = exfat_init_ext_entry(parent_inode, p_dir, newentry, ++ ret = exfat_init_ext_entry(parent_inode, &dir, newentry, + num_new_entries, p_uniname); + if (ret) + return ret; + + exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0, + num_old_entries); +- ei->dir = *p_dir; ++ ei->dir = dir; + ei->entry = newentry; + } else { + if (exfat_get_entry_type(epold) == TYPE_FILE) { +@@ -1097,13 +1095,13 @@ static int exfat_rename_file(struct inod + } + + 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, num_old_entries; + struct exfat_dentry *epmov, *epnew; + struct super_block *sb = parent_inode->i_sb; + struct buffer_head *mov_bh, *new_bh; ++ struct exfat_chain newdir; + + epmov = exfat_get_dentry(sb, &ei->dir, ei->entry, &mov_bh); + if (!epmov) +@@ -1119,12 +1117,12 @@ static int exfat_move_file(struct inode + if (num_new_entries < 0) + return num_new_entries; + +- newentry = exfat_find_empty_entry(parent_inode, p_newdir, ++ newentry = exfat_find_empty_entry(parent_inode, &newdir, + num_new_entries); + if (newentry < 0) + return newentry; /* -EIO or -ENOSPC */ + +- epnew = exfat_get_dentry(sb, p_newdir, newentry, &new_bh); ++ epnew = exfat_get_dentry(sb, &newdir, newentry, &new_bh); + if (!epnew) + return -EIO; + +@@ -1140,7 +1138,7 @@ static int exfat_move_file(struct inode + epmov = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &mov_bh); + if (!epmov) + return -EIO; +- epnew = exfat_get_dentry(sb, p_newdir, newentry + 1, &new_bh); ++ epnew = exfat_get_dentry(sb, &newdir, newentry + 1, &new_bh); + if (!epnew) { + brelse(mov_bh); + return -EIO; +@@ -1151,7 +1149,7 @@ static int exfat_move_file(struct inode + brelse(mov_bh); + brelse(new_bh); + +- ret = exfat_init_ext_entry(parent_inode, p_newdir, newentry, ++ ret = exfat_init_ext_entry(parent_inode, &newdir, newentry, + num_new_entries, p_uniname); + if (ret) + return ret; +@@ -1159,9 +1157,7 @@ static int exfat_move_file(struct inode + exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0, + num_old_entries); + +- exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, +- p_newdir->flags); +- ++ ei->dir = newdir; + ei->entry = newentry; + return 0; + } +@@ -1172,7 +1168,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); +@@ -1217,19 +1212,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_dentry *ep; diff --git a/queue-6.6/exfat-move-free-cluster-out-of-exfat_init_ext_entry.patch b/queue-6.6/exfat-move-free-cluster-out-of-exfat_init_ext_entry.patch new file mode 100644 index 0000000000..e906f7d711 --- /dev/null +++ b/queue-6.6/exfat-move-free-cluster-out-of-exfat_init_ext_entry.patch @@ -0,0 +1,64 @@ +From stable+bounces-278567-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:00 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 07:47:38 -0400 +Subject: exfat: move free cluster out of exfat_init_ext_entry() +To: stable@vger.kernel.org +Cc: Yuezhang Mo , Andy Wu , Aoyama Wataru , Sungjong Seo , Namjae Jeon , Sasha Levin +Message-ID: <20260721114743.3689685-1-sashal@kernel.org> + +From: Yuezhang Mo + +[ Upstream commit 4e1aa22fea106014397455506d1383d519c4d3d1 ] + +exfat_init_ext_entry() is an init function, it's a bit strange +to free cluster in it. And the argument 'inode' will be removed +from exfat_init_ext_entry(). So this commit changes to free the +cluster in exfat_remove_entries(). + +Code refinement, no functional changes. + +Signed-off-by: Yuezhang Mo +Reviewed-by: Andy Wu +Reviewed-by: Aoyama Wataru +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/dir.c | 3 --- + fs/exfat/namei.c | 7 ++++--- + 2 files changed, 4 insertions(+), 6 deletions(-) + +--- a/fs/exfat/dir.c ++++ b/fs/exfat/dir.c +@@ -584,9 +584,6 @@ int exfat_init_ext_entry(struct inode *i + if (!ep) + return -EIO; + +- if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC) +- exfat_free_benign_secondary_clusters(inode, ep); +- + exfat_init_name_entry(ep, uniname); + exfat_update_bh(bh, sync); + brelse(bh); +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -1089,13 +1089,14 @@ static int exfat_rename_file(struct inod + } + exfat_update_bh(old_bh, sync); + brelse(old_bh); ++ ++ exfat_remove_entries(inode, p_dir, oldentry, ++ ES_IDX_FIRST_FILENAME + 1, num_old_entries); ++ + ret = exfat_init_ext_entry(inode, p_dir, oldentry, + num_new_entries, p_uniname); + if (ret) + return ret; +- +- exfat_remove_entries(inode, p_dir, oldentry, num_new_entries, +- num_old_entries); + } + return 0; + } diff --git a/queue-6.6/exfat-preserve-benign-secondary-entries-during-rename-and-move.patch b/queue-6.6/exfat-preserve-benign-secondary-entries-during-rename-and-move.patch new file mode 100644 index 0000000000..b6852d2ad1 --- /dev/null +++ b/queue-6.6/exfat-preserve-benign-secondary-entries-during-rename-and-move.patch @@ -0,0 +1,463 @@ +From stable+bounces-278572-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:45 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 07:47:43 -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: <20260721114743.3689685-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 | 45 ++++++++++-- + fs/exfat/exfat_fs.h | 5 - + fs/exfat/namei.c | 191 ++++++++++++++++++++++++++++++++++------------------ + 3 files changed, 169 insertions(+), 72 deletions(-) + +--- a/fs/exfat/dir.c ++++ b/fs/exfat/dir.c +@@ -552,21 +552,43 @@ static void exfat_free_benign_secondary_ + exfat_free_cluster(inode, &dir); + } + ++/* ++ * exfat_init_ext_entry - initialize extension entries of a dentry set ++ * @inode: parent directory inode ++ * @p_dir: directory the dentry set is in ++ * @entry: index of the first entry of the dentry 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, optionally ++ * preserving @num_extra benign secondary entries from @old_es. @old_es ++ * may refer to the same dentry set that is being initialized. ++ */ + int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, +- int entry, int num_entries, struct exfat_uni_name *p_uniname) ++ int entry, int num_entries, struct exfat_uni_name *p_uniname, ++ struct exfat_entry_set_cache *old_es, int num_extra) + { + struct super_block *sb = inode->i_sb; +- int i; ++ int i, src_start = 0; + unsigned short *uniname = p_uniname->name; + struct exfat_dentry *ep; + struct buffer_head *bh; + int sync = IS_DIRSYNC(inode); + ++ if (WARN_ON(num_extra < 0 || (num_extra && (!old_es || ++ old_es->num_entries < ES_IDX_FIRST_FILENAME + num_extra)))) ++ num_extra = 0; ++ ++ if (old_es && num_extra > 0) ++ src_start = old_es->num_entries - num_extra; ++ + ep = exfat_get_dentry(sb, p_dir, entry, &bh); + if (!ep) + return -EIO; + +- ep->dentry.file.num_ext = (unsigned char)(num_entries - 1); ++ ep->dentry.file.num_ext = (unsigned char)(num_entries - 1 + num_extra); + exfat_update_bh(bh, sync); + brelse(bh); + +@@ -579,6 +601,19 @@ int exfat_init_ext_entry(struct inode *i + exfat_update_bh(bh, sync); + brelse(bh); + ++ if (old_es && num_extra > 0) { ++ for (i = 0; i < num_extra; i++) { ++ ep = exfat_get_dentry(sb, p_dir, ++ entry + num_entries + i, &bh); ++ if (!ep) ++ return -EIO; ++ ++ *ep = *exfat_get_dentry_cached(old_es, src_start + i); ++ exfat_update_bh(bh, sync); ++ brelse(bh); ++ } ++ } ++ + for (i = EXFAT_FIRST_CLUSTER; i < num_entries; i++) { + ep = exfat_get_dentry(sb, p_dir, entry + i, &bh); + if (!ep) +@@ -595,7 +630,7 @@ int exfat_init_ext_entry(struct inode *i + } + + int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, +- int entry, int order, int num_entries) ++ int entry, int order, int num_entries, bool free_benign) + { + struct super_block *sb = inode->i_sb; + int i; +@@ -607,7 +642,7 @@ int exfat_remove_entries(struct inode *i + if (!ep) + return -EIO; + +- 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 +@@ -480,9 +480,10 @@ int exfat_init_dir_entry(struct inode *i + int entry, unsigned int type, unsigned int start_clu, + unsigned long long size); + int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, +- int entry, int num_entries, struct exfat_uni_name *p_uniname); ++ int entry, int num_entries, struct exfat_uni_name *p_uniname, ++ struct exfat_entry_set_cache *old_es, int num_extra); + int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, +- int entry, int order, int num_entries); ++ int entry, int order, int num_entries, bool free_benign); + int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, + int entry); + void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es); +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -531,7 +531,8 @@ static int exfat_add_entry(struct inode + if (ret) + goto out; + +- ret = exfat_init_ext_entry(inode, p_dir, dentry, num_entries, &uniname); ++ ret = exfat_init_ext_entry(inode, p_dir, dentry, num_entries, &uniname, ++ NULL, 0); + if (ret) + goto out; + +@@ -814,7 +815,8 @@ static int exfat_unlink(struct inode *di + + exfat_set_volume_dirty(sb); + /* update the directory entry */ +- if (exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries)) { ++ if (exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries, ++ true)) { + err = -EIO; + goto unlock; + } +@@ -981,7 +983,8 @@ static int exfat_rmdir(struct inode *dir + brelse(bh); + + exfat_set_volume_dirty(sb); +- err = exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries); ++ err = exfat_remove_entries(dir, &ei->dir, ei->entry, 0, num_entries, ++ true); + if (err) { + exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err); + goto unlock; +@@ -1007,41 +1010,67 @@ 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) + { + int ret, num_old_entries, num_new_entries; + struct exfat_dentry *epold, *epnew; + struct super_block *sb = parent_inode->i_sb; ++ struct exfat_entry_set_cache old_es; + struct buffer_head *new_bh, *old_bh; + int sync = IS_DIRSYNC(parent_inode); +- +- epold = exfat_get_dentry(sb, &ei->dir, ei->entry, &old_bh); +- if (!epold) +- return -EIO; +- +- num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry, +- epold); +- if (num_old_entries < 0) +- return -EIO; +- num_old_entries++; ++ unsigned int num_extra_entries, num_total_entries; + + num_new_entries = exfat_calc_num_entries(p_uniname); + if (num_new_entries < 0) + return num_new_entries; + +- if (num_old_entries < num_new_entries) { ++ ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei); ++ if (ret) ++ return -EIO; ++ ++ num_old_entries = old_es.num_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 (num_old_entries < num_total_entries) { + int newentry; + struct exfat_chain dir; + + newentry = exfat_find_empty_entry(parent_inode, &dir, +- num_new_entries); +- if (newentry < 0) +- return newentry; /* -EIO or -ENOSPC */ ++ num_total_entries); ++ if (newentry < 0) { ++ ret = newentry; /* -EIO or -ENOSPC */ ++ goto put_old_es; ++ } + ++ epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE); + epnew = exfat_get_dentry(sb, &dir, newentry, &new_bh); +- if (!epnew) +- return -EIO; ++ if (!epnew) { ++ ret = -EIO; ++ goto put_old_es; ++ } + + *epnew = *epold; + if (exfat_get_entry_type(epnew) == TYPE_FILE) { +@@ -1049,33 +1078,43 @@ static int exfat_rename_file(struct inod + ei->attr |= ATTR_ARCHIVE; + } + exfat_update_bh(new_bh, sync); +- brelse(old_bh); + brelse(new_bh); + +- epold = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &old_bh); +- if (!epold) +- return -EIO; ++ epold = exfat_get_dentry_cached(&old_es, ES_IDX_STREAM); + epnew = exfat_get_dentry(sb, &dir, newentry + 1, &new_bh); + if (!epnew) { +- brelse(old_bh); +- return -EIO; ++ /* Best-effort delete to avoid duplicate entries */ ++ exfat_remove_entries(parent_inode, &dir, newentry, 0, ++ num_total_entries, false); ++ ret = -EIO; ++ goto put_old_es; + } + + *epnew = *epold; + exfat_update_bh(new_bh, sync); +- brelse(old_bh); + brelse(new_bh); + + ret = exfat_init_ext_entry(parent_inode, &dir, newentry, +- num_new_entries, p_uniname); +- if (ret) +- return ret; ++ num_new_entries, p_uniname, &old_es, ++ num_extra_entries); ++ if (ret) { ++ /* Best-effort delete to avoid duplicate entries */ ++ exfat_remove_entries(parent_inode, &dir, newentry, 0, ++ num_total_entries, false); ++ goto put_old_es; ++ } + + exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0, +- num_old_entries); ++ num_old_entries, false); + ei->dir = dir; + ei->entry = newentry; + } else { ++ epold = exfat_get_dentry(sb, &ei->dir, ei->entry, &old_bh); ++ if (!epold) { ++ ret = -EIO; ++ goto put_old_es; ++ } ++ + if (exfat_get_entry_type(epold) == TYPE_FILE) { + epold->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); + ei->attr |= ATTR_ARCHIVE; +@@ -1083,15 +1122,21 @@ static int exfat_rename_file(struct inod + exfat_update_bh(old_bh, sync); + brelse(old_bh); + +- exfat_remove_entries(parent_inode, &ei->dir, ei->entry, +- ES_IDX_FIRST_FILENAME + 1, num_old_entries); +- + ret = exfat_init_ext_entry(parent_inode, &ei->dir, ei->entry, +- num_new_entries, p_uniname); ++ num_new_entries, p_uniname, &old_es, ++ num_extra_entries); + if (ret) +- return ret; ++ goto put_old_es; ++ ++ /* Mark excess old entries as deleted (in-place shrink) */ ++ exfat_remove_entries(parent_inode, &ei->dir, ei->entry, ++ num_total_entries, num_old_entries, false); + } +- return 0; ++ ret = 0; ++ ++put_old_es: ++ exfat_put_dentry_set(&old_es, false); ++ return ret; + } + + static int exfat_move_file(struct inode *parent_inode, +@@ -1100,31 +1145,40 @@ static int exfat_move_file(struct inode + int ret, newentry, num_new_entries, num_old_entries; + struct exfat_dentry *epmov, *epnew; + struct super_block *sb = parent_inode->i_sb; +- struct buffer_head *mov_bh, *new_bh; ++ struct exfat_entry_set_cache mov_es; ++ struct buffer_head *new_bh; + struct exfat_chain newdir; +- +- epmov = exfat_get_dentry(sb, &ei->dir, ei->entry, &mov_bh); +- if (!epmov) +- return -EIO; +- +- num_old_entries = exfat_count_ext_entries(sb, &ei->dir, ei->entry, +- epmov); +- if (num_old_entries < 0) +- return -EIO; +- num_old_entries++; ++ unsigned int num_extra_entries, num_total_entries; + + num_new_entries = exfat_calc_num_entries(p_uniname); + if (num_new_entries < 0) + return num_new_entries; + ++ ret = exfat_get_dentry_set_by_ei(&mov_es, sb, ei); ++ if (ret) ++ return -EIO; ++ ++ num_old_entries = mov_es.num_entries; ++ ++ 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); +- if (newentry < 0) +- return newentry; /* -EIO or -ENOSPC */ ++ num_total_entries); ++ if (newentry < 0) { ++ ret = newentry; /* -EIO or -ENOSPC */ ++ goto put_mov_es; ++ } + ++ epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_FILE); + epnew = exfat_get_dentry(sb, &newdir, newentry, &new_bh); +- if (!epnew) +- return -EIO; ++ if (!epnew) { ++ ret = -EIO; ++ goto put_mov_es; ++ } + + *epnew = *epmov; + if (exfat_get_entry_type(epnew) == TYPE_FILE) { +@@ -1132,34 +1186,41 @@ static int exfat_move_file(struct inode + ei->attr |= ATTR_ARCHIVE; + } + exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode)); +- brelse(mov_bh); + brelse(new_bh); + +- epmov = exfat_get_dentry(sb, &ei->dir, ei->entry + 1, &mov_bh); +- if (!epmov) +- return -EIO; ++ epmov = exfat_get_dentry_cached(&mov_es, ES_IDX_STREAM); + epnew = exfat_get_dentry(sb, &newdir, newentry + 1, &new_bh); + if (!epnew) { +- brelse(mov_bh); +- return -EIO; ++ /* Best-effort delete to avoid duplicate entries */ ++ exfat_remove_entries(parent_inode, &newdir, newentry, 0, ++ num_total_entries, false); ++ ret = -EIO; ++ goto put_mov_es; + } + + *epnew = *epmov; + exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode)); +- brelse(mov_bh); + brelse(new_bh); + + ret = exfat_init_ext_entry(parent_inode, &newdir, newentry, +- num_new_entries, p_uniname); +- if (ret) +- return ret; ++ num_new_entries, p_uniname, &mov_es, num_extra_entries); ++ if (ret) { ++ /* Best-effort delete to avoid duplicate entries */ ++ exfat_remove_entries(parent_inode, &newdir, newentry, 0, ++ num_total_entries, false); ++ goto put_mov_es; ++ } + + exfat_remove_entries(parent_inode, &ei->dir, ei->entry, 0, +- num_old_entries); ++ num_old_entries, false); + + ei->dir = newdir; + ei->entry = newentry; +- return 0; ++ ret = 0; ++ ++put_mov_es: ++ exfat_put_dentry_set(&mov_es, false); ++ return ret; + } + + /* rename or move a old file into a new file */ +@@ -1243,7 +1304,7 @@ static int __exfat_rename(struct inode * + brelse(new_bh); + + if (exfat_remove_entries(new_inode, &new_ei->dir, new_ei->entry, +- 0, num_entries + 1)) { ++ 0, num_entries + 1, true)) { + ret = -EIO; + goto del_out; + } diff --git a/queue-6.6/exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch b/queue-6.6/exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch new file mode 100644 index 0000000000..0d54d787bd --- /dev/null +++ b/queue-6.6/exfat-remove-unnecessary-read-entry-in-__exfat_rename.patch @@ -0,0 +1,88 @@ +From stable+bounces-278568-greg=kroah.com@vger.kernel.org Tue Jul 21 13:52:13 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 07:47:39 -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: <20260721114743.3689685-2-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 | 23 +++++++---------------- + 1 file changed, 7 insertions(+), 16 deletions(-) + +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -1177,18 +1177,13 @@ 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; + int num_entries; + 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) +@@ -1214,17 +1209,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; +@@ -1255,6 +1241,11 @@ static int __exfat_rename(struct inode * + &newdir, &uni_name, ei); + + if (!ret && new_inode) { ++ struct exfat_chain *p_dir = &(new_ei->dir); ++ int new_entry = new_ei->entry; ++ struct exfat_dentry *ep; ++ struct buffer_head *new_bh = NULL; ++ + /* delete entries of new_dir */ + ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh); + if (!ep) { +@@ -1276,7 +1267,7 @@ static int __exfat_rename(struct inode * + } + + /* 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.6/exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch b/queue-6.6/exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch new file mode 100644 index 0000000000..4f4547d76d --- /dev/null +++ b/queue-6.6/exfat-rename-argument-name-for-exfat_move_file-and-exfat_rename_file.patch @@ -0,0 +1,149 @@ +From stable+bounces-278569-greg=kroah.com@vger.kernel.org Tue Jul 21 13:47:54 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 07:47:40 -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: <20260721114743.3689685-3-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 | 36 +++++++++++++++++++----------------- + 1 file changed, 19 insertions(+), 17 deletions(-) + +--- a/fs/exfat/namei.c ++++ b/fs/exfat/namei.c +@@ -1015,15 +1015,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_old_entries, num_new_entries; + struct exfat_dentry *epold, *epnew; +- struct super_block *sb = inode->i_sb; ++ struct super_block *sb = parent_inode->i_sb; + struct buffer_head *new_bh, *old_bh; +- int sync = IS_DIRSYNC(inode); ++ int sync = IS_DIRSYNC(parent_inode); + + epold = exfat_get_dentry(sb, p_dir, oldentry, &old_bh); + if (!epold) +@@ -1041,8 +1041,8 @@ static int exfat_rename_file(struct inod + if (num_old_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); + if (newentry < 0) + return newentry; /* -EIO or -ENOSPC */ + +@@ -1073,12 +1073,12 @@ static int exfat_rename_file(struct inod + brelse(old_bh); + brelse(new_bh); + +- ret = exfat_init_ext_entry(inode, p_dir, newentry, ++ ret = exfat_init_ext_entry(parent_inode, p_dir, newentry, + num_new_entries, p_uniname); + if (ret) + return ret; + +- exfat_remove_entries(inode, p_dir, oldentry, 0, ++ exfat_remove_entries(parent_inode, p_dir, oldentry, 0, + num_old_entries); + ei->dir = *p_dir; + ei->entry = newentry; +@@ -1090,10 +1090,10 @@ static int exfat_rename_file(struct inod + exfat_update_bh(old_bh, sync); + brelse(old_bh); + +- exfat_remove_entries(inode, p_dir, oldentry, ++ exfat_remove_entries(parent_inode, p_dir, oldentry, + ES_IDX_FIRST_FILENAME + 1, num_old_entries); + +- ret = exfat_init_ext_entry(inode, p_dir, oldentry, ++ ret = exfat_init_ext_entry(parent_inode, p_dir, oldentry, + num_new_entries, p_uniname); + if (ret) + return ret; +@@ -1101,13 +1101,13 @@ static int exfat_rename_file(struct inod + return 0; + } + +-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, num_old_entries; + struct exfat_dentry *epmov, *epnew; +- struct super_block *sb = inode->i_sb; ++ struct super_block *sb = parent_inode->i_sb; + struct buffer_head *mov_bh, *new_bh; + + epmov = exfat_get_dentry(sb, p_olddir, oldentry, &mov_bh); +@@ -1124,7 +1124,8 @@ static int exfat_move_file(struct inode + if (num_new_entries < 0) + return num_new_entries; + +- newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries); ++ newentry = exfat_find_empty_entry(parent_inode, p_newdir, ++ num_new_entries); + if (newentry < 0) + return newentry; /* -EIO or -ENOSPC */ + +@@ -1137,7 +1138,7 @@ static int exfat_move_file(struct inode + epnew->dentry.file.attr |= cpu_to_le16(ATTR_ARCHIVE); + ei->attr |= ATTR_ARCHIVE; + } +- exfat_update_bh(new_bh, IS_DIRSYNC(inode)); ++ exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode)); + brelse(mov_bh); + brelse(new_bh); + +@@ -1151,16 +1152,17 @@ static int exfat_move_file(struct inode + } + + *epnew = *epmov; +- exfat_update_bh(new_bh, IS_DIRSYNC(inode)); ++ exfat_update_bh(new_bh, IS_DIRSYNC(parent_inode)); + brelse(mov_bh); + brelse(new_bh); + +- ret = exfat_init_ext_entry(inode, p_newdir, newentry, num_new_entries, +- p_uniname); ++ ret = exfat_init_ext_entry(parent_inode, p_newdir, newentry, ++ num_new_entries, p_uniname); + if (ret) + return ret; + +- exfat_remove_entries(inode, p_olddir, oldentry, 0, num_old_entries); ++ exfat_remove_entries(parent_inode, p_olddir, oldentry, 0, ++ num_old_entries); + + exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size, + p_newdir->flags); diff --git a/queue-6.6/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch b/queue-6.6/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch new file mode 100644 index 0000000000..cc080400ba --- /dev/null +++ b/queue-6.6/gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch @@ -0,0 +1,164 @@ +From stable+bounces-274995-greg=kroah.com@vger.kernel.org Wed Jul 15 18:33:06 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:32:56 -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: <20260715163256.953315-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 +[ adjusted context for 6.6's pre-MMIO `iobase` port I/O and void `sch_gpio_set()` (no set_rv `return 0;`) ] +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 +@@ -38,7 +38,7 @@ + + struct sch_gpio { + struct gpio_chip chip; +- spinlock_t lock; ++ raw_spinlock_t lock; + unsigned short iobase; + unsigned short resume_base; + +@@ -102,9 +102,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; + } + +@@ -120,9 +120,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, +@@ -131,9 +131,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 +@@ -193,14 +193,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; + } +@@ -212,9 +212,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) +@@ -222,9 +222,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) +@@ -265,12 +265,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 = inl(sch->iobase + CORE_BANK_OFFSET + GTS); + resume_status = inl(sch->iobase + 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) +@@ -336,7 +336,7 @@ static int sch_gpio_probe(struct platfor + pdev->name)) + return -EBUSY; + +- spin_lock_init(&sch->lock); ++ raw_spin_lock_init(&sch->lock); + sch->iobase = res->start; + sch->chip = sch_gpio_chip; + sch->chip.label = dev_name(&pdev->dev); diff --git a/queue-6.6/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch b/queue-6.6/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch new file mode 100644 index 0000000000..89fc8bf9c3 --- /dev/null +++ b/queue-6.6/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch @@ -0,0 +1,69 @@ +From stable+bounces-277240-greg=kroah.com@vger.kernel.org Sat Jul 18 03:34:08 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 21:34:00 -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: <20260718013400.2623033-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 +@@ -613,7 +613,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.6/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch b/queue-6.6/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch new file mode 100644 index 0000000000..3949a50832 --- /dev/null +++ b/queue-6.6/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch @@ -0,0 +1,957 @@ +From stable+bounces-277241-greg=kroah.com@vger.kernel.org Sat Jul 18 03:34:10 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 21:33:59 -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: <20260718013400.2623033-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 page **pagep, 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 page **pagep, ++ 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 *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 +@@ -50,7 +50,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 page **pagep, void **fsdata) ++ loff_t pos, unsigned int len, struct page **pagep, ++ 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 +@@ -388,21 +388,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); +@@ -417,7 +417,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 */ +@@ -430,7 +430,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 */ +@@ -588,14 +588,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); + } +@@ -604,7 +604,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; + +@@ -614,12 +614,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.6/hid-add-haptics-page-defines.patch b/queue-6.6/hid-add-haptics-page-defines.patch new file mode 100644 index 0000000000..401221131b --- /dev/null +++ b/queue-6.6/hid-add-haptics-page-defines.patch @@ -0,0 +1,78 @@ +From stable+bounces-277063-greg=kroah.com@vger.kernel.org Fri Jul 17 16:13:39 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:13:00 -0400 +Subject: HID: add haptics page defines +To: stable@vger.kernel.org +Cc: Angela Czubak , Jonathan Denose , Benjamin Tissoires , Sasha Levin +Message-ID: <20260717141302.1738251-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.6/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch b/queue-6.6/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch new file mode 100644 index 0000000000..8dcada3c50 --- /dev/null +++ b/queue-6.6/hid-appleir-fix-uaf-on-pending-key_up_timer-in-remove.patch @@ -0,0 +1,150 @@ +From stable+bounces-277094-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:31 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:59:59 -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: <20260717145959.1930377-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.6/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch b/queue-6.6/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch new file mode 100644 index 0000000000..d8eb48c10d --- /dev/null +++ b/queue-6.6/hid-multitouch-fix-out-of-bounds-bit-access-on-mt_io_flags.patch @@ -0,0 +1,158 @@ +From stable+bounces-277064-greg=kroah.com@vger.kernel.org Fri Jul 17 16:13:09 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:13:01 -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: <20260717141302.1738251-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 +@@ -83,8 +84,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; +@@ -947,7 +946,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); +@@ -1153,9 +1152,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; +@@ -1290,7 +1289,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 +@@ -1330,6 +1329,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), +@@ -1738,7 +1746,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); +@@ -1761,7 +1769,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.6/iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch b/queue-6.6/iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch new file mode 100644 index 0000000000..5270d6b303 --- /dev/null +++ b/queue-6.6/iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch @@ -0,0 +1,109 @@ +From stable+bounces-274001-greg=kroah.com@vger.kernel.org Mon Jul 13 22:39:34 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 16:39:24 -0400 +Subject: iio: common: st_sensors: honour channel endianness in read_axis_data +To: stable@vger.kernel.org +Cc: Herman van Hazendonk , Andy Shevchenko , Jonathan Cameron , Sasha Levin +Message-ID: <20260713203924.2143667-2-sashal@kernel.org> + +From: Herman van Hazendonk + +[ Upstream commit 55052184ac9011db2ea983e54d6c21f0b1079a12 ] + +st_sensors_read_axis_data() unconditionally decoded multi-byte +results with get_unaligned_le16() / get_unaligned_le24() regardless +of the channel's declared scan_type.endianness. + +For every ST sensor that has used this helper since it was introduced +this happened to be fine because the ST IMU/accel/gyro/pressure +families publish their data registers as little-endian and the +channel specs in those drivers declare IIO_LE accordingly. + +The LSM303DLH magnetometer however publishes its X/Y/Z output as a +pair of big-endian bytes (the H register sits at the lower address, +0x03/0x05/0x07, and the L register immediately after), and its +channel specs in st_magn_core.c correctly declare IIO_BE -- but +read_axis_data() ignored that and decoded as little-endian, swapping +the high and low bytes of every magnetometer sample. The LSM303DLHC +and LSM303DLM share the same st_magn_16bit_channels (IIO_BE) and +were therefore byte-swapped by the same bug; users of those parts +will see different in_magn_*_raw values after this fix lands. + +The bug is most visible on a stationary chip: in earth's field the +true X reading is small and the high byte sits at 0x00, so swapping +the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0 += 240). Y and Z still appear "to vary" because their magnitudes are +larger and the noise in the low byte produces big swings in the +swapped high byte: + + before (LSM303DLH flat, sysfs in_magn_*_raw): + X=240 (stuck), Y= 12032..23296, Z=-16128..-9728 + + after (direct i2c-dev big-endian decode, same chip same orientation): + X≈-4096, Y≈210, Z≈80 (sensible values reflecting earth's + ambient field at low gauss range) + +Fix read_axis_data() to dispatch on ch->scan_type.endianness and +call get_unaligned_be16() / get_unaligned_be24() when the channel +declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro, +st_pressure, st_lsm6dsx and others) are unaffected because their +channel specs already declare IIO_LE and the LE path is unchanged. + +While restructuring the branches, replace the previously implicit +silent-success-with-uninitialised-*data fall-through for +byte_for_channel outside 1..3 with an explicit return -EINVAL. No +in-tree ST sensor publishes such a channel, but the new behaviour +is strictly safer than handing userspace garbage. + +Fixes: 23491b513bcd ("iio:common: Add STMicroelectronics common library") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 sparse smatch clang-analyzer coccinelle checkpatch +Assisted-by: Sashiko:claude-opus-4-7 +Signed-off-by: Herman van Hazendonk +Reviewed-by: Andy Shevchenko +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/common/st_sensors/st_sensors_core.c | 23 +++++++++++++++++------ + 1 file changed, 17 insertions(+), 6 deletions(-) + +--- a/drivers/iio/common/st_sensors/st_sensors_core.c ++++ b/drivers/iio/common/st_sensors/st_sensors_core.c +@@ -498,6 +498,7 @@ static int st_sensors_read_axis_data(str + u8 *outdata; + struct st_sensor_data *sdata = iio_priv(indio_dev); + unsigned int byte_for_channel; ++ u32 tmp; + + byte_for_channel = DIV_ROUND_UP(ch->scan_type.realbits + + ch->scan_type.shift, 8); +@@ -510,12 +511,22 @@ static int st_sensors_read_axis_data(str + if (err < 0) + goto st_sensors_free_memory; + +- if (byte_for_channel == 1) +- *data = (s8)*outdata; +- else if (byte_for_channel == 2) +- *data = (s16)get_unaligned_le16(outdata); +- else if (byte_for_channel == 3) +- *data = (s32)sign_extend32(get_unaligned_le24(outdata), 23); ++ if (byte_for_channel == 1) { ++ tmp = *outdata; ++ } else if (byte_for_channel == 2) { ++ if (ch->scan_type.endianness == IIO_BE) ++ tmp = get_unaligned_be16(outdata); ++ else ++ tmp = get_unaligned_le16(outdata); ++ } else if (byte_for_channel == 3) { ++ if (ch->scan_type.endianness == IIO_BE) ++ tmp = get_unaligned_be24(outdata); ++ else ++ tmp = get_unaligned_le24(outdata); ++ } else { ++ return -EINVAL; ++ } ++ *data = sign_extend32(tmp, BYTES_TO_BITS(byte_for_channel) - 1); + + st_sensors_free_memory: + kfree(outdata); diff --git a/queue-6.6/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch b/queue-6.6/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch new file mode 100644 index 0000000000..cab095a166 --- /dev/null +++ b/queue-6.6/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch @@ -0,0 +1,89 @@ +From stable+bounces-273949-greg=kroah.com@vger.kernel.org Mon Jul 13 20:16:49 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 14:13:19 -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: <20260713181319.1932247-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.6/iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch b/queue-6.6/iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch new file mode 100644 index 0000000000..a285074e59 --- /dev/null +++ b/queue-6.6/iio-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch @@ -0,0 +1,49 @@ +From stable+bounces-273934-greg=kroah.com@vger.kernel.org Mon Jul 13 19:57:57 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 13:57:46 -0400 +Subject: iio: imu: adis: add IRQF_NO_THREAD to non-FIFO trigger IRQ +To: stable@vger.kernel.org +Cc: Runyu Xiao , Jonathan Cameron , Sasha Levin +Message-ID: <20260713175746.1907755-1-sashal@kernel.org> + +From: Runyu Xiao + +[ Upstream commit 6e1b9bff1202da55c464e36bd34a2b6863d7fe30 ] + +devm_adis_probe_trigger() registers iio_trigger_generic_data_rdy_poll() +through devm_request_irq() on the non-FIFO path, but it does not add +IRQF_NO_THREAD to the IRQ flags. + +When the kernel is booted with forced IRQ threading, the parent IRQ can +otherwise be threaded by the IRQ core and the subsequent IIO trigger +child IRQ is then dispatched from irq/... thread context instead of +hardirq context. Because iio_trigger_generic_data_rdy_poll() +immediately drives iio_trigger_poll(), this violates the hardirq-only +IIO trigger helper contract and can push downstream trigger consumers +through the wrong execution context. + +Add IRQF_NO_THREAD on top of the existing adis->irq_flag value for the +non-FIFO request_irq() path, while preserving the current trigger +polarity and IRQF_NO_AUTOEN behavior. + +Fixes: fec86c6b8369 ("iio: imu: adis: Add Managed device functions") +Cc: stable@vger.kernel.org +Signed-off-by: Runyu Xiao +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/imu/adis_trigger.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/iio/imu/adis_trigger.c ++++ b/drivers/iio/imu/adis_trigger.c +@@ -79,7 +79,7 @@ int devm_adis_probe_trigger(struct adis + + ret = devm_request_irq(&adis->spi->dev, adis->spi->irq, + &iio_trigger_generic_data_rdy_poll, +- adis->irq_flag, ++ adis->irq_flag | IRQF_NO_THREAD, + indio_dev->name, + adis->trig); + if (ret) diff --git a/queue-6.6/iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch b/queue-6.6/iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch new file mode 100644 index 0000000000..a644523db6 --- /dev/null +++ b/queue-6.6/iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch @@ -0,0 +1,72 @@ +From stable+bounces-273961-greg=kroah.com@vger.kernel.org Mon Jul 13 20:33:59 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 14:33:48 -0400 +Subject: iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Jonathan Cameron , Sasha Levin +Message-ID: <20260713183348.1951183-4-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit affe3f077d7a4eeb25937f5323ff059a54b4712c ] + +Timestamps are made by measuring the chip clock using the watermark +interrupts. If we read more than watermark samples as done today, we +are reducing the period between interrupts and distort the time +measurement. Fix that by reading only watermark samples in the +interrupt case. + +Fixes: 7f85e42a6c54 ("iio: imu: inv_icm42600: add buffer support in iio devices") +Cc: stable@vger.kernel.org +Signed-off-by: Jean-Baptiste Maneyrol +Signed-off-by: Jonathan Cameron +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 9 +++++---- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h | 1 + + 2 files changed, 6 insertions(+), 4 deletions(-) + +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +@@ -257,6 +257,7 @@ int inv_icm42600_buffer_update_watermark + + /* compute watermark value in bytes */ + wm_size = watermark * packet_size; ++ st->fifo.watermark.value = watermark; + + /* changing FIFO watermark requires to turn off watermark interrupt */ + ret = regmap_update_bits_check(st->map, INV_ICM42600_REG_INT_SOURCE0, +@@ -476,11 +477,10 @@ int inv_icm42600_buffer_fifo_read(struct + st->fifo.nb.accel = 0; + st->fifo.nb.total = 0; + +- /* compute maximum FIFO read size */ ++ /* compute maximum FIFO read size (watermark for max = 0 interrupt case) */ + if (max == 0) +- max_count = sizeof(st->fifo.data); +- else +- max_count = max * inv_icm42600_get_packet_size(st->fifo.en); ++ max = st->fifo.watermark.value; ++ max_count = max * inv_icm42600_get_packet_size(st->fifo.en); + + /* read FIFO count value */ + raw_fifo_count = (__be16 *)st->buffer; +@@ -592,6 +592,7 @@ int inv_icm42600_buffer_init(struct inv_ + + st->fifo.watermark.eff_gyro = 1; + st->fifo.watermark.eff_accel = 1; ++ st->fifo.watermark.value = 1; + + /* + * Default FIFO configuration (bits 7 to 5) +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h +@@ -34,6 +34,7 @@ struct inv_icm42600_fifo { + unsigned int accel; + unsigned int eff_gyro; + unsigned int eff_accel; ++ unsigned int value; + } watermark; + size_t count; + struct { diff --git a/queue-6.6/iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch b/queue-6.6/iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch new file mode 100644 index 0000000000..c8ee896905 --- /dev/null +++ b/queue-6.6/iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch @@ -0,0 +1,116 @@ +From stable+bounces-273960-greg=kroah.com@vger.kernel.org Mon Jul 13 20:33:58 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 14:33:47 -0400 +Subject: iio: imu: inv_icm42600: stabilized timestamp in interrupt +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Jonathan Cameron , Sasha Levin +Message-ID: <20260713183348.1951183-3-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit d7bd473632d07f8a54655c270c0940cc3671c548 ] + +Use IRQF_ONESHOT flag to ensure the timestamp is not updated in the +hard handler during the thread handler. And compute and use the +effective watermark value that correspond to this first timestamp. + +This way we can ensure the timestamp is always corresponding to the +value used by the timestamping mechanism. Otherwise, it is possible +that between FIFO count read and FIFO processing the timestamp is +overwritten in the hard handler. + +Fixes: ec74ae9fd37c ("iio: imu: inv_icm42600: add accurate timestamping") +Cc: stable@vger.kernel.org +Signed-off-by: Jean-Baptiste Maneyrol +Link: https://lore.kernel.org/r/20240529154717.651863-1-inv.git-commit@tdk.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 19 +++++++++++++++++-- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h | 2 ++ + drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 1 + + 3 files changed, 20 insertions(+), 2 deletions(-) + +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +@@ -222,10 +222,15 @@ int inv_icm42600_buffer_update_watermark + latency_accel = period_accel * wm_accel; + + /* 0 value for watermark means that the sensor is turned off */ ++ if (wm_gyro == 0 && wm_accel == 0) ++ return 0; ++ + if (latency_gyro == 0) { + watermark = wm_accel; ++ st->fifo.watermark.eff_accel = wm_accel; + } else if (latency_accel == 0) { + watermark = wm_gyro; ++ st->fifo.watermark.eff_gyro = wm_gyro; + } else { + /* compute the smallest latency that is a multiple of both */ + if (latency_gyro <= latency_accel) +@@ -241,6 +246,13 @@ int inv_icm42600_buffer_update_watermark + watermark = latency / period; + if (watermark < 1) + watermark = 1; ++ /* update effective watermark */ ++ st->fifo.watermark.eff_gyro = latency / period_gyro; ++ if (st->fifo.watermark.eff_gyro < 1) ++ st->fifo.watermark.eff_gyro = 1; ++ st->fifo.watermark.eff_accel = latency / period_accel; ++ if (st->fifo.watermark.eff_accel < 1) ++ st->fifo.watermark.eff_accel = 1; + } + + /* compute watermark value in bytes */ +@@ -517,7 +529,7 @@ int inv_icm42600_buffer_fifo_parse(struc + /* handle gyroscope timestamp and FIFO data parsing */ + if (st->fifo.nb.gyro > 0) { + ts = iio_priv(st->indio_gyro); +- inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, ++ inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro, + st->timestamp.gyro); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) +@@ -527,7 +539,7 @@ int inv_icm42600_buffer_fifo_parse(struc + /* handle accelerometer timestamp and FIFO data parsing */ + if (st->fifo.nb.accel > 0) { + ts = iio_priv(st->indio_accel); +- inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, ++ inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel, + st->timestamp.accel); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) +@@ -578,6 +590,9 @@ int inv_icm42600_buffer_init(struct inv_ + unsigned int val; + int ret; + ++ st->fifo.watermark.eff_gyro = 1; ++ st->fifo.watermark.eff_accel = 1; ++ + /* + * Default FIFO configuration (bits 7 to 5) + * - use invalid value +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h +@@ -32,6 +32,8 @@ struct inv_icm42600_fifo { + struct { + unsigned int gyro; + unsigned int accel; ++ unsigned int eff_gyro; ++ unsigned int eff_accel; + } watermark; + size_t count; + struct { +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +@@ -523,6 +523,7 @@ static int inv_icm42600_irq_init(struct + if (ret) + return ret; + ++ irq_type |= IRQF_ONESHOT; + return devm_request_threaded_irq(dev, irq, inv_icm42600_irq_timestamp, + inv_icm42600_irq_handler, irq_type, + "inv_icm42600", st); diff --git a/queue-6.6/iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch b/queue-6.6/iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch new file mode 100644 index 0000000000..2ac9391ffb --- /dev/null +++ b/queue-6.6/iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch @@ -0,0 +1,185 @@ +From stable+bounces-273959-greg=kroah.com@vger.kernel.org Mon Jul 13 20:34:02 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 14:33:46 -0400 +Subject: iio: invensense: fix timestamp glitches when switching frequency +To: stable@vger.kernel.org +Cc: Jean-Baptiste Maneyrol , Stable@vger.kernel.org, Jonathan Cameron , Sasha Levin +Message-ID: <20260713183348.1951183-2-sashal@kernel.org> + +From: Jean-Baptiste Maneyrol + +[ Upstream commit bf8367b00c33c64a9391c262bb2e11d274c9f2a4 ] + +When a sensor is running and there is a FIFO frequency change due to +another sensor turned on/off, there are glitches on timestamp. Fix that +by using only interrupt timestamp when there is the corresponding sensor +data in the FIFO. + +Delete FIFO period handling and simplify internal functions. + +Update integration inside inv_mpu6050 and inv_icm42600 drivers. + +Fixes: 0ecc363ccea7 ("iio: make invensense timestamp module generic") +Cc: Stable@vger.kernel.org +Signed-off-by: Jean-Baptiste Maneyrol +Link: https://lore.kernel.org/r/20240426094835.138389-1-inv.git-commit@tdk.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/common/inv_sensors/inv_sensors_timestamp.c | 24 +++++++---------- + drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 20 +++++--------- + drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 2 - + include/linux/iio/common/inv_sensors_timestamp.h | 3 -- + 4 files changed, 21 insertions(+), 28 deletions(-) + +--- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c ++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c +@@ -78,13 +78,13 @@ int inv_sensors_timestamp_update_odr(str + } + EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_update_odr, IIO_INV_SENSORS_TIMESTAMP); + +-static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period, uint32_t mult) ++static bool inv_validate_period(struct inv_sensors_timestamp *ts, uint32_t period) + { + uint32_t period_min, period_max; + + /* check that period is acceptable */ +- period_min = ts->min_period * mult; +- period_max = ts->max_period * mult; ++ period_min = ts->min_period * ts->mult; ++ period_max = ts->max_period * ts->mult; + if (period > period_min && period < period_max) + return true; + else +@@ -92,15 +92,15 @@ static bool inv_validate_period(struct i + } + + static bool inv_update_chip_period(struct inv_sensors_timestamp *ts, +- uint32_t mult, uint32_t period) ++ uint32_t period) + { + uint32_t new_chip_period; + +- if (!inv_validate_period(ts, period, mult)) ++ if (!inv_validate_period(ts, period)) + return false; + + /* update chip internal period estimation */ +- new_chip_period = period / mult; ++ new_chip_period = period / ts->mult; + inv_update_acc(&ts->chip_period, new_chip_period); + ts->period = ts->mult * ts->chip_period.val; + +@@ -133,16 +133,14 @@ static void inv_align_timestamp_it(struc + } + + void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- size_t sensor_nb, int64_t timestamp) ++ size_t sample_nb, int64_t timestamp) + { + struct inv_sensors_timestamp_interval *it; + int64_t delta, interval; +- const uint32_t fifo_mult = fifo_period / ts->chip.clock_period; + uint32_t period; + bool valid = false; + +- if (fifo_nb == 0) ++ if (sample_nb == 0) + return; + + /* update interrupt timestamp and compute chip and sensor periods */ +@@ -152,14 +150,14 @@ void inv_sensors_timestamp_interrupt(str + delta = it->up - it->lo; + if (it->lo != 0) { + /* compute period: delta time divided by number of samples */ +- period = div_s64(delta, fifo_nb); +- valid = inv_update_chip_period(ts, fifo_mult, period); ++ period = div_s64(delta, sample_nb); ++ valid = inv_update_chip_period(ts, period); + } + + /* no previous data, compute theoritical value from interrupt */ + if (ts->timestamp == 0) { + /* elapsed time: sensor period * sensor samples number */ +- interval = (int64_t)ts->period * (int64_t)sensor_nb; ++ interval = (int64_t)ts->period * (int64_t)sample_nb; + ts->timestamp = it->up - interval; + return; + } +--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c ++++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +@@ -515,20 +515,20 @@ int inv_icm42600_buffer_fifo_parse(struc + return 0; + + /* handle gyroscope timestamp and FIFO data parsing */ +- ts = iio_priv(st->indio_gyro); +- inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, +- st->fifo.nb.gyro, st->timestamp.gyro); + if (st->fifo.nb.gyro > 0) { ++ ts = iio_priv(st->indio_gyro); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, ++ st->timestamp.gyro); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + /* handle accelerometer timestamp and FIFO data parsing */ +- ts = iio_priv(st->indio_accel); +- inv_sensors_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, +- st->fifo.nb.accel, st->timestamp.accel); + if (st->fifo.nb.accel > 0) { ++ ts = iio_priv(st->indio_accel); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, ++ st->timestamp.accel); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; +@@ -556,9 +556,7 @@ int inv_icm42600_buffer_hwfifo_flush(str + + if (st->fifo.nb.gyro > 0) { + ts = iio_priv(st->indio_gyro); +- inv_sensors_timestamp_interrupt(ts, st->fifo.period, +- st->fifo.nb.total, st->fifo.nb.gyro, +- gyro_ts); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; +@@ -566,9 +564,7 @@ int inv_icm42600_buffer_hwfifo_flush(str + + if (st->fifo.nb.accel > 0) { + ts = iio_priv(st->indio_accel); +- inv_sensors_timestamp_interrupt(ts, st->fifo.period, +- st->fifo.nb.total, st->fifo.nb.accel, +- accel_ts); ++ inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; +--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c ++++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +@@ -113,7 +113,7 @@ irqreturn_t inv_mpu6050_read_fifo(int ir + goto end_session; + /* Each FIFO data contains all sensors, so same number for FIFO and sensor data */ + fifo_period = NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); +- inv_sensors_timestamp_interrupt(&st->timestamp, fifo_period, nb, nb, pf->timestamp); ++ inv_sensors_timestamp_interrupt(&st->timestamp, nb, pf->timestamp); + inv_sensors_timestamp_apply_odr(&st->timestamp, fifo_period, nb, 0); + + /* clear internal data buffer for avoiding kernel data leak */ +--- a/include/linux/iio/common/inv_sensors_timestamp.h ++++ b/include/linux/iio/common/inv_sensors_timestamp.h +@@ -71,8 +71,7 @@ int inv_sensors_timestamp_update_odr(str + uint32_t period, bool fifo); + + void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, +- uint32_t fifo_period, size_t fifo_nb, +- size_t sensor_nb, int64_t timestamp); ++ size_t sample_nb, int64_t timestamp); + + static inline int64_t inv_sensors_timestamp_pop(struct inv_sensors_timestamp *ts) + { diff --git a/queue-6.6/iio-invensense-remove-redundant-initialization-of-variable-period.patch b/queue-6.6/iio-invensense-remove-redundant-initialization-of-variable-period.patch new file mode 100644 index 0000000000..9a09bfa96d --- /dev/null +++ b/queue-6.6/iio-invensense-remove-redundant-initialization-of-variable-period.patch @@ -0,0 +1,42 @@ +From stable+bounces-273958-greg=kroah.com@vger.kernel.org Mon Jul 13 20:33:55 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 14:33:45 -0400 +Subject: iio: invensense: remove redundant initialization of variable period +To: stable@vger.kernel.org +Cc: Colin Ian King , Jean-Baptiste Maneyrol , Jonathan Cameron , Sasha Levin +Message-ID: <20260713183348.1951183-1-sashal@kernel.org> + +From: Colin Ian King + +[ Upstream commit b58b13f156c00c2457035b7071eaaac105fe6836 ] + +The variable period is being initialized with a value that is never +read, it is being re-assigned a new value later on before it is read. +The initialization is redundant and can be removed. + +Cleans up clang scan build warning: +Value stored to 'period' during its initialization is never +read [deadcode.DeadStores] + +Signed-off-by: Colin Ian King +Acked-by: Jean-Baptiste Maneyrol +Link: https://lore.kernel.org/r/20240106153202.54861-1-colin.i.king@gmail.com +Signed-off-by: Jonathan Cameron +Stable-dep-of: affe3f077d7a ("iio: imu: inv_icm42600: fix timestamping by limiting FIFO reading") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/common/inv_sensors/inv_sensors_timestamp.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c ++++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c +@@ -139,7 +139,7 @@ void inv_sensors_timestamp_interrupt(str + struct inv_sensors_timestamp_interval *it; + int64_t delta, interval; + const uint32_t fifo_mult = fifo_period / ts->chip.clock_period; +- uint32_t period = ts->period; ++ uint32_t period; + bool valid = false; + + if (fifo_nb == 0) diff --git a/queue-6.6/iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch b/queue-6.6/iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch new file mode 100644 index 0000000000..bc45ea0a83 --- /dev/null +++ b/queue-6.6/iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch @@ -0,0 +1,60 @@ +From stable+bounces-273981-greg=kroah.com@vger.kernel.org Mon Jul 13 21:44:36 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 15:43:07 -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: <20260713194307.2064131-1-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 +[ moved pm_runtime_mark_last_busy() together with pm_runtime_put_autosuspend() ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/iio/pressure/mpl115.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/drivers/iio/pressure/mpl115.c ++++ b/drivers/iio/pressure/mpl115.c +@@ -106,20 +106,20 @@ 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); +- if (ret < 0) +- return ret; + pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); ++ if (ret < 0) ++ return ret; + + 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); +- if (ret < 0) +- return ret; + pm_runtime_mark_last_busy(data->dev); + pm_runtime_put_autosuspend(data->dev); ++ if (ret < 0) ++ return ret; + *val = ret >> 6; + + return IIO_VAL_INT; diff --git a/queue-6.6/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch b/queue-6.6/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch new file mode 100644 index 0000000000..1cad4a11d5 --- /dev/null +++ b/queue-6.6/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch @@ -0,0 +1,457 @@ +From stable+bounces-275395-greg=kroah.com@vger.kernel.org Thu Jul 16 15:39:07 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 09:37:25 -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: <20260716133726.212306-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" +@@ -116,6 +117,7 @@ struct ksmbd_conn { + bool binding; + atomic_t refcnt; + bool is_aapl; ++ struct work_struct release_work; + }; + + struct ksmbd_conn_ops { +@@ -161,6 +163,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); + } +@@ -1049,25 +1069,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; +@@ -1078,6 +1105,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; + } + +@@ -1164,15 +1193,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); +@@ -1184,8 +1225,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.6/ksmbd-track-the-connection-owning-a-byte-range-lock.patch b/queue-6.6/ksmbd-track-the-connection-owning-a-byte-range-lock.patch new file mode 100644 index 0000000000..7b16b44ae7 --- /dev/null +++ b/queue-6.6/ksmbd-track-the-connection-owning-a-byte-range-lock.patch @@ -0,0 +1,138 @@ +From stable+bounces-275394-greg=kroah.com@vger.kernel.org Thu Jul 16 15:38:08 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 09:37:26 -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: <20260716133726.212306-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 +@@ -7580,9 +7580,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; +@@ -7717,6 +7719,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); +@@ -7771,11 +7774,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); +@@ -1092,9 +1096,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; +@@ -1215,6 +1225,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.6/ksmbd-use-opener-credentials-for-fsctl-mutations.patch b/queue-6.6/ksmbd-use-opener-credentials-for-fsctl-mutations.patch new file mode 100644 index 0000000000..6b437c6b32 --- /dev/null +++ b/queue-6.6/ksmbd-use-opener-credentials-for-fsctl-mutations.patch @@ -0,0 +1,86 @@ +From stable+bounces-275316-greg=kroah.com@vger.kernel.org Thu Jul 16 13:52:33 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:52:27 -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: <20260716115227.1785304-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 +@@ -8194,6 +8194,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, +@@ -8202,9 +8203,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 +@@ -994,15 +994,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.6/media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.patch b/queue-6.6/media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.patch new file mode 100644 index 0000000000..93a2e3e8d0 --- /dev/null +++ b/queue-6.6/media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.patch @@ -0,0 +1,64 @@ +From stable+bounces-275003-greg=kroah.com@vger.kernel.org Wed Jul 15 18:55:20 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:54:08 -0400 +Subject: media: nxp: imx8-isi: Convert to platform remove callback returning void +To: stable@vger.kernel.org +Cc: "Uwe Kleine-König" , "Laurent Pinchart" , "Hans Verkuil" , "Sasha Levin" +Message-ID: <20260715165410.963211-1-sashal@kernel.org> + +From: Uwe Kleine-König + +[ Upstream commit e992ee7eb56f827088f63c9d5210ab4da25a5c4d ] + +The .remove() callback for a platform driver returns an int which makes +many driver authors wrongly assume it's possible to do error handling by +returning an error code. However the value returned is ignored (apart +from emitting a warning) and this typically results in resource leaks. + +To improve here there is a quest to make the remove callback return +void. In the first step of this quest all drivers are converted to +.remove_new(), which already returns void. Eventually after all drivers +are converted, .remove_new() will be renamed to .remove(). + +Trivially convert this driver from always returning zero in the remove +callback to the void returning variant. + +Signed-off-by: Uwe Kleine-König +Reviewed-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 | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c ++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +@@ -508,7 +508,7 @@ err_pm: + return ret; + } + +-static int mxc_isi_remove(struct platform_device *pdev) ++static void mxc_isi_remove(struct platform_device *pdev) + { + struct mxc_isi_dev *isi = platform_get_drvdata(pdev); + unsigned int i; +@@ -525,8 +525,6 @@ static int mxc_isi_remove(struct platfor + mxc_isi_v4l2_cleanup(isi); + + pm_runtime_disable(isi->dev); +- +- return 0; + } + + static const struct of_device_id mxc_isi_of_match[] = { +@@ -539,7 +537,7 @@ MODULE_DEVICE_TABLE(of, mxc_isi_of_match + + static struct platform_driver mxc_isi_driver = { + .probe = mxc_isi_probe, +- .remove = mxc_isi_remove, ++ .remove_new = mxc_isi_remove, + .driver = { + .of_match_table = mxc_isi_of_match, + .name = MXC_ISI_DRIVER_NAME, diff --git a/queue-6.6/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch b/queue-6.6/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch new file mode 100644 index 0000000000..6eb8ffd2b0 --- /dev/null +++ b/queue-6.6/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch @@ -0,0 +1,73 @@ +From stable+bounces-275004-greg=kroah.com@vger.kernel.org Wed Jul 15 18:55:26 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:54:10 -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: <20260715165410.963211-3-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 +@@ -521,8 +521,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.6/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch b/queue-6.6/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch new file mode 100644 index 0000000000..4b972e28de --- /dev/null +++ b/queue-6.6/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch @@ -0,0 +1,70 @@ +From stable+bounces-275002-greg=kroah.com@vger.kernel.org Wed Jul 15 18:54:20 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 12:54:09 -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: <20260715165410.963211-2-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 +@@ -474,13 +474,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); +@@ -503,8 +504,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; + } + +@@ -523,8 +523,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.6/mm-do-file-ownership-checks-with-the-proper-mount-idmap.patch b/queue-6.6/mm-do-file-ownership-checks-with-the-proper-mount-idmap.patch new file mode 100644 index 0000000000..3bd0af00d6 --- /dev/null +++ b/queue-6.6/mm-do-file-ownership-checks-with-the-proper-mount-idmap.patch @@ -0,0 +1,107 @@ +From stable+bounces-278565-greg=kroah.com@vger.kernel.org Tue Jul 21 13:47:26 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 07:47:16 -0400 +Subject: mm: do file ownership checks with the proper mount idmap +To: stable@vger.kernel.org +Cc: Pedro Falcato , Jan Kara , "Christian Brauner (Amutable)" , "David Hildenbrand (Arm)" , Al Viro , Jann Horn , "Liam R. Howlett" , "Matthew Wilcox (Oracle)" , Vlastimil Babka , Andrew Morton , Sasha Levin +Message-ID: <20260721114716.3688887-1-sashal@kernel.org> + +From: Pedro Falcato + +[ Upstream commit e187bc02f8fa4226d62814592cf064ee4557c470 ] + +Ever since idmapped mounts were introduced, inode ownership checks (for +side-channel protection) in mincore() and madvise(MADV_PAGEOUT) were done +against the nop_mnt_idmap, which completely ignores the file's mount's +idmap. This results in odd edgecases like: + +1) mount/bind-mount with an idmap userA:userB:1 +2) userB runs an owner_or_capable() check on file that is owned by userA +on-disk/in-memory, but owned by userB after idmap translation +3) owner_or_capable() mysteriously fails as the correct idmap wasn't supplied + +In the case of mincore/madvise MADV_PAGEOUT, this is usually benign, +because file_permission(file, MAY_WRITE) will probably succeed, as it uses +the proper idmap internally, but it does not need to be the case on e.g a +0444 file where even the owner itself doesn't have permissions to write to +it. + +Since this is clearly not trivial to get right, introduce a +file_owner_or_capable() that can carry the correct semantics, and switch +the various users in mm to it. + +The issue was found by manual code inspection & an off-list discussion +with Jan Kara. + +Link: https://lore.kernel.org/20260625153853.913949-1-pfalcato@suse.de +Fixes: 9caccd41541a ("fs: introduce MOUNT_ATTR_IDMAP") +Signed-off-by: Pedro Falcato +Reviewed-by: Jan Kara +Reviewed-by: Christian Brauner (Amutable) +Acked-by: David Hildenbrand (Arm) +Cc: Al Viro +Cc: Jann Horn +Cc: Liam R. Howlett +Cc: Matthew Wilcox (Oracle) +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +[ dropped const from file_owner_or_capable()'s parameter since 6.6's file_mnt_idmap() takes a non-const struct file * ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/fs.h | 5 +++++ + mm/filemap.c | 2 +- + mm/madvise.c | 3 +-- + mm/mincore.c | 3 +-- + 4 files changed, 8 insertions(+), 5 deletions(-) + +--- a/include/linux/fs.h ++++ b/include/linux/fs.h +@@ -2481,6 +2481,11 @@ static inline struct mnt_idmap *file_mnt + return mnt_idmap(file->f_path.mnt); + } + ++static inline bool file_owner_or_capable(struct file *file) ++{ ++ return inode_owner_or_capable(file_mnt_idmap(file), file_inode(file)); ++} ++ + /** + * is_idmapped_mnt - check whether a mount is mapped + * @mnt: the mount to check +--- a/mm/filemap.c ++++ b/mm/filemap.c +@@ -4314,7 +4314,7 @@ static inline bool can_do_cachestat(stru + { + if (f->f_mode & FMODE_WRITE) + return true; +- if (inode_owner_or_capable(file_mnt_idmap(f), file_inode(f))) ++ if (file_owner_or_capable(f)) + return true; + return file_permission(f, MAY_WRITE) == 0; + } +--- a/mm/madvise.c ++++ b/mm/madvise.c +@@ -334,8 +334,7 @@ static inline bool can_do_file_pageout(s + * otherwise we'd be including shared non-exclusive mappings, which + * opens a side channel. + */ +- return inode_owner_or_capable(&nop_mnt_idmap, +- file_inode(vma->vm_file)) || ++ return file_owner_or_capable(vma->vm_file) || + file_permission(vma->vm_file, MAY_WRITE) == 0; + } + +--- a/mm/mincore.c ++++ b/mm/mincore.c +@@ -167,8 +167,7 @@ static inline bool can_do_mincore(struct + * for writing; otherwise we'd be including shared non-exclusive + * mappings, which opens a side channel. + */ +- return inode_owner_or_capable(&nop_mnt_idmap, +- file_inode(vma->vm_file)) || ++ return file_owner_or_capable(vma->vm_file) || + file_permission(vma->vm_file, MAY_WRITE) == 0; + } + diff --git a/queue-6.6/mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch b/queue-6.6/mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch new file mode 100644 index 0000000000..1c62c51e4d --- /dev/null +++ b/queue-6.6/mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch @@ -0,0 +1,78 @@ +From stable+bounces-274916-greg=kroah.com@vger.kernel.org Wed Jul 15 13:40:50 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 07:40:37 -0400 +Subject: mm/shrinker: do not hold RCU lock in shrinker_debugfs_count_show() +To: stable@vger.kernel.org +Cc: Shakeel Butt , Zenghui Yu , Nhat Pham , SeongJae Park , Qi Zheng , Muchun Song , Roman Gushchin , Dave Chinner , Andrew Morton , Sasha Levin +Message-ID: <20260715114037.728053-2-sashal@kernel.org> + +From: Shakeel Butt + +[ Upstream commit b902890c62d200b3509cb5e09cf1e0a66553c128 ] + +Reading the debugfs "count" file of a memcg-aware shrinker can sleep +inside an RCU read-side critical section: + + BUG: sleeping function called from invalid context at kernel/cgroup/rstat.c:421 + RCU nest depth: 1, expected: 0 + css_rstat_flush + mem_cgroup_flush_stats + zswap_shrinker_count + shrinker_debugfs_count_show + +shrinker_debugfs_count_show() invokes the ->count_objects() callback under +rcu_read_lock(). The zswap callback flushes memcg stats via +css_rstat_flush(), which may sleep, so it must not run under RCU. + +The RCU lock is not needed here. mem_cgroup_iter() takes RCU internally +and returns a memcg holding a css reference (dropped on the next iteration +or by mem_cgroup_iter_break()), so the memcg stays alive without it. The +shrinker is kept alive by the open debugfs file: shrinker_free() removes +the debugfs entries via debugfs_remove_recursive(), which waits for +in-flight readers to drain, before call_rcu(..., shrinker_free_rcu_cb). +The sibling "scan" handler already invokes the sleeping ->scan_objects() +callback with no RCU section. + +Drop the rcu_read_lock()/rcu_read_unlock(). + +Link: https://lore.kernel.org/20260610232048.62930-1-shakeel.butt@linux.dev +Fixes: 5035ebc644ae ("mm: shrinkers: introduce debugfs interface for memory shrinkers") +Signed-off-by: Shakeel Butt +Reported-by: Zenghui Yu +Closes: https://lore.kernel.org/all/c052a064-cddb-494f-a0d8-f8a10b4b1c4d@linux.dev/ +Suggested-by: Nhat Pham +Reviewed-by: SeongJae Park +Reviewed-by: Qi Zheng +Tested-by: Zenghui Yu (Huawei) +Reviewed-by: Nhat Pham +Acked-by: Muchun Song +Reviewed-by: Roman Gushchin +Cc: Dave Chinner +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + mm/shrinker_debug.c | 4 ---- + 1 file changed, 4 deletions(-) + +--- a/mm/shrinker_debug.c ++++ b/mm/shrinker_debug.c +@@ -55,8 +55,6 @@ static int shrinker_debugfs_count_show(s + if (!count_per_node) + return -ENOMEM; + +- rcu_read_lock(); +- + memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE; + + memcg = mem_cgroup_iter(NULL, NULL, NULL); +@@ -86,8 +84,6 @@ static int shrinker_debugfs_count_show(s + } + } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL); + +- rcu_read_unlock(); +- + kfree(count_per_node); + return ret; + } diff --git a/queue-6.6/mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch b/queue-6.6/mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch new file mode 100644 index 0000000000..0c79624ad8 --- /dev/null +++ b/queue-6.6/mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch @@ -0,0 +1,159 @@ +From stable+bounces-274915-greg=kroah.com@vger.kernel.org Wed Jul 15 13:40:49 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 07:40:36 -0400 +Subject: mm: shrinker: remove redundant shrinker_rwsem in debugfs operations +To: stable@vger.kernel.org +Cc: "Qi Zheng" , "Muchun Song" , "Christian Brauner" , "Christian König" , "Chuck Lever" , "Daniel Vetter" , "Daniel Vetter" , "Darrick J. Wong" , "Dave Chinner" , "Greg Kroah-Hartman" , "Joel Fernandes" , "Kirill Tkhai" , "Paul E. McKenney" , "Roman Gushchin" , "Sergey Senozhatsky" , "Steven Price" , "Theodore Ts'o" , "Vlastimil Babka" , "Abhinav Kumar" , "Alasdair Kergon" , "Alexander Viro" , "Alyssa Rosenzweig" , "Andreas Dilger" , "Andreas Gruenbacher" , "Anna Schumaker" , "Arnd Bergmann" , "Bob Peterson" , "Borislav Petkov" , "Carlos Llamas" , "Chandan Babu R" , "Chao Yu" , "Chris Mason" , "Coly Li" , "Dai Ngo" , "Dave Hansen" , "David Airlie" , "David Hildenbrand" , "David Sterba" , "Dmitry Baryshkov" , "Gao Xiang" , "Huang Rui" , "Ingo Molnar" , "Jaegeuk Kim" , "Jani Nikula" , "Jan Kara" , "Jason Wang" , "Jeff Layton" , "Jeffle Xu" , "Joonas Lahtinen" , "Josef Bacik" , "Juergen Gross" , "Kent Overstreet" , "Marijn Suijten" , "Michael S. Tsirkin" , "Mike Snitzer" , "Minchan Kim" , "Muchun Song" , "Nadav Amit" , "Neil Brown" , "Oleksandr Tyshchenko" , "Olga Kornievskaia" , "Richard Weinberger" , "Rob Clark" , "Rob Herring" , "Rodrigo Vivi" , "Sean Paul" , "Song Liu" , "Stefano Stabellini" , "Thomas Gleixner" , "Tomeu Vizoso" , "Tom Talpey" , "Trond Myklebust" , "Tvrtko Ursulin" , "Xuan Zhuo" , "Yue Hu" , "Andrew Morton" , "Sasha Levin" +Message-ID: <20260715114037.728053-1-sashal@kernel.org> + +From: Qi Zheng + +[ Upstream commit 1dd49e58f966b1eecd935dc28458a8369ae94ad1 ] + +debugfs_remove_recursive() will wait for debugfs_file_put() to return, so +the shrinker will not be freed when doing debugfs operations (such as +shrinker_debugfs_count_show() and shrinker_debugfs_scan_write()), so there +is no need to hold shrinker_rwsem during debugfs operations. + +Link: https://lkml.kernel.org/r/20230911092517.64141-4-zhengqi.arch@bytedance.com +Signed-off-by: Qi Zheng +Reviewed-by: Muchun Song +Cc: Christian Brauner +Cc: Christian König +Cc: Chuck Lever +Cc: Daniel Vetter +Cc: Daniel Vetter +Cc: Darrick J. Wong +Cc: Dave Chinner +Cc: Greg Kroah-Hartman +Cc: Joel Fernandes +Cc: Kirill Tkhai +Cc: Paul E. McKenney +Cc: Roman Gushchin +Cc: Sergey Senozhatsky +Cc: Steven Price +Cc: Theodore Ts'o +Cc: Vlastimil Babka +Cc: Abhinav Kumar +Cc: Alasdair Kergon +Cc: Alexander Viro +Cc: Alyssa Rosenzweig +Cc: Andreas Dilger +Cc: Andreas Gruenbacher +Cc: Anna Schumaker +Cc: Arnd Bergmann +Cc: Bob Peterson +Cc: Borislav Petkov +Cc: Carlos Llamas +Cc: Chandan Babu R +Cc: Chao Yu +Cc: Chris Mason +Cc: Coly Li +Cc: Dai Ngo +Cc: Dave Hansen +Cc: David Airlie +Cc: David Hildenbrand +Cc: David Sterba +Cc: Dmitry Baryshkov +Cc: Gao Xiang +Cc: Huang Rui +Cc: Ingo Molnar +Cc: Jaegeuk Kim +Cc: Jani Nikula +Cc: Jan Kara +Cc: Jason Wang +Cc: Jeff Layton +Cc: Jeffle Xu +Cc: Joonas Lahtinen +Cc: Josef Bacik +Cc: Juergen Gross +Cc: Kent Overstreet +Cc: Marijn Suijten +Cc: "Michael S. Tsirkin" +Cc: Mike Snitzer +Cc: Minchan Kim +Cc: Muchun Song +Cc: Nadav Amit +Cc: Neil Brown +Cc: Oleksandr Tyshchenko +Cc: Olga Kornievskaia +Cc: Richard Weinberger +Cc: Rob Clark +Cc: Rob Herring +Cc: Rodrigo Vivi +Cc: Sean Paul +Cc: Song Liu +Cc: Stefano Stabellini +Cc: Thomas Gleixner +Cc: Tomeu Vizoso +Cc: Tom Talpey +Cc: Trond Myklebust +Cc: Tvrtko Ursulin +Cc: Xuan Zhuo +Cc: Yue Hu +Signed-off-by: Andrew Morton +Stable-dep-of: b902890c62d2 ("mm/shrinker: do not hold RCU lock in shrinker_debugfs_count_show()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + mm/shrinker_debug.c | 16 +--------------- + 1 file changed, 1 insertion(+), 15 deletions(-) + +--- a/mm/shrinker_debug.c ++++ b/mm/shrinker_debug.c +@@ -49,17 +49,12 @@ static int shrinker_debugfs_count_show(s + struct mem_cgroup *memcg; + unsigned long total; + bool memcg_aware; +- int ret, nid; ++ int ret = 0, nid; + + count_per_node = kcalloc(nr_node_ids, sizeof(unsigned long), GFP_KERNEL); + if (!count_per_node) + return -ENOMEM; + +- ret = down_read_killable(&shrinker_rwsem); +- if (ret) { +- kfree(count_per_node); +- return ret; +- } + rcu_read_lock(); + + memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE; +@@ -92,7 +87,6 @@ static int shrinker_debugfs_count_show(s + } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL); + + rcu_read_unlock(); +- up_read(&shrinker_rwsem); + + kfree(count_per_node); + return ret; +@@ -117,7 +111,6 @@ static ssize_t shrinker_debugfs_scan_wri + struct mem_cgroup *memcg = NULL; + int nid; + char kbuf[72]; +- ssize_t ret; + + read_len = size < (sizeof(kbuf) - 1) ? size : (sizeof(kbuf) - 1); + if (copy_from_user(kbuf, buf, read_len)) +@@ -146,12 +139,6 @@ static ssize_t shrinker_debugfs_scan_wri + return -EINVAL; + } + +- ret = down_read_killable(&shrinker_rwsem); +- if (ret) { +- mem_cgroup_put(memcg); +- return ret; +- } +- + sc.nid = nid; + sc.memcg = memcg; + sc.nr_to_scan = nr_to_scan; +@@ -159,7 +146,6 @@ static ssize_t shrinker_debugfs_scan_wri + + shrinker->scan_objects(shrinker, &sc); + +- up_read(&shrinker_rwsem); + mem_cgroup_put(memcg); + + return size; diff --git a/queue-6.6/mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.patch b/queue-6.6/mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.patch new file mode 100644 index 0000000000..d84b6de1b6 --- /dev/null +++ b/queue-6.6/mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.patch @@ -0,0 +1,70 @@ +From stable+bounces-277067-greg=kroah.com@vger.kernel.org Fri Jul 17 16:29:51 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:22:44 -0400 +Subject: mm/slab: do not limit zeroing to orig_size when only red zoning is enabled +To: stable@vger.kernel.org +Cc: "Vlastimil Babka (SUSE)" , "Harry Yoo (Oracle)" , Hao Li , Sasha Levin +Message-ID: <20260717142244.1820826-1-sashal@kernel.org> + +From: "Vlastimil Babka (SUSE)" + +[ Upstream commit 648927ceb84021a25a0fbd5673740956f318d534 ] + +When init (zeroing) on allocation is requested, for kmalloc() we +generally have to zero the full object size even if a smaller size is +requested, in order to provide krealloc()'s __GFP_ZERO guarantees. + +But if we track the requested size, krealloc() uses that information to +do the right thing, so we can zero only the requested size. With red +zoning also enabled, any extra size became part of the red zone, so it +must not be zeroed and thus we must zero only the requested size. + +However the current check is imprecise, and will trigger also when only +SLAB_RED_ZONE is enabled without SLAB_STORE_USER (which enables tracking +the requested size). This means enabling red zoning alone can compromise +krealloc()'s __GFP_ZERO contract. + +Fix this by using slub_debug_orig_size() instead, which is the exact +check for whether the requested size is tracked. We don't need to care +if red zoning is also enabled or not. Also update and expand the +comment accordingly. + +Fixes: 9ce67395f5a0 ("mm/slub: only zero requested size of buffer for kzalloc when debug enabled") +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260610-slab_alloc_flags-v2-1-7190909db118@kernel.org +Reviewed-by: Harry Yoo (Oracle) +Reviewed-by: Hao Li +Signed-off-by: Vlastimil Babka (SUSE) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + mm/slab.h | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +--- a/mm/slab.h ++++ b/mm/slab.h +@@ -734,14 +734,17 @@ static inline void slab_post_alloc_hook( + flags &= gfp_allowed_mask; + + /* +- * For kmalloc object, the allocated memory size(object_size) is likely +- * larger than the requested size(orig_size). If redzone check is +- * enabled for the extra space, don't zero it, as it will be redzoned +- * soon. The redzone operation for this extra space could be seen as a +- * replacement of current poisoning under certain debug option, and +- * won't break other sanity checks. ++ * For kmalloc object, the allocated size (object_size) can be larger ++ * than the requested size (orig_size). We however need to zero the ++ * whole object_size to handle possible later krealloc() with ++ *__GFP_ZERO properly. ++ * ++ * But if we keep track of the requested size, krealloc() uses that ++ * information. Additionally if red zoning is enabled, the extra space ++ * is also red zone, so we should not overwrite it. So limit zeroing to ++ * orig_size if we track it. + */ +- if (kmem_cache_debug_flags(s, SLAB_STORE_USER | SLAB_RED_ZONE) && ++ if (kmem_cache_debug_flags(s, SLAB_STORE_USER) && + (s->flags & SLAB_KMALLOC)) + zero_size = orig_size; + diff --git a/queue-6.6/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch b/queue-6.6/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch new file mode 100644 index 0000000000..cf606a9c7b --- /dev/null +++ b/queue-6.6/netfilter-ebtables-use-vmalloc_array-to-improve-code.patch @@ -0,0 +1,77 @@ +From stable+bounces-275038-greg=kroah.com@vger.kernel.org Wed Jul 15 22:49:56 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 16:49:46 -0400 +Subject: netfilter: ebtables: Use vmalloc_array() to improve code +To: stable@vger.kernel.org +Cc: Qianfeng Rong , Florian Westphal , Sasha Levin +Message-ID: <20260715204947.205286-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.6/netfilter-ebtables-zero-chainstack-array.patch b/queue-6.6/netfilter-ebtables-zero-chainstack-array.patch new file mode 100644 index 0000000000..469412fe28 --- /dev/null +++ b/queue-6.6/netfilter-ebtables-zero-chainstack-array.patch @@ -0,0 +1,46 @@ +From stable+bounces-275039-greg=kroah.com@vger.kernel.org Wed Jul 15 22:49:55 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 16:49:47 -0400 +Subject: netfilter: ebtables: zero chainstack array +To: stable@vger.kernel.org +Cc: Florian Westphal , Sasha Levin +Message-ID: <20260715204947.205286-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.6/nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch b/queue-6.6/nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch new file mode 100644 index 0000000000..c56abd5334 --- /dev/null +++ b/queue-6.6/nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch @@ -0,0 +1,77 @@ +From stable+bounces-277828-greg=kroah.com@vger.kernel.org Mon Jul 20 16:19:16 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 10:10:33 -0400 +Subject: nvmet-auth: validate reply message payload bounds against transfer length +To: stable@vger.kernel.org +Cc: Tianchu Chen , Hannes Reinecke , Keith Busch , Sasha Levin +Message-ID: <20260720141033.1500263-3-sashal@kernel.org> + +From: Tianchu Chen + +[ Upstream commit 3a413ece2504c70aa34a20be4dafec04e8c741f9 ] + +nvmet_auth_reply() accesses the variable-length rval[] array using +attacker-controlled hl (hash length) and dhvlen (DH value length) fields +without verifying they fit within the allocated buffer of tl bytes. + +A malicious NVMe-oF initiator can craft a DHCHAP_REPLY message with a +small transfer length but large hl/dhvlen values, causing out-of-bounds +heap reads when the target processes the DH public key (rval + 2*hl) or +performs the host response memcmp. + +With DH authentication configured, the OOB pointer is passed directly to +sg_init_one() and read by crypto_kpp_compute_shared_secret(), reaching +up to 526 bytes past the buffer. This is exploitable pre-authentication. + +Add bounds validation ensuring sizeof(*data) + 2*hl + dhvlen <= tl before +any access to the variable-length fields. + +Discovered by Atuin - Automated Vulnerability Discovery Engine. + +Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication") +Cc: stable@vger.kernel.org +Reviewed-by: Hannes Reinecke +Signed-off-by: Tianchu Chen +Signed-off-by: Keith Busch +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/nvme/target/fabrics-cmd-auth.c | 15 ++++++++++++--- + 1 file changed, 12 insertions(+), 3 deletions(-) + +--- a/drivers/nvme/target/fabrics-cmd-auth.c ++++ b/drivers/nvme/target/fabrics-cmd-auth.c +@@ -109,13 +109,22 @@ static u8 nvmet_auth_negotiate(struct nv + return 0; + } + +-static u8 nvmet_auth_reply(struct nvmet_req *req, void *d) ++static u8 nvmet_auth_reply(struct nvmet_req *req, void *d, u32 tl) + { + struct nvmet_ctrl *ctrl = req->sq->ctrl; + struct nvmf_auth_dhchap_reply_data *data = d; +- u16 dhvlen = le16_to_cpu(data->dhvlen); ++ u16 dhvlen; + u8 *response; + ++ if (tl < sizeof(*data)) ++ return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; ++ ++ dhvlen = le16_to_cpu(data->dhvlen); ++ ++ /* Validate that hl and dhvlen fit within the transfer length */ ++ if (sizeof(*data) + 2 * (size_t)data->hl + dhvlen > tl) ++ return NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; ++ + pr_debug("%s: ctrl %d qid %d: data hl %d cvalid %d dhvlen %u\n", + __func__, ctrl->cntlid, req->sq->qid, + data->hl, data->cvalid, dhvlen); +@@ -287,7 +296,7 @@ void nvmet_execute_auth_send(struct nvme + + switch (data->auth_id) { + case NVME_AUTH_DHCHAP_MESSAGE_REPLY: +- dhchap_status = nvmet_auth_reply(req, d); ++ dhchap_status = nvmet_auth_reply(req, d, tl); + if (dhchap_status == 0) + req->sq->dhchap_step = + NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1; diff --git a/queue-6.6/nvmet-remove-superfluous-initialization.patch b/queue-6.6/nvmet-remove-superfluous-initialization.patch new file mode 100644 index 0000000000..e097e5ace5 --- /dev/null +++ b/queue-6.6/nvmet-remove-superfluous-initialization.patch @@ -0,0 +1,45 @@ +From stable+bounces-277826-greg=kroah.com@vger.kernel.org Mon Jul 20 16:49:20 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 10:10:31 -0400 +Subject: nvmet: remove superfluous initialization +To: stable@vger.kernel.org +Cc: Chaitanya Kulkarni , Keith Busch , Sasha Levin +Message-ID: <20260720141033.1500263-1-sashal@kernel.org> + +From: Chaitanya Kulkarni + +[ Upstream commit 8d30528a170905ede9ab6ab81f229e441808590b ] + +Remove superfluous initialization of status variable in +nvmet_execute_admin_connect() and nvmet_execute_io_connect(), since it +will get overwritten by nvmet_copy_from_sgl(). + +Signed-off-by: Chaitanya Kulkarni +Signed-off-by: Keith Busch +Stable-dep-of: 3a413ece2504 ("nvmet-auth: validate reply message payload bounds against transfer length") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/nvme/target/fabrics-cmd.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/nvme/target/fabrics-cmd.c ++++ b/drivers/nvme/target/fabrics-cmd.c +@@ -209,7 +209,7 @@ static void nvmet_execute_admin_connect( + struct nvmf_connect_command *c = &req->cmd->connect; + struct nvmf_connect_data *d; + struct nvmet_ctrl *ctrl = NULL; +- u16 status = 0; ++ u16 status; + int ret; + + if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data))) +@@ -287,7 +287,7 @@ static void nvmet_execute_io_connect(str + struct nvmf_connect_data *d; + struct nvmet_ctrl *ctrl; + u16 qid = le16_to_cpu(c->qid); +- u16 status = 0; ++ u16 status; + + if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data))) + return; diff --git a/queue-6.6/nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch b/queue-6.6/nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch new file mode 100644 index 0000000000..d3d0d3fb6c --- /dev/null +++ b/queue-6.6/nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch @@ -0,0 +1,280 @@ +From stable+bounces-277827-greg=kroah.com@vger.kernel.org Mon Jul 20 16:49:21 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 10:10:32 -0400 +Subject: nvmet: return DHCHAP status codes from nvmet_setup_auth() +To: stable@vger.kernel.org +Cc: Hannes Reinecke , Christoph Hellwig , Hannes Reinecke , Daniel Wagner , Keith Busch , Sasha Levin +Message-ID: <20260720141033.1500263-2-sashal@kernel.org> + +From: Hannes Reinecke + +[ Upstream commit 44e3c25efae8575e06f1c5d1dc40058a991e3cb2 ] + +A failure in nvmet_setup_auth() does not mean that the NVMe +authentication command failed, so we should rather return a protocol +error with a 'failure1' response than an NVMe status. + +Also update the type used for dhchap_step and dhchap_status to u8 to +avoid confusions with nvme status. Furthermore, split dhchap_status and +nvme status so we don't accidentally mix these return values. + +Reviewed-by: Christoph Hellwig +Signed-off-by: Hannes Reinecke +[dwagner: - use u8 as type for dhchap_{step|status} + - separate nvme status from dhcap_status] +Signed-off-by: Daniel Wagner +Signed-off-by: Keith Busch +Stable-dep-of: 3a413ece2504 ("nvmet-auth: validate reply message payload bounds against transfer length") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/nvme/target/auth.c | 20 +++++-------- + drivers/nvme/target/fabrics-cmd-auth.c | 49 ++++++++++++++++----------------- + drivers/nvme/target/fabrics-cmd.c | 11 ++++--- + drivers/nvme/target/nvmet.h | 8 ++--- + 4 files changed, 43 insertions(+), 45 deletions(-) + +--- a/drivers/nvme/target/auth.c ++++ b/drivers/nvme/target/auth.c +@@ -125,12 +125,11 @@ int nvmet_setup_dhgroup(struct nvmet_ctr + return ret; + } + +-int nvmet_setup_auth(struct nvmet_ctrl *ctrl) ++u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl) + { + int ret = 0; + struct nvmet_host_link *p; + struct nvmet_host *host = NULL; +- const char *hash_name; + + down_read(&nvmet_config_sem); + if (nvmet_is_disc_subsys(ctrl->subsys)) +@@ -148,13 +147,16 @@ int nvmet_setup_auth(struct nvmet_ctrl * + } + if (!host) { + pr_debug("host %s not found\n", ctrl->hostnqn); +- ret = -EPERM; ++ ret = NVME_AUTH_DHCHAP_FAILURE_FAILED; + goto out_unlock; + } + + ret = nvmet_setup_dhgroup(ctrl, host->dhchap_dhgroup_id); +- if (ret < 0) ++ if (ret < 0) { + pr_warn("Failed to setup DH group"); ++ ret = NVME_AUTH_DHCHAP_FAILURE_DHGROUP_UNUSABLE; ++ goto out_unlock; ++ } + + if (!host->dhchap_secret) { + pr_debug("No authentication provided\n"); +@@ -165,12 +167,6 @@ int nvmet_setup_auth(struct nvmet_ctrl * + pr_debug("Re-use existing hash ID %d\n", + ctrl->shash_id); + } else { +- hash_name = nvme_auth_hmac_name(host->dhchap_hash_id); +- if (!hash_name) { +- pr_warn("Hash ID %d invalid\n", host->dhchap_hash_id); +- ret = -EINVAL; +- goto out_unlock; +- } + ctrl->shash_id = host->dhchap_hash_id; + } + +@@ -179,7 +175,7 @@ int nvmet_setup_auth(struct nvmet_ctrl * + ctrl->host_key = nvme_auth_extract_key(host->dhchap_secret + 10, + host->dhchap_key_hash); + if (IS_ERR(ctrl->host_key)) { +- ret = PTR_ERR(ctrl->host_key); ++ ret = NVME_AUTH_DHCHAP_FAILURE_NOT_USABLE; + ctrl->host_key = NULL; + goto out_free_hash; + } +@@ -197,7 +193,7 @@ int nvmet_setup_auth(struct nvmet_ctrl * + ctrl->ctrl_key = nvme_auth_extract_key(host->dhchap_ctrl_secret + 10, + host->dhchap_ctrl_key_hash); + if (IS_ERR(ctrl->ctrl_key)) { +- ret = PTR_ERR(ctrl->ctrl_key); ++ ret = NVME_AUTH_DHCHAP_FAILURE_NOT_USABLE; + ctrl->ctrl_key = NULL; + goto out_free_hash; + } +--- a/drivers/nvme/target/fabrics-cmd-auth.c ++++ b/drivers/nvme/target/fabrics-cmd-auth.c +@@ -31,7 +31,7 @@ void nvmet_auth_sq_init(struct nvmet_sq + sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE; + } + +-static u16 nvmet_auth_negotiate(struct nvmet_req *req, void *d) ++static u8 nvmet_auth_negotiate(struct nvmet_req *req, void *d) + { + struct nvmet_ctrl *ctrl = req->sq->ctrl; + struct nvmf_auth_dhchap_negotiate_data *data = d; +@@ -109,7 +109,7 @@ static u16 nvmet_auth_negotiate(struct n + return 0; + } + +-static u16 nvmet_auth_reply(struct nvmet_req *req, void *d) ++static u8 nvmet_auth_reply(struct nvmet_req *req, void *d) + { + struct nvmet_ctrl *ctrl = req->sq->ctrl; + struct nvmf_auth_dhchap_reply_data *data = d; +@@ -172,7 +172,7 @@ static u16 nvmet_auth_reply(struct nvmet + return 0; + } + +-static u16 nvmet_auth_failure2(void *d) ++static u8 nvmet_auth_failure2(void *d) + { + struct nvmf_auth_dhchap_failure_data *data = d; + +@@ -186,6 +186,7 @@ void nvmet_execute_auth_send(struct nvme + void *d; + u32 tl; + u16 status = 0; ++ u8 dhchap_status; + + if (req->cmd->auth_send.secp != NVME_AUTH_DHCHAP_PROTOCOL_IDENTIFIER) { + status = NVME_SC_INVALID_FIELD | NVME_SC_DNR; +@@ -237,30 +238,32 @@ void nvmet_execute_auth_send(struct nvme + if (data->auth_type == NVME_AUTH_COMMON_MESSAGES) { + if (data->auth_id == NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE) { + /* Restart negotiation */ +- pr_debug("%s: ctrl %d qid %d reset negotiation\n", __func__, +- ctrl->cntlid, req->sq->qid); ++ pr_debug("%s: ctrl %d qid %d reset negotiation\n", ++ __func__, ctrl->cntlid, req->sq->qid); + if (!req->sq->qid) { +- if (nvmet_setup_auth(ctrl) < 0) { +- status = NVME_SC_INTERNAL; +- pr_err("ctrl %d qid 0 failed to setup" +- "re-authentication", ++ dhchap_status = nvmet_setup_auth(ctrl); ++ if (dhchap_status) { ++ pr_err("ctrl %d qid 0 failed to setup re-authentication\n", + ctrl->cntlid); +- goto done_failure1; ++ req->sq->dhchap_status = dhchap_status; ++ req->sq->dhchap_step = ++ NVME_AUTH_DHCHAP_MESSAGE_FAILURE1; ++ goto done_kfree; + } + } +- req->sq->dhchap_step = NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE; ++ req->sq->dhchap_step = ++ NVME_AUTH_DHCHAP_MESSAGE_NEGOTIATE; + } else if (data->auth_id != req->sq->dhchap_step) + goto done_failure1; + /* Validate negotiation parameters */ +- status = nvmet_auth_negotiate(req, d); +- if (status == 0) ++ dhchap_status = nvmet_auth_negotiate(req, d); ++ if (dhchap_status == 0) + req->sq->dhchap_step = + NVME_AUTH_DHCHAP_MESSAGE_CHALLENGE; + else { + req->sq->dhchap_step = + NVME_AUTH_DHCHAP_MESSAGE_FAILURE1; +- req->sq->dhchap_status = status; +- status = 0; ++ req->sq->dhchap_status = dhchap_status; + } + goto done_kfree; + } +@@ -284,15 +287,14 @@ void nvmet_execute_auth_send(struct nvme + + switch (data->auth_id) { + case NVME_AUTH_DHCHAP_MESSAGE_REPLY: +- status = nvmet_auth_reply(req, d); +- if (status == 0) ++ dhchap_status = nvmet_auth_reply(req, d); ++ if (dhchap_status == 0) + req->sq->dhchap_step = + NVME_AUTH_DHCHAP_MESSAGE_SUCCESS1; + else { + req->sq->dhchap_step = + NVME_AUTH_DHCHAP_MESSAGE_FAILURE1; +- req->sq->dhchap_status = status; +- status = 0; ++ req->sq->dhchap_status = dhchap_status; + } + goto done_kfree; + case NVME_AUTH_DHCHAP_MESSAGE_SUCCESS2: +@@ -301,13 +303,12 @@ void nvmet_execute_auth_send(struct nvme + __func__, ctrl->cntlid, req->sq->qid); + goto done_kfree; + case NVME_AUTH_DHCHAP_MESSAGE_FAILURE2: +- status = nvmet_auth_failure2(d); +- if (status) { ++ dhchap_status = nvmet_auth_failure2(d); ++ if (dhchap_status) { + pr_warn("ctrl %d qid %d: authentication failed (%d)\n", +- ctrl->cntlid, req->sq->qid, status); +- req->sq->dhchap_status = status; ++ ctrl->cntlid, req->sq->qid, dhchap_status); ++ req->sq->dhchap_status = dhchap_status; + req->sq->authenticated = false; +- status = 0; + } + goto done_kfree; + default: +--- a/drivers/nvme/target/fabrics-cmd.c ++++ b/drivers/nvme/target/fabrics-cmd.c +@@ -210,7 +210,7 @@ static void nvmet_execute_admin_connect( + struct nvmf_connect_data *d; + struct nvmet_ctrl *ctrl = NULL; + u16 status; +- int ret; ++ u8 dhchap_status; + + if (!nvmet_check_transfer_len(req, sizeof(struct nvmf_connect_data))) + return; +@@ -252,11 +252,12 @@ static void nvmet_execute_admin_connect( + + uuid_copy(&ctrl->hostid, &d->hostid); + +- ret = nvmet_setup_auth(ctrl); +- if (ret < 0) { +- pr_err("Failed to setup authentication, error %d\n", ret); ++ dhchap_status = nvmet_setup_auth(ctrl); ++ if (dhchap_status) { ++ pr_err("Failed to setup authentication, dhchap status %u\n", ++ dhchap_status); + nvmet_ctrl_put(ctrl); +- if (ret == -EPERM) ++ if (dhchap_status == NVME_AUTH_DHCHAP_FAILURE_FAILED) + status = (NVME_SC_CONNECT_INVALID_HOST | NVME_SC_DNR); + else + status = NVME_SC_INTERNAL; +--- a/drivers/nvme/target/nvmet.h ++++ b/drivers/nvme/target/nvmet.h +@@ -112,8 +112,8 @@ struct nvmet_sq { + bool authenticated; + struct delayed_work auth_expired_work; + u16 dhchap_tid; +- u16 dhchap_status; +- int dhchap_step; ++ u8 dhchap_status; ++ u8 dhchap_step; + u8 *dhchap_c1; + u8 *dhchap_c2; + u32 dhchap_s1; +@@ -700,7 +700,7 @@ void nvmet_execute_auth_receive(struct n + int nvmet_auth_set_key(struct nvmet_host *host, const char *secret, + bool set_ctrl); + int nvmet_auth_set_host_hash(struct nvmet_host *host, const char *hash); +-int nvmet_setup_auth(struct nvmet_ctrl *ctrl); ++u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl); + void nvmet_auth_sq_init(struct nvmet_sq *sq); + void nvmet_destroy_auth(struct nvmet_ctrl *ctrl); + void nvmet_auth_sq_free(struct nvmet_sq *sq); +@@ -719,7 +719,7 @@ int nvmet_auth_ctrl_exponential(struct n + int nvmet_auth_ctrl_sesskey(struct nvmet_req *req, + u8 *buf, int buf_size); + #else +-static inline int nvmet_setup_auth(struct nvmet_ctrl *ctrl) ++static inline u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl) + { + return 0; + } diff --git a/queue-6.6/pci-add-kerneldoc-for-pci_resize_resource.patch b/queue-6.6/pci-add-kerneldoc-for-pci_resize_resource.patch new file mode 100644 index 0000000000..25e6d4f697 --- /dev/null +++ b/queue-6.6/pci-add-kerneldoc-for-pci_resize_resource.patch @@ -0,0 +1,55 @@ +From stable+bounces-274640-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:05 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:17:54 -0400 +Subject: PCI: Add kerneldoc for pci_resize_resource() +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Alex Bennée" , "Sasha Levin" +Message-ID: <20260715001756.3783927-4-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit d787018e2dfdc4c1331538e7a8717690d1b7c9b3 ] + +As pci_resize_resource() is meant to be used also outside of PCI core, +document the interface with kerneldoc. + +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-8-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-res.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +--- a/drivers/pci/setup-res.c ++++ b/drivers/pci/setup-res.c +@@ -427,6 +427,26 @@ void pci_release_resource(struct pci_dev + } + EXPORT_SYMBOL(pci_release_resource); + ++/** ++ * pci_resize_resource - reconfigure a Resizable BAR and resources ++ * @dev: the PCI device ++ * @resno: index of the BAR to be resized ++ * @size: new size as defined in the spec (0=1MB, 31=128TB) ++ * @exclude_bars: a mask of BARs that should not be released ++ * ++ * Reconfigure @resno to @size and re-run resource assignment algorithm ++ * with the new size. ++ * ++ * Prior to resize, release @dev resources that share a bridge window with ++ * @resno. This unpins the bridge window resource to allow changing it. ++ * ++ * The caller may prevent releasing a particular BAR by providing ++ * @exclude_bars mask, but this may result in the resize operation failing ++ * due to insufficient space. ++ * ++ * Return: 0 on success, or negative on error. In case of an error, the ++ * resources are restored to their original places. ++ */ + int pci_resize_resource(struct pci_dev *dev, int resno, int size, + int exclude_bars) + { diff --git a/queue-6.6/pci-altera-fix-resource-leaks-on-probe-failure.patch b/queue-6.6/pci-altera-fix-resource-leaks-on-probe-failure.patch new file mode 100644 index 0000000000..b074c51876 --- /dev/null +++ b/queue-6.6/pci-altera-fix-resource-leaks-on-probe-failure.patch @@ -0,0 +1,99 @@ +From stable+bounces-274303-greg=kroah.com@vger.kernel.org Tue Jul 14 16:50:59 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 10:46:24 -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: <20260714144624.2781507-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 +@@ -680,8 +680,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); + } +@@ -706,7 +716,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; + } + +@@ -791,6 +800,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 */ +@@ -801,7 +816,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.6/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch b/queue-6.6/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch new file mode 100644 index 0000000000..2d1158d191 --- /dev/null +++ b/queue-6.6/pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.patch @@ -0,0 +1,144 @@ +From stable+bounces-274574-greg=kroah.com@vger.kernel.org Wed Jul 15 00:04:38 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:11 -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: <20260714220414.3333873-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-nwl.c | 2 +- + 7 files changed, 10 insertions(+), 13 deletions(-) + +--- a/drivers/pci/controller/dwc/pcie-designware-host.c ++++ b/drivers/pci/controller/dwc/pcie-designware-host.c +@@ -233,7 +233,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 +@@ -442,7 +442,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); +@@ -468,13 +468,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 +@@ -171,7 +171,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 +@@ -667,10 +667,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 +@@ -661,8 +661,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 +@@ -496,7 +496,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-nwl.c ++++ b/drivers/pci/controller/pcie-xilinx-nwl.c +@@ -502,7 +502,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, diff --git a/queue-6.6/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch b/queue-6.6/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch new file mode 100644 index 0000000000..a3a63d6e94 --- /dev/null +++ b/queue-6.6/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch @@ -0,0 +1,384 @@ +From stable+bounces-274639-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:04 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:17:53 -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: <20260715001756.3783927-3-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/pci/pci-sysfs.c | 14 --- + drivers/pci/pci.h | 3 + drivers/pci/setup-bus.c | 101 +++++++++++++++++++--------- + drivers/pci/setup-res.c | 19 +---- + include/linux/pci.h | 4 - + 7 files changed, 87 insertions(+), 60 deletions(-) + +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +@@ -1168,7 +1168,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/pci/pci-sysfs.c ++++ b/drivers/pci/pci-sysfs.c +@@ -1459,8 +1459,8 @@ static ssize_t resource##n##_resize_stor + const char *buf, size_t count)\ + { \ + struct pci_dev *pdev = to_pci_dev(dev); \ +- unsigned long size, flags; \ +- int ret, i; \ ++ unsigned long size; \ ++ int ret; \ + u16 cmd; \ + \ + if (kstrtoul(buf, 0, &size) < 0) \ +@@ -1485,17 +1485,9 @@ static ssize_t resource##n##_resize_stor + 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_STD_NUM_BARS; i++) { \ +- if (pci_resource_len(pdev, i) && \ +- pci_resource_flags(pdev, i) == flags) \ +- 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(pdev->bus); \ + \ +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -263,6 +263,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 crs_timeout); +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2221,18 +2221,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 { +@@ -2249,9 +2247,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); + +@@ -2267,67 +2265,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 +@@ -427,9 +427,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; +@@ -440,10 +440,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; +@@ -463,19 +459,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 +@@ -1390,7 +1390,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); +@@ -1460,7 +1461,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.6/pci-free-saved-list-without-holding-pci_bus_sem.patch b/queue-6.6/pci-free-saved-list-without-holding-pci_bus_sem.patch new file mode 100644 index 0000000000..d200e44ae4 --- /dev/null +++ b/queue-6.6/pci-free-saved-list-without-holding-pci_bus_sem.patch @@ -0,0 +1,38 @@ +From stable+bounces-274638-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:34 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:17:52 -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: <20260715001756.3783927-2-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 +@@ -2324,8 +2324,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.6/pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch b/queue-6.6/pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch new file mode 100644 index 0000000000..321b00b56d --- /dev/null +++ b/queue-6.6/pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch @@ -0,0 +1,54 @@ +From stable+bounces-274468-greg=kroah.com@vger.kernel.org Tue Jul 14 18:58:05 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 12:56:09 -0400 +Subject: PCI: imx6: Fix IMX6SX_GPR12_PCIE_TEST_POWERDOWN handling +To: stable@vger.kernel.org +Cc: Richard Zhu , Manivannan Sadhasivam , Bjorn Helgaas , Frank Li , Sasha Levin +Message-ID: <20260714165609.2885969-1-sashal@kernel.org> + +From: Richard Zhu + +[ Upstream commit aad953fb4eed0df5486cd54ccad80ac197678e01 ] + +The IMX6SX_GPR12_PCIE_TEST_POWERDOWN bit does not control the PCIe +reference clock on i.MX6SX. Instead, it is part of i.MX6SX PCIe core +reset sequence. + +Move the IMX6SX_GPR12_PCIE_TEST_POWERDOWN assertion/deassertion into +the core reset functions to properly reflect its purpose. Remove the +.enable_ref_clk() callback for i.MX6SX since it was incorrectly +manipulating this bit. + +Fixes: e3c06cd063d6 ("PCI: imx6: Add initial imx6sx support") +Signed-off-by: Richard Zhu +Signed-off-by: Manivannan Sadhasivam +Signed-off-by: Bjorn Helgaas +Reviewed-by: Frank Li +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260319090844.444987-1-hongxing.zhu@nxp.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/dwc/pci-imx6.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/pci/controller/dwc/pci-imx6.c ++++ b/drivers/pci/controller/dwc/pci-imx6.c +@@ -560,8 +560,6 @@ static int imx6_pcie_enable_ref_clk(stru + + switch (imx6_pcie->drvdata->variant) { + case IMX6SX: +- regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, +- IMX6SX_GPR12_PCIE_TEST_POWERDOWN, 0); + break; + case IMX6QP: + case IMX6Q: +@@ -733,6 +731,8 @@ static int imx6_pcie_deassert_core_reset + imx7d_pcie_wait_for_phy_pll_lock(imx6_pcie); + break; + case IMX6SX: ++ regmap_clear_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, ++ IMX6SX_GPR12_PCIE_TEST_POWERDOWN); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5, + IMX6SX_GPR5_PCIE_BTNRST_RESET, 0); + break; diff --git a/queue-6.6/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch b/queue-6.6/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch new file mode 100644 index 0000000000..7a2f346b4f --- /dev/null +++ b/queue-6.6/pci-mediatek-convert-bool-to-single-quirks-entry-and-bitmap.patch @@ -0,0 +1,127 @@ +From stable+bounces-274575-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:12 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:12 -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: <20260714220414.3333873-2-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 +@@ -143,23 +143,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; + }; + + /** +@@ -720,7 +728,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); + +@@ -728,7 +736,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 */ +@@ -1129,7 +1137,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) +@@ -1217,9 +1225,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 = { +@@ -1229,19 +1237,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.6/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch b/queue-6.6/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch new file mode 100644 index 0000000000..9c4afb1030 --- /dev/null +++ b/queue-6.6/pci-mediatek-fix-irq-domain-leak-when-port-fails-to-enable.patch @@ -0,0 +1,170 @@ +From stable+bounces-274577-greg=kroah.com@vger.kernel.org Wed Jul 15 00:05:05 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:14 -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: <20260714220414.3333873-4-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 | 67 ++++++++++++++++++++------------- + 1 file changed, 42 insertions(+), 25 deletions(-) + +--- a/drivers/pci/controller/pcie-mediatek.c ++++ b/drivers/pci/controller/pcie-mediatek.c +@@ -540,25 +540,29 @@ 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->msi_domain) ++ irq_domain_remove(port->msi_domain); ++ if (port->inner_domain) ++ irq_domain_remove(port->inner_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); +- } ++ 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, +@@ -841,7 +845,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; +@@ -850,7 +854,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); +@@ -898,11 +902,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); +@@ -918,8 +926,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, +@@ -1095,8 +1103,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)) +@@ -1198,14 +1211,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.6/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch b/queue-6.6/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch new file mode 100644 index 0000000000..7a98629c70 --- /dev/null +++ b/queue-6.6/pci-mediatek-use-generic-macro-for-tpvperl-delay.patch @@ -0,0 +1,42 @@ +From stable+bounces-274576-greg=kroah.com@vger.kernel.org Wed Jul 15 00:04:59 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 18:04:13 -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: <20260714220414.3333873-3-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 +@@ -714,12 +714,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.6/pci-move-resizable-bar-code-to-rebar.c.patch b/queue-6.6/pci-move-resizable-bar-code-to-rebar.c.patch new file mode 100644 index 0000000000..feb6b02c19 --- /dev/null +++ b/queue-6.6/pci-move-resizable-bar-code-to-rebar.c.patch @@ -0,0 +1,510 @@ +From stable+bounces-274641-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:06 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:17:55 -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: <20260715001756.3783927-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 | 136 ---------------------- + drivers/pci/pci.h | 1 + drivers/pci/rebar.c | 212 +++++++++++++++++++++++++++++++++++ + drivers/pci/setup-res.c | 64 ---------- + 6 files changed, 217 insertions(+), 201 deletions(-) + create mode 100644 drivers/pci/rebar.c + +--- a/Documentation/driver-api/pci/pci.rst ++++ b/Documentation/driver-api/pci/pci.rst +@@ -31,6 +31,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 \ +- pci-sysfs.o rom.o setup-res.o irq.o vpd.o \ ++ pci-sysfs.o rebar.o rom.o setup-res.o irq.o vpd.o \ + setup-bus.o vc.o mmap.o setup-irq.o + + obj-$(CONFIG_PCI) += msi/ +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -1870,33 +1870,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 = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >> +- PCI_REBAR_CTRL_NBAR_SHIFT; +- +- 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 |= size << PCI_REBAR_CTRL_BAR_SHIFT; +- 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 +@@ -3830,115 +3803,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 = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >> +- PCI_REBAR_CTRL_NBAR_SHIFT; +- +- for (i = 0; i < nbars; i++, pos += 8) { +- int bar_idx; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; +- 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 (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT; +-} +- +-/** +- * 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 |= size << PCI_REBAR_CTRL_BAR_SHIFT; +- 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 +@@ -643,6 +643,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,212 @@ ++// 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 = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >> ++ PCI_REBAR_CTRL_NBAR_SHIFT; ++ ++ for (i = 0; i < nbars; i++, pos += 8) { ++ int bar_idx; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; ++ 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 (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT; ++} ++ ++/** ++ * 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 |= size << PCI_REBAR_CTRL_BAR_SHIFT; ++ 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 |= size << PCI_REBAR_CTRL_BAR_SHIFT; ++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); ++ } ++} ++ ++/** ++ * pci_resize_resource - reconfigure a Resizable BAR and resources ++ * @dev: the PCI device ++ * @resno: index of the BAR to be resized ++ * @size: new size as defined in the spec (0=1MB, 31=128TB) ++ * @exclude_bars: a mask of BARs that should not be released ++ * ++ * Reconfigure @resno to @size and re-run resource assignment algorithm ++ * with the new size. ++ * ++ * Prior to resize, release @dev resources that share a bridge window with ++ * @resno. This unpins the bridge window resource to allow changing it. ++ * ++ * The caller may prevent releasing a particular BAR by providing ++ * @exclude_bars mask, but this may result in the resize operation failing ++ * due to insufficient space. ++ * ++ * Return: 0 on success, or negative on error. In case of an error, the ++ * resources are restored to their original places. ++ */ ++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 +@@ -427,70 +427,6 @@ void pci_release_resource(struct pci_dev + } + EXPORT_SYMBOL(pci_release_resource); + +-/** +- * pci_resize_resource - reconfigure a Resizable BAR and resources +- * @dev: the PCI device +- * @resno: index of the BAR to be resized +- * @size: new size as defined in the spec (0=1MB, 31=128TB) +- * @exclude_bars: a mask of BARs that should not be released +- * +- * Reconfigure @resno to @size and re-run resource assignment algorithm +- * with the new size. +- * +- * Prior to resize, release @dev resources that share a bridge window with +- * @resno. This unpins the bridge window resource to allow changing it. +- * +- * The caller may prevent releasing a particular BAR by providing +- * @exclude_bars mask, but this may result in the resize operation failing +- * due to insufficient space. +- * +- * Return: 0 on success, or negative on error. In case of an error, the +- * resources are restored to their original places. +- */ +-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.6/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch b/queue-6.6/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch new file mode 100644 index 0000000000..39ada15d2d --- /dev/null +++ b/queue-6.6/pci-prevent-resource-tree-corruption-when-bar-resize-fails.patch @@ -0,0 +1,71 @@ +From stable+bounces-274637-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:03 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:17:51 -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: <20260715001756.3783927-1-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 +@@ -2312,6 +2312,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.6/pci-skip-resizable-bar-restore-on-read-error.patch b/queue-6.6/pci-skip-resizable-bar-restore-on-read-error.patch new file mode 100644 index 0000000000..dfd3c32896 --- /dev/null +++ b/queue-6.6/pci-skip-resizable-bar-restore-on-read-error.patch @@ -0,0 +1,61 @@ +From stable+bounces-274642-greg=kroah.com@vger.kernel.org Wed Jul 15 02:18:45 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:17:56 -0400 +Subject: PCI: Skip Resizable BAR restore on read error +To: stable@vger.kernel.org +Cc: Marco Nenciarini , Bjorn Helgaas , Sasha Levin +Message-ID: <20260715001756.3783927-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 +@@ -131,6 +131,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) { +@@ -138,6 +141,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.6/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch b/queue-6.6/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch new file mode 100644 index 0000000000..475ab5d2ea --- /dev/null +++ b/queue-6.6/perf-x86-intel-uncore-defer-adl-global-pmon-enable-to-enable_box.patch @@ -0,0 +1,57 @@ +From stable+bounces-276817-greg=kroah.com@vger.kernel.org Thu Jul 16 20:53:34 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 14:50:56 -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: <20260716185056.1020113-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 +@@ -537,12 +537,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); +@@ -561,7 +555,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.6/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch b/queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch new file mode 100644 index 0000000000..6cd7984391 --- /dev/null +++ b/queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch @@ -0,0 +1,274 @@ +From stable+bounces-276783-greg=kroah.com@vger.kernel.org Thu Jul 16 19:00:00 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 12:58:56 -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: <20260716165856.683482-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; + } + +@@ -2248,21 +2228,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 +@@ -107,7 +107,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.6/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch b/queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch new file mode 100644 index 0000000000..8efd76a7a6 --- /dev/null +++ b/queue-6.6/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch @@ -0,0 +1,207 @@ +From stable+bounces-277082-greg=kroah.com@vger.kernel.org Fri Jul 17 16:35:58 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:34:50 -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: <20260717143450.1877881-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 */ +@@ -2333,17 +2339,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)) +@@ -2393,23 +2397,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; + +@@ -2435,8 +2438,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; +@@ -2444,7 +2446,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 */ +@@ -2461,6 +2462,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.6/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch b/queue-6.6/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch new file mode 100644 index 0000000000..d7ecd5ebe8 --- /dev/null +++ b/queue-6.6/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch @@ -0,0 +1,204 @@ +From stable+bounces-276782-greg=kroah.com@vger.kernel.org Thu Jul 16 19:00:07 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 12:58:55 -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: <20260716165856.683482-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, + }; + + +@@ -2317,7 +2317,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 * +@@ -2396,7 +2396,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 +@@ -2892,7 +2892,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 +@@ -2951,7 +2951,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 +@@ -3444,7 +3444,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, + }; + +@@ -3644,7 +3644,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, + }; + +@@ -3775,7 +3775,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, +@@ -3973,7 +3973,7 @@ static int proc_task_getattr(struct mnt_ + 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 +@@ -104,7 +104,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 = { +@@ -374,7 +374,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, +@@ -415,7 +415,7 @@ static int proc_readfdinfo(struct file * + 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 +@@ -183,8 +183,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 +@@ -321,7 +321,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.6/regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch b/queue-6.6/regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch new file mode 100644 index 0000000000..d3b1069ac6 --- /dev/null +++ b/queue-6.6/regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch @@ -0,0 +1,45 @@ +From stable+bounces-278286-greg=kroah.com@vger.kernel.org Tue Jul 21 02:46:38 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 20:45:19 -0400 +Subject: regulator: scmi: fix of_node refcount leak in scmi_regulator_probe() +To: stable@vger.kernel.org +Cc: Wentao Liang , Mark Brown , Sasha Levin +Message-ID: <20260721004519.3422055-2-sashal@kernel.org> + +From: Wentao Liang + +[ Upstream commit fa11039d6cdff84584a3ef8cc1f5e1b56e045da2 ] + +scmi_regulator_probe() calls of_find_node_by_name() which takes a +reference on the returned device node. On the error path where +process_scmi_regulator_of_node() fails, the function returns without +calling of_node_put() on the child node, leaking the reference. + +Add of_node_put(np) on the error path to properly release the +reference. + +Cc: stable@vger.kernel.org +Fixes: 0fbeae70ee7c ("regulator: add SCMI driver") +Signed-off-by: Wentao Liang +Link: https://patch.msgid.link/20260527104850.872415-1-vulab@iscas.ac.cn +Signed-off-by: Mark Brown +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/regulator/scmi-regulator.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +--- a/drivers/regulator/scmi-regulator.c ++++ b/drivers/regulator/scmi-regulator.c +@@ -345,8 +345,10 @@ static int scmi_regulator_probe(struct s + for_each_child_of_node_scoped(np, child) { + ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo); + /* abort on any mem issue */ +- if (ret == -ENOMEM) ++ if (ret == -ENOMEM) { ++ of_node_put(np); + return ret; ++ } + } + of_node_put(np); + /* diff --git a/queue-6.6/regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch b/queue-6.6/regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch new file mode 100644 index 0000000000..2d4b59a2dd --- /dev/null +++ b/queue-6.6/regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch @@ -0,0 +1,52 @@ +From stable+bounces-278285-greg=kroah.com@vger.kernel.org Tue Jul 21 02:46:33 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 20:45:18 -0400 +Subject: regulator: scmi: Simplify with scoped for each OF child loop +To: stable@vger.kernel.org +Cc: Krzysztof Kozlowski , Mark Brown , Sasha Levin +Message-ID: <20260721004519.3422055-1-sashal@kernel.org> + +From: Krzysztof Kozlowski + +[ Upstream commit 99cf5db9cdd39136fd5dbd10bda833aa0f870452 ] + +Use scoped for_each_available_child_of_node_scoped() when iterating over +device nodes to make code a bit simpler. + +Signed-off-by: Krzysztof Kozlowski +Link: https://patch.msgid.link/20240814-cleanup-h-of-node-put-regulator-v1-7-87151088b883@linaro.org +Signed-off-by: Mark Brown +Stable-dep-of: fa11039d6cdf ("regulator: scmi: fix of_node refcount leak in scmi_regulator_probe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/regulator/scmi-regulator.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +--- a/drivers/regulator/scmi-regulator.c ++++ b/drivers/regulator/scmi-regulator.c +@@ -298,7 +298,7 @@ static int process_scmi_regulator_of_nod + static int scmi_regulator_probe(struct scmi_device *sdev) + { + int d, ret, num_doms; +- struct device_node *np, *child; ++ struct device_node *np; + const struct scmi_handle *handle = sdev->handle; + struct scmi_regulator_info *rinfo; + struct scmi_protocol_handle *ph; +@@ -342,13 +342,11 @@ static int scmi_regulator_probe(struct s + */ + of_node_get(handle->dev->of_node); + np = of_find_node_by_name(handle->dev->of_node, "regulators"); +- for_each_child_of_node(np, child) { ++ for_each_child_of_node_scoped(np, child) { + ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo); + /* abort on any mem issue */ +- if (ret == -ENOMEM) { +- of_node_put(child); ++ if (ret == -ENOMEM) + return ret; +- } + } + of_node_put(np); + /* diff --git a/queue-6.6/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch b/queue-6.6/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch new file mode 100644 index 0000000000..7ad79544b8 --- /dev/null +++ b/queue-6.6/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch @@ -0,0 +1,56 @@ +From stable+bounces-277078-greg=kroah.com@vger.kernel.org Fri Jul 17 16:35:14 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:34:49 -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: <20260717143450.1877881-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.6/seqlock-introduce-scoped_seqlock_read.patch b/queue-6.6/seqlock-introduce-scoped_seqlock_read.patch new file mode 100644 index 0000000000..22d7f94fde --- /dev/null +++ b/queue-6.6/seqlock-introduce-scoped_seqlock_read.patch @@ -0,0 +1,164 @@ +From stable+bounces-277077-greg=kroah.com@vger.kernel.org Fri Jul 17 16:35:05 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:34:48 -0400 +Subject: seqlock: Introduce scoped_seqlock_read() +To: stable@vger.kernel.org +Cc: Peter Zijlstra , Oleg Nesterov , Sasha Levin +Message-ID: <20260717143450.1877881-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 +@@ -1266,4 +1266,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.6/serial-8250_mid-disable-dma-for-selected-platforms.patch b/queue-6.6/serial-8250_mid-disable-dma-for-selected-platforms.patch new file mode 100644 index 0000000000..ef78b0c5a2 --- /dev/null +++ b/queue-6.6/serial-8250_mid-disable-dma-for-selected-platforms.patch @@ -0,0 +1,64 @@ +From sashal@kernel.org Fri Jul 17 21:55:24 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 15:55:20 -0400 +Subject: serial: 8250_mid: Disable DMA for selected platforms +To: stable@vger.kernel.org +Cc: Andy Shevchenko , micas-opensource , stable , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260717195520.2195138-2-sashal@kernel.org> + +From: Andy Shevchenko + +[ Upstream commit b1b4efea05a56c0995e4702a86d6624b4fdff32f ] + +In accordance with Errata (specification updates) +HSUART May Stop Functioning when DMA is Active. + +- Denverton document #572409, rev 3.4, DNV60 +- Ice Lake Xeon D document #714070, ICXD65 +- Snowridge document #731931, SNR44 + +For a quick fix just disable the respective callbacks during the device probe. +Depending on the future development we might remove them completely. + +Reported-by: micas-opensource +Closes: https://lore.kernel.org/linux-serial/20250625031409.2404219-1-opensource@ruijie.com.cn/ +Fixes: 6ede6dcd87aa ("serial: 8250_mid: add support for DMA engine handling from UART MMIO") +Cc: stable +Signed-off-by: Andy Shevchenko +Link: https://patch.msgid.link/20260626094937.561776-1-andriy.shevchenko@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/tty/serial/8250/8250_mid.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +--- a/drivers/tty/serial/8250/8250_mid.c ++++ b/drivers/tty/serial/8250/8250_mid.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + + #include + +@@ -368,8 +369,16 @@ static const struct mid8250_board dnv_bo + .freq = 133333333, + .base_baud = 115200, + .bar = 1, +- .setup = dnv_setup, +- .exit = dnv_exit, ++ /* ++ * Errata: ++ * HSUART May Stop Functioning when DMA is Active. ++ * ++ * - Denverton document #572409, rev 3.4, DNV60 ++ * - Ice Lake Xeon D document #714070, ICXD65 ++ * - Snowridge document #731931, SNR44 ++ */ ++ .setup = PTR_IF(false, dnv_setup), ++ .exit = PTR_IF(false, dnv_exit), + }; + + static const struct pci_device_id pci_ids[] = { diff --git a/queue-6.6/serial-8250_mid-remove-8250_pci-usage.patch b/queue-6.6/serial-8250_mid-remove-8250_pci-usage.patch new file mode 100644 index 0000000000..8ee780443e --- /dev/null +++ b/queue-6.6/serial-8250_mid-remove-8250_pci-usage.patch @@ -0,0 +1,120 @@ +From sashal@kernel.org Fri Jul 17 21:55:23 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 15:55:19 -0400 +Subject: serial: 8250_mid: Remove 8250_pci usage +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Greg Kroah-Hartman" , "Sasha Levin" +Message-ID: <20260717195520.2195138-1-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit a136abd7e7abe0f1247f8ffde6cc7c8ab09f985b ] + +8250_mid uses FL_*BASE* from linux/8250_pci.h and nothing else. The +code can be simplified by directly defining BARs within the driver +instead. + +Signed-off-by: Ilpo Järvinen +Link: https://lore.kernel.org/r/20230915094336.13278-1-ilpo.jarvinen@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: b1b4efea05a5 ("serial: 8250_mid: Disable DMA for selected platforms") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/tty/serial/8250/8250_mid.c | 18 +++++++----------- + 1 file changed, 7 insertions(+), 11 deletions(-) + +--- a/drivers/tty/serial/8250/8250_mid.c ++++ b/drivers/tty/serial/8250/8250_mid.c +@@ -12,7 +12,6 @@ + #include + + #include +-#include + + #include "8250.h" + +@@ -32,9 +31,9 @@ + struct mid8250; + + struct mid8250_board { +- unsigned int flags; + unsigned long freq; + unsigned int base_baud; ++ unsigned int bar; + int (*setup)(struct mid8250 *, struct uart_port *p); + void (*exit)(struct mid8250 *); + }; +@@ -169,7 +168,6 @@ static int dnv_setup(struct mid8250 *mid + { + struct hsu_dma_chip *chip = &mid->dma_chip; + struct pci_dev *pdev = to_pci_dev(p->dev); +- unsigned int bar = FL_GET_BASE(mid->board->flags); + int ret; + + pci_set_master(pdev); +@@ -183,7 +181,7 @@ static int dnv_setup(struct mid8250 *mid + chip->dev = &pdev->dev; + chip->irq = pci_irq_vector(pdev, 0); + chip->regs = p->membase; +- chip->length = pci_resource_len(pdev, bar); ++ chip->length = pci_resource_len(pdev, mid->board->bar); + chip->offset = DNV_DMA_CHAN_OFFSET; + + /* Falling back to PIO mode if DMA probing fails */ +@@ -291,7 +289,6 @@ static int mid8250_probe(struct pci_dev + { + struct uart_8250_port uart; + struct mid8250 *mid; +- unsigned int bar; + int ret; + + ret = pcim_enable_device(pdev); +@@ -303,7 +300,6 @@ static int mid8250_probe(struct pci_dev + return -ENOMEM; + + mid->board = (struct mid8250_board *)id->driver_data; +- bar = FL_GET_BASE(mid->board->flags); + + memset(&uart, 0, sizeof(struct uart_8250_port)); + +@@ -316,8 +312,8 @@ static int mid8250_probe(struct pci_dev + uart.port.flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE; + uart.port.set_termios = mid8250_set_termios; + +- uart.port.mapbase = pci_resource_start(pdev, bar); +- uart.port.membase = pcim_iomap(pdev, bar, 0); ++ uart.port.mapbase = pci_resource_start(pdev, mid->board->bar); ++ uart.port.membase = pcim_iomap(pdev, mid->board->bar, 0); + if (!uart.port.membase) + return -ENOMEM; + +@@ -353,25 +349,25 @@ static void mid8250_remove(struct pci_de + } + + static const struct mid8250_board pnw_board = { +- .flags = FL_BASE0, + .freq = 50000000, + .base_baud = 115200, ++ .bar = 0, + .setup = pnw_setup, + .exit = pnw_exit, + }; + + static const struct mid8250_board tng_board = { +- .flags = FL_BASE0, + .freq = 38400000, + .base_baud = 1843200, ++ .bar = 0, + .setup = tng_setup, + .exit = tng_exit, + }; + + static const struct mid8250_board dnv_board = { +- .flags = FL_BASE1, + .freq = 133333333, + .base_baud = 115200, ++ .bar = 1, + .setup = dnv_setup, + .exit = dnv_exit, + }; diff --git a/queue-6.6/series b/queue-6.6/series index 33ae867594..57608333ee 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -1147,3 +1147,100 @@ mtd-rawnand-fsl_ifc-return-errors-for-failed-page-reads.patch mtd-rawnand-lpc32xx_mlc-fail-dma-transfers-on-timeout.patch mtd-rawnand-lpc32xx_slc-fail-dma-transfer-on-completion-timeout.patch perf-x86-amd-brs-fix-kernel-address-leakage.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-imu-adis-add-irqf_no_thread-to-non-fifo-trigger-irq.patch +iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch +iio-invensense-remove-redundant-initialization-of-variable-period.patch +iio-invensense-fix-timestamp-glitches-when-switching-frequency.patch +iio-imu-inv_icm42600-stabilized-timestamp-in-interrupt.patch +iio-imu-inv_icm42600-fix-timestamping-by-limiting-fifo-reading.patch +iio-pressure-mpl115-fix-runtime-pm-leak-on-read-error.patch +bitops-make-bytes_to_bits-treewide-available.patch +iio-common-st_sensors-honour-channel-endianness-in-read_axis_data.patch +alsa-aoa-check-snd_ctl_new1-return-value.patch +pci-altera-fix-resource-leaks-on-probe-failure.patch +vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch +pci-imx6-fix-imx6sx_gpr12_pcie_test_powerdown-handling.patch +pci-controller-use-dev_fwnode-instead-of-of_fwnode_handle.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-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-add-kerneldoc-for-pci_resize_resource.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 +mm-shrinker-remove-redundant-shrinker_rwsem-in-debugfs-operations.patch +mm-shrinker-do-not-hold-rcu-lock-in-shrinker_debugfs_count_show.patch +coresight-etb10-restore-atomic_t-for-shared-reading-state.patch +gpio-sch-use-raw_spinlock_t-in-the-irq-startup-path.patch +media-nxp-imx8-isi-convert-to-platform-remove-callback-returning-void.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-cancel-pending_rx_work-before-taking-conn-lock.patch +bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch +bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch +smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch +smb-client-resolve-swn-tcon-from-live-registrations.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 +cpufreq-pcc-remove-empty-exit-callback.patch +cpufreq-make-cpufreq_driver-exit-return-void.patch +cpufreq-qcom-cpufreq-hw-fix-possible-double-free.patch +writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch +writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.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 +mm-slab-do-not-limit-zeroing-to-orig_size-when-only-red-zoning-is-enabled.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 +serial-8250_mid-remove-8250_pci-usage.patch +serial-8250_mid-disable-dma-for-selected-platforms.patch +hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch +hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.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-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch +usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.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 +nvmet-remove-superfluous-initialization.patch +nvmet-return-dhchap-status-codes-from-nvmet_setup_auth.patch +nvmet-auth-validate-reply-message-payload-bounds-against-transfer-length.patch +usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch +btrfs-check-and-set-extent_delalloc_new-before-clearing-extent_delalloc.patch +crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch +audit-add-audit_log_nf_skb-helper-function.patch +audit-fix-potential-integer-overflow-in-audit_log_n_hex.patch +regulator-scmi-simplify-with-scoped-for-each-of-child-loop.patch +regulator-scmi-fix-of_node-refcount-leak-in-scmi_regulator_probe.patch +mm-do-file-ownership-checks-with-the-proper-mount-idmap.patch +exfat-move-free-cluster-out-of-exfat_init_ext_entry.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-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.6/smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch b/queue-6.6/smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch new file mode 100644 index 0000000000..e2d235bab7 --- /dev/null +++ b/queue-6.6/smb-client-improve-unlocking-of-a-mutex-in-cifs_get_swn_reg.patch @@ -0,0 +1,65 @@ +From stable+bounces-275113-greg=kroah.com@vger.kernel.org Thu Jul 16 03:16:05 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 21:15:57 -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: <20260716011558.683323-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.6/smb-client-resolve-swn-tcon-from-live-registrations.patch b/queue-6.6/smb-client-resolve-swn-tcon-from-live-registrations.patch new file mode 100644 index 0000000000..3c1c3a91e6 --- /dev/null +++ b/queue-6.6/smb-client-resolve-swn-tcon-from-live-registrations.patch @@ -0,0 +1,604 @@ +From stable+bounces-275114-greg=kroah.com@vger.kernel.org Thu Jul 16 03:16:11 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 21:15:58 -0400 +Subject: smb: client: resolve SWN tcon from live registrations +To: stable@vger.kernel.org +Cc: Michael Bommarito , Steve French , Sasha Levin +Message-ID: <20260716011558.683323-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 +@@ -35,6 +35,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") \ +@@ -46,6 +47,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.6/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch b/queue-6.6/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch new file mode 100644 index 0000000000..40b557f777 --- /dev/null +++ b/queue-6.6/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch @@ -0,0 +1,56 @@ +From stable+bounces-274646-greg=kroah.com@vger.kernel.org Wed Jul 15 02:22:18 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:22:08 -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: <20260715002210.3789107-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 +@@ -1012,7 +1012,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; +@@ -1022,7 +1022,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 +@@ -1538,7 +1538,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.6/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch b/queue-6.6/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..edf130cb90 --- /dev/null +++ b/queue-6.6/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-274648-greg=kroah.com@vger.kernel.org Wed Jul 15 02:22:21 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:22:10 -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: <20260715002210.3789107-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 +@@ -584,9 +584,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) +@@ -619,9 +624,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); +@@ -706,6 +716,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; +@@ -716,6 +729,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.6/staging-rtl8723bs-fix-spaces-around-binary-operators.patch b/queue-6.6/staging-rtl8723bs-fix-spaces-around-binary-operators.patch new file mode 100644 index 0000000000..fdbaaf66c2 --- /dev/null +++ b/queue-6.6/staging-rtl8723bs-fix-spaces-around-binary-operators.patch @@ -0,0 +1,206 @@ +From stable+bounces-274647-greg=kroah.com@vger.kernel.org Wed Jul 15 02:22:20 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:22:09 -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: <20260715002210.3789107-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 +@@ -455,10 +455,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; + +@@ -518,7 +518,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; +@@ -586,18 +586,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 */ + } + } + +@@ -621,9 +621,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 */ +@@ -632,10 +633,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 */ + } + } + } +@@ -667,20 +668,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; +@@ -758,12 +759,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; +@@ -1009,20 +1010,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; +@@ -1091,21 +1097,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.6/treewide-switch-rename-to-timer_delete.patch b/queue-6.6/treewide-switch-rename-to-timer_delete.patch new file mode 100644 index 0000000000..8da90c55eb --- /dev/null +++ b/queue-6.6/treewide-switch-rename-to-timer_delete.patch @@ -0,0 +1,37 @@ +From stable+bounces-277093-greg=kroah.com@vger.kernel.org Fri Jul 17 17:00:26 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 10:59:58 -0400 +Subject: treewide: Switch/rename to timer_delete[_sync]() +To: stable@vger.kernel.org +Cc: Thomas Gleixner , Ingo Molnar , Sasha Levin +Message-ID: <20260717145959.1930377-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.6/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch b/queue-6.6/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch new file mode 100644 index 0000000000..74c71c788c --- /dev/null +++ b/queue-6.6/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch @@ -0,0 +1,175 @@ +From stable+bounces-277506-greg=kroah.com@vger.kernel.org Sun Jul 19 18:57:18 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 12:57:06 -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: <20260719165706.748133-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); +@@ -2598,8 +2602,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) { +@@ -2630,6 +2649,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.6/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch b/queue-6.6/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch new file mode 100644 index 0000000000..a9851cde12 --- /dev/null +++ b/queue-6.6/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch @@ -0,0 +1,94 @@ +From sashal@kernel.org Mon Jul 20 15:30:41 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 09:30:36 -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: <20260720133036.1149470-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 +[ dropped the ffs_func_disable hunk since 6.6 predates the disable-body split and routes disable through ffs_func_set_alt, so removing the single lazy INIT_WORK there covers both paths ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -255,6 +255,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 ****************************************************/ + +@@ -1734,6 +1735,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; +@@ -3261,7 +3263,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; + } diff --git a/queue-6.6/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch b/queue-6.6/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch new file mode 100644 index 0000000000..1fc0f11113 --- /dev/null +++ b/queue-6.6/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch @@ -0,0 +1,55 @@ +From sashal@kernel.org Mon Jul 20 16:17:44 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 10:17:37 -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: <20260720141737.1619471-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 +@@ -1269,7 +1269,6 @@ ffs_epfile_release(struct inode *inode, + { + struct ffs_epfile *epfile = inode->i_private; + +- __ffs_epfile_read_buffer_free(epfile); + ffs_data_closed(epfile->ffs); + + return 0; +@@ -1893,6 +1892,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.6/usb-iowarrior-remove-inherent-race-with-minor-number.patch b/queue-6.6/usb-iowarrior-remove-inherent-race-with-minor-number.patch new file mode 100644 index 0000000000..9821697584 --- /dev/null +++ b/queue-6.6/usb-iowarrior-remove-inherent-race-with-minor-number.patch @@ -0,0 +1,109 @@ +From stable+bounces-277483-greg=kroah.com@vger.kernel.org Sun Jul 19 17:17:20 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 11:17:13 -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: <20260719151714.124320-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; + io_res = 0; + switch (cmd) { +@@ -672,8 +662,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); + +@@ -776,6 +764,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); +@@ -891,12 +880,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.6/usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch b/queue-6.6/usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch new file mode 100644 index 0000000000..4d4f2716b5 --- /dev/null +++ b/queue-6.6/usb-typec-tcpm-fix-vdm-type-for-enter-mode-commands.patch @@ -0,0 +1,39 @@ +From stable+bounces-277507-greg=kroah.com@vger.kernel.org Sun Jul 19 18:57:19 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 12:57:13 -0400 +Subject: usb: typec: tcpm: Fix VDM type for Enter Mode commands +To: stable@vger.kernel.org +Cc: Andy Yan , stable , Heikki Krogerus , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260719165713.748572-1-sashal@kernel.org> + +From: Andy Yan + +[ Upstream commit 9cff680e47632b7723cb19f9c5e63669063c3417 ] + +VDO() second parameter is VDM type (bit 15): 1 for SVDM, 0 for UVDM. +Using 'vdo ? 2 : 1' corrupts SVID low bit when vdo is non-NULL +(2 << 15 = BIT(16)). Enter Mode is always SVDM, hardcode to 1. + +Fixes: 8face9aa57c8 ("usb: typec: Add parameter for the VDO to typec_altmode_enter()") +Cc: stable +Signed-off-by: Andy Yan +Reviewed-by: Heikki Krogerus +Link: https://patch.msgid.link/20260604105059.18750-1-andyshrk@163.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/typec/tcpm/tcpm.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -2237,7 +2237,7 @@ static int tcpm_altmode_enter(struct typ + if (svdm_version < 0) + return svdm_version; + +- header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE); ++ header = VDO(altmode->svid, 1, svdm_version, CMD_ENTER_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0); diff --git a/queue-6.6/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch b/queue-6.6/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch new file mode 100644 index 0000000000..47ca5ad07e --- /dev/null +++ b/queue-6.6/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch @@ -0,0 +1,92 @@ +From stable+bounces-274304-greg=kroah.com@vger.kernel.org Tue Jul 14 16:51:27 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 10:46:37 -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: <20260714144637.2781948-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 | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +--- a/drivers/vfio/pci/mlx5/cmd.h ++++ b/drivers/vfio/pci/mlx5/cmd.h +@@ -147,23 +147,26 @@ struct mlx5_vhca_qp { + struct mlx5_vhca_page_tracker { + u32 id; + u32 pdn; +- u8 is_err:1; ++ /* Flags modified at runtime - dedicated storage unit */ ++ u8 is_err; ++ 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; ++ /* 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.6/writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch b/queue-6.6/writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch new file mode 100644 index 0000000000..7e5f91e699 --- /dev/null +++ b/queue-6.6/writeback-avoid-contention-on-wb-list_lock-when-switching-inodes.patch @@ -0,0 +1,294 @@ +From stable+bounces-276794-greg=kroah.com@vger.kernel.org Thu Jul 16 19:50:37 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 13:50:29 -0400 +Subject: writeback: Avoid contention on wb->list_lock when switching inodes +To: stable@vger.kernel.org +Cc: Jan Kara , Tejun Heo , Sasha Levin +Message-ID: <20260716175030.804337-1-sashal@kernel.org> + +From: Jan Kara + +[ Upstream commit e1b849cfa6b61f1c866a908c9e8dd9b5aaab820b ] + +There can be multiple inode switch works that are trying to switch +inodes to / from the same wb. This can happen in particular if some +cgroup exits which owns many (thousands) inodes and we need to switch +them all. In this case several inode_switch_wbs_work_fn() instances will +be just spinning on the same wb->list_lock while only one of them makes +forward progress. This wastes CPU cycles and quickly leads to softlockup +reports and unusable system. + +Instead of running several inode_switch_wbs_work_fn() instances in +parallel switching to the same wb and contending on wb->list_lock, run +just one work item per wb and manage a queue of isw items switching to +this wb. + +Acked-by: Tejun Heo +Signed-off-by: Jan Kara +Stable-dep-of: cba38ec4cbd3 ("writeback: fix race between cgroup_writeback_umount() and inode_switch_wbs()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/fs-writeback.c | 99 ++++++++++++++++++++++++--------------- + include/linux/backing-dev-defs.h | 4 + + include/linux/writeback.h | 2 + mm/backing-dev.c | 5 + + 4 files changed, 74 insertions(+), 36 deletions(-) + +--- a/fs/fs-writeback.c ++++ b/fs/fs-writeback.c +@@ -345,7 +345,8 @@ static struct bdi_writeback *inode_to_wb + } + + struct inode_switch_wbs_context { +- struct rcu_work work; ++ /* List of queued switching contexts for the wb */ ++ struct llist_node list; + + /* + * Multiple inodes can be switched at once. The switching procedure +@@ -355,7 +356,6 @@ struct inode_switch_wbs_context { + * array embedded into struct inode_switch_wbs_context. Otherwise + * an inode could be left in a non-consistent state. + */ +- struct bdi_writeback *new_wb; + struct inode *inodes[]; + }; + +@@ -464,13 +464,11 @@ skip_switch: + return switched; + } + +-static void inode_switch_wbs_work_fn(struct work_struct *work) ++static void process_inode_switch_wbs(struct bdi_writeback *new_wb, ++ struct inode_switch_wbs_context *isw) + { +- struct inode_switch_wbs_context *isw = +- container_of(to_rcu_work(work), struct inode_switch_wbs_context, work); + struct backing_dev_info *bdi = inode_to_bdi(isw->inodes[0]); + struct bdi_writeback *old_wb = isw->inodes[0]->i_wb; +- struct bdi_writeback *new_wb = isw->new_wb; + unsigned long nr_switched = 0; + struct inode **inodep; + +@@ -530,6 +528,38 @@ relock: + atomic_dec(&isw_nr_in_flight); + } + ++void inode_switch_wbs_work_fn(struct work_struct *work) ++{ ++ struct bdi_writeback *new_wb = container_of(work, struct bdi_writeback, ++ switch_work); ++ struct inode_switch_wbs_context *isw, *next_isw; ++ struct llist_node *list; ++ ++ /* ++ * Grab out reference to wb so that it cannot get freed under us ++ * after we process all the isw items. ++ */ ++ wb_get(new_wb); ++ while (1) { ++ list = llist_del_all(&new_wb->switch_wbs_ctxs); ++ /* Nothing to do? */ ++ if (!list) ++ break; ++ /* ++ * In addition to synchronizing among switchers, I_WB_SWITCH ++ * tells the RCU protected stat update paths to grab the i_page ++ * lock so that stat transfer can synchronize against them. ++ * Let's continue after I_WB_SWITCH is guaranteed to be ++ * visible. ++ */ ++ synchronize_rcu(); ++ ++ llist_for_each_entry_safe(isw, next_isw, list, list) ++ process_inode_switch_wbs(new_wb, isw); ++ } ++ wb_put(new_wb); ++} ++ + static bool inode_prepare_wbs_switch(struct inode *inode, + struct bdi_writeback *new_wb) + { +@@ -559,6 +589,13 @@ static bool inode_prepare_wbs_switch(str + return true; + } + ++static void wb_queue_isw(struct bdi_writeback *wb, ++ struct inode_switch_wbs_context *isw) ++{ ++ if (llist_add(&isw->list, &wb->switch_wbs_ctxs)) ++ queue_work(isw_wq, &wb->switch_work); ++} ++ + /** + * inode_switch_wbs - change the wb association of an inode + * @inode: target inode +@@ -572,6 +609,7 @@ static void inode_switch_wbs(struct inod + struct backing_dev_info *bdi = inode_to_bdi(inode); + struct cgroup_subsys_state *memcg_css; + struct inode_switch_wbs_context *isw; ++ struct bdi_writeback *new_wb = NULL; + + /* noop if seems to be already in progress */ + if (inode->i_state & I_WB_SWITCH) +@@ -596,40 +634,34 @@ static void inode_switch_wbs(struct inod + if (!memcg_css) + goto out_free; + +- isw->new_wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC); ++ new_wb = wb_get_create(bdi, memcg_css, GFP_ATOMIC); + css_put(memcg_css); +- if (!isw->new_wb) ++ if (!new_wb) + goto out_free; + +- if (!inode_prepare_wbs_switch(inode, isw->new_wb)) ++ if (!inode_prepare_wbs_switch(inode, new_wb)) + goto out_free; + + isw->inodes[0] = inode; + +- /* +- * In addition to synchronizing among switchers, I_WB_SWITCH tells +- * the RCU protected stat update paths to grab the i_page +- * lock so that stat transfer can synchronize against them. +- * Let's continue after I_WB_SWITCH is guaranteed to be visible. +- */ +- INIT_RCU_WORK(&isw->work, inode_switch_wbs_work_fn); +- queue_rcu_work(isw_wq, &isw->work); ++ wb_queue_isw(new_wb, isw); + return; + + out_free: + atomic_dec(&isw_nr_in_flight); +- if (isw->new_wb) +- wb_put(isw->new_wb); ++ if (new_wb) ++ wb_put(new_wb); + kfree(isw); + } + +-static bool isw_prepare_wbs_switch(struct inode_switch_wbs_context *isw, ++static bool isw_prepare_wbs_switch(struct bdi_writeback *new_wb, ++ struct inode_switch_wbs_context *isw, + struct list_head *list, int *nr) + { + struct inode *inode; + + list_for_each_entry(inode, list, i_io_list) { +- if (!inode_prepare_wbs_switch(inode, isw->new_wb)) ++ if (!inode_prepare_wbs_switch(inode, new_wb)) + continue; + + isw->inodes[*nr] = inode; +@@ -653,6 +685,7 @@ bool cleanup_offline_cgwb(struct bdi_wri + { + struct cgroup_subsys_state *memcg_css; + struct inode_switch_wbs_context *isw; ++ struct bdi_writeback *new_wb; + int nr; + bool restart = false; + +@@ -665,12 +698,12 @@ bool cleanup_offline_cgwb(struct bdi_wri + + for (memcg_css = wb->memcg_css->parent; memcg_css; + memcg_css = memcg_css->parent) { +- isw->new_wb = wb_get_create(wb->bdi, memcg_css, GFP_KERNEL); +- if (isw->new_wb) ++ new_wb = wb_get_create(wb->bdi, memcg_css, GFP_KERNEL); ++ if (new_wb) + break; + } +- if (unlikely(!isw->new_wb)) +- isw->new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */ ++ if (unlikely(!new_wb)) ++ new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */ + + nr = 0; + spin_lock(&wb->list_lock); +@@ -682,27 +715,21 @@ bool cleanup_offline_cgwb(struct bdi_wri + * bandwidth restrictions, as writeback of inode metadata is not + * accounted for. + */ +- restart = isw_prepare_wbs_switch(isw, &wb->b_attached, &nr); ++ restart = isw_prepare_wbs_switch(new_wb, isw, &wb->b_attached, &nr); + if (!restart) +- restart = isw_prepare_wbs_switch(isw, &wb->b_dirty_time, &nr); ++ restart = isw_prepare_wbs_switch(new_wb, isw, &wb->b_dirty_time, ++ &nr); + spin_unlock(&wb->list_lock); + + /* no attached inodes? bail out */ + if (nr == 0) { + atomic_dec(&isw_nr_in_flight); +- wb_put(isw->new_wb); ++ wb_put(new_wb); + kfree(isw); + return restart; + } + +- /* +- * In addition to synchronizing among switchers, I_WB_SWITCH tells +- * the RCU protected stat update paths to grab the i_page +- * lock so that stat transfer can synchronize against them. +- * Let's continue after I_WB_SWITCH is guaranteed to be visible. +- */ +- INIT_RCU_WORK(&isw->work, inode_switch_wbs_work_fn); +- queue_rcu_work(isw_wq, &isw->work); ++ wb_queue_isw(new_wb, isw); + + return restart; + } +--- a/include/linux/backing-dev-defs.h ++++ b/include/linux/backing-dev-defs.h +@@ -152,6 +152,10 @@ struct bdi_writeback { + struct list_head blkcg_node; /* anchored at blkcg->cgwb_list */ + struct list_head b_attached; /* attached inodes, protected by list_lock */ + struct list_head offline_node; /* anchored at offline_cgwbs */ ++ struct work_struct switch_work; /* work used to perform inode switching ++ * to this wb */ ++ struct llist_head switch_wbs_ctxs; /* queued contexts for ++ * writeback switching */ + + union { + struct work_struct release_work; +--- a/include/linux/writeback.h ++++ b/include/linux/writeback.h +@@ -283,6 +283,8 @@ static inline void wbc_init_bio(struct w + bio_associate_blkg_from_css(bio, wbc->wb->blkcg_css); + } + ++void inode_switch_wbs_work_fn(struct work_struct *work); ++ + #else /* CONFIG_CGROUP_WRITEBACK */ + + static inline void inode_attach_wb(struct inode *inode, struct folio *folio) +--- a/mm/backing-dev.c ++++ b/mm/backing-dev.c +@@ -545,6 +545,7 @@ static void cgwb_release_workfn(struct w + wb_exit(wb); + bdi_put(bdi); + WARN_ON_ONCE(!list_empty(&wb->b_attached)); ++ WARN_ON_ONCE(work_pending(&wb->switch_work)); + call_rcu(&wb->rcu, cgwb_free_rcu); + } + +@@ -621,6 +622,8 @@ static int cgwb_create(struct backing_de + wb->memcg_css = memcg_css; + wb->blkcg_css = blkcg_css; + INIT_LIST_HEAD(&wb->b_attached); ++ INIT_WORK(&wb->switch_work, inode_switch_wbs_work_fn); ++ init_llist_head(&wb->switch_wbs_ctxs); + INIT_WORK(&wb->release_work, cgwb_release_workfn); + set_bit(WB_registered, &wb->state); + bdi_get(bdi); +@@ -751,6 +754,8 @@ static int cgwb_bdi_init(struct backing_ + if (!ret) { + bdi->wb.memcg_css = &root_mem_cgroup->css; + bdi->wb.blkcg_css = blkcg_root_css; ++ INIT_WORK(&bdi->wb.switch_work, inode_switch_wbs_work_fn); ++ init_llist_head(&bdi->wb.switch_wbs_ctxs); + } + return ret; + } diff --git a/queue-6.6/writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch b/queue-6.6/writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch new file mode 100644 index 0000000000..2b9676b2be --- /dev/null +++ b/queue-6.6/writeback-fix-race-between-cgroup_writeback_umount-and-inode_switch_wbs.patch @@ -0,0 +1,181 @@ +From stable+bounces-276795-greg=kroah.com@vger.kernel.org Thu Jul 16 19:56:02 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 13:50:30 -0400 +Subject: writeback: fix race between cgroup_writeback_umount() and inode_switch_wbs() +To: stable@vger.kernel.org +Cc: Baokun Li , Jan Kara , Tejun Heo , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260716175030.804337-2-sashal@kernel.org> + +From: Baokun Li + +[ Upstream commit cba38ec4cbd3a7b8b942a8d52531a05be8a9ff0d ] + +When a container exits, the following BUG_ON() is occasionally triggered: + +================================================================== + VFS: Busy inodes after unmount of sdb (ext4) + ------------[ cut here ]------------ + kernel BUG at fs/super.c:695! + CPU: 3 PID: 6 Comm: containerd-shim Tainted: G OE K 6.6 #1 + pstate: 63400009 (nZCv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--) + pc : generic_shutdown_super+0xf0/0x100 + lr : generic_shutdown_super+0xf0/0x100 + Call trace: + generic_shutdown_super+0xf0/0x100 + kill_block_super+0x20/0x48 + ext4_kill_sb+0x28/0x60 + deactivate_locked_super+0x54/0x130 + deactivate_super+0x84/0xa0 + cleanup_mnt+0xa4/0x140 + __cleanup_mnt+0x18/0x28 + task_work_run+0x78/0xe0 + do_notify_resume+0x204/0x240 +================================================================== + +The root cause is a race between cgroup_writeback_umount() and +inode_switch_wbs()/cleanup_offline_cgwb(). There is a window between +inode_prepare_wbs_switch() returning true and the subsequent +wb_queue_isw() call. Following is the process that triggers the issue: + + CPU A (umount) | CPU B (writeback) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + inode_switch_wbs/cleanup_offline_cgwb + atomic_inc(&isw_nr_in_flight) + inode_prepare_wbs_switch + -> passes SB_ACTIVE check + __iget(inode) + generic_shutdown_super + sb->s_flags &= ~SB_ACTIVE + cgroup_writeback_umount(sb) + smp_mb() + atomic_read(&isw_nr_in_flight) + rcu_barrier() + -> no pending RCU callbacks + flush_workqueue(isw_wq) + -> nothing queued, returns + evict_inodes(sb) + -> Inode skipped as isw still holds a ref. + sop->put_super(sb) + /* destroys percpu counters */ + -> VFS: Busy inodes after unmount! + wb_queue_isw() + queue_work(isw_wq, ...) + /* later in work function */ + inode_switch_wbs_work_fn + process_inode_switch_wbs + iput() -> evict + percpu_counter_dec() // UAF! + +Fix this by extending the RCU read-side critical section in +inode_switch_wbs() and cleanup_offline_cgwb() to cover from +inode_prepare_wbs_switch() through wb_queue_isw(). Since there is +no sleep in this window, rcu_read_lock() can be used. Then add a +synchronize_rcu() in cgroup_writeback_umount() before the existing +rcu_barrier(), so that all in-flight switchers that have passed the +SB_ACTIVE check have completed queue_work() before flush_workqueue() +is called. + +The existing rcu_barrier() is intentionally retained so this fix can +be backported unchanged to stable kernels (5.10.y, 6.6.y, ...) that +still queue switches via queue_rcu_work(). It is a no-op on current +mainline (since commit e1b849cfa6b6 ("writeback: Avoid contention on +wb->list_lock when switching inodes")) and is removed in a follow-up +patch. + +Fixes: a1a0e23e4903 ("writeback: flush inode cgroup wb switches instead of pinning super_block") +Cc: stable@vger.kernel.org +Suggested-by: Jan Kara +Link: https://lore.kernel.org/all/mxnjq2l6guusfchvauxr3v7c4bwjasybxlleqbbh4efloeqspz@iqylk76ohufz +Reviewed-by: Jan Kara +Signed-off-by: Baokun Li +Link: https://patch.msgid.link/20260521095016.2791354-2-libaokun@linux.alibaba.com +Acked-by: Tejun Heo +Signed-off-by: Christian Brauner (Amutable) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/fs-writeback.c | 31 +++++++++++++++++++++++++++++-- + 1 file changed, 29 insertions(+), 2 deletions(-) + +--- a/fs/fs-writeback.c ++++ b/fs/fs-writeback.c +@@ -625,12 +625,19 @@ static void inode_switch_wbs(struct inod + + atomic_inc(&isw_nr_in_flight); + +- /* find and pin the new wb */ ++ /* ++ * Paired with synchronize_rcu() in cgroup_writeback_umount(): ++ * holding rcu_read_lock across inode_prepare_wbs_switch() ++ * (covering the SB_ACTIVE check and the inode grab) and ++ * wb_queue_isw() ensures synchronize_rcu() cannot return until ++ * the work is queued, so the subsequent flush_workqueue() will ++ * wait for the switch. ++ */ + rcu_read_lock(); ++ /* find and pin the new wb */ + memcg_css = css_from_id(new_wb_id, &memory_cgrp_subsys); + if (memcg_css && !css_tryget(memcg_css)) + memcg_css = NULL; +- rcu_read_unlock(); + if (!memcg_css) + goto out_free; + +@@ -645,9 +652,11 @@ static void inode_switch_wbs(struct inod + isw->inodes[0] = inode; + + wb_queue_isw(new_wb, isw); ++ rcu_read_unlock(); + return; + + out_free: ++ rcu_read_unlock(); + atomic_dec(&isw_nr_in_flight); + if (new_wb) + wb_put(new_wb); +@@ -706,6 +715,14 @@ bool cleanup_offline_cgwb(struct bdi_wri + new_wb = &wb->bdi->wb; /* wb_get() is noop for bdi's wb */ + + nr = 0; ++ /* ++ * Paired with synchronize_rcu() in cgroup_writeback_umount(). ++ * Holding rcu_read_lock across the SB_ACTIVE check, the inode grab ++ * and wb_queue_isw() ensures synchronize_rcu() cannot return until ++ * the work is queued, so the subsequent flush_workqueue() will wait ++ * for the switch. ++ */ ++ rcu_read_lock(); + spin_lock(&wb->list_lock); + /* + * In addition to the inodes that have completed writeback, also switch +@@ -723,6 +740,7 @@ bool cleanup_offline_cgwb(struct bdi_wri + + /* no attached inodes? bail out */ + if (nr == 0) { ++ rcu_read_unlock(); + atomic_dec(&isw_nr_in_flight); + wb_put(new_wb); + kfree(isw); +@@ -730,6 +748,7 @@ bool cleanup_offline_cgwb(struct bdi_wri + } + + wb_queue_isw(new_wb, isw); ++ rcu_read_unlock(); + + return restart; + } +@@ -1163,6 +1182,14 @@ void cgroup_writeback_umount(void) + + if (atomic_read(&isw_nr_in_flight)) { + /* ++ * Paired with rcu_read_lock() in inode_switch_wbs() and ++ * cleanup_offline_cgwb(). synchronize_rcu() waits for any ++ * in-flight switcher that already passed the SB_ACTIVE check ++ * to finish queueing its work, so flush_workqueue() below ++ * will then drain it. ++ */ ++ synchronize_rcu(); ++ /* + * Use rcu_barrier() to wait for all pending callbacks to + * ensure that all in-flight wb switches are in the workqueue. + */