From: Greg Kroah-Hartman Date: Tue, 21 Jul 2026 10:07:21 +0000 (+0200) Subject: 6.18-stable patches X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=26d72b078bf3cdfbe06129b117d6921b89e39f4f;p=thirdparty%2Fkernel%2Fstable-queue.git 6.18-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-hda-tas2781-cancel-async-firmware-request-at-unbind.patch alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch binder-cache-secctx-size-before-release-zeroes-it.patch binder-use-list_head-to-initialize-on-stack-list-head.patch bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.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 bpf-consistently-use-reg_state-for-register-access-in-the-verifier.patch bpf-introduce-struct-bpf_map_desc-in-verifier.patch bpf-keep-dynamic-inner-array-lookups-nullable.patch btrfs-concentrate-the-error-handling-of-submit_one_sector.patch btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch cifs-smb1-split-add-some-includes.patch cifs-smb1-split-rename-cifstransport.c.patch crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch firmware_loader-add-cancel-helper-for-async-requests.patch functionfs-don-t-abuse-ffs_data_closed-on-fs-shutdown.patch functionfs-don-t-bother-with-ffs-ref-in-ffs_data_-opened-closed.patch functionfs-switch-to-simple_remove_by_name.patch functionfs-use-spinlock-for-ffs_deactivated-ffs_closing-transitions.patch hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch ksmbd-track-the-connection-owning-a-byte-range-lock.patch ksmbd-use-hmac-md5-library-for-ntlmv2.patch ksmbd-use-hmac-sha256-library-for-message-signing-and-key-generation.patch ksmbd-use-opener-credentials-for-fsctl-mutations.patch ksmbd-use-sha-512-library-for-smb3.1.1-preauth-hash.patch ksmbd-validate-ntlmv2-response-before-updating-session-key.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 new-helper-simple_remove_by_name.patch pci-add-kerneldoc-for-pci_resize_resource.patch pci-change-pci_dev-variable-from-bridge-to-dev.patch pci-fix-restoring-bars-on-bar-resize-rollback-path.patch pci-free-saved-list-without-holding-pci_bus_sem.patch pci-imx6-configure-ref_use_pad-before-phy-reset-for-i.mx95.patch pci-imx6-fix-reference-clock-source-selection-for-i.mx95.patch pci-iov-adjust-barsz-when-changing-bar-size.patch pci-move-resizable-bar-code-to-rebar.c.patch pci-skip-resizable-bar-restore-on-read-error.patch pci-try-bar-resize-even-when-no-window-was-released.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 seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch seqlock-introduce-scoped_seqlock_read.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 usb-atm-ueagle-atm-remove-function-entry-exit-debug-messages.patch usb-atm-ueagle-atm-use-dev_dbg-for-device-found-message.patch usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch usb-dwc3-fix-dwc3_readl-and-dwc3_writel-calls-in-dwc3_ulpi_setup.patch usb-dwc3-support-usb3340x-ulpi-phy-high-speed-negotiation.patch usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch xfs-add-a-xfs_groups_to_rfsbs-helper.patch xfs-initialize-iomap-flags-earlier-in-xfs_bmbt_to_iomap.patch xfs-only-log-freed-extents-for-the-current-rtg-in-zoned-growfs.patch xfs-use-bio_reuse-in-the-zone-gc-code.patch --- diff --git a/queue-6.18/acpi-bus-introduce-devm_acpi_install_notify_handler.patch b/queue-6.18/acpi-bus-introduce-devm_acpi_install_notify_handler.patch new file mode 100644 index 0000000000..13ce7007d3 --- /dev/null +++ b/queue-6.18/acpi-bus-introduce-devm_acpi_install_notify_handler.patch @@ -0,0 +1,123 @@ +From stable+bounces-273270-greg=kroah.com@vger.kernel.org Fri Jul 10 17:53:54 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 11:47:07 -0400 +Subject: ACPI: bus: Introduce devm_acpi_install_notify_handler() +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Sasha Levin +Message-ID: <20260710154710.286956-2-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit ca70ce555a1eb8edf5fb1d5575f1fe19c9ae17f4 ] + +Introduce devm_acpi_install_notify_handler() for installing an ACPI +notify handler managed by devres that will be removed automatically on +driver detach. + +It installs the notify handler on the device object in the ACPI +namespace that corresponds to the owner device's ACPI companion, if +present (an error is returned if the owner device doesn't have an ACPI +companion). + +Currently, there is no way to manually remove the notify handler +installed by it because none of its users brought on subsequently +will need to do that. + +Signed-off-by: Rafael J. Wysocki +[ rjw: Kerneldoc comment refinement ] +Link: https://patch.msgid.link/2268031.irdbgypaU6@rafael.j.wysocki +Signed-off-by: Rafael J. Wysocki +Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/bus.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ + include/acpi/acpi_bus.h | 2 + + 2 files changed, 68 insertions(+) + +--- a/drivers/acpi/bus.c ++++ b/drivers/acpi/bus.c +@@ -622,6 +622,72 @@ void acpi_dev_remove_notify_handler(stru + } + EXPORT_SYMBOL_GPL(acpi_dev_remove_notify_handler); + ++struct acpi_notify_handler_devres { ++ acpi_notify_handler handler; ++ u32 handler_type; ++}; ++ ++static void devm_acpi_notify_handler_release(struct device *dev, void *res) ++{ ++ struct acpi_notify_handler_devres *dr = res; ++ ++ acpi_dev_remove_notify_handler(ACPI_COMPANION(dev), dr->handler_type, ++ dr->handler); ++} ++ ++/** ++ * devm_acpi_install_notify_handler - Install an ACPI notify handler for a ++ * managed device ++ * @dev: Device to install a notify handler for ++ * @handler_type: Type of the notify handler ++ * @handler: Handler function to install ++ * @context: Data passed back to the handler function ++ * ++ * This function performs the same function as acpi_dev_install_notify_handler() ++ * called for the ACPI companion of @dev with the same @handler_type, @handler, ++ * and @context arguments, but the ACPI notify handler installed by it will be ++ * automatically removed on driver detach. ++ * ++ * Callers should ensure that all resources used by @handler have been allocated ++ * prior to invoking this function, in which case those resources should be ++ * devres-managed so that they won't be released before the notify handler ++ * removal. Otherwise, special synchronization between @handler and the ++ * management of those resources is required. ++ * ++ * When the request fails, an error message is printed. Don't add extra error ++ * messages at the call sites. ++ * ++ * Return: 0 on success or a negative error number. ++ */ ++int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type, ++ acpi_notify_handler handler, void *context) ++{ ++ struct acpi_notify_handler_devres *dr; ++ struct acpi_device *adev; ++ int ret; ++ ++ adev = ACPI_COMPANION(dev); ++ if (!adev) ++ return dev_err_probe(dev, -ENODEV, "No ACPI companion in %s()\n", __func__); ++ ++ dr = devres_alloc(devm_acpi_notify_handler_release, sizeof(*dr), GFP_KERNEL); ++ if (!dr) ++ return -ENOMEM; ++ ++ ret = acpi_dev_install_notify_handler(adev, handler_type, handler, context); ++ if (ret) { ++ devres_free(dr); ++ return dev_err_probe(dev, ret, "Failed to install an ACPI notify handler\n"); ++ } ++ ++ dr->handler = handler; ++ dr->handler_type = handler_type; ++ devres_add(dev, dr); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(devm_acpi_install_notify_handler); ++ + /* Handle events targeting \_SB device (at present only graceful shutdown) */ + + #define ACPI_SB_NOTIFY_SHUTDOWN_REQUEST 0x81 +--- a/include/acpi/acpi_bus.h ++++ b/include/acpi/acpi_bus.h +@@ -625,6 +625,8 @@ int acpi_dev_install_notify_handler(stru + void acpi_dev_remove_notify_handler(struct acpi_device *adev, + u32 handler_type, + acpi_notify_handler handler); ++int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type, ++ acpi_notify_handler handler, void *context); + extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32); + extern int register_acpi_notifier(struct notifier_block *); + extern int unregister_acpi_notifier(struct notifier_block *); diff --git a/queue-6.18/acpi-driver-check-acpi_companion-against-null-during-probe.patch b/queue-6.18/acpi-driver-check-acpi_companion-against-null-during-probe.patch new file mode 100644 index 0000000000..da401c36ab --- /dev/null +++ b/queue-6.18/acpi-driver-check-acpi_companion-against-null-during-probe.patch @@ -0,0 +1,144 @@ +From stable+bounces-273269-greg=kroah.com@vger.kernel.org Fri Jul 10 17:53:46 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 11:47:06 -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: <20260710154710.286956-1-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit e4865a56d013e86e46ea6acea15bb6eae01898ff ] + +Since every platform driver can be forced to match a device that doesn't +match its list of device IDs because of device_match_driver_override(), +platform drivers that rely on the existence of a device's ACPI companion +object should verify its presence. + +Accordingly, add requisite ACPI_COMPANION() or ACPI_HANDLE() checks +against NULL to 13 platform drivers handling core ACPI devices. + +Also change the value returned by the ACPI thermal zone driver when +the device's ACPI companion is not present to -ENODEV for consistency +with the other drivers. + +Signed-off-by: Rafael J. Wysocki +Reviewed-by: Hans de Goede +Reviewed-by: Andy Shevchenko +Link: https://patch.msgid.link/4516068.ejJDZkT8p0@rafael.j.wysocki +Cc: 7.0+ # 7.0+ +Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/ac.c | 6 +++++- + drivers/acpi/acpi_pad.c | 6 +++++- + drivers/acpi/acpi_tad.c | 6 +++++- + drivers/acpi/pfr_telemetry.c | 6 +++++- + drivers/acpi/pfr_update.c | 6 +++++- + drivers/acpi/thermal.c | 2 +- + 6 files changed, 26 insertions(+), 6 deletions(-) + +--- a/drivers/acpi/ac.c ++++ b/drivers/acpi/ac.c +@@ -203,11 +203,15 @@ static const struct dmi_system_id ac_dmi + + static int acpi_ac_probe(struct platform_device *pdev) + { +- struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct power_supply_config psy_cfg = {}; ++ struct acpi_device *adev; + struct acpi_ac *ac; + int result; + ++ adev = ACPI_COMPANION(&pdev->dev); ++ if (!adev) ++ return -ENODEV; ++ + ac = kzalloc(sizeof(struct acpi_ac), GFP_KERNEL); + if (!ac) + return -ENOMEM; +--- a/drivers/acpi/acpi_pad.c ++++ b/drivers/acpi/acpi_pad.c +@@ -426,9 +426,13 @@ static void acpi_pad_notify(acpi_handle + + static int acpi_pad_probe(struct platform_device *pdev) + { +- struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); ++ struct acpi_device *adev; + acpi_status status; + ++ adev = ACPI_COMPANION(&pdev->dev); ++ if (!adev) ++ return -ENODEV; ++ + strscpy(acpi_device_name(adev), ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME); + strscpy(acpi_device_class(adev), ACPI_PROCESSOR_AGGREGATOR_CLASS); + +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -588,12 +588,16 @@ static void acpi_tad_remove(struct platf + static int acpi_tad_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +- acpi_handle handle = ACPI_HANDLE(dev); + struct acpi_tad_driver_data *dd; ++ acpi_handle handle; + acpi_status status; + unsigned long long caps; + int ret; + ++ handle = ACPI_HANDLE(dev); ++ if (!handle) ++ return -ENODEV; ++ + ret = acpi_install_cmos_rtc_space_handler(handle); + if (ret < 0) { + dev_info(dev, "Unable to install space handler\n"); +--- a/drivers/acpi/pfr_telemetry.c ++++ b/drivers/acpi/pfr_telemetry.c +@@ -360,10 +360,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 +@@ -538,10 +538,14 @@ static void pfru_put_idx(void *data) + + static int acpi_pfru_probe(struct platform_device *pdev) + { +- acpi_handle handle = ACPI_HANDLE(&pdev->dev); + struct pfru_device *pfru_dev; ++ acpi_handle handle; + int ret; + ++ handle = ACPI_HANDLE(&pdev->dev); ++ if (!handle) ++ return -ENODEV; ++ + if (!acpi_has_method(handle, "_DSM")) { + dev_dbg(&pdev->dev, "Missing _DSM\n"); + return -ENODEV; +--- a/drivers/acpi/thermal.c ++++ b/drivers/acpi/thermal.c +@@ -789,7 +789,7 @@ static int acpi_thermal_add(struct acpi_ + int i; + + if (!device) +- return -EINVAL; ++ return -ENODEV; + + tz = kzalloc(sizeof(struct acpi_thermal), GFP_KERNEL); + if (!tz) diff --git a/queue-6.18/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch b/queue-6.18/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch new file mode 100644 index 0000000000..7e47dabddb --- /dev/null +++ b/queue-6.18/acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch @@ -0,0 +1,110 @@ +From stable+bounces-273237-greg=kroah.com@vger.kernel.org Fri Jul 10 15:33:07 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 09:32:35 -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: <20260710133235.81141-1-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit 38bf27511ef41bffebd157ec3eba41fc89ba59cd ] + +If acpi_nfit_init() fails after adding the acpi_desc object to the +acpi_descs list, that object is never removed from that list because +the acpi_nfit_shutdown() devm action is not added for the NFIT device +in that case. Next, the acpi_nfit_init() failure causes +acpi_nfit_probe() to fail, the acpi_desc object is freed, and a +dangling pointer is left behind in the acpi_descs. Any subsequent +ACPI Machine Check Exception will trigger nfit_handle_mce() which +iterates over acpi_descs and so a use-after-free will occur. + +Moreover, if acpi_nfit_probe() returns 0 after installing a notify +handler for the NFIT device and without allocating the acpi_desc +object and setting the NFIT device's driver data pointer, the +acpi_desc object will be allocated by acpi_nfit_update_notify() +and acpi_nfit_init() will be called to initialize it. Regardless +of whether or not acpi_nfit_init() fails in that case, the +acpi_nfit_shutdown() devm action is not added for the NFIT device +and acpi_desc is never removed from the acpi_descs list. If the +acpi_desc object is freed subsequently on driver removal, any +subsequent ACPI MCE will lead to a use-after-free like in the +previous case. + +To address the first issue mentioned above, make acpi_nfit_probe() +call acpi_nfit_shutdown() directly on acpi_nfit_init() failures and +to address the other one, add a remove callback to the driver and +make it call acpi_nfit_shutdown(). Also, since it is now possible to +pass NULL to acpi_nfit_shutdown() or the acpi_desc object passed to it +may not have been initialized, add checks against NULL for acpi_desc and +its nvdimm_bus field to that function and make acpi_nfit_unregister() +clear the latter after unregistering the NVDIMM bus. + +Fixes: a61fe6f7902e ("nfit, tools/testing/nvdimm: unify common init for acpi_nfit_desc") +Fixes: fbabd829fe76 ("acpi, nfit: fix module unload vs workqueue shutdown race") +Signed-off-by: Rafael J. Wysocki +Cc: All applicable +Reviewed-by: Dave Jiang +Link: https://patch.msgid.link/1963615.tdWV9SEqCh@rafael.j.wysocki +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/nfit/core.c | 18 +++++++++++++++--- + 1 file changed, 15 insertions(+), 3 deletions(-) + +--- a/drivers/acpi/nfit/core.c ++++ b/drivers/acpi/nfit/core.c +@@ -3061,6 +3061,8 @@ static void acpi_nfit_unregister(void *d + struct acpi_nfit_desc *acpi_desc = data; + + nvdimm_bus_unregister(acpi_desc->nvdimm_bus); ++ /* The nvdimm_bus object may have been freed, so clear the pointer. */ ++ acpi_desc->nvdimm_bus = NULL; + } + + int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *data, acpi_size sz) +@@ -3301,7 +3303,10 @@ static void acpi_nfit_remove_notify_hand + void acpi_nfit_shutdown(void *data) + { + struct acpi_nfit_desc *acpi_desc = data; +- struct device *bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus); ++ struct device *bus_dev; ++ ++ if (!acpi_desc || !acpi_desc->nvdimm_bus) ++ return; + + /* + * Destruct under acpi_desc_lock so that nfit_handle_mce does not +@@ -3316,6 +3321,7 @@ void acpi_nfit_shutdown(void *data) + mutex_unlock(&acpi_desc->init_mutex); + cancel_delayed_work_sync(&acpi_desc->dwork); + ++ bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus); + /* + * Bounce the nvdimm bus lock to make sure any in-flight + * acpi_nfit_ars_rescan() submissions have had a chance to +@@ -3393,9 +3399,14 @@ static int acpi_nfit_add(struct acpi_dev + sz - sizeof(struct acpi_table_nfit)); + + if (rc) +- return rc; ++ acpi_nfit_shutdown(acpi_desc); + +- return devm_add_action_or_reset(dev, acpi_nfit_shutdown, acpi_desc); ++ return rc; ++} ++ ++static void acpi_nfit_remove(struct acpi_device *adev) ++{ ++ acpi_nfit_shutdown(dev_get_drvdata(&adev->dev)); + } + + static void acpi_nfit_update_notify(struct device *dev, acpi_handle handle) +@@ -3482,6 +3493,7 @@ static struct acpi_driver acpi_nfit_driv + .ids = acpi_nfit_ids, + .ops = { + .add = acpi_nfit_add, ++ .remove = acpi_nfit_remove, + }, + }; + diff --git a/queue-6.18/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch b/queue-6.18/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch new file mode 100644 index 0000000000..d85a1725e2 --- /dev/null +++ b/queue-6.18/acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch @@ -0,0 +1,222 @@ +From stable+bounces-273273-greg=kroah.com@vger.kernel.org Fri Jul 10 17:47:23 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 11:47:10 -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: <20260710154710.286956-5-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit 18a00ed0e718473f5c3fcfa49df46c944575e60c ] + +After commit 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before +getting NFIT table"), ACPI NFIT driver removal may deadlock if an ACPI +notify on the NFIT device is triggered concurrently. A similar deadlock +may occur if an ACPI notify on the NFIT device is triggered during a +failing driver probe. + +The deadlock is possible because acpi_dev_remove_notify_handler() calls +acpi_os_wait_events_complete() after removing the notify handler and the +driver core invokes it under the NFIT platform device lock which is also +acquired by acpi_nfit_notify(). Thus acpi_os_wait_events_complete() may +be waiting for acpi_nfit_notify() to complete, but the latter may not be +able to acquire the device lock which is being held by the driver core +while the former is being executed. + +Moreover, after commit 03667e146f81 ("ACPI: NFIT: core: Convert the +driver to a platform one"), there are no sysfs notifications regarding +NVDIMM devices because __acpi_nvdimm_notify() always bails out after +checking the driver data pointer of the device's parent. That parent +is the ACPI companion of the platform device used for driver binding, +so its driver data pointer is always NULL after the commit in question +which was overlooked by it. + +A remedy for the deadlock is to use a special separate lock for ACPI +notify synchronization with driver probe and removal instead of the +device lock of the NFIT device, while a remedy for the second issue +is to populate the driver data pointer of the NFIT device's ACPI +companion when the driver is ready to operate, so do both these things. +However, since the new lock is not held across the entire teardown and +acpi_nfit_notify() should do nothing when teardown is in progress, make +it check the driver data pointer of the NFIT device's ACPI companion, in +analogy with the existing check in __acpi_nvdimm_notify(), and bail out +if that pointer is NULL. + +Fixes: 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before getting NFIT table") +Fixes: 03667e146f81 ("ACPI: NFIT: core: Convert the driver to a platform one") +Signed-off-by: Rafael J. Wysocki +Cc: All applicable # 9995e4404ea4: ACPI: NFIT: core: Eliminate redundant local variable +Reviewed-by: Dave Jiang +Link: https://patch.msgid.link/3420096.aeNJFYEL58@rafael.j.wysocki +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/bus.c | 6 +++- + drivers/acpi/nfit/core.c | 62 +++++++++++++++++++++++++++++++++++++---------- + 2 files changed, 54 insertions(+), 14 deletions(-) + +--- a/drivers/acpi/bus.c ++++ b/drivers/acpi/bus.c +@@ -623,6 +623,7 @@ void acpi_dev_remove_notify_handler(stru + EXPORT_SYMBOL_GPL(acpi_dev_remove_notify_handler); + + struct acpi_notify_handler_devres { ++ struct acpi_device *adev; + acpi_notify_handler handler; + u32 handler_type; + }; +@@ -631,7 +632,7 @@ static void devm_acpi_notify_handler_rel + { + struct acpi_notify_handler_devres *dr = res; + +- acpi_dev_remove_notify_handler(ACPI_COMPANION(dev), dr->handler_type, ++ acpi_dev_remove_notify_handler(dr->adev, dr->handler_type, + dr->handler); + } + +@@ -667,6 +668,8 @@ int devm_acpi_install_notify_handler(str + int ret; + + adev = ACPI_COMPANION(dev); ++ if (!adev && dev->bus == &acpi_bus_type) ++ adev = to_acpi_device(dev); + if (!adev) + return dev_err_probe(dev, -ENODEV, "No ACPI companion in %s()\n", __func__); + +@@ -680,6 +683,7 @@ int devm_acpi_install_notify_handler(str + return dev_err_probe(dev, ret, "Failed to install an ACPI notify handler\n"); + } + ++ dr->adev = adev; + dr->handler = handler; + dr->handler_type = handler_type; + devres_add(dev, dr); +--- a/drivers/acpi/nfit/core.c ++++ b/drivers/acpi/nfit/core.c +@@ -55,6 +55,8 @@ MODULE_PARM_DESC(force_labels, "Opt-in t + LIST_HEAD(acpi_descs); + DEFINE_MUTEX(acpi_desc_lock); + ++DEFINE_MUTEX(acpi_notify_lock); ++ + static struct workqueue_struct *nfit_wq; + + struct nfit_table_prev { +@@ -1702,9 +1704,15 @@ static void acpi_nvdimm_notify(acpi_hand + struct acpi_device *adev = data; + struct device *dev = &adev->dev; + +- device_lock(dev->parent); +- __acpi_nvdimm_notify(dev, event); +- device_unlock(dev->parent); ++ /* ++ * Locking is needed here for synchronization with driver probe and ++ * removal and the parent NFIT device's ACPI driver data pointer is ++ * NULL when teardown is in progress. ++ */ ++ guard(mutex)(&acpi_notify_lock); ++ ++ if (acpi_driver_data(to_acpi_device(dev->parent))) ++ __acpi_nvdimm_notify(dev, event); + } + + static bool acpi_nvdimm_has_method(struct acpi_device *adev, char *method) +@@ -3150,11 +3158,10 @@ EXPORT_SYMBOL_GPL(acpi_nfit_init); + static int acpi_nfit_flush_probe(struct nvdimm_bus_descriptor *nd_desc) + { + struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc); +- struct device *dev = acpi_desc->dev; + +- /* Bounce the device lock to flush acpi_nfit_add / acpi_nfit_notify */ +- device_lock(dev); +- device_unlock(dev); ++ /* Bounce the notify lock to flush acpi_nfit_add / acpi_nfit_notify */ ++ mutex_lock(&acpi_notify_lock); ++ mutex_unlock(&acpi_notify_lock); + + /* Bounce the init_mutex to complete initial registration */ + mutex_lock(&acpi_desc->init_mutex); +@@ -3286,10 +3293,17 @@ static void acpi_nfit_put_table(void *ta + static void acpi_nfit_notify(acpi_handle handle, u32 event, void *data) + { + struct device *dev = data; ++ struct acpi_device *adev = to_acpi_device(dev); + +- device_lock(dev); +- __acpi_nfit_notify(dev, handle, event); +- device_unlock(dev); ++ /* ++ * Locking is needed here for synchronization with driver probe and ++ * removal and the ACPI driver data pointer is NULL when teardown ++ * is in progress. ++ */ ++ guard(mutex)(&acpi_notify_lock); ++ ++ if (acpi_driver_data(adev)) ++ __acpi_nfit_notify(dev, handle, event); + } + + void acpi_nfit_shutdown(void *data) +@@ -3336,6 +3350,12 @@ static int acpi_nfit_add(struct acpi_dev + acpi_size sz; + int rc = 0; + ++ /* ++ * Prevent acpi_nfit_notify() from progressing until the probe is ++ * complete in case there is a concurrent event to process. ++ */ ++ guard(mutex)(&acpi_notify_lock); ++ + rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY, + acpi_nfit_notify, dev); + if (rc) +@@ -3351,6 +3371,11 @@ static int acpi_nfit_add(struct acpi_dev + * data in the format of a series of NFIT Structures. + */ + dev_dbg(dev, "failed to find NFIT at startup\n"); ++ /* ++ * Let acpi_nfit_update_notify() run in case it will need to ++ * allocate the acpi_desc object. ++ */ ++ adev->driver_data = dev; + return 0; + } + +@@ -3368,7 +3393,7 @@ static int acpi_nfit_add(struct acpi_dev + acpi_desc->acpi_header = *tbl; + + /* Evaluate _FIT and override with that if present */ +- status = acpi_evaluate_object(ACPI_HANDLE(dev), "_FIT", NULL, &buf); ++ status = acpi_evaluate_object(adev->handle, "_FIT", NULL, &buf); + if (ACPI_SUCCESS(status) && buf.length > 0) { + union acpi_object *obj = buf.pointer; + +@@ -3385,14 +3410,25 @@ static int acpi_nfit_add(struct acpi_dev + + sizeof(struct acpi_table_nfit), + sz - sizeof(struct acpi_table_nfit)); + +- if (rc) ++ if (rc) { + acpi_nfit_shutdown(acpi_desc); ++ return rc; ++ } + +- return rc; ++ /* ++ * Let notify handlers operate (the actual value of the ACPI driver ++ * data pointer does not matter here so long as it is not NULL). ++ */ ++ adev->driver_data = dev; ++ return 0; + } + + static void acpi_nfit_remove(struct acpi_device *adev) + { ++ guard(mutex)(&acpi_notify_lock); ++ ++ /* Make notify handlers bail out early going forward. */ ++ adev->driver_data = NULL; + acpi_nfit_shutdown(dev_get_drvdata(&adev->dev)); + } + diff --git a/queue-6.18/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch b/queue-6.18/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch new file mode 100644 index 0000000000..18e49ff8e0 --- /dev/null +++ b/queue-6.18/acpi-nfit-core-use-devm_acpi_install_notify_handler.patch @@ -0,0 +1,83 @@ +From stable+bounces-273271-greg=kroah.com@vger.kernel.org Fri Jul 10 17:53:59 2026 +From: Sasha Levin +Date: Fri, 10 Jul 2026 11:47:08 -0400 +Subject: ACPI: NFIT: core: Use devm_acpi_install_notify_handler() +To: stable@vger.kernel.org +Cc: "Rafael J. Wysocki" , Sasha Levin +Message-ID: <20260710154710.286956-3-sashal@kernel.org> + +From: "Rafael J. Wysocki" + +[ Upstream commit 198541ad53c0d0d891fedea4098f9953a0f566c0 ] + +Now that devm_acpi_install_notify_handler() is available, use it in +acpi_nfit_probe() instead of a custom devm action removing an ACPI +notify handler installed via acpi_dev_install_notify_handler(). + +Also drop the explicit ACPI_COMPANION() check against NULL that is +not necessary any more becuase devm_acpi_install_notify_handler() +carries out an equivalent check internally and use ACPI_HANDLE() to +retrieve the platform device's ACPI handle. + +No intentional functional impact. + +Signed-off-by: Rafael J. Wysocki +Link: https://patch.msgid.link/3048737.e9J7NaK4W3@rafael.j.wysocki +Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/acpi/nfit/core.c | 27 +++++++-------------------- + 1 file changed, 7 insertions(+), 20 deletions(-) + +--- a/drivers/acpi/nfit/core.c ++++ b/drivers/acpi/nfit/core.c +@@ -3285,19 +3285,11 @@ static void acpi_nfit_put_table(void *ta + + static void acpi_nfit_notify(acpi_handle handle, u32 event, void *data) + { +- struct acpi_device *adev = data; ++ struct device *dev = data; + +- device_lock(&adev->dev); +- __acpi_nfit_notify(&adev->dev, handle, event); +- device_unlock(&adev->dev); +-} +- +-static void acpi_nfit_remove_notify_handler(void *data) +-{ +- struct acpi_device *adev = data; +- +- acpi_dev_remove_notify_handler(adev, ACPI_DEVICE_NOTIFY, +- acpi_nfit_notify); ++ device_lock(dev); ++ __acpi_nfit_notify(dev, handle, event); ++ device_unlock(dev); + } + + void acpi_nfit_shutdown(void *data) +@@ -3344,13 +3336,8 @@ static int acpi_nfit_add(struct acpi_dev + acpi_size sz; + int rc = 0; + +- rc = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, +- acpi_nfit_notify, adev); +- if (rc) +- return rc; +- +- rc = devm_add_action_or_reset(dev, acpi_nfit_remove_notify_handler, +- adev); ++ rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY, ++ acpi_nfit_notify, dev); + if (rc) + return rc; + +@@ -3381,7 +3368,7 @@ static int acpi_nfit_add(struct acpi_dev + acpi_desc->acpi_header = *tbl; + + /* Evaluate _FIT and override with that if present */ +- status = acpi_evaluate_object(adev->handle, "_FIT", NULL, &buf); ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "_FIT", NULL, &buf); + if (ACPI_SUCCESS(status) && buf.length > 0) { + union acpi_object *obj = buf.pointer; + diff --git a/queue-6.18/alsa-hda-tas2781-cancel-async-firmware-request-at-unbind.patch b/queue-6.18/alsa-hda-tas2781-cancel-async-firmware-request-at-unbind.patch new file mode 100644 index 0000000000..e615950d62 --- /dev/null +++ b/queue-6.18/alsa-hda-tas2781-cancel-async-firmware-request-at-unbind.patch @@ -0,0 +1,65 @@ +From stable+bounces-274118-greg=kroah.com@vger.kernel.org Tue Jul 14 06:05:49 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 00:05:39 -0400 +Subject: ALSA: hda/tas2781: Cancel async firmware request at unbind +To: stable@vger.kernel.org +Cc: "Cássio Gabriel" , "Takashi Iwai" , "Danilo Krummrich" , "Sasha Levin" +Message-ID: <20260714040539.2433035-2-sashal@kernel.org> + +From: Cássio Gabriel + +[ Upstream commit 5367e2ad14f0ae9350a7aaf2e77c87de39a43ae9 ] + +TAS2781 HDA I2C and SPI queue RCA firmware loading from component +bind with request_firmware_nowait(). The firmware loader keeps the +callback module pinned and holds a device reference, but the callback +still uses driver-private HDA state. + +Component unbind removes controls and DSP state immediately. Later +device removal tears down the TAS2781 private data, including +codec_lock. If the async firmware callback runs after unbind has +started, it can operate on state that is being torn down. + +Cancel or synchronize the async firmware request before removing +controls and DSP state. A queued callback is cancelled, and an +already-running callback is allowed to finish before unbind continues. + +Fixes: 5be27f1e3ec9 ("ALSA: hda/tas2781: Add tas2781 HDA driver") +Fixes: bb5f86ea50ff ("ALSA: hda/tas2781: Add tas2781 hda SPI driver") +Cc: stable@vger.kernel.org +Signed-off-by: Cássio Gabriel +Reviewed-by: Takashi Iwai +Acked-by: Danilo Krummrich +Signed-off-by: Takashi Iwai +Link: https://patch.msgid.link/20260505-alsa-hda-tas2781-fw-callback-teardown-v4-2-e7c4bf930dc8@gmail.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 3 +++ + sound/hda/codecs/side-codecs/tas2781_hda_spi.c | 3 +++ + 2 files changed, 6 insertions(+) + +--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +@@ -616,6 +616,9 @@ static void tas2781_hda_unbind(struct de + comp->playback_hook = NULL; + } + ++ request_firmware_nowait_cancel(tas_hda->priv->dev, tas_hda->priv, ++ tasdev_fw_ready); ++ + tas2781_hda_remove_controls(tas_hda); + + tasdevice_config_info_remove(tas_hda->priv); +--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +@@ -753,6 +753,9 @@ static void tas2781_hda_unbind(struct de + comp->playback_hook = NULL; + } + ++ request_firmware_nowait_cancel(tas_priv->dev, tas_priv, ++ tasdev_fw_ready); ++ + tas2781_hda_remove_controls(tas_hda); + + tasdevice_config_info_remove(tas_priv); diff --git a/queue-6.18/alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch b/queue-6.18/alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch new file mode 100644 index 0000000000..92cf9ee9b7 --- /dev/null +++ b/queue-6.18/alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch @@ -0,0 +1,340 @@ +From stable+bounces-274082-greg=kroah.com@vger.kernel.org Tue Jul 14 04:10:15 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 22:09:13 -0400 +Subject: ALSA: scarlett2: Allow selecting config_set by firmware version +To: stable@vger.kernel.org +Cc: "Geoffrey D. Bennett" , Takashi Iwai , Sasha Levin +Message-ID: <20260714020914.2356076-1-sashal@kernel.org> + +From: "Geoffrey D. Bennett" + +[ Upstream commit 732a6397a526c025cd29c3c9309b0db6a2c08837 ] + +The Scarlett 2i2 Gen 4 firmware 2417 moved the direct monitor gain +parameters, so we now need to allow each device to list multiple +scarlett2_config_set entries, one per applicable firmware version +range, and pick the matching one at probe time. + +No functional change yet: each device gets a single config_sets +entry whose from_firmware_version matches the existing +min_firmware_version (0 where none was set). This both prepares for +selection and lets a follow-up commit remove the now-redundant +min_firmware_version field. + +scarlett2_count_io() depends on the resolved config_set so it moves +out of scarlett2_init_private() into snd_scarlett2_controls_create() +after the firmware version has been read. + +Signed-off-by: Geoffrey D. Bennett +Signed-off-by: Takashi Iwai +Link: https://patch.msgid.link/ae1695b4c4825f365b4c86b22174035f742807e3.1777151532.git.g@b4.vu +Stable-dep-of: 3ca15754b561 ("ALSA: scarlett2: Update offsets for 2i2 Gen 4 firmware 2417") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/usb/mixer_scarlett2.c | 143 +++++++++++++++++++++++++++++++++++++------- + 1 file changed, 121 insertions(+), 22 deletions(-) + +--- a/sound/usb/mixer_scarlett2.c ++++ b/sound/usb/mixer_scarlett2.c +@@ -612,6 +612,20 @@ struct scarlett2_config_set { + const struct scarlett2_config items[SCARLETT2_CONFIG_COUNT]; + }; + ++/* Map firmware versions to config sets per-device. ++ * ++ * Each device lists one or more entries, sorted in ascending order of ++ * from_firmware_version. At probe time the running firmware version ++ * is looked up against this list and the last entry whose ++ * from_firmware_version is <= the running version is selected. ++ * ++ * The list is terminated by a sentinel entry with config_set == NULL. ++ */ ++struct scarlett2_config_set_entry { ++ u16 from_firmware_version; ++ const struct scarlett2_config_set *config_set; ++}; ++ + /* Input gain TLV dB ranges */ + + static const DECLARE_TLV_DB_MINMAX( +@@ -1100,8 +1114,8 @@ struct scarlett2_meter_entry { + }; + + struct scarlett2_device_info { +- /* which set of configuration parameters the device uses */ +- const struct scarlett2_config_set *config_set; ++ /* which sets of configuration parameters the device uses */ ++ const struct scarlett2_config_set_entry *config_sets; + + /* minimum firmware version required */ + u16 min_firmware_version; +@@ -1343,7 +1357,10 @@ struct scarlett2_data { + /*** Model-specific data ***/ + + static const struct scarlett2_device_info s6i6_gen2_info = { +- .config_set = &scarlett2_config_set_gen2a, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen2a }, ++ { } ++ }, + .level_input_count = 2, + .pad_input_count = 2, + +@@ -1393,7 +1410,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s18i8_gen2_info = { +- .config_set = &scarlett2_config_set_gen2a, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen2a }, ++ { } ++ }, + .level_input_count = 2, + .pad_input_count = 4, + +@@ -1446,7 +1466,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s18i20_gen2_info = { +- .config_set = &scarlett2_config_set_gen2b, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen2b }, ++ { } ++ }, + + .line_out_descrs = { + "Monitor L", +@@ -1503,7 +1526,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info solo_gen3_info = { +- .config_set = &scarlett2_config_set_gen3a, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3a }, ++ { } ++ }, + .level_input_count = 1, + .level_input_first = 1, + .air_input_count = 1, +@@ -1513,7 +1539,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s2i2_gen3_info = { +- .config_set = &scarlett2_config_set_gen3a, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3a }, ++ { } ++ }, + .level_input_count = 2, + .air_input_count = 2, + .phantom_count = 1, +@@ -1522,7 +1551,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s4i4_gen3_info = { +- .config_set = &scarlett2_config_set_gen3b, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3b }, ++ { } ++ }, + .level_input_count = 2, + .pad_input_count = 2, + .air_input_count = 2, +@@ -1571,7 +1603,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s8i6_gen3_info = { +- .config_set = &scarlett2_config_set_gen3b, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3b }, ++ { } ++ }, + .level_input_count = 2, + .pad_input_count = 2, + .air_input_count = 2, +@@ -1637,7 +1672,10 @@ static const char * const scarlett2_spdi + }; + + static const struct scarlett2_device_info s18i8_gen3_info = { +- .config_set = &scarlett2_config_set_gen3c, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3c }, ++ { } ++ }, + .has_speaker_switching = 1, + .level_input_count = 2, + .pad_input_count = 4, +@@ -1729,7 +1767,10 @@ static const char * const scarlett2_spdi + }; + + static const struct scarlett2_device_info s18i20_gen3_info = { +- .config_set = &scarlett2_config_set_gen3c, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_gen3c }, ++ { } ++ }, + .has_speaker_switching = 1, + .has_talkback = 1, + .level_input_count = 2, +@@ -1803,7 +1844,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info vocaster_one_info = { +- .config_set = &scarlett2_config_set_vocaster, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 1769, &scarlett2_config_set_vocaster }, ++ { } ++ }, + .min_firmware_version = 1769, + .has_devmap = 1, + +@@ -1847,7 +1891,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info vocaster_two_info = { +- .config_set = &scarlett2_config_set_vocaster, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 1769, &scarlett2_config_set_vocaster }, ++ { } ++ }, + .min_firmware_version = 1769, + .has_devmap = 1, + +@@ -1892,7 +1939,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info solo_gen4_info = { +- .config_set = &scarlett2_config_set_gen4_solo, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 2115, &scarlett2_config_set_gen4_solo }, ++ { } ++ }, + .min_firmware_version = 2115, + .has_devmap = 1, + +@@ -1947,7 +1997,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s2i2_gen4_info = { +- .config_set = &scarlett2_config_set_gen4_2i2, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 2115, &scarlett2_config_set_gen4_2i2 }, ++ { } ++ }, + .min_firmware_version = 2115, + .has_devmap = 1, + +@@ -2002,7 +2055,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info s4i4_gen4_info = { +- .config_set = &scarlett2_config_set_gen4_4i4, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 2089, &scarlett2_config_set_gen4_4i4 }, ++ { } ++ }, + .min_firmware_version = 2089, + .has_devmap = 1, + +@@ -2051,7 +2107,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info clarett_2pre_info = { +- .config_set = &scarlett2_config_set_clarett, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_clarett }, ++ { } ++ }, + .level_input_count = 2, + .air_input_count = 2, + +@@ -2107,7 +2166,10 @@ static const char * const scarlett2_spdi + }; + + static const struct scarlett2_device_info clarett_4pre_info = { +- .config_set = &scarlett2_config_set_clarett, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_clarett }, ++ { } ++ }, + .level_input_count = 2, + .air_input_count = 4, + +@@ -2163,7 +2225,10 @@ static const struct scarlett2_device_inf + }; + + static const struct scarlett2_device_info clarett_8pre_info = { +- .config_set = &scarlett2_config_set_clarett, ++ .config_sets = (const struct scarlett2_config_set_entry[]) { ++ { 0, &scarlett2_config_set_clarett }, ++ { } ++ }, + .level_input_count = 2, + .air_input_count = 8, + +@@ -8211,10 +8276,32 @@ static void scarlett2_private_suspend(st + + /*** Initialisation ***/ + ++/* Select the config_set matching the running firmware version. ++ * ++ * The device info's config_sets array is ordered by ascending ++ * from_firmware_version; pick the last entry whose version is <= the ++ * running firmware version. If the running firmware is older than the ++ * first entry's from_firmware_version (i.e. older than the driver's ++ * minimum supported version for this device), the first entry's ++ * config_set is selected anyway so firmware updates can still be done ++ * (requires only the ACK handler), but the usual mixer controls ++ * aren't created. ++ */ ++static void scarlett2_resolve_config_set(struct scarlett2_data *private) ++{ ++ const struct scarlett2_config_set_entry *entry = ++ private->info->config_sets; ++ ++ private->config_set = entry->config_set; ++ for (entry++; entry->config_set; entry++) ++ if (entry->from_firmware_version <= private->firmware_version) ++ private->config_set = entry->config_set; ++} ++ + static void scarlett2_count_io(struct scarlett2_data *private) + { + const struct scarlett2_device_info *info = private->info; +- const struct scarlett2_config_set *config_set = info->config_set; ++ const struct scarlett2_config_set *config_set = private->config_set; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + int port_type, srcs = 0, dsts = 0, i; + +@@ -8311,9 +8398,14 @@ static int scarlett2_init_private(struct + mixer->private_suspend = scarlett2_private_suspend; + + private->info = entry->info; +- private->config_set = entry->info->config_set; ++ ++ /* Set config_set to the first entry's config_set so the ++ * notify handler has a valid pointer while USB init runs; it ++ * is re-resolved once the firmware version has been read. ++ */ ++ private->config_set = entry->info->config_sets[0].config_set; ++ + private->series_name = entry->series_name; +- scarlett2_count_io(private); + private->scarlett2_seq = 0; + private->mixer = mixer; + +@@ -8717,6 +8809,13 @@ static int snd_scarlett2_controls_create + if (err < 0) + return err; + ++ /* Now that the firmware version is known, pick the matching ++ * config_set ++ */ ++ scarlett2_resolve_config_set(private); ++ ++ scarlett2_count_io(private); ++ + /* Get the upgrade & settings flash segment numbers */ + err = scarlett2_get_flash_segment_nums(mixer); + if (err < 0) diff --git a/queue-6.18/alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch b/queue-6.18/alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch new file mode 100644 index 0000000000..6b6d00acc6 --- /dev/null +++ b/queue-6.18/alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch @@ -0,0 +1,103 @@ +From stable+bounces-274083-greg=kroah.com@vger.kernel.org Tue Jul 14 04:09:27 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 22:09:14 -0400 +Subject: ALSA: scarlett2: Update offsets for 2i2 Gen 4 firmware 2417 +To: stable@vger.kernel.org +Cc: "Geoffrey D. Bennett" , Takashi Iwai , Sasha Levin +Message-ID: <20260714020914.2356076-2-sashal@kernel.org> + +From: "Geoffrey D. Bennett" + +[ Upstream commit 3ca15754b561483aa7a1bce51677d6389f8ff5bb ] + +Firmware 2417 for the Scarlett 4th Gen 2i2 moved the direct monitor gain +parameters, so add a second config_set with the shifted offset and +select it for firmware versions >= 2417. + +Fixes: 4e809a299677 ("ALSA: scarlett2: Add support for Solo, 2i2, and 4i4 Gen 4") +Cc: stable@vger.kernel.org # ALSA: scarlett2: Allow selecting config_set by firmware version +Cc: stable@vger.kernel.org # ALSA: scarlett2: Fold min_firmware_version into config_sets +Cc: stable@vger.kernel.org +Signed-off-by: Geoffrey D. Bennett +Signed-off-by: Takashi Iwai +Link: https://patch.msgid.link/ad0fc5a131e76eb656a24e0e198382f7134068fe.1777151532.git.g@b4.vu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + sound/usb/mixer_scarlett2.c | 58 ++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 58 insertions(+) + +--- a/sound/usb/mixer_scarlett2.c ++++ b/sound/usb/mixer_scarlett2.c +@@ -937,6 +937,63 @@ static const struct scarlett2_config_set + } + }; + ++/* 2i2 Gen 4, firmware version 2417 and above ++ * ++ * Firmware 2417 shifted DIRECT_MONITOR_GAIN by 4 bytes; all other ++ * offsets are unchanged from scarlett2_config_set_gen4_2i2. ++ */ ++static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2_2417 = { ++ .notifications = scarlett4_2i2_notifications, ++ .param_buf_addr = 0xfc, ++ .input_gain_tlv = db_scale_gen4_gain, ++ .autogain_status_texts = scarlett2_autogain_status_gen4, ++ .items = { ++ [SCARLETT2_CONFIG_MSD_SWITCH] = { ++ .offset = 0x49, .size = 8, .activate = 4 }, ++ ++ [SCARLETT2_CONFIG_DIRECT_MONITOR] = { ++ .offset = 0x14a, .size = 8, .activate = 16, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { ++ .offset = 0x135, .size = 8, .activate = 10, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { ++ .offset = 0x137, .size = 8 }, ++ ++ [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { ++ .offset = 0x131, .size = 8, .activate = 29, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { ++ .offset = 0x132, .size = 8, .activate = 30, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { ++ .offset = 0x48, .size = 8, .activate = 11, .pbuf = 1, ++ .mute = 1 }, ++ ++ [SCARLETT2_CONFIG_INPUT_GAIN] = { ++ .offset = 0x4b, .size = 8, .activate = 12, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_LEVEL_SWITCH] = { ++ .offset = 0x3c, .size = 8, .activate = 13, .pbuf = 1, ++ .mute = 1 }, ++ ++ [SCARLETT2_CONFIG_SAFE_SWITCH] = { ++ .offset = 0x147, .size = 8, .activate = 14, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_AIR_SWITCH] = { ++ .offset = 0x3e, .size = 8, .activate = 15, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { ++ .offset = 0x14b, .size = 8, .activate = 17, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { ++ .offset = 0x14e, .size = 8, .activate = 18, .pbuf = 1 }, ++ ++ [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { ++ .offset = 0x2a4, .size = 16, .activate = 36 } ++ } ++}; ++ + /* 4i4 Gen 4 */ + static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4 = { + .notifications = scarlett4_4i4_notifications, +@@ -1999,6 +2056,7 @@ static const struct scarlett2_device_inf + static const struct scarlett2_device_info s2i2_gen4_info = { + .config_sets = (const struct scarlett2_config_set_entry[]) { + { 2115, &scarlett2_config_set_gen4_2i2 }, ++ { 2417, &scarlett2_config_set_gen4_2i2_2417 }, + { } + }, + .min_firmware_version = 2115, diff --git a/queue-6.18/binder-cache-secctx-size-before-release-zeroes-it.patch b/queue-6.18/binder-cache-secctx-size-before-release-zeroes-it.patch new file mode 100644 index 0000000000..07e33b2538 --- /dev/null +++ b/queue-6.18/binder-cache-secctx-size-before-release-zeroes-it.patch @@ -0,0 +1,105 @@ +From sashal@kernel.org Tue Jul 14 15:43:11 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 09:43:07 -0400 +Subject: binder: cache secctx size before release zeroes it +To: stable@vger.kernel.org +Cc: Chris Mason , stable , Alice Ryhl , Carlos Llamas , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260714134307.2675829-2-sashal@kernel.org> + +From: Chris Mason + +[ Upstream commit b34826e55aad3520ec813f1f367c11b24b29dc9f ] + +binder_transaction() bounds the scatter-gather buffer area with +sg_buf_end_offset and subtracts the aligned LSM context size because +the secctx is written at the tail of that area. The subtraction reads +lsmctx.len, but that field has already been cleared by the time the +line runs: + + security_secid_to_secctx(secid, &lsmctx) /* lsmctx.len set */ + lsmctx_aligned_size = ALIGN(lsmctx.len, sizeof(u64)) + extra_buffers_size += lsmctx_aligned_size + ... + security_release_secctx(&lsmctx) /* memset zeroes len */ + ... + sg_buf_end_offset = sg_buf_offset + extra_buffers_size + - ALIGN(lsmctx.len, sizeof(u64)) /* ALIGN(0,8) */ + +security_release_secctx() does memset(cp, 0, sizeof(*cp)), so lsmctx.len +reads back as 0 and the subtraction contributes nothing, leaving +sg_buf_end_offset too large by the aligned secctx size on every +transaction to a txn_security_ctx node. + +Each BINDER_TYPE_PTR object then derives buf_left = sg_buf_end_offset - +sg_buf_offset as the sole upper bound on its copy, so the inflated end +offset lets the copy run into the bytes that already hold the secctx. + +The aligned size must therefore be cached before release rather than +re-read from the now-cleared field. Fix by caching it in +lsmctx_aligned_size at function scope when it is first computed and +subtracting lsmctx_aligned_size instead of re-reading lsmctx.len after +release. Reuse the same value for the earlier buf_offset computation. + +Fixes: 6fba89813ccf ("lsm: ensure the correct LSM context releaser") +Cc: stable +Assisted-by: kres:claude-opus-4-8 +Signed-off-by: Chris Mason +Reviewed-by: Alice Ryhl +Acked-by: Carlos Llamas +Link: https://patch.msgid.link/20260603174506.1957278-1-clm@meta.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/android/binder.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +--- a/drivers/android/binder.c ++++ b/drivers/android/binder.c +@@ -3097,6 +3097,7 @@ static void binder_transaction(struct bi + int t_debug_id = atomic_inc_return(&binder_last_id); + ktime_t t_start_time = ktime_get(); + struct lsm_context lsmctx = { }; ++ size_t lsmctx_aligned_size = 0; + LIST_HEAD(sgc_head); + LIST_HEAD(pf_head); + const void __user *user_buffer = (const void __user *) +@@ -3363,7 +3364,6 @@ static void binder_transaction(struct bi + + if (target_node && target_node->txn_security_ctx) { + u32 secid; +- size_t added_size; + + security_cred_getsecid(proc->cred, &secid); + ret = security_secid_to_secctx(secid, &lsmctx); +@@ -3375,9 +3375,9 @@ static void binder_transaction(struct bi + return_error_line = __LINE__; + goto err_get_secctx_failed; + } +- added_size = ALIGN(lsmctx.len, sizeof(u64)); +- extra_buffers_size += added_size; +- if (extra_buffers_size < added_size) { ++ lsmctx_aligned_size = ALIGN(lsmctx.len, sizeof(u64)); ++ extra_buffers_size += lsmctx_aligned_size; ++ if (extra_buffers_size < lsmctx_aligned_size) { + binder_txn_error("%d:%d integer overflow of extra_buffers_size\n", + thread->pid, proc->pid); + return_error = BR_FAILED_REPLY; +@@ -3414,7 +3414,7 @@ static void binder_transaction(struct bi + size_t buf_offset = ALIGN(tr->data_size, sizeof(void *)) + + ALIGN(tr->offsets_size, sizeof(void *)) + + ALIGN(extra_buffers_size, sizeof(void *)) - +- ALIGN(lsmctx.len, sizeof(u64)); ++ lsmctx_aligned_size; + + t->security_ctx = t->buffer->user_data + buf_offset; + err = binder_alloc_copy_to_buffer(&target_proc->alloc, +@@ -3469,7 +3469,7 @@ static void binder_transaction(struct bi + off_end_offset = off_start_offset + tr->offsets_size; + sg_buf_offset = ALIGN(off_end_offset, sizeof(void *)); + sg_buf_end_offset = sg_buf_offset + extra_buffers_size - +- ALIGN(lsmctx.len, sizeof(u64)); ++ lsmctx_aligned_size; + off_min = 0; + for (buffer_offset = off_start_offset; buffer_offset < off_end_offset; + buffer_offset += sizeof(binder_size_t)) { diff --git a/queue-6.18/binder-use-list_head-to-initialize-on-stack-list-head.patch b/queue-6.18/binder-use-list_head-to-initialize-on-stack-list-head.patch new file mode 100644 index 0000000000..3eac07bb43 --- /dev/null +++ b/queue-6.18/binder-use-list_head-to-initialize-on-stack-list-head.patch @@ -0,0 +1,52 @@ +From sashal@kernel.org Tue Jul 14 15:43:10 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 09:43:06 -0400 +Subject: binder: Use LIST_HEAD() to initialize on stack list head +To: stable@vger.kernel.org +Cc: Jisheng Zhang , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260714134307.2675829-1-sashal@kernel.org> + +From: Jisheng Zhang + +[ Upstream commit 91f818b184b44b105b1c92859ea8d2d6f47912a9 ] + +Use LIST_HEAD to initialize on stack list head. No intentional +functional impact. + +Change generated with below coccinelle script: + +@@ +identifier name; +@@ +- struct list_head name; ++ LIST_HEAD(name); +... when != name +- INIT_LIST_HEAD(&name); + +Signed-off-by: Jisheng Zhang +Link: https://patch.msgid.link/20260519055623.13142-1-jszhang@kernel.org +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: b34826e55aad ("binder: cache secctx size before release zeroes it") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/android/binder.c | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +--- a/drivers/android/binder.c ++++ b/drivers/android/binder.c +@@ -3097,12 +3097,10 @@ static void binder_transaction(struct bi + int t_debug_id = atomic_inc_return(&binder_last_id); + ktime_t t_start_time = ktime_get(); + struct lsm_context lsmctx = { }; +- struct list_head sgc_head; +- struct list_head pf_head; ++ LIST_HEAD(sgc_head); ++ LIST_HEAD(pf_head); + const void __user *user_buffer = (const void __user *) + (uintptr_t)tr->data.ptr.buffer; +- INIT_LIST_HEAD(&sgc_head); +- INIT_LIST_HEAD(&pf_head); + + e = binder_transaction_log_add(&binder_transaction_log); + e->debug_id = t_debug_id; diff --git a/queue-6.18/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch b/queue-6.18/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch new file mode 100644 index 0000000000..7af1d520db --- /dev/null +++ b/queue-6.18/bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch @@ -0,0 +1,101 @@ +From stable+bounces-275041-greg=kroah.com@vger.kernel.org Wed Jul 15 23:13:51 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:13:37 -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: <20260715211338.252572-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 +@@ -760,13 +760,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. */ + } +@@ -1249,6 +1269,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); +@@ -1260,7 +1281,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.18/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch b/queue-6.18/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch new file mode 100644 index 0000000000..85b69bab7d --- /dev/null +++ b/queue-6.18/bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch @@ -0,0 +1,542 @@ +From stable+bounces-275042-greg=kroah.com@vger.kernel.org Wed Jul 15 23:13:47 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 17:13:38 -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: <20260715211338.252572-2-sashal@kernel.org> + +From: Siwei Zhang + +[ Upstream commit 6fef032af0092ed5ccb767239a9ac1bc38c08a40 ] + +l2cap_sock_new_connection_cb() returned l2cap_pi(sk)->chan after +release_sock(parent). Once the parent lock is dropped the newly +enqueued child socket sk is reachable via the accept queue, so another +task can accept and free it before the callback dereferences sk, +resulting in a use-after-free. + +Rework the ->new_connection() op so the core, rather than the callback, +owns the child channel's lifetime. The op now receives a pre-allocated +new_chan and returns an errno instead of allocating and returning a +channel. l2cap_new_connection() allocates the child channel and links +it into the conn list via __l2cap_chan_add() before invoking the +callback, so the conn-list reference keeps the channel alive once +release_sock(parent) exposes the socket to other tasks. + +Channel configuration that was duplicated in l2cap_sock_init() and the +various new_connection callbacks is consolidated into +l2cap_chan_set_defaults(), which now inherits from the parent channel +when one is supplied. + +Fixes: 8ffb929098a5 ("Bluetooth: Remove parent socket usage from l2cap_core.c") +Cc: stable@kernel.org +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Siwei Zhang +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + include/net/bluetooth/l2cap.h | 10 ++-- + net/bluetooth/6lowpan.c | 18 ------- + net/bluetooth/l2cap_core.c | 78 ++++++++++++++++++++++++++----- + net/bluetooth/l2cap_sock.c | 103 +++++++++++++++++++----------------------- + net/bluetooth/smp.c | 27 ++--------- + 5 files changed, 127 insertions(+), 109 deletions(-) + +--- a/include/net/bluetooth/l2cap.h ++++ b/include/net/bluetooth/l2cap.h +@@ -620,7 +620,8 @@ struct l2cap_chan { + struct l2cap_ops { + char *name; + +- struct l2cap_chan *(*new_connection) (struct l2cap_chan *chan); ++ int (*new_connection)(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan); + int (*recv) (struct l2cap_chan * chan, + struct sk_buff *skb); + void (*teardown) (struct l2cap_chan *chan, int err); +@@ -885,9 +886,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) +@@ -964,7 +966,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 +@@ -632,7 +632,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; +@@ -745,21 +745,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); +@@ -889,7 +874,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); + +@@ -4027,6 +4055,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) + { +@@ -4073,7 +4133,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; + +@@ -4091,8 +4151,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)); +@@ -4984,7 +5042,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; +@@ -4999,8 +5057,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; +@@ -5208,7 +5264,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; +@@ -5223,8 +5279,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 */ +@@ -7502,14 +7556,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) +@@ -1256,6 +1257,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. + */ +@@ -1266,13 +1284,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); + } +@@ -1515,12 +1529,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); + +@@ -1528,25 +1543,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) +@@ -1844,10 +1862,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); +@@ -1880,30 +1895,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) { +@@ -1929,7 +1926,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 */ +@@ -1948,10 +1945,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) +@@ -1962,16 +1959,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; +@@ -1981,6 +1969,7 @@ static int l2cap_sock_create(struct net + int kern) + { + struct sock *sk; ++ struct l2cap_chan *chan; + + BT_DBG("sock %p", sock); + +@@ -1995,10 +1984,16 @@ static int l2cap_sock_create(struct net + + sock->ops = &l2cap_sock_ops; + +- sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern); +- if (!sk) ++ chan = l2cap_chan_create(); ++ if (!chan) + return -ENOMEM; + ++ sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern, chan); ++ if (!sk) { ++ l2cap_chan_put(chan); ++ return -ENOMEM; ++ } ++ + l2cap_sock_init(sk, NULL); + bt_sock_link(&l2cap_sk_list, sk); + return 0; +--- a/net/bluetooth/smp.c ++++ b/net/bluetooth/smp.c +@@ -3234,34 +3234,19 @@ static const struct l2cap_ops smp_chan_o + .get_sndtimeo = l2cap_chan_no_get_sndtimeo, + }; + +-static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan) ++static inline int smp_new_conn_cb(struct l2cap_chan *chan, ++ struct l2cap_chan *new_chan) + { +- struct l2cap_chan *chan; +- +- BT_DBG("pchan %p", pchan); +- +- chan = l2cap_chan_create(); +- if (!chan) +- return NULL; +- +- chan->chan_type = pchan->chan_type; +- chan->ops = &smp_chan_ops; +- chan->scid = pchan->scid; +- chan->dcid = chan->scid; +- chan->imtu = pchan->imtu; +- chan->omtu = pchan->omtu; +- chan->mode = pchan->mode; ++ new_chan->ops = &smp_chan_ops; + + /* Other L2CAP channels may request SMP routines in order to + * change the security level. This means that the SMP channel + * lock must be considered in its own category to avoid lockdep + * warnings. + */ +- atomic_set(&chan->nesting, L2CAP_NESTING_SMP); +- +- BT_DBG("created chan %p", chan); ++ atomic_set(&new_chan->nesting, L2CAP_NESTING_SMP); + +- return chan; ++ return 0; + } + + static const struct l2cap_ops smp_root_chan_ops = { +@@ -3332,7 +3317,7 @@ create_chan: + + l2cap_add_scid(chan, cid); + +- l2cap_chan_set_defaults(chan); ++ l2cap_chan_set_defaults(chan, NULL); + + if (cid == L2CAP_CID_SMP) { + u8 bdaddr_type; diff --git a/queue-6.18/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch b/queue-6.18/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch new file mode 100644 index 0000000000..9f4bf10617 --- /dev/null +++ b/queue-6.18/bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch @@ -0,0 +1,118 @@ +From stable+bounces-277302-greg=kroah.com@vger.kernel.org Sat Jul 18 13:45:21 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 07:44:41 -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: <20260718114441.3048017-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 | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/kernel/bpf/lpm_trie.c ++++ b/kernel/bpf/lpm_trie.c +@@ -246,7 +246,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; +@@ -280,7 +280,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) +@@ -359,7 +359,7 @@ static long trie_update_elem(struct bpf_ + */ + slot = &trie->root; + +- while ((node = rcu_dereference(*slot))) { ++ while ((node = rcu_dereference_protected(*slot, 1))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || +@@ -482,7 +482,7 @@ static long trie_delete_elem(struct bpf_ + trim = &trie->root; + trim2 = trim; + parent = NULL; +- while ((node = rcu_dereference(*trim))) { ++ while ((node = rcu_dereference_protected(*trim, 1))) { + matchlen = longest_prefix_match(trie, node, key); + + if (node->prefixlen != matchlen || diff --git a/queue-6.18/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch b/queue-6.18/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch new file mode 100644 index 0000000000..7b93711375 --- /dev/null +++ b/queue-6.18/bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch @@ -0,0 +1,168 @@ +From stable+bounces-277301-greg=kroah.com@vger.kernel.org Sat Jul 18 13:45:16 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 07:44:40 -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: <20260718114441.3048017-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 ++++-------- + 4 files changed, 14 insertions(+), 25 deletions(-) + +--- a/include/linux/bpf.h ++++ b/include/linux/bpf.h +@@ -2375,6 +2375,9 @@ bpf_prog_run_array_uprobe(const struct b + bool bpf_jit_bypass_spec_v1(void); + bool bpf_jit_bypass_spec_v4(void); + ++#define bpf_rcu_lock_held() \ ++ (rcu_read_lock_held() || rcu_read_lock_trace_held() || rcu_read_lock_bh_held()) ++ + #ifdef CONFIG_BPF_SYSCALL + DECLARE_PER_CPU(int, bpf_prog_active); + extern struct mutex bpf_stats_enabled_mutex; +--- a/include/linux/bpf_local_storage.h ++++ b/include/linux/bpf_local_storage.h +@@ -18,9 +18,6 @@ + + #define BPF_LOCAL_STORAGE_CACHE_SIZE 16 + +-#define bpf_rcu_lock_held() \ +- (rcu_read_lock_held() || rcu_read_lock_trace_held() || \ +- rcu_read_lock_bh_held()) + struct bpf_local_storage_map_bucket { + struct hlist_head list; + raw_spinlock_t lock; +--- a/kernel/bpf/hashtab.c ++++ b/kernel/bpf/hashtab.c +@@ -669,8 +669,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; + +@@ -1104,8 +1103,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; + +@@ -1212,8 +1210,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; + +@@ -1281,8 +1278,7 @@ static long htab_map_update_elem_in_plac + /* 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; + +@@ -1344,8 +1340,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; + +@@ -1422,8 +1417,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; + +@@ -1458,8 +1452,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 +@@ -42,8 +42,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); + } + +@@ -59,8 +58,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); + } + +@@ -77,8 +75,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); + } + +@@ -134,8 +131,7 @@ const struct bpf_func_proto bpf_map_peek + + BPF_CALL_3(bpf_map_lookup_percpu_elem, struct bpf_map *, map, void *, key, u32, cpu) + { +- WARN_ON_ONCE(!rcu_read_lock_held() && !rcu_read_lock_trace_held() && +- !rcu_read_lock_bh_held()); ++ WARN_ON_ONCE(!bpf_rcu_lock_held()); + return (unsigned long) map->ops->map_lookup_percpu_elem(map, key, cpu); + } + diff --git a/queue-6.18/bpf-consistently-use-reg_state-for-register-access-in-the-verifier.patch b/queue-6.18/bpf-consistently-use-reg_state-for-register-access-in-the-verifier.patch new file mode 100644 index 0000000000..6f4d2d247d --- /dev/null +++ b/queue-6.18/bpf-consistently-use-reg_state-for-register-access-in-the-verifier.patch @@ -0,0 +1,198 @@ +From stable+bounces-277298-greg=kroah.com@vger.kernel.org Sat Jul 18 13:44:45 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 07:44:32 -0400 +Subject: bpf: Consistently use reg_state() for register access in the verifier +To: stable@vger.kernel.org +Cc: Mykyta Yatsenko , Andrii Nakryiko , Alexei Starovoitov , Sasha Levin +Message-ID: <20260718114435.3046796-1-sashal@kernel.org> + +From: Mykyta Yatsenko + +[ Upstream commit 7af3339948601e188d93d7e03326aeb8fcbd6bea ] + +Replace the pattern of declaring a local regs array from cur_regs() +and then indexing into it with the more concise reg_state() helper. +This simplifies the code by eliminating intermediate variables and +makes register access more consistent throughout the verifier. + +Signed-off-by: Mykyta Yatsenko +Acked-by: Andrii Nakryiko +Link: https://lore.kernel.org/r/20260113134826.2214860-1-mykyta.yatsenko5@gmail.com +Signed-off-by: Alexei Starovoitov +Stable-dep-of: 53040a81ae57 ("bpf: Keep dynamic inner array lookups nullable") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/bpf/verifier.c | 42 +++++++++++++++++++----------------------- + 1 file changed, 19 insertions(+), 23 deletions(-) + +--- a/kernel/bpf/verifier.c ++++ b/kernel/bpf/verifier.c +@@ -5690,8 +5690,8 @@ static int check_stack_write(struct bpf_ + static int check_map_access_type(struct bpf_verifier_env *env, u32 regno, + int off, int size, enum bpf_access_type type) + { +- struct bpf_reg_state *regs = cur_regs(env); +- struct bpf_map *map = regs[regno].map_ptr; ++ struct bpf_reg_state *reg = reg_state(env, regno); ++ struct bpf_map *map = reg->map_ptr; + u32 cap = bpf_map_flags_to_cap(map); + + if (type == BPF_WRITE && !(cap & BPF_MAP_CAN_WRITE)) { +@@ -6195,8 +6195,7 @@ static bool may_access_direct_pkt_data(s + static int check_packet_access(struct bpf_verifier_env *env, u32 regno, int off, + int size, bool zero_size_allowed) + { +- struct bpf_reg_state *regs = cur_regs(env); +- struct bpf_reg_state *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + int err; + + /* We may have added a variable offset to the packet pointer; but any +@@ -6283,8 +6282,7 @@ static int check_sock_access(struct bpf_ + u32 regno, int off, int size, + enum bpf_access_type t) + { +- struct bpf_reg_state *regs = cur_regs(env); +- struct bpf_reg_state *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + struct bpf_insn_access_aux info = {}; + bool valid; + +@@ -7469,8 +7467,7 @@ static int check_stack_access_within_bou + int regno, int off, int access_size, + enum bpf_access_type type) + { +- struct bpf_reg_state *regs = cur_regs(env); +- struct bpf_reg_state *reg = regs + regno; ++ struct bpf_reg_state *reg = reg_state(env, regno); + struct bpf_func_state *state = func(env, reg); + s64 min_off, max_off; + int err; +@@ -8414,7 +8411,7 @@ static int process_spin_lock(struct bpf_ + { + bool is_lock = flags & PROCESS_SPIN_LOCK, is_res_lock = flags & PROCESS_RES_LOCK; + const char *lock_str = is_res_lock ? "bpf_res_spin" : "bpf_spin"; +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + struct bpf_verifier_state *cur = env->cur_state; + bool is_const = tnum_is_const(reg->var_off); + bool is_irq = flags & PROCESS_LOCK_IRQ; +@@ -8530,7 +8527,7 @@ static int process_spin_lock(struct bpf_ + static int check_map_field_pointer(struct bpf_verifier_env *env, u32 regno, + enum btf_field_type field_type) + { +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + bool is_const = tnum_is_const(reg->var_off); + struct bpf_map *map = reg->map_ptr; + u64 val = reg->var_off.value; +@@ -8577,7 +8574,7 @@ static int check_map_field_pointer(struc + static int process_timer_func(struct bpf_verifier_env *env, int regno, + struct bpf_call_arg_meta *meta) + { +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + struct bpf_map *map = reg->map_ptr; + int err; + +@@ -8601,7 +8598,7 @@ static int process_timer_func(struct bpf + static int process_wq_func(struct bpf_verifier_env *env, int regno, + struct bpf_kfunc_call_arg_meta *meta) + { +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + struct bpf_map *map = reg->map_ptr; + int err; + +@@ -8622,7 +8619,7 @@ static int process_wq_func(struct bpf_ve + static int process_task_work_func(struct bpf_verifier_env *env, int regno, + struct bpf_kfunc_call_arg_meta *meta) + { +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + struct bpf_map *map = reg->map_ptr; + int err; + +@@ -8642,7 +8639,7 @@ static int process_task_work_func(struct + static int process_kptr_func(struct bpf_verifier_env *env, int regno, + struct bpf_call_arg_meta *meta) + { +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + struct btf_field *kptr_field; + struct bpf_map *map_ptr; + struct btf_record *rec; +@@ -8715,7 +8712,7 @@ static int process_kptr_func(struct bpf_ + static int process_dynptr_func(struct bpf_verifier_env *env, int regno, int insn_idx, + enum bpf_arg_type arg_type, int clone_ref_obj_id) + { +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + int err; + + if (reg->type != PTR_TO_STACK && reg->type != CONST_PTR_TO_DYNPTR) { +@@ -8835,7 +8832,7 @@ static bool is_kfunc_arg_iter(struct bpf + static int process_iter_arg(struct bpf_verifier_env *env, int regno, int insn_idx, + struct bpf_kfunc_call_arg_meta *meta) + { +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + const struct btf_type *t; + int spi, err, i, nr_slots, btf_id; + +@@ -9307,7 +9304,7 @@ static int check_reg_type(struct bpf_ver + const u32 *arg_btf_id, + struct bpf_call_arg_meta *meta) + { +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + enum bpf_reg_type expected, type = reg->type; + const struct bpf_reg_types *compatible; + int i, j; +@@ -9720,7 +9717,7 @@ static int check_func_arg(struct bpf_ver + int insn_idx) + { + u32 regno = BPF_REG_1 + arg; +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + enum bpf_arg_type arg_type = fn->arg_type[arg]; + enum bpf_reg_type type = reg->type; + u32 *arg_btf_id = NULL; +@@ -11250,7 +11247,7 @@ record_func_key(struct bpf_verifier_env + int func_id, int insn_idx) + { + struct bpf_insn_aux_data *aux = &env->insn_aux_data[insn_idx]; +- struct bpf_reg_state *regs = cur_regs(env), *reg; ++ struct bpf_reg_state *reg; + struct bpf_map *map = meta->map_ptr; + u64 val, max; + int err; +@@ -11262,7 +11259,7 @@ record_func_key(struct bpf_verifier_env + return -EINVAL; + } + +- reg = ®s[BPF_REG_3]; ++ reg = reg_state(env, BPF_REG_3); + val = reg->var_off.value; + max = map->max_entries; + +@@ -11408,8 +11405,7 @@ static struct bpf_insn_aux_data *cur_aux + + static bool loop_flag_is_zero(struct bpf_verifier_env *env) + { +- struct bpf_reg_state *regs = cur_regs(env); +- struct bpf_reg_state *reg = ®s[BPF_REG_4]; ++ struct bpf_reg_state *reg = reg_state(env, BPF_REG_4); + bool reg_is_null = register_is_null(reg); + + if (reg_is_null) +@@ -12680,7 +12676,7 @@ static int process_kf_arg_ptr_to_btf_id( + static int process_irq_flag(struct bpf_verifier_env *env, int regno, + struct bpf_kfunc_call_arg_meta *meta) + { +- struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[regno]; ++ struct bpf_reg_state *reg = reg_state(env, regno); + int err, kfunc_class = IRQ_NATIVE_KFUNC; + bool irq_save; + diff --git a/queue-6.18/bpf-introduce-struct-bpf_map_desc-in-verifier.patch b/queue-6.18/bpf-introduce-struct-bpf_map_desc-in-verifier.patch new file mode 100644 index 0000000000..9aabb5e002 --- /dev/null +++ b/queue-6.18/bpf-introduce-struct-bpf_map_desc-in-verifier.patch @@ -0,0 +1,275 @@ +From stable+bounces-277299-greg=kroah.com@vger.kernel.org Sat Jul 18 13:44:45 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 07:44:33 -0400 +Subject: bpf: Introduce struct bpf_map_desc in verifier +To: stable@vger.kernel.org +Cc: Mykyta Yatsenko , Eduard Zingerman , Alexei Starovoitov , Sasha Levin +Message-ID: <20260718114435.3046796-2-sashal@kernel.org> + +From: Mykyta Yatsenko + +[ Upstream commit 98c4fd2963cb2bc5eae22b5365e6bbf3a5cd0f14 ] + +Introduce struct bpf_map_desc to hold bpf_map pointer and map uid. Use +this struct in both bpf_call_arg_meta and bpf_kfunc_call_arg_meta +instead of having different representations: + - bpf_call_arg_meta had separate map_ptr and map_uid fields + - bpf_kfunc_call_arg_meta had an anonymous inline struct + +This unifies the map fields layout across both metadata structures, +making the code more consistent and preparing for further refactoring of +map field pointer validation. + +Acked-by: Eduard Zingerman +Signed-off-by: Mykyta Yatsenko +Link: https://lore.kernel.org/r/20260130-verif_special_fields-v2-1-2c59e637da7d@meta.com +Signed-off-by: Alexei Starovoitov +Stable-dep-of: 53040a81ae57 ("bpf: Keep dynamic inner array lookups nullable") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/bpf/verifier.c | 79 +++++++++++++++++++++++++------------------------- + 1 file changed, 40 insertions(+), 39 deletions(-) + +--- a/kernel/bpf/verifier.c ++++ b/kernel/bpf/verifier.c +@@ -274,8 +274,13 @@ static bool bpf_pseudo_kfunc_call(const + insn->src_reg == BPF_PSEUDO_KFUNC_CALL; + } + ++struct bpf_map_desc { ++ struct bpf_map *ptr; ++ int uid; ++}; ++ + struct bpf_call_arg_meta { +- struct bpf_map *map_ptr; ++ struct bpf_map_desc map; + bool raw_mode; + bool pkt_access; + u8 release_regno; +@@ -285,7 +290,6 @@ struct bpf_call_arg_meta { + u64 msize_max_value; + int ref_obj_id; + int dynptr_id; +- int map_uid; + int func_id; + struct btf *btf; + u32 btf_id; +@@ -345,10 +349,7 @@ struct bpf_kfunc_call_arg_meta { + u8 spi; + u8 frameno; + } iter; +- struct { +- struct bpf_map *ptr; +- int uid; +- } map; ++ struct bpf_map_desc map; + u64 mem_size; + }; + +@@ -8582,7 +8583,7 @@ static int process_timer_func(struct bpf + if (err) + return err; + +- if (meta->map_ptr) { ++ if (meta->map.ptr) { + verifier_bug(env, "Two map pointers in a timer helper"); + return -EFAULT; + } +@@ -8590,8 +8591,8 @@ static int process_timer_func(struct bpf + verbose(env, "bpf_timer cannot be used for PREEMPT_RT.\n"); + return -EOPNOTSUPP; + } +- meta->map_uid = reg->map_uid; +- meta->map_ptr = map; ++ meta->map.uid = reg->map_uid; ++ meta->map.ptr = map; + return 0; + } + +@@ -8655,7 +8656,7 @@ static int process_kptr_func(struct bpf_ + return -EINVAL; + } + rec = map_ptr->record; +- meta->map_ptr = map_ptr; ++ meta->map.ptr = map_ptr; + } + + if (!tnum_is_const(reg->var_off)) { +@@ -9162,13 +9163,13 @@ static int resolve_map_arg_type(struct b + const struct bpf_call_arg_meta *meta, + enum bpf_arg_type *arg_type) + { +- if (!meta->map_ptr) { ++ if (!meta->map.ptr) { + /* kernel subsystem misconfigured verifier */ + verifier_bug(env, "invalid map_ptr to access map->type"); + return -EFAULT; + } + +- switch (meta->map_ptr->map_type) { ++ switch (meta->map.ptr->map_type) { + case BPF_MAP_TYPE_SOCKMAP: + case BPF_MAP_TYPE_SOCKHASH: + if (*arg_type == ARG_PTR_TO_MAP_VALUE) { +@@ -9817,7 +9818,7 @@ skip_type_check: + switch (base_type(arg_type)) { + case ARG_CONST_MAP_PTR: + /* bpf_map_xxx(map_ptr) call: remember that map_ptr */ +- if (meta->map_ptr) { ++ if (meta->map.ptr) { + /* Use map_uid (which is unique id of inner map) to reject: + * inner_map1 = bpf_map_lookup_elem(outer_map, key1) + * inner_map2 = bpf_map_lookup_elem(outer_map, key2) +@@ -9830,23 +9831,23 @@ skip_type_check: + * + * Comparing map_ptr is enough to distinguish normal and outer maps. + */ +- if (meta->map_ptr != reg->map_ptr || +- meta->map_uid != reg->map_uid) { ++ if (meta->map.ptr != reg->map_ptr || ++ meta->map.uid != reg->map_uid) { + verbose(env, + "timer pointer in R1 map_uid=%d doesn't match map pointer in R2 map_uid=%d\n", +- meta->map_uid, reg->map_uid); ++ meta->map.uid, reg->map_uid); + return -EINVAL; + } + } +- meta->map_ptr = reg->map_ptr; +- meta->map_uid = reg->map_uid; ++ meta->map.ptr = reg->map_ptr; ++ meta->map.uid = reg->map_uid; + break; + case ARG_PTR_TO_MAP_KEY: + /* bpf_map_xxx(..., map_ptr, ..., key) call: + * check that [key, key + map->key_size) are within + * stack limits and initialized + */ +- if (!meta->map_ptr) { ++ if (!meta->map.ptr) { + /* in function declaration map_ptr must come before + * map_key, so that it's verified and known before + * we have to check map_key here. Otherwise it means +@@ -9855,11 +9856,11 @@ skip_type_check: + verifier_bug(env, "invalid map_ptr to access map->key"); + return -EFAULT; + } +- key_size = meta->map_ptr->key_size; ++ key_size = meta->map.ptr->key_size; + err = check_helper_mem_access(env, regno, key_size, BPF_READ, false, NULL); + if (err) + return err; +- if (can_elide_value_nullness(meta->map_ptr->map_type)) { ++ if (can_elide_value_nullness(meta->map.ptr->map_type)) { + err = get_constant_map_key(env, reg, key_size, &meta->const_map_key); + if (err < 0) { + meta->const_map_key = -1; +@@ -9877,13 +9878,13 @@ skip_type_check: + /* bpf_map_xxx(..., map_ptr, ..., value) call: + * check [value, value + map->value_size) validity + */ +- if (!meta->map_ptr) { ++ if (!meta->map.ptr) { + /* kernel subsystem misconfigured verifier */ + verifier_bug(env, "invalid map_ptr to access map->value"); + return -EFAULT; + } + meta->raw_mode = arg_type & MEM_UNINIT; +- err = check_helper_mem_access(env, regno, meta->map_ptr->value_size, ++ err = check_helper_mem_access(env, regno, meta->map.ptr->value_size, + arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ, + false, meta); + break; +@@ -11201,7 +11202,7 @@ record_func_map(struct bpf_verifier_env + int func_id, int insn_idx) + { + struct bpf_insn_aux_data *aux = &env->insn_aux_data[insn_idx]; +- struct bpf_map *map = meta->map_ptr; ++ struct bpf_map *map = meta->map.ptr; + + if (func_id != BPF_FUNC_tail_call && + func_id != BPF_FUNC_map_lookup_elem && +@@ -11234,11 +11235,11 @@ record_func_map(struct bpf_verifier_env + } + + if (!aux->map_ptr_state.map_ptr) +- bpf_map_ptr_store(aux, meta->map_ptr, +- !meta->map_ptr->bypass_spec_v1, false); +- else if (aux->map_ptr_state.map_ptr != meta->map_ptr) +- bpf_map_ptr_store(aux, meta->map_ptr, +- !meta->map_ptr->bypass_spec_v1, true); ++ bpf_map_ptr_store(aux, meta->map.ptr, ++ !meta->map.ptr->bypass_spec_v1, false); ++ else if (aux->map_ptr_state.map_ptr != meta->map.ptr) ++ bpf_map_ptr_store(aux, meta->map.ptr, ++ !meta->map.ptr->bypass_spec_v1, true); + return 0; + } + +@@ -11248,7 +11249,7 @@ record_func_key(struct bpf_verifier_env + { + struct bpf_insn_aux_data *aux = &env->insn_aux_data[insn_idx]; + struct bpf_reg_state *reg; +- struct bpf_map *map = meta->map_ptr; ++ struct bpf_map *map = meta->map.ptr; + u64 val, max; + int err; + +@@ -11814,22 +11815,22 @@ static int check_helper_call(struct bpf_ + * can check 'value_size' boundary of memory access + * to map element returned from bpf_map_lookup_elem() + */ +- if (meta.map_ptr == NULL) { ++ if (meta.map.ptr == NULL) { + verifier_bug(env, "unexpected null map_ptr"); + return -EFAULT; + } + + if (func_id == BPF_FUNC_map_lookup_elem && +- can_elide_value_nullness(meta.map_ptr->map_type) && ++ can_elide_value_nullness(meta.map.ptr->map_type) && + meta.const_map_key >= 0 && +- meta.const_map_key < meta.map_ptr->max_entries) ++ meta.const_map_key < meta.map.ptr->max_entries) + ret_flag &= ~PTR_MAYBE_NULL; + +- regs[BPF_REG_0].map_ptr = meta.map_ptr; +- regs[BPF_REG_0].map_uid = meta.map_uid; ++ regs[BPF_REG_0].map_ptr = meta.map.ptr; ++ regs[BPF_REG_0].map_uid = meta.map.uid; + regs[BPF_REG_0].type = PTR_TO_MAP_VALUE | ret_flag; + if (!type_may_be_null(ret_flag) && +- btf_record_has_field(meta.map_ptr->record, BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK)) { ++ btf_record_has_field(meta.map.ptr->record, BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK)) { + regs[BPF_REG_0].id = ++env->id_gen; + } + break; +@@ -11932,7 +11933,7 @@ static int check_helper_call(struct bpf_ + if (type_may_be_null(regs[BPF_REG_0].type)) + regs[BPF_REG_0].id = ++env->id_gen; + +- if (helper_multiple_ref_obj_use(func_id, meta.map_ptr)) { ++ if (helper_multiple_ref_obj_use(func_id, meta.map.ptr)) { + verifier_bug(env, "func %s#%d sets ref_obj_id more than once", + func_id_name(func_id), func_id); + return -EFAULT; +@@ -11944,7 +11945,7 @@ static int check_helper_call(struct bpf_ + if (is_ptr_cast_function(func_id) || is_dynptr_ref_function(func_id)) { + /* For release_reference() */ + regs[BPF_REG_0].ref_obj_id = meta.ref_obj_id; +- } else if (is_acquire_function(func_id, meta.map_ptr)) { ++ } else if (is_acquire_function(func_id, meta.map.ptr)) { + int id = acquire_reference(env, insn_idx); + + if (id < 0) +@@ -11959,7 +11960,7 @@ static int check_helper_call(struct bpf_ + if (err) + return err; + +- err = check_map_func_compatibility(env, meta.map_ptr, func_id); ++ err = check_map_func_compatibility(env, meta.map.ptr, func_id); + if (err) + return err; + diff --git a/queue-6.18/bpf-keep-dynamic-inner-array-lookups-nullable.patch b/queue-6.18/bpf-keep-dynamic-inner-array-lookups-nullable.patch new file mode 100644 index 0000000000..ee7a18fc44 --- /dev/null +++ b/queue-6.18/bpf-keep-dynamic-inner-array-lookups-nullable.patch @@ -0,0 +1,87 @@ +From stable+bounces-277300-greg=kroah.com@vger.kernel.org Sat Jul 18 13:45:10 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 07:44:34 -0400 +Subject: bpf: Keep dynamic inner array lookups nullable +To: stable@vger.kernel.org +Cc: Nuoqi Gui , Eduard Zingerman , Jiri Olsa , Kumar Kartikeya Dwivedi , Sasha Levin +Message-ID: <20260718114435.3046796-3-sashal@kernel.org> + +From: Nuoqi Gui + +[ Upstream commit 53040a81ae57cdca8af8ac36fe4e661730cf7c6b ] + +An ARRAY_OF_MAPS can use an array created with BPF_F_INNER_MAP as its +inner map template. A concrete inner array with a different max_entries +value can then replace the template. + +After a successful outer map lookup, the verifier represents the +resulting map pointer using the inner map template. Const-key lookup +nullness elision consequently uses the template max_entries even though +the runtime helper uses the concrete inner map max_entries. + +Do not elide lookup result nullness for maps marked with BPF_F_INNER_MAP, +because the template max_entries does not prove that the key is in bounds +for the concrete runtime map. + +Fixes: d2102f2f5d75 ("bpf: verifier: Support eliding map lookup nullness") +Signed-off-by: Nuoqi Gui +Acked-by: Eduard Zingerman +Acked-by: Jiri Olsa +Cc: stable@vger.kernel.org +Link: https://lore.kernel.org/bpf/20260607-f01-v2-v2-1-da48453146e8@mails.tsinghua.edu.cn +Signed-off-by: Kumar Kartikeya Dwivedi +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + kernel/bpf/verifier.c | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +--- a/kernel/bpf/verifier.c ++++ b/kernel/bpf/verifier.c +@@ -9710,7 +9710,7 @@ static int get_constant_map_key(struct b + return 0; + } + +-static bool can_elide_value_nullness(enum bpf_map_type type); ++static bool can_elide_value_nullness(const struct bpf_map *map); + + static int check_func_arg(struct bpf_verifier_env *env, u32 arg, + struct bpf_call_arg_meta *meta, +@@ -9860,7 +9860,7 @@ skip_type_check: + err = check_helper_mem_access(env, regno, key_size, BPF_READ, false, NULL); + if (err) + return err; +- if (can_elide_value_nullness(meta->map.ptr->map_type)) { ++ if (can_elide_value_nullness(meta->map.ptr)) { + err = get_constant_map_key(env, reg, key_size, &meta->const_map_key); + if (err < 0) { + meta->const_map_key = -1; +@@ -11433,13 +11433,16 @@ static void update_loop_inline_state(str + state->callback_subprogno == subprogno); + } + +-/* Returns whether or not the given map type can potentially elide ++/* Returns whether or not the given map can potentially elide + * lookup return value nullness check. This is possible if the key + * is statically known. + */ +-static bool can_elide_value_nullness(enum bpf_map_type type) ++static bool can_elide_value_nullness(const struct bpf_map *map) + { +- switch (type) { ++ if (map->map_flags & BPF_F_INNER_MAP) ++ return false; ++ ++ switch (map->map_type) { + case BPF_MAP_TYPE_ARRAY: + case BPF_MAP_TYPE_PERCPU_ARRAY: + return true; +@@ -11821,7 +11824,7 @@ static int check_helper_call(struct bpf_ + } + + if (func_id == BPF_FUNC_map_lookup_elem && +- can_elide_value_nullness(meta.map.ptr->map_type) && ++ can_elide_value_nullness(meta.map.ptr) && + meta.const_map_key >= 0 && + meta.const_map_key < meta.map.ptr->max_entries) + ret_flag &= ~PTR_MAYBE_NULL; diff --git a/queue-6.18/btrfs-concentrate-the-error-handling-of-submit_one_sector.patch b/queue-6.18/btrfs-concentrate-the-error-handling-of-submit_one_sector.patch new file mode 100644 index 0000000000..fc329de8cb --- /dev/null +++ b/queue-6.18/btrfs-concentrate-the-error-handling-of-submit_one_sector.patch @@ -0,0 +1,95 @@ +From stable+bounces-277912-greg=kroah.com@vger.kernel.org Mon Jul 20 17:25:39 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 11:01:06 -0400 +Subject: btrfs: concentrate the error handling of submit_one_sector() +To: stable@vger.kernel.org +Cc: Qu Wenruo , Boris Burkov , David Sterba , Sasha Levin +Message-ID: <20260720150109.1921272-1-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit 44820d80026e0b509007d41c83d42f1213ee8589 ] + +Currently submit_one_sector() has only one failure path from +btrfs_get_extent(). + +However the error handling is split into two parts, one inside +submit_one_sector(), which clears the dirty flag and finishes the +writeback for the fs block. + +The other part is to submit any remaining bio inside bio_ctrl and mark +the ordered extent finished for the fs block. + +There is no special reason that we must split the error handling, let's +just concentrate all the error handling into submit_one_sector(). + +Reviewed-by: Boris Burkov +Signed-off-by: Qu Wenruo +Reviewed-by: David Sterba +Signed-off-by: David Sterba +Stable-dep-of: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/extent_io.c | 29 +++++++++++++++-------------- + 1 file changed, 15 insertions(+), 14 deletions(-) + +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -1599,7 +1599,7 @@ out: + + /* + * Return 0 if we have submitted or queued the sector for submission. +- * Return <0 for critical errors, and the sector will have its dirty flag cleared. ++ * Return <0 for critical errors, and the involved sector will be cleaned up. + * + * Caller should make sure filepos < i_size and handle filepos >= i_size case. + */ +@@ -1624,6 +1624,13 @@ static int submit_one_sector(struct btrf + em = btrfs_get_extent(inode, NULL, filepos, sectorsize); + if (IS_ERR(em)) { + /* ++ * bio_ctrl may contain a bio crossing several folios. ++ * Submit it immediately so that the bio has a chance ++ * to finish normally, other than marked as error. ++ */ ++ submit_one_bio(bio_ctrl); ++ ++ /* + * When submission failed, we should still clear the folio dirty. + * Or the folio will be written back again but without any + * ordered extent. +@@ -1631,6 +1638,13 @@ static int submit_one_sector(struct btrf + btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize); + btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize); + btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize); ++ ++ /* ++ * Since there is no bio submitted to finish the ordered ++ * extent, we have to manually finish this sector. ++ */ ++ btrfs_mark_ordered_io_finished(inode, folio, filepos, ++ fs_info->sectorsize, false); + return PTR_ERR(em); + } + +@@ -1756,19 +1770,6 @@ static noinline_for_stack int extent_wri + } + ret = submit_one_sector(inode, folio, cur, bio_ctrl, i_size); + if (unlikely(ret < 0)) { +- /* +- * bio_ctrl may contain a bio crossing several folios. +- * Submit it immediately so that the bio has a chance +- * to finish normally, other than marked as error. +- */ +- submit_one_bio(bio_ctrl); +- /* +- * Failed to grab the extent map which should be very rare. +- * Since there is no bio submitted to finish the ordered +- * extent, we have to manually finish this sector. +- */ +- btrfs_mark_ordered_io_finished(inode, folio, cur, +- fs_info->sectorsize, false); + if (!found_error) + found_error = ret; + continue; diff --git a/queue-6.18/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch b/queue-6.18/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch new file mode 100644 index 0000000000..f11419137a --- /dev/null +++ b/queue-6.18/btrfs-fix-false-io-failure-after-falling-back-to-buffered-write.patch @@ -0,0 +1,189 @@ +From stable+bounces-277915-greg=kroah.com@vger.kernel.org Mon Jul 20 17:49:25 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 11:01:09 -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: <20260720150109.1921272-4-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit 66ff4d366e7eb4d31813d2acabf3af512ce03aa5 ] + +[BUG] +The test case generic/362 will fail with "nodatasum" mount option (*): + + MOUNT_OPTIONS -- -o nodatasum /dev/mapper/test-scratch1 /mnt/scratch + +# generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) +# --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930 +# +++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:21:17.574771567 +0930 +# @@ -1,2 +1,3 @@ +# QA output created by 362 +# +First write failed: Input/output error +# Silence is golden +# ... + +*: If the test case has been executed before with default data checksum, +the failure will not reproduce. Need the following fix to make it +reliably reproducible: +https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/ + +[CAUSE] +Inside __iomap_dio_rw(), the -EFAULT/-ENOTBLK error is not directly returned. +Thus we never got an error pointer from __iomap_dio_rw(). + +The call chain looks like this: + + btrfs_direct_write() + |- btrfs_dio_write() + |- __iomap_dio_rw() + | |- iomap_iter() + | | |- btrfs_dio_iomap_begin() + | | Now an ordered extent is allocated for the 4K write. + | | + | |- iomi.status = iomap_dio_iter() + | | Where iomap_dio_iter() returned -EFAULT. + | | + | |- ret = iomap_iter() + | | |- btrfs_dio_iomap_end() + | | | |- btrfs_finish_ordered_extent(uptodate = false) + | | | | |- can_finish_ordered_extent() + | | | | |- btrfs_mark_ordered_extent_error() + | | | | |- mapping_set_error() + | | | | Now the address space is marked error. + | | | | return -ENOTBLK + | | |- return -ENOTBLK + | |- if (ret == -ENOTBLK) { ret = 0; } + | Now the return value is reset to 0. + | Thus no error pointer will be returned. + | + |- ret = iomap_dio_complete() + | Since no byte is submitted, @ret is 0. + | + |- Fallback to buffered IO + | And the buffered write finished without error + | + |- filemap_fdatawait_range() + |- filemap_check_errors() + The previous error is recorded, thus an error is returned + +However the buffered write is properly submitted and finished, the error +is from the btrfs_finish_ordered_extent() call with @uptodate = false. + +[FIX] +When a short dio write happened, any range that is submitted will have +btrfs_extract_ordered_extent() to be called, thus the submitted range +will always have an OE just covering the submitted range. + +The remaining OE range is never submitted, thus they should be treated +as truncated, not an error. So that we can properly reclaim and not +insert an unnecessary file extent item, without marking the mapping as +error. + +Extract a helper, btrfs_mark_ordered_extent_truncated(), and utilize +that helper to mark the direct IO ordered extent as truncated, so it +won't cause failure for the later buffered fallback. + +[REASON FOR NO FIXES TAG] +The bug itself is pretty old, at commit f85781fb505e ("btrfs: switch to +iomap for direct IO") we're already passing @uptodate=false finishing +the OE. +But at that time OE with IOERR won't call mapping_set_error(), so it's +not exposed. +Later commit d61bec08b904 ("btrfs: mark ordered extent and inode with +error if we fail to finish") finally exposed the bug, but that commit +is doing a correct job, not the root cause. + +Anyway the bug is very old, dating back to 5.1x days, thus only CC to +stable. + +CC: stable@vger.kernel.org # 5.15+ +Reviewed-by: Boris Burkov +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/direct-io.c | 17 ++++++++++++++--- + fs/btrfs/inode.c | 6 +----- + fs/btrfs/ordered-data.c | 12 ++++++++++++ + fs/btrfs/ordered-data.h | 2 ++ + 4 files changed, 29 insertions(+), 8 deletions(-) + +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -624,12 +624,23 @@ static int btrfs_dio_iomap_end(struct in + if (submitted < length) { + pos += submitted; + length -= submitted; +- if (write) ++ if (write) { ++ /* ++ * We have a short write, if there is any range ++ * that is submitted properly, that part will have ++ * its own OE split from the original one. ++ * ++ * So for the OE at dio_data->ordered, it's the part ++ * that is not submitted, and should be marked ++ * as fully truncated. ++ */ ++ btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); + btrfs_finish_ordered_extent(dio_data->ordered, +- pos, length, false); +- else ++ pos, length, true); ++ } else { + btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, + pos + length - 1, NULL); ++ } + ret = -ENOTBLK; + } + if (write) { +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -7681,11 +7681,7 @@ static void btrfs_invalidate_folio(struc + EXTENT_LOCKED | EXTENT_DO_ACCOUNTING | + EXTENT_DEFRAG, &cached_state); + +- spin_lock(&inode->ordered_tree_lock); +- set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags); +- ordered->truncated_len = min(ordered->truncated_len, +- cur - ordered->file_offset); +- spin_unlock(&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 +@@ -339,6 +339,18 @@ void btrfs_mark_ordered_extent_error(str + mapping_set_error(ordered->inode->vfs_inode.i_mapping, -EIO); + } + ++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered, ++ u64 truncate_len) ++{ ++ struct btrfs_inode *inode = ordered->inode; ++ ++ ASSERT(truncate_len <= ordered->num_bytes); ++ spin_lock(&inode->ordered_tree_lock); ++ set_bit(BTRFS_ORDERED_TRUNCATED, &ordered->flags); ++ ordered->truncated_len = min(ordered->truncated_len, truncate_len); ++ spin_unlock(&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 +@@ -220,6 +220,8 @@ bool btrfs_try_lock_ordered_range(struct + struct btrfs_ordered_extent *btrfs_split_ordered_extent( + struct btrfs_ordered_extent *ordered, u64 len); + void btrfs_mark_ordered_extent_error(struct btrfs_ordered_extent *ordered); ++void btrfs_mark_ordered_extent_truncated(struct btrfs_ordered_extent *ordered, ++ u64 truncate_len); + int __init ordered_data_init(void); + void __cold ordered_data_exit(void); + diff --git a/queue-6.18/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch b/queue-6.18/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch new file mode 100644 index 0000000000..8ccfb3d27b --- /dev/null +++ b/queue-6.18/btrfs-fix-incorrect-buffered-io-fallback-for-append-direct-writes.patch @@ -0,0 +1,200 @@ +From stable+bounces-278149-greg=kroah.com@vger.kernel.org Mon Jul 20 19:39:15 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 12:51:15 -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: <20260720165115.2527045-5-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit ff66fe6662330226b3f486014c375538d91c44aa ] + +[BUG] +With the previous bug of short direct writes fixed, test case +generic/362 (*) still fails with the following error with nodatasum +mount option: + +# generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) +# - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) +# --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930 +# +++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:13:09.072485767 +0930 +# @@ -1,2 +1,3 @@ +# QA output created by 362 +# +Wrong file size after first write, got 8192 expected 4096 +# Silence is golden +# ... + +*: If the test case has been executed before with default data checksum, +the failure will not reproduce. Need the following fix to make it +reliably reproducible: +https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/ + +[CAUSE] +Inside btrfs_dio_iomap_begin() for a direct write, we increase the isize +if it's beyond the current isize. + +But if the direct io finished short, we do not revert the isize to the +previous value nor to the short write end. + +Then if we need to fall back to buffered writes, and the write has +IOCB_APPEND flag, then the buffered write will be positioned at the +incorrect isize. + +The call chain looks like this: + + btrfs_direct_write(pos=0, length=4K) + |- __iomap_dio_rw() + | |- iomap_iter() + | | |- btrfs_dio_iomap_begin() + | | |- btrfs_get_blocks_direct_write() + | | |- i_size_write() + | | Which updates the isize to the write end (4K). + | | + | |- iomap_dio_iter() + | | Failed with -EFAULT on the first page. + | | + | |- iomap_iter() + | | |- btrfs_dio_iomap_end() + | | Detects a short write, return -ENOTBLK + | |- if (ret == -ENOTBLK) { ret = 0;} + | Which resets the return value. + | + |- ret = iomap_dio_complet() + | Which returns 0. + | + |- btrfs_buffered_write(iocb, from); + |- generic_write_checks() + |- iocb->ki_pos = i_size_read() + Which is still the new size (4K), other than the original + isize 0. + +[FIX] +Introduce the following btrfs_dio_data members: + +- old_isize + +- updated_isize + If the direct write has enlarged the isize. + +Then if we got a short write, and btrfs_dio_data::updated_isize is set, +revert to the correct isize based on old_isize and current file +position. + +And here we call i_size_write() without holding an extent lock, which is +a very special case that we're safe to do: + + - Only a single writer can be enlarging isize + Enlarging isize will take the exclusive inode lock. + + - Buffered readers need to wait for the OE we're holding + Buffered readers will lock extent and wait for OE of the folio range. + Sometimes we can skip the OE wait, but since all page cache is + invalidated, the OE wait can not be skipped. + +But I do not think this is the most elegant solution, nor covers all +cases. E.g. if the bio is submitted but IO failed, we are unable to do +the revert. + +I believe the more elegant one would be extend the EXTENT_DIO_LOCKED +lifespan for direct writes, so that we can update the isize when a +write beyond EOF finished successfully. + +However that change is too huge for a small bug fix. +So only implement the minimal partial fix for now. + +[REASON FOR NO FIXES TAG] +The bug is again very old, before commit f85781fb505e ("btrfs: switch to +iomap for direct IO") we are already increasing isize without a +proper rollback for short writes. + +Thus only a CC to stable. + +CC: stable@vger.kernel.org # 5.15+ +Reviewed-by: Boris Burkov +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/direct-io.c | 43 ++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 42 insertions(+), 1 deletion(-) + +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -15,10 +15,12 @@ + + struct btrfs_dio_data { + ssize_t submitted; ++ loff_t old_isize; + struct extent_changeset *data_reserved; + struct btrfs_ordered_extent *ordered; + bool data_space_reserved; + bool nocow_done; ++ bool updated_isize; + }; + + struct btrfs_dio_private { +@@ -228,6 +230,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; + + /* +@@ -341,8 +344,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); +@@ -626,6 +635,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.18/btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch b/queue-6.18/btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch new file mode 100644 index 0000000000..3d18bea288 --- /dev/null +++ b/queue-6.18/btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch @@ -0,0 +1,354 @@ +From stable+bounces-277914-greg=kroah.com@vger.kernel.org Mon Jul 20 17:25:41 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 11:01:08 -0400 +Subject: btrfs: remove folio parameter from ordered io related functions +To: stable@vger.kernel.org +Cc: Qu Wenruo , Filipe Manana , David Sterba , Sasha Levin +Message-ID: <20260720150109.1921272-3-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit a11d6912fdd9e57aff889ec97256b1d6b4e5bf06 ] + +Both functions btrfs_finish_ordered_extent() and +btrfs_mark_ordered_io_finished() are accepting an optional folio +parameter. + +That @folio is passed into can_finish_ordered_extent(), which later will +test and clear the ordered flag for the involved range. + +However I do not think there is any other call site that can clear +ordered flags of an page cache folio and can affect +can_finish_ordered_extent(). + +There are limited *_clear_ordered() callers out of +can_finish_ordered_extent() function: + +- btrfs_migrate_folio() + This is completely unrelated, it's just migrating the ordered flag to + the new folio. + +- btrfs_cleanup_ordered_extents() + We manually clean the ordered flags of all involved folios, then call + btrfs_mark_ordered_io_finished() without a @folio parameter. + So it doesn't need and didn't pass a @folio parameter in the first + place. + +- btrfs_writepage_fixup_worker() + This function is going to be removed soon, and we should not hit that + function anymore. + +- btrfs_invalidate_folio() + This is the real call site we need to bother with. + + If we already have a bio running, btrfs_finish_ordered_extent() in + end_bbio_data_write() will be executed first, as + btrfs_invalidate_folio() will wait for the writeback to finish. + + Thus if there is a running bio, it will not see the range has + ordered flags, and just skip to the next range. + + If there is no bio running, meaning the ordered extent is created but + the folio is not yet submitted. + + In that case btrfs_invalidate_folio() will manually clear the folio + ordered range, but then manually finish the ordered extent with + btrfs_dec_test_ordered_pending() without bothering the folio ordered + flags. + + Meaning if the OE range with folio ordered flags will be finished + manually without the need to call can_finish_ordered_extent(). + +This means all can_finish_ordered_extent() call sites should get a range +that has folio ordered flag set, thus the old "return false" branch +should never be triggered. + +Now we can: + +- Remove the @folio parameter from involved functions + * btrfs_mark_ordered_io_finished() + * btrfs_finish_ordered_extent() + + For call sites passing a @folio into those functions, let them + manually clear the ordered flag of involved folios. + +- Move btrfs_finish_ordered_extent() out of the loop in + end_bbio_data_write() + + We only need to call btrfs_finish_ordered_extent() once per bbio, + not per folio. + +- Add an ASSERT() to make sure all folio ranges have ordered flags + It's only for end_bbio_data_write(). + + And we already have enough safe nets to catch over-accounting of ordered + extents. + +Reviewed-by: Filipe Manana +Signed-off-by: Qu Wenruo +Signed-off-by: David Sterba +Stable-dep-of: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/compression.c | 2 +- + fs/btrfs/direct-io.c | 9 ++++----- + fs/btrfs/extent_io.c | 23 ++++++++++++++--------- + fs/btrfs/inode.c | 10 ++++++---- + fs/btrfs/ordered-data.c | 29 +++++------------------------ + fs/btrfs/ordered-data.h | 6 ++---- + 6 files changed, 32 insertions(+), 47 deletions(-) + +--- a/fs/btrfs/compression.c ++++ b/fs/btrfs/compression.c +@@ -324,7 +324,7 @@ static void btrfs_finish_compressed_writ + struct compressed_bio *cb = + container_of(work, struct compressed_bio, write_end_work); + +- btrfs_finish_ordered_extent(cb->bbio.ordered, NULL, cb->start, cb->len, ++ btrfs_finish_ordered_extent(cb->bbio.ordered, cb->start, cb->len, + cb->bbio.bio.bi_status == BLK_STS_OK); + + if (cb->writeback) +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -625,7 +625,7 @@ static int btrfs_dio_iomap_end(struct in + pos += submitted; + length -= submitted; + if (write) +- btrfs_finish_ordered_extent(dio_data->ordered, NULL, ++ btrfs_finish_ordered_extent(dio_data->ordered, + pos, length, false); + else + btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +@@ -657,9 +657,8 @@ static void btrfs_dio_end_io(struct btrf + } + + if (btrfs_op(bio) == BTRFS_MAP_WRITE) { +- btrfs_finish_ordered_extent(bbio->ordered, NULL, +- dip->file_offset, dip->bytes, +- !bio->bi_status); ++ btrfs_finish_ordered_extent(bbio->ordered, dip->file_offset, ++ dip->bytes, !bio->bi_status); + } else { + btrfs_unlock_dio_extent(&inode->io_tree, dip->file_offset, + dip->file_offset + dip->bytes - 1, NULL); +@@ -735,7 +734,7 @@ static void btrfs_dio_submit_io(const st + + ret = btrfs_extract_ordered_extent(bbio, dio_data->ordered); + if (ret) { +- btrfs_finish_ordered_extent(dio_data->ordered, NULL, ++ btrfs_finish_ordered_extent(dio_data->ordered, + file_offset, dip->bytes, + !ret); + bio->bi_status = errno_to_blk_status(ret); +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -523,6 +523,7 @@ static void end_bbio_data_write(struct b + int error = blk_status_to_errno(bio->bi_status); + struct folio_iter fi; + const u32 sectorsize = fs_info->sectorsize; ++ u32 bio_size = 0; + + ASSERT(!bio_flagged(bio, BIO_CLONED)); + bio_for_each_folio_all(fi, bio) { +@@ -530,6 +531,7 @@ static void end_bbio_data_write(struct b + u64 start = folio_pos(folio) + fi.offset; + u32 len = fi.length; + ++ bio_size += len; + /* Our read/write should always be sector aligned. */ + if (!IS_ALIGNED(fi.offset, sectorsize)) + btrfs_err(fs_info, +@@ -540,13 +542,15 @@ static void end_bbio_data_write(struct b + "incomplete page write with offset %zu and length %zu", + fi.offset, fi.length); + +- btrfs_finish_ordered_extent(bbio->ordered, folio, start, len, +- !error); + if (error) + mapping_set_error(folio->mapping, error); ++ ++ ASSERT(btrfs_folio_test_ordered(fs_info, folio, start, len)); ++ btrfs_folio_clear_ordered(fs_info, folio, start, len); + btrfs_folio_clear_writeback(fs_info, folio, start, len); + } + ++ btrfs_finish_ordered_extent(bbio->ordered, bbio->file_offset, bio_size, !error); + bio_put(bio); + } + +@@ -1566,7 +1570,8 @@ static noinline_for_stack int writepage_ + u64 start = page_start + (start_bit << fs_info->sectorsize_bits); + u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits; + +- btrfs_mark_ordered_io_finished(inode, folio, start, len, false); ++ btrfs_folio_clear_ordered(fs_info, folio, start, len); ++ btrfs_mark_ordered_io_finished(inode, start, len, false); + } + return ret; + } +@@ -1642,6 +1647,7 @@ static int submit_one_sector(struct btrf + * ordered extent. + */ + btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize); ++ btrfs_folio_clear_ordered(fs_info, folio, filepos, sectorsize); + btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize); + btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize); + +@@ -1649,8 +1655,8 @@ static int submit_one_sector(struct btrf + * Since there is no bio submitted to finish the ordered + * extent, we have to manually finish this sector. + */ +- btrfs_mark_ordered_io_finished(inode, folio, filepos, +- fs_info->sectorsize, false); ++ btrfs_mark_ordered_io_finished(inode, filepos, fs_info->sectorsize, ++ false); + return PTR_ERR(em); + } + +@@ -1761,8 +1767,8 @@ static noinline_for_stack int extent_wri + spin_unlock_irqrestore(&inode->ordered_tree_lock, flags); + btrfs_put_ordered_extent(ordered); + +- btrfs_mark_ordered_io_finished(inode, folio, cur, +- fs_info->sectorsize, true); ++ btrfs_folio_clear_ordered(fs_info, folio, cur, fs_info->sectorsize); ++ btrfs_mark_ordered_io_finished(inode, cur, fs_info->sectorsize, true); + /* + * This range is beyond i_size, thus we don't need to + * bother writing back. +@@ -2640,8 +2646,7 @@ void extent_write_locked_range(struct in + if (IS_ERR(folio)) { + cur_end = min(round_down(cur, PAGE_SIZE) + PAGE_SIZE - 1, end); + cur_len = cur_end + 1 - cur; +- btrfs_mark_ordered_io_finished(BTRFS_I(inode), NULL, +- cur, cur_len, false); ++ btrfs_mark_ordered_io_finished(BTRFS_I(inode), cur, cur_len, false); + mapping_set_error(mapping, PTR_ERR(folio)); + cur = cur_end; + continue; +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -424,7 +424,7 @@ static inline void btrfs_cleanup_ordered + folio_put(folio); + } + +- return btrfs_mark_ordered_io_finished(inode, NULL, offset, bytes, false); ++ return btrfs_mark_ordered_io_finished(inode, offset, bytes, false); + } + + static int btrfs_dirty_inode(struct btrfs_inode *inode); +@@ -2864,7 +2864,9 @@ out_page: + * to reflect the errors and clean the page. + */ + mapping_set_error(folio->mapping, ret); +- btrfs_mark_ordered_io_finished(inode, folio, page_start, ++ btrfs_folio_clear_ordered(fs_info, folio, page_start, ++ folio_size(folio)); ++ btrfs_mark_ordered_io_finished(inode, page_start, + folio_size(folio), !ret); + folio_clear_dirty_for_io(folio); + } +@@ -7679,11 +7681,11 @@ static void btrfs_invalidate_folio(struc + EXTENT_LOCKED | EXTENT_DO_ACCOUNTING | + EXTENT_DEFRAG, &cached_state); + +- spin_lock_irq(&inode->ordered_tree_lock); ++ spin_lock(&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); ++ spin_unlock(&inode->ordered_tree_lock); + + /* + * If the ordered extent has finished, we're safe to delete all +--- a/fs/btrfs/ordered-data.c ++++ b/fs/btrfs/ordered-data.c +@@ -348,30 +348,13 @@ static void finish_ordered_fn(struct btr + } + + static bool can_finish_ordered_extent(struct btrfs_ordered_extent *ordered, +- struct folio *folio, u64 file_offset, +- u64 len, bool uptodate) ++ u64 file_offset, u64 len, bool uptodate) + { + struct btrfs_inode *inode = ordered->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + + lockdep_assert_held(&inode->ordered_tree_lock); + +- if (folio) { +- ASSERT(folio->mapping); +- ASSERT(folio_pos(folio) <= file_offset); +- ASSERT(file_offset + len <= folio_end(folio)); +- +- /* +- * Ordered flag indicates whether we still have +- * pending io unfinished for the ordered extent. +- * +- * If it's not set, we need to skip to next range. +- */ +- if (!btrfs_folio_test_ordered(fs_info, folio, file_offset, len)) +- return false; +- btrfs_folio_clear_ordered(fs_info, folio, file_offset, len); +- } +- + /* Now we're fine to update the accounting. */ + if (WARN_ON_ONCE(len > ordered->bytes_left)) { + btrfs_crit(fs_info, +@@ -413,8 +396,7 @@ static void btrfs_queue_ordered_fn(struc + } + + void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered, +- struct folio *folio, u64 file_offset, u64 len, +- bool uptodate) ++ u64 file_offset, u64 len, bool uptodate) + { + struct btrfs_inode *inode = ordered->inode; + unsigned long flags; +@@ -423,7 +405,7 @@ void btrfs_finish_ordered_extent(struct + trace_btrfs_finish_ordered_extent(inode, file_offset, len, uptodate); + + spin_lock_irqsave(&inode->ordered_tree_lock, flags); +- ret = can_finish_ordered_extent(ordered, folio, file_offset, len, ++ ret = can_finish_ordered_extent(ordered, file_offset, len, + uptodate); + spin_unlock_irqrestore(&inode->ordered_tree_lock, flags); + +@@ -476,8 +458,7 @@ void btrfs_finish_ordered_extent(struct + * extent(s) covering it. + */ + void btrfs_mark_ordered_io_finished(struct btrfs_inode *inode, +- struct folio *folio, u64 file_offset, +- u64 num_bytes, bool uptodate) ++ u64 file_offset, u64 num_bytes, bool uptodate) + { + struct rb_node *node; + struct btrfs_ordered_extent *entry = NULL; +@@ -540,7 +521,7 @@ void btrfs_mark_ordered_io_finished(stru + ASSERT(end + 1 - cur < U32_MAX); + len = end + 1 - cur; + +- if (can_finish_ordered_extent(entry, folio, cur, len, uptodate)) { ++ if (can_finish_ordered_extent(entry, cur, len, uptodate)) { + spin_unlock_irqrestore(&inode->ordered_tree_lock, flags); + btrfs_queue_ordered_fn(entry); + spin_lock_irqsave(&inode->ordered_tree_lock, flags); +--- a/fs/btrfs/ordered-data.h ++++ b/fs/btrfs/ordered-data.h +@@ -164,11 +164,9 @@ void btrfs_put_ordered_extent(struct btr + void btrfs_remove_ordered_extent(struct btrfs_inode *btrfs_inode, + struct btrfs_ordered_extent *entry); + void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered, +- struct folio *folio, u64 file_offset, u64 len, +- bool uptodate); ++ u64 file_offset, u64 len, bool uptodate); + void btrfs_mark_ordered_io_finished(struct btrfs_inode *inode, +- struct folio *folio, u64 file_offset, +- u64 num_bytes, bool uptodate); ++ u64 file_offset, u64 num_bytes, bool uptodate); + bool btrfs_dec_test_ordered_pending(struct btrfs_inode *inode, + struct btrfs_ordered_extent **cached, + u64 file_offset, u64 io_size); diff --git a/queue-6.18/btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch b/queue-6.18/btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch new file mode 100644 index 0000000000..e1b159636a --- /dev/null +++ b/queue-6.18/btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch @@ -0,0 +1,102 @@ +From stable+bounces-277913-greg=kroah.com@vger.kernel.org Mon Jul 20 17:25:40 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 11:01:07 -0400 +Subject: btrfs: replace for_each_set_bit() with for_each_set_bitmap() +To: stable@vger.kernel.org +Cc: Qu Wenruo , Boris Burkov , David Sterba , Sasha Levin +Message-ID: <20260720150109.1921272-2-sashal@kernel.org> + +From: Qu Wenruo + +[ Upstream commit e6698b34fab33867ef3faeeea6feb165f31aae24 ] + +Inside extent_io.c, there are several simple call sites doing things +like: + + for_each_set_bit(bit, bitmap, bitmap_size) { + /* handle one fs block */ + } + +The workload includes: + +- set_bit() + Inside extent_writepage_io(). + + This can be replaced with a bitmap_set(). + +- btrfs_folio_set_lock() +- btrfs_mark_ordered_io_finished() + Inside writepage_delalloc(). + + Instead of calling it multiple times, we can pass a range into the + function with one call. + +Reviewed-by: Boris Burkov +Signed-off-by: Qu Wenruo +Reviewed-by: David Sterba +Signed-off-by: David Sterba +Stable-dep-of: 66ff4d366e7e ("btrfs: fix false IO failure after falling back to buffered write") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/btrfs/extent_io.c | 26 ++++++++++++++++---------- + 1 file changed, 16 insertions(+), 10 deletions(-) + +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -1427,8 +1427,9 @@ static noinline_for_stack int writepage_ + u64 delalloc_start = page_start; + u64 delalloc_end = page_end; + u64 delalloc_to_write = 0; ++ unsigned int start_bit; ++ unsigned int end_bit; + int ret = 0; +- int bit; + + /* Save the dirty bitmap as our submission bitmap will be a subset of it. */ + if (btrfs_is_subpage(fs_info, folio)) { +@@ -1438,10 +1439,12 @@ static noinline_for_stack int writepage_ + bio_ctrl->submit_bitmap = 1; + } + +- for_each_set_bit(bit, &bio_ctrl->submit_bitmap, blocks_per_folio) { +- u64 start = page_start + (bit << fs_info->sectorsize_bits); ++ for_each_set_bitrange(start_bit, end_bit, &bio_ctrl->submit_bitmap, ++ blocks_per_folio) { ++ u64 start = page_start + (start_bit << fs_info->sectorsize_bits); ++ u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits; + +- btrfs_folio_set_lock(fs_info, folio, start, fs_info->sectorsize); ++ btrfs_folio_set_lock(fs_info, folio, start, len); + } + + /* Lock all (subpage) delalloc ranges inside the folio first. */ +@@ -1558,10 +1561,13 @@ static noinline_for_stack int writepage_ + fs_info->sectorsize_bits, + blocks_per_folio); + +- for_each_set_bit(bit, &bio_ctrl->submit_bitmap, bitmap_size) +- btrfs_mark_ordered_io_finished(inode, folio, +- page_start + (bit << fs_info->sectorsize_bits), +- fs_info->sectorsize, false); ++ for_each_set_bitrange(start_bit, end_bit, &bio_ctrl->submit_bitmap, ++ bitmap_size) { ++ u64 start = page_start + (start_bit << fs_info->sectorsize_bits); ++ u32 len = (end_bit - start_bit) << fs_info->sectorsize_bits; ++ ++ btrfs_mark_ordered_io_finished(inode, folio, start, len, false); ++ } + return ret; + } + out: +@@ -1727,8 +1733,8 @@ static noinline_for_stack int extent_wri + return ret; + } + +- for (cur = start; cur < end; cur += fs_info->sectorsize) +- set_bit((cur - folio_start) >> fs_info->sectorsize_bits, &range_bitmap); ++ bitmap_set(&range_bitmap, (start - folio_pos(folio)) >> fs_info->sectorsize_bits, ++ len >> fs_info->sectorsize_bits); + bitmap_and(&bio_ctrl->submit_bitmap, &bio_ctrl->submit_bitmap, &range_bitmap, + blocks_per_folio); + diff --git a/queue-6.18/cifs-smb1-split-add-some-includes.patch b/queue-6.18/cifs-smb1-split-add-some-includes.patch new file mode 100644 index 0000000000..6065d38833 --- /dev/null +++ b/queue-6.18/cifs-smb1-split-add-some-includes.patch @@ -0,0 +1,67 @@ +From stable+bounces-275227-greg=kroah.com@vger.kernel.org Thu Jul 16 12:23:46 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 06:18:18 -0400 +Subject: cifs: SMB1 split: Add some #includes +To: stable@vger.kernel.org +Cc: David Howells , Steve French , Paulo Alcantara , Enzo Matsumiya , linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Steve French , Sasha Levin +Message-ID: <20260716101819.1461638-2-sashal@kernel.org> + +From: David Howells + +[ Upstream commit a7c7f35bcf0943ba85ab14bafbfbcbc3a30ad402 ] + +Add some #includes to make sure things continue to compile as splitting +occurs. + +Signed-off-by: David Howells +cc: Steve French +cc: Paulo Alcantara +cc: Enzo Matsumiya +cc: linux-cifs@vger.kernel.org +cc: linux-fsdevel@vger.kernel.org +cc: linux-kernel@vger.kernel.org +Acked-by: Enzo Matsumiya +Signed-off-by: Steve French +Stable-dep-of: c6394bcaf254 ("ksmbd: use opener credentials for FSCTL mutations") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/cifssmb.c | 5 +++-- + fs/smb/client/smb1transport.c | 4 ++-- + 2 files changed, 5 insertions(+), 4 deletions(-) + +--- a/fs/smb/client/cifssmb.c ++++ b/fs/smb/client/cifssmb.c +@@ -26,11 +26,12 @@ + #include + #include + #include ++#include "cifsglob.h" ++#include "cifsproto.h" ++#include "../common/smbfsctl.h" + #include "cifspdu.h" + #include "cifsfs.h" +-#include "cifsglob.h" + #include "cifsacl.h" +-#include "cifsproto.h" + #include "cifs_unicode.h" + #include "cifs_debug.h" + #include "fscache.h" +--- a/fs/smb/client/smb1transport.c ++++ b/fs/smb/client/smb1transport.c +@@ -22,13 +22,13 @@ + #include + #include + #include +-#include "cifspdu.h" + #include "cifsglob.h" + #include "cifsproto.h" +-#include "cifs_debug.h" ++#include "cifspdu.h" + #include "smb2proto.h" + #include "smbdirect.h" + #include "compress.h" ++#include "cifs_debug.h" + + /* Max number of iovectors we can use off the stack when sending requests. */ + #define CIFS_MAX_IOV_SIZE 8 diff --git a/queue-6.18/cifs-smb1-split-rename-cifstransport.c.patch b/queue-6.18/cifs-smb1-split-rename-cifstransport.c.patch new file mode 100644 index 0000000000..e102d81293 --- /dev/null +++ b/queue-6.18/cifs-smb1-split-rename-cifstransport.c.patch @@ -0,0 +1,1185 @@ +From stable+bounces-275226-greg=kroah.com@vger.kernel.org Thu Jul 16 12:23:22 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 06:18:17 -0400 +Subject: cifs: SMB1 split: Rename cifstransport.c +To: stable@vger.kernel.org +Cc: David Howells , Steve French , Paulo Alcantara , Enzo Matsumiya , linux-cifs@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Steve French , Sasha Levin +Message-ID: <20260716101819.1461638-1-sashal@kernel.org> + +From: David Howells + +[ Upstream commit b09eab52b307df58a36f34d047378053a2e13e13 ] + +Rename cifstransport.c to smb1transport.c in order to give consistent names +SMB1-specific files. + +Signed-off-by: David Howells +cc: Steve French +cc: Paulo Alcantara +cc: Enzo Matsumiya +cc: linux-cifs@vger.kernel.org +cc: linux-fsdevel@vger.kernel.org +cc: linux-kernel@vger.kernel.org +Acked-by: Enzo Matsumiya +Signed-off-by: Steve French +Stable-dep-of: c6394bcaf254 ("ksmbd: use opener credentials for FSCTL mutations") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/client/Makefile | 5 ++++- + fs/smb/client/{cifstransport.c => smb1transport.c} | 0 + fs/smb/client/Makefile | 5 + fs/smb/client/cifstransport.c | 565 ------------------------------------------ + fs/smb/client/smb1transport.c | 565 ++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 569 insertions(+), 566 deletions(-) + rename fs/smb/client/{cifstransport.c => smb1transport.c} (100%) + +--- a/fs/smb/client/Makefile ++++ b/fs/smb/client/Makefile +@@ -32,6 +32,9 @@ cifs-$(CONFIG_CIFS_SMB_DIRECT) += smbdir + + cifs-$(CONFIG_CIFS_ROOT) += cifsroot.o + +-cifs-$(CONFIG_CIFS_ALLOW_INSECURE_LEGACY) += smb1ops.o cifssmb.o cifstransport.o ++cifs-$(CONFIG_CIFS_ALLOW_INSECURE_LEGACY) += \ ++ cifssmb.o \ ++ smb1ops.o \ ++ smb1transport.o + + cifs-$(CONFIG_CIFS_COMPRESSION) += compress.o compress/lz77.o +--- a/fs/smb/client/cifstransport.c ++++ /dev/null +@@ -1,565 +0,0 @@ +-// SPDX-License-Identifier: LGPL-2.1 +-/* +- * +- * Copyright (C) International Business Machines Corp., 2002,2008 +- * Author(s): Steve French (sfrench@us.ibm.com) +- * Jeremy Allison (jra@samba.org) 2006. +- * +- */ +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include "cifspdu.h" +-#include "cifsglob.h" +-#include "cifsproto.h" +-#include "cifs_debug.h" +-#include "smb2proto.h" +-#include "smbdirect.h" +-#include "compress.h" +- +-/* Max number of iovectors we can use off the stack when sending requests. */ +-#define CIFS_MAX_IOV_SIZE 8 +- +-static struct mid_q_entry * +-alloc_mid(const struct smb_hdr *smb_buffer, struct TCP_Server_Info *server) +-{ +- struct mid_q_entry *temp; +- +- if (server == NULL) { +- cifs_dbg(VFS, "%s: null TCP session\n", __func__); +- return NULL; +- } +- +- temp = mempool_alloc(cifs_mid_poolp, GFP_NOFS); +- memset(temp, 0, sizeof(struct mid_q_entry)); +- kref_init(&temp->refcount); +- spin_lock_init(&temp->mid_lock); +- temp->mid = get_mid(smb_buffer); +- temp->pid = current->pid; +- temp->command = cpu_to_le16(smb_buffer->Command); +- cifs_dbg(FYI, "For smb_command %d\n", smb_buffer->Command); +- /* easier to use jiffies */ +- /* when mid allocated can be before when sent */ +- temp->when_alloc = jiffies; +- temp->server = server; +- +- /* +- * The default is for the mid to be synchronous, so the +- * default callback just wakes up the current task. +- */ +- get_task_struct(current); +- temp->creator = current; +- temp->callback = cifs_wake_up_task; +- temp->callback_data = current; +- +- atomic_inc(&mid_count); +- temp->mid_state = MID_REQUEST_ALLOCATED; +- return temp; +-} +- +-int +-smb_send(struct TCP_Server_Info *server, struct smb_hdr *smb_buffer, +- unsigned int smb_buf_length) +-{ +- struct kvec iov[2]; +- struct smb_rqst rqst = { .rq_iov = iov, +- .rq_nvec = 2 }; +- +- iov[0].iov_base = smb_buffer; +- iov[0].iov_len = 4; +- iov[1].iov_base = (char *)smb_buffer + 4; +- iov[1].iov_len = smb_buf_length; +- +- return __smb_send_rqst(server, 1, &rqst); +-} +- +-static int allocate_mid(struct cifs_ses *ses, struct smb_hdr *in_buf, +- struct mid_q_entry **ppmidQ) +-{ +- spin_lock(&ses->ses_lock); +- if (ses->ses_status == SES_NEW) { +- if ((in_buf->Command != SMB_COM_SESSION_SETUP_ANDX) && +- (in_buf->Command != SMB_COM_NEGOTIATE)) { +- spin_unlock(&ses->ses_lock); +- return -EAGAIN; +- } +- /* else ok - we are setting up session */ +- } +- +- if (ses->ses_status == SES_EXITING) { +- /* check if SMB session is bad because we are setting it up */ +- if (in_buf->Command != SMB_COM_LOGOFF_ANDX) { +- spin_unlock(&ses->ses_lock); +- return -EAGAIN; +- } +- /* else ok - we are shutting down session */ +- } +- spin_unlock(&ses->ses_lock); +- +- *ppmidQ = alloc_mid(in_buf, ses->server); +- if (*ppmidQ == NULL) +- return -ENOMEM; +- spin_lock(&ses->server->mid_queue_lock); +- list_add_tail(&(*ppmidQ)->qhead, &ses->server->pending_mid_q); +- spin_unlock(&ses->server->mid_queue_lock); +- return 0; +-} +- +-struct mid_q_entry * +-cifs_setup_async_request(struct TCP_Server_Info *server, struct smb_rqst *rqst) +-{ +- int rc; +- struct smb_hdr *hdr = (struct smb_hdr *)rqst->rq_iov[0].iov_base; +- struct mid_q_entry *mid; +- +- if (rqst->rq_iov[0].iov_len != 4 || +- rqst->rq_iov[0].iov_base + 4 != rqst->rq_iov[1].iov_base) +- return ERR_PTR(-EIO); +- +- /* enable signing if server requires it */ +- if (server->sign) +- hdr->Flags2 |= SMBFLG2_SECURITY_SIGNATURE; +- +- mid = alloc_mid(hdr, server); +- if (mid == NULL) +- return ERR_PTR(-ENOMEM); +- +- rc = cifs_sign_rqst(rqst, server, &mid->sequence_number); +- if (rc) { +- release_mid(mid); +- return ERR_PTR(rc); +- } +- +- return mid; +-} +- +-/* +- * +- * Send an SMB Request. No response info (other than return code) +- * needs to be parsed. +- * +- * flags indicate the type of request buffer and how long to wait +- * and whether to log NT STATUS code (error) before mapping it to POSIX error +- * +- */ +-int +-SendReceiveNoRsp(const unsigned int xid, struct cifs_ses *ses, +- char *in_buf, int flags) +-{ +- int rc; +- struct kvec iov[1]; +- struct kvec rsp_iov; +- int resp_buf_type; +- +- iov[0].iov_base = in_buf; +- iov[0].iov_len = get_rfc1002_length(in_buf) + 4; +- flags |= CIFS_NO_RSP_BUF; +- rc = SendReceive2(xid, ses, iov, 1, &resp_buf_type, flags, &rsp_iov); +- cifs_dbg(NOISY, "SendRcvNoRsp flags %d rc %d\n", flags, rc); +- +- return rc; +-} +- +-int +-cifs_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server, +- bool log_error) +-{ +- unsigned int len = get_rfc1002_length(mid->resp_buf) + 4; +- +- dump_smb(mid->resp_buf, min_t(u32, 92, len)); +- +- /* convert the length into a more usable form */ +- if (server->sign) { +- struct kvec iov[2]; +- int rc = 0; +- struct smb_rqst rqst = { .rq_iov = iov, +- .rq_nvec = 2 }; +- +- iov[0].iov_base = mid->resp_buf; +- iov[0].iov_len = 4; +- iov[1].iov_base = (char *)mid->resp_buf + 4; +- iov[1].iov_len = len - 4; +- /* FIXME: add code to kill session */ +- rc = cifs_verify_signature(&rqst, server, +- mid->sequence_number); +- if (rc) +- cifs_server_dbg(VFS, "SMB signature verification returned error = %d\n", +- rc); +- } +- +- /* BB special case reconnect tid and uid here? */ +- return map_and_check_smb_error(mid, log_error); +-} +- +-struct mid_q_entry * +-cifs_setup_request(struct cifs_ses *ses, struct TCP_Server_Info *ignored, +- struct smb_rqst *rqst) +-{ +- int rc; +- struct smb_hdr *hdr = (struct smb_hdr *)rqst->rq_iov[0].iov_base; +- struct mid_q_entry *mid; +- +- if (rqst->rq_iov[0].iov_len != 4 || +- rqst->rq_iov[0].iov_base + 4 != rqst->rq_iov[1].iov_base) +- return ERR_PTR(-EIO); +- +- rc = allocate_mid(ses, hdr, &mid); +- if (rc) +- return ERR_PTR(rc); +- rc = cifs_sign_rqst(rqst, ses->server, &mid->sequence_number); +- if (rc) { +- delete_mid(mid); +- return ERR_PTR(rc); +- } +- return mid; +-} +- +-int +-SendReceive2(const unsigned int xid, struct cifs_ses *ses, +- struct kvec *iov, int n_vec, int *resp_buf_type /* ret */, +- const int flags, struct kvec *resp_iov) +-{ +- struct smb_rqst rqst; +- struct kvec s_iov[CIFS_MAX_IOV_SIZE], *new_iov; +- int rc; +- +- if (n_vec + 1 > CIFS_MAX_IOV_SIZE) { +- new_iov = kmalloc_array(n_vec + 1, sizeof(struct kvec), +- GFP_KERNEL); +- if (!new_iov) { +- /* otherwise cifs_send_recv below sets resp_buf_type */ +- *resp_buf_type = CIFS_NO_BUFFER; +- return -ENOMEM; +- } +- } else +- new_iov = s_iov; +- +- /* 1st iov is a RFC1001 length followed by the rest of the packet */ +- memcpy(new_iov + 1, iov, (sizeof(struct kvec) * n_vec)); +- +- new_iov[0].iov_base = new_iov[1].iov_base; +- new_iov[0].iov_len = 4; +- new_iov[1].iov_base += 4; +- new_iov[1].iov_len -= 4; +- +- memset(&rqst, 0, sizeof(struct smb_rqst)); +- rqst.rq_iov = new_iov; +- rqst.rq_nvec = n_vec + 1; +- +- rc = cifs_send_recv(xid, ses, ses->server, +- &rqst, resp_buf_type, flags, resp_iov); +- if (n_vec + 1 > CIFS_MAX_IOV_SIZE) +- kfree(new_iov); +- return rc; +-} +- +-int +-SendReceive(const unsigned int xid, struct cifs_ses *ses, +- struct smb_hdr *in_buf, struct smb_hdr *out_buf, +- int *pbytes_returned, const int flags) +-{ +- int rc = 0; +- struct mid_q_entry *midQ; +- unsigned int len = be32_to_cpu(in_buf->smb_buf_length); +- struct kvec iov = { .iov_base = in_buf, .iov_len = len }; +- struct smb_rqst rqst = { .rq_iov = &iov, .rq_nvec = 1 }; +- struct cifs_credits credits = { .value = 1, .instance = 0 }; +- struct TCP_Server_Info *server; +- +- if (ses == NULL) { +- cifs_dbg(VFS, "Null smb session\n"); +- return -EIO; +- } +- server = ses->server; +- if (server == NULL) { +- cifs_dbg(VFS, "Null tcp session\n"); +- return -EIO; +- } +- +- spin_lock(&server->srv_lock); +- if (server->tcpStatus == CifsExiting) { +- spin_unlock(&server->srv_lock); +- return -ENOENT; +- } +- spin_unlock(&server->srv_lock); +- +- /* Ensure that we do not send more than 50 overlapping requests +- to the same server. We may make this configurable later or +- use ses->maxReq */ +- +- if (len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) { +- cifs_server_dbg(VFS, "Invalid length, greater than maximum frame, %d\n", +- len); +- return -EIO; +- } +- +- rc = wait_for_free_request(server, flags, &credits.instance); +- if (rc) +- return rc; +- +- /* make sure that we sign in the same order that we send on this socket +- and avoid races inside tcp sendmsg code that could cause corruption +- of smb data */ +- +- cifs_server_lock(server); +- +- rc = allocate_mid(ses, in_buf, &midQ); +- if (rc) { +- cifs_server_unlock(server); +- /* Update # of requests on wire to server */ +- add_credits(server, &credits, 0); +- return rc; +- } +- +- rc = cifs_sign_smb(in_buf, server, &midQ->sequence_number); +- if (rc) { +- cifs_server_unlock(server); +- goto out; +- } +- +- midQ->mid_state = MID_REQUEST_SUBMITTED; +- +- rc = smb_send(server, in_buf, len); +- cifs_save_when_sent(midQ); +- +- if (rc < 0) +- server->sequence_number -= 2; +- +- cifs_server_unlock(server); +- +- if (rc < 0) +- goto out; +- +- rc = wait_for_response(server, midQ); +- if (rc != 0) { +- send_cancel(server, &rqst, midQ); +- spin_lock(&midQ->mid_lock); +- if (midQ->callback) { +- /* no longer considered to be "in-flight" */ +- midQ->callback = release_mid; +- spin_unlock(&midQ->mid_lock); +- add_credits(server, &credits, 0); +- return rc; +- } +- spin_unlock(&midQ->mid_lock); +- } +- +- rc = cifs_sync_mid_result(midQ, server); +- if (rc != 0) { +- add_credits(server, &credits, 0); +- return rc; +- } +- +- if (!midQ->resp_buf || !out_buf || +- midQ->mid_state != MID_RESPONSE_READY) { +- rc = -EIO; +- cifs_server_dbg(VFS, "Bad MID state?\n"); +- goto out; +- } +- +- *pbytes_returned = get_rfc1002_length(midQ->resp_buf); +- memcpy(out_buf, midQ->resp_buf, *pbytes_returned + 4); +- rc = cifs_check_receive(midQ, server, 0); +-out: +- delete_mid(midQ); +- add_credits(server, &credits, 0); +- +- return rc; +-} +- +-/* We send a LOCKINGX_CANCEL_LOCK to cause the Windows +- blocking lock to return. */ +- +-static int +-send_lock_cancel(const unsigned int xid, struct cifs_tcon *tcon, +- struct smb_hdr *in_buf, +- struct smb_hdr *out_buf) +-{ +- int bytes_returned; +- struct cifs_ses *ses = tcon->ses; +- LOCK_REQ *pSMB = (LOCK_REQ *)in_buf; +- +- /* We just modify the current in_buf to change +- the type of lock from LOCKING_ANDX_SHARED_LOCK +- or LOCKING_ANDX_EXCLUSIVE_LOCK to +- LOCKING_ANDX_CANCEL_LOCK. */ +- +- pSMB->LockType = LOCKING_ANDX_CANCEL_LOCK|LOCKING_ANDX_LARGE_FILES; +- pSMB->Timeout = 0; +- pSMB->hdr.Mid = get_next_mid(ses->server); +- +- return SendReceive(xid, ses, in_buf, out_buf, +- &bytes_returned, 0); +-} +- +-int +-SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon, +- struct smb_hdr *in_buf, struct smb_hdr *out_buf, +- int *pbytes_returned) +-{ +- int rc = 0; +- int rstart = 0; +- struct mid_q_entry *midQ; +- struct cifs_ses *ses; +- unsigned int len = be32_to_cpu(in_buf->smb_buf_length); +- struct kvec iov = { .iov_base = in_buf, .iov_len = len }; +- struct smb_rqst rqst = { .rq_iov = &iov, .rq_nvec = 1 }; +- unsigned int instance; +- struct TCP_Server_Info *server; +- +- if (tcon == NULL || tcon->ses == NULL) { +- cifs_dbg(VFS, "Null smb session\n"); +- return -EIO; +- } +- ses = tcon->ses; +- server = ses->server; +- +- if (server == NULL) { +- cifs_dbg(VFS, "Null tcp session\n"); +- return -EIO; +- } +- +- spin_lock(&server->srv_lock); +- if (server->tcpStatus == CifsExiting) { +- spin_unlock(&server->srv_lock); +- return -ENOENT; +- } +- spin_unlock(&server->srv_lock); +- +- /* Ensure that we do not send more than 50 overlapping requests +- to the same server. We may make this configurable later or +- use ses->maxReq */ +- +- if (len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) { +- cifs_tcon_dbg(VFS, "Invalid length, greater than maximum frame, %d\n", +- len); +- return -EIO; +- } +- +- rc = wait_for_free_request(server, CIFS_BLOCKING_OP, &instance); +- if (rc) +- return rc; +- +- /* make sure that we sign in the same order that we send on this socket +- and avoid races inside tcp sendmsg code that could cause corruption +- of smb data */ +- +- cifs_server_lock(server); +- +- rc = allocate_mid(ses, in_buf, &midQ); +- if (rc) { +- cifs_server_unlock(server); +- return rc; +- } +- +- rc = cifs_sign_smb(in_buf, server, &midQ->sequence_number); +- if (rc) { +- delete_mid(midQ); +- cifs_server_unlock(server); +- return rc; +- } +- +- midQ->mid_state = MID_REQUEST_SUBMITTED; +- rc = smb_send(server, in_buf, len); +- cifs_save_when_sent(midQ); +- +- if (rc < 0) +- server->sequence_number -= 2; +- +- cifs_server_unlock(server); +- +- if (rc < 0) { +- delete_mid(midQ); +- return rc; +- } +- +- /* Wait for a reply - allow signals to interrupt. */ +- rc = wait_event_interruptible(server->response_q, +- (!(midQ->mid_state == MID_REQUEST_SUBMITTED || +- midQ->mid_state == MID_RESPONSE_RECEIVED)) || +- ((server->tcpStatus != CifsGood) && +- (server->tcpStatus != CifsNew))); +- +- /* Were we interrupted by a signal ? */ +- spin_lock(&server->srv_lock); +- if ((rc == -ERESTARTSYS) && +- (midQ->mid_state == MID_REQUEST_SUBMITTED || +- midQ->mid_state == MID_RESPONSE_RECEIVED) && +- ((server->tcpStatus == CifsGood) || +- (server->tcpStatus == CifsNew))) { +- spin_unlock(&server->srv_lock); +- +- if (in_buf->Command == SMB_COM_TRANSACTION2) { +- /* POSIX lock. We send a NT_CANCEL SMB to cause the +- blocking lock to return. */ +- rc = send_cancel(server, &rqst, midQ); +- if (rc) { +- delete_mid(midQ); +- return rc; +- } +- } else { +- /* Windows lock. We send a LOCKINGX_CANCEL_LOCK +- to cause the blocking lock to return. */ +- +- rc = send_lock_cancel(xid, tcon, in_buf, out_buf); +- +- /* If we get -ENOLCK back the lock may have +- already been removed. Don't exit in this case. */ +- if (rc && rc != -ENOLCK) { +- delete_mid(midQ); +- return rc; +- } +- } +- +- rc = wait_for_response(server, midQ); +- if (rc) { +- send_cancel(server, &rqst, midQ); +- spin_lock(&midQ->mid_lock); +- if (midQ->callback) { +- /* no longer considered to be "in-flight" */ +- midQ->callback = release_mid; +- spin_unlock(&midQ->mid_lock); +- return rc; +- } +- spin_unlock(&midQ->mid_lock); +- } +- +- /* We got the response - restart system call. */ +- rstart = 1; +- spin_lock(&server->srv_lock); +- } +- spin_unlock(&server->srv_lock); +- +- rc = cifs_sync_mid_result(midQ, server); +- if (rc != 0) +- return rc; +- +- /* rcvd frame is ok */ +- if (out_buf == NULL || midQ->mid_state != MID_RESPONSE_READY) { +- rc = -EIO; +- cifs_tcon_dbg(VFS, "Bad MID state?\n"); +- goto out; +- } +- +- *pbytes_returned = get_rfc1002_length(midQ->resp_buf); +- memcpy(out_buf, midQ->resp_buf, *pbytes_returned + 4); +- rc = cifs_check_receive(midQ, server, 0); +-out: +- delete_mid(midQ); +- if (rstart && rc == -EACCES) +- return -ERESTARTSYS; +- return rc; +-} +--- /dev/null ++++ b/fs/smb/client/smb1transport.c +@@ -0,0 +1,565 @@ ++// SPDX-License-Identifier: LGPL-2.1 ++/* ++ * ++ * Copyright (C) International Business Machines Corp., 2002,2008 ++ * Author(s): Steve French (sfrench@us.ibm.com) ++ * Jeremy Allison (jra@samba.org) 2006. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "cifspdu.h" ++#include "cifsglob.h" ++#include "cifsproto.h" ++#include "cifs_debug.h" ++#include "smb2proto.h" ++#include "smbdirect.h" ++#include "compress.h" ++ ++/* Max number of iovectors we can use off the stack when sending requests. */ ++#define CIFS_MAX_IOV_SIZE 8 ++ ++static struct mid_q_entry * ++alloc_mid(const struct smb_hdr *smb_buffer, struct TCP_Server_Info *server) ++{ ++ struct mid_q_entry *temp; ++ ++ if (server == NULL) { ++ cifs_dbg(VFS, "%s: null TCP session\n", __func__); ++ return NULL; ++ } ++ ++ temp = mempool_alloc(cifs_mid_poolp, GFP_NOFS); ++ memset(temp, 0, sizeof(struct mid_q_entry)); ++ kref_init(&temp->refcount); ++ spin_lock_init(&temp->mid_lock); ++ temp->mid = get_mid(smb_buffer); ++ temp->pid = current->pid; ++ temp->command = cpu_to_le16(smb_buffer->Command); ++ cifs_dbg(FYI, "For smb_command %d\n", smb_buffer->Command); ++ /* easier to use jiffies */ ++ /* when mid allocated can be before when sent */ ++ temp->when_alloc = jiffies; ++ temp->server = server; ++ ++ /* ++ * The default is for the mid to be synchronous, so the ++ * default callback just wakes up the current task. ++ */ ++ get_task_struct(current); ++ temp->creator = current; ++ temp->callback = cifs_wake_up_task; ++ temp->callback_data = current; ++ ++ atomic_inc(&mid_count); ++ temp->mid_state = MID_REQUEST_ALLOCATED; ++ return temp; ++} ++ ++int ++smb_send(struct TCP_Server_Info *server, struct smb_hdr *smb_buffer, ++ unsigned int smb_buf_length) ++{ ++ struct kvec iov[2]; ++ struct smb_rqst rqst = { .rq_iov = iov, ++ .rq_nvec = 2 }; ++ ++ iov[0].iov_base = smb_buffer; ++ iov[0].iov_len = 4; ++ iov[1].iov_base = (char *)smb_buffer + 4; ++ iov[1].iov_len = smb_buf_length; ++ ++ return __smb_send_rqst(server, 1, &rqst); ++} ++ ++static int allocate_mid(struct cifs_ses *ses, struct smb_hdr *in_buf, ++ struct mid_q_entry **ppmidQ) ++{ ++ spin_lock(&ses->ses_lock); ++ if (ses->ses_status == SES_NEW) { ++ if ((in_buf->Command != SMB_COM_SESSION_SETUP_ANDX) && ++ (in_buf->Command != SMB_COM_NEGOTIATE)) { ++ spin_unlock(&ses->ses_lock); ++ return -EAGAIN; ++ } ++ /* else ok - we are setting up session */ ++ } ++ ++ if (ses->ses_status == SES_EXITING) { ++ /* check if SMB session is bad because we are setting it up */ ++ if (in_buf->Command != SMB_COM_LOGOFF_ANDX) { ++ spin_unlock(&ses->ses_lock); ++ return -EAGAIN; ++ } ++ /* else ok - we are shutting down session */ ++ } ++ spin_unlock(&ses->ses_lock); ++ ++ *ppmidQ = alloc_mid(in_buf, ses->server); ++ if (*ppmidQ == NULL) ++ return -ENOMEM; ++ spin_lock(&ses->server->mid_queue_lock); ++ list_add_tail(&(*ppmidQ)->qhead, &ses->server->pending_mid_q); ++ spin_unlock(&ses->server->mid_queue_lock); ++ return 0; ++} ++ ++struct mid_q_entry * ++cifs_setup_async_request(struct TCP_Server_Info *server, struct smb_rqst *rqst) ++{ ++ int rc; ++ struct smb_hdr *hdr = (struct smb_hdr *)rqst->rq_iov[0].iov_base; ++ struct mid_q_entry *mid; ++ ++ if (rqst->rq_iov[0].iov_len != 4 || ++ rqst->rq_iov[0].iov_base + 4 != rqst->rq_iov[1].iov_base) ++ return ERR_PTR(-EIO); ++ ++ /* enable signing if server requires it */ ++ if (server->sign) ++ hdr->Flags2 |= SMBFLG2_SECURITY_SIGNATURE; ++ ++ mid = alloc_mid(hdr, server); ++ if (mid == NULL) ++ return ERR_PTR(-ENOMEM); ++ ++ rc = cifs_sign_rqst(rqst, server, &mid->sequence_number); ++ if (rc) { ++ release_mid(mid); ++ return ERR_PTR(rc); ++ } ++ ++ return mid; ++} ++ ++/* ++ * ++ * Send an SMB Request. No response info (other than return code) ++ * needs to be parsed. ++ * ++ * flags indicate the type of request buffer and how long to wait ++ * and whether to log NT STATUS code (error) before mapping it to POSIX error ++ * ++ */ ++int ++SendReceiveNoRsp(const unsigned int xid, struct cifs_ses *ses, ++ char *in_buf, int flags) ++{ ++ int rc; ++ struct kvec iov[1]; ++ struct kvec rsp_iov; ++ int resp_buf_type; ++ ++ iov[0].iov_base = in_buf; ++ iov[0].iov_len = get_rfc1002_length(in_buf) + 4; ++ flags |= CIFS_NO_RSP_BUF; ++ rc = SendReceive2(xid, ses, iov, 1, &resp_buf_type, flags, &rsp_iov); ++ cifs_dbg(NOISY, "SendRcvNoRsp flags %d rc %d\n", flags, rc); ++ ++ return rc; ++} ++ ++int ++cifs_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server, ++ bool log_error) ++{ ++ unsigned int len = get_rfc1002_length(mid->resp_buf) + 4; ++ ++ dump_smb(mid->resp_buf, min_t(u32, 92, len)); ++ ++ /* convert the length into a more usable form */ ++ if (server->sign) { ++ struct kvec iov[2]; ++ int rc = 0; ++ struct smb_rqst rqst = { .rq_iov = iov, ++ .rq_nvec = 2 }; ++ ++ iov[0].iov_base = mid->resp_buf; ++ iov[0].iov_len = 4; ++ iov[1].iov_base = (char *)mid->resp_buf + 4; ++ iov[1].iov_len = len - 4; ++ /* FIXME: add code to kill session */ ++ rc = cifs_verify_signature(&rqst, server, ++ mid->sequence_number); ++ if (rc) ++ cifs_server_dbg(VFS, "SMB signature verification returned error = %d\n", ++ rc); ++ } ++ ++ /* BB special case reconnect tid and uid here? */ ++ return map_and_check_smb_error(mid, log_error); ++} ++ ++struct mid_q_entry * ++cifs_setup_request(struct cifs_ses *ses, struct TCP_Server_Info *ignored, ++ struct smb_rqst *rqst) ++{ ++ int rc; ++ struct smb_hdr *hdr = (struct smb_hdr *)rqst->rq_iov[0].iov_base; ++ struct mid_q_entry *mid; ++ ++ if (rqst->rq_iov[0].iov_len != 4 || ++ rqst->rq_iov[0].iov_base + 4 != rqst->rq_iov[1].iov_base) ++ return ERR_PTR(-EIO); ++ ++ rc = allocate_mid(ses, hdr, &mid); ++ if (rc) ++ return ERR_PTR(rc); ++ rc = cifs_sign_rqst(rqst, ses->server, &mid->sequence_number); ++ if (rc) { ++ delete_mid(mid); ++ return ERR_PTR(rc); ++ } ++ return mid; ++} ++ ++int ++SendReceive2(const unsigned int xid, struct cifs_ses *ses, ++ struct kvec *iov, int n_vec, int *resp_buf_type /* ret */, ++ const int flags, struct kvec *resp_iov) ++{ ++ struct smb_rqst rqst; ++ struct kvec s_iov[CIFS_MAX_IOV_SIZE], *new_iov; ++ int rc; ++ ++ if (n_vec + 1 > CIFS_MAX_IOV_SIZE) { ++ new_iov = kmalloc_array(n_vec + 1, sizeof(struct kvec), ++ GFP_KERNEL); ++ if (!new_iov) { ++ /* otherwise cifs_send_recv below sets resp_buf_type */ ++ *resp_buf_type = CIFS_NO_BUFFER; ++ return -ENOMEM; ++ } ++ } else ++ new_iov = s_iov; ++ ++ /* 1st iov is a RFC1001 length followed by the rest of the packet */ ++ memcpy(new_iov + 1, iov, (sizeof(struct kvec) * n_vec)); ++ ++ new_iov[0].iov_base = new_iov[1].iov_base; ++ new_iov[0].iov_len = 4; ++ new_iov[1].iov_base += 4; ++ new_iov[1].iov_len -= 4; ++ ++ memset(&rqst, 0, sizeof(struct smb_rqst)); ++ rqst.rq_iov = new_iov; ++ rqst.rq_nvec = n_vec + 1; ++ ++ rc = cifs_send_recv(xid, ses, ses->server, ++ &rqst, resp_buf_type, flags, resp_iov); ++ if (n_vec + 1 > CIFS_MAX_IOV_SIZE) ++ kfree(new_iov); ++ return rc; ++} ++ ++int ++SendReceive(const unsigned int xid, struct cifs_ses *ses, ++ struct smb_hdr *in_buf, struct smb_hdr *out_buf, ++ int *pbytes_returned, const int flags) ++{ ++ int rc = 0; ++ struct mid_q_entry *midQ; ++ unsigned int len = be32_to_cpu(in_buf->smb_buf_length); ++ struct kvec iov = { .iov_base = in_buf, .iov_len = len }; ++ struct smb_rqst rqst = { .rq_iov = &iov, .rq_nvec = 1 }; ++ struct cifs_credits credits = { .value = 1, .instance = 0 }; ++ struct TCP_Server_Info *server; ++ ++ if (ses == NULL) { ++ cifs_dbg(VFS, "Null smb session\n"); ++ return -EIO; ++ } ++ server = ses->server; ++ if (server == NULL) { ++ cifs_dbg(VFS, "Null tcp session\n"); ++ return -EIO; ++ } ++ ++ spin_lock(&server->srv_lock); ++ if (server->tcpStatus == CifsExiting) { ++ spin_unlock(&server->srv_lock); ++ return -ENOENT; ++ } ++ spin_unlock(&server->srv_lock); ++ ++ /* Ensure that we do not send more than 50 overlapping requests ++ to the same server. We may make this configurable later or ++ use ses->maxReq */ ++ ++ if (len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) { ++ cifs_server_dbg(VFS, "Invalid length, greater than maximum frame, %d\n", ++ len); ++ return -EIO; ++ } ++ ++ rc = wait_for_free_request(server, flags, &credits.instance); ++ if (rc) ++ return rc; ++ ++ /* make sure that we sign in the same order that we send on this socket ++ and avoid races inside tcp sendmsg code that could cause corruption ++ of smb data */ ++ ++ cifs_server_lock(server); ++ ++ rc = allocate_mid(ses, in_buf, &midQ); ++ if (rc) { ++ cifs_server_unlock(server); ++ /* Update # of requests on wire to server */ ++ add_credits(server, &credits, 0); ++ return rc; ++ } ++ ++ rc = cifs_sign_smb(in_buf, server, &midQ->sequence_number); ++ if (rc) { ++ cifs_server_unlock(server); ++ goto out; ++ } ++ ++ midQ->mid_state = MID_REQUEST_SUBMITTED; ++ ++ rc = smb_send(server, in_buf, len); ++ cifs_save_when_sent(midQ); ++ ++ if (rc < 0) ++ server->sequence_number -= 2; ++ ++ cifs_server_unlock(server); ++ ++ if (rc < 0) ++ goto out; ++ ++ rc = wait_for_response(server, midQ); ++ if (rc != 0) { ++ send_cancel(server, &rqst, midQ); ++ spin_lock(&midQ->mid_lock); ++ if (midQ->callback) { ++ /* no longer considered to be "in-flight" */ ++ midQ->callback = release_mid; ++ spin_unlock(&midQ->mid_lock); ++ add_credits(server, &credits, 0); ++ return rc; ++ } ++ spin_unlock(&midQ->mid_lock); ++ } ++ ++ rc = cifs_sync_mid_result(midQ, server); ++ if (rc != 0) { ++ add_credits(server, &credits, 0); ++ return rc; ++ } ++ ++ if (!midQ->resp_buf || !out_buf || ++ midQ->mid_state != MID_RESPONSE_READY) { ++ rc = -EIO; ++ cifs_server_dbg(VFS, "Bad MID state?\n"); ++ goto out; ++ } ++ ++ *pbytes_returned = get_rfc1002_length(midQ->resp_buf); ++ memcpy(out_buf, midQ->resp_buf, *pbytes_returned + 4); ++ rc = cifs_check_receive(midQ, server, 0); ++out: ++ delete_mid(midQ); ++ add_credits(server, &credits, 0); ++ ++ return rc; ++} ++ ++/* We send a LOCKINGX_CANCEL_LOCK to cause the Windows ++ blocking lock to return. */ ++ ++static int ++send_lock_cancel(const unsigned int xid, struct cifs_tcon *tcon, ++ struct smb_hdr *in_buf, ++ struct smb_hdr *out_buf) ++{ ++ int bytes_returned; ++ struct cifs_ses *ses = tcon->ses; ++ LOCK_REQ *pSMB = (LOCK_REQ *)in_buf; ++ ++ /* We just modify the current in_buf to change ++ the type of lock from LOCKING_ANDX_SHARED_LOCK ++ or LOCKING_ANDX_EXCLUSIVE_LOCK to ++ LOCKING_ANDX_CANCEL_LOCK. */ ++ ++ pSMB->LockType = LOCKING_ANDX_CANCEL_LOCK|LOCKING_ANDX_LARGE_FILES; ++ pSMB->Timeout = 0; ++ pSMB->hdr.Mid = get_next_mid(ses->server); ++ ++ return SendReceive(xid, ses, in_buf, out_buf, ++ &bytes_returned, 0); ++} ++ ++int ++SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon, ++ struct smb_hdr *in_buf, struct smb_hdr *out_buf, ++ int *pbytes_returned) ++{ ++ int rc = 0; ++ int rstart = 0; ++ struct mid_q_entry *midQ; ++ struct cifs_ses *ses; ++ unsigned int len = be32_to_cpu(in_buf->smb_buf_length); ++ struct kvec iov = { .iov_base = in_buf, .iov_len = len }; ++ struct smb_rqst rqst = { .rq_iov = &iov, .rq_nvec = 1 }; ++ unsigned int instance; ++ struct TCP_Server_Info *server; ++ ++ if (tcon == NULL || tcon->ses == NULL) { ++ cifs_dbg(VFS, "Null smb session\n"); ++ return -EIO; ++ } ++ ses = tcon->ses; ++ server = ses->server; ++ ++ if (server == NULL) { ++ cifs_dbg(VFS, "Null tcp session\n"); ++ return -EIO; ++ } ++ ++ spin_lock(&server->srv_lock); ++ if (server->tcpStatus == CifsExiting) { ++ spin_unlock(&server->srv_lock); ++ return -ENOENT; ++ } ++ spin_unlock(&server->srv_lock); ++ ++ /* Ensure that we do not send more than 50 overlapping requests ++ to the same server. We may make this configurable later or ++ use ses->maxReq */ ++ ++ if (len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) { ++ cifs_tcon_dbg(VFS, "Invalid length, greater than maximum frame, %d\n", ++ len); ++ return -EIO; ++ } ++ ++ rc = wait_for_free_request(server, CIFS_BLOCKING_OP, &instance); ++ if (rc) ++ return rc; ++ ++ /* make sure that we sign in the same order that we send on this socket ++ and avoid races inside tcp sendmsg code that could cause corruption ++ of smb data */ ++ ++ cifs_server_lock(server); ++ ++ rc = allocate_mid(ses, in_buf, &midQ); ++ if (rc) { ++ cifs_server_unlock(server); ++ return rc; ++ } ++ ++ rc = cifs_sign_smb(in_buf, server, &midQ->sequence_number); ++ if (rc) { ++ delete_mid(midQ); ++ cifs_server_unlock(server); ++ return rc; ++ } ++ ++ midQ->mid_state = MID_REQUEST_SUBMITTED; ++ rc = smb_send(server, in_buf, len); ++ cifs_save_when_sent(midQ); ++ ++ if (rc < 0) ++ server->sequence_number -= 2; ++ ++ cifs_server_unlock(server); ++ ++ if (rc < 0) { ++ delete_mid(midQ); ++ return rc; ++ } ++ ++ /* Wait for a reply - allow signals to interrupt. */ ++ rc = wait_event_interruptible(server->response_q, ++ (!(midQ->mid_state == MID_REQUEST_SUBMITTED || ++ midQ->mid_state == MID_RESPONSE_RECEIVED)) || ++ ((server->tcpStatus != CifsGood) && ++ (server->tcpStatus != CifsNew))); ++ ++ /* Were we interrupted by a signal ? */ ++ spin_lock(&server->srv_lock); ++ if ((rc == -ERESTARTSYS) && ++ (midQ->mid_state == MID_REQUEST_SUBMITTED || ++ midQ->mid_state == MID_RESPONSE_RECEIVED) && ++ ((server->tcpStatus == CifsGood) || ++ (server->tcpStatus == CifsNew))) { ++ spin_unlock(&server->srv_lock); ++ ++ if (in_buf->Command == SMB_COM_TRANSACTION2) { ++ /* POSIX lock. We send a NT_CANCEL SMB to cause the ++ blocking lock to return. */ ++ rc = send_cancel(server, &rqst, midQ); ++ if (rc) { ++ delete_mid(midQ); ++ return rc; ++ } ++ } else { ++ /* Windows lock. We send a LOCKINGX_CANCEL_LOCK ++ to cause the blocking lock to return. */ ++ ++ rc = send_lock_cancel(xid, tcon, in_buf, out_buf); ++ ++ /* If we get -ENOLCK back the lock may have ++ already been removed. Don't exit in this case. */ ++ if (rc && rc != -ENOLCK) { ++ delete_mid(midQ); ++ return rc; ++ } ++ } ++ ++ rc = wait_for_response(server, midQ); ++ if (rc) { ++ send_cancel(server, &rqst, midQ); ++ spin_lock(&midQ->mid_lock); ++ if (midQ->callback) { ++ /* no longer considered to be "in-flight" */ ++ midQ->callback = release_mid; ++ spin_unlock(&midQ->mid_lock); ++ return rc; ++ } ++ spin_unlock(&midQ->mid_lock); ++ } ++ ++ /* We got the response - restart system call. */ ++ rstart = 1; ++ spin_lock(&server->srv_lock); ++ } ++ spin_unlock(&server->srv_lock); ++ ++ rc = cifs_sync_mid_result(midQ, server); ++ if (rc != 0) ++ return rc; ++ ++ /* rcvd frame is ok */ ++ if (out_buf == NULL || midQ->mid_state != MID_RESPONSE_READY) { ++ rc = -EIO; ++ cifs_tcon_dbg(VFS, "Bad MID state?\n"); ++ goto out; ++ } ++ ++ *pbytes_returned = get_rfc1002_length(midQ->resp_buf); ++ memcpy(out_buf, midQ->resp_buf, *pbytes_returned + 4); ++ rc = cifs_check_receive(midQ, server, 0); ++out: ++ delete_mid(midQ); ++ if (rstart && rc == -EACCES) ++ return -ERESTARTSYS; ++ return rc; ++} diff --git a/queue-6.18/crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch b/queue-6.18/crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch new file mode 100644 index 0000000000..ec10cb15f7 --- /dev/null +++ b/queue-6.18/crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch @@ -0,0 +1,55 @@ +From stable+bounces-277786-greg=kroah.com@vger.kernel.org Mon Jul 20 16:15:18 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 09:30:39 -0400 +Subject: crypto: atmel-sha204a - fail on hwrng registration error in probe path +To: stable@vger.kernel.org +Cc: Thorsten Blum , Herbert Xu , Sasha Levin +Message-ID: <20260720133039.1149543-1-sashal@kernel.org> + +From: Thorsten Blum + +[ Upstream commit 49e05bb00f2e8168695f7af4d694c39e1423e8a2 ] + +Commit 13909a0c8897 ("crypto: atmel-sha204a - provide the otp content") +overwrote the hwrng registration return value when creating the sysfs +group, which allowed atmel_sha204a_probe() to succeed even if +devm_hwrng_register() failed. + +Return immediately when devm_hwrng_register() fails, and report both +hwrng and sysfs registration errors with dev_err(). Adjust the sysfs +error log message for consistency. + +Fixes: 13909a0c8897 ("crypto: atmel-sha204a - provide the otp content") +Cc: stable@vger.kernel.org +Signed-off-by: Thorsten Blum +Signed-off-by: Herbert Xu +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/crypto/atmel-sha204a.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +--- a/drivers/crypto/atmel-sha204a.c ++++ b/drivers/crypto/atmel-sha204a.c +@@ -183,8 +183,10 @@ static int atmel_sha204a_probe(struct i2 + i2c_priv->hwrng.quality = *quality; + + ret = devm_hwrng_register(&client->dev, &i2c_priv->hwrng); +- if (ret) +- dev_warn(&client->dev, "failed to register RNG (%d)\n", ret); ++ if (ret) { ++ dev_err(&client->dev, "failed to register RNG (%d)\n", ret); ++ return ret; ++ } + + /* otp read out */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) +@@ -192,7 +194,7 @@ static int atmel_sha204a_probe(struct i2 + + ret = sysfs_create_group(&client->dev.kobj, &atmel_sha204a_groups); + if (ret) { +- dev_err(&client->dev, "failed to register sysfs entry\n"); ++ dev_err(&client->dev, "failed to create sysfs group (%d)\n", ret); + return ret; + } + diff --git a/queue-6.18/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch b/queue-6.18/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch new file mode 100644 index 0000000000..d24e488d00 --- /dev/null +++ b/queue-6.18/crypto-qat-fix-restarting-state-leak-on-allocation-failure.patch @@ -0,0 +1,57 @@ +From stable+bounces-278178-greg=kroah.com@vger.kernel.org Mon Jul 20 20:12:43 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 14:08:35 -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: <20260720180835.2922413-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 +@@ -207,13 +207,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.18/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch b/queue-6.18/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch new file mode 100644 index 0000000000..2d015dd144 --- /dev/null +++ b/queue-6.18/crypto-qat-fix-vf2pf-work-teardown-race-in-adf_disable_sriov.patch @@ -0,0 +1,218 @@ +From stable+bounces-274652-greg=kroah.com@vger.kernel.org Wed Jul 15 02:47:26 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:47:11 -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: <20260715004711.3934675-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 +@@ -472,6 +472,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 +@@ -118,6 +118,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); + +@@ -191,6 +192,7 @@ int adf_sriov_configure(struct pci_dev * + void adf_disable_sriov(struct adf_accel_dev *accel_dev); + void adf_reenable_sriov(struct adf_accel_dev *accel_dev); + void adf_enable_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 vf_mask); ++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs); + void adf_disable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev); + bool adf_recv_and_handle_pf2vf_msg(struct adf_accel_dev *accel_dev); + bool adf_recv_and_handle_vf2pf_msg(struct adf_accel_dev *accel_dev, u32 vf_nr); +--- a/drivers/crypto/intel/qat/qat_common/adf_isr.c ++++ b/drivers/crypto/intel/qat/qat_common/adf_isr.c +@@ -62,6 +62,23 @@ void adf_enable_vf2pf_interrupts(struct + unsigned long flags; + + spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags); ++ if (!READ_ONCE(accel_dev->pf.vf2pf_disabled)) ++ GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask); ++ spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags); ++} ++ ++void adf_enable_all_vf2pf_interrupts(struct adf_accel_dev *accel_dev, u32 num_vfs) ++{ ++ void __iomem *pmisc_addr = adf_get_pmisc_base(accel_dev); ++ unsigned long flags; ++ u32 vf_mask; ++ ++ vf_mask = BIT_ULL(num_vfs) - 1; ++ if (!vf_mask) ++ return; ++ ++ spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags); ++ WRITE_ONCE(accel_dev->pf.vf2pf_disabled, false); + GET_PFVF_OPS(accel_dev)->enable_vf2pf_interrupts(pmisc_addr, vf_mask); + spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags); + } +@@ -72,6 +89,7 @@ void adf_disable_all_vf2pf_interrupts(st + unsigned long flags; + + spin_lock_irqsave(&accel_dev->pf.vf2pf_ints_lock, flags); ++ WRITE_ONCE(accel_dev->pf.vf2pf_disabled, true); + GET_PFVF_OPS(accel_dev)->disable_all_vf2pf_interrupts(pmisc_addr); + spin_unlock_irqrestore(&accel_dev->pf.vf2pf_ints_lock, flags); + } +@@ -174,6 +192,27 @@ static irqreturn_t adf_msix_isr_ae(int i + return IRQ_NONE; + } + ++void adf_isr_sync_ae_cluster(struct adf_accel_dev *accel_dev) ++{ ++ struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev; ++ struct adf_hw_device_data *hw_data = GET_HW_DATA(accel_dev); ++ u32 num_entries = pci_dev_info->msix_entries.num_entries; ++ struct adf_irq *irqs = pci_dev_info->msix_entries.irqs; ++ u32 irq_idx; ++ int irq; ++ ++ if (!test_bit(ADF_STATUS_IRQ_ALLOCATED, &accel_dev->status) || !irqs) ++ return; ++ ++ irq_idx = num_entries > 1 ? hw_data->num_banks : 0; ++ if (irq_idx >= num_entries || !irqs[irq_idx].enabled) ++ return; ++ ++ irq = pci_irq_vector(pci_dev_info->pci_dev, hw_data->num_banks); ++ if (irq > 0) ++ synchronize_irq(irq); ++} ++ + static void adf_free_irqs(struct adf_accel_dev *accel_dev) + { + struct adf_accel_pci *pci_dev_info = &accel_dev->accel_pci_dev; +--- a/drivers/crypto/intel/qat/qat_common/adf_sriov.c ++++ b/drivers/crypto/intel/qat/qat_common/adf_sriov.c +@@ -26,6 +26,9 @@ static void adf_iov_send_resp(struct wor + u32 vf_nr = vf_info->vf_nr; + bool ret; + ++ if (READ_ONCE(accel_dev->pf.vf2pf_disabled)) ++ goto out; ++ + mutex_lock(&vf_info->pfvf_mig_lock); + ret = adf_recv_and_handle_vf2pf_msg(accel_dev, vf_nr); + if (ret) +@@ -33,13 +36,18 @@ static void adf_iov_send_resp(struct wor + adf_enable_vf2pf_interrupts(accel_dev, 1 << vf_nr); + mutex_unlock(&vf_info->pfvf_mig_lock); + ++out: + kfree(pf2vf_resp); + } + + void adf_schedule_vf2pf_handler(struct adf_accel_vf_info *vf_info) + { ++ struct adf_accel_dev *accel_dev = vf_info->accel_dev; + struct adf_pf2vf_resp *pf2vf_resp; + ++ if (READ_ONCE(accel_dev->pf.vf2pf_disabled)) ++ return; ++ + pf2vf_resp = kzalloc(sizeof(*pf2vf_resp), GFP_ATOMIC); + if (!pf2vf_resp) + return; +@@ -49,6 +57,12 @@ void adf_schedule_vf2pf_handler(struct a + queue_work(pf2vf_resp_wq, &pf2vf_resp->pf2vf_resp_work); + } + ++static void adf_flush_pf2vf_resp_wq(void) ++{ ++ if (pf2vf_resp_wq) ++ flush_workqueue(pf2vf_resp_wq); ++} ++ + static int adf_enable_sriov(struct adf_accel_dev *accel_dev) + { + struct pci_dev *pdev = accel_to_pci_dev(accel_dev); +@@ -75,7 +89,7 @@ static int adf_enable_sriov(struct adf_a + hw_data->configure_iov_threads(accel_dev, true); + + /* Enable VF to PF interrupts for all VFs */ +- adf_enable_vf2pf_interrupts(accel_dev, BIT_ULL(totalvfs) - 1); ++ adf_enable_all_vf2pf_interrupts(accel_dev, totalvfs); + + /* Do not enable SR-IOV if already enabled */ + if (pci_num_vf(pdev)) +@@ -259,8 +273,10 @@ void adf_disable_sriov(struct adf_accel_ + if (!test_bit(ADF_STATUS_RESTARTING, &accel_dev->status)) + pci_disable_sriov(accel_to_pci_dev(accel_dev)); + +- /* Disable VF to PF interrupts */ ++ /* Block VF2PF work and disable VF to PF interrupts */ + adf_disable_all_vf2pf_interrupts(accel_dev); ++ adf_isr_sync_ae_cluster(accel_dev); ++ adf_flush_pf2vf_resp_wq(); + + /* Clear Valid bits in AE Thread to PCIe Function Mapping */ + if (hw_data->configure_iov_threads) diff --git a/queue-6.18/firmware_loader-add-cancel-helper-for-async-requests.patch b/queue-6.18/firmware_loader-add-cancel-helper-for-async-requests.patch new file mode 100644 index 0000000000..8d600804a4 --- /dev/null +++ b/queue-6.18/firmware_loader-add-cancel-helper-for-async-requests.patch @@ -0,0 +1,180 @@ +From stable+bounces-274117-greg=kroah.com@vger.kernel.org Tue Jul 14 06:05:48 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 00:05:38 -0400 +Subject: firmware_loader: Add cancel helper for async requests +To: stable@vger.kernel.org +Cc: "Cássio Gabriel" , "Takashi Iwai" , "Danilo Krummrich" , "Sasha Levin" +Message-ID: <20260714040539.2433035-1-sashal@kernel.org> + +From: Cássio Gabriel + +[ Upstream commit b9bdd68b8b979c7e9de58b2e7d21e1d7d932c755 ] + +request_firmware_nowait() keeps the callback module pinned and holds +a device reference until the firmware work completes. + +Callers still have no way to cancel or synchronize the queued callback +before tearing down their driver-private state. + +Track scheduled async firmware work in an internal list and add +request_firmware_nowait_cancel(). The helper cancels work matching the +device, callback context and callback function. It cancels work that has +not started yet and waits for an already-running callback to return. If +the request has already completed, it is a no-op. + +Keep the existing request_firmware_nowait() lifetime model manual. A +devres-managed variant can be layered on top separately if needed. + +Signed-off-by: Cássio Gabriel +Reviewed-by: Takashi Iwai +Acked-by: Danilo Krummrich +Signed-off-by: Takashi Iwai +Link: https://patch.msgid.link/20260505-alsa-hda-tas2781-fw-callback-teardown-v4-1-e7c4bf930dc8@gmail.com +Stable-dep-of: 5367e2ad14f0 ("ALSA: hda/tas2781: Cancel async firmware request at unbind") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/base/firmware_loader/main.c | 68 +++++++++++++++++++++++++++++++++--- + include/linux/firmware.h | 10 +++++ + 2 files changed, 74 insertions(+), 4 deletions(-) + +--- a/drivers/base/firmware_loader/main.c ++++ b/drivers/base/firmware_loader/main.c +@@ -1141,6 +1141,7 @@ EXPORT_SYMBOL(release_firmware); + /* Async support */ + struct firmware_work { + struct work_struct work; ++ struct list_head list; + struct module *module; + const char *name; + struct device *device; +@@ -1149,6 +1150,17 @@ struct firmware_work { + u32 opt_flags; + }; + ++static LIST_HEAD(firmware_work_list); ++static DEFINE_SPINLOCK(firmware_work_lock); ++ ++static void firmware_work_free(struct firmware_work *fw_work) ++{ ++ put_device(fw_work->device); /* taken in request_firmware_nowait() */ ++ module_put(fw_work->module); ++ kfree_const(fw_work->name); ++ kfree(fw_work); ++} ++ + static void request_firmware_work_func(struct work_struct *work) + { + struct firmware_work *fw_work; +@@ -1159,11 +1171,15 @@ static void request_firmware_work_func(s + _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, 0, + fw_work->opt_flags); + fw_work->cont(fw, fw_work->context); +- put_device(fw_work->device); /* taken in request_firmware_nowait() */ + +- module_put(fw_work->module); +- kfree_const(fw_work->name); +- kfree(fw_work); ++ spin_lock_irq(&firmware_work_lock); ++ if (!list_empty(&fw_work->list)) { ++ list_del_init(&fw_work->list); ++ spin_unlock_irq(&firmware_work_lock); ++ firmware_work_free(fw_work); ++ return; ++ } ++ spin_unlock_irq(&firmware_work_lock); + } + + +@@ -1173,6 +1189,7 @@ static int _request_firmware_nowait( + void (*cont)(const struct firmware *fw, void *context), bool nowarn) + { + struct firmware_work *fw_work; ++ unsigned long flags; + + fw_work = kzalloc(sizeof(struct firmware_work), gfp); + if (!fw_work) +@@ -1205,7 +1222,12 @@ static int _request_firmware_nowait( + + get_device(fw_work->device); + INIT_WORK(&fw_work->work, request_firmware_work_func); ++ ++ spin_lock_irqsave(&firmware_work_lock, flags); ++ list_add_tail(&fw_work->list, &firmware_work_list); + schedule_work(&fw_work->work); ++ spin_unlock_irqrestore(&firmware_work_lock, flags); ++ + return 0; + } + +@@ -1268,6 +1290,44 @@ int firmware_request_nowait_nowarn( + } + EXPORT_SYMBOL_GPL(firmware_request_nowait_nowarn); + ++/** ++ * request_firmware_nowait_cancel() - cancel an async firmware request ++ * @device: device for which the firmware is being loaded ++ * @context: context passed to request_firmware_nowait() ++ * @cont: callback passed to request_firmware_nowait() ++ * ++ * Cancel a pending request_firmware_nowait() request for @device, @context ++ * and @cont. If the associated work has already started, this function waits ++ * until the callback has returned. If the callback has already completed, this ++ * function does nothing. ++ * ++ * This function may sleep. ++ */ ++void request_firmware_nowait_cancel(struct device *device, void *context, ++ void (*cont)(const struct firmware *fw, ++ void *context)) ++{ ++ struct firmware_work *fw_work = NULL; ++ struct firmware_work *tmp; ++ ++ spin_lock_irq(&firmware_work_lock); ++ list_for_each_entry_reverse(tmp, &firmware_work_list, list) { ++ if (tmp->device == device && tmp->context == context && ++ tmp->cont == cont) { ++ fw_work = tmp; ++ list_del_init(&fw_work->list); ++ break; ++ } ++ } ++ spin_unlock_irq(&firmware_work_lock); ++ ++ if (!fw_work) ++ return; ++ cancel_work_sync(&fw_work->work); ++ firmware_work_free(fw_work); ++} ++EXPORT_SYMBOL_GPL(request_firmware_nowait_cancel); ++ + #ifdef CONFIG_FW_CACHE + static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain); + +--- a/include/linux/firmware.h ++++ b/include/linux/firmware.h +@@ -110,6 +110,9 @@ int request_firmware_nowait( + struct module *module, bool uevent, + const char *name, struct device *device, gfp_t gfp, void *context, + void (*cont)(const struct firmware *fw, void *context)); ++void request_firmware_nowait_cancel(struct device *device, void *context, ++ void (*cont)(const struct firmware *fw, ++ void *context)); + int request_firmware_direct(const struct firmware **fw, const char *name, + struct device *device); + int request_firmware_into_buf(const struct firmware **firmware_p, +@@ -157,6 +160,13 @@ static inline int request_firmware_nowai + return -EINVAL; + } + ++static inline void request_firmware_nowait_cancel(struct device *device, ++ void *context, ++ void (*cont)(const struct firmware *fw, ++ void *context)) ++{ ++} ++ + static inline void release_firmware(const struct firmware *fw) + { + } diff --git a/queue-6.18/functionfs-don-t-abuse-ffs_data_closed-on-fs-shutdown.patch b/queue-6.18/functionfs-don-t-abuse-ffs_data_closed-on-fs-shutdown.patch new file mode 100644 index 0000000000..5373312027 --- /dev/null +++ b/queue-6.18/functionfs-don-t-abuse-ffs_data_closed-on-fs-shutdown.patch @@ -0,0 +1,84 @@ +From stable+bounces-277542-greg=kroah.com@vger.kernel.org Mon Jul 20 04:36:07 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 22:25:47 -0400 +Subject: functionfs: don't abuse ffs_data_closed() on fs shutdown +To: stable@vger.kernel.org +Cc: Al Viro , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260720023553.2928009-2-sashal@kernel.org> + +From: Al Viro + +[ Upstream commit fcb8985143540cbcb8e91c0ea8b7fb5d37c88177 ] + +ffs_data_closed() has a seriously confusing logics in it: in addition +to the normal "decrement a counter and do some work if it hits zero" +there's "... and if it has somehow become negative, do that" bit. + +It's not a race, despite smelling rather fishy. What really happens +is that in addition to "call that on close of files there, to match +the increments of counter on opens" there's one call in ->kill_sb(). +Counter starts at 0 and never goes negative over the lifetime of +filesystem (or we have much worse problems everywhere - ->release() +call of some file somehow unpaired with successful ->open() of the +same). At the filesystem shutdown it will be 0 or, again, we have +much worse problems - filesystem instance destroyed with files on it +still open. In other words, at that call and at that call alone +the decrement would go from 0 to -1, hitting that chunk (and not +hitting the "if it hits 0" part). + +So that check is a weirdly spelled "called from ffs_kill_sb()". +Just expand the call in the latter and kill the misplaced chunk +in ffs_data_closed(). + +Reviewed-by: Greg Kroah-Hartman +Signed-off-by: Al Viro +Stable-dep-of: 3137b243c939 ("usb: gadget: f_fs: initialize reset_work at allocation time") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -2086,12 +2086,18 @@ static int ffs_fs_init_fs_context(struct + return 0; + } + ++static void ffs_data_reset(struct ffs_data *ffs); ++ + static void + ffs_fs_kill_sb(struct super_block *sb) + { + kill_litter_super(sb); +- if (sb->s_fs_info) +- ffs_data_closed(sb->s_fs_info); ++ if (sb->s_fs_info) { ++ struct ffs_data *ffs = sb->s_fs_info; ++ ffs->state = FFS_CLOSING; ++ ffs_data_reset(ffs); ++ ffs_data_put(ffs); ++ } + } + + static struct file_system_type ffs_fs_type = { +@@ -2129,7 +2135,6 @@ static void functionfs_cleanup(void) + /* ffs_data and ffs_function construction and destruction code **************/ + + static void ffs_data_clear(struct ffs_data *ffs); +-static void ffs_data_reset(struct ffs_data *ffs); + + static void ffs_data_get(struct ffs_data *ffs) + { +@@ -2186,11 +2191,6 @@ static void ffs_data_closed(struct ffs_d + ffs_data_reset(ffs); + } + } +- if (atomic_read(&ffs->opened) < 0) { +- ffs->state = FFS_CLOSING; +- ffs_data_reset(ffs); +- } +- + ffs_data_put(ffs); + } + diff --git a/queue-6.18/functionfs-don-t-bother-with-ffs-ref-in-ffs_data_-opened-closed.patch b/queue-6.18/functionfs-don-t-bother-with-ffs-ref-in-ffs_data_-opened-closed.patch new file mode 100644 index 0000000000..fb5ae1c965 --- /dev/null +++ b/queue-6.18/functionfs-don-t-bother-with-ffs-ref-in-ffs_data_-opened-closed.patch @@ -0,0 +1,58 @@ +From stable+bounces-277543-greg=kroah.com@vger.kernel.org Mon Jul 20 04:36:01 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 22:25:48 -0400 +Subject: functionfs: don't bother with ffs->ref in ffs_data_{opened,closed}() +To: stable@vger.kernel.org +Cc: Al Viro , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260720023553.2928009-3-sashal@kernel.org> + +From: Al Viro + +[ Upstream commit fe47466282a69499ae17a22f064e827badb77078 ] + +A reference is held by the superblock (it's dropped in ffs_kill_sb()) +and filesystem will not get to ->kill_sb() while there are any opened +files, TYVM... + +Reviewed-by: Greg Kroah-Hartman +Signed-off-by: Al Viro +Stable-dep-of: 3137b243c939 ("usb: gadget: f_fs: initialize reset_work at allocation time") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -2143,7 +2143,6 @@ static void ffs_data_get(struct ffs_data + + static void ffs_data_opened(struct ffs_data *ffs) + { +- refcount_inc(&ffs->ref); + if (atomic_add_return(1, &ffs->opened) == 1 && + ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; +@@ -2168,11 +2167,11 @@ static void ffs_data_put(struct ffs_data + + static void ffs_data_closed(struct ffs_data *ffs) + { +- struct ffs_epfile *epfiles; +- unsigned long flags; +- + if (atomic_dec_and_test(&ffs->opened)) { + if (ffs->no_disconnect) { ++ struct ffs_epfile *epfiles; ++ unsigned long flags; ++ + ffs->state = FFS_DEACTIVATED; + spin_lock_irqsave(&ffs->eps_lock, flags); + epfiles = ffs->epfiles; +@@ -2191,7 +2190,6 @@ static void ffs_data_closed(struct ffs_d + ffs_data_reset(ffs); + } + } +- ffs_data_put(ffs); + } + + static struct ffs_data *ffs_data_new(const char *dev_name) diff --git a/queue-6.18/functionfs-switch-to-simple_remove_by_name.patch b/queue-6.18/functionfs-switch-to-simple_remove_by_name.patch new file mode 100644 index 0000000000..040eee88fe --- /dev/null +++ b/queue-6.18/functionfs-switch-to-simple_remove_by_name.patch @@ -0,0 +1,164 @@ +From stable+bounces-277544-greg=kroah.com@vger.kernel.org Mon Jul 20 04:36:02 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 22:25:49 -0400 +Subject: functionfs: switch to simple_remove_by_name() +To: stable@vger.kernel.org +Cc: Al Viro , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260720023553.2928009-4-sashal@kernel.org> + +From: Al Viro + +[ Upstream commit c7747fafaba0dcdad3d7da240e961927b4865f98 ] + +No need to return dentry from ffs_sb_create_file() or keep it around +afterwards. + +To avoid subtle issues with getting to ffs from epfiles in +ffs_epfiles_destroy(), pass the superblock as explicit argument. +Callers have it anyway. + +Reviewed-by: Greg Kroah-Hartman +Signed-off-by: Al Viro +Stable-dep-of: 3137b243c939 ("usb: gadget: f_fs: initialize reset_work at allocation time") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 51 +++++++++++++++---------------------- + 1 file changed, 22 insertions(+), 29 deletions(-) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -162,8 +162,6 @@ struct ffs_epfile { + struct ffs_data *ffs; + struct ffs_ep *ep; /* P: ffs->eps_lock */ + +- struct dentry *dentry; +- + /* + * Buffer for holding data from partial reads which may happen since + * we’re rounding user read requests to a multiple of a max packet size. +@@ -273,11 +271,11 @@ struct ffs_desc_helper { + }; + + static int __must_check ffs_epfiles_create(struct ffs_data *ffs); +-static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count); ++static void ffs_epfiles_destroy(struct super_block *sb, ++ struct ffs_epfile *epfiles, unsigned count); + +-static struct dentry * +-ffs_sb_create_file(struct super_block *sb, const char *name, void *data, +- const struct file_operations *fops); ++static int ffs_sb_create_file(struct super_block *sb, const char *name, ++ void *data, const struct file_operations *fops); + + /* Devices management *******************************************************/ + +@@ -1881,9 +1879,8 @@ ffs_sb_make_inode(struct super_block *sb + } + + /* Create "regular" file */ +-static struct dentry *ffs_sb_create_file(struct super_block *sb, +- const char *name, void *data, +- const struct file_operations *fops) ++static int ffs_sb_create_file(struct super_block *sb, const char *name, ++ void *data, const struct file_operations *fops) + { + struct ffs_data *ffs = sb->s_fs_info; + struct dentry *dentry; +@@ -1891,16 +1888,16 @@ static struct dentry *ffs_sb_create_file + + dentry = d_alloc_name(sb->s_root, name); + if (!dentry) +- return NULL; ++ return -ENOMEM; + + inode = ffs_sb_make_inode(sb, data, fops, NULL, &ffs->file_perms); + if (!inode) { + dput(dentry); +- return NULL; ++ return -ENOMEM; + } + + d_add(dentry, inode); +- return dentry; ++ return 0; + } + + /* Super block */ +@@ -1943,10 +1940,7 @@ static int ffs_sb_fill(struct super_bloc + return -ENOMEM; + + /* EP0 file */ +- if (!ffs_sb_create_file(sb, "ep0", ffs, &ffs_ep0_operations)) +- return -ENOMEM; +- +- return 0; ++ return ffs_sb_create_file(sb, "ep0", ffs, &ffs_ep0_operations); + } + + enum { +@@ -2180,7 +2174,7 @@ static void ffs_data_closed(struct ffs_d + flags); + + if (epfiles) +- ffs_epfiles_destroy(epfiles, ++ ffs_epfiles_destroy(ffs->sb, epfiles, + ffs->eps_count); + + if (ffs->setup_state == FFS_SETUP_PENDING) +@@ -2239,7 +2233,7 @@ static void ffs_data_clear(struct ffs_da + * copy of epfile will save us from use-after-free. + */ + if (epfiles) { +- ffs_epfiles_destroy(epfiles, ffs->eps_count); ++ ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); + ffs->epfiles = NULL; + } + +@@ -2336,6 +2330,7 @@ static int ffs_epfiles_create(struct ffs + { + struct ffs_epfile *epfile, *epfiles; + unsigned i, count; ++ int err; + + count = ffs->eps_count; + epfiles = kcalloc(count, sizeof(*epfiles), GFP_KERNEL); +@@ -2353,12 +2348,11 @@ static int ffs_epfiles_create(struct ffs + else + sprintf(epfile->name, "ep%u", i); + epfile->in = (ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) ? 1 : 0; +- epfile->dentry = ffs_sb_create_file(ffs->sb, epfile->name, +- epfile, +- &ffs_epfile_operations); +- if (!epfile->dentry) { +- ffs_epfiles_destroy(epfiles, i - 1); +- return -ENOMEM; ++ err = ffs_sb_create_file(ffs->sb, epfile->name, ++ epfile, &ffs_epfile_operations); ++ if (err) { ++ ffs_epfiles_destroy(ffs->sb, epfiles, i - 1); ++ return err; + } + } + +@@ -2366,16 +2360,15 @@ static int ffs_epfiles_create(struct ffs + return 0; + } + +-static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) ++static void ffs_epfiles_destroy(struct super_block *sb, ++ struct ffs_epfile *epfiles, unsigned count) + { + struct ffs_epfile *epfile = epfiles; ++ struct dentry *root = sb->s_root; + + for (; count; --count, ++epfile) { + BUG_ON(mutex_is_locked(&epfile->mutex)); +- if (epfile->dentry) { +- simple_recursive_removal(epfile->dentry, NULL); +- epfile->dentry = NULL; +- } ++ simple_remove_by_name(root, epfile->name, NULL); + } + + kfree(epfiles); diff --git a/queue-6.18/functionfs-use-spinlock-for-ffs_deactivated-ffs_closing-transitions.patch b/queue-6.18/functionfs-use-spinlock-for-ffs_deactivated-ffs_closing-transitions.patch new file mode 100644 index 0000000000..0160e35a67 --- /dev/null +++ b/queue-6.18/functionfs-use-spinlock-for-ffs_deactivated-ffs_closing-transitions.patch @@ -0,0 +1,114 @@ +From stable+bounces-277545-greg=kroah.com@vger.kernel.org Mon Jul 20 04:36:07 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 22:25:50 -0400 +Subject: functionfs: use spinlock for FFS_DEACTIVATED/FFS_CLOSING transitions +To: stable@vger.kernel.org +Cc: Al Viro , Samuel Wu , Sasha Levin +Message-ID: <20260720023553.2928009-5-sashal@kernel.org> + +From: Al Viro + +[ Upstream commit 2005aabe94eaab8608879d98afb901bc99bc3a31 ] + +When all files are closed, functionfs needs ffs_data_reset() to be +done before any further opens are allowed. + +During that time we have ffs->state set to FFS_CLOSING; that makes +->open() fail with -EBUSY. Once ffs_data_reset() is done, it +switches state (to FFS_READ_DESCRIPTORS) indicating that opening +that thing is allowed again. There's a couple of additional twists: + * mounting with -o no_disconnect delays ffs_data_reset() +from doing that at the final ->release() to the first subsequent +open(). That's indicated by ffs->state set to FFS_DEACTIVATED; +if open() sees that, it immediately switches to FFS_CLOSING and +proceeds with doing ffs_data_reset() before returning to userland. + * a couple of usb callbacks need to force the delayed +transition; unfortunately, they are done in locking environment +that does not allow blocking and ffs_data_reset() can block. +As the result, if these callbacks see FFS_DEACTIVATED, they change +state to FFS_CLOSING and use schedule_work() to get ffs_data_reset() +executed asynchronously. + +Unfortunately, the locking is rather insufficient. A fix attempted +in e5bf5ee26663 ("functionfs: fix the open/removal races") had closed +a bunch of UAF, but it didn't do anything to the callbacks, lacked +barriers in transition from FFS_CLOSING to FFS_READ_DESCRIPTORS +_and_ it had been too heavy-handed in open()/open() serialization - +I've used ffs->mutex for that, and it's being held over actual IO on +ep0, complete with copy_from_user(), etc. + +Even more unfortunately, the userland side is apparently racy enough +to have the resulting timing changes (no failures, just a delayed +return of open(2)) disrupt the things quite badly. Userland bugs +or not, it's a clear regression that needs to be dealt with. + +Solution is to use a spinlock for serializing these state checks and +transitions - unlike ffs->mutex it can be taken in these callbacks +and it doesn't disrupt the timings in open(). + +We could introduce a new spinlock, but it's easier to use the one +that is already there (ffs->eps_lock) instead - the locking +environment is safe for it in all affected places. + +Since now it is held over all places that alter or check the +open count (ffs->opened), there's no need to keep that atomic_t - +int would serve just fine and it's simpler that way. + +Fixes: e5bf5ee26663 ("functionfs: fix the open/removal races") +Fixes: 18d6b32fca38 ("usb: gadget: f_fs: add "no_disconnect" mode") # v4.0 +Tested-by: Samuel Wu +Signed-off-by: Al Viro +Stable-dep-of: 3137b243c939 ("usb: gadget: f_fs: initialize reset_work at allocation time") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -3736,6 +3736,7 @@ static int ffs_func_set_alt(struct usb_f + { + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; ++ unsigned long flags; + int ret = 0, intf; + + if (alt > MAX_ALT_SETTINGS) +@@ -3748,12 +3749,15 @@ static int ffs_func_set_alt(struct usb_f + if (ffs->func) + ffs_func_eps_disable(ffs->func); + ++ spin_lock_irqsave(&ffs->eps_lock, flags); + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; ++ spin_unlock_irqrestore(&ffs->eps_lock, flags); + INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return -ENODEV; + } ++ spin_unlock_irqrestore(&ffs->eps_lock, flags); + + if (ffs->state != FFS_ACTIVE) + return -ENODEV; +@@ -3771,16 +3775,20 @@ static void ffs_func_disable(struct usb_ + { + struct ffs_function *func = ffs_func_from_usb(f); + struct ffs_data *ffs = func->ffs; ++ unsigned long flags; + + if (ffs->func) + ffs_func_eps_disable(ffs->func); + ++ spin_lock_irqsave(&ffs->eps_lock, flags); + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; ++ spin_unlock_irqrestore(&ffs->eps_lock, flags); + INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return; + } ++ spin_unlock_irqrestore(&ffs->eps_lock, flags); + + if (ffs->state == FFS_ACTIVE) { + ffs->func = NULL; diff --git a/queue-6.18/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch b/queue-6.18/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch new file mode 100644 index 0000000000..9421b29572 --- /dev/null +++ b/queue-6.18/hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch @@ -0,0 +1,69 @@ +From stable+bounces-277200-greg=kroah.com@vger.kernel.org Fri Jul 17 21:59:49 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 15:55:16 -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: <20260717195516.2194907-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 +@@ -585,7 +585,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.18/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch b/queue-6.18/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch new file mode 100644 index 0000000000..5388a8630f --- /dev/null +++ b/queue-6.18/hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch @@ -0,0 +1,957 @@ +From stable+bounces-277201-greg=kroah.com@vger.kernel.org Fri Jul 17 21:59:53 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 15:55:15 -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: <20260717195516.2194907-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 +@@ -140,74 +140,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(const struct kiocb *iocb, struct address_space *mapping, +- loff_t pos, unsigned len, struct folio **foliop, void **fsdata); +-extern struct inode *hfs_new_inode(struct inode *, const struct qstr *, umode_t); +-extern void hfs_inode_write_fork(struct inode *, struct hfs_extent *, __be32 *, __be32 *); +-extern int hfs_write_inode(struct inode *, struct writeback_control *); +-extern int hfs_inode_setattr(struct mnt_idmap *, struct dentry *, +- struct iattr *); ++ loff_t pos, unsigned int len, struct folio **foliop, ++ void **fsdata); ++extern struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, ++ umode_t mode); ++extern void hfs_inode_write_fork(struct inode *inode, struct hfs_extent *ext, ++ __be32 *log_size, __be32 *phys_size); ++extern int hfs_write_inode(struct inode *inode, struct writeback_control *wbc); ++extern int hfs_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, ++ struct iattr *attr); + extern void hfs_inode_read_fork(struct inode *inode, struct hfs_extent *ext, +- __be32 log_size, __be32 phys_size, u32 clump_size); +-extern struct inode *hfs_iget(struct super_block *, struct hfs_cat_key *, hfs_cat_rec *); +-extern void hfs_evict_inode(struct inode *); +-extern void hfs_delete_inode(struct inode *); ++ __be32 __log_size, __be32 phys_size, ++ u32 clump_size); ++extern struct inode *hfs_iget(struct super_block *sb, struct hfs_cat_key *key, ++ hfs_cat_rec *rec); ++extern void hfs_evict_inode(struct inode *inode); ++extern void hfs_delete_inode(struct inode *inode); + + /* attr.c */ + extern const struct xattr_handler * const hfs_xattr_handlers[]; + + /* mdb.c */ +-extern int hfs_mdb_get(struct super_block *); +-extern void hfs_mdb_commit(struct super_block *); +-extern void hfs_mdb_close(struct super_block *); +-extern void hfs_mdb_put(struct super_block *); ++extern int hfs_mdb_get(struct super_block *sb); ++extern void hfs_mdb_commit(struct super_block *sb); ++extern void hfs_mdb_close(struct super_block *sb); ++extern void hfs_mdb_put(struct super_block *sb); + + /* part_tbl.c */ +-extern int hfs_part_find(struct super_block *, sector_t *, sector_t *); ++extern int hfs_part_find(struct super_block *sb, ++ sector_t *part_start, sector_t *part_size); + + /* string.c */ + extern const struct dentry_operations hfs_dentry_operations; + +-extern int hfs_hash_dentry(const struct dentry *, struct qstr *); +-extern int hfs_strcmp(const unsigned char *, unsigned int, +- const unsigned char *, unsigned int); ++extern int hfs_hash_dentry(const struct dentry *dentry, struct qstr *this); ++extern int hfs_strcmp(const unsigned char *s1, unsigned int len1, ++ const unsigned char *s2, unsigned int len2); + extern int hfs_compare_dentry(const struct dentry *dentry, +- unsigned int len, const char *str, const struct qstr *name); ++ unsigned int len, const char *str, ++ const struct qstr *name); + + /* trans.c */ +-extern void hfs_asc2mac(struct super_block *, struct hfs_name *, const struct qstr *); +-extern int hfs_mac2asc(struct super_block *, char *, const struct hfs_name *); ++extern void hfs_asc2mac(struct super_block *sb, ++ struct hfs_name *out, const struct qstr *in); ++extern int hfs_mac2asc(struct super_block *sb, ++ char *out, const struct hfs_name *in); + + /* super.c */ + extern void hfs_mark_mdb_dirty(struct super_block *sb); +--- a/fs/hfs/inode.c ++++ b/fs/hfs/inode.c +@@ -45,7 +45,8 @@ static void hfs_write_failed(struct addr + } + + int hfs_write_begin(const struct kiocb *iocb, struct address_space *mapping, +- loff_t pos, unsigned len, struct folio **foliop, void **fsdata) ++ loff_t pos, unsigned int len, struct folio **foliop, ++ void **fsdata) + { + int ret; + +--- a/fs/hfsplus/bfind.c ++++ b/fs/hfsplus/bfind.c +@@ -210,7 +210,7 @@ release: + return res; + } + +-int hfs_brec_read(struct hfs_find_data *fd, void *rec, int rec_len) ++int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len) + { + int res; + +--- a/fs/hfsplus/bnode.c ++++ b/fs/hfsplus/bnode.c +@@ -20,10 +20,10 @@ + + + /* Copy a specified range of bytes from the raw data of a node */ +-void hfs_bnode_read(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page **pagep; +- int l; ++ u32 l; + + memset(buf, 0, len); + +@@ -33,7 +33,7 @@ void hfs_bnode_read(struct hfs_bnode *no + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -45,17 +45,17 @@ void hfs_bnode_read(struct hfs_bnode *no + pagep = node->page + (off >> PAGE_SHIFT); + off &= ~PAGE_MASK; + +- l = min_t(int, len, PAGE_SIZE - off); ++ l = min_t(u32, len, PAGE_SIZE - off); + memcpy_from_page(buf, *pagep, off, l); + + while ((len -= l) != 0) { + buf += l; +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memcpy_from_page(buf, *++pagep, 0, l); + } + } + +-u16 hfs_bnode_read_u16(struct hfs_bnode *node, int off) ++u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off) + { + __be16 data; + /* TODO: optimize later... */ +@@ -63,7 +63,7 @@ u16 hfs_bnode_read_u16(struct hfs_bnode + return be16_to_cpu(data); + } + +-u8 hfs_bnode_read_u8(struct hfs_bnode *node, int off) ++u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off) + { + u8 data; + /* TODO: optimize later... */ +@@ -71,10 +71,10 @@ u8 hfs_bnode_read_u8(struct hfs_bnode *n + return data; + } + +-void hfs_bnode_read_key(struct hfs_bnode *node, void *key, int off) ++void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off) + { + struct hfs_btree *tree; +- int key_len; ++ u32 key_len; + + tree = node->tree; + if (node->type == HFS_NODE_LEAF || +@@ -86,17 +86,17 @@ void hfs_bnode_read_key(struct hfs_bnode + + if (key_len > sizeof(hfsplus_btree_key) || key_len < 1) { + memset(key, 0, sizeof(hfsplus_btree_key)); +- pr_err("hfsplus: Invalid key length: %d\n", key_len); ++ pr_err("hfsplus: Invalid key length: %u\n", key_len); + return; + } + + hfs_bnode_read(node, key, off, key_len); + } + +-void hfs_bnode_write(struct hfs_bnode *node, void *buf, int off, int len) ++void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len) + { + struct page **pagep; +- int l; ++ u32 l; + + if (!is_bnode_offset_valid(node, off)) + return; +@@ -104,7 +104,7 @@ void hfs_bnode_write(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -116,29 +116,29 @@ void hfs_bnode_write(struct hfs_bnode *n + pagep = node->page + (off >> PAGE_SHIFT); + off &= ~PAGE_MASK; + +- l = min_t(int, len, PAGE_SIZE - off); ++ l = min_t(u32, len, PAGE_SIZE - off); + memcpy_to_page(*pagep, off, buf, l); + set_page_dirty(*pagep); + + while ((len -= l) != 0) { + buf += l; +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memcpy_to_page(*++pagep, 0, buf, l); + set_page_dirty(*pagep); + } + } + +-void hfs_bnode_write_u16(struct hfs_bnode *node, int off, u16 data) ++void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data) + { + __be16 v = cpu_to_be16(data); + /* TODO: optimize later... */ + hfs_bnode_write(node, &v, off, 2); + } + +-void hfs_bnode_clear(struct hfs_bnode *node, int off, int len) ++void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len) + { + struct page **pagep; +- int l; ++ u32 l; + + if (!is_bnode_offset_valid(node, off)) + return; +@@ -146,7 +146,7 @@ void hfs_bnode_clear(struct hfs_bnode *n + if (len == 0) { + pr_err("requested zero length: " + "NODE: id %u, type %#x, height %u, " +- "node_size %u, offset %d, len %d\n", ++ "node_size %u, offset %u, len %u\n", + node->this, node->type, node->height, + node->tree->node_size, off, len); + return; +@@ -158,22 +158,22 @@ void hfs_bnode_clear(struct hfs_bnode *n + pagep = node->page + (off >> PAGE_SHIFT); + off &= ~PAGE_MASK; + +- l = min_t(int, len, PAGE_SIZE - off); ++ l = min_t(u32, len, PAGE_SIZE - off); + memzero_page(*pagep, off, l); + set_page_dirty(*pagep); + + while ((len -= l) != 0) { +- l = min_t(int, len, PAGE_SIZE); ++ l = min_t(u32, len, PAGE_SIZE); + memzero_page(*++pagep, 0, l); + set_page_dirty(*pagep); + } + } + +-void hfs_bnode_copy(struct hfs_bnode *dst_node, int dst, +- struct hfs_bnode *src_node, int src, int len) ++void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst, ++ struct hfs_bnode *src_node, u32 src, u32 len) + { + struct page **src_page, **dst_page; +- int l; ++ u32 l; + + hfs_dbg("dst %u, src %u, len %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("dst %u, src %u, len %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 +@@ -357,21 +357,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); +@@ -386,7 +386,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 */ +@@ -399,7 +399,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 */ +@@ -560,14 +560,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); + } +@@ -576,7 +576,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; + +@@ -586,12 +586,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.18/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch b/queue-6.18/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch new file mode 100644 index 0000000000..a99902b850 --- /dev/null +++ b/queue-6.18/iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch @@ -0,0 +1,89 @@ +From stable+bounces-273933-greg=kroah.com@vger.kernel.org Mon Jul 13 19:57:52 2026 +From: Sasha Levin +Date: Mon, 13 Jul 2026 13:57:33 -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: <20260713175733.1907391-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 +@@ -74,6 +74,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; + +@@ -83,8 +90,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.18/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch b/queue-6.18/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch new file mode 100644 index 0000000000..88e627f76b --- /dev/null +++ b/queue-6.18/ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch @@ -0,0 +1,457 @@ +From stable+bounces-275248-greg=kroah.com@vger.kernel.org Thu Jul 16 13:21:53 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:21:20 -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: <20260716112121.1735294-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); +@@ -458,8 +530,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" +@@ -119,6 +120,7 @@ struct ksmbd_conn { + bool binding; + atomic_t refcnt; + bool is_aapl; ++ struct work_struct release_work; + }; + + struct ksmbd_conn_ops { +@@ -164,6 +166,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 +@@ -588,8 +588,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: +@@ -615,6 +621,12 @@ static void __exit ksmbd_server_exit(voi + { + ksmbd_server_shutdown(); + rcu_barrier(); ++ /* ++ * ksmbd_conn_put() defers the final release onto ksmbd_conn_wq, ++ * so drain it after rcu_barrier() has fired any pending RCU ++ * callbacks that may have queued a release. ++ */ ++ ksmbd_conn_wq_destroy(); + ksmbd_release_inode_hash(); + } + +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -402,6 +402,17 @@ static void __ksmbd_close_fd(struct ksmb + kfree(smb_lock); + } + ++ /* ++ * Drop fp's strong reference on conn (taken in ksmbd_open_fd() / ++ * ksmbd_reopen_durable_fd()). Durable fps that reached the ++ * scavenger have already had fp->conn cleared by session_fd_check(), ++ * in which case there is nothing to drop here. ++ */ ++ if (fp->conn) { ++ ksmbd_conn_put(fp->conn); ++ fp->conn = NULL; ++ } ++ + if (ksmbd_stream_fd(fp)) + kfree(fp->stream.name); + kfree(fp->owner.name); +@@ -694,7 +705,14 @@ struct ksmbd_file *ksmbd_open_fd(struct + atomic_set(&fp->refcount, 1); + + fp->filp = filp; +- fp->conn = work->conn; ++ /* ++ * fp owns a strong reference on fp->conn for as long as fp->conn is ++ * non-NULL, so session_fd_check() and __ksmbd_close_fd() never ++ * dereference a dangling pointer. Paired with ksmbd_conn_put() in ++ * session_fd_check() (durable preserve), in __ksmbd_close_fd() ++ * (final close), and on the error paths below. ++ */ ++ fp->conn = ksmbd_conn_get(work->conn); + fp->tcon = work->tcon; + fp->volatile_id = KSMBD_NO_FID; + fp->persistent_id = KSMBD_NO_FID; +@@ -716,6 +734,8 @@ struct ksmbd_file *ksmbd_open_fd(struct + return fp; + + err_out: ++ /* fp->conn was set and refcounted before every branch here. */ ++ ksmbd_conn_put(fp->conn); + kmem_cache_free(filp_cache, fp); + return ERR_PTR(ret); + } +@@ -1043,25 +1063,32 @@ static bool session_fd_check(struct ksmb + if (!is_reconnectable(fp)) + return false; + ++ if (WARN_ON_ONCE(!fp->conn)) ++ return false; ++ + if (ksmbd_vfs_copy_durable_owner(fp, user)) + return false; + ++ /* ++ * fp owns a strong reference on fp->conn (taken in ksmbd_open_fd() ++ * / ksmbd_reopen_durable_fd()), so conn stays valid for the whole ++ * body of this function regardless of any op->conn puts below. ++ */ + conn = fp->conn; + ci = fp->f_ci; + down_write(&ci->m_lock); + list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) { + if (op->conn != conn) + continue; +- if (op->conn && atomic_dec_and_test(&op->conn->refcnt)) +- kfree(op->conn); ++ ksmbd_conn_put(op->conn); + op->conn = NULL; + } + up_write(&ci->m_lock); + + list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) { +- spin_lock(&fp->conn->llist_lock); ++ spin_lock(&conn->llist_lock); + list_del_init(&smb_lock->clist); +- spin_unlock(&fp->conn->llist_lock); ++ spin_unlock(&conn->llist_lock); + } + + fp->conn = NULL; +@@ -1072,6 +1099,8 @@ static bool session_fd_check(struct ksmb + fp->durable_scavenger_timeout = + jiffies_to_msecs(jiffies) + fp->durable_timeout; + ++ /* Drop fp's own reference on conn. */ ++ ksmbd_conn_put(conn); + return true; + } + +@@ -1158,15 +1187,27 @@ int ksmbd_reopen_durable_fd(struct ksmbd + + old_f_state = fp->f_state; + fp->f_state = FP_NEW; ++ ++ /* ++ * Initialize fp's connection binding before publishing fp into the ++ * session's file table. If __open_id() is ordered first, a ++ * concurrent teardown that iterates the table can observe a valid ++ * volatile_id with fp->conn == NULL and preserve a ++ * partially-initialized fp. fp owns a strong reference on the new ++ * conn (see ksmbd_open_fd()); undo it on __open_id() failure. ++ */ ++ fp->conn = ksmbd_conn_get(conn); ++ fp->tcon = work->tcon; ++ + __open_id(&work->sess->file_table, fp, OPEN_ID_TYPE_VOLATILE_ID); + if (!has_file_id(fp->volatile_id)) { ++ fp->conn = NULL; ++ fp->tcon = NULL; ++ ksmbd_conn_put(conn); + fp->f_state = old_f_state; + return -EBADF; + } + +- fp->conn = conn; +- fp->tcon = work->tcon; +- + list_for_each_entry(smb_lock, &fp->lock_list, flist) { + spin_lock(&conn->llist_lock); + list_add_tail(&smb_lock->clist, &conn->lock_list); +@@ -1178,8 +1219,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd + list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) { + if (op->conn) + continue; +- op->conn = fp->conn; +- atomic_inc(&op->conn->refcnt); ++ op->conn = ksmbd_conn_get(fp->conn); + } + up_write(&ci->m_lock); + diff --git a/queue-6.18/ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch b/queue-6.18/ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch new file mode 100644 index 0000000000..b6b8de16df --- /dev/null +++ b/queue-6.18/ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch @@ -0,0 +1,145 @@ +From stable+bounces-275234-greg=kroah.com@vger.kernel.org Thu Jul 16 12:19:38 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 06:18:49 -0400 +Subject: ksmbd: fix path resolution in ksmbd_vfs_kern_path_create +To: stable@vger.kernel.org +Cc: Davide Ornaghi , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716101849.1464192-1-sashal@kernel.org> + +From: Davide Ornaghi + +[ Upstream commit 1c8951963d8ed357f70f59e0ad4ddce2199d2016 ] + +The SMB2 open lookup is rooted at the share with LOOKUP_BENEATH, but the +create/mkdir/hardlink sink is not: ksmbd_vfs_kern_path_create() builds an +absolute path with convert_to_unix_name() and resolves it from AT_FDCWD +via start_creating_path(), so a ".." component is walked from the real +filesystem root and escapes the export. + +An authenticated client races a missing path component so the rooted open +lookup returns -ENOENT (taking the create branch) while the same component +is present (a directory) when the create walk runs; the create then +resolves ".." out of the share. + +Root the create walk at the share like the lookup and rename paths already +are: resolve the parent with vfs_path_parent_lookup(..., LOOKUP_BENEATH, +&share_conf->vfs_path) and create the final component with +start_creating_noperm(). convert_to_unix_name() then has no callers and is +removed. + +Fixes: 265fd1991c1d ("ksmbd: use LOOKUP_BENEATH to prevent the out of share access") +Cc: stable@vger.kernel.org +Signed-off-by: Davide Ornaghi +Acked-by: Namjae Jeon +Signed-off-by: Steve French +[ replaced start_creating_noperm() and CLASS(filename_kernel) with open-coded getname_kernel()/inode_lock_nested()/lookup_one_qstr_excl() equivalents absent in 6.18 ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/misc.c | 33 --------------------------------- + fs/smb/server/misc.h | 1 - + fs/smb/server/vfs.c | 38 ++++++++++++++++++++++++++++++++------ + 3 files changed, 32 insertions(+), 40 deletions(-) + +--- a/fs/smb/server/misc.c ++++ b/fs/smb/server/misc.c +@@ -278,39 +278,6 @@ char *ksmbd_extract_sharename(struct uni + return ksmbd_casefold_sharename(um, name); + } + +-/** +- * convert_to_unix_name() - convert windows name to unix format +- * @share: ksmbd_share_config pointer +- * @name: file name that is relative to share +- * +- * Return: converted name on success, otherwise NULL +- */ +-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name) +-{ +- int no_slash = 0, name_len, path_len; +- char *new_name; +- +- if (name[0] == '/') +- name++; +- +- path_len = share->path_sz; +- name_len = strlen(name); +- new_name = kmalloc(path_len + name_len + 2, KSMBD_DEFAULT_GFP); +- if (!new_name) +- return new_name; +- +- memcpy(new_name, share->path, path_len); +- if (new_name[path_len - 1] != '/') { +- new_name[path_len] = '/'; +- no_slash = 1; +- } +- +- memcpy(new_name + path_len + no_slash, name, name_len); +- path_len += name_len + no_slash; +- new_name[path_len] = 0x00; +- return new_name; +-} +- + char *ksmbd_convert_dir_info_name(struct ksmbd_dir_info *d_info, + const struct nls_table *local_nls, + int *conv_len) +--- a/fs/smb/server/misc.h ++++ b/fs/smb/server/misc.h +@@ -22,7 +22,6 @@ void ksmbd_strip_last_slash(char *path); + void ksmbd_conv_path_to_windows(char *path); + char *ksmbd_casefold_sharename(struct unicode_map *um, const char *name); + char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename); +-char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name); + + #define KSMBD_DIR_INFO_ALIGNMENT 8 + struct ksmbd_dir_info; +--- a/fs/smb/server/vfs.c ++++ b/fs/smb/server/vfs.c +@@ -1338,15 +1338,41 @@ struct dentry *ksmbd_vfs_kern_path_creat + unsigned int flags, + struct path *path) + { +- char *abs_name; ++ struct ksmbd_share_config *share_conf = work->tcon->share_conf; ++ struct filename *filename __free(putname) = NULL; ++ struct qstr last; + struct dentry *dent; ++ int err, type; + +- abs_name = convert_to_unix_name(work->tcon->share_conf, name); +- if (!abs_name) +- return ERR_PTR(-ENOMEM); ++ /* resolve the name beneath the share root so ".." cannot escape */ ++ filename = getname_kernel(name); ++ if (IS_ERR(filename)) ++ return ERR_CAST(filename); + +- dent = start_creating_path(AT_FDCWD, abs_name, path, flags); +- kfree(abs_name); ++ err = vfs_path_parent_lookup(filename, flags | LOOKUP_BENEATH, ++ path, &last, &type, ++ &share_conf->vfs_path); ++ if (err) ++ return ERR_PTR(err); ++ ++ if (unlikely(type != LAST_NORM)) { ++ path_put(path); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ err = mnt_want_write(path->mnt); ++ if (err) { ++ path_put(path); ++ return ERR_PTR(err); ++ } ++ ++ inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT); ++ dent = lookup_one_qstr_excl(&last, path->dentry, LOOKUP_CREATE); ++ if (IS_ERR(dent)) { ++ inode_unlock(path->dentry->d_inode); ++ mnt_drop_write(path->mnt); ++ path_put(path); ++ } + return dent; + } + diff --git a/queue-6.18/ksmbd-track-the-connection-owning-a-byte-range-lock.patch b/queue-6.18/ksmbd-track-the-connection-owning-a-byte-range-lock.patch new file mode 100644 index 0000000000..69621bf5de --- /dev/null +++ b/queue-6.18/ksmbd-track-the-connection-owning-a-byte-range-lock.patch @@ -0,0 +1,138 @@ +From stable+bounces-275249-greg=kroah.com@vger.kernel.org Thu Jul 16 13:21:32 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:21:21 -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: <20260716112121.1735294-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 +@@ -7660,9 +7660,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; +@@ -7797,6 +7799,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); +@@ -7851,11 +7854,14 @@ out: + } + + list_del(&smb_lock->llist); +- spin_lock(&work->conn->llist_lock); ++ conn = smb_lock->conn; ++ spin_lock(&conn->llist_lock); + if (!list_empty(&smb_lock->flist)) + list_del(&smb_lock->flist); + list_del(&smb_lock->clist); +- spin_unlock(&work->conn->llist_lock); ++ smb_lock->conn = NULL; ++ spin_unlock(&conn->llist_lock); ++ ksmbd_conn_put(conn); + + locks_free_lock(smb_lock->fl); + if (rlock) +--- a/fs/smb/server/vfs_cache.c ++++ b/fs/smb/server/vfs_cache.c +@@ -391,10 +391,14 @@ static void __ksmbd_close_fd(struct ksmb + * there are not accesses to fp->lock_list. + */ + list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) { +- if (!list_empty(&smb_lock->clist) && fp->conn) { +- spin_lock(&fp->conn->llist_lock); +- list_del(&smb_lock->clist); +- spin_unlock(&fp->conn->llist_lock); ++ struct ksmbd_conn *conn = smb_lock->conn; ++ ++ if (conn) { ++ spin_lock(&conn->llist_lock); ++ list_del_init(&smb_lock->clist); ++ smb_lock->conn = NULL; ++ spin_unlock(&conn->llist_lock); ++ ksmbd_conn_put(conn); + } + + list_del(&smb_lock->flist); +@@ -1086,9 +1090,15 @@ static bool session_fd_check(struct ksmb + up_write(&ci->m_lock); + + list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) { +- spin_lock(&conn->llist_lock); ++ struct ksmbd_conn *lock_conn = smb_lock->conn; ++ ++ if (!lock_conn) ++ continue; ++ spin_lock(&lock_conn->llist_lock); + list_del_init(&smb_lock->clist); +- spin_unlock(&conn->llist_lock); ++ smb_lock->conn = NULL; ++ spin_unlock(&lock_conn->llist_lock); ++ ksmbd_conn_put(lock_conn); + } + + fp->conn = NULL; +@@ -1209,6 +1219,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd + } + + list_for_each_entry(smb_lock, &fp->lock_list, flist) { ++ smb_lock->conn = ksmbd_conn_get(conn); + spin_lock(&conn->llist_lock); + list_add_tail(&smb_lock->clist, &conn->lock_list); + spin_unlock(&conn->llist_lock); +--- a/fs/smb/server/vfs_cache.h ++++ b/fs/smb/server/vfs_cache.h +@@ -32,6 +32,7 @@ struct ksmbd_session; + + struct ksmbd_lock { + struct file_lock *fl; ++ struct ksmbd_conn *conn; + struct list_head clist; + struct list_head flist; + struct list_head llist; diff --git a/queue-6.18/ksmbd-use-hmac-md5-library-for-ntlmv2.patch b/queue-6.18/ksmbd-use-hmac-md5-library-for-ntlmv2.patch new file mode 100644 index 0000000000..3e97d3ca68 --- /dev/null +++ b/queue-6.18/ksmbd-use-hmac-md5-library-for-ntlmv2.patch @@ -0,0 +1,350 @@ +From stable+bounces-275253-greg=kroah.com@vger.kernel.org Thu Jul 16 13:22:08 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:21:46 -0400 +Subject: ksmbd: Use HMAC-MD5 library for NTLMv2 +To: stable@vger.kernel.org +Cc: Eric Biggers , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716112147.1735816-3-sashal@kernel.org> + +From: Eric Biggers + +[ Upstream commit 3a597e6e9701eb7af04864ffdc0a6a91bc8c6496 ] + +For the HMAC-MD5 computations in NTLMv2, use the HMAC-MD5 library +instead of a "hmac(md5)" crypto_shash. This is simpler and faster. +With the library there's no need to allocate memory, no need to handle +errors, and the HMAC-MD5 code is accessed directly without inefficient +indirect calls and other unnecessary API overhead. + +To preserve the existing behavior of NTLMv2 support being disabled when +the kernel is booted with "fips=1", make ksmbd_auth_ntlmv2() check +fips_enabled itself. Previously it relied on the error from +crypto_alloc_shash("hmac(md5)") being bubbled up. I don't know for sure +that this is actually needed, but this preserves the existing behavior. + +Signed-off-by: Eric Biggers +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Stable-dep-of: 954d196bebb2 ("ksmbd: validate NTLMv2 response before updating session key") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/Kconfig | 3 + fs/smb/server/auth.c | 175 ++++++--------------------------------------- + fs/smb/server/crypto_ctx.c | 8 -- + fs/smb/server/crypto_ctx.h | 6 - + fs/smb/server/server.c | 2 + 5 files changed, 28 insertions(+), 166 deletions(-) + +--- a/fs/smb/server/Kconfig ++++ b/fs/smb/server/Kconfig +@@ -7,11 +7,10 @@ config SMB_SERVER + select NLS_UTF8 + select NLS_UCS2_UTILS + select CRYPTO +- select CRYPTO_MD5 +- select CRYPTO_HMAC + select CRYPTO_ECB + select CRYPTO_LIB_ARC4 + select CRYPTO_LIB_DES ++ select CRYPTO_LIB_MD5 + select CRYPTO_LIB_SHA256 + select CRYPTO_LIB_SHA512 + select CRYPTO_LIB_UTILS +--- a/fs/smb/server/auth.c ++++ b/fs/smb/server/auth.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -71,85 +72,16 @@ void ksmbd_copy_gss_neg_header(void *buf + memcpy(buf, NEGOTIATE_GSS_HEADER, AUTH_GSS_LENGTH); + } + +-/** +- * ksmbd_gen_sess_key() - function to generate session key +- * @sess: session of connection +- * @hash: source hash value to be used for find session key +- * @hmac: source hmac value to be used for finding session key +- * +- */ +-static int ksmbd_gen_sess_key(struct ksmbd_session *sess, char *hash, +- char *hmac) +-{ +- struct ksmbd_crypto_ctx *ctx; +- int rc; +- +- ctx = ksmbd_crypto_ctx_find_hmacmd5(); +- if (!ctx) { +- ksmbd_debug(AUTH, "could not crypto alloc hmacmd5\n"); +- return -ENOMEM; +- } +- +- rc = crypto_shash_setkey(CRYPTO_HMACMD5_TFM(ctx), +- hash, +- CIFS_HMAC_MD5_HASH_SIZE); +- if (rc) { +- ksmbd_debug(AUTH, "hmacmd5 set key fail error %d\n", rc); +- goto out; +- } +- +- rc = crypto_shash_init(CRYPTO_HMACMD5(ctx)); +- if (rc) { +- ksmbd_debug(AUTH, "could not init hmacmd5 error %d\n", rc); +- goto out; +- } +- +- rc = crypto_shash_update(CRYPTO_HMACMD5(ctx), +- hmac, +- SMB2_NTLMV2_SESSKEY_SIZE); +- if (rc) { +- ksmbd_debug(AUTH, "Could not update with response error %d\n", rc); +- goto out; +- } +- +- rc = crypto_shash_final(CRYPTO_HMACMD5(ctx), sess->sess_key); +- if (rc) { +- ksmbd_debug(AUTH, "Could not generate hmacmd5 hash error %d\n", rc); +- goto out; +- } +- +-out: +- ksmbd_release_crypto_ctx(ctx); +- return rc; +-} +- + static int calc_ntlmv2_hash(struct ksmbd_conn *conn, struct ksmbd_session *sess, + char *ntlmv2_hash, char *dname) + { + int ret, len, conv_len; + wchar_t *domain = NULL; + __le16 *uniname = NULL; +- struct ksmbd_crypto_ctx *ctx; ++ struct hmac_md5_ctx ctx; + +- ctx = ksmbd_crypto_ctx_find_hmacmd5(); +- if (!ctx) { +- ksmbd_debug(AUTH, "can't generate ntlmv2 hash\n"); +- return -ENOMEM; +- } +- +- ret = crypto_shash_setkey(CRYPTO_HMACMD5_TFM(ctx), +- user_passkey(sess->user), ++ hmac_md5_init_usingrawkey(&ctx, user_passkey(sess->user), + CIFS_ENCPWD_SIZE); +- if (ret) { +- ksmbd_debug(AUTH, "Could not set NT Hash as a key\n"); +- goto out; +- } +- +- ret = crypto_shash_init(CRYPTO_HMACMD5(ctx)); +- if (ret) { +- ksmbd_debug(AUTH, "could not init hmacmd5\n"); +- goto out; +- } + + /* convert user_name to unicode */ + len = strlen(user_name(sess->user)); +@@ -167,13 +99,7 @@ static int calc_ntlmv2_hash(struct ksmbd + } + UniStrupr(uniname); + +- ret = crypto_shash_update(CRYPTO_HMACMD5(ctx), +- (char *)uniname, +- UNICODE_LEN(conv_len)); +- if (ret) { +- ksmbd_debug(AUTH, "Could not update with user\n"); +- goto out; +- } ++ hmac_md5_update(&ctx, (const u8 *)uniname, UNICODE_LEN(conv_len)); + + /* Convert domain name or conn name to unicode and uppercase */ + len = strlen(dname); +@@ -190,21 +116,12 @@ static int calc_ntlmv2_hash(struct ksmbd + goto out; + } + +- ret = crypto_shash_update(CRYPTO_HMACMD5(ctx), +- (char *)domain, +- UNICODE_LEN(conv_len)); +- if (ret) { +- ksmbd_debug(AUTH, "Could not update with domain\n"); +- goto out; +- } +- +- ret = crypto_shash_final(CRYPTO_HMACMD5(ctx), ntlmv2_hash); +- if (ret) +- ksmbd_debug(AUTH, "Could not generate md5 hash\n"); ++ hmac_md5_update(&ctx, (const u8 *)domain, UNICODE_LEN(conv_len)); ++ hmac_md5_final(&ctx, ntlmv2_hash); ++ ret = 0; + out: + kfree(uniname); + kfree(domain); +- ksmbd_release_crypto_ctx(ctx); + return ret; + } + +@@ -225,74 +142,34 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn + { + char ntlmv2_hash[CIFS_ENCPWD_SIZE]; + char ntlmv2_rsp[CIFS_HMAC_MD5_HASH_SIZE]; +- struct ksmbd_crypto_ctx *ctx = NULL; +- char *construct = NULL; +- int rc, len; +- +- rc = calc_ntlmv2_hash(conn, sess, ntlmv2_hash, domain_name); +- if (rc) { +- ksmbd_debug(AUTH, "could not get v2 hash rc %d\n", rc); +- goto out; +- } +- +- ctx = ksmbd_crypto_ctx_find_hmacmd5(); +- if (!ctx) { +- ksmbd_debug(AUTH, "could not crypto alloc hmacmd5\n"); +- return -ENOMEM; +- } +- +- rc = crypto_shash_setkey(CRYPTO_HMACMD5_TFM(ctx), +- ntlmv2_hash, +- CIFS_HMAC_MD5_HASH_SIZE); +- if (rc) { +- ksmbd_debug(AUTH, "Could not set NTLMV2 Hash as a key\n"); +- goto out; +- } +- +- rc = crypto_shash_init(CRYPTO_HMACMD5(ctx)); +- if (rc) { +- ksmbd_debug(AUTH, "Could not init hmacmd5\n"); +- goto out; +- } ++ struct hmac_md5_ctx ctx; ++ int rc; + +- len = CIFS_CRYPTO_KEY_SIZE + blen; +- construct = kzalloc(len, KSMBD_DEFAULT_GFP); +- if (!construct) { +- rc = -ENOMEM; +- goto out; ++ if (fips_enabled) { ++ ksmbd_debug(AUTH, "NTLMv2 support is disabled due to FIPS\n"); ++ return -EOPNOTSUPP; + } + +- memcpy(construct, cryptkey, CIFS_CRYPTO_KEY_SIZE); +- memcpy(construct + CIFS_CRYPTO_KEY_SIZE, &ntlmv2->blob_signature, blen); +- +- rc = crypto_shash_update(CRYPTO_HMACMD5(ctx), construct, len); +- if (rc) { +- ksmbd_debug(AUTH, "Could not update with response\n"); +- goto out; +- } +- +- rc = crypto_shash_final(CRYPTO_HMACMD5(ctx), ntlmv2_rsp); ++ rc = calc_ntlmv2_hash(conn, sess, ntlmv2_hash, domain_name); + if (rc) { +- ksmbd_debug(AUTH, "Could not generate md5 hash\n"); +- goto out; ++ ksmbd_debug(AUTH, "could not get v2 hash rc %d\n", rc); ++ return rc; + } +- ksmbd_release_crypto_ctx(ctx); +- ctx = NULL; + +- rc = ksmbd_gen_sess_key(sess, ntlmv2_hash, ntlmv2_rsp); +- if (rc) { +- ksmbd_debug(AUTH, "Could not generate sess key\n"); +- goto out; +- } ++ hmac_md5_init_usingrawkey(&ctx, ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE); ++ hmac_md5_update(&ctx, cryptkey, CIFS_CRYPTO_KEY_SIZE); ++ hmac_md5_update(&ctx, (const u8 *)&ntlmv2->blob_signature, blen); ++ hmac_md5_final(&ctx, ntlmv2_rsp); ++ ++ /* Generate the session key */ ++ hmac_md5_usingrawkey(ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE, ++ ntlmv2_rsp, CIFS_HMAC_MD5_HASH_SIZE, ++ sess->sess_key); + + if (crypto_memneq(ntlmv2->ntlmv2_hash, ntlmv2_rsp, + CIFS_HMAC_MD5_HASH_SIZE)) +- rc = -EINVAL; +-out: +- if (ctx) +- ksmbd_release_crypto_ctx(ctx); +- kfree(construct); +- return rc; ++ return -EINVAL; ++ return 0; + } + + /** +--- a/fs/smb/server/crypto_ctx.c ++++ b/fs/smb/server/crypto_ctx.c +@@ -66,9 +66,6 @@ static struct shash_desc *alloc_shash_de + struct shash_desc *shash; + + switch (id) { +- case CRYPTO_SHASH_HMACMD5: +- tfm = crypto_alloc_shash("hmac(md5)", 0, 0); +- break; + case CRYPTO_SHASH_CMACAES: + tfm = crypto_alloc_shash("cmac(aes)", 0, 0); + break; +@@ -174,11 +171,6 @@ static struct ksmbd_crypto_ctx *____cryp + return NULL; + } + +-struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacmd5(void) +-{ +- return ____crypto_shash_ctx_find(CRYPTO_SHASH_HMACMD5); +-} +- + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_cmacaes(void) + { + return ____crypto_shash_ctx_find(CRYPTO_SHASH_CMACAES); +--- a/fs/smb/server/crypto_ctx.h ++++ b/fs/smb/server/crypto_ctx.h +@@ -10,8 +10,7 @@ + #include + + enum { +- CRYPTO_SHASH_HMACMD5 = 0, +- CRYPTO_SHASH_CMACAES, ++ CRYPTO_SHASH_CMACAES = 0, + CRYPTO_SHASH_MAX, + }; + +@@ -33,17 +32,14 @@ struct ksmbd_crypto_ctx { + struct crypto_aead *ccmaes[CRYPTO_AEAD_MAX]; + }; + +-#define CRYPTO_HMACMD5(c) ((c)->desc[CRYPTO_SHASH_HMACMD5]) + #define CRYPTO_CMACAES(c) ((c)->desc[CRYPTO_SHASH_CMACAES]) + +-#define CRYPTO_HMACMD5_TFM(c) ((c)->desc[CRYPTO_SHASH_HMACMD5]->tfm) + #define CRYPTO_CMACAES_TFM(c) ((c)->desc[CRYPTO_SHASH_CMACAES]->tfm) + + #define CRYPTO_GCM(c) ((c)->ccmaes[CRYPTO_AEAD_AES_GCM]) + #define CRYPTO_CCM(c) ((c)->ccmaes[CRYPTO_AEAD_AES_CCM]) + + void ksmbd_release_crypto_ctx(struct ksmbd_crypto_ctx *ctx); +-struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacmd5(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_cmacaes(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_gcm(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_ccm(void); +--- a/fs/smb/server/server.c ++++ b/fs/smb/server/server.c +@@ -634,8 +634,6 @@ MODULE_AUTHOR("Namjae Jeon +Date: Thu, 16 Jul 2026 07:21:45 -0400 +Subject: ksmbd: Use HMAC-SHA256 library for message signing and key generation +To: stable@vger.kernel.org +Cc: Eric Biggers , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716112147.1735816-2-sashal@kernel.org> + +From: Eric Biggers + +[ Upstream commit 924067ef183bd17f39d790752190f99982524598 ] + +Convert ksmbd_sign_smb2_pdu() and generate_key() to use the HMAC-SHA256 +library instead of a "hmac(sha256)" crypto_shash. This is simpler and +faster. With the library there's no need to allocate memory, no need to +handle errors, and the HMAC-SHA256 code is accessed directly without +inefficient indirect calls and other unnecessary API overhead. + +Signed-off-by: Eric Biggers +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Stable-dep-of: 954d196bebb2 ("ksmbd: validate NTLMv2 response before updating session key") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/Kconfig | 1 + fs/smb/server/auth.c | 180 ++++++++++----------------------------------- + fs/smb/server/auth.h | 10 +- + fs/smb/server/crypto_ctx.c | 8 -- + fs/smb/server/crypto_ctx.h | 5 - + fs/smb/server/server.c | 1 + fs/smb/server/smb2pdu.c | 25 +----- + fs/smb/server/smb_common.h | 2 + 8 files changed, 54 insertions(+), 178 deletions(-) + +--- a/fs/smb/server/Kconfig ++++ b/fs/smb/server/Kconfig +@@ -15,7 +15,6 @@ config SMB_SERVER + select CRYPTO_LIB_SHA256 + select CRYPTO_LIB_SHA512 + select CRYPTO_LIB_UTILS +- select CRYPTO_SHA256 + select CRYPTO_CMAC + select CRYPTO_AEAD2 + select CRYPTO_CCM +--- a/fs/smb/server/auth.c ++++ b/fs/smb/server/auth.c +@@ -592,46 +592,16 @@ int ksmbd_krb5_authenticate(struct ksmbd + * @sig: signature value generated for client request packet + * + */ +-int ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov, +- int n_vec, char *sig) ++void ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov, ++ int n_vec, char *sig) + { +- struct ksmbd_crypto_ctx *ctx; +- int rc, i; ++ struct hmac_sha256_ctx ctx; ++ int i; + +- ctx = ksmbd_crypto_ctx_find_hmacsha256(); +- if (!ctx) { +- ksmbd_debug(AUTH, "could not crypto alloc hmacmd5\n"); +- return -ENOMEM; +- } +- +- rc = crypto_shash_setkey(CRYPTO_HMACSHA256_TFM(ctx), +- key, +- SMB2_NTLMV2_SESSKEY_SIZE); +- if (rc) +- goto out; +- +- rc = crypto_shash_init(CRYPTO_HMACSHA256(ctx)); +- if (rc) { +- ksmbd_debug(AUTH, "hmacsha256 init error %d\n", rc); +- goto out; +- } +- +- for (i = 0; i < n_vec; i++) { +- rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), +- iov[i].iov_base, +- iov[i].iov_len); +- if (rc) { +- ksmbd_debug(AUTH, "hmacsha256 update error %d\n", rc); +- goto out; +- } +- } +- +- rc = crypto_shash_final(CRYPTO_HMACSHA256(ctx), sig); +- if (rc) +- ksmbd_debug(AUTH, "hmacsha256 generation error %d\n", rc); +-out: +- ksmbd_release_crypto_ctx(ctx); +- return rc; ++ hmac_sha256_init_usingrawkey(&ctx, key, SMB2_NTLMV2_SESSKEY_SIZE); ++ for (i = 0; i < n_vec; i++) ++ hmac_sha256_update(&ctx, iov[i].iov_base, iov[i].iov_len); ++ hmac_sha256_final(&ctx, sig); + } + + /** +@@ -691,98 +661,39 @@ struct derivation { + bool binding; + }; + +-static int generate_key(struct ksmbd_conn *conn, struct ksmbd_session *sess, +- struct kvec label, struct kvec context, __u8 *key, +- unsigned int key_size) ++static void generate_key(struct ksmbd_conn *conn, struct ksmbd_session *sess, ++ struct kvec label, struct kvec context, __u8 *key, ++ unsigned int key_size) + { + unsigned char zero = 0x0; + __u8 i[4] = {0, 0, 0, 1}; + __u8 L128[4] = {0, 0, 0, 128}; + __u8 L256[4] = {0, 0, 1, 0}; +- int rc; + unsigned char prfhash[SMB2_HMACSHA256_SIZE]; +- unsigned char *hashptr = prfhash; +- struct ksmbd_crypto_ctx *ctx; +- +- memset(prfhash, 0x0, SMB2_HMACSHA256_SIZE); +- memset(key, 0x0, key_size); +- +- ctx = ksmbd_crypto_ctx_find_hmacsha256(); +- if (!ctx) { +- ksmbd_debug(AUTH, "could not crypto alloc hmacmd5\n"); +- return -ENOMEM; +- } ++ struct hmac_sha256_ctx ctx; + +- rc = crypto_shash_setkey(CRYPTO_HMACSHA256_TFM(ctx), +- sess->sess_key, +- SMB2_NTLMV2_SESSKEY_SIZE); +- if (rc) +- goto smb3signkey_ret; +- +- rc = crypto_shash_init(CRYPTO_HMACSHA256(ctx)); +- if (rc) { +- ksmbd_debug(AUTH, "hmacsha256 init error %d\n", rc); +- goto smb3signkey_ret; +- } +- +- rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), i, 4); +- if (rc) { +- ksmbd_debug(AUTH, "could not update with n\n"); +- goto smb3signkey_ret; +- } +- +- rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), +- label.iov_base, +- label.iov_len); +- if (rc) { +- ksmbd_debug(AUTH, "could not update with label\n"); +- goto smb3signkey_ret; +- } +- +- rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), &zero, 1); +- if (rc) { +- ksmbd_debug(AUTH, "could not update with zero\n"); +- goto smb3signkey_ret; +- } +- +- rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), +- context.iov_base, +- context.iov_len); +- if (rc) { +- ksmbd_debug(AUTH, "could not update with context\n"); +- goto smb3signkey_ret; +- } ++ hmac_sha256_init_usingrawkey(&ctx, sess->sess_key, ++ SMB2_NTLMV2_SESSKEY_SIZE); ++ hmac_sha256_update(&ctx, i, 4); ++ hmac_sha256_update(&ctx, label.iov_base, label.iov_len); ++ hmac_sha256_update(&ctx, &zero, 1); ++ hmac_sha256_update(&ctx, context.iov_base, context.iov_len); + + if (key_size == SMB3_ENC_DEC_KEY_SIZE && + (conn->cipher_type == SMB2_ENCRYPTION_AES256_CCM || + conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) +- rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), L256, 4); ++ hmac_sha256_update(&ctx, L256, 4); + else +- rc = crypto_shash_update(CRYPTO_HMACSHA256(ctx), L128, 4); +- if (rc) { +- ksmbd_debug(AUTH, "could not update with L\n"); +- goto smb3signkey_ret; +- } +- +- rc = crypto_shash_final(CRYPTO_HMACSHA256(ctx), hashptr); +- if (rc) { +- ksmbd_debug(AUTH, "Could not generate hmacmd5 hash error %d\n", +- rc); +- goto smb3signkey_ret; +- } ++ hmac_sha256_update(&ctx, L128, 4); + +- memcpy(key, hashptr, key_size); +- +-smb3signkey_ret: +- ksmbd_release_crypto_ctx(ctx); +- return rc; ++ hmac_sha256_final(&ctx, prfhash); ++ memcpy(key, prfhash, key_size); + } + + static int generate_smb3signingkey(struct ksmbd_session *sess, + struct ksmbd_conn *conn, + const struct derivation *signing) + { +- int rc; + struct channel *chann; + char *key; + +@@ -795,10 +706,8 @@ static int generate_smb3signingkey(struc + else + key = sess->smb3signingkey; + +- rc = generate_key(conn, sess, signing->label, signing->context, key, +- SMB3_SIGN_KEY_SIZE); +- if (rc) +- return rc; ++ generate_key(conn, sess, signing->label, signing->context, key, ++ SMB3_SIGN_KEY_SIZE); + + if (!(conn->dialect >= SMB30_PROT_ID && signing->binding)) + memcpy(chann->smb3signingkey, key, SMB3_SIGN_KEY_SIZE); +@@ -850,32 +759,25 @@ struct derivation_twin { + struct derivation decryption; + }; + +-static int generate_smb3encryptionkey(struct ksmbd_conn *conn, +- struct ksmbd_session *sess, +- const struct derivation_twin *ptwin) +-{ +- int rc; +- +- rc = generate_key(conn, sess, ptwin->encryption.label, +- ptwin->encryption.context, sess->smb3encryptionkey, +- SMB3_ENC_DEC_KEY_SIZE); +- if (rc) +- return rc; +- +- rc = generate_key(conn, sess, ptwin->decryption.label, +- ptwin->decryption.context, +- sess->smb3decryptionkey, SMB3_ENC_DEC_KEY_SIZE); +- if (rc) +- return rc; ++static void generate_smb3encryptionkey(struct ksmbd_conn *conn, ++ struct ksmbd_session *sess, ++ const struct derivation_twin *ptwin) ++{ ++ generate_key(conn, sess, ptwin->encryption.label, ++ ptwin->encryption.context, sess->smb3encryptionkey, ++ SMB3_ENC_DEC_KEY_SIZE); ++ ++ generate_key(conn, sess, ptwin->decryption.label, ++ ptwin->decryption.context, ++ sess->smb3decryptionkey, SMB3_ENC_DEC_KEY_SIZE); + + ksmbd_debug(AUTH, "generated SMB3 encryption/decryption keys\n"); + ksmbd_debug(AUTH, "Cipher type %d\n", conn->cipher_type); + ksmbd_debug(AUTH, "Session Id %llu\n", sess->id); +- return 0; + } + +-int ksmbd_gen_smb30_encryptionkey(struct ksmbd_conn *conn, +- struct ksmbd_session *sess) ++void ksmbd_gen_smb30_encryptionkey(struct ksmbd_conn *conn, ++ struct ksmbd_session *sess) + { + struct derivation_twin twin; + struct derivation *d; +@@ -892,11 +794,11 @@ int ksmbd_gen_smb30_encryptionkey(struct + d->context.iov_base = "ServerIn "; + d->context.iov_len = 10; + +- return generate_smb3encryptionkey(conn, sess, &twin); ++ generate_smb3encryptionkey(conn, sess, &twin); + } + +-int ksmbd_gen_smb311_encryptionkey(struct ksmbd_conn *conn, +- struct ksmbd_session *sess) ++void ksmbd_gen_smb311_encryptionkey(struct ksmbd_conn *conn, ++ struct ksmbd_session *sess) + { + struct derivation_twin twin; + struct derivation *d; +@@ -913,7 +815,7 @@ int ksmbd_gen_smb311_encryptionkey(struc + d->context.iov_base = sess->Preauth_HashValue; + d->context.iov_len = 64; + +- return generate_smb3encryptionkey(conn, sess, &twin); ++ generate_smb3encryptionkey(conn, sess, &twin); + } + + int ksmbd_gen_preauth_integrity_hash(struct ksmbd_conn *conn, char *buf, +--- a/fs/smb/server/auth.h ++++ b/fs/smb/server/auth.h +@@ -52,18 +52,18 @@ ksmbd_build_ntlmssp_challenge_blob(struc + struct ksmbd_conn *conn); + int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob, + int in_len, char *out_blob, int *out_len); +-int ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov, +- int n_vec, char *sig); ++void ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov, ++ int n_vec, char *sig); + int ksmbd_sign_smb3_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov, + int n_vec, char *sig); + int ksmbd_gen_smb30_signingkey(struct ksmbd_session *sess, + struct ksmbd_conn *conn); + int ksmbd_gen_smb311_signingkey(struct ksmbd_session *sess, + struct ksmbd_conn *conn); +-int ksmbd_gen_smb30_encryptionkey(struct ksmbd_conn *conn, +- struct ksmbd_session *sess); +-int ksmbd_gen_smb311_encryptionkey(struct ksmbd_conn *conn, ++void ksmbd_gen_smb30_encryptionkey(struct ksmbd_conn *conn, + struct ksmbd_session *sess); ++void ksmbd_gen_smb311_encryptionkey(struct ksmbd_conn *conn, ++ struct ksmbd_session *sess); + int ksmbd_gen_preauth_integrity_hash(struct ksmbd_conn *conn, char *buf, + __u8 *pi_hash); + #endif +--- a/fs/smb/server/crypto_ctx.c ++++ b/fs/smb/server/crypto_ctx.c +@@ -69,9 +69,6 @@ static struct shash_desc *alloc_shash_de + case CRYPTO_SHASH_HMACMD5: + tfm = crypto_alloc_shash("hmac(md5)", 0, 0); + break; +- case CRYPTO_SHASH_HMACSHA256: +- tfm = crypto_alloc_shash("hmac(sha256)", 0, 0); +- break; + case CRYPTO_SHASH_CMACAES: + tfm = crypto_alloc_shash("cmac(aes)", 0, 0); + break; +@@ -182,11 +179,6 @@ struct ksmbd_crypto_ctx *ksmbd_crypto_ct + return ____crypto_shash_ctx_find(CRYPTO_SHASH_HMACMD5); + } + +-struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacsha256(void) +-{ +- return ____crypto_shash_ctx_find(CRYPTO_SHASH_HMACSHA256); +-} +- + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_cmacaes(void) + { + return ____crypto_shash_ctx_find(CRYPTO_SHASH_CMACAES); +--- a/fs/smb/server/crypto_ctx.h ++++ b/fs/smb/server/crypto_ctx.h +@@ -11,7 +11,6 @@ + + enum { + CRYPTO_SHASH_HMACMD5 = 0, +- CRYPTO_SHASH_HMACSHA256, + CRYPTO_SHASH_CMACAES, + CRYPTO_SHASH_MAX, + }; +@@ -35,12 +34,9 @@ struct ksmbd_crypto_ctx { + }; + + #define CRYPTO_HMACMD5(c) ((c)->desc[CRYPTO_SHASH_HMACMD5]) +-#define CRYPTO_HMACSHA256(c) ((c)->desc[CRYPTO_SHASH_HMACSHA256]) + #define CRYPTO_CMACAES(c) ((c)->desc[CRYPTO_SHASH_CMACAES]) + + #define CRYPTO_HMACMD5_TFM(c) ((c)->desc[CRYPTO_SHASH_HMACMD5]->tfm) +-#define CRYPTO_HMACSHA256_TFM(c)\ +- ((c)->desc[CRYPTO_SHASH_HMACSHA256]->tfm) + #define CRYPTO_CMACAES_TFM(c) ((c)->desc[CRYPTO_SHASH_CMACAES]->tfm) + + #define CRYPTO_GCM(c) ((c)->ccmaes[CRYPTO_AEAD_AES_GCM]) +@@ -48,7 +44,6 @@ struct ksmbd_crypto_ctx { + + void ksmbd_release_crypto_ctx(struct ksmbd_crypto_ctx *ctx); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacmd5(void); +-struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacsha256(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_cmacaes(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_gcm(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_ccm(void); +--- a/fs/smb/server/server.c ++++ b/fs/smb/server/server.c +@@ -639,7 +639,6 @@ MODULE_SOFTDEP("pre: md5"); + MODULE_SOFTDEP("pre: nls"); + MODULE_SOFTDEP("pre: aes"); + MODULE_SOFTDEP("pre: cmac"); +-MODULE_SOFTDEP("pre: sha256"); + MODULE_SOFTDEP("pre: aead2"); + MODULE_SOFTDEP("pre: ccm"); + MODULE_SOFTDEP("pre: gcm"); +--- a/fs/smb/server/smb2pdu.c ++++ b/fs/smb/server/smb2pdu.c +@@ -1552,12 +1552,7 @@ static int ntlm_authenticate(struct ksmb + + if (smb3_encryption_negotiated(conn) && + !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) { +- rc = conn->ops->generate_encryptionkey(conn, sess); +- if (rc) { +- ksmbd_debug(SMB, +- "SMB3 encryption key generation failed\n"); +- return -EINVAL; +- } ++ conn->ops->generate_encryptionkey(conn, sess); + sess->enc = true; + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION) + rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE; +@@ -1656,12 +1651,7 @@ static int krb5_authenticate(struct ksmb + + if (smb3_encryption_negotiated(conn) && + !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) { +- retval = conn->ops->generate_encryptionkey(conn, sess); +- if (retval) { +- ksmbd_debug(SMB, +- "SMB3 encryption key generation failed\n"); +- return -EINVAL; +- } ++ conn->ops->generate_encryptionkey(conn, sess); + sess->enc = true; + if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION) + rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE; +@@ -9077,9 +9067,8 @@ int smb2_check_sign_req(struct ksmbd_wor + iov[0].iov_base = (char *)&hdr->ProtocolId; + iov[0].iov_len = len; + +- if (ksmbd_sign_smb2_pdu(work->conn, work->sess->sess_key, iov, 1, +- signature)) +- return 0; ++ ksmbd_sign_smb2_pdu(work->conn, work->sess->sess_key, iov, 1, ++ signature); + + if (crypto_memneq(signature, signature_req, SMB2_SIGNATURE_SIZE)) { + pr_err("bad smb2 signature\n"); +@@ -9112,9 +9101,9 @@ void smb2_set_sign_rsp(struct ksmbd_work + iov = &work->iov[work->iov_idx]; + } + +- if (!ksmbd_sign_smb2_pdu(work->conn, work->sess->sess_key, iov, n_vec, +- signature)) +- memcpy(hdr->Signature, signature, SMB2_SIGNATURE_SIZE); ++ ksmbd_sign_smb2_pdu(work->conn, work->sess->sess_key, iov, n_vec, ++ signature); ++ memcpy(hdr->Signature, signature, SMB2_SIGNATURE_SIZE); + } + + /** +--- a/fs/smb/server/smb_common.h ++++ b/fs/smb/server/smb_common.h +@@ -373,7 +373,7 @@ struct smb_version_ops { + int (*check_sign_req)(struct ksmbd_work *work); + void (*set_sign_rsp)(struct ksmbd_work *work); + int (*generate_signingkey)(struct ksmbd_session *sess, struct ksmbd_conn *conn); +- int (*generate_encryptionkey)(struct ksmbd_conn *conn, struct ksmbd_session *sess); ++ void (*generate_encryptionkey)(struct ksmbd_conn *conn, struct ksmbd_session *sess); + bool (*is_transform_hdr)(void *buf); + int (*decrypt_req)(struct ksmbd_work *work); + int (*encrypt_resp)(struct ksmbd_work *work); diff --git a/queue-6.18/ksmbd-use-opener-credentials-for-fsctl-mutations.patch b/queue-6.18/ksmbd-use-opener-credentials-for-fsctl-mutations.patch new file mode 100644 index 0000000000..ee9bbcb1fa --- /dev/null +++ b/queue-6.18/ksmbd-use-opener-credentials-for-fsctl-mutations.patch @@ -0,0 +1,86 @@ +From stable+bounces-275228-greg=kroah.com@vger.kernel.org Thu Jul 16 12:23:59 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 06:18:19 -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: <20260716101819.1461638-3-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 +@@ -8273,6 +8273,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, +@@ -8281,9 +8282,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 +@@ -995,15 +995,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.18/ksmbd-use-sha-512-library-for-smb3.1.1-preauth-hash.patch b/queue-6.18/ksmbd-use-sha-512-library-for-smb3.1.1-preauth-hash.patch new file mode 100644 index 0000000000..5f66f7bd6c --- /dev/null +++ b/queue-6.18/ksmbd-use-sha-512-library-for-smb3.1.1-preauth-hash.patch @@ -0,0 +1,177 @@ +From stable+bounces-275251-greg=kroah.com@vger.kernel.org Thu Jul 16 13:22:06 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:21:44 -0400 +Subject: ksmbd: Use SHA-512 library for SMB3.1.1 preauth hash +To: stable@vger.kernel.org +Cc: Eric Biggers , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716112147.1735816-1-sashal@kernel.org> + +From: Eric Biggers + +[ Upstream commit e009cb1e3093977c5b96a1c6697a7dc9332222d5 ] + +Convert ksmbd_gen_preauth_integrity_hash() to use the SHA-512 library +instead of a "sha512" crypto_shash. This is simpler and faster. With +the library there's no need to allocate memory, no need to handle +errors, and the SHA-512 code is accessed directly without inefficient +indirect calls and other unnecessary API overhead. + +Signed-off-by: Eric Biggers +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Stable-dep-of: 954d196bebb2 ("ksmbd: validate NTLMv2 response before updating session key") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/Kconfig | 2 +- + fs/smb/server/auth.c | 41 +++++++---------------------------------- + fs/smb/server/crypto_ctx.c | 8 -------- + fs/smb/server/crypto_ctx.h | 4 ---- + fs/smb/server/server.c | 1 - + 5 files changed, 8 insertions(+), 48 deletions(-) + +--- a/fs/smb/server/Kconfig ++++ b/fs/smb/server/Kconfig +@@ -13,10 +13,10 @@ config SMB_SERVER + select CRYPTO_LIB_ARC4 + select CRYPTO_LIB_DES + select CRYPTO_LIB_SHA256 ++ select CRYPTO_LIB_SHA512 + select CRYPTO_LIB_UTILS + select CRYPTO_SHA256 + select CRYPTO_CMAC +- select CRYPTO_SHA512 + select CRYPTO_AEAD2 + select CRYPTO_CCM + select CRYPTO_GCM +--- a/fs/smb/server/auth.c ++++ b/fs/smb/server/auth.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -918,48 +919,20 @@ int ksmbd_gen_smb311_encryptionkey(struc + int ksmbd_gen_preauth_integrity_hash(struct ksmbd_conn *conn, char *buf, + __u8 *pi_hash) + { +- int rc; + struct smb2_hdr *rcv_hdr = smb2_get_msg(buf); + char *all_bytes_msg = (char *)&rcv_hdr->ProtocolId; + int msg_size = get_rfc1002_len(buf); +- struct ksmbd_crypto_ctx *ctx = NULL; ++ struct sha512_ctx sha_ctx; + + if (conn->preauth_info->Preauth_HashId != + SMB2_PREAUTH_INTEGRITY_SHA512) + return -EINVAL; + +- ctx = ksmbd_crypto_ctx_find_sha512(); +- if (!ctx) { +- ksmbd_debug(AUTH, "could not alloc sha512\n"); +- return -ENOMEM; +- } +- +- rc = crypto_shash_init(CRYPTO_SHA512(ctx)); +- if (rc) { +- ksmbd_debug(AUTH, "could not init shashn"); +- goto out; +- } +- +- rc = crypto_shash_update(CRYPTO_SHA512(ctx), pi_hash, 64); +- if (rc) { +- ksmbd_debug(AUTH, "could not update with n\n"); +- goto out; +- } +- +- rc = crypto_shash_update(CRYPTO_SHA512(ctx), all_bytes_msg, msg_size); +- if (rc) { +- ksmbd_debug(AUTH, "could not update with n\n"); +- goto out; +- } +- +- rc = crypto_shash_final(CRYPTO_SHA512(ctx), pi_hash); +- if (rc) { +- ksmbd_debug(AUTH, "Could not generate hash err : %d\n", rc); +- goto out; +- } +-out: +- ksmbd_release_crypto_ctx(ctx); +- return rc; ++ sha512_init(&sha_ctx); ++ sha512_update(&sha_ctx, pi_hash, 64); ++ sha512_update(&sha_ctx, all_bytes_msg, msg_size); ++ sha512_final(&sha_ctx, pi_hash); ++ return 0; + } + + static int ksmbd_get_encryption_key(struct ksmbd_work *work, __u64 ses_id, +--- a/fs/smb/server/crypto_ctx.c ++++ b/fs/smb/server/crypto_ctx.c +@@ -75,9 +75,6 @@ static struct shash_desc *alloc_shash_de + case CRYPTO_SHASH_CMACAES: + tfm = crypto_alloc_shash("cmac(aes)", 0, 0); + break; +- case CRYPTO_SHASH_SHA512: +- tfm = crypto_alloc_shash("sha512", 0, 0); +- break; + default: + return NULL; + } +@@ -195,11 +192,6 @@ struct ksmbd_crypto_ctx *ksmbd_crypto_ct + return ____crypto_shash_ctx_find(CRYPTO_SHASH_CMACAES); + } + +-struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_sha512(void) +-{ +- return ____crypto_shash_ctx_find(CRYPTO_SHASH_SHA512); +-} +- + static struct ksmbd_crypto_ctx *____crypto_aead_ctx_find(int id) + { + struct ksmbd_crypto_ctx *ctx; +--- a/fs/smb/server/crypto_ctx.h ++++ b/fs/smb/server/crypto_ctx.h +@@ -13,7 +13,6 @@ enum { + CRYPTO_SHASH_HMACMD5 = 0, + CRYPTO_SHASH_HMACSHA256, + CRYPTO_SHASH_CMACAES, +- CRYPTO_SHASH_SHA512, + CRYPTO_SHASH_MAX, + }; + +@@ -38,13 +37,11 @@ struct ksmbd_crypto_ctx { + #define CRYPTO_HMACMD5(c) ((c)->desc[CRYPTO_SHASH_HMACMD5]) + #define CRYPTO_HMACSHA256(c) ((c)->desc[CRYPTO_SHASH_HMACSHA256]) + #define CRYPTO_CMACAES(c) ((c)->desc[CRYPTO_SHASH_CMACAES]) +-#define CRYPTO_SHA512(c) ((c)->desc[CRYPTO_SHASH_SHA512]) + + #define CRYPTO_HMACMD5_TFM(c) ((c)->desc[CRYPTO_SHASH_HMACMD5]->tfm) + #define CRYPTO_HMACSHA256_TFM(c)\ + ((c)->desc[CRYPTO_SHASH_HMACSHA256]->tfm) + #define CRYPTO_CMACAES_TFM(c) ((c)->desc[CRYPTO_SHASH_CMACAES]->tfm) +-#define CRYPTO_SHA512_TFM(c) ((c)->desc[CRYPTO_SHASH_SHA512]->tfm) + + #define CRYPTO_GCM(c) ((c)->ccmaes[CRYPTO_AEAD_AES_GCM]) + #define CRYPTO_CCM(c) ((c)->ccmaes[CRYPTO_AEAD_AES_CCM]) +@@ -53,7 +50,6 @@ void ksmbd_release_crypto_ctx(struct ksm + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacmd5(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_hmacsha256(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_cmacaes(void); +-struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_sha512(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_gcm(void); + struct ksmbd_crypto_ctx *ksmbd_crypto_ctx_find_ccm(void); + void ksmbd_crypto_destroy(void); +--- a/fs/smb/server/server.c ++++ b/fs/smb/server/server.c +@@ -640,7 +640,6 @@ MODULE_SOFTDEP("pre: nls"); + MODULE_SOFTDEP("pre: aes"); + MODULE_SOFTDEP("pre: cmac"); + MODULE_SOFTDEP("pre: sha256"); +-MODULE_SOFTDEP("pre: sha512"); + MODULE_SOFTDEP("pre: aead2"); + MODULE_SOFTDEP("pre: ccm"); + MODULE_SOFTDEP("pre: gcm"); diff --git a/queue-6.18/ksmbd-validate-ntlmv2-response-before-updating-session-key.patch b/queue-6.18/ksmbd-validate-ntlmv2-response-before-updating-session-key.patch new file mode 100644 index 0000000000..6b99b10060 --- /dev/null +++ b/queue-6.18/ksmbd-validate-ntlmv2-response-before-updating-session-key.patch @@ -0,0 +1,100 @@ +From stable+bounces-275254-greg=kroah.com@vger.kernel.org Thu Jul 16 13:22:31 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 07:21:47 -0400 +Subject: ksmbd: validate NTLMv2 response before updating session key +To: stable@vger.kernel.org +Cc: Haofeng Li , ChenXiaoSong , Namjae Jeon , Steve French , Sasha Levin +Message-ID: <20260716112147.1735816-4-sashal@kernel.org> + +From: Haofeng Li + +[ Upstream commit 954d196bebb2b50151cb96454c72dc113b2af1ac ] + +ksmbd_auth_ntlmv2() derives the NTLMv2 session key into +sess->sess_key before it verifies the NTLMv2 response. +ksmbd_decode_ntlmssp_auth_blob() then continues into KEY_XCH even +when ksmbd_auth_ntlmv2() failed. + +With SMB3 multichannel binding, the failed authentication operates on +an existing session and the session setup error path does not expire +binding sessions. A client can send a binding session setup with a +bad NT proof and KEY_XCH and still modify sess->sess_key before +STATUS_LOGON_FAILURE is returned. + +Relevant path: + + smb2_sess_setup() + -> conn->binding = true + -> ntlm_authenticate() + -> session_user() + -> ksmbd_decode_ntlmssp_auth_blob() + -> ksmbd_auth_ntlmv2() + -> calc_ntlmv2_hash() + -> hmac_md5_usingrawkey(..., sess->sess_key) + -> crypto_memneq() returns mismatch + -> KEY_XCH arc4_crypt(..., sess->sess_key, ...) + -> out_err without expiring the binding session + +Derive the base session key into a local buffer and copy it to +sess->sess_key only after the proof matches. Return immediately on +authentication failure so KEY_XCH is only processed after successful +authentication. + +Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") +Fixes: f9929ef6a2a5 ("ksmbd: add support for key exchange") +Cc: stable@vger.kernel.org +Signed-off-by: Haofeng Li +Reviewed-by: ChenXiaoSong +Acked-by: Namjae Jeon +Signed-off-by: Steve French +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/smb/server/auth.c | 20 ++++++++++++++++---- + 1 file changed, 16 insertions(+), 4 deletions(-) + +--- a/fs/smb/server/auth.c ++++ b/fs/smb/server/auth.c +@@ -142,6 +142,7 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn + { + char ntlmv2_hash[CIFS_ENCPWD_SIZE]; + char ntlmv2_rsp[CIFS_HMAC_MD5_HASH_SIZE]; ++ char sess_key[SMB2_NTLMV2_SESSKEY_SIZE]; + struct hmac_md5_ctx ctx; + int rc; + +@@ -164,12 +165,21 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn + /* Generate the session key */ + hmac_md5_usingrawkey(ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE, + ntlmv2_rsp, CIFS_HMAC_MD5_HASH_SIZE, +- sess->sess_key); ++ sess_key); + + if (crypto_memneq(ntlmv2->ntlmv2_hash, ntlmv2_rsp, +- CIFS_HMAC_MD5_HASH_SIZE)) +- return -EINVAL; +- return 0; ++ CIFS_HMAC_MD5_HASH_SIZE)) { ++ rc = -EINVAL; ++ goto out; ++ } ++ ++ memcpy(sess->sess_key, sess_key, sizeof(sess_key)); ++ rc = 0; ++out: ++ memzero_explicit(ntlmv2_hash, sizeof(ntlmv2_hash)); ++ memzero_explicit(ntlmv2_rsp, sizeof(ntlmv2_rsp)); ++ memzero_explicit(sess_key, sizeof(sess_key)); ++ return rc; + } + + /** +@@ -226,6 +236,8 @@ int ksmbd_decode_ntlmssp_auth_blob(struc + nt_len - CIFS_ENCPWD_SIZE, + domain_name, conn->ntlmssp.cryptkey); + kfree(domain_name); ++ if (ret) ++ return ret; + + /* The recovered secondary session key */ + if (conn->ntlmssp.client_flags & NTLMSSP_NEGOTIATE_KEY_XCH) { diff --git a/queue-6.18/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch b/queue-6.18/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch new file mode 100644 index 0000000000..a959944fff --- /dev/null +++ b/queue-6.18/media-nxp-imx8-isi-fix-use-after-free-on-remove.patch @@ -0,0 +1,73 @@ +From stable+bounces-274914-greg=kroah.com@vger.kernel.org Wed Jul 15 13:44:01 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 07:40:28 -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: <20260715114028.727360-2-sashal@kernel.org> + +From: Xiaolei Wang + +[ Upstream commit b670bf89824ede5d07d20bb9bfbafb754846081d ] + +KASAN reports a slab-use-after-free in __media_entity_remove_link() +during rmmod of imx8_isi: + + BUG: KASAN: slab-use-after-free in __media_entity_remove_link+0x608/0x650 + Read of size 2 at addr ffff0000d47cb02a by task rmmod/724 + + Call trace: + __media_entity_remove_link+0x608/0x650 + __media_entity_remove_links+0x78/0x144 + __media_device_unregister_entity+0x150/0x280 + media_device_unregister_entity+0x48/0x68 + v4l2_device_unregister_subdev+0x158/0x300 + v4l2_async_unbind_subdev_one+0x22c/0x358 + v4l2_async_nf_unbind_all_subdevs+0xfc/0x1c0 + v4l2_async_nf_unregister+0x5c/0x14c + mxc_isi_remove+0x124/0x2a0 [imx8_isi] + + Allocated by task 249: + __kmalloc_noprof+0x27c/0x690 + mxc_isi_crossbar_init+0x22c/0x560 [imx8_isi] + + Freed by task 724: + kfree+0x1e4/0x5b0 + mxc_isi_crossbar_cleanup+0x34/0x80 [imx8_isi] + mxc_isi_remove+0x11c/0x2a0 [imx8_isi] + +The problem is that mxc_isi_remove() calls mxc_isi_crossbar_cleanup() +before mxc_isi_v4l2_cleanup(). The crossbar cleanup frees the media +entity pads, but the subsequent v4l2 cleanup still tries to remove +media links that reference those pads. + +Fix this by calling mxc_isi_v4l2_cleanup() before +mxc_isi_crossbar_cleanup() to ensure all media entities are properly +unregistered while the pads are still valid. + +Fixes: cf21f328fcaf ("media: nxp: Add i.MX8 ISI driver") +Cc: stable@vger.kernel.org +Signed-off-by: Xiaolei Wang +Reviewed-by: Frank Li +Reviewed-by: Laurent Pinchart +Link: https://patch.msgid.link/20260507041318.491594-2-xiaolei.wang@windriver.com +Signed-off-by: Laurent Pinchart +Signed-off-by: Hans Verkuil +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c ++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +@@ -535,8 +535,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.18/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch b/queue-6.18/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch new file mode 100644 index 0000000000..50ff517c4e --- /dev/null +++ b/queue-6.18/media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch @@ -0,0 +1,70 @@ +From stable+bounces-274913-greg=kroah.com@vger.kernel.org Wed Jul 15 13:43:57 2026 +From: Sasha Levin +Date: Wed, 15 Jul 2026 07:40:27 -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: <20260715114028.727360-1-sashal@kernel.org> + +From: Frank Li + +[ Upstream commit 078161dd44d6f848a62a473206d69025607736ec ] + +Use devm_pm_runtime_enable() to simplify code. Change to use +dev_err_probe() because previous goto change to return. + +No functional change. + +Signed-off-by: Frank Li +Reviewed-by: Laurent Pinchart +Link: https://patch.msgid.link/20260116-cam_cleanup-v4-2-29ce01640443@nxp.com +Signed-off-by: Laurent Pinchart +Signed-off-by: Hans Verkuil +Stable-dep-of: b670bf89824e ("media: nxp: imx8-isi: Fix use-after-free on remove") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c | 16 +++++++--------- + 1 file changed, 7 insertions(+), 9 deletions(-) + +--- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c ++++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +@@ -488,13 +488,14 @@ static int mxc_isi_probe(struct platform + dma_size = isi->pdata->has_36bit_dma ? 36 : 32; + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size)); + +- 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); +@@ -517,8 +518,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; + } + +@@ -537,8 +537,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.18/new-helper-simple_remove_by_name.patch b/queue-6.18/new-helper-simple_remove_by_name.patch new file mode 100644 index 0000000000..20b7681c44 --- /dev/null +++ b/queue-6.18/new-helper-simple_remove_by_name.patch @@ -0,0 +1,85 @@ +From stable+bounces-277541-greg=kroah.com@vger.kernel.org Mon Jul 20 04:36:03 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 22:25:46 -0400 +Subject: new helper: simple_remove_by_name() +To: stable@vger.kernel.org +Cc: Al Viro , Miklos Szeredi , Sasha Levin +Message-ID: <20260720023553.2928009-1-sashal@kernel.org> + +From: Al Viro + +[ Upstream commit 4051a9115ad24bb9a691774730ca9c1dd56de665 ] + +simple_recursive_removal(), but instead of victim dentry it takes +parent + name. + +Used to be open-coded in fs/fuse/control.c, but there's no need to expose +the guts of that thing there and there are other potential users, so +let's lift it into libfs... + +Acked-by: Miklos Szeredi +Signed-off-by: Al Viro +Stable-dep-of: 3137b243c939 ("usb: gadget: f_fs: initialize reset_work at allocation time") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/fuse/control.c | 7 +------ + fs/libfs.c | 13 +++++++++++++ + include/linux/fs.h | 2 ++ + 3 files changed, 16 insertions(+), 6 deletions(-) + +--- a/fs/fuse/control.c ++++ b/fs/fuse/control.c +@@ -290,18 +290,13 @@ static void remove_one(struct dentry *de + */ + void fuse_ctl_remove_conn(struct fuse_conn *fc) + { +- struct dentry *dentry; + char name[32]; + + if (!fuse_control_sb || fc->no_control) + return; + + sprintf(name, "%u", fc->dev); +- dentry = lookup_noperm_positive_unlocked(&QSTR(name), fuse_control_sb->s_root); +- if (!IS_ERR(dentry)) { +- simple_recursive_removal(dentry, remove_one); +- dput(dentry); // paired with lookup_noperm_positive_unlocked() +- } ++ simple_remove_by_name(fuse_control_sb->s_root, name, remove_one); + } + + static int fuse_ctl_fill_super(struct super_block *sb, struct fs_context *fsc) +--- a/fs/libfs.c ++++ b/fs/libfs.c +@@ -647,6 +647,19 @@ void simple_recursive_removal(struct den + } + EXPORT_SYMBOL(simple_recursive_removal); + ++void simple_remove_by_name(struct dentry *parent, const char *name, ++ void (*callback)(struct dentry *)) ++{ ++ struct dentry *dentry; ++ ++ dentry = lookup_noperm_positive_unlocked(&QSTR(name), parent); ++ if (!IS_ERR(dentry)) { ++ simple_recursive_removal(dentry, callback); ++ dput(dentry); // paired with lookup_noperm_positive_unlocked() ++ } ++} ++EXPORT_SYMBOL(simple_remove_by_name); ++ + /* caller holds parent directory with I_MUTEX_PARENT */ + void locked_recursive_removal(struct dentry *dentry, + void (*callback)(struct dentry *)) +--- a/include/linux/fs.h ++++ b/include/linux/fs.h +@@ -3655,6 +3655,8 @@ extern int simple_rename(struct mnt_idma + unsigned int); + extern void simple_recursive_removal(struct dentry *, + void (*callback)(struct dentry *)); ++extern void simple_remove_by_name(struct dentry *, const char *, ++ void (*callback)(struct dentry *)); + extern void locked_recursive_removal(struct dentry *, + void (*callback)(struct dentry *)); + extern int noop_fsync(struct file *, loff_t, loff_t, int); diff --git a/queue-6.18/pci-add-kerneldoc-for-pci_resize_resource.patch b/queue-6.18/pci-add-kerneldoc-for-pci_resize_resource.patch new file mode 100644 index 0000000000..01e560c46b --- /dev/null +++ b/queue-6.18/pci-add-kerneldoc-for-pci_resize_resource.patch @@ -0,0 +1,55 @@ +From stable+bounces-274549-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:10 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:34 -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: <20260714200236.3153778-6-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 +@@ -457,6 +457,26 @@ void pci_resize_resource_set_size(struct + resource_set_size(res, res_size); + } + ++/** ++ * 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.18/pci-change-pci_dev-variable-from-bridge-to-dev.patch b/queue-6.18/pci-change-pci_dev-variable-from-bridge-to-dev.patch new file mode 100644 index 0000000000..e19b1aa75f --- /dev/null +++ b/queue-6.18/pci-change-pci_dev-variable-from-bridge-to-dev.patch @@ -0,0 +1,82 @@ +From stable+bounces-274544-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:55 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:30 -0400 +Subject: PCI: Change pci_dev variable from 'bridge' to 'dev' +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Alex Bennée" , "Sasha Levin" +Message-ID: <20260714200236.3153778-2-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit 34c702ea0497e092a487eacdf28a037f43e3e39f ] + +Upcoming fix to BAR resize will store also device BAR resource in the +saved list. Change the pci_dev variable in the loop from 'bridge' to +'dev' as the former would be misleading with non-bridges in the list. + +This is in a separate change to reduce churn in the upcoming BAR resize +fix. + +While it appears that the logic in the loop doing pci_setup_bridge() is +altered as 'bridge' variable is no longer updated, a bridge should never +appear more than once in the saved list so the check can only match to the +first entry. As such, the code with two distinct pci_dev variables better +represents the intention of the check compared with the old code where +bridge variable was reused for a different purpose. + +Signed-off-by: Ilpo Järvinen +Signed-off-by: Bjorn Helgaas +Tested-by: Alex Bennée # AVA, AMD GPU +Reviewed-by: Alex Bennée +Link: https://patch.msgid.link/20251113162628.5946-4-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 | 17 +++++++++-------- + 1 file changed, 9 insertions(+), 8 deletions(-) + +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2431,12 +2431,13 @@ int pbus_reassign_bridge_resources(struc + } + + list_for_each_entry(dev_res, &saved, list) { ++ struct pci_dev *dev = dev_res->dev; ++ + /* Skip the bridge we just assigned resources for */ +- if (bridge == dev_res->dev) ++ if (bridge == dev) + continue; + +- bridge = dev_res->dev; +- pci_setup_bridge(bridge->subordinate); ++ pci_setup_bridge(dev->subordinate); + } + + free_list(&saved); +@@ -2452,19 +2453,19 @@ cleanup: + /* 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 = pci_resource_num(bridge, res); ++ i = pci_resource_num(dev, res); + + if (res->parent) { + release_child_resources(res); +- pci_release_resource(bridge, i); ++ pci_release_resource(dev, i); + } + + restore_dev_resource(dev_res); + +- pci_claim_resource(bridge, i); +- pci_setup_bridge(bridge->subordinate); ++ pci_claim_resource(dev, i); ++ pci_setup_bridge(dev->subordinate); + } + free_list(&saved); + up_read(&pci_bus_sem); diff --git a/queue-6.18/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch b/queue-6.18/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch new file mode 100644 index 0000000000..1b088e152a --- /dev/null +++ b/queue-6.18/pci-fix-restoring-bars-on-bar-resize-rollback-path.patch @@ -0,0 +1,393 @@ +From stable+bounces-274550-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:21 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:33 -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: <20260714200236.3153778-5-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit 337b1b566db087347194e4543ddfdfa5645275cc ] + +BAR resize operation is implemented in the pci_resize_resource() and +pbus_reassign_bridge_resources() functions. pci_resize_resource() can be +called either from __resource_resize_store() from sysfs or directly by the +driver for the Endpoint Device. + +The pci_resize_resource() requires that caller has released the device +resources that share the bridge window with the BAR to be resized as +otherwise the bridge window is pinned in place and cannot be changed. + +pbus_reassign_bridge_resources() rolls back resources if the resize +operation fails, but rollback is performed only for the bridge windows. +Because releasing the device resources are done by the caller of the BAR +resize interface, these functions performing the BAR resize do not have +access to the device resources as they were before the resize. + +pbus_reassign_bridge_resources() could try __pci_bridge_assign_resources() +after rolling back the bridge windows as they were, however, it will not +guarantee the resource are assigned due to differences in how FW and the +kernel assign the resources (alignment of the start address and tail). + +To perform rollback robustly, the BAR resize interface has to be altered to +also release the device resources that share the bridge window with the BAR +to be resized. + +Also, remove restoring from the entries failed list as saved list should +now contain both the bridge windows and device resources so the extra +restore is duplicated work. + +Some drivers (currently only amdgpu) want to prevent releasing some +resources. Add exclude_bars param to pci_resize_resource() and make amdgpu +pass its register BAR (BAR 2 or 5), which should never be released during +resize operation. Normally 64-bit prefetchable resources do not share a +bridge window with the 32-bit only register BAR, but there are various +fallbacks in the resource assignment logic which may make the resources +share the bridge window in rare cases. + +This change (together with the driver side changes) is to counter the +resource releases that had to be done to prevent resource tree corruption +in the ("PCI: Release assigned resource before restoring them") change. As +such, it likely restores functionality in cases where device resources were +released to avoid resource tree conflicts which appeared to be "working" +when such conflicts were not correctly detected by the kernel. + +Reported-by: Simon Richter +Link: https://lore.kernel.org/linux-pci/f9a8c975-f5d3-4dd2-988e-4371a1433a60@hogyros.de/ +Reported-by: Alex Bennée +Link: https://lore.kernel.org/linux-pci/874irqop6b.fsf@draig.linaro.org/ +Signed-off-by: Ilpo Järvinen +[bhelgaas: squash amdgpu BAR selection from +https: //lore.kernel.org/r/20251114103053.13778-1-ilpo.jarvinen@linux.intel.com] +Signed-off-by: Bjorn Helgaas +Tested-by: Alex Bennée # AVA, AMD GPU +Reviewed-by: Christian König +Link: https://patch.msgid.link/20251113162628.5946-7-ilpo.jarvinen@linux.intel.com +Stable-dep-of: ee7471fe968d ("PCI: Skip Resizable BAR restore on read error") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 4 - + drivers/gpu/drm/i915/gt/intel_region_lmem.c | 2 + drivers/gpu/drm/xe/xe_vram.c | 2 + drivers/pci/pci-sysfs.c | 17 ---- + drivers/pci/pci.h | 4 - + drivers/pci/setup-bus.c | 100 +++++++++++++++++++++------- + drivers/pci/setup-res.c | 23 +----- + include/linux/pci.h | 3 + 8 files changed, 93 insertions(+), 62 deletions(-) + +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +@@ -1736,7 +1736,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) + dev_info(adev->dev, + "Not enough PCI address space for a large BAR."); +--- a/drivers/gpu/drm/i915/gt/intel_region_lmem.c ++++ b/drivers/gpu/drm/i915/gt/intel_region_lmem.c +@@ -37,7 +37,7 @@ _resize_bar(struct drm_i915_private *i91 + + _release_bars(pdev); + +- ret = pci_resize_resource(pdev, resno, bar_size); ++ ret = pci_resize_resource(pdev, resno, bar_size, 0); + if (ret) { + drm_info(&i915->drm, "Failed to resize BAR%d to %dM (%pe)\n", + resno, 1 << bar_size, ERR_PTR(ret)); +--- a/drivers/gpu/drm/xe/xe_vram.c ++++ b/drivers/gpu/drm/xe/xe_vram.c +@@ -56,7 +56,7 @@ static void resize_bar(struct xe_device + + release_bars(pdev); + +- ret = pci_resize_resource(pdev, resno, bar_size); ++ ret = pci_resize_resource(pdev, resno, bar_size, 0); + if (ret) { + drm_info(&xe->drm, "Failed to resize BAR%d to %dM (%pe). Consider enabling 'Resizable BAR' support in your BIOS\n", + resno, 1 << bar_size, ERR_PTR(ret)); +--- a/drivers/pci/pci-sysfs.c ++++ b/drivers/pci/pci-sysfs.c +@@ -1571,18 +1571,13 @@ static ssize_t __resource_resize_store(s + { + struct pci_dev *pdev = to_pci_dev(dev); + struct pci_bus *bus = pdev->bus; +- struct resource *b_win, *res; + unsigned long size; +- int ret, i; ++ int ret; + u16 cmd; + + if (kstrtoul(buf, 0, &size) < 0) + return -EINVAL; + +- b_win = pbus_select_window(bus, pci_resource_n(pdev, n)); +- if (!b_win) +- return -EINVAL; +- + device_lock(dev); + if (dev->driver || pci_num_vf(pdev)) { + ret = -EBUSY; +@@ -1604,15 +1599,7 @@ static ssize_t __resource_resize_store(s + + pci_remove_resource_files(pdev); + +- pci_dev_for_each_resource(pdev, res, i) { +- if (i >= PCI_BRIDGE_RESOURCES) +- break; +- +- if (b_win == pbus_select_window(bus, res)) +- pci_release_resource(pdev, i); +- } +- +- ret = pci_resize_resource(pdev, n, size); ++ ret = pci_resize_resource(pdev, n, size, 0); + + pci_assign_unassigned_bus_resources(bus); + +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -439,8 +439,10 @@ enum pci_bar_type { + struct device *pci_get_host_bridge_device(struct pci_dev *dev); + void pci_put_host_bridge_device(struct device *dev); + ++void pci_resize_resource_set_size(struct pci_dev *dev, int resno, int size); ++int pci_do_resource_release_and_resize(struct pci_dev *dev, int resno, int size, ++ int exclude_bars); + unsigned int pci_rescan_bus_bridge_resize(struct pci_dev *bridge); +-int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res); + int __must_check pci_reassign_resource(struct pci_dev *dev, int i, resource_size_t add_size, resource_size_t align); + + int pci_configure_extended_tags(struct pci_dev *dev, void *ign); +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2372,18 +2372,16 @@ EXPORT_SYMBOL_GPL(pci_assign_unassigned_ + * release it when possible. If the bridge window contains assigned + * resources, it cannot be released. + */ +-int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res) ++static int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *res, ++ struct list_head *saved) + { + unsigned long type = res->flags; + struct pci_dev_resource *dev_res; + struct pci_dev *bridge = NULL; +- LIST_HEAD(saved); + LIST_HEAD(added); + LIST_HEAD(failed); + unsigned int i; +- int ret; +- +- down_read(&pci_bus_sem); ++ int ret = 0; + + while (!pci_is_root_bus(bus)) { + bridge = bus->self; +@@ -2395,9 +2393,9 @@ int pbus_reassign_bridge_resources(struc + + /* Ignore BARs which are still in use */ + if (!res->child) { +- 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_release_resource(bridge, i); + } else { +@@ -2420,34 +2418,78 @@ int pbus_reassign_bridge_resources(struc + free_list(&added); + + if (!list_empty(&failed)) { +- if (pci_required_resource_failed(&failed, type)) { ++ if (pci_required_resource_failed(&failed, type)) + ret = -ENOSPC; +- goto cleanup; +- } +- /* Only resources with unrelated types failed (again) */ + free_list(&failed); ++ if (ret) ++ return ret; ++ ++ /* Only resources with unrelated types failed (again) */ + } + +- list_for_each_entry(dev_res, &saved, list) { ++ list_for_each_entry(dev_res, saved, list) { + struct pci_dev *dev = dev_res->dev; + + /* Skip the bridge we just assigned resources for */ + if (bridge == dev) + continue; + ++ if (!dev->subordinate) ++ continue; ++ + pci_setup_bridge(dev->subordinate); + } + +- free_list(&saved); +- up_read(&pci_bus_sem); + return 0; ++} + +-cleanup: +- /* Restore size and flags */ +- list_for_each_entry(dev_res, &failed, list) +- restore_dev_resource(dev_res); +- free_list(&failed); ++int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size, ++ int exclude_bars) ++{ ++ struct resource *res = pci_resource_n(pdev, resno); ++ struct pci_dev_resource *dev_res; ++ struct pci_bus *bus = pdev->bus; ++ struct resource *b_win, *r; ++ LIST_HEAD(saved); ++ unsigned int i; ++ int ret = 0; ++ ++ b_win = pbus_select_window(bus, res); ++ if (!b_win) ++ return -EINVAL; ++ ++ pci_dev_for_each_resource(pdev, r, i) { ++ if (i >= PCI_BRIDGE_RESOURCES) ++ break; ++ ++ if (exclude_bars & BIT(i)) ++ continue; + ++ if (b_win != pbus_select_window(bus, r)) ++ continue; ++ ++ ret = add_to_list(&saved, pdev, r, 0, 0); ++ if (ret) ++ goto restore; ++ pci_release_resource(pdev, i); ++ } ++ ++ pci_resize_resource_set_size(pdev, resno, size); ++ ++ if (!bus->self) ++ goto out; ++ ++ down_read(&pci_bus_sem); ++ ret = pbus_reassign_bridge_resources(bus, res, &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; +@@ -2462,13 +2504,21 @@ cleanup: + + restore_dev_resource(dev_res); + +- pci_claim_resource(dev, i); +- pci_setup_bridge(dev->subordinate); +- } +- up_read(&pci_bus_sem); +- free_list(&saved); ++ ret = pci_claim_resource(dev, i); ++ if (ret) ++ 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 +@@ -446,8 +446,7 @@ static bool pci_resize_is_memory_decodin + return cmd & PCI_COMMAND_MEMORY; + } + +-static void pci_resize_resource_set_size(struct pci_dev *dev, int resno, +- int size) ++void pci_resize_resource_set_size(struct pci_dev *dev, int resno, int size) + { + resource_size_t res_size = pci_rebar_size_to_bytes(size); + struct resource *res = pci_resource_n(dev, resno); +@@ -458,9 +457,9 @@ static void pci_resize_resource_set_size + resource_set_size(res, res_size); + } + +-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 = pci_resource_n(dev, resno); + struct pci_host_bridge *host; + int old, ret; + u32 sizes; +@@ -470,10 +469,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; +- + if (pci_resize_is_memory_decoding_enabled(dev, resno)) + return -EBUSY; + +@@ -492,19 +487,13 @@ int pci_resize_resource(struct pci_dev * + if (ret) + return ret; + +- pci_resize_resource_set_size(dev, resno, size); +- +- /* Check if the new config works by trying to assign everything. */ +- if (dev->bus->self) { +- ret = pbus_reassign_bridge_resources(dev->bus, res); +- if (ret) +- goto error_resize; +- } ++ 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); +- pci_resize_resource_set_size(dev, resno, old); + return ret; + } + EXPORT_SYMBOL(pci_resize_resource); +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -1425,7 +1425,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); diff --git a/queue-6.18/pci-free-saved-list-without-holding-pci_bus_sem.patch b/queue-6.18/pci-free-saved-list-without-holding-pci_bus_sem.patch new file mode 100644 index 0000000000..b4c3d73566 --- /dev/null +++ b/queue-6.18/pci-free-saved-list-without-holding-pci_bus_sem.patch @@ -0,0 +1,38 @@ +From stable+bounces-274551-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:58 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:32 -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: <20260714200236.3153778-4-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 +@@ -2465,8 +2465,8 @@ cleanup: + pci_claim_resource(dev, i); + pci_setup_bridge(dev->subordinate); + } +- free_list(&saved); + up_read(&pci_bus_sem); ++ free_list(&saved); + + return ret; + } diff --git a/queue-6.18/pci-imx6-configure-ref_use_pad-before-phy-reset-for-i.mx95.patch b/queue-6.18/pci-imx6-configure-ref_use_pad-before-phy-reset-for-i.mx95.patch new file mode 100644 index 0000000000..251d5f1d12 --- /dev/null +++ b/queue-6.18/pci-imx6-configure-ref_use_pad-before-phy-reset-for-i.mx95.patch @@ -0,0 +1,103 @@ +From stable+bounces-274494-greg=kroah.com@vger.kernel.org Tue Jul 14 19:55:34 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 13:49:21 -0400 +Subject: PCI: imx6: Configure REF_USE_PAD before PHY reset for i.MX95 +To: stable@vger.kernel.org +Cc: Richard Zhu , Manivannan Sadhasivam , Sasha Levin +Message-ID: <20260714174921.3041761-2-sashal@kernel.org> + +From: Richard Zhu + +[ Upstream commit 0c26b1c34d12d4debfb5363cc0be6cdf68e87ba2 ] + +According to the i.MX95 PCIe PHY Databook, the ref_use_pad signal in the +Common Block Signals section selects the reference clock source connected +to the PHY pads. Per the specification, any change to this input must be +followed by a PHY reset assertion to take effect. + +Move the REF_USE_PAD configuration before the PHY reset toggle to comply +with the required initialization sequence. + +Fixes: 47f54a902dcd ("PCI: imx6: Toggle the core reset for i.MX95 PCIe") +Signed-off-by: Richard Zhu +[mani: renamed the callback and helper to match the usecase] +Signed-off-by: Manivannan Sadhasivam +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260518072715.3166514-2-hongxing.zhu@nxp.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/dwc/pci-imx6.c | 27 ++++++++++++++++++++++++--- + 1 file changed, 24 insertions(+), 3 deletions(-) + +--- a/drivers/pci/controller/dwc/pci-imx6.c ++++ b/drivers/pci/controller/dwc/pci-imx6.c +@@ -135,6 +135,7 @@ struct imx_pcie_drvdata { + const u32 mode_off[IMX_PCIE_MAX_INSTANCES]; + const u32 mode_mask[IMX_PCIE_MAX_INSTANCES]; + const struct pci_epc_features *epc_features; ++ int (*select_ref_clk_src)(struct imx_pcie *pcie); + int (*init_phy)(struct imx_pcie *pcie); + int (*enable_ref_clk)(struct imx_pcie *pcie, bool enable); + int (*core_reset)(struct imx_pcie *pcie, bool assert); +@@ -243,6 +244,24 @@ static unsigned int imx_pcie_grp_offset( + return imx_pcie->controller_id == 1 ? IOMUXC_GPR16 : IOMUXC_GPR14; + } + ++static int imx95_pcie_select_ref_clk_src(struct imx_pcie *imx_pcie) ++{ ++ bool ext = imx_pcie->enable_ext_refclk; ++ ++ /* ++ * Regarding the Signal Descriptions of i.MX95 PCIe PHY, ref_use_pad is ++ * used to select reference clock connected to a pair of pads. ++ * ++ * Any change in this input must be followed by phy_reset assertion. ++ */ ++ ++ regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL, ++ IMX95_PCIE_REF_USE_PAD, ++ ext ? IMX95_PCIE_REF_USE_PAD : 0); ++ ++ return 0; ++} ++ + static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) + { + bool ext = imx_pcie->enable_ext_refclk; +@@ -265,9 +284,6 @@ static int imx95_pcie_init_phy(struct im + IMX95_PCIE_PHY_CR_PARA_SEL, + IMX95_PCIE_PHY_CR_PARA_SEL); + +- regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL, +- IMX95_PCIE_REF_USE_PAD, +- ext ? IMX95_PCIE_REF_USE_PAD : 0); + regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0, + IMX95_PCIE_REF_CLKEN, + ext ? 0 : IMX95_PCIE_REF_CLKEN); +@@ -1232,6 +1248,9 @@ static int imx_pcie_host_init(struct dw_ + pp->bridge->disable_device = imx_pcie_disable_device; + } + ++ if (imx_pcie->drvdata->select_ref_clk_src) ++ imx_pcie->drvdata->select_ref_clk_src(imx_pcie); ++ + imx_pcie_assert_core_reset(imx_pcie); + + if (imx_pcie->drvdata->init_phy) +@@ -1932,6 +1951,7 @@ static const struct imx_pcie_drvdata drv + .mode_mask[0] = IMX95_PCIE_DEVICE_TYPE, + .core_reset = imx95_pcie_core_reset, + .init_phy = imx95_pcie_init_phy, ++ .select_ref_clk_src = imx95_pcie_select_ref_clk_src, + .wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock, + .enable_ref_clk = imx95_pcie_enable_ref_clk, + }, +@@ -1986,6 +2006,7 @@ static const struct imx_pcie_drvdata drv + .ltssm_mask = IMX95_PCIE_LTSSM_EN, + .mode_off[0] = IMX95_PE0_GEN_CTRL_1, + .mode_mask[0] = IMX95_PCIE_DEVICE_TYPE, ++ .select_ref_clk_src = imx95_pcie_select_ref_clk_src, + .init_phy = imx95_pcie_init_phy, + .core_reset = imx95_pcie_core_reset, + .wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock, diff --git a/queue-6.18/pci-imx6-fix-reference-clock-source-selection-for-i.mx95.patch b/queue-6.18/pci-imx6-fix-reference-clock-source-selection-for-i.mx95.patch new file mode 100644 index 0000000000..46ec561af0 --- /dev/null +++ b/queue-6.18/pci-imx6-fix-reference-clock-source-selection-for-i.mx95.patch @@ -0,0 +1,71 @@ +From stable+bounces-274491-greg=kroah.com@vger.kernel.org Tue Jul 14 19:55:28 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 13:49:20 -0400 +Subject: PCI: imx6: Fix reference clock source selection for i.MX95 +To: stable@vger.kernel.org +Cc: Franz Schnyder , Manivannan Sadhasivam , Bjorn Helgaas , Richard Zhu , Sasha Levin +Message-ID: <20260714174921.3041761-1-sashal@kernel.org> + +From: Franz Schnyder + +[ Upstream commit 88cc4cbe08bba27bb58888d25d336774aa0ccab1 ] + +In the PCIe PHY init for the i.MX95, the reference clock source selection +uses a conditional instead of always passing the mask. This currently +breaks functionality if the internal refclk is used. + +To fix this issue, always pass IMX95_PCIE_REF_USE_PAD as the mask and clear +bit if external refclk is not used. This essentially swaps the parameters. + +Fixes: d8574ce57d76 ("PCI: imx6: Add external reference clock input mode support") +Signed-off-by: Franz Schnyder +Signed-off-by: Manivannan Sadhasivam +Signed-off-by: Bjorn Helgaas +Acked-by: Richard Zhu +Cc: stable@vger.kernel.org +Link: https://patch.msgid.link/20260325093118.684142-1-fra.schnyder@gmail.com +Stable-dep-of: 0c26b1c34d12 ("PCI: imx6: Configure REF_USE_PAD before PHY reset for i.MX95") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/pci/controller/dwc/pci-imx6.c | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +--- a/drivers/pci/controller/dwc/pci-imx6.c ++++ b/drivers/pci/controller/dwc/pci-imx6.c +@@ -150,6 +150,7 @@ struct imx_lut_data { + struct imx_pcie { + struct dw_pcie *pci; + struct gpio_desc *reset_gpiod; ++ bool enable_ext_refclk; + struct clk_bulk_data *clks; + int num_clks; + struct regmap *iomuxc_gpr; +@@ -244,6 +245,8 @@ static unsigned int imx_pcie_grp_offset( + + static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) + { ++ bool ext = imx_pcie->enable_ext_refclk; ++ + /* + * ERR051624: The Controller Without Vaux Cannot Exit L23 Ready + * Through Beacon or PERST# De-assertion +@@ -262,13 +265,12 @@ static int imx95_pcie_init_phy(struct im + IMX95_PCIE_PHY_CR_PARA_SEL, + IMX95_PCIE_PHY_CR_PARA_SEL); + +- regmap_update_bits(imx_pcie->iomuxc_gpr, +- IMX95_PCIE_PHY_GEN_CTRL, +- IMX95_PCIE_REF_USE_PAD, 0); +- regmap_update_bits(imx_pcie->iomuxc_gpr, +- IMX95_PCIE_SS_RW_REG_0, ++ regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL, ++ IMX95_PCIE_REF_USE_PAD, ++ ext ? IMX95_PCIE_REF_USE_PAD : 0); ++ regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0, + IMX95_PCIE_REF_CLKEN, +- IMX95_PCIE_REF_CLKEN); ++ ext ? 0 : IMX95_PCIE_REF_CLKEN); + + return 0; + } diff --git a/queue-6.18/pci-iov-adjust-barsz-when-changing-bar-size.patch b/queue-6.18/pci-iov-adjust-barsz-when-changing-bar-size.patch new file mode 100644 index 0000000000..0199956f5b --- /dev/null +++ b/queue-6.18/pci-iov-adjust-barsz-when-changing-bar-size.patch @@ -0,0 +1,141 @@ +From stable+bounces-274545-greg=kroah.com@vger.kernel.org Tue Jul 14 22:03:46 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:29 -0400 +Subject: PCI/IOV: Adjust ->barsz[] when changing BAR size +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Alex Bennée" , "Sasha Levin" +Message-ID: <20260714200236.3153778-1-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit 4687b3315a3f76647746f5b7f92684cf1045b085 ] + +pci_rebar_set_size() adjusts BAR size for both normal and IOV BARs. The +struct pci_sriov keeps a cached copy of BAR size in ->barsz[] which is not +adjusted by pci_rebar_set_size() but by pci_iov_resource_set_size(). +pci_iov_resource_set_size() is called also from +pci_resize_resource_set_size(). + +The current arrangement is problematic once BAR resize algorithm starts to +roll back changes properly in case of a failure. The normal resource +fitting algorithm rolls back resource size using the struct +pci_dev_resource easily but also calling pci_resize_resource_set_size() or +pci_iov_resource_set_size() to roll back BAR size would be an extra burden, +whereas combining ->barsz[] update with pci_rebar_set_size() naturally +rolls back it when restoring the old BAR size on a different layer of the +BAR resize operation. + +Thus, rework pci_rebar_set_size() to also update ->barsz[]. + +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-3-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/iov.c | 15 ++++----------- + drivers/pci/pci.c | 4 ++++ + drivers/pci/pci.h | 5 ++--- + drivers/pci/setup-res.c | 10 ++++------ + 4 files changed, 14 insertions(+), 20 deletions(-) + +--- a/drivers/pci/iov.c ++++ b/drivers/pci/iov.c +@@ -158,8 +158,7 @@ resource_size_t pci_iov_resource_size(st + return dev->sriov->barsz[pci_resource_num_to_vf_bar(resno)]; + } + +-void pci_iov_resource_set_size(struct pci_dev *dev, int resno, +- resource_size_t size) ++void pci_iov_resource_set_size(struct pci_dev *dev, int resno, int size) + { + if (!pci_resource_is_iov(resno)) { + pci_warn(dev, "%s is not an IOV resource\n", +@@ -167,7 +166,8 @@ void pci_iov_resource_set_size(struct pc + return; + } + +- dev->sriov->barsz[pci_resource_num_to_vf_bar(resno)] = size; ++ resno = pci_resource_num_to_vf_bar(resno); ++ dev->sriov->barsz[resno] = pci_rebar_size_to_bytes(size); + } + + bool pci_iov_is_memory_decoding_enabled(struct pci_dev *dev) +@@ -1345,7 +1345,6 @@ EXPORT_SYMBOL_GPL(pci_sriov_configure_si + int pci_iov_vf_bar_set_size(struct pci_dev *dev, int resno, int size) + { + u32 sizes; +- int ret; + + if (!pci_resource_is_iov(resno)) + return -EINVAL; +@@ -1360,13 +1359,7 @@ int pci_iov_vf_bar_set_size(struct pci_d + if (!(sizes & BIT(size))) + return -EINVAL; + +- ret = pci_rebar_set_size(dev, resno, size); +- if (ret) +- return ret; +- +- pci_iov_resource_set_size(dev, resno, pci_rebar_size_to_bytes(size)); +- +- return 0; ++ return pci_rebar_set_size(dev, resno, size); + } + EXPORT_SYMBOL_GPL(pci_iov_vf_bar_set_size); + +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -3797,6 +3797,10 @@ int pci_rebar_set_size(struct pci_dev *p + ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; + ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); + pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); ++ ++ if (pci_resource_is_iov(bar)) ++ pci_iov_resource_set_size(pdev, bar, size); ++ + return 0; + } + +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -826,8 +826,7 @@ void pci_iov_update_resource(struct pci_ + resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno); + void pci_restore_iov_state(struct pci_dev *dev); + int pci_iov_bus_range(struct pci_bus *bus); +-void pci_iov_resource_set_size(struct pci_dev *dev, int resno, +- resource_size_t size); ++void pci_iov_resource_set_size(struct pci_dev *dev, int resno, int size); + bool pci_iov_is_memory_decoding_enabled(struct pci_dev *dev); + static inline u16 pci_iov_vf_rebar_cap(struct pci_dev *dev) + { +@@ -869,7 +868,7 @@ static inline int pci_iov_bus_range(stru + return 0; + } + static inline void pci_iov_resource_set_size(struct pci_dev *dev, int resno, +- resource_size_t size) { } ++ int size) { } + static inline bool pci_iov_is_memory_decoding_enabled(struct pci_dev *dev) + { + return false; +--- a/drivers/pci/setup-res.c ++++ b/drivers/pci/setup-res.c +@@ -452,12 +452,10 @@ static void pci_resize_resource_set_size + resource_size_t res_size = pci_rebar_size_to_bytes(size); + struct resource *res = pci_resource_n(dev, resno); + +- if (!pci_resource_is_iov(resno)) { +- resource_set_size(res, res_size); +- } else { +- resource_set_size(res, res_size * pci_sriov_get_totalvfs(dev)); +- pci_iov_resource_set_size(dev, resno, res_size); +- } ++ if (pci_resource_is_iov(resno)) ++ res_size *= pci_sriov_get_totalvfs(dev); ++ ++ resource_set_size(res, res_size); + } + + int pci_resize_resource(struct pci_dev *dev, int resno, int size) diff --git a/queue-6.18/pci-move-resizable-bar-code-to-rebar.c.patch b/queue-6.18/pci-move-resizable-bar-code-to-rebar.c.patch new file mode 100644 index 0000000000..277975a9e6 --- /dev/null +++ b/queue-6.18/pci-move-resizable-bar-code-to-rebar.c.patch @@ -0,0 +1,579 @@ +From stable+bounces-274553-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:33 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:35 -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: <20260714200236.3153778-7-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 | 149 --------------------- + drivers/pci/pci.h | 1 + drivers/pci/rebar.c | 247 +++++++++++++++++++++++++++++++++++ + drivers/pci/setup-res.c | 85 ------------ + 6 files changed, 252 insertions(+), 235 deletions(-) + create mode 100644 drivers/pci/rebar.c + +--- a/Documentation/driver-api/pci/pci.rst ++++ b/Documentation/driver-api/pci/pci.rst +@@ -37,6 +37,9 @@ PCI Support Library + .. kernel-doc:: drivers/pci/slot.c + :export: + ++.. kernel-doc:: drivers/pci/rebar.c ++ :export: ++ + .. kernel-doc:: drivers/pci/rom.c + :export: + +--- a/drivers/pci/Makefile ++++ b/drivers/pci/Makefile +@@ -4,7 +4,7 @@ + + obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \ + remove.o pci.o pci-driver.o search.o \ +- rom.o setup-res.o irq.o vpd.o \ ++ rebar.o rom.o setup-res.o irq.o vpd.o \ + setup-bus.o vc.o mmap.o devres.o + + obj-$(CONFIG_PCI) += msi/ +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -1823,32 +1823,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 = pdev->rebar_cap; +- if (!pos) +- return; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); +- +- for (i = 0; i < nbars; i++, pos += 8) { +- struct resource *res; +- int bar_idx, size; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; +- res = pci_resource_n(pdev, bar_idx); +- size = pci_rebar_bytes_to_size(resource_size(res)); +- ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; +- ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); +- pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); +- } +-} +- + /** + * pci_restore_state - Restore the saved state of a PCI device + * @dev: PCI device that we're dealing with +@@ -3681,129 +3655,6 @@ void pci_acs_init(struct pci_dev *dev) + dev->acs_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS); + } + +-void pci_rebar_init(struct pci_dev *pdev) +-{ +- pdev->rebar_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); +-} +- +-/** +- * 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; +- +- if (pci_resource_is_iov(bar)) { +- pos = pci_iov_vf_rebar_cap(pdev); +- bar = pci_resource_num_to_vf_bar(bar); +- } else { +- pos = pdev->rebar_cap; +- } +- +- if (!pos) +- return -ENOTSUPP; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); +- +- for (i = 0; i < nbars; i++, pos += 8) { +- int bar_idx; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- bar_idx = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, ctrl); +- if (bar_idx == bar) +- return pos; +- } +- +- return -ENOENT; +-} +- +-/** +- * pci_rebar_get_possible_sizes - get possible sizes for BAR +- * @pdev: PCI device +- * @bar: BAR to query +- * +- * Get the possible sizes of a resizable BAR as bitmask defined in the spec +- * (bit 0=1MB, bit 31=128TB). Returns 0 if BAR isn't resizable. +- */ +-u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar) +-{ +- int pos; +- u32 cap; +- +- pos = pci_rebar_find_pos(pdev, bar); +- if (pos < 0) +- return 0; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); +- cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap); +- +- /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */ +- if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f && +- bar == 0 && cap == 0x700) +- return 0x3f00; +- +- return cap; +-} +-EXPORT_SYMBOL(pci_rebar_get_possible_sizes); +- +-/** +- * pci_rebar_get_current_size - get the current size of a BAR +- * @pdev: PCI device +- * @bar: BAR to set size to +- * +- * Read the size of a BAR from the resizable BAR config. +- * Returns size if found or negative error code. +- */ +-int pci_rebar_get_current_size(struct pci_dev *pdev, int bar) +-{ +- int pos; +- u32 ctrl; +- +- pos = pci_rebar_find_pos(pdev, bar); +- if (pos < 0) +- return pos; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- return FIELD_GET(PCI_REBAR_CTRL_BAR_SIZE, ctrl); +-} +- +-/** +- * pci_rebar_set_size - set a new size for a BAR +- * @pdev: PCI device +- * @bar: BAR to set size to +- * @size: new size as defined in the spec (0=1MB, 31=128TB) +- * +- * Set the new size of a BAR as defined in the spec. +- * Returns zero if resizing was successful, error code otherwise. +- */ +-int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size) +-{ +- int pos; +- u32 ctrl; +- +- pos = pci_rebar_find_pos(pdev, bar); +- if (pos < 0) +- return pos; +- +- pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); +- ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; +- ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); +- pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); +- +- if (pci_resource_is_iov(bar)) +- pci_iov_resource_set_size(pdev, bar, size); +- +- return 0; +-} +- + /** + * pci_enable_atomic_ops_to_root - enable AtomicOp requests to root port + * @dev: the PCI device +--- a/drivers/pci/pci.h ++++ b/drivers/pci/pci.h +@@ -1042,6 +1042,7 @@ static inline int acpi_get_rc_resources( + #endif + + void pci_rebar_init(struct pci_dev *pdev); ++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,247 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * PCI Resizable BAR Extended Capability handling. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pci.h" ++ ++void pci_rebar_init(struct pci_dev *pdev) ++{ ++ pdev->rebar_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); ++} ++ ++/** ++ * 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; ++ ++ if (pci_resource_is_iov(bar)) { ++ pos = pci_iov_vf_rebar_cap(pdev); ++ bar = pci_resource_num_to_vf_bar(bar); ++ } else { ++ pos = pdev->rebar_cap; ++ } ++ ++ if (!pos) ++ return -ENOTSUPP; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); ++ ++ for (i = 0; i < nbars; i++, pos += 8) { ++ int bar_idx; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ bar_idx = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, ctrl); ++ if (bar_idx == bar) ++ return pos; ++ } ++ ++ return -ENOENT; ++} ++ ++/** ++ * pci_rebar_get_possible_sizes - get possible sizes for BAR ++ * @pdev: PCI device ++ * @bar: BAR to query ++ * ++ * Get the possible sizes of a resizable BAR as bitmask defined in the spec ++ * (bit 0=1MB, bit 31=128TB). Returns 0 if BAR isn't resizable. ++ */ ++u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar) ++{ ++ int pos; ++ u32 cap; ++ ++ pos = pci_rebar_find_pos(pdev, bar); ++ if (pos < 0) ++ return 0; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); ++ cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap); ++ ++ /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */ ++ if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f && ++ bar == 0 && cap == 0x700) ++ return 0x3f00; ++ ++ return cap; ++} ++EXPORT_SYMBOL(pci_rebar_get_possible_sizes); ++ ++/** ++ * pci_rebar_get_current_size - get the current size of a BAR ++ * @pdev: PCI device ++ * @bar: BAR to set size to ++ * ++ * Read the size of a BAR from the resizable BAR config. ++ * Returns size if found or negative error code. ++ */ ++int pci_rebar_get_current_size(struct pci_dev *pdev, int bar) ++{ ++ int pos; ++ u32 ctrl; ++ ++ pos = pci_rebar_find_pos(pdev, bar); ++ if (pos < 0) ++ return pos; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ return FIELD_GET(PCI_REBAR_CTRL_BAR_SIZE, ctrl); ++} ++ ++/** ++ * pci_rebar_set_size - set a new size for a BAR ++ * @pdev: PCI device ++ * @bar: BAR to set size to ++ * @size: new size as defined in the spec (0=1MB, 31=128TB) ++ * ++ * Set the new size of a BAR as defined in the spec. ++ * Returns zero if resizing was successful, error code otherwise. ++ */ ++int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size) ++{ ++ int pos; ++ u32 ctrl; ++ ++ pos = pci_rebar_find_pos(pdev, bar); ++ if (pos < 0) ++ return pos; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; ++ ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); ++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); ++ ++ if (pci_resource_is_iov(bar)) ++ pci_iov_resource_set_size(pdev, bar, size); ++ ++ return 0; ++} ++ ++void pci_restore_rebar_state(struct pci_dev *pdev) ++{ ++ unsigned int pos, nbars, i; ++ u32 ctrl; ++ ++ pos = pdev->rebar_cap; ++ if (!pos) ++ return; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); ++ ++ for (i = 0; i < nbars; i++, pos += 8) { ++ struct resource *res; ++ int bar_idx, size; ++ ++ pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ++ bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; ++ res = pci_resource_n(pdev, bar_idx); ++ size = pci_rebar_bytes_to_size(resource_size(res)); ++ ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; ++ ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); ++ pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); ++ } ++} ++ ++static bool pci_resize_is_memory_decoding_enabled(struct pci_dev *dev, ++ int resno) ++{ ++ u16 cmd; ++ ++ if (pci_resource_is_iov(resno)) ++ return pci_iov_is_memory_decoding_enabled(dev); ++ ++ pci_read_config_word(dev, PCI_COMMAND, &cmd); ++ ++ return cmd & PCI_COMMAND_MEMORY; ++} ++ ++void pci_resize_resource_set_size(struct pci_dev *dev, int resno, int size) ++{ ++ resource_size_t res_size = pci_rebar_size_to_bytes(size); ++ struct resource *res = pci_resource_n(dev, resno); ++ ++ if (pci_resource_is_iov(resno)) ++ res_size *= pci_sriov_get_totalvfs(dev); ++ ++ resource_set_size(res, res_size); ++} ++ ++/** ++ * 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; ++ ++ /* Check if we must preserve the firmware's resource assignment */ ++ host = pci_find_host_bridge(dev->bus); ++ if (host->preserve_config) ++ return -ENOTSUPP; ++ ++ if (pci_resize_is_memory_decoding_enabled(dev, resno)) ++ 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 +@@ -433,91 +433,6 @@ int pci_release_resource(struct pci_dev + } + EXPORT_SYMBOL(pci_release_resource); + +-static bool pci_resize_is_memory_decoding_enabled(struct pci_dev *dev, +- int resno) +-{ +- u16 cmd; +- +- if (pci_resource_is_iov(resno)) +- return pci_iov_is_memory_decoding_enabled(dev); +- +- pci_read_config_word(dev, PCI_COMMAND, &cmd); +- +- return cmd & PCI_COMMAND_MEMORY; +-} +- +-void pci_resize_resource_set_size(struct pci_dev *dev, int resno, int size) +-{ +- resource_size_t res_size = pci_rebar_size_to_bytes(size); +- struct resource *res = pci_resource_n(dev, resno); +- +- if (pci_resource_is_iov(resno)) +- res_size *= pci_sriov_get_totalvfs(dev); +- +- resource_set_size(res, res_size); +-} +- +-/** +- * 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; +- +- /* Check if we must preserve the firmware's resource assignment */ +- host = pci_find_host_bridge(dev->bus); +- if (host->preserve_config) +- return -ENOTSUPP; +- +- if (pci_resize_is_memory_decoding_enabled(dev, resno)) +- 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.18/pci-skip-resizable-bar-restore-on-read-error.patch b/queue-6.18/pci-skip-resizable-bar-restore-on-read-error.patch new file mode 100644 index 0000000000..db0402ee0a --- /dev/null +++ b/queue-6.18/pci-skip-resizable-bar-restore-on-read-error.patch @@ -0,0 +1,61 @@ +From stable+bounces-274548-greg=kroah.com@vger.kernel.org Tue Jul 14 22:04:11 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:36 -0400 +Subject: PCI: Skip Resizable BAR restore on read error +To: stable@vger.kernel.org +Cc: Marco Nenciarini , Bjorn Helgaas , Sasha Levin +Message-ID: <20260714200236.3153778-8-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 +@@ -145,6 +145,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) { +@@ -152,6 +155,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.18/pci-try-bar-resize-even-when-no-window-was-released.patch b/queue-6.18/pci-try-bar-resize-even-when-no-window-was-released.patch new file mode 100644 index 0000000000..08f53ca661 --- /dev/null +++ b/queue-6.18/pci-try-bar-resize-even-when-no-window-was-released.patch @@ -0,0 +1,55 @@ +From stable+bounces-274546-greg=kroah.com@vger.kernel.org Tue Jul 14 22:02:57 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 16:02:31 -0400 +Subject: PCI: Try BAR resize even when no window was released +To: stable@vger.kernel.org +Cc: "Ilpo Järvinen" , "Bjorn Helgaas" , "Alex Bennée" , "Sasha Levin" +Message-ID: <20260714200236.3153778-3-sashal@kernel.org> + +From: Ilpo Järvinen + +[ Upstream commit 121d3e9e4b217f2f23715901fb15c6a3ca2bab46 ] + +Usually, resizing BARs requires releasing bridge windows in order to +resize it to fit a larger BAR into the window. This is not always the +case, however, FW could have made the window large enough to accommodate +larger BAR as is, or the user might prefer to shrink a BAR to make more +space for another Resizable BAR. + +Thus, replace the check that requires that at least one bridge window +was released with a check that simply ensures bridge is not NULL. + +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-5-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 | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +--- a/drivers/pci/setup-bus.c ++++ b/drivers/pci/setup-bus.c +@@ -2376,7 +2376,7 @@ int pbus_reassign_bridge_resources(struc + { + unsigned long type = res->flags; + struct pci_dev_resource *dev_res; +- struct pci_dev *bridge; ++ struct pci_dev *bridge = NULL; + LIST_HEAD(saved); + LIST_HEAD(added); + LIST_HEAD(failed); +@@ -2411,10 +2411,8 @@ int pbus_reassign_bridge_resources(struc + bus = bus->parent; + } + +- if (list_empty(&saved)) { +- up_read(&pci_bus_sem); ++ if (!bridge) + return -ENOENT; +- } + + __pci_bus_size_bridges(bridge->subordinate, &added); + __pci_bridge_assign_resources(bridge, &added, &failed); diff --git a/queue-6.18/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch b/queue-6.18/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch new file mode 100644 index 0000000000..8913df72ea --- /dev/null +++ b/queue-6.18/proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.patch @@ -0,0 +1,274 @@ +From stable+bounces-276776-greg=kroah.com@vger.kernel.org Thu Jul 16 18:47:46 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 12:45:14 -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: <20260716164514.668766-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) + { +@@ -1777,16 +1751,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); +@@ -1796,26 +1766,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); + } + +@@ -1849,17 +1835,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; + } + +@@ -2250,21 +2230,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 +@@ -171,24 +171,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.18/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch b/queue-6.18/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch new file mode 100644 index 0000000000..1eb6073fb6 --- /dev/null +++ b/queue-6.18/proc-protect-ptrace_may_access-with-exec_update_lock-part-1.patch @@ -0,0 +1,207 @@ +From stable+bounces-277029-greg=kroah.com@vger.kernel.org Fri Jul 17 13:19:01 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 07:18:34 -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: <20260717111835.1574543-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 +@@ -481,6 +481,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; +@@ -656,6 +661,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 */ +@@ -2335,17 +2341,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)) +@@ -2395,23 +2399,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; + +@@ -2437,8 +2440,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; +@@ -2446,7 +2448,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 */ +@@ -2463,6 +2464,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, strlen(name)); + } ++ up_read(&task->signal->exec_update_lock); ++out_put_task: + put_task_struct(task); + return res; + } diff --git a/queue-6.18/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch b/queue-6.18/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch new file mode 100644 index 0000000000..c7d48e0f2e --- /dev/null +++ b/queue-6.18/proc-rename-proc_setattr-to-proc_nochmod_setattr.patch @@ -0,0 +1,204 @@ +From stable+bounces-276777-greg=kroah.com@vger.kernel.org Thu Jul 16 18:47:47 2026 +From: Sasha Levin +Date: Thu, 16 Jul 2026 12:45:13 -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: <20260716164514.668766-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) +@@ -1866,7 +1866,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, + }; + + +@@ -2319,7 +2319,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 * +@@ -2398,7 +2398,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 +@@ -2885,7 +2885,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 +@@ -2944,7 +2944,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 +@@ -3460,7 +3460,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, + }; + +@@ -3659,7 +3659,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, + }; + +@@ -3788,7 +3788,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, +@@ -4001,7 +4001,7 @@ static loff_t proc_dir_llseek(struct fil + static const struct inode_operations proc_task_inode_operations = { + .lookup = proc_task_lookup, + .getattr = proc_task_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + .permission = proc_pid_permission, + }; + +--- a/fs/proc/fd.c ++++ b/fs/proc/fd.c +@@ -102,7 +102,7 @@ static int proc_fdinfo_permission(struct + + static const struct inode_operations proc_fdinfo_file_inode_operations = { + .permission = proc_fdinfo_permission, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static const struct file_operations proc_fdinfo_file_operations = { +@@ -361,7 +361,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, +@@ -402,7 +402,7 @@ static int proc_fdinfo_iterate(struct fi + const struct inode_operations proc_fdinfo_inode_operations = { + .lookup = proc_lookupfdinfo, + .permission = proc_fdinfo_permission, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + const struct file_operations proc_fdinfo_operations = { +--- a/fs/proc/internal.h ++++ b/fs/proc/internal.h +@@ -257,8 +257,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, +@@ -178,5 +178,5 @@ out_no_task: + const struct inode_operations proc_ns_dir_inode_operations = { + .lookup = proc_ns_dir_lookup, + .getattr = pid_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; +--- a/fs/proc/proc_net.c ++++ b/fs/proc/proc_net.c +@@ -322,7 +322,7 @@ static int proc_tgid_net_getattr(struct + const struct inode_operations proc_net_inode_operations = { + .lookup = proc_tgid_net_lookup, + .getattr = proc_tgid_net_getattr, +- .setattr = proc_setattr, ++ .setattr = proc_nochmod_setattr, + }; + + static int proc_tgid_net_readdir(struct file *file, struct dir_context *ctx) diff --git a/queue-6.18/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch b/queue-6.18/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch new file mode 100644 index 0000000000..e455b059cd --- /dev/null +++ b/queue-6.18/seqlock-change-do_task_stat-to-use-scoped_seqlock_read.patch @@ -0,0 +1,56 @@ +From stable+bounces-277028-greg=kroah.com@vger.kernel.org Fri Jul 17 13:18:51 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 07:18:33 -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: <20260717111835.1574543-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 +@@ -481,7 +481,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; +@@ -538,10 +537,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; +@@ -563,8 +559,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.18/seqlock-introduce-scoped_seqlock_read.patch b/queue-6.18/seqlock-introduce-scoped_seqlock_read.patch new file mode 100644 index 0000000000..1e1ae3af8f --- /dev/null +++ b/queue-6.18/seqlock-introduce-scoped_seqlock_read.patch @@ -0,0 +1,164 @@ +From stable+bounces-277027-greg=kroah.com@vger.kernel.org Fri Jul 17 13:42:38 2026 +From: Sasha Levin +Date: Fri, 17 Jul 2026 07:18:32 -0400 +Subject: seqlock: Introduce scoped_seqlock_read() +To: stable@vger.kernel.org +Cc: Peter Zijlstra , Oleg Nesterov , Sasha Levin +Message-ID: <20260717111835.1574543-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 +@@ -1209,4 +1209,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.18/series b/queue-6.18/series index 42ada9ade2..236af89c16 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -1516,3 +1516,79 @@ mmc-sdhci-esdhc-imx-use-pm_runtime_resume_and_get-in-suspend.patch mmc-sdhci-esdhc-imx-make-non-fatal-errors-non-blocking-in-suspend.patch mmc-sdhci-esdhc-imx-fix-resume-error-handling.patch crypto-xilinx-trng-remove-crypto_rng-interface.patch +acpi-nfit-core-fix-acpi_nfit_init-error-cleanup.patch +acpi-driver-check-acpi_companion-against-null-during-probe.patch +acpi-bus-introduce-devm_acpi_install_notify_handler.patch +acpi-nfit-core-use-devm_acpi_install_notify_handler.patch +acpi-nfit-core-fix-possible-deadlock-and-missing-notifications.patch +iio-hid-sensor-rotation-fix-stale-or-zero-output-when-reading-raw-values.patch +alsa-scarlett2-allow-selecting-config_set-by-firmware-version.patch +alsa-scarlett2-update-offsets-for-2i2-gen-4-firmware-2417.patch +firmware_loader-add-cancel-helper-for-async-requests.patch +alsa-hda-tas2781-cancel-async-firmware-request-at-unbind.patch +vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch +binder-use-list_head-to-initialize-on-stack-list-head.patch +binder-cache-secctx-size-before-release-zeroes-it.patch +pci-imx6-fix-reference-clock-source-selection-for-i.mx95.patch +pci-imx6-configure-ref_use_pad-before-phy-reset-for-i.mx95.patch +pci-iov-adjust-barsz-when-changing-bar-size.patch +pci-change-pci_dev-variable-from-bridge-to-dev.patch +pci-try-bar-resize-even-when-no-window-was-released.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 +media-nxp-imx8-isi-use-devm_pm_runtime_enable-to-simplify-code.patch +media-nxp-imx8-isi-fix-use-after-free-on-remove.patch +bluetooth-6lowpan-fix-cyclic-locking-warning-on-netdev-unregister.patch +bluetooth-l2cap-fix-use-after-free-in-l2cap_sock_new_connection_cb.patch +cifs-smb1-split-rename-cifstransport.c.patch +cifs-smb1-split-add-some-includes.patch +ksmbd-use-opener-credentials-for-fsctl-mutations.patch +ksmbd-fix-path-resolution-in-ksmbd_vfs_kern_path_create.patch +ksmbd-centralize-ksmbd_conn-final-release-to-plug-transport-leak.patch +ksmbd-track-the-connection-owning-a-byte-range-lock.patch +ksmbd-use-sha-512-library-for-smb3.1.1-preauth-hash.patch +ksmbd-use-hmac-sha256-library-for-message-signing-and-key-generation.patch +ksmbd-use-hmac-md5-library-for-ntlmv2.patch +ksmbd-validate-ntlmv2-response-before-updating-session-key.patch +proc-rename-proc_setattr-to-proc_nochmod_setattr.patch +proc-protect-ptrace_may_access-with-exec_update_lock-fd-links.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 +hfs-hfsplus-prevent-getting-negative-values-of-offset-length.patch +hfs-hfsplus-fix-u32-overflow-in-check_and_correct_requested_length.patch +xfs-initialize-iomap-flags-earlier-in-xfs_bmbt_to_iomap.patch +bpf-consistently-use-reg_state-for-register-access-in-the-verifier.patch +bpf-introduce-struct-bpf_map_desc-in-verifier.patch +bpf-keep-dynamic-inner-array-lookups-nullable.patch +bpf-consistently-use-bpf_rcu_lock_held-everywhere.patch +bpf-allow-lpm-map-access-from-sleepable-bpf-programs.patch +xfs-add-a-xfs_groups_to_rfsbs-helper.patch +xfs-only-log-freed-extents-for-the-current-rtg-in-zoned-growfs.patch +xfs-use-bio_reuse-in-the-zone-gc-code.patch +usb-dwc3-support-usb3340x-ulpi-phy-high-speed-negotiation.patch +usb-dwc3-fix-dwc3_readl-and-dwc3_writel-calls-in-dwc3_ulpi_setup.patch +usb-atm-ueagle-atm-use-dev_dbg-for-device-found-message.patch +usb-atm-ueagle-atm-remove-function-entry-exit-debug-messages.patch +usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch +new-helper-simple_remove_by_name.patch +functionfs-don-t-abuse-ffs_data_closed-on-fs-shutdown.patch +functionfs-don-t-bother-with-ffs-ref-in-ffs_data_-opened-closed.patch +functionfs-switch-to-simple_remove_by_name.patch +functionfs-use-spinlock-for-ffs_deactivated-ffs_closing-transitions.patch +usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch +usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch +crypto-atmel-sha204a-fail-on-hwrng-registration-error-in-probe-path.patch +btrfs-concentrate-the-error-handling-of-submit_one_sector.patch +btrfs-replace-for_each_set_bit-with-for_each_set_bitmap.patch +btrfs-remove-folio-parameter-from-ordered-io-related-functions.patch +crypto-qat-fix-restarting-state-leak-on-allocation-failure.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.18/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch b/queue-6.18/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch new file mode 100644 index 0000000000..0d391e22de --- /dev/null +++ b/queue-6.18/staging-rtl8723bs-core-move-constants-to-right-side-in-comparison.patch @@ -0,0 +1,56 @@ +From stable+bounces-274617-greg=kroah.com@vger.kernel.org Wed Jul 15 02:07:44 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:07:34 -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: <20260715000736.3730201-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 +@@ -1495,7 +1495,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.18/staging-rtl8723bs-fix-oob-reads-in-rtw_get_sec_ie-rtw_get_wapi_ie-and-rtw_get_wps_attr.patch b/queue-6.18/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..638c7d60cf --- /dev/null +++ b/queue-6.18/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-274619-greg=kroah.com@vger.kernel.org Wed Jul 15 02:07:49 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:07:36 -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: <20260715000736.3730201-3-sashal@kernel.org> + +From: Alexandru Hossu + +[ Upstream commit 1463ca3ec6601cbb097d8d87dbf5dcf1cb86a344 ] + +Three IE/attribute parsing functions have missing bounds checks. + +rtw_get_sec_ie() and rtw_get_wapi_ie() iterate over a raw IE buffer +without verifying that the header bytes (tag + length) are within the +remaining buffer before reading them. Additionally, rtw_get_sec_ie() +compares the 4-byte WPA OUI at cnt+2 without checking that at least +6 bytes remain, and rtw_get_wapi_ie() compares a 4-byte WAPI OUI at +cnt+6 without checking that at least 10 bytes remain. + +rtw_get_wps_attr() reads wps_ie[0] and wps_ie+2 unconditionally at +entry, before verifying that wps_ielen is large enough to contain +the 6-byte WPS IE header (element_id + length + 4-byte OUI). Inside +the attribute loop, get_unaligned_be16() is called on attr_ptr and +attr_ptr+2 without checking that 4 bytes remain in the buffer. + +Add a cnt+2 bounds check before each loop body in rtw_get_sec_ie() +and rtw_get_wapi_ie(), guard each multi-byte comparison with a minimum +IE length requirement, add a wps_ielen < 6 early return in +rtw_get_wps_attr(), and add a 4-byte bounds check in its inner loop. + +Fixes: 554c0a3abf21 ("staging: Add rtl8723bs sdio wifi driver") +Cc: stable +Signed-off-by: Alexandru Hossu +Link: https://patch.msgid.link/20260522004531.1038924-8-hossu.alexandru@gmail.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c ++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +@@ -585,9 +585,14 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l + cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_); + + while (cnt < in_len) { ++ if (cnt + 2 > in_len) ++ break; ++ if (cnt + 2 + in_ie[cnt + 1] > in_len) ++ break; + authmode = in_ie[cnt]; + + if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && ++ in_ie[cnt + 1] >= 8 && + (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) || + !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) { + if (wapi_ie) +@@ -620,9 +625,14 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l + cnt = (_TIMESTAMP_ + _BEACON_ITERVAL_ + _CAPABILITY_); + + while (cnt < in_len) { ++ if (cnt + 2 > in_len) ++ break; ++ if (cnt + 2 + in_ie[cnt + 1] > in_len) ++ break; + authmode = in_ie[cnt]; + + if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && ++ in_ie[cnt + 1] >= 4 && + (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) { + if (wpa_ie) + memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); +@@ -707,6 +717,9 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp + if (len_attr) + *len_attr = 0; + ++ if (wps_ielen < 6) ++ return attr_ptr; ++ + if ((wps_ie[0] != WLAN_EID_VENDOR_SPECIFIC) || + (memcmp(wps_ie + 2, wps_oui, 4))) { + return attr_ptr; +@@ -717,6 +730,8 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wp + + while (attr_ptr - wps_ie < wps_ielen) { + /* 4 = 2(Attribute ID) + 2(Length) */ ++ if (attr_ptr + 4 > wps_ie + wps_ielen) ++ break; + u16 attr_id = get_unaligned_be16(attr_ptr); + u16 attr_data_len = get_unaligned_be16(attr_ptr + 2); + u16 attr_len = attr_data_len + 4; diff --git a/queue-6.18/staging-rtl8723bs-fix-spaces-around-binary-operators.patch b/queue-6.18/staging-rtl8723bs-fix-spaces-around-binary-operators.patch new file mode 100644 index 0000000000..bc8a8b6782 --- /dev/null +++ b/queue-6.18/staging-rtl8723bs-fix-spaces-around-binary-operators.patch @@ -0,0 +1,206 @@ +From stable+bounces-274618-greg=kroah.com@vger.kernel.org Wed Jul 15 02:07:47 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 20:07:35 -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: <20260715000736.3730201-2-sashal@kernel.org> + +From: Nikolay Kulikov + +[ Upstream commit 0c9d1b56f9af0762a5be5118e1bb962f23784dc4 ] + +Add missing spaces and fix line length to comply with kernel coding +style. + +Signed-off-by: Nikolay Kulikov +Reviewed-by: Ethan Tidmore +Link: https://patch.msgid.link/20260221172751.52329-1-nikolayof23@gmail.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: 1463ca3ec660 ("staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 78 +++++++++++++------------ + 1 file changed, 42 insertions(+), 36 deletions(-) + +--- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c ++++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +@@ -456,10 +456,10 @@ int rtw_parse_wpa_ie(u8 *wpa_ie, int wpa + return _FAIL; + } + +- if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) || (*(wpa_ie+1) != (u8)(wpa_ie_len - 2)) || +- (memcmp(wpa_ie+2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN))) { ++ if ((*wpa_ie != WLAN_EID_VENDOR_SPECIFIC) || ++ (*(wpa_ie + 1) != (u8)(wpa_ie_len - 2)) || ++ (memcmp(wpa_ie + 2, RTW_WPA_OUI_TYPE, WPA_SELECTOR_LEN))) + return _FAIL; +- } + + pos = wpa_ie; + +@@ -519,7 +519,7 @@ int rtw_parse_wpa2_ie(u8 *rsn_ie, int rs + return _FAIL; + } + +- if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie+1) != (u8)(rsn_ie_len - 2))) ++ if ((*rsn_ie != WLAN_EID_RSN) || (*(rsn_ie + 1) != (u8)(rsn_ie_len - 2))) + return _FAIL; + + pos = rsn_ie; +@@ -587,18 +587,18 @@ int rtw_get_wapi_ie(u8 *in_ie, uint in_l + while (cnt < in_len) { + authmode = in_ie[cnt]; + +- /* if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY) */ +- if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && (!memcmp(&in_ie[cnt+6], wapi_oui1, 4) || +- !memcmp(&in_ie[cnt+6], wapi_oui2, 4))) { ++ if (authmode == WLAN_EID_BSS_AC_ACCESS_DELAY && ++ (!memcmp(&in_ie[cnt + 6], wapi_oui1, 4) || ++ !memcmp(&in_ie[cnt + 6], wapi_oui2, 4))) { + if (wapi_ie) +- memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt+1]+2); ++ memcpy(wapi_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + + if (wapi_len) +- *wapi_len = in_ie[cnt+1]+2; ++ *wapi_len = in_ie[cnt + 1] + 2; + +- cnt += in_ie[cnt+1]+2; /* get next */ ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } else { +- cnt += in_ie[cnt+1]+2; /* get next */ ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } + } + +@@ -622,9 +622,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l + while (cnt < in_len) { + authmode = in_ie[cnt]; + +- if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], &wpa_oui[0], 4))) { ++ if ((authmode == WLAN_EID_VENDOR_SPECIFIC) && ++ (!memcmp(&in_ie[cnt + 2], &wpa_oui[0], 4))) { + if (wpa_ie) +- memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt+1]+2); ++ memcpy(wpa_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + + *wpa_len = in_ie[cnt + 1] + 2; + cnt += in_ie[cnt + 1] + 2; /* get next */ +@@ -633,10 +634,10 @@ void rtw_get_sec_ie(u8 *in_ie, uint in_l + if (rsn_ie) + memcpy(rsn_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + +- *rsn_len = in_ie[cnt+1]+2; +- cnt += in_ie[cnt+1]+2; /* get next */ ++ *rsn_len = in_ie[cnt + 1] + 2; ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } else { +- cnt += in_ie[cnt+1]+2; /* get next */ ++ cnt += in_ie[cnt + 1] + 2; /* get next */ + } + } + } +@@ -668,20 +669,20 @@ u8 *rtw_get_wps_ie(u8 *in_ie, uint in_le + while (cnt < in_len) { + eid = in_ie[cnt]; + +- if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt+2], wps_oui, 4))) { ++ if ((eid == WLAN_EID_VENDOR_SPECIFIC) && (!memcmp(&in_ie[cnt + 2], wps_oui, 4))) { + wpsie_ptr = &in_ie[cnt]; + + if (wps_ie) +- memcpy(wps_ie, &in_ie[cnt], in_ie[cnt+1]+2); ++ memcpy(wps_ie, &in_ie[cnt], in_ie[cnt + 1] + 2); + + if (wps_ielen) +- *wps_ielen = in_ie[cnt+1]+2; ++ *wps_ielen = in_ie[cnt + 1] + 2; + +- cnt += in_ie[cnt+1]+2; ++ cnt += in_ie[cnt + 1] + 2; + + break; + } +- cnt += in_ie[cnt+1]+2; /* goto next */ ++ cnt += in_ie[cnt + 1] + 2; /* goto next */ + } + + return wpsie_ptr; +@@ -759,12 +760,12 @@ u8 *rtw_get_wps_attr_content(u8 *wps_ie, + + if (attr_ptr && attr_len) { + if (buf_content) +- memcpy(buf_content, attr_ptr+4, attr_len-4); ++ memcpy(buf_content, attr_ptr + 4, attr_len - 4); + + if (len_content) +- *len_content = attr_len-4; ++ *len_content = attr_len - 4; + +- return attr_ptr+4; ++ return attr_ptr + 4; + } + + return NULL; +@@ -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.18/usb-atm-ueagle-atm-remove-function-entry-exit-debug-messages.patch b/queue-6.18/usb-atm-ueagle-atm-remove-function-entry-exit-debug-messages.patch new file mode 100644 index 0000000000..e91fbf43f1 --- /dev/null +++ b/queue-6.18/usb-atm-ueagle-atm-remove-function-entry-exit-debug-messages.patch @@ -0,0 +1,337 @@ +From stable+bounces-277438-greg=kroah.com@vger.kernel.org Sun Jul 19 14:43:59 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 08:43:47 -0400 +Subject: usb: atm: ueagle-atm: remove function entry/exit debug messages +To: stable@vger.kernel.org +Cc: Mauricio Faria de Oliveira , Greg Kroah-Hartman , Stanislaw Gruszka , Sasha Levin +Message-ID: <20260719124348.3646001-4-sashal@kernel.org> + +From: Mauricio Faria de Oliveira + +[ Upstream commit 1414b2242c2e9d670c4d8b83480f13db57627ba9 ] + +Remove the driver-internal function entry/exit debug messages +in favor of existing kernel-level function tracing mechanisms. + +Suggested-by: Greg Kroah-Hartman +Link: https://lore.kernel.org/all/2026051657-scruffy-embark-45ea@gregkh/ +Signed-off-by: Mauricio Faria de Oliveira +Acked-by: Stanislaw Gruszka +Link: https://patch.msgid.link/20260520-ueagle-atm-cleanup-v1-2-010c8bc7b214@igalia.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: e2674dfbed8a ("usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/atm/ueagle-atm.c | 56 ++++--------------------------------------- + 1 file changed, 6 insertions(+), 50 deletions(-) + +--- a/drivers/usb/atm/ueagle-atm.c ++++ b/drivers/usb/atm/ueagle-atm.c +@@ -50,12 +50,6 @@ + "[ueagle-atm vdbg] " format, ##args); \ + } while (0) + +-#define uea_enters(usb_dev) \ +- uea_vdbg(usb_dev, "entering %s\n" , __func__) +- +-#define uea_leaves(usb_dev) \ +- uea_vdbg(usb_dev, "leaving %s\n" , __func__) +- + #define uea_err(usb_dev, format, args...) \ + dev_err(&(usb_dev)->dev , "[UEAGLE-ATM] " format , ##args) + +@@ -605,7 +599,6 @@ static void uea_upload_pre_firmware(cons + u32 crc = 0; + int ret, size; + +- uea_enters(usb); + if (!fw_entry) { + uea_err(usb, "firmware is not available\n"); + goto err; +@@ -669,7 +662,6 @@ err_fw_corrupted: + uea_err(usb, "firmware is corrupted\n"); + err: + release_firmware(fw_entry); +- uea_leaves(usb); + } + + /* +@@ -680,7 +672,6 @@ static int uea_load_firmware(struct usb_ + int ret; + char *fw_name = EAGLE_FIRMWARE; + +- uea_enters(usb); + uea_info(usb, "pre-firmware device, uploading firmware\n"); + + switch (ver) { +@@ -709,7 +700,6 @@ static int uea_load_firmware(struct usb_ + else + uea_info(usb, "loading firmware %s\n", fw_name); + +- uea_leaves(usb); + return ret; + } + +@@ -1136,7 +1126,6 @@ static int uea_cmv_e1(struct uea_softc * + struct cmv_e1 cmv; + int ret; + +- uea_enters(INS_TO_USBDEV(sc)); + uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Address : %c%c%c%c, " + "offset : 0x%04x, data : 0x%08x\n", + E1_FUNCTION_TYPE(function), +@@ -1163,9 +1152,8 @@ static int uea_cmv_e1(struct uea_softc * + sizeof(cmv), &cmv); + if (ret < 0) + return ret; +- ret = wait_cmv_ack(sc); +- uea_leaves(INS_TO_USBDEV(sc)); +- return ret; ++ ++ return wait_cmv_ack(sc); + } + + static int uea_cmv_e4(struct uea_softc *sc, +@@ -1174,7 +1162,6 @@ static int uea_cmv_e4(struct uea_softc * + struct cmv_e4 cmv; + int ret; + +- uea_enters(INS_TO_USBDEV(sc)); + memset(&cmv, 0, sizeof(cmv)); + + uea_vdbg(INS_TO_USBDEV(sc), "Function : %d-%d, Group : 0x%04x, " +@@ -1198,9 +1185,8 @@ static int uea_cmv_e4(struct uea_softc * + sizeof(cmv), &cmv); + if (ret < 0) + return ret; +- ret = wait_cmv_ack(sc); +- uea_leaves(INS_TO_USBDEV(sc)); +- return ret; ++ ++ return wait_cmv_ack(sc); + } + + static inline int uea_read_cmv_e1(struct uea_softc *sc, +@@ -1294,7 +1280,6 @@ static int uea_stat_e1(struct uea_softc + u32 data; + int ret; + +- uea_enters(INS_TO_USBDEV(sc)); + data = sc->stats.phy.state; + + ret = uea_read_cmv_e1(sc, E1_SA_STAT, 0, &sc->stats.phy.state); +@@ -1437,7 +1422,6 @@ static int uea_stat_e4(struct uea_softc + u32 tmp_arr[2]; + int ret; + +- uea_enters(INS_TO_USBDEV(sc)); + data = sc->stats.phy.state; + + /* XXX only need to be done before operationnal... */ +@@ -1804,7 +1788,6 @@ static int uea_start_reset(struct uea_so + u16 zero = 0; /* ;-) */ + int ret; + +- uea_enters(INS_TO_USBDEV(sc)); + uea_info(INS_TO_USBDEV(sc), "(re)booting started\n"); + + /* mask interrupt */ +@@ -1872,7 +1855,6 @@ static int uea_start_reset(struct uea_so + return ret; + + sc->reset = 0; +- uea_leaves(INS_TO_USBDEV(sc)); + return ret; + } + +@@ -1888,7 +1870,6 @@ static int uea_kthread(void *data) + int ret = -EAGAIN; + + set_freezable(); +- uea_enters(INS_TO_USBDEV(sc)); + while (!kthread_should_stop()) { + if (ret < 0 || sc->reset) + ret = uea_start_reset(sc); +@@ -1897,7 +1878,7 @@ static int uea_kthread(void *data) + if (ret != -EAGAIN) + uea_wait(sc, 0, msecs_to_jiffies(1000)); + } +- uea_leaves(INS_TO_USBDEV(sc)); ++ + return ret; + } + +@@ -1910,8 +1891,6 @@ static int load_XILINX_firmware(struct u + u8 value; + char *fw_name = FPGA930_FIRMWARE; + +- uea_enters(INS_TO_USBDEV(sc)); +- + ret = request_firmware(&fw_entry, fw_name, &sc->usb_dev->dev); + if (ret) { + uea_err(INS_TO_USBDEV(sc), "firmware %s is not available\n", +@@ -1955,7 +1934,6 @@ static int load_XILINX_firmware(struct u + err1: + release_firmware(fw_entry); + err0: +- uea_leaves(INS_TO_USBDEV(sc)); + return ret; + } + +@@ -1965,7 +1943,6 @@ static void uea_dispatch_cmv_e1(struct u + struct cmv_dsc_e1 *dsc = &sc->cmv_dsc.e1; + struct cmv_e1 *cmv = &intr->u.e1.s2.cmv; + +- uea_enters(INS_TO_USBDEV(sc)); + if (le16_to_cpu(cmv->wPreamble) != E1_PREAMBLE) + goto bad1; + +@@ -1989,7 +1966,6 @@ static void uea_dispatch_cmv_e1(struct u + if (cmv->bFunction == E1_MAKEFUNCTION(E1_ADSLDIRECTIVE, + E1_MODEMREADY)) { + wake_up_cmv_ack(sc); +- uea_leaves(INS_TO_USBDEV(sc)); + return; + } + +@@ -2003,7 +1979,6 @@ static void uea_dispatch_cmv_e1(struct u + sc->data = sc->data << 16 | sc->data >> 16; + + wake_up_cmv_ack(sc); +- uea_leaves(INS_TO_USBDEV(sc)); + return; + + bad2: +@@ -2011,14 +1986,12 @@ bad2: + "Function : %d, Subfunction : %d\n", + E1_FUNCTION_TYPE(cmv->bFunction), + E1_FUNCTION_SUBTYPE(cmv->bFunction)); +- uea_leaves(INS_TO_USBDEV(sc)); + return; + + bad1: + uea_err(INS_TO_USBDEV(sc), "invalid cmv received, " + "wPreamble %d, bDirection %d\n", + le16_to_cpu(cmv->wPreamble), cmv->bDirection); +- uea_leaves(INS_TO_USBDEV(sc)); + } + + /* The modem send us an ack. First with check if it right */ +@@ -2027,7 +2000,6 @@ static void uea_dispatch_cmv_e4(struct u + struct cmv_dsc_e4 *dsc = &sc->cmv_dsc.e4; + struct cmv_e4 *cmv = &intr->u.e4.s2.cmv; + +- uea_enters(INS_TO_USBDEV(sc)); + uea_dbg(INS_TO_USBDEV(sc), "cmv %x %x %x %x %x %x\n", + be16_to_cpu(cmv->wGroup), be16_to_cpu(cmv->wFunction), + be16_to_cpu(cmv->wOffset), be16_to_cpu(cmv->wAddress), +@@ -2039,7 +2011,6 @@ static void uea_dispatch_cmv_e4(struct u + if (be16_to_cpu(cmv->wFunction) == E4_MAKEFUNCTION(E4_ADSLDIRECTIVE, + E4_MODEMREADY, 1)) { + wake_up_cmv_ack(sc); +- uea_leaves(INS_TO_USBDEV(sc)); + return; + } + +@@ -2052,7 +2023,6 @@ static void uea_dispatch_cmv_e4(struct u + sc->data = be32_to_cpu(cmv->dwData[0]); + sc->data1 = be32_to_cpu(cmv->dwData[1]); + wake_up_cmv_ack(sc); +- uea_leaves(INS_TO_USBDEV(sc)); + return; + + bad2: +@@ -2060,7 +2030,6 @@ bad2: + "Function : %d, Subfunction : %d\n", + E4_FUNCTION_TYPE(cmv->wFunction), + E4_FUNCTION_SUBTYPE(cmv->wFunction)); +- uea_leaves(INS_TO_USBDEV(sc)); + return; + } + +@@ -2088,8 +2057,6 @@ static void uea_intr(struct urb *urb) + struct intr_pkt *intr = urb->transfer_buffer; + int status = urb->status; + +- uea_enters(INS_TO_USBDEV(sc)); +- + if (unlikely(status < 0)) { + uea_err(INS_TO_USBDEV(sc), "uea_intr() failed with %d\n", + status); +@@ -2129,8 +2096,6 @@ static int uea_boot(struct uea_softc *sc + int ret = -ENOMEM; + int size; + +- uea_enters(INS_TO_USBDEV(sc)); +- + if (UEA_CHIP_VERSION(sc) == EAGLE_IV) { + size = E4_INTR_PKT_SIZE; + sc->dispatch_cmv = uea_dispatch_cmv_e4; +@@ -2187,7 +2152,6 @@ static int uea_boot(struct uea_softc *sc + goto err2; + } + +- uea_leaves(INS_TO_USBDEV(sc)); + return 0; + + err2: +@@ -2197,7 +2161,6 @@ err1: + sc->urb_int = NULL; + kfree(intr); + err0: +- uea_leaves(INS_TO_USBDEV(sc)); + return ret; + } + +@@ -2207,7 +2170,7 @@ err0: + static void uea_stop(struct uea_softc *sc) + { + int ret; +- uea_enters(INS_TO_USBDEV(sc)); ++ + ret = kthread_stop(sc->kthread); + uea_dbg(INS_TO_USBDEV(sc), "kthread finish with status %d\n", ret); + +@@ -2221,7 +2184,6 @@ static void uea_stop(struct uea_softc *s + flush_work(&sc->task); + + release_firmware(sc->dsp_firm); +- uea_leaves(INS_TO_USBDEV(sc)); + } + + /* syfs interface */ +@@ -2494,8 +2456,6 @@ static int uea_bind(struct usbatm_data * + int ret, ifnum = intf->altsetting->desc.bInterfaceNumber; + unsigned int alt; + +- uea_enters(usb); +- + /* interface 0 is for firmware/monitoring */ + if (ifnum != UEA_INTR_IFACE_NO) + return -ENODEV; +@@ -2588,7 +2548,6 @@ static int uea_probe(struct usb_interfac + struct usb_device *usb = interface_to_usbdev(intf); + int ret; + +- uea_enters(usb); + uea_dbg(usb, "ADSL device found with vid (%#X) pid (%#X) Rev (%#X): %s\n", + le16_to_cpu(usb->descriptor.idVendor), + le16_to_cpu(usb->descriptor.idProduct), +@@ -2619,7 +2578,6 @@ static void uea_disconnect(struct usb_in + { + struct usb_device *usb = interface_to_usbdev(intf); + int ifnum = intf->altsetting->desc.bInterfaceNumber; +- uea_enters(usb); + + /* ADI930 has 2 interfaces and eagle 3 interfaces. + * Pre-firmware device has one interface +@@ -2630,8 +2588,6 @@ static void uea_disconnect(struct usb_in + mutex_unlock(&uea_mutex); + uea_info(usb, "ADSL device removed\n"); + } +- +- uea_leaves(usb); + } + + /* diff --git a/queue-6.18/usb-atm-ueagle-atm-use-dev_dbg-for-device-found-message.patch b/queue-6.18/usb-atm-ueagle-atm-use-dev_dbg-for-device-found-message.patch new file mode 100644 index 0000000000..48c4aabf6b --- /dev/null +++ b/queue-6.18/usb-atm-ueagle-atm-use-dev_dbg-for-device-found-message.patch @@ -0,0 +1,47 @@ +From stable+bounces-277437-greg=kroah.com@vger.kernel.org Sun Jul 19 14:43:58 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 08:43:46 -0400 +Subject: usb: atm: ueagle-atm: use dev_dbg() for 'device found' message +To: stable@vger.kernel.org +Cc: Mauricio Faria de Oliveira , Greg Kroah-Hartman , Stanislaw Gruszka , Sasha Levin +Message-ID: <20260719124348.3646001-3-sashal@kernel.org> + +From: Mauricio Faria de Oliveira + +[ Upstream commit 05c5075c3115010f97434c53d353d9a0d4b0e64e ] + +Convert dev_info() to dev_dbg(). + +Per 'Documentation/process/coding-style.rst': + + 13) Printing kernel messages + + ... When drivers are working properly they are quiet, + so prefer to use dev_dbg/pr_debug unless something is wrong. + +While in there, correct the verb form and add 'with' for clarity. + +Suggested-by: Greg Kroah-Hartman +Link: https://lore.kernel.org/all/2026051628-squatter-stature-c0e0@gregkh/ +Signed-off-by: Mauricio Faria de Oliveira +Acked-by: Stanislaw Gruszka +Link: https://patch.msgid.link/20260520-ueagle-atm-cleanup-v1-1-010c8bc7b214@igalia.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: e2674dfbed8a ("usb: atm: ueagle-atm: wait for pre-firmware load in .disconnect()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/atm/ueagle-atm.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/usb/atm/ueagle-atm.c ++++ b/drivers/usb/atm/ueagle-atm.c +@@ -2589,7 +2589,7 @@ static int uea_probe(struct usb_interfac + int ret; + + uea_enters(usb); +- uea_info(usb, "ADSL device founded vid (%#X) pid (%#X) Rev (%#X): %s\n", ++ uea_dbg(usb, "ADSL device found with vid (%#X) pid (%#X) Rev (%#X): %s\n", + le16_to_cpu(usb->descriptor.idVendor), + le16_to_cpu(usb->descriptor.idProduct), + le16_to_cpu(usb->descriptor.bcdDevice), diff --git a/queue-6.18/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch b/queue-6.18/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch new file mode 100644 index 0000000000..8f44c7ca4c --- /dev/null +++ b/queue-6.18/usb-atm-ueagle-atm-wait-for-pre-firmware-load-in-.disconnect.patch @@ -0,0 +1,175 @@ +From stable+bounces-277439-greg=kroah.com@vger.kernel.org Sun Jul 19 14:44:00 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 08:43:48 -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: <20260719124348.3646001-5-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 +@@ -593,7 +593,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; +@@ -662,15 +664,17 @@ err_fw_corrupted: + uea_err(usb, "firmware is corrupted\n"); + err: + release_firmware(fw_entry); ++ 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_info(usb, "pre-firmware device, uploading firmware\n"); + +@@ -693,7 +697,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); +@@ -2556,8 +2560,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_obj(*fw_done); ++ 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) { +@@ -2587,6 +2606,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); + } + } + diff --git a/queue-6.18/usb-dwc3-fix-dwc3_readl-and-dwc3_writel-calls-in-dwc3_ulpi_setup.patch b/queue-6.18/usb-dwc3-fix-dwc3_readl-and-dwc3_writel-calls-in-dwc3_ulpi_setup.patch new file mode 100644 index 0000000000..651b993a54 --- /dev/null +++ b/queue-6.18/usb-dwc3-fix-dwc3_readl-and-dwc3_writel-calls-in-dwc3_ulpi_setup.patch @@ -0,0 +1,51 @@ +From stable+bounces-277386-greg=kroah.com@vger.kernel.org Sat Jul 18 23:51:02 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 17:50:15 -0400 +Subject: usb: dwc3: fix dwc3_readl() and dwc3_writel() calls in dwc3_ulpi_setup() +To: stable@vger.kernel.org +Cc: Ben Dooks , stable , Thinh Nguyen , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260718215015.3447229-2-sashal@kernel.org> + +From: Ben Dooks + +[ Upstream commit e0f844d9d74200d311c6438a0f04270834ba5365 ] + +The dwc3_ulpi_setup() calls the register read and write calls with +dwc3->regs when both these calls take the dwc3 structure directly. + +Chnage these two calls to fix the following sparse warning, and +possibly a nasty bug in the dwc3_ulpi_setup() code: + +drivers/usb/dwc3/core.c:796:45: warning: incorrect type in argument 1 (different address spaces) +drivers/usb/dwc3/core.c:796:45: expected struct dwc3 *dwc +drivers/usb/dwc3/core.c:796:45: got void [noderef] __iomem *regs +drivers/usb/dwc3/core.c:798:40: warning: incorrect type in argument 1 (different address spaces) +drivers/usb/dwc3/core.c:798:40: expected struct dwc3 *dwc +drivers/usb/dwc3/core.c:798:40: got void [noderef] __iomem *regs + +Cc: stable +Fixes: 9accc68b1cf0 ("usb: dwc3: Add dwc pointer to dwc3_readl/writel") +Acked-by: Thinh Nguyen +Signed-off-by: Ben Dooks +Link: https://patch.msgid.link/20260703162033.2847599-1-ben.dooks@codethink.co.uk +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/dwc3/core.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/usb/dwc3/core.c ++++ b/drivers/usb/dwc3/core.c +@@ -792,9 +792,9 @@ static void dwc3_ulpi_setup(struct dwc3 + + if (dwc->enable_usb2_transceiver_delay) { + for (index = 0; index < dwc->num_usb2_ports; index++) { +- reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(index)); ++ reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(index)); + reg |= DWC3_GUSB2PHYCFG_XCVRDLY; +- dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(index), reg); ++ dwc3_writel(dwc, DWC3_GUSB2PHYCFG(index), reg); + } + } + } diff --git a/queue-6.18/usb-dwc3-support-usb3340x-ulpi-phy-high-speed-negotiation.patch b/queue-6.18/usb-dwc3-support-usb3340x-ulpi-phy-high-speed-negotiation.patch new file mode 100644 index 0000000000..3d6de20f5d --- /dev/null +++ b/queue-6.18/usb-dwc3-support-usb3340x-ulpi-phy-high-speed-negotiation.patch @@ -0,0 +1,151 @@ +From stable+bounces-277385-greg=kroah.com@vger.kernel.org Sat Jul 18 23:51:08 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 17:50:14 -0400 +Subject: usb: dwc3: Support USB3340x ULPI PHY high-speed negotiation. +To: stable@vger.kernel.org +Cc: Ingo Rohloff , Thinh Nguyen , Greg Kroah-Hartman , Sasha Levin +Message-ID: <20260718215015.3447229-1-sashal@kernel.org> + +From: Ingo Rohloff + +[ Upstream commit a28de63356575612954d4e5d5f48a2488f50e16d ] + +The Microchip USB3340x ULPI PHY requires a delay when switching to the +high-speed transmitter. See: + http://ww1.microchip.com/downloads/en/DeviceDoc/80000645A.pdf + Module 2 "Device Enumeration Failure with Link IP Systems" + +For details on the behavior and fix, refer to the AMD (formerly Xilinx) +forum post: "USB stuck in full speed mode with USB3340 ULPI PHY, ZynqMP." + +This patch uses the USB PHY Vendor-ID and Product-ID to detect the +USB3340 PHY and then applies the necessary fix if this PHY is found. + +Signed-off-by: Ingo Rohloff +Acked-by: Thinh Nguyen +Link: https://patch.msgid.link/20260305121452.54082-2-ingo.rohloff@lauterbach.com +Signed-off-by: Greg Kroah-Hartman +Stable-dep-of: e0f844d9d742 ("usb: dwc3: fix dwc3_readl() and dwc3_writel() calls in dwc3_ulpi_setup()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/dwc3/core.c | 20 ++++++++++++++++++++ + drivers/usb/dwc3/core.h | 4 ++++ + drivers/usb/dwc3/ulpi.c | 25 +++++++++++++++++++++++++ + 3 files changed, 49 insertions(+) + +--- a/drivers/usb/dwc3/core.c ++++ b/drivers/usb/dwc3/core.c +@@ -781,6 +781,24 @@ static int dwc3_hs_phy_setup(struct dwc3 + return 0; + } + ++static void dwc3_ulpi_setup(struct dwc3 *dwc) ++{ ++ int index; ++ u32 reg; ++ ++ /* Don't do anything if there is no ULPI PHY */ ++ if (!dwc->ulpi) ++ return; ++ ++ if (dwc->enable_usb2_transceiver_delay) { ++ for (index = 0; index < dwc->num_usb2_ports; index++) { ++ reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(index)); ++ reg |= DWC3_GUSB2PHYCFG_XCVRDLY; ++ dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(index), reg); ++ } ++ } ++} ++ + /** + * dwc3_phy_setup - Configure USB PHY Interface of DWC3 Core + * @dwc: Pointer to our controller context structure +@@ -1355,6 +1373,8 @@ static int dwc3_core_init(struct dwc3 *d + dwc->ulpi_ready = true; + } + ++ dwc3_ulpi_setup(dwc); ++ + if (!dwc->phys_ready) { + ret = dwc3_core_get_phy(dwc); + if (ret) +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -302,6 +302,7 @@ + #define DWC3_GUSB2PHYCFG_SUSPHY BIT(6) + #define DWC3_GUSB2PHYCFG_ULPI_UTMI BIT(4) + #define DWC3_GUSB2PHYCFG_ENBLSLPM BIT(8) ++#define DWC3_GUSB2PHYCFG_XCVRDLY BIT(9) + #define DWC3_GUSB2PHYCFG_PHYIF(n) (n << 3) + #define DWC3_GUSB2PHYCFG_PHYIF_MASK DWC3_GUSB2PHYCFG_PHYIF(1) + #define DWC3_GUSB2PHYCFG_USBTRDTIM(n) (n << 10) +@@ -1161,6 +1162,8 @@ struct dwc3_glue_ops { + * 3 - Reserved + * @dis_metastability_quirk: set to disable metastability quirk. + * @dis_split_quirk: set to disable split boundary. ++ * @enable_usb2_transceiver_delay: Set to insert a delay before the ++ * assertion of the TxValid signal during a HS Chirp. + * @sys_wakeup: set if the device may do system wakeup. + * @wakeup_configured: set if the device is configured for remote wakeup. + * @suspended: set to track suspend event due to U3/L2. +@@ -1403,6 +1406,7 @@ struct dwc3 { + unsigned dis_metastability_quirk:1; + + unsigned dis_split_quirk:1; ++ unsigned enable_usb2_transceiver_delay:1; + unsigned async_callbacks:1; + unsigned sys_wakeup:1; + unsigned wakeup_configured:1; +--- a/drivers/usb/dwc3/ulpi.c ++++ b/drivers/usb/dwc3/ulpi.c +@@ -10,10 +10,13 @@ + #include + #include + #include ++#include + + #include "core.h" + #include "io.h" + ++#define USB_VENDOR_MICROCHIP 0x0424 ++ + #define DWC3_ULPI_ADDR(a) \ + ((a >= ULPI_EXT_VENDOR_SPECIFIC) ? \ + DWC3_GUSB2PHYACC_ADDR(ULPI_ACCESS_EXTENDED) | \ +@@ -83,6 +86,26 @@ static const struct ulpi_ops dwc3_ulpi_o + .write = dwc3_ulpi_write, + }; + ++static void dwc3_ulpi_detect_config(struct dwc3 *dwc) ++{ ++ struct ulpi *ulpi = dwc->ulpi; ++ ++ switch (ulpi->id.vendor) { ++ case USB_VENDOR_MICROCHIP: ++ switch (ulpi->id.product) { ++ case 0x0009: ++ /* Microchip USB3340 ULPI PHY */ ++ dwc->enable_usb2_transceiver_delay = true; ++ break; ++ default: ++ break; ++ } ++ break; ++ default: ++ break; ++ } ++} ++ + int dwc3_ulpi_init(struct dwc3 *dwc) + { + /* Register the interface */ +@@ -92,6 +115,8 @@ int dwc3_ulpi_init(struct dwc3 *dwc) + return PTR_ERR(dwc->ulpi); + } + ++ dwc3_ulpi_detect_config(dwc); ++ + return 0; + } + diff --git a/queue-6.18/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch b/queue-6.18/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch new file mode 100644 index 0000000000..c1e6e71838 --- /dev/null +++ b/queue-6.18/usb-gadget-f_fs-initialize-reset_work-at-allocation-time.patch @@ -0,0 +1,101 @@ +From stable+bounces-277546-greg=kroah.com@vger.kernel.org Mon Jul 20 04:36:24 2026 +From: Sasha Levin +Date: Sun, 19 Jul 2026 22:25:51 -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: <20260720023553.2928009-6-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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/gadget/function/f_fs.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -289,6 +289,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 ****************************************************/ + +@@ -2206,6 +2207,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; +@@ -3753,7 +3755,6 @@ static int ffs_func_set_alt(struct usb_f + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + spin_unlock_irqrestore(&ffs->eps_lock, flags); +- INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return -ENODEV; + } +@@ -3784,7 +3785,6 @@ static void ffs_func_disable(struct usb_ + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + spin_unlock_irqrestore(&ffs->eps_lock, flags); +- INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return; + } diff --git a/queue-6.18/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch b/queue-6.18/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch new file mode 100644 index 0000000000..c61e9c05c3 --- /dev/null +++ b/queue-6.18/usb-gadget-f_fs-tie-read_buffer-lifetime-to-ffs_epfile.patch @@ -0,0 +1,54 @@ +From sashal@kernel.org Mon Jul 20 12:54:57 2026 +From: Sasha Levin +Date: Mon, 20 Jul 2026 06:54:48 -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: <20260720105452.216179-3-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 +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 +@@ -1353,7 +1353,6 @@ ffs_epfile_release(struct inode *inode, + + mutex_unlock(&epfile->dmabufs_mutex); + +- __ffs_epfile_read_buffer_free(epfile); + ffs_data_closed(epfile->ffs); + + return 0; +@@ -2370,6 +2369,7 @@ static void ffs_epfiles_destroy(struct s + + for (; count; --count, ++epfile) { + BUG_ON(mutex_is_locked(&epfile->mutex)); ++ __ffs_epfile_read_buffer_free(epfile); + simple_remove_by_name(root, epfile->name, NULL); + } + diff --git a/queue-6.18/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch b/queue-6.18/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch new file mode 100644 index 0000000000..9a3744f3e0 --- /dev/null +++ b/queue-6.18/vfio-mlx5-fix-racy-bitfields-and-tighten-struct-layout.patch @@ -0,0 +1,95 @@ +From stable+bounces-274246-greg=kroah.com@vger.kernel.org Tue Jul 14 15:32:39 2026 +From: Sasha Levin +Date: Tue, 14 Jul 2026 09:27:08 -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: <20260714132708.2663976-1-sashal@kernel.org> + +From: Alex Williamson + +[ Upstream commit f2365a63b02ddea32e7db78b742c2503ec7b81f1 ] + +Bitfield operations are not atomic, they use a read-modify-write +pattern, therefore we should be careful not to pack bitfields that +can be concurrently updated into the same storage unit. + +This split takes a binary approach: flags that are only modified +pre/post open/close remain bitfields, flags modified from user +action, including actions that reach across to another device (ex. +reset) use dedicated storage units. + +Note mlx5_vhca_page_tracker.status is relocated to fill the alignment +hole this split exposes. + +Bitfield justifications: + + migrate_cap: written only in mlx5vf_cmd_set_migratable() at probe + chunk_mode: written only in mlx5vf_cmd_set_migratable() at probe + mig_state_cap: written only in mlx5vf_cmd_set_migratable() at probe + +Dedicated storage units: + + mdev_detach: written in the VF attach/detach event notifier + mlx5fv_vf_event() at runtime + log_active: written in mlx5vf_start_page_tracker()/ + mlx5vf_stop_page_tracker() during runtime dirty tracking + deferred_reset: written in mlx5vf_state_mutex_unlock()/ + mlx5vf_pci_aer_reset_done() during runtime reset handling + is_err: set by tracker error handling and dirty-log polling at runtime + object_changed: set by tracker event handling and cleared by dirty-log + polling at runtime + +Fixes: 61a2f1460fd0 ("vfio/mlx5: Manage the VF attach/detach callback from the PF") +Fixes: 79c3cf279926 ("vfio/mlx5: Init QP based resources for dirty tracking") +Fixes: f886473071d6 ("vfio/mlx5: Add support for tracker object change event") +Cc: Yishai Hadas +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Alex Williamson +Reviewed-by: Kevin Tian +Link: https://lore.kernel.org/r/20260615191241.688297-5-alex.williamson@nvidia.com +Signed-off-by: Alex Williamson +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + drivers/vfio/pci/mlx5/cmd.h | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +--- a/drivers/vfio/pci/mlx5/cmd.h ++++ b/drivers/vfio/pci/mlx5/cmd.h +@@ -155,25 +155,28 @@ struct mlx5_vhca_qp { + struct mlx5_vhca_page_tracker { + u32 id; + u32 pdn; +- u8 is_err:1; +- u8 object_changed:1; ++ /* Flags modified at runtime - dedicated storage unit */ ++ u8 is_err; ++ u8 object_changed; ++ int status; + struct mlx5_uars_page *uar; + struct mlx5_vhca_cq cq; + struct mlx5_vhca_qp *host_qp; + struct mlx5_vhca_qp *fw_qp; + struct mlx5_nb nb; +- int status; + }; + + struct mlx5vf_pci_core_device { + struct vfio_pci_core_device core_device; + int vf_id; + u16 vhca_id; ++ /* Flags only modified on setup/release - bitfield ok */ + u8 migrate_cap:1; +- u8 deferred_reset:1; +- u8 mdev_detach:1; +- u8 log_active:1; + u8 chunk_mode:1; ++ /* Flags modified at runtime - dedicated storage unit */ ++ u8 mdev_detach; ++ u8 log_active; ++ u8 deferred_reset; + struct completion tracker_comp; + /* protect migration state */ + struct mutex state_mutex; diff --git a/queue-6.18/xfs-add-a-xfs_groups_to_rfsbs-helper.patch b/queue-6.18/xfs-add-a-xfs_groups_to_rfsbs-helper.patch new file mode 100644 index 0000000000..28a80579c3 --- /dev/null +++ b/queue-6.18/xfs-add-a-xfs_groups_to_rfsbs-helper.patch @@ -0,0 +1,91 @@ +From stable+bounces-277346-greg=kroah.com@vger.kernel.org Sat Jul 18 17:33:33 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 11:32:56 -0400 +Subject: xfs: add a xfs_groups_to_rfsbs helper +To: stable@vger.kernel.org +Cc: Christoph Hellwig , "Darrick J. Wong" , Carlos Maiolino , Sasha Levin +Message-ID: <20260718153257.3311310-1-sashal@kernel.org> + +From: Christoph Hellwig + +[ Upstream commit 0ec73eb3f12350799c4b3fb764225f6e38b42d1e ] + +Plus a rtgroup wrapper and use that to avoid overflows when converting +zone/rtg counts to block counts. + +Signed-off-by: Christoph Hellwig +Reviewed-by: Darrick J. Wong +Signed-off-by: Carlos Maiolino +Stable-dep-of: 44cccefe6574 ("xfs: only log freed extents for the current RTG in zoned growfs") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/xfs/libxfs/xfs_group.h | 9 +++++++++ + fs/xfs/libxfs/xfs_rtgroup.h | 8 ++++++++ + fs/xfs/xfs_zone_gc.c | 3 +-- + fs/xfs/xfs_zone_space_resv.c | 8 +++----- + 4 files changed, 21 insertions(+), 7 deletions(-) + +--- a/fs/xfs/libxfs/xfs_group.h ++++ b/fs/xfs/libxfs/xfs_group.h +@@ -98,6 +98,15 @@ xfs_group_max_blocks( + return xg->xg_mount->m_groups[xg->xg_type].blocks; + } + ++static inline xfs_rfsblock_t ++xfs_groups_to_rfsbs( ++ struct xfs_mount *mp, ++ uint32_t nr_groups, ++ enum xfs_group_type type) ++{ ++ return (xfs_rfsblock_t)mp->m_groups[type].blocks * nr_groups; ++} ++ + static inline xfs_fsblock_t + xfs_group_start_fsb( + struct xfs_group *xg) +--- a/fs/xfs/libxfs/xfs_rtgroup.h ++++ b/fs/xfs/libxfs/xfs_rtgroup.h +@@ -371,4 +371,12 @@ static inline int xfs_initialize_rtgroup + # define xfs_rtgroup_get_geometry(rtg, rgeo) (-EOPNOTSUPP) + #endif /* CONFIG_XFS_RT */ + ++static inline xfs_rfsblock_t ++xfs_rtgs_to_rfsbs( ++ struct xfs_mount *mp, ++ uint32_t nr_groups) ++{ ++ return xfs_groups_to_rfsbs(mp, nr_groups, XG_TYPE_RTG); ++} ++ + #endif /* __LIBXFS_RTGROUP_H */ +--- a/fs/xfs/xfs_zone_gc.c ++++ b/fs/xfs/xfs_zone_gc.c +@@ -181,8 +181,7 @@ xfs_zoned_need_gc( + available = xfs_estimate_freecounter(mp, XC_FREE_RTAVAILABLE); + + if (available < +- mp->m_groups[XG_TYPE_RTG].blocks * +- (mp->m_max_open_zones - XFS_OPEN_GC_ZONES)) ++ xfs_rtgs_to_rfsbs(mp, mp->m_max_open_zones - XFS_OPEN_GC_ZONES)) + return true; + + free = xfs_estimate_freecounter(mp, XC_FREE_RTEXTENTS); +--- a/fs/xfs/xfs_zone_space_resv.c ++++ b/fs/xfs/xfs_zone_space_resv.c +@@ -54,12 +54,10 @@ xfs_zoned_default_resblks( + { + switch (ctr) { + case XC_FREE_RTEXTENTS: +- return (uint64_t)XFS_RESERVED_ZONES * +- mp->m_groups[XG_TYPE_RTG].blocks + +- mp->m_sb.sb_rtreserved; ++ return xfs_rtgs_to_rfsbs(mp, XFS_RESERVED_ZONES) + ++ mp->m_sb.sb_rtreserved; + case XC_FREE_RTAVAILABLE: +- return (uint64_t)XFS_GC_ZONES * +- mp->m_groups[XG_TYPE_RTG].blocks; ++ return xfs_rtgs_to_rfsbs(mp, XFS_GC_ZONES); + default: + ASSERT(0); + return 0; diff --git a/queue-6.18/xfs-initialize-iomap-flags-earlier-in-xfs_bmbt_to_iomap.patch b/queue-6.18/xfs-initialize-iomap-flags-earlier-in-xfs_bmbt_to_iomap.patch new file mode 100644 index 0000000000..3c4490e364 --- /dev/null +++ b/queue-6.18/xfs-initialize-iomap-flags-earlier-in-xfs_bmbt_to_iomap.patch @@ -0,0 +1,45 @@ +From stable+bounces-277289-greg=kroah.com@vger.kernel.org Sat Jul 18 13:20:42 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 07:20:31 -0400 +Subject: xfs: initialize iomap->flags earlier in xfs_bmbt_to_iomap +To: stable@vger.kernel.org +Cc: Christoph Hellwig , "Darrick J. Wong" , Carlos Maiolino , Sasha Levin +Message-ID: <20260718112031.2956124-1-sashal@kernel.org> + +From: Christoph Hellwig + +[ Upstream commit 327e58826eb72f8bae9419cf1a4e722b57c85694 ] + +Otherwise we lose the IOMAP_IOEND_BOUNDARY assingment for writes to the +first block in a realtime group, and could cause incorrect merges for +such writes. + +Fixes: b91afef72471 ("xfs: don't merge ioends across RTGs") +Cc: # v6.13 +Signed-off-by: Christoph Hellwig +Reviewed-by: "Darrick J. Wong" +Signed-off-by: Carlos Maiolino +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/xfs/xfs_iomap.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/xfs/xfs_iomap.c ++++ b/fs/xfs/xfs_iomap.c +@@ -113,6 +113,7 @@ xfs_bmbt_to_iomap( + return xfs_alert_fsblock_zero(ip, imap); + } + ++ iomap->flags = iomap_flags; + if (imap->br_startblock == HOLESTARTBLOCK) { + iomap->addr = IOMAP_NULL_ADDR; + iomap->type = IOMAP_HOLE; +@@ -147,7 +148,6 @@ xfs_bmbt_to_iomap( + iomap->dax_dev = target->bt_daxdev; + else + iomap->bdev = target->bt_bdev; +- iomap->flags = iomap_flags; + + /* + * If the inode is dirty for datasync purposes, let iomap know so it diff --git a/queue-6.18/xfs-only-log-freed-extents-for-the-current-rtg-in-zoned-growfs.patch b/queue-6.18/xfs-only-log-freed-extents-for-the-current-rtg-in-zoned-growfs.patch new file mode 100644 index 0000000000..e7f981e16e --- /dev/null +++ b/queue-6.18/xfs-only-log-freed-extents-for-the-current-rtg-in-zoned-growfs.patch @@ -0,0 +1,62 @@ +From stable+bounces-277347-greg=kroah.com@vger.kernel.org Sat Jul 18 17:33:56 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 11:32:57 -0400 +Subject: xfs: only log freed extents for the current RTG in zoned growfs +To: stable@vger.kernel.org +Cc: Christoph Hellwig , "Darrick J. Wong" , Damien Le Moal , Carlos Maiolino , Sasha Levin +Message-ID: <20260718153257.3311310-2-sashal@kernel.org> + +From: Christoph Hellwig + +[ Upstream commit 44cccefe65749821d9a13523c8b763bf1262ef73 ] + +Otherwise a power fail or crash during growfs could lead to an +elevated sb_rblocks counter. + +Note that the step function is much simpler compared to the classic RT +allocator as zoned RT sections must be aligned to real time group +boundaries. + +Fixes: 01b71e64bb87 ("xfs: support growfs on zoned file systems") +Cc: # v6.15 +Signed-off-by: Christoph Hellwig +Reviewed-by: "Darrick J. Wong" +Reviewed-by: Damien Le Moal +Signed-off-by: Carlos Maiolino +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/xfs/xfs_rtalloc.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/fs/xfs/xfs_rtalloc.c ++++ b/fs/xfs/xfs_rtalloc.c +@@ -887,8 +887,7 @@ xfs_growfs_rt_sb_fields( + + static int + xfs_growfs_rt_zoned( +- struct xfs_rtgroup *rtg, +- xfs_rfsblock_t nrblocks) ++ struct xfs_rtgroup *rtg) + { + struct xfs_mount *mp = rtg_mount(rtg); + struct xfs_mount *nmp; +@@ -900,7 +899,8 @@ xfs_growfs_rt_zoned( + * Calculate new sb and mount fields for this round. Also ensure the + * rtg_extents value is uptodate as the rtbitmap code relies on it. + */ +- nmp = xfs_growfs_rt_alloc_fake_mount(mp, nrblocks, ++ nmp = xfs_growfs_rt_alloc_fake_mount(mp, ++ xfs_rtgs_to_rfsbs(mp, rtg_rgno(rtg) + 1), + mp->m_sb.sb_rextsize); + if (!nmp) + return -ENOMEM; +@@ -1189,7 +1189,7 @@ xfs_growfs_rtg( + } + + if (xfs_has_zoned(mp)) { +- error = xfs_growfs_rt_zoned(rtg, nrblocks); ++ error = xfs_growfs_rt_zoned(rtg); + goto out_rele; + } + diff --git a/queue-6.18/xfs-use-bio_reuse-in-the-zone-gc-code.patch b/queue-6.18/xfs-use-bio_reuse-in-the-zone-gc-code.patch new file mode 100644 index 0000000000..b5efc449a5 --- /dev/null +++ b/queue-6.18/xfs-use-bio_reuse-in-the-zone-gc-code.patch @@ -0,0 +1,57 @@ +From stable+bounces-277349-greg=kroah.com@vger.kernel.org Sat Jul 18 17:48:19 2026 +From: Sasha Levin +Date: Sat, 18 Jul 2026 11:47:52 -0400 +Subject: xfs: use bio_reuse in the zone GC code +To: stable@vger.kernel.org +Cc: Christoph Hellwig , Hans Holmberg , Carlos Maiolino , Damien Le Moal , Carlos Maiolino , Sasha Levin +Message-ID: <20260718154759.3315619-1-sashal@kernel.org> + +From: Christoph Hellwig + +[ Upstream commit 0506d32f7c52e41f6e8db7c337e0ce6374c6ffbb ] + +Replace our somewhat fragile code to reuse the bio, which caused a +regression in the past with the block layer bio_reuse helper. + +Signed-off-by: Christoph Hellwig +Reviewed-by: Hans Holmberg +Reviewed-by: Carlos Maiolino +Reviewed-by: Damien Le Moal +Signed-off-by: Carlos Maiolino +[ sashal: open-coded the bio_reuse logic at the call site as 6.18 lacks + commit 7ca44303f9f6 ("block: add a bio_reuse helper") ] +Stable-dep-of: ae3692c7f440 ("xfs: add newly added RTGs to the free pool in growfs") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + fs/xfs/xfs_zone_gc.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +--- a/fs/xfs/xfs_zone_gc.c ++++ b/fs/xfs/xfs_zone_gc.c +@@ -812,9 +812,8 @@ xfs_zone_gc_write_chunk( + { + struct xfs_zone_gc_data *data = chunk->data; + struct xfs_mount *mp = chunk->ip->i_mount; +- phys_addr_t bvec_paddr = +- bvec_phys(bio_first_bvec_all(&chunk->bio)); + struct xfs_gc_bio *split_chunk; ++ unsigned short vcnt, i; + + if (chunk->bio.bi_status) + xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR); +@@ -826,10 +825,11 @@ xfs_zone_gc_write_chunk( + WRITE_ONCE(chunk->state, XFS_GC_BIO_NEW); + list_move_tail(&chunk->entry, &data->writing); + ++ vcnt = chunk->bio.bi_vcnt; + bio_reset(&chunk->bio, mp->m_rtdev_targp->bt_bdev, REQ_OP_WRITE); +- bio_add_folio_nofail(&chunk->bio, chunk->scratch->folio, chunk->len, +- offset_in_folio(chunk->scratch->folio, bvec_paddr)); +- ++ for (i = 0; i < vcnt; i++) ++ chunk->bio.bi_iter.bi_size += chunk->bio.bi_io_vec[i].bv_len; ++ chunk->bio.bi_vcnt = vcnt; + while ((split_chunk = xfs_zone_gc_split_write(data, chunk))) + xfs_zone_gc_submit_write(data, split_chunk); + xfs_zone_gc_submit_write(data, chunk);